[
  {
    "path": ".gitignore",
    "content": "*-ide-plugin.xml\r\n*.class\r\n*.ear\r\n*.ec\r\n*.egg-info\r\n*.iml\r\n*.ipr\r\n*.iws\r\n*.MainThread-*\r\n*.pid\r\n*.pid.lock\r\n*.pyc\r\n*.sqlite\r\n*.swp\r\n*.war\r\n*.bak\r\n*class\r\n*.log\r\n*~\r\n.classpath\r\n.DS_Store\r\n.gradle\r\n.idea\r\n.idea/\r\n.idea/*\r\n.installed.cfg\r\n.project\r\n.settings\r\n.settings/\r\n.svn\r\n.svn/*\r\n/bin\r\n/bin/\r\nbin\r\nbuild\r\ndevelop-eggs\r\ndist\r\ndist/*\r\ndocs/\r\neggs\r\nGemfile.lock\r\ngit.properties\r\nivy*jar\r\nlogs\r\nparts\r\nreports\r\nreports/*\r\ntarget/\r\ntest/.coverage\r\ntest/coverage.xml\r\ntest/nosetests.xml\r\ntest-output/\r\ntmp\r\ntmp/*\r\n\r\n### JetBrains template\r\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm\r\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\r\n\r\n# User-specific stuff:\r\n.idea/workspace.xml\r\n.idea/tasks.xml\r\n.idea/dictionaries\r\n.idea/vcs.xml\r\n.idea/jsLibraryMappings.xml\r\n\r\n# Sensitive or high-churn files:\r\n.idea/dataSources.ids\r\n.idea/dataSources.xml\r\n.idea/dataSources.local.xml\r\n.idea/sqlDataSources.xml\r\n.idea/dynamic.xml\r\n.idea/uiDesigner.xml\r\n\r\n# Gradle:\r\n.idea/gradle.xml\r\n.idea/libraries\r\n\r\n# Mongo Explorer plugin:\r\n.idea/mongoSettings.xml\r\n\r\n## File-based project format:\r\n*.iws\r\n\r\n## Plugin-specific files:\r\n\r\n# IntelliJ\r\n/out/\r\n\r\n# mpeltonen/sbt-idea plugin\r\n.idea_modules/"
  },
  {
    "path": "LICENSE",
    "content": "GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    {one line to give the program's name and a brief idea of what it does.}\n    Copyright (C) {year}  {name of author}\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    {project}  Copyright (C) {year}  {fullname}\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "trolCommander\n=============\n\nFork of muCommander file manager\n\nSee a full description at https://trolsoft.ru/en/soft/trolcommander/\n\nHow to build\n============\n\nBuild jar file:\n`gradlew clean build`\n\nNightly builds\n==============\n\nLatest jar file: https://trolsoft.ru/content/soft/trolcommander/nightly/trolcommander.jar\n\nLatest MacOS dmg: https://trolsoft.ru/content/soft/trolcommander/nightly/trolCommander.dmg\n"
  },
  {
    "path": "build.gradle",
    "content": "plugins {\n    id 'java'\n    id 'application'\n    id \"idea\"\n    id 'com.gradleup.shadow' version '9.2.0'\n    id 'org.jetbrains.kotlin.jvm' version '2.3.20'\n    id 'com.github.ben-manes.versions' version '0.53.0'\n    id 'org.jetbrains.kotlin.plugin.lombok' version '2.3.20'\n}\n\ngroup = 'ru.trolsoft'\nversion = '1.0.0'\n\njava {\n    sourceCompatibility = JavaVersion.VERSION_21\n    targetCompatibility = JavaVersion.VERSION_21\n}\n\ndef opensJvmApps = [\n    '--add-opens', 'java.base/java.io=ALL-UNNAMED',\n    '--add-opens', 'java.desktop/sun.awt.X11=ALL-UNNAMED',\n    '--add-opens', 'java.base/java.net=ALL-UNNAMED',\n    '--add-opens', 'java.desktop/javax.swing.plaf.basic=ALL-UNNAMED',\n    '--add-opens', 'java.desktop/sun.awt.X11=ALL-UNNAMED',\n    '--add-opens', 'java.transaction.xa/javax.transaction.xa=ALL-UNNAMED',\n    '--add-opens', 'java.management/javax.management=ALL-UNNAMED',\n    '--add-opens', 'java.rmi/java.rmi=ALL-UNNAMED',\n    '--add-opens', 'java.security.jgss/org.ietf.jgss=ALL-UNNAMED',\n    '--add-opens', 'java.sql/java.sql=ALL-UNNAMED',\n    '--add-opens', 'java.base/sun.net.www.protocol.http=ALL-UNNAMED',\n    '--add-opens', 'java.base/sun.net.www.protocol.https=ALL-UNNAMED',\n    '--add-opens', 'jdk.httpserver/com.sun.net.httpserver=ALL-UNNAMED',\n    '--add-opens', 'java.compiler/javax.lang.model.element=ALL-UNNAMED'\n]\n\ndef macosJvmArgs = [\n    '--add-opens', 'java.desktop/com.apple.laf=ALL-UNNAMED',\n    '--add-opens', 'java.desktop/com.apple.laf.AquaLookAndFeel=ALL-UNNAMED',\n    '--add-exports', 'java.desktop/com.apple.laf=ALL-UNNAMED',\n    '--add-exports', 'java.desktop/com.apple.eawt=ALL-UNNAMED',\n    '--add-opens', 'java.desktop/com.apple.eawt=ALL-UNNAMED',\n    '-Dcom.apple.smallTabs=true',\n    '-Dcom.apple.hwaccel',\n    '-Dapple.laf.useScreenMenuBar=true',\n    '-Xdock:name=trolCommander'\n]\n\ndef isMac = System.getProperty('os.name').toLowerCase().contains('mac')\n\napplication {\n    mainClass = 'com.mucommander.TrolCommander'\n    applicationDefaultJvmArgs = [\n        '-Xmx128m',\n        '-Xms128m',\n        '-XX:ReservedCodeCacheSize=64m',\n        '-XX:+IgnoreUnrecognizedVMOptions',\n        '-Djava.system.class.loader=com.mucommander.commons.file.AbstractFileClassLoader',\n        '-Dslf4j.provider=ch.qos.logback.classic.spi.LogbackServiceProvider',\n        '-Dfile.encoding=UTF-8'\n    ] + opensJvmApps + (isMac ? macosJvmArgs : [])\n}\n\nrepositories {\n    mavenCentral()\n    \n    flatDir {\n        dirs 'lib/runtime', 'lib/compile', 'lib/test'\n    }\n}\n\n\ndependencies {\n    compileOnly 'org.projectlombok:lombok:1.18.36'\n    annotationProcessor 'org.projectlombok:lombok:1.18.36'\n\n    testCompileOnly 'org.projectlombok:lombok:1.18.36'\n    testAnnotationProcessor 'org.projectlombok:lombok:1.18.36'\n\n    implementation name: 'apple/AppleJavaExtensions-1.6'\n\n    implementation name: 'jediterm/jediterm-ui-3.66-SNAPSHOT'\n    implementation name: 'jediterm/jediterm-core-3.66-SNAPSHOT'\n    implementation name: 'jediterm/JediTerm-3.66-SNAPSHOT'\n\n    implementation name: 'jediterm/pty4j-0.13.4'\n\n    implementation name: 'jediterm/annotations'\n    implementation name: 'jediterm/jzlib-1.1.1'\n    implementation name: 'jediterm/purejavacomm'\n    \n    implementation name: 'jets3t/jets3t-0.7.2'\n    implementation name: 'vmware/vim25-2.5'\n\n    implementation name: 'quaqua/quaqua-native'\n    implementation name: 'quaqua/quaqua'\n\n    implementation name: 'jets3t/jets3t-0.7.2'\n    implementation name: 'jcifs/jcifs-1.3.18-kohsuke-2-SNAPSHOT'\n    implementation name: 'java-iso-tools/iso9660-writer-2.0.0'\n    implementation name: 'java-iso-tools/sabre-2.0.0'\n\n    implementation name: 'image4j'\n    implementation name: 'javadjvu'\n    implementation name: 'jftp-1.60-trol1'\n    implementation name: 'jide-oss-3.7.4'\n    implementation name: 'rsyntaxtextarea-3.6.3-SNAPSHOT'\n    implementation name: 'sardine-5.3'\n    implementation name: 'sevenzipjbinding-AllPlatforms'\n    implementation name: 'sevenzipjbinding-Mac-arm64'\n    implementation name: 'sevenzipjbinding'\n    implementation name: 'trolcommander-native'\n    implementation name: 'trolsoft'\n    implementation name: 'VAqua13'\n\n    // lib.compile\n    compileOnly name: 'jfxrt'\n\n    if (!isMac) {\n        compileOnly name: 'apple/ui'\n    }\n\n\n    implementation 'org.apache.xmlgraphics:batik-anim:1.19'\n    implementation 'org.apache.xmlgraphics:batik-awt-util:1.19'\n    implementation 'org.apache.xmlgraphics:batik-bridge:1.19'\n    implementation 'org.apache.xmlgraphics:batik-codec:1.19'\n    implementation 'org.apache.xmlgraphics:batik-css:1.19'\n    implementation 'org.apache.xmlgraphics:batik-dom:1.19'\n    implementation 'org.apache.xmlgraphics:batik-ext:1.19'\n    implementation 'org.apache.xmlgraphics:batik-extension:1.19'\n    implementation 'org.apache.xmlgraphics:batik-gui-util:1.19'\n    implementation 'org.apache.xmlgraphics:batik-gvt:1.19'\n    implementation 'org.apache.xmlgraphics:batik-parser:1.19'\n    implementation 'org.apache.xmlgraphics:batik-script:1.19'\n    implementation 'org.apache.xmlgraphics:batik-svg-dom:1.19'\n    implementation 'org.apache.xmlgraphics:batik-svggen:1.19'\n    implementation 'org.apache.xmlgraphics:batik-swing:1.19'\n    implementation 'org.apache.xmlgraphics:batik-transcoder:1.19'\n    implementation \"org.apache.xmlgraphics:batik-util:1.19\"\n    implementation \"org.apache.xmlgraphics:batik-xml:1.19\"\n\n    implementation 'xml-apis:xml-apis:2.0.2'\n    implementation 'org.apache.xmlgraphics:xmlgraphics-commons:2.11'\n\n    implementation 'commons-net:commons-net:3.9.0'\n    implementation 'commons-collections:commons-collections:3.2.2'\n    implementation 'commons-logging:commons-logging:1.2'\n    implementation 'commons-io:commons-io:2.20.0'\n    implementation 'commons-collections:commons-collections:3.2.2'\n    implementation 'org.apache.commons:commons-collections4:4.5.0'\n    implementation 'commons-beanutils:commons-beanutils:1.9.4'\n    implementation 'org.apache.commons:commons-imaging:1.0-alpha2'\n\n    implementation 'org.apache.hadoop:hadoop-common:2.6.0'\n    implementation 'xerces:xercesImpl:2.12.2'\n\n    implementation 'ch.qos.logback:logback-classic:1.5.32'\n    implementation 'ch.qos.logback:logback-core:1.5.32'\n    implementation 'org.slf4j:slf4j-api:2.0.9'\n\n    implementation 'com.formdev:flatlaf:3.7.1'\n\n    // JNA\n//    implementation 'net.java.dev.jna:jna:5.13.0'\n\n    implementation 'oro:oro:2.0.8'\n\n    implementation 'org.yaml:snakeyaml:2.4'\n\n    implementation 'com.twelvemonkeys.imageio:imageio-core:3.13.1'\n    implementation 'com.twelvemonkeys.imageio:imageio-jpeg:3.13.1'\n    implementation 'com.twelvemonkeys.imageio:imageio-tiff:3.13.1'\n    implementation 'com.twelvemonkeys.imageio:imageio-psd:3.13.1'\n    implementation 'com.twelvemonkeys.imageio:imageio-webp:3.13.1'\n    implementation 'com.twelvemonkeys.imageio:imageio-bmp:3.13.1'\n    implementation 'com.twelvemonkeys.imageio:imageio-dds:3.13.1'\n    implementation 'com.twelvemonkeys.imageio:imageio-hdr:3.13.1'\n    implementation 'com.twelvemonkeys.imageio:imageio-icns:3.13.1'\n    implementation 'com.twelvemonkeys.imageio:imageio-iff:3.13.1'\n    implementation 'com.twelvemonkeys.imageio:imageio-pcx:3.13.1'\n    implementation 'com.twelvemonkeys.imageio:imageio-pnm:3.13.1'\n    implementation 'com.twelvemonkeys.imageio:imageio-sgi:3.13.1'\n    implementation 'com.twelvemonkeys.imageio:imageio-tga:3.13.1'\n    implementation 'com.twelvemonkeys.imageio:imageio-webp:3.13.1'\n    implementation 'com.twelvemonkeys.imageio:imageio-xwd:3.13.1'\n    implementation 'com.twelvemonkeys.imageio:imageio-metadata:3.13.1'\n    implementation 'com.twelvemonkeys.common:common-lang:3.13.'\n    implementation 'com.twelvemonkeys.common:common-io:3.13.1'\n\n    implementation 'net.java.dev.jna:jna:5.18.1'\n    implementation 'net.java.dev.jna:jna-platform:5.18.1'\n    implementation 'com.fifesoft.rtext:fife.common:6.0.3'\n    implementation 'org.jmdns:jmdns:3.6.3'\n    implementation 'com.sshtools:j2ssh-maverick:1.5.5'\n    implementation 'org.slf4j:jcl-over-slf4j:2.0.17'\n    implementation 'org.slf4j:jul-to-slf4j:2.0.17'\n    implementation 'org.slf4j:slf4j-api:2.0.17'\n    implementation 'com.googlecode.plist:dd-plist:1.29'\n    implementation 'com.github.pcorless.icepdf:icepdf-core:7.3.2'\n    implementation 'com.github.pcorless.icepdf:icepdf-viewer:7.3.2'\n\n\n    testImplementation 'org.junit.jupiter:junit-jupiter:6.0.3'\n    testImplementation 'org.junit.jupiter:junit-jupiter-api:6.0.3'\n\n    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'\n    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:6.0.3'\n\n    testImplementation 'org.mockito:mockito-core:5.23.0'\n    testImplementation 'org.mockito:mockito-junit-jupiter:5.23.0'\n    testImplementation 'org.assertj:assertj-core:3.27.7'\n    testImplementation \"io.kotest:kotest-assertions-core:6.1.9\"\n}\n\nsourceSets {\n    main {\n        java {\n            srcDirs = ['src/main/java']\n        }\n        resources {\n            srcDirs = ['src/main/resources']\n            exclude '**/*.java'\n        }\n    }\n    test {\n        java {\n            srcDirs = ['src/test/java']\n        }\n        resources {\n            srcDirs = ['src/test/resources']\n        }\n    }\n}\n\n\njar {\n    manifest {\n        attributes(\n            'Main-Class': application.mainClass,\n            'Specification-Title': 'trolCommander',\n            'Specification-Vendor': 'Oleg Trifonov',\n            'Specification-Version': project.version,\n            'Implementation-Title': 'trolCommander',\n            'Implementation-Vendor': 'Oleg Trifonov',\n            'Implementation-Version': project.version,\n            'Build-Date': new Date().format('yyyy-MM-dd'),\n            'Build-Time': new Date().format('hh:mm:ss'),\n            'Build-URL': 'http://www.trolsoft.ru/content/soft/trolcommander/version.xml',\n            'Class-Path': configurations.runtimeClasspath.files.collect { \n                \"lib/${it.name}\" \n            }.join(' ')\n        )\n    }\n    \n\n//    from('res/runtime') {\n//        include '**/*'\n//        exclude '**/*.java'\n//    }\n}\n\ntasks.withType(Jar).configureEach {\n    exclude('META-INF/maven/**', 'docs/**')\n    exclude {\n        def name = it.name.toLowerCase()\n        def path = it.relativePath.pathString\n        ((name.contains('license') && name.endsWith('.txt')) || name.equals('readme.txt')) && path != 'license.txt'\n    }\n}\n\n// ==================== SHADOW JAR (fat-jar со всеми зависимостями) ====================\n\nshadowJar {\n    archiveBaseName.set('trolcommander')\n    archiveClassifier.set('')\n    archiveVersion.set(project.version)\n    \n    manifest {\n        inheritFrom jar.manifest\n    }\n    \n    // Исключение подписей зависимостей (чтобы не было конфликтов)\n    exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA'\n    \n    // Объединение сервис-файлов\n    mergeServiceFiles()\n}\n\ntasks.named('shadowDistZip') {\n   archiveFileName.set(\"trolCommander-${project.version}.zip\")\n   from(\"src/main/resources/images/trolcommander\") {\n      include \"icon*.png\"\n      into \"trolCommander-${project.version}/icons\"\n   }\n}\n\n\n// Задача для сборки с локальными зависимостями\ntasks.register('buildWithLocalLibs') {\n    group = 'build'\n    description = 'Сборка с использованием локальных JAR из lib/'\n    dependsOn 'shadowJar'\n}\n\n// Задача для сборки с миграцией на Maven\ntasks.register('buildWithMaven') {\n    group = 'build'\n    description = 'Сборка с приоритетом Maven-зависимостей'\n    dependsOn 'shadowJar'\n}\n\n// Задача для запуска приложения\ntasks.register('runApp', JavaExec) {\n    group = 'application'\n    description = 'Запуск trolCommander'\n    mainClass = application.mainClass\n    classpath = sourceSets.main.runtimeClasspath\n    jvmArgs = application.applicationDefaultJvmArgs\n}\n\n// ==================== УТИЛИТЫ ДЛЯ МИГРАЦИИ ====================\n\n// Задача: показать, какие зависимости берутся из локальных файлов\ntasks.register('listLocalDependencies') {\n    group = 'help'\n    description = 'Показать зависимости из локальной папки lib/'\n    \n    doLast {\n        def localDeps = configurations.runtimeClasspath.files.findAll { \n            it.absolutePath.contains('/lib/') \n        }\n        if (localDeps.isEmpty()) {\n            println \"✓ Все зависимости загружаются из Maven-репозиториев\"\n        } else {\n            println \"⚠ Локальные зависимости:\"\n            localDeps.each { println \"  - ${it.name}\" }\n            println \"\\n💡 Подсказка: замените 'implementation name: ...' на 'implementation \\\"group:artifact:version\\\"\"\n        }\n    }\n}\n\ntest {\n    useJUnitPlatform()\n\n//    dependsOn 'compileJava', 'processResources'\n    \n    jvmArgs = [\n        '-Djava.awt.headless=true',\n        '-Djava.system.class.loader=com.mucommander.commons.file.AbstractFileClassLoader'\n    ]\n    \n    testLogging {\n        //events 'passed', 'skipped', 'failed'\n        events 'skipped', 'failed'\n        exceptionFormat = 'full'\n        showStandardStreams = true\n    }\n}\n\n// Отключение предупреждений о кодировке\ntasks.withType(JavaCompile) {\n    options.encoding = 'UTF-8'\n    options.compilerArgs += ['-Xlint:unchecked', '-Xlint:deprecation']\n    options.annotationProcessorPath = configurations.annotationProcessor\n\n    if (isMac) {\n        options.compilerArgs += [\n            '--add-exports', 'java.desktop/com.apple.laf=ALL-UNNAMED',\n            '--add-exports', 'java.desktop/com.apple.eawt=ALL-UNNAMED',\n            '--add-opens', 'java.desktop/com.apple.laf=ALL-UNNAMED',\n            '--add-opens', 'java.desktop/com.apple.eawt=ALL-UNNAMED'\n        ]\n    }\n}\n\n// Копирование библиотек в output для классического запуска (опционально)\ntasks.register('copyLibs', Copy) {\n    from configurations.runtimeClasspath\n    into \"$buildDir/libs\"\n}\n\n\n//tasks.named('startScripts') {\n//    dependsOn 'shadowJar'\n//    // Перенастроить класспасс на shadowJar\n//    classpath = files(shadowJar.archiveFile)\n//}\n//\n//// И использовать другой classifier для shadowJar, чтобы не конфликтовать\n//shadowJar {\n//    archiveClassifier = 'all'  // будет trolcommander-0.9.9-all.jar\n//}\n\n\n//  Отключить стандартные задачи дистрибуции (конфликтуют с shadowJar)\ntasks.named('distZip') { enabled = false }\ntasks.named('distTar') { enabled = false }\ntasks.named('startScripts') { enabled = false }\n\n// Отключить стандартный jar, если используете только shadowJar\ntasks.named('jar') { enabled = false }\n\n// Сделать shadowJar задачей сборки по умолчанию\ntasks.named('assemble') {\n    dependsOn 'shadowJar'\n}\ntasks.named('compileJava') {\n    dependsOn 'processResources'\n}"
  },
  {
    "path": "build.properties",
    "content": "# - Build configuration ------------------------------------------------------------------------------------------------\n# ----------------------------------------------------------------------------------------------------------------------\n# If set to true, the build will fail whenever a release artifact can't be generated because an external tool isn't\n# available.\nbuild.pedantic=false\n\n# If set to true, the generated artifacts are not considered to be part of an official release.\n# This impacts file names (trolcommander-current.. instead of trolcommander-${app.version}..) as well as the metadata of\n# some artifacts (the debian version number will use a timestamp, for example).\nbuild.snapshot=false\n\n\n\n# - Application configuration ------------------------------------------------------------------------------------------\n# ----------------------------------------------------------------------------------------------------------------------\n# Name of the application.\n# This should probably not be modified.\napp.name=trolCommander\n\n# Copyright holder.\n# This should probably not be modified.\napp.vendor=Oleg Trifonov\n\n# trolCommander version.\napp.version=0.9.9\n\n# Fully qualified name of the class that starts trolCommander.\napp.main=com.mucommander.TrolCommander\n\n# trolCommander copyright line.\napp.copyright=2002-2023\n\n\n\n# - Source code configuration ------------------------------------------------------------------------------------------\n# ----------------------------------------------------------------------------------------------------------------------\n# Java version the source was written for.\nsource.version=17\n\n# Encoding of the source code.\nsource.encoding=UTF-8\n\n\n\n# - Application URLs ---------------------------------------------------------------------------------------------------\n# ----------------------------------------------------------------------------------------------------------------------\n# trolCommander homepage.\nurl.homepage=http://www.trolsoft.ru/en/soft/trolcommander\n\n# URL that trolCommander should connect to to check for new versions.\nurl.version=http://www.trolsoft.ru/content/soft/trolcommander/version.xml\n\n# URL that users can go to to download the latest trolCommander version.\nurl.download=http://www.trolsoft.ru/en/soft/trolcommander/#download\n\n# URL of the latest trolCommander JAR file.\nurl.jar=http://www.trolsoft.ru/content/soft/trolcommander/download/trolcommander.jar\n\n# URL of the trolCommander JNLP.\nurl.jnlp=http://www.trolsoft.ru/soft/trolcommander/webstart/\n\n\n\n# - External tools -----------------------------------------------------------------------------------------------------\n# ----------------------------------------------------------------------------------------------------------------------\n# Path to the 7z executable.\n# If this is not set, compression will be done using standard ZIP algorithms.\n7za.executable=/Users/trol/Bin/7za\n\n# Password for the JAR keystore.\n# If you're not Maxence, you needn't bother with this.\nstore.pass=\n\n# Path to the NSIS installation dir.\n# If not set, Windows executables won't be generated.\nnsis.dir=/usr/local/Cellar/makensis/3.0/share/nsis\n\n# Path to the NSIS executable file.\n# This will more often than not be ${nsis.dir}/makensis.\n# If not set, Windows executables won't be generated.\nnsis.bin=/usr/local/Cellar/makensis/3.0/bin/makensis\n\n# Path to the launch4j installation dir.\n# If not set, Windows executable won't be generated.\nlaunch4j.dir=/Users/trol/Bin/launch4j\n"
  },
  {
    "path": "build_template.properties",
    "content": "# - Build configuration ------------------------------------------------------------------------------------------------\n# ----------------------------------------------------------------------------------------------------------------------\n# If set to true, the build will fail whenever a release artifact can't be generated because an external tool isn't\n# available.\nbuild.pedantic=false\n\n# If set to true, the generated artifacts are not considered to be part of an official release.\n# This impacts file names (mucommander-current.. instead of mucommander-${app.version}..) as well as the metadata of\n# some artifacts (the debian version number will use a timestamp, for example).\nbuild.snapshot=true\n\n\n\n# - Application configuration ------------------------------------------------------------------------------------------\n# ----------------------------------------------------------------------------------------------------------------------\n# Name of the application.\n# This should probably not be modified.\napp.name=muCommander\n\n# Copyright holder.\n# This should probably not be modified.\napp.vendor=Maxence Bernard\n\n# muCommander version.\napp.version=0.9.0\n\n# Fully qualified name of the class that starts muCommander.\napp.main=com.mucommander.TrolCommander\n\n# muCommander copyright line.\napp.copyright=2002-2012\n\n\n\n# - Source code configuration ------------------------------------------------------------------------------------------\n# ----------------------------------------------------------------------------------------------------------------------\n# Java version the source was written for.\nsource.version=1.5\n\n# Encoding of the source code.\nsource.encoding=UTF-8\n\n\n\n# - Application URLs ---------------------------------------------------------------------------------------------------\n# ----------------------------------------------------------------------------------------------------------------------\n# muCommander homepage.\nurl.homepage=http://www.mucommander.com\n\n# URL that muCommander should connect to to check for new versions.\nurl.version=http://www.mucommander.com/version/version.xml\n\n# URL that users can go to to download the latest muCommander version.\nurl.download=http://www.mucommander.com/#download\n\n# URL of the latest muCommander JAR file.\nurl.jar=http://www.mucommander.com/download/mucommander.jar\n\n# URL of the muCommander JNLP.\nurl.jnlp=http://www.mucommander.com/webstart/\n\n\n\n# - External tools -----------------------------------------------------------------------------------------------------\n# ----------------------------------------------------------------------------------------------------------------------\n# Path to the 7z executable.\n# If this is not set, compression will be done using standard ZIP algorithms.\n7za.executable=\n\n# Password for the JAR keystore.\n# If you're not Maxence, you needn't bother with this.\nstore.pass=\n\n# Path to the NSIS installation dir.\n# If not set, Windows executables won't be generated.\nnsis.dir=\n\n# Path to the NSIS executable file.\n# This will more often than not be ${nsis.dir}/makensis.\n# If not set, Windows executables won't be generated.\nnsis.bin=\n\n# Path to the launch4j installation dir.\n# If not set, Windows executable won't be generated.\nlaunch4j.dir=\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.14.4-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Настройки сборки\norg.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8\norg.gradle.parallel=true\norg.gradle.caching=true\n\n# Версия приложения\napp.version=1.0.0\napp.vendor=Oleg Trifonov\n\n# Путь к локальным библиотекам (для гибкости)\nlib.runtime.dir=lib/runtime\nlib.compile.dir=lib/compile\nlib.test.dir=lib/test"
  },
  {
    "path": "gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=\"\\\\\\\"\\\\\\\"\"\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        -jar \"$APP_HOME/gradle/wrapper/gradle-wrapper.jar\" \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n@rem SPDX-License-Identifier: Apache-2.0\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" -jar \"%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\" %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "jni/Makefile",
    "content": "all: \n\t./build.sh\n"
  },
  {
    "path": "jni/build.sh",
    "content": "export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/\ngcc -I\"$JAVA_HOME/include\" -I\"$JAVA_HOME/include/darwin/\" -o libtrolsoft.jnilib -shared ru_trolsoft_jni_NativeFileUtils.c\ncp -f libtrolsoft.jnilib ../ \njar cvf ../lib/runtime/trolsoft.jar libtrolsoft.jnilib\n\n"
  },
  {
    "path": "jni/ru_trolsoft_jni_NativeFileUtils.c",
    "content": "#include <stdio.h>\n\n#include \"ru_trolsoft_jni_NativeFileUtils.h\"\n\n#include <stdbool.h>\n#include <unistd.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <string.h>\n#include <limits.h>\n\n#define VERSION\t\t\t\t2\n\n\n#define FA_MASK_EXISTS\t\t\t1\n#define FA_MASK_DIRECTORY\t\t2\n#define FA_MASK_HIDDEN\t\t\t4\n\n// https://www.gnu.org/software/libc/manual/html_node/Testing-File-Type.html\n\nstatic bool is_regular_file(const char *path) {\n\tstruct stat path_stat;\n\tstat(path, &path_stat);\n\treturn S_ISREG(path_stat.st_mode);\n}\n\nstatic bool is_directory(const char *path) {\n\tstruct stat path_stat;\n\tstat(path, &path_stat);\n\treturn S_ISDIR(path_stat.st_mode);\n}\n\nstatic bool is_executable_file(const char *path) {\n\tstruct stat path_stat;\n\treturn (stat(path, &path_stat) == 0 && path_stat.st_mode & S_IXUSR);\n}\n\nstatic bool is_hidden_file(const char *path) {\n  \tchar *name = strrchr(path, '/');\n  \tchar folderName[PATH_MAX];\n  \tif (name && name[0] == '/' && name[1] == 0) {\n  \t\tuint len = strlen(path);\n  \t\tif (!len || len < 2) {\n  \t\t\treturn false;\n  \t\t}\n  \t\tfolderName[len-1] = 0;\n  \t\tfor (uint i = len-2; i != 0; i--) {\n  \t\t\tfolderName[i] = path[i];\n  \t\t\tif (path[i] == '/') {\n  \t\t\t\tname = &folderName[i];\n  \t\t\t\tbreak;\n  \t\t\t}\n  \t\t}\n  \t\t\n  \t}  \t\n  \tif (name) {\n  \t  \tif (name[1] == 0) {\n  \t\t\treturn false;\n  \t\t} else if (name[1] == '.') {\n  \t\t\treturn true;\n  \t\t} else if (name[2] == 0) {\n  \t\t\tuint len = strlen(path);\n  \t\t\tif (len < 2) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tname = NULL;\n\t\t\tfor (uint i = len-2; i != 0; i--) {\n\t\t\t\tif (path[i] == '/') {\n\t\t\t\t\treturn path[i+1] == '.';\n\t\t\t\t}\n\t\t\t}\n  \t\t}\n  \t\treturn false;\n  \t}\n  \treturn path[0] == '.';\n}\n\n\nJNIEXPORT jint JNICALL Java_ru_trolsoft_jni_NativeFileUtils_getLibraryVersion\n\t(JNIEnv *env, jclass class) {\n\n\treturn VERSION;\n}\n\nJNIEXPORT jint JNICALL Java_ru_trolsoft_jni_NativeFileUtils_getLocalFileAttributes\n  (JNIEnv *env, jclass class, jstring path) {\n\n  \tif (path == NULL) {\n  \t\treturn 0;\n  \t}\n  \tconst char *pathUtf = (*env)->GetStringUTFChars(env, path, NULL);\n  \tbool exists = access(pathUtf, F_OK) != -1;\n  \tbool isDirectory = is_directory(pathUtf);\n\tbool isHidden = is_hidden_file(pathUtf);\n\tjint res = 0;\n\tif (exists) {\n\t\tres |= FA_MASK_EXISTS;\n\t}\n\tif (isDirectory) {\n\t\tres |= FA_MASK_DIRECTORY;\n\t}\n\tif (isHidden) {\n\t\tres |= FA_MASK_HIDDEN;\n\t}\n\t(*env)->ReleaseStringUTFChars(env, path, pathUtf);\n  \treturn res;\n}\n\nJNIEXPORT jboolean JNICALL Java_ru_trolsoft_jni_NativeFileUtils_isLocalFileHidden\n  (JNIEnv *env, jclass class, jstring path) {\n\n  \tif (path == NULL) {\n  \t\treturn false;\n  \t}\n  \tconst char *pathUtf = (*env)->GetStringUTFChars(env, path, NULL); \n  \tbool result = is_hidden_file(pathUtf);\n  \t(*env)->ReleaseStringUTFChars(env, path, pathUtf);\n  \treturn result;\n}\n\nJNIEXPORT jboolean JNICALL Java_ru_trolsoft_jni_NativeFileUtils_isLocalFileExecutable\n  (JNIEnv *env, jclass class, jstring path) {\n\n  \tif (path == NULL) {\n  \t\treturn false;\n  \t}\n  \tconst char *pathUtf = (*env)->GetStringUTFChars(env, path, NULL); \n  \tbool result = is_executable_file(pathUtf);\n  \t(*env)->ReleaseStringUTFChars(env, path, pathUtf);\n  \treturn result;\n}\n\n\nJNIEXPORT jboolean JNICALL Java_ru_trolsoft_jni_NativeFileUtils_isLocalDirectory\n  (JNIEnv *env, jclass class, jstring path) {\n\n  \tif (path == NULL) {\n  \t\treturn false;\n  \t}\n  \tconst char *pathUtf = (*env)->GetStringUTFChars(env, path, NULL);\n\tbool result = is_directory(pathUtf);\n\t(*env)->ReleaseStringUTFChars(env, path, pathUtf);\n\treturn result;\n}\n\n\n"
  },
  {
    "path": "jni/ru_trolsoft_jni_NativeFileUtils.h",
    "content": "/* DO NOT EDIT THIS FILE - it is machine generated */\n#include <jni.h>\n/* Header for class ru_trolsoft_jni_FileUtils */\n\n#ifndef _Included_ru_trolsoft_jni_NativeFileUtils\n#define _Included_ru_trolsoft_jni_NativeFileUtils\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nJNIEXPORT jint JNICALL Java_ru_trolsoft_jni_NativeFileUtils_getLibraryVersion\n\t(JNIEnv *, jclass);\n/*\n * Class:     ru_trolsoft_jni_FileUtils\n * Method:    getLocalFileAttributes\n * Signature: (Ljava/lang/String;)I\n */\nJNIEXPORT jint JNICALL Java_ru_trolsoft_jni_NativeFileUtils_getLocalFileAttributes\n\t(JNIEnv *, jclass, jstring);\n\nJNIEXPORT jboolean JNICALL Java_ru_trolsoft_jni_NativeFileUtils_isLocalFileHidden\n\t(JNIEnv *, jclass, jstring);\n\nJNIEXPORT jboolean JNICALL Java_ru_trolsoft_jni_NativeFileUtils_isLocalFileExecutable\n\t(JNIEnv *, jclass, jstring);\n\nJNIEXPORT jboolean JNICALL Java_ru_trolsoft_jni_NativeFileUtils_isLocalDirectory\n\t(JNIEnv *, jclass, jstring);  \n\n\n#ifdef __cplusplus\n}\n#endif\n#endif\n"
  },
  {
    "path": "lib/tools/version.txt",
    "content": "proguard.jar   -> 4.11\njdeb.jar       -> 1.2\nantdoclet.jar  -> 1.1 (patched to disable Velocity logging).\nvelocity.jar   -> 1.3.1\nsimian.jar     -> 2.2.21\njavancss.jar   -> 28.49\njdepend.jar    -> 2.9\ndoccheck.jar   -> 1.2b2\nantcontrib.jar -> 1.0b3\nmuant.jar -> 1.0 (compiled from original extracted mucommander source rev. 3628)"
  },
  {
    "path": "readme.txt",
    "content": "\n████████╗██████╗  ██████╗ ██╗\n╚══██╔══╝██╔══██╗██╔═══██╗██║\n   ██║   ██████╔╝██║   ██║██║\n   ██║   ██╔══██╗██║   ██║██║\n   ██║   ██║  ██║╚██████╔╝███████╗\n   ╚═╝   ╚═╝  ╚═╝ ╚═════╝ ╚══════╝\n\n  ██████╗ ██████╗ ███╗   ███╗███╗   ███╗ █████╗ ███╗   ██╗██████╗ ███████╗██████╗\n ██╔════╝██╔═══██╗████╗ ████║████╗ ████║██╔══██╗████╗  ██║██╔══██╗██╔════╝██╔══██╗\n ██║     ██║   ██║██╔████╔██║██╔████╔██║███████║██╔██╗ ██║██║  ██║█████╗  ██████╔╝\n ██║     ██║   ██║██║╚██╔╝██║██║╚██╔╝██║██╔══██║██║╚██╗██║██║  ██║██╔══╝  ██╔══██╗\n ╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ╚═╝ ██║██║  ██║██║ ╚████║██████╔╝███████╗██║  ██║\n  ╚═════╝ ╚═════╝ ╚═╝     ╚═╝╚═╝     ╚═╝╚═╝  ╚═╝╚═╝  ╚═══╝╚═════╝ ╚══════╝╚═╝  ╚═╝\n\n\n--------------------\ntrolCommander v1.0.0\n--------------------\n\ntrolCommander is a lightweight, cross-platform file manager with a dual-pane interface.\nIt runs on any operating system with Java support (Mac OS X, Windows, Linux, *BSD, Solaris...).\n\nDocumentation can be found at http://trac.mucommander.com/ .\n\nPlease visit the muCommander forums (http://www.mucommander.com/forums) to ask questions, suggest features or\nreport a bug. Your feedback is always welcome!\n\nOfficial website: http://trolsoft.ru/en/soft/trolcommander\nCopyright (C) 2002-2014 Maxence Bernard.\nCopyright (C) 2013-2026 Oleg Trifonov.\n\n\nRequirements\n------------\n\nA Java Runtime Environment (JRE) 1.7 or later is required to run trolCommander.\nJava 1.8 is recommended, you can download it at http://java.com.\n\nMac OS X users: your favorite OS already comes with a Java runtime so you're good to go!\n\nIf you're having problems launching trolCommander, make sure the JAVA_HOME environment variable points to the directory\nwhere your Java runtime is installed.\n\n\nWhat's new since v0.9 ?\n-----------------------\n\nNew features:\n- Lock tab capability, which prevents closing/moving the tab or changing its location.\n- New quick list that presents open tabs in the current panel, mapped onto Alt+6 by default (ticket #450).\n- Added the option to set fixed title for tab.\n- Added the following actions: add tab, duplicate tab, clone tab to other panel.\n- Added support for VMware vSphere virtual machines file system, contributed by Yuval Kohavi <yuval.kohavi@intigua.com>\n\nImprovements:\n- The state of all windows from last run is now restored on startup.\n- Added the ability to copy the base name of files (ticket #462), contributed by Chen Rozenes.\n- User can choose to always display tabs headers from preferences dialog (even when the panel contains single tab).\n- Add the application name to window title on all OSs except Mac OS X (ticket #501).\n- The visited locations history is now saved per-tab.\n- The recently visited locations quick list now presents the visited locations on all tabs and windows.\n- The content of recently visited locations quick list is now restored from previous run on startup (ticket #471).\n- Added fullscreen support for Mac OS X Lion (ticket #468).\n- Text file editor/viewer restore the full screen mode of last used (closed) editor/viewer on startup.\n- 'Bonjour' support is now disabled by default on Mac OS (on fresh installation, i.e, with no previous \n  preferences) to prevent firewall dialog which keeps popping up on startup (workaround for ticket #339).\n- Added 'ctrl+m' keystroke to toggle text file editor/viewer full screen mode.\n- Tab can be closed by clicking on its header with middle mouse button.\n- Assign 'ctrl+page_down' keystroke for switching to next tab, and 'ctrl+page_up' for switching to \n  previous tab (the keystrokes that were previously assigned to those actions remain as alternative keystrokes).\n- Improved names and descriptions presented for tab-related actions.\n- Added new category of actions in the 'shortcuts dialog' for tab-related actions.\n- Changed tab's not-fixed-title to be in the pattern '<host>:<filename>'\n- Show backward/forward locations list when pressing with right click on the back/forward buttons in the toolbar \n  instead of trigger back/forward actions\n- Keyboard shortcuts can now be set for commands defined at commands.xml (ticket #456), contributed by Jarek Czekalski.\n- Show empty name in the make file/directory dialog when it is opened (ticket #512), contributed by hclsiva.\n- Mac OS X: enabled high-resolution rendering on Retina displays (ticket #518), contributed Alexey Lysiuk.\n- Added Windows 8 and Mac OS X 10.8 to the OS versions.\n- System files can now be filtered also on windows, contributed by Markus Bullmann.\n\nLocalization:\n- Turkish translation has been updated.\n\nBug fixes:\n- Prevent deadlock which caused the application to freeze while switching tabs on MAC OS.\n- Recycle Bin is now working on Windows 64-bit with a 64-bit Java runtime (ticket #234).\n- Key combinations that contain the TAB key can be set as shortcuts (ticket #465).\n- Fix installation via software center on Ubuntu.\n- Symbolic links cannot be opened (ticket #467).\n- Encoding of text file is changed after being modified by the viewer/editor (ticket #438).\n- Cannot connect to some FTP/SFTP bookmarks if there are more than 4 of them (ticket #525), contributed by Ondrej Dusek.\n- Quick lists on the right panel sometimes not being focused (ticket #552), contributed by Jarek Czekalski.\n\nKnown issues:\n- Some translations may not be up-to-date. Refer to http://trac.mucommander.com/wiki/Translations for more information.\n- Mac OS X: \"Do you want the application \"muCommander.app\" to accept incoming network connections?\" dialog keeps popping\n  up on startup even if the dialog has been previously accepted (ticket #339), when 'Bonjour' support is enabled.\n- Executable permissions on local files are not properly preserved when running a unix-based OS with Java 1.5.\n- SMB support may not work properly on non multi-language JRE.\n- 'Copy files to clipboard' not working with some applications (files are not pasted).\n- Mac OS X: some keyboard shortcuts may conflict with global system shortcuts.\n- Authentication issues when using several sets of credentials (login/password) for the same server (see ticket #76).\n- Untrusted HTTPS connections are allowed without a warning.\n- Windows Vista/7: \"java.net.SocketException: Permission denied: recv failed\" error can appear when trying to access FTP\n  sites. This seems to be a Windows firewall problem, with a possible workaround:\n  http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7077696\n- Unpacking files from 7z archive files can be slow.\n\nLicense\n-------\n\nmuCommander is released under the terms of the GNU General Public License.\nPlease refer to the 'license.txt' file bundled with muCommander.\n\nmuCommander uses the following great third party works :\n\n- the Ant library released under the Apache License.\n Ant can be found at http://ant.apache.org .\n\n- Apache Commons libraries released under the Apache License.\n Apache Commons can be found at http://commons.apache.org .\n\n- Apache Hadoop released under the Apache License.\n Apache Hadoop can be found at http://hadoop.apache.org .\n\n- the Furbelow library released under the GNU LGPL.\n Furbelow can be found at http://sourceforge.net/projects/furbelow .\n\n- the ICU4J library released under the ICU License.\n the ICU project can be found at http://icu-project.org .\n\n- the J2SSH library released under the GNU LGPL.\n J2SSH can be found at http://www.sshtools.com .\n\n- the J7Zip library released under the GNU LGPL.\n J7Zip can be found at http://sourceforge.net/projects/p7zip/ .\n\n- the jCIFS library released under the GNU LGPL.\n jCIFS can be found at http://jcifs.samba.org .\n\n- the JetS3t library released under the Apache License.\n JetS3t can be found at http://jets3t.s3.amazonaws.com/index.html .\n\n- the JmDNS library released under the GNU LGPL.\n JmDNS can be found at http://jmdns.sourceforge.net .\n\n- the JNA library released under the GNU LGPL.\n JmDNS can be found at http://jna.dev.java.net .\n\n- the JUnRar library released as Freeware.\n JUnRar can be found at http://sourceforge.net/projects/java-unrar .\n\n- the Yanfs library released under the BSD license.\n Yanfs can be found at http://yanfs.dev.java.net .\n\n- Icons by Mark James released under the Creative Commons Attribution License.\n Mark James can be found at http://famfamfam.com .\n\n\nCredits\n-------\n\nCore developers: \n- Maxence Bernard\n- Nicolas Rinaudo\n- Arik Hadas\n- Mariusz Jakubowski\n\nContributors:\n- Ivan Baidakov\n- Vassil Dichev\n- Karel Klic\n- David Kovar\n- Joshua Lebo\n- LeO\n- Xavier Martin\n- Alejandro Scandroli\n- Alexander Yerenkow\n- Johann Schmitz\n\nTranslators: \n- Frank Berger and Tony Klüver (German)\n- Marcos Cobeña and Xavi Miró (Spanish)\n- Jaromír Mára and Peter Vasko (Czech)\n- Kent Hsu (Traditional Chinese)\n- Jioh L. Jung (Korean)\n- Andrzej Kosiński (Polish)\n- György Varga and Tamás Balogh-Walder (Hungarian)\n- 4X_Pro and Evgeny Morozov (Russian)\n- whiteriver and Woodie (Simplified Chinese)\n- Joze Kovacic (Slovenian)\n- Catalin Hritcu (Romanian)\n- Roberto Angeletti (Italian)\n- Cristiano Duarte (Brazilian Portuguese)\n- Pieter Kristensen (Dutch)\n- Ján Ľudvík (Slovak)\n- Jonathan Murphy (British English)\n- Nardog (Japanese)\n- Jakob Ekström (Swedish)\n- Jeppe Toustrup (Danish)\n- Mykola Bilovus (Ukrainian)\n- ChArLoK_16 (Arabic)\n- vboo (Belarusian)\n- Ingrid Amundsen (Norwegian)\n- Emre Aytaç (Turkish)\n- Jordi Plantalech (Catalan)\n\nSpecial thanks:\n- Semyon Filippov (muCommander icon)\n- Stefano Perelli (former muCommander icon)\n\nMany thanks to all of you who suggested new features, reported bugs, sent warm emails or generously donated to the\nproject !\n\n\nCommand Line Interface\n----------------------\n\nmuCommander comes with a few command line switches.\nThe following options are available:\n -a FILE, --assoc FILE             Load associations from FILE.\n -b FILE, --bookmarks FILE         Load bookmarks from FILE.\n -c FILE, --configuration FILE     Load configuration from FILE\n -C FILE, --commandbar FILE        Load command bar from FILE.\n -e FOLDER, --extensions FOLDER    Load extensions from FOLDER.\n -f FILE, --commands FILE          Load custom commands from FILE.\n -i, --ignore-warnings             Do not fail on warnings (default).\n -k FILE, --keymap FILE            Load keymap from FILE\n -p FOLDER, --preferences FOLDER   Store configuration files in FOLDER\n -S, --silent                      Do not print verbose error messages\n -s FILE, --shell-history FILE     Load shell history from FILE\n -t FILE, --toolbar FILE           Load toolbar from FILE\n -u FILE, --credentials FILE       Load credentials from FILE\n -h, --help                        Print the help text and exit\n -v, --version                     Print the version and exit\n -V, --verbose                     Print verbose error messages (default)\n -w, --fail-on-warnings            Quits when a warning is encountered during\n                                   the boot process.\n\nIn addition to these, muCommander will interpret anything that comes after the last switch as a URI and load it in\nits windows.\nSo for example:\n\n mucommander -b ~/.bookmarks.xml ftp://user@myftp.com ~/dev http://slashdot.org\n\nWill:\n - read bookmarks from ~/bookmarks.xml\n - load a connection to myftp.com in the left panel of the main window\n - load ~/dev in the right panel of the main window\n - open a second window and load http://slashdot.org in its left panel\n - load the default directory in the second window's fourth panel\n\n\nDocumentation\n-------------\n\nDocumentation on how to use, customize and extend muCommander is available at:\nhttp://trac.mucommander.com\n"
  },
  {
    "path": "release-linux.sh",
    "content": "VERSION='1.0.0'\n\n\nTMP_OUT_PATH=dist\n#TMP_RES_PATH=dist/resources\nOUT_PATH=dist\nARCH=$(uname -m)\n\nset -e\n\n./gradlew clean build\n\nmkdir -p $TMP_OUT_PATH\n#mkdir -p $TMP_RES_PATH\n\ncp build/libs/trolcommander-$VERSION.jar $TMP_OUT_PATH/trolcommander-linux.jar\nzip -d $TMP_OUT_PATH/trolcommander-linux.jar \"Windows-amd64/*\" \"Windows-x86/*\" \"Mac-arm64/*\" \"Mac-x86_64/*\" \"win/*\" \"jtermios/freebsd/*\" \"jtermios/Mac-x86_64/*\" \"jtermios/solaris/*\" \"jtermios/windows/*\"\nzip -d $TMP_OUT_PATH/trolcommander-linux.jar \"com/sun/jna/freebsd-amd64/*\" \"com/sun/jna/freebsd-i386/*\" \"com/sun/jna/platform/win32/*\" \"com/sun/jna/platform/wince/*\" \"com/sun/jna/sunos-amd64/*\" \"com/sun/jna/sunos-sparc/*\" \"com/sun/jna/sunos-sparcv9/*\" \"com/sun/jna/sunos-x86/*\" \"com/sun/jna/w32ce-arm/*\" \"com/sun/jna/win32/*\" \"com/sun/jna/win32-amd64/*\" \"com/sun/jna/win32-x86/*\"\n\nexport JVM_OPENS=\"--add-opens java.desktop/com.apple.eawt=ALL-UNNAMED --add-opens java.desktop/com.apple.laf=ALL-UNNAMED --add-opens java.desktop/com.apple.eio=ALL-UNNAMED --add-opens java.desktop/com.apple.laf.AquaLookAndFeel=ALL-UNNAMED\"\nexport JVM_PARAMS=\"-Xmx128m -Xms128m -Dfile.encoding=UTF-8 -XX:ReservedCodeCacheSize=64m -XX:+IgnoreUnrecognizedVMOptions\"\nexport JVM_LOGING=\"-Dslf4j.provider=ch.qos.logback.classic.spi.LogbackServiceProvider\"\nexport JVM_OPTS=\"$JVM_OPENS $JVM_PARAMS $JVM_LOGING -Djava.system.class.loader=com.mucommander.commons.file.AbstractFileClassLoader\"\n\t\t\n\n\n\n#cp res/package/osx/icon.icns $TMP_RES_PATH/trolCommander.icns\n\njpackage --input \"$TMP_OUT_PATH/\" \\\n         --name trolCommander \\\n         --app-version $VERSION \\\n         --main-jar trolcommander-linux.jar \\\n         --main-class com.mucommander.TrolCommander \\\n         --resource-dir \"$TMP_RES_PATH\" \\\n         --java-options \"$JVM_OPTS\" \\\n         --type rpm \\\n         --dest \"${OUT_PATH}\"\n\njpackage --input \"$TMP_OUT_PATH/\" \\\n         --name trolCommander \\\n         --app-version $VERSION \\\n         --main-jar trolcommander-linux.jar \\\n         --main-class com.mucommander.TrolCommander \\\n         --resource-dir \"$TMP_RES_PATH\" \\\n         --java-options \"$JVM_OPTS\" \\\n         --type deb \\\n         --dest $OUT_PATH\n                \n#                --runtime-image 'jre/linux' \\\n#mv $OUT_PATH/trolCommander-$VERSION.dmg $OUT_PATH/trolCommander-$ARCH-$VERSION.dmg\n\n#rm $TMP_RES_PATH/trolCommander.icns\n#mv $TMP_OUT_PATH/trolcommander-lunux.jar $OUT_PATH/trolcommander-linux.jar\n#rmdir $TMP_RES_PATH\n#rmdir $TMP_OUT_PATH\n#rmdir dist/macos\n"
  },
  {
    "path": "release.sh",
    "content": "VERSION='1.0.0'\n\nJAVA_HOME_MAC_X64=./tools/jdk21-macos-x64/Contents/Home/bin/\nJAVA_HOME_WINDOWS=./tools/jdk21-windows/bin\n\nOUT_PATH=dist\n\n\nTMP_OUT_PATH_MAC=dist/macos/jar\nTMP_OUT_PATH_WIN=dist/windows/jar\nTMP_RES_PATH=dist/resources\nOUT_PATH=dist\n#ARCH=$(uname -m)\n\nMAIN_CLASS=\"com.mucommander.TrolCommander\"\nCLASS_LOADER=\"com.mucommander.commons.file.AbstractFileClassLoader\"\nAPP_NAME=\"trolCommander\"\n\nset -e\n\n./gradlew clean build\n\nmkdir -p $TMP_OUT_PATH_MAC\nmkdir -p $TMP_OUT_PATH_WIN\nmkdir -p $TMP_RES_PATH\n\n# ------- macos jar -------------\ncp build/libs/trolcommander-$VERSION.jar $TMP_OUT_PATH_MAC/trolcommander-macosx.jar\n\nzip -d $TMP_OUT_PATH_MAC/trolcommander-macosx.jar \"Windows-amd64/*\" \"Windows-x86/*\" \"Linux-amd64/*\" \"Linux-i386/*\" \"win/*\" \"linux/*\" \"jtermios/freebsd/*\" \"jtermios/linux/*\" \"jtermios/solaris/*\" \"jtermios/windows/*\"\nzip -d $TMP_OUT_PATH_MAC/trolcommander-macosx.jar \"com/sun/jna/freebsd-amd64/*\" \"com/sun/jna/freebsd-i386/*\" \"com/sun/jna/linux-amd64/*\" \"com/sun/jna/linux-arm/*\" \"com/sun/jna/linux-i386/*\" \"com/sun/jna/linux-ia64/*\" \"com/sun/jna/linux-ppc/*\" \"com/sun/jna/linux-ppc64/*\" \"com/sun/jna/platform/win32/*\" \"com/sun/jna/platform/wince/*\" \"com/sun/jna/sunos-amd64/*\" \"com/sun/jna/sunos-sparc/*\" \"com/sun/jna/sunos-sparcv9/*\" \"com/sun/jna/sunos-x86/*\" \"com/sun/jna/w32ce-arm/*\" \"com/sun/jna/win32/*\" \"com/sun/jna/win32-amd64/*\" \"com/sun/jna/win32-x86/*\"\n\n# -------- Windows jar ----------\ncp build/libs/trolcommander-$VERSION.jar $TMP_OUT_PATH_WIN/trolcommander-windows.jar\nzip -d $TMP_OUT_PATH_WIN/trolcommander-windows.jar \"Mac-arm64/*\" \"Mac-x86_64/*\"  \"jtermios/Mac-x86_64/*\" \"jtermios/solaris/*\" \nzip -d $TMP_OUT_PATH_WIN/trolcommander-windows.jar \"jtermios/freebsd/*\" \"com/sun/jna/freebsd-amd64/*\" \"com/sun/jna/freebsd-i386/*\" \"com/sun/jna/platform/wince/*\" \"com/sun/jna/sunos-amd64/*\" \"com/sun/jna/sunos-sparc/*\" \"com/sun/jna/sunos-sparcv9/*\" \"com/sun/jna/sunos-x86/*\" \"com/sun/jna/w32ce-arm/*\" \nzip -d $TMP_OUT_PATH_WIN/trolcommander-windows.jar \"Linux-amd64/*\" \"Linux-i386/*\" \"linux/*\" \"jtermios/freebsd/*\" \"jtermios/linux/*\" \"jtermios/solaris/*\" \nzip -d $TMP_OUT_PATH_WIN/trolcommander-windows.jar  \"com/sun/jna/linux-amd64/*\" \"com/sun/jna/linux-arm/*\" \"com/sun/jna/linux-i386/*\" \"com/sun/jna/linux-ia64/*\" \"com/sun/jna/linux-ppc/*\" \"com/sun/jna/linux-ppc64/*\" \"com/sun/jna/platform/wince/*\" \"com/sun/jna/sunos-amd64/*\" \"com/sun/jna/sunos-sparc/*\" \"com/sun/jna/sunos-sparcv9/*\" \"com/sun/jna/sunos-x86/*\"\n\n\nJVM_OPENS=\"--add-opens java.desktop/javax.swing.plaf.basic=ALL-UNNAMED\\\n --add-opens java.base/java.io=ALL-UNNAMED\\\n --add-opens java.base/java.net=ALL-UNNAMED\\\n --add-opens java.transaction.xa/javax.transaction.xa=ALL-UNNAMED\\\n --add-opens java.management/javax.management=ALL-UNNAMED\\\n --add-opens java.rmi/java.rmi=ALL-UNNAMED\\\n --add-opens java.security.jgss/org.ietf.jgss=ALL-UNNAMED\\\n --add-opens java.sql/java.sql=ALL-UNNAMED\\\n --add-opens java.base/sun.net.www.protocol.http=ALL-UNNAMED\\\n --add-opens java.base/sun.net.www.protocol.https=ALL-UNNAMED\\\n --add-opens java.compiler/javax.lang.model.element=ALL-UNNAMED\"\n\nJVM_OPENS_APPLE=\"--add-opens java.desktop/com.apple.eawt=ALL-UNNAMED\\\n --add-opens java.desktop/com.apple.laf=ALL-UNNAMED\\\n --add-opens java.desktop/com.apple.eio=ALL-UNNAMED\\\n --add-opens java.desktop/com.apple.laf.AquaLookAndFeel=ALL-UNNAMED\"\n\nJVM_PARAMS=\"-Xmx128m -Xms128m -Dfile.encoding=UTF-8 -XX:ReservedCodeCacheSize=64m -XX:+IgnoreUnrecognizedVMOptions\"\nJVM_LOGING=\"-Dslf4j.provider=ch.qos.logback.classic.spi.LogbackServiceProvider\"\nJVM_OPTS=\"$JVM_OPENS $JVM_OPENS_APPLE $JVM_PARAMS $JVM_LOGING -Djava.system.class.loader=$CLASS_LOADER -Dlog.stdout=false\"\n\n\n# ------------ shadow\n\ncp build/distributions/trolCommander-$VERSION.zip $OUT_PATH/\n\n# ---------- macos -------------------\n\ncp res/package/osx/icon.icns $TMP_RES_PATH/trolCommander.icns\n\nARCH=\"arm64\" \n\njpackage --input \"$TMP_OUT_PATH_MAC/\" \\\n         --name $APP_NAME \\\n         --app-version $VERSION \\\n         --main-jar trolcommander-macosx.jar \\\n         --main-class $MAIN_CLASS \\\n         --resource-dir \"$TMP_RES_PATH\" \\\n         --java-options \"$JVM_OPTS\" \\\n         --type app-image \\\n         --dest \"${OUT_PATH}/macos-$ARCH\"\n\njpackage --input \"$TMP_OUT_PATH_MAC/\" \\\n         --name $APP_NAME \\\n         --app-version $VERSION \\\n         --main-jar trolcommander-macosx.jar \\\n         --main-class $MAIN_CLASS \\\n         --resource-dir \"$TMP_RES_PATH\" \\\n         --java-options \"$JVM_OPTS\" \\\n         --type dmg \\\n         --dest $OUT_PATH\n\nmv $OUT_PATH/trolCommander-$VERSION.dmg $OUT_PATH/trolCommander-$ARCH-$VERSION.dmg\n\nARCH=\"x64\"\n\n$JAVA_HOME_MAC_X64/jpackage --input \"$TMP_OUT_PATH_MAC/\" \\\n         --name $APP_NAME \\\n         --app-version $VERSION \\\n         --main-jar trolcommander-macosx.jar \\\n         --main-class $MAIN_CLASS \\\n         --resource-dir \"$TMP_RES_PATH\" \\\n         --java-options \"$JVM_OPTS\" \\\n         --type app-image \\\n         --dest \"${OUT_PATH}/macos-$ARCH\"\n\n$JAVA_HOME_MAC_X64/jpackage --input \"$TMP_OUT_PATH_MAC/\" \\\n         --name $APP_NAME \\\n         --app-version $VERSION \\\n         --main-jar trolcommander-macosx.jar \\\n         --main-class $MAIN_CLASS \\\n         --resource-dir \"$TMP_RES_PATH\" \\\n         --java-options \"$JVM_OPTS\" \\\n         --type dmg \\\n         --dest $OUT_PATH\n                \n\nmv $OUT_PATH/trolCommander-$VERSION.dmg $OUT_PATH/trolCommander-$ARCH-$VERSION.dmg\n\nrm $TMP_RES_PATH/trolCommander.icns\nmv $TMP_OUT_PATH_MAC/trolcommander-macosx.jar $OUT_PATH/trolcommander-macosx.jar\nrmdir $TMP_RES_PATH\nrmdir $TMP_OUT_PATH_MAC\nrmdir dist/macos\n\n\n# ----------- windows\n\nJVM_OPTS=\"$JVM_OPENS $JVM_PARAMS $JVM_LOGING -Djava.system.class.loader=$CLASS_LOADER\"\n\n\nwine $JAVA_HOME_WINDOWS/jpackage.exe \\\n         --type app-image \\\n         --input \"$TMP_OUT_PATH_WIN/\" \\\n         --name $APP_NAME \\\n         --app-version $VERSION \\\n         --main-jar trolcommander-windows.jar \\\n         --main-class $MAIN_CLASS \\\n         --java-options \"$JVM_OPTS\" \\\n         --resource-dir \"package/windows\" \\\n         --dest ${OUT_PATH}\ncd $OUT_PATH\nzip -rm trolCommander-windows-${VERSION}.zip trolCommander\ncd ..\n\n\nmv $TMP_OUT_PATH_WIN/trolcommander-windows.jar $OUT_PATH/trolcommander-windows.jar\nrmdir $TMP_OUT_PATH_WIN\nrmdir dist/windows\n\n"
  },
  {
    "path": "res/jar/services/services/org.apache.hadoop.crypto.key.KeyProviderFactory",
    "content": "# Licensed to the Apache Software Foundation (ASF) under one or more\n# contributor license agreements.  See the NOTICE file distributed with\n# this work for additional information regarding copyright ownership.\n# The ASF licenses this file to You under the Apache License, Version 2.0\n# (the \"License\"); you may not use this file except in compliance with\n# the License.  You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\norg.apache.hadoop.crypto.key.JavaKeyStoreProvider$Factory\norg.apache.hadoop.crypto.key.UserProvider$Factory\norg.apache.hadoop.crypto.key.kms.KMSClientProvider$Factory\n"
  },
  {
    "path": "res/jar/services/services/org.apache.hadoop.fs.FileSystem",
    "content": "# Licensed to the Apache Software Foundation (ASF) under one or more\n# contributor license agreements.  See the NOTICE file distributed with\n# this work for additional information regarding copyright ownership.\n# The ASF licenses this file to You under the Apache License, Version 2.0\n# (the \"License\"); you may not use this file except in compliance with\n# the License.  You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\norg.apache.hadoop.fs.LocalFileSystem\norg.apache.hadoop.fs.viewfs.ViewFileSystem\norg.apache.hadoop.fs.ftp.FTPFileSystem\norg.apache.hadoop.fs.HarFileSystem\norg.apache.hadoop.hdfs.DistributedFileSystem\norg.apache.hadoop.hdfs.web.HftpFileSystem\norg.apache.hadoop.hdfs.web.HsftpFileSystem\norg.apache.hadoop.hdfs.web.WebHdfsFileSystem\norg.apache.hadoop.hdfs.web.SWebHdfsFileSystem\n"
  },
  {
    "path": "res/jar/services/services/org.apache.hadoop.io.compress.CompressionCodec",
    "content": "#\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n#\n#       http://www.apache.org/licenses/LICENSE-2.0\n#\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n#\norg.apache.hadoop.io.compress.BZip2Codec\norg.apache.hadoop.io.compress.DefaultCodec\norg.apache.hadoop.io.compress.DeflateCodec\norg.apache.hadoop.io.compress.GzipCodec\norg.apache.hadoop.io.compress.Lz4Codec\norg.apache.hadoop.io.compress.SnappyCodec\n\n"
  },
  {
    "path": "res/jar/services/services/org.apache.hadoop.security.SecurityInfo",
    "content": "#\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n#\n#       http://www.apache.org/licenses/LICENSE-2.0\n#\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n#\norg.apache.hadoop.security.AnnotatedSecurityInfo\n"
  },
  {
    "path": "res/jar/services/services/org.apache.hadoop.security.alias.CredentialProviderFactory",
    "content": "# Licensed to the Apache Software Foundation (ASF) under one or more\n# contributor license agreements.  See the NOTICE file distributed with\n# this work for additional information regarding copyright ownership.\n# The ASF licenses this file to You under the Apache License, Version 2.0\n# (the \"License\"); you may not use this file except in compliance with\n# the License.  You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\norg.apache.hadoop.security.alias.JavaKeyStoreProvider$Factory\norg.apache.hadoop.security.alias.UserProvider$Factory\n"
  },
  {
    "path": "res/jar/services/services/org.apache.hadoop.security.token.TokenIdentifier",
    "content": "#\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n#\n#       http://www.apache.org/licenses/LICENSE-2.0\n#\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n#\norg.apache.hadoop.hdfs.security.token.block.BlockTokenIdentifier\norg.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier\norg.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier$WebHdfsDelegationTokenIdentifier\norg.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier$SWebHdfsDelegationTokenIdentifier\n"
  },
  {
    "path": "res/jar/services/services/org.apache.hadoop.security.token.TokenRenewer",
    "content": "#\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n#\n#       http://www.apache.org/licenses/LICENSE-2.0\n#\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n#\norg.apache.hadoop.hdfs.DFSClient$Renewer\norg.apache.hadoop.hdfs.security.token.block.BlockTokenIdentifier$Renewer\norg.apache.hadoop.hdfs.web.TokenAspect$TokenManager\n"
  },
  {
    "path": "res/jar/servicesCOMMON/org.apache.hadoop.crypto.key.KeyProviderFactory",
    "content": "# Licensed to the Apache Software Foundation (ASF) under one or more\n# contributor license agreements.  See the NOTICE file distributed with\n# this work for additional information regarding copyright ownership.\n# The ASF licenses this file to You under the Apache License, Version 2.0\n# (the \"License\"); you may not use this file except in compliance with\n# the License.  You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\norg.apache.hadoop.crypto.key.JavaKeyStoreProvider$Factory\norg.apache.hadoop.crypto.key.UserProvider$Factory\norg.apache.hadoop.crypto.key.kms.KMSClientProvider$Factory\n"
  },
  {
    "path": "res/jar/servicesCOMMON/org.apache.hadoop.fs.FileSystem",
    "content": "# Licensed to the Apache Software Foundation (ASF) under one or more\n# contributor license agreements.  See the NOTICE file distributed with\n# this work for additional information regarding copyright ownership.\n# The ASF licenses this file to You under the Apache License, Version 2.0\n# (the \"License\"); you may not use this file except in compliance with\n# the License.  You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\norg.apache.hadoop.fs.LocalFileSystem\norg.apache.hadoop.fs.viewfs.ViewFileSystem\norg.apache.hadoop.fs.ftp.FTPFileSystem\norg.apache.hadoop.fs.HarFileSystem\n"
  },
  {
    "path": "res/jar/servicesCOMMON/org.apache.hadoop.io.compress.CompressionCodec",
    "content": "#\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n#\n#       http://www.apache.org/licenses/LICENSE-2.0\n#\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n#\norg.apache.hadoop.io.compress.BZip2Codec\norg.apache.hadoop.io.compress.DefaultCodec\norg.apache.hadoop.io.compress.DeflateCodec\norg.apache.hadoop.io.compress.GzipCodec\norg.apache.hadoop.io.compress.Lz4Codec\norg.apache.hadoop.io.compress.SnappyCodec\n\n"
  },
  {
    "path": "res/jar/servicesCOMMON/org.apache.hadoop.security.SecurityInfo",
    "content": "#\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n#\n#       http://www.apache.org/licenses/LICENSE-2.0\n#\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n#\norg.apache.hadoop.security.AnnotatedSecurityInfo\n"
  },
  {
    "path": "res/jar/servicesCOMMON/org.apache.hadoop.security.alias.CredentialProviderFactory",
    "content": "# Licensed to the Apache Software Foundation (ASF) under one or more\n# contributor license agreements.  See the NOTICE file distributed with\n# this work for additional information regarding copyright ownership.\n# The ASF licenses this file to You under the Apache License, Version 2.0\n# (the \"License\"); you may not use this file except in compliance with\n# the License.  You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\norg.apache.hadoop.security.alias.JavaKeyStoreProvider$Factory\norg.apache.hadoop.security.alias.UserProvider$Factory\n"
  },
  {
    "path": "res/jar/servicesHDFS/org.apache.hadoop.fs.FileSystem",
    "content": "# Licensed to the Apache Software Foundation (ASF) under one or more\n# contributor license agreements.  See the NOTICE file distributed with\n# this work for additional information regarding copyright ownership.\n# The ASF licenses this file to You under the Apache License, Version 2.0\n# (the \"License\"); you may not use this file except in compliance with\n# the License.  You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\norg.apache.hadoop.hdfs.DistributedFileSystem\norg.apache.hadoop.hdfs.web.HftpFileSystem\norg.apache.hadoop.hdfs.web.HsftpFileSystem\norg.apache.hadoop.hdfs.web.WebHdfsFileSystem\norg.apache.hadoop.hdfs.web.SWebHdfsFileSystem\n"
  },
  {
    "path": "res/jar/servicesHDFS/org.apache.hadoop.security.token.TokenIdentifier",
    "content": "#\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n#\n#       http://www.apache.org/licenses/LICENSE-2.0\n#\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n#\norg.apache.hadoop.hdfs.security.token.block.BlockTokenIdentifier\norg.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier\norg.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier$WebHdfsDelegationTokenIdentifier\norg.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier$SWebHdfsDelegationTokenIdentifier\n"
  },
  {
    "path": "res/jar/servicesHDFS/org.apache.hadoop.security.token.TokenRenewer",
    "content": "#\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n#\n#       http://www.apache.org/licenses/LICENSE-2.0\n#\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n#\norg.apache.hadoop.hdfs.DFSClient$Renewer\norg.apache.hadoop.hdfs.security.token.block.BlockTokenIdentifier$Renewer\norg.apache.hadoop.hdfs.web.TokenAspect$TokenManager\n"
  },
  {
    "path": "res/package/unix/deb/control",
    "content": "Package: mucommander\nVersion: @VERSION@\nSection: utils\nPriority: optional\nRecommends: java-virtual-machine, java2-runtime\nSuggests: sun-java6-jre\nArchitecture: all\nInstalled-Size: @SIZE@\nMaintainer: Oleg Trifonov <admin@trolsoft.ru>\nDescription: a lightweight, cross-platform file manager\n trolCommander is a lightweight, cross-platform file manager running on any operating system with Java support.\n It features a dual-pane interface in the tradition of Norton Commander and other commanders, allowing to quickly\n and efficiently manage your files.\n .\n trolCommander comes with built-in support for a variety of file protocols (Local, FTP, SFTP, SMB, NFS, HTTP) and\n archive formats (ZIP, TAR, GZIP, BZIP2, ISO, NRG, AR, DEB, LST, 7Z, RAR) and is available in 18 languages.\n .\n Java 1.7 or higher is required to run trolCommander. \n .\n trolCommander can be found at http://www.trolsoft.ru/trolcommander\n"
  },
  {
    "path": "res/package/unix/deb/postinst",
    "content": "#! /bin/sh\nln -s /usr/share/trolcommander/trolcommander.sh /usr/bin/trolcommander\n"
  },
  {
    "path": "res/package/unix/deb/postrm",
    "content": "#! /bin/sh\nif [ -h /usr/bin/trolcommander ] ; then\n  rm /usr/bin/trolcommander\nfi\n"
  },
  {
    "path": "res/package/unix/trolcommander.desktop",
    "content": "[Desktop Entry]\nName=muCommander\nComment=A lightweight, cross-platform file manager\nExec=mucommander\nIcon=mucommander\nTerminal=false\nType=Application\nCategories=Application;FileManager;Utility;\nStartupNotify=true\n\n"
  },
  {
    "path": "res/package/unix/trolcommander.sh",
    "content": "#! /bin/sh\n\nTROLCOMMANDER_ARGS=\"@ARGS@\"\nJAVA_ARGS=\"@JAVA_ARGS@\"\n\n# Locates the java executable.\nif [ \"$JAVA_HOME\" != \"\" ] ; then\n    JAVA=$JAVA_HOME/bin/java\nelse\n    JAVACMD=`which java 2> /dev/null `\n    if [ -z \"$JAVACMD\" ] ; then\n        echo \"Error: cannot find java VM.\"\n        exit 1\n    else\n        JAVA=java\n    fi\nfi\n\n# Resolve the path to the trolcommander.jar located in the same directory as this script\nif [ -h $0 ]\nthen\n    # This script has been invoked from a symlink, resolve the link's target (i.e. the path to this script)\n    TROLCOMMANDER_SH=`ls -l \"$0\"`\n    TROLCOMMANDER_SH=${TROLCOMMANDER_SH#*-> }\nelse\n    TROLCOMMANDER_SH=$0\nfi\n\nCURRENT_DIR=`dirname \"$TROLCOMMANDER_SH\"`\nTROLCOMMANDER_JAR=$CURRENT_DIR/trolcommander.jar\n\nOPEN_ARGS=\"--add-opens java.base/java.io=ALL-UNNAMED --add-opens java.desktop/sun.awt.X11=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.desktop/javax.swing.plaf.basic=ALL-UNNAMED --add-opens java.transaction.xa/javax.transaction.xa=ALL-UNNAMED --add-opens java.management/javax.management=ALL-UNNAMED --add-opens java.rmi/java.rmi=ALL-UNNAMED --add-opens java.security.jgss/org.ietf.jgss=ALL-UNNAMED --add-opens java.sql/java.sql=ALL-UNNAMED --add-opens java.base/sun.net.www.protocol.http=ALL-UNNAMED --add-opens java.base/sun.net.www.protocol.https=ALL-UNNAMED --add-opens jdk.httpserver/com.sun.net.httpserver=ALL-UNNAMED --add-opens java.compiler/javax.lang.model.element=ALL-UNNAMED\"\nif [ ! -f $TROLCOMMANDER_JAR ]\nthen\n    echo \"Error: cannot find file trolcommander.jar in directory $CURRENT_DIR\"\n    exit 1\nfi\n\n# Starts trolcommander.\n$JAVA $JAVA_ARGS $OPEN_ARGS -DGNOME_DESKTOP_SESSION_ID=$GNOME_DESKTOP_SESSION_ID -DKDE_FULL_SESSION=$KDE_FULL_SESSION -DKDE_SESSION_VERSION=$KDE_SESSION_VERSION -jar $TROLCOMMANDER_JAR $TROLCOMMANDER_ARGS $@\n"
  },
  {
    "path": "res/package/version.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<mucommander>\n  <latest_version>@VERSION@</latest_version>\n  <release_date>@DATE@</release_date>\n  <download_url>@DOWNLOAD_URL@</download_url>\n  <jar_url>@JAR_URL@</jar_url>\n</mucommander>\n    \n"
  },
  {
    "path": "res/package/windows/trolcommander.nsi",
    "content": "; -*- coding: utf-8 -*-\n; trolcommander install script\n;\n\n; Include Modern UI\n!include MUI2.nsh\n\n; The name of the installer\nName \"trolCommander @MU_VERSION@\"\n\n; The file to write\nOutFile @MU_OUT@\n\n; Installer icon\n!define MUI_ICON @MU_ICON@\n!define MUI_UNICON @MU_ICON@\n\n; The default installation directory\nInstallDir $PROGRAMFILES\\trolCommander\n; Registry key to check for directory (so if you install again, it will \n; overwrite the old one automatically)\nInstallDirRegKey HKLM SOFTWARE\\trolCommander \"Install_Dir\"\n\n; Specifies the requested execution level for Windows Vista. \n; Necessary for correct uninstallation of Start menu shortcuts.\nRequestExecutionLevel admin\n\n; Pages\n!insertmacro MUI_PAGE_WELCOME\n!insertmacro MUI_PAGE_DIRECTORY\n!define MUI_COMPONENTSPAGE_NODESC\n!insertmacro MUI_PAGE_COMPONENTS\n!insertmacro MUI_PAGE_INSTFILES\n!define MUI_FINISHPAGE_RUN \"$INSTDIR\\trolCommander.exe\"\n!define MUI_FINISHPAGE_SHOWREADME_NOTCHECKED\n!define MUI_FINISHPAGE_SHOWREADME \"$INSTDIR\\readme.txt\"\n!insertmacro MUI_PAGE_FINISH\n!insertmacro MUI_UNPAGE_WELCOME\n!insertmacro MUI_UNPAGE_CONFIRM\n!insertmacro MUI_UNPAGE_INSTFILES\n!insertmacro MUI_UNPAGE_FINISH\n\n; Languages\n; Installer should support same languages as trolCommander.\n!insertmacro MUI_LANGUAGE \"English\" ; first language is the default language\n!insertmacro MUI_LANGUAGE \"French\"\n!insertmacro MUI_LANGUAGE \"Spanish\"\n!insertmacro MUI_LANGUAGE \"SpanishInternational\"\n!insertmacro MUI_LANGUAGE \"German\"\n!insertmacro MUI_LANGUAGE \"Czech\"\n!insertmacro MUI_LANGUAGE \"SimpChinese\"\n!insertmacro MUI_LANGUAGE \"TradChinese\"\n!insertmacro MUI_LANGUAGE \"Polish\"\n!insertmacro MUI_LANGUAGE \"Hungarian\"\n!insertmacro MUI_LANGUAGE \"Russian\"\n!insertmacro MUI_LANGUAGE \"Slovenian\"\n!insertmacro MUI_LANGUAGE \"Romanian\"\n!insertmacro MUI_LANGUAGE \"Italian\"\n!insertmacro MUI_LANGUAGE \"Korean\"\n!insertmacro MUI_LANGUAGE \"Portuguese\"\n!insertmacro MUI_LANGUAGE \"PortugueseBR\"\n!insertmacro MUI_LANGUAGE \"Dutch\"\n!insertmacro MUI_LANGUAGE \"Slovak\"\n!insertmacro MUI_LANGUAGE \"Japanese\"\n!insertmacro MUI_LANGUAGE \"Swedish\"\n!insertmacro MUI_LANGUAGE \"Danish\"\n\n; The stuff to install\nSection \"trolCommander @MU_VERSION@ (required)\"\n  ; Read only section. It will always be set to install.\n  SectionIn RO\n\n  ; Set output path to the installation directory.\n  SetOutPath $INSTDIR\n  ; Copy trolCommander files\n  File /oname=trolCommander.exe @MU_EXE@\n  File /oname=trolcommander.jar @MU_JAR@\n  File /oname=readme.txt @MU_README@\n  File /oname=license.txt @MU_LICENSE@\n  ; Write the installation path into the registry\n  WriteRegStr HKLM SOFTWARE\\trolCommander \"Install_Dir\" \"$INSTDIR\"\n  ; Write the uninstall keys for Windows\n  WriteRegStr HKLM \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\trolCommander\" \"DisplayName\" \"trolCommander (remove only)\"\n  WriteRegStr HKLM \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\trolCommander\" \"UninstallString\" '\"$INSTDIR\\uninstall.exe\"'\n  WriteRegDWORD HKLM \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\trolCommander\" \"NoModify\" 1\n  WriteRegDWORD HKLM \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\trolCommander\" \"NoRepair\" 1\n  WriteUninstaller \"uninstall.exe\"\n  \n  ; Create Start Menu directory and shortcuts\n  CreateDirectory \"$SMPROGRAMS\\trolCommander\"\n  CreateShortCut \"$SMPROGRAMS\\trolCommander\\trolCommander.lnk\" \"$INSTDIR\\trolCommander.exe\" \"\" \"\" 0 SW_SHOWMINIMIZED\n  CreateShortCut \"$SMPROGRAMS\\trolCommander\\Read Me.lnk\" \"$INSTDIR\\readme.txt\" \"\" \"\" 0\n  CreateShortCut \"$SMPROGRAMS\\trolCommander\\License.lnk\" \"$INSTDIR\\license.txt\" \"\" \"\" 0\n  CreateShortCut \"$SMPROGRAMS\\trolCommander\\Uninstall.lnk\" \"$INSTDIR\\uninstall.exe\" \"\" \"\" 0 \nSectionEnd\n\n; Quick launch shortcut (optional section)\nSection \"Quick Launch shortcut\"\n  CreateShortCut \"$QUICKLAUNCH\\trolCommander.lnk\" \"$INSTDIR\\trolCommander.exe\" \"\" \"\" 0 SW_SHOWMINIMIZED\nSectionEnd\n\n; Desktop shortcut (optional section)\nSection \"Desktop shortcut\"\n  CreateShortCut \"$DESKTOP\\trolCommander.lnk\" \"$INSTDIR\\trolCommander.exe\" \"\" \"\" 0 SW_SHOWMINIMIZED\nSectionEnd\n\n; Special uninstall section.\nSection \"Uninstall\"\n  ; remove registry keys\n  DeleteRegKey HKLM \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\trolCommander\"\n  DeleteRegKey HKLM SOFTWARE\\trolCommander\n  ; remove files\n  Delete $INSTDIR\\trolCommander.exe\n  Delete $INSTDIR\\trolcommander.jar\n  Delete $INSTDIR\\trolCommander.lnk\n  Delete $INSTDIR\\readme.txt\n  Delete $INSTDIR\\license.txt\n  ; MUST REMOVE UNINSTALLER, too\n  Delete $INSTDIR\\uninstall.exe\n  ; remove shortcuts, if any.\n  Delete \"$SMPROGRAMS\\trolCommander\\*.*\"\n  Delete \"$QUICKLAUNCH\\trolCommander.lnk\"\n  Delete \"$DESKTOP\\trolCommander.lnk\"\n  ; remove directories used.\n  RMDir \"$SMPROGRAMS\\trolCommander\"\n  RMDir \"$INSTDIR\"\nSectionEnd\n\n; eof\n"
  },
  {
    "path": "settings.gradle",
    "content": "rootProject.name = 'trolcommander'\n\n// Включение локальных модулей (если нужно)\n// include ':libs:local-lib1'\n// project(':libs:local-lib1').projectDir = file('lib/local-lib1')"
  },
  {
    "path": "src/main/java/com/ibm/icu/text/CharsetDetector.java",
    "content": "/*\n*******************************************************************************\n* Copyright (C) 2005-2013, International Business Machines Corporation and    *\n* others. All Rights Reserved.                                                *\n*******************************************************************************\n*/\npackage com.ibm.icu.text;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.Reader;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\n\n/**\n * <code>CharsetDetector</code> provides a facility for detecting the\n * charset or encoding of character data in an unknown format.\n * The input data can either be from an input stream or an array of bytes.\n * The result of the detection operation is a list of possibly matching\n * charsets, or, for simple use, you can just ask for a Java Reader that\n * will will work over the input data.\n * <p>\n * Character set detection is at best an imprecise operation.  The detection\n * process will attempt to identify the charset that best matches the characteristics\n * of the byte data, but the process is partly statistical in nature, and\n * the results can not be guaranteed to always be correct.\n * <p>\n * For best accuracy in charset detection, the input data should be primarily\n * in a single language, and a minimum of a few hundred bytes worth of plain text\n * in the language are needed.  The detection process will attempt to\n * ignore html or xml style markup that could otherwise obscure the content.\n * stable ICU 3.4\n */\npublic class CharsetDetector {\n\n//   Question: Should we have getters corresponding to the setters for input text\n//   and declared encoding?\n\n//   A thought: If we were to create our own type of Java Reader, we could defer\n//   figuring out an actual charset for data that starts out with too much English\n//   only ASCII until the user actually read through to something that didn't look\n//   like 7 bit English.  If  nothing else ever appeared, we would never need to\n//   actually choose the \"real\" charset.  All assuming that the application just\n//   wants the data, and doesn't care about a char set name.\n\n    /**\n     *   Constructor\n     * \n     */\n    public CharsetDetector() {\n    }\n\n    /**\n     * Set the declared encoding for charset detection.\n     * The declared encoding of an input text is an encoding obtained\n     * from an http header or xml declaration or similar source that\n     * can be provided as additional information to the charset detector.\n     * A match between a declared encoding and a possible detected encoding\n     * will raise the quality of that detected encoding by a small delta,\n     * and will also appear as a \"reason\" for the match.\n     * <p>\n     * A declared encoding that is incompatible with the input data being\n     * analyzed will not be added to the list of possible encodings.\n     * \n     * @param encoding The declared encoding\n     * @return this\n     *\n     */\n    public CharsetDetector setDeclaredEncoding(String encoding) {\n        fDeclaredEncoding = encoding;\n        return this;\n    }\n    \n    /**\n     * Set the input text (byte) data whose charset is to be detected.\n     * \n     * @param in the input text of unknown encoding\n     * \n     * @return This CharsetDetector\n     *\n     */\n    public CharsetDetector setText(byte [] in) {\n        fRawInput  = in;\n        fRawLength = in.length;\n        \n        return this;\n    }\n    \n    private static final int kBufSize = 8000;\n\n    /**\n     * Set the input text (byte) data whose charset is to be detected.\n     *  <p>\n     *  The input stream that supplies the character data must have markSupported()\n     *  == true; the charset detection process will read a small amount of data,\n     *  then return the stream to its original position via\n     *  the InputStream.reset() operation.  The exact amount that will\n     *  be read depends on the characteristics of the data itself.\n     *\n     * @param in the input text of unknown encoding\n     * \n     * @return This CharsetDetector\n     *\n\t * @throws IOException if an I/O error occurs.\n     *\n     */\n    public CharsetDetector setText(InputStream in) throws IOException {\n        fInputStream = in;\n        fInputStream.mark(kBufSize);\n        fRawInput = new byte[kBufSize];   // Always make a new buffer because the\n                                          //   previous one may have come from the caller,\n                                          //   in which case we can't touch it.\n        fRawLength = 0;\n        int remainingLength = kBufSize;\n        while (remainingLength > 0 ) {\n            // read() may give data in smallish chunks, esp. for remote sources.  Hence, this loop.\n            int  bytesRead = fInputStream.read(fRawInput, fRawLength, remainingLength);\n            if (bytesRead <= 0) {\n                 break;\n            }\n            fRawLength += bytesRead;\n            remainingLength -= bytesRead;\n        }\n        fInputStream.reset();\n        \n        return this;\n    }\n\n  \n    /**\n     * Return the charset that best matches the supplied input data.\n     * Note though, that because the detection \n     * only looks at the start of the input data,\n     * there is a possibility that the returned charset will fail to handle\n     * the full set of input data.\n     * <p>\n     * Raise an exception if \n     *  <ul>\n     *    <li>no charset appears to match the data.</li>\n     *    <li>no input text has been provided</li>\n     *  </ul>\n     *\n     * @return a CharsetMatch object representing the best matching charset, or\n     *         <code>null</code> if there are no matches.\n     *\n     */\n    public CharsetMatch detect() {\n//   TODO:  A better implementation would be to copy the detect loop from\n//          detectAll(), and cut it short as soon as a match with a high confidence\n//          is found.  This is something to be done later, after things are otherwise\n//          working.\n        CharsetMatch[] matches = detectAll();\n        \n        if (matches == null || matches.length == 0) {\n            return null;\n        }\n        \n        return matches[0];\n     }\n    \n    /**\n     *  Return an array of all charsets that appear to be plausible\n     *  matches with the input data.  The array is ordered with the\n     *  best quality match first.\n     * <p>\n     * Raise an exception if \n     *  <ul>\n     *    <li>no charsets appear to match the input data.</li>\n     *    <li>no input text has been provided</li>\n     *  </ul>\n     * \n     * @return An array of CharsetMatch objects representing possibly matching charsets.\n     *\n     */\n    public CharsetMatch[] detectAll() {\n        ArrayList<CharsetMatch> matches = new ArrayList<>();\n        \n        MungeInput();  // Strip html markup, collect byte stats.\n        \n        //  Iterate over all possible charsets, remember all that\n        //    give a match quality > 0.\n        for (int i = 0; i < ALL_CS_RECOGNIZERS.size(); i++) {\n            CSRecognizerInfo rcInfo = ALL_CS_RECOGNIZERS.get(i);\n            boolean active = (fEnabledRecognizers != null) ? fEnabledRecognizers[i] : rcInfo.isDefaultEnabled;\n            if (active) {\n                CharsetMatch m = rcInfo.recognizer.match(this);\n                if (m != null) {\n                    matches.add(m);\n                }\n            }\n        }\n        Collections.sort(matches);      // CharsetMatch compares on confidence\n        Collections.reverse(matches);   //  Put best match first.\n        CharsetMatch [] resultArray = new CharsetMatch[matches.size()];\n        resultArray = matches.toArray(resultArray);\n        return resultArray;\n    }\n\n    \n    /**\n     * Autodetect the charset of an inputStream, and return a Java Reader\n     * to access the converted input data.\n     * <p>\n     * This is a convenience method that is equivalent to\n     *   <code>this.setDeclaredEncoding(declaredEncoding).setText(in).detect().getReader();</code>\n     * <p>\n     *   For the input stream that supplies the character data, markSupported()\n     *   must be true; the  charset detection will read a small amount of data,\n     *   then return the stream to its original position via\n     *   the InputStream.reset() operation.  The exact amount that will\n     *   be read depends on the characteristics of the data itself.\n     * <p>\n     * Raise an exception if no charsets appear to match the input data.\n     * \n     * @param in The source of the byte data in the unknown charset.\n     *\n     * @param declaredEncoding  A declared encoding for the data, if available,\n     *           or null or an empty string if none is available.\n     *\n\t * @return Reader to access the converted input data\n     *\n     */\n    public Reader getReader(InputStream in, String declaredEncoding) {\n        fDeclaredEncoding = declaredEncoding;\n        \n        try {\n            setText(in);\n            \n            CharsetMatch match = detect();\n            \n            if (match == null) {\n                return null;\n            }\n            return match.getReader();\n        } catch (IOException e) {\n            return null;\n        }\n    }\n\n    /**\n     * Autodetect the charset of an inputStream, and return a String\n     * containing the converted input data.\n     * <p>\n     * This is a convenience method that is equivalent to\n     *   <code>this.setDeclaredEncoding(declaredEncoding).setText(in).detect().getString();</code>\n     * <p>\n     * Raise an exception if no charsets appear to match the input data.\n     * \n     * @param in The source of the byte data in the unknown charset.\n     *\n     * @param declaredEncoding  A declared encoding for the data, if available,\n     *           or null or an empty string if none is available.\n     *\n\t * @return a String containing the converted input data\n     *\n     */\n    public String getString(byte[] in, String declaredEncoding) {\n        fDeclaredEncoding = declaredEncoding;\n       \n        try {\n            setText(in);\n            \n            CharsetMatch match = detect();\n            \n            if (match == null) {\n                return null;\n            }\n            \n            return match.getString(-1);\n        } catch (IOException e) {\n            return null;\n        }\n    }\n\n \n    /**\n     * Get the names of all charsets supported by <code>CharsetDetector</code> class.\n     * <p>\n     * <b>Note:</b> Multiple different charset encodings in a same family may use\n     * a single shared name in this implementation. For example, this method returns\n     * an array including \"ISO-8859-1\" (ISO Latin 1), but not including \"windows-1252\"\n     * (Windows Latin 1). However, actual detection result could be \"windows-1252\"\n     * when the input data matches Latin 1 code points with any points only available\n     * in \"windows-1252\".\n     *\n     * @return an array of the names of all charsets supported by\n     * <code>CharsetDetector</code> class.\n     *\n     */\n    public static String[] getAllDetectableCharsets() {\n        String[] allCharsetNames = new String[ALL_CS_RECOGNIZERS.size()];\n        for (int i = 0; i < allCharsetNames.length; i++) {\n            allCharsetNames[i] = ALL_CS_RECOGNIZERS.get(i).recognizer.getName();\n        }\n        return allCharsetNames;\n    }   \n       \n    /**\n     * Test whether input filtering is enabled.\n     * \n     * @return <code>true</code> if input text will be filtered.\n     * \n     * @see #enableInputFilter\n     *\n     */\n    public boolean inputFilterEnabled() {\n        return fStripTags;\n    }\n    \n    /**\n     * Enable filtering of input text. If filtering is enabled,\n     * text within angle brackets (\"&lt;\" and \"&gt;\") will be removed\n     * before detection.\n     * \n     * @param filter <code>true</code> to enable input text filtering.\n     * \n     * @return The previous setting.\n     *\n     */\n    public boolean enableInputFilter(boolean filter) {\n        boolean previous = fStripTags;\n        \n        fStripTags = filter;\n        \n        return previous;\n    }\n    \n    /*\n     *  MungeInput - after getting a set of raw input data to be analyzed, preprocess\n     *               it by removing what appears to be html markup.\n     */\n    private void MungeInput() {\n        boolean  inMarkup = false;\n        int      openTags = 0;\n        int      badTags  = 0;\n        \n        //\n        //  html / xml markup stripping.\n        //     quick and dirty, not 100% accurate, but hopefully good enough, statistically.\n        //     discard everything within < brackets >\n        //     Count how many total '<' and illegal (nested) '<' occur, so we can make some\n        //     guess whether the input was actually marked up at all.\n        int srci;\n\n        if (fStripTags) {\n            int dsti = 0;\n\n            for (srci = 0; srci < fRawLength && dsti < fInputBytes.length; srci++) {\n                byte b = fRawInput[srci];\n                if (b == (byte)'<') {\n                    if (inMarkup) {\n                        badTags++;\n                    }\n                    inMarkup = true;\n                    openTags++;\n                }\n                \n                if (!inMarkup) {\n                    fInputBytes[dsti++] = b;\n                }\n                \n                if (b == (byte)'>') {\n                    inMarkup = false;\n                }        \n            }\n            \n            fInputLen = dsti;\n        }\n        //  If it looks like this input wasn't marked up, or if it looks like it's\n        //    essentially nothing but markup abandon the markup stripping.\n        //    Detection will have to work on the unstripped input.\n        if (openTags < 5 || openTags/5 < badTags || (fInputLen < 100 && fRawLength>600)) {\n            int limit = fRawLength;\n            \n            if (limit > kBufSize) {\n                limit = kBufSize;\n            }\n            \n            for (srci=0; srci<limit; srci++) {\n                fInputBytes[srci] = fRawInput[srci];\n            }\n            fInputLen = srci;\n        }\n        // Tally up the byte occurrence statistics.\n        // These are available for use by the various detectors.\n        Arrays.fill(fByteStats, (short)0);\n        for (srci=0; srci<fInputLen; srci++) {\n            int val = fInputBytes[srci] & 0x00ff;\n            fByteStats[val]++;\n        }\n        \n        fC1Bytes = false;\n        for (int i = 0x80; i <= 0x9F; i += 1) {\n            if (fByteStats[i] != 0) {\n                fC1Bytes = true;\n                break;\n            }\n        }\n     }\n\n    /*\n     *  The following items are accessed by individual CharsetRecognizers during the recognition process\n     */\n    byte[]      fInputBytes =       // The text to be checked.  Markup will have been\n                   new byte[kBufSize];  //   removed if appropriate.\n    \n    int         fInputLen;          // Length of the byte data in fInputBytes.\n    \n    short[] fByteStats =      // byte frequency statistics for the input text.\n                   new short[256];  //   Value is percent, not absolute.\n                                    //   Value is rounded up, so zero really means zero occurences.\n    \n    boolean     fC1Bytes =          // True if any bytes in the range 0x80 - 0x9F are in the input;\n                   false;\n    \n    String      fDeclaredEncoding;\n\n\n    byte[]               fRawInput;     // Original, untouched input bytes.\n                                        //  If user gave us a byte array, this is it.\n                                        //  If user gave us a stream, it's read to a \n                                        //  buffer here.\n    int                  fRawLength;    // Length of data in fRawInput array.\n    \n    InputStream          fInputStream;  // User's input stream, or null if the user\n                                        //   gave us a byte array.\n     \n    //  Stuff private to CharsetDetector\n    private boolean      fStripTags = false;  // If true, setText() will strip tags from input text.\n    private boolean[]    fEnabledRecognizers;   // If not null, active set of charset recognizers had\n                                                // been changed from the default. The array index is\n                                                // corresponding to ALL_RECOGNIZER. See setDetectableCharset().\n    private static class CSRecognizerInfo {\n        CharsetRecognizer recognizer;\n        boolean isDefaultEnabled;\n\n        CSRecognizerInfo(CharsetRecognizer recognizer, boolean isDefaultEnabled) {\n            this.recognizer = recognizer;\n            this.isDefaultEnabled = isDefaultEnabled;\n        }\n    }\n\n    /*\n     * List of recognizers for all charsets known to the implementation.\n     */\n    private static final List<CSRecognizerInfo> ALL_CS_RECOGNIZERS = List.of(\n        new CSRecognizerInfo(new CharsetRecog_UTF8(), true),\n        new CSRecognizerInfo(new CharsetRecog_Unicode.CharsetRecog_UTF_16_BE(), true),\n        new CSRecognizerInfo(new CharsetRecog_Unicode.CharsetRecog_UTF_16_LE(), true),\n        new CSRecognizerInfo(new CharsetRecog_Unicode.CharsetRecog_UTF_32_BE(), true),\n        new CSRecognizerInfo(new CharsetRecog_Unicode.CharsetRecog_UTF_32_LE(), true),\n        new CSRecognizerInfo(new CharsetRecog_mbcs.CharsetRecog_sjis(), true),\n        new CSRecognizerInfo(new CharsetRecog_2022.CharsetRecog_2022JP(), true),\n        new CSRecognizerInfo(new CharsetRecog_2022.CharsetRecog_2022CN(), true),\n        new CSRecognizerInfo(new CharsetRecog_2022.CharsetRecog_2022KR(), true),\n        new CSRecognizerInfo(new CharsetRecog_mbcs.CharsetRecog_euc.CharsetRecog_gb_18030(), true),\n        new CSRecognizerInfo(new CharsetRecog_mbcs.CharsetRecog_euc.CharsetRecog_euc_jp(), true),\n        new CSRecognizerInfo(new CharsetRecog_mbcs.CharsetRecog_euc.CharsetRecog_euc_kr(), true),\n        new CSRecognizerInfo(new CharsetRecog_mbcs.CharsetRecog_big5(), true),\n        new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_8859_1(), true),\n        new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_8859_2(), true),\n        new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_8859_5_ru(), true),\n        new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_8859_6_ar(), true),\n        new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_8859_7_el(), true),\n        new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_8859_8_I_he(), true),\n        new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_8859_8_he(), true),\n        new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_windows_1251(), true),\n        new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_windows_1256(), true),\n        new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_KOI8_R(), true),\n        new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_8859_9_tr(), true),\n        // IBM 420/424 recognizers are disabled by default\n        new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_IBM424_he_rtl(), false),\n        new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_IBM424_he_ltr(), false),\n        new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_IBM420_ar_rtl(), false),\n        new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_IBM420_ar_ltr(), false),\n        new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_cp866(), true)\n    );\n\n    /**\n     * Get the names of charsets that can be recognized by this CharsetDetector instance.\n     *\n     * @return an array of the names of charsets that can be recognized by this CharsetDetector\n     * instance.\n     * internal\n     * @deprecated This API is ICU internal only.\n     */\n    public String[] getDetectableCharsets() {\n        List<String> csnames = new ArrayList<>(ALL_CS_RECOGNIZERS.size());\n        for (int i = 0; i < ALL_CS_RECOGNIZERS.size(); i++) {\n            CSRecognizerInfo rcinfo = ALL_CS_RECOGNIZERS.get(i);\n            boolean active = (fEnabledRecognizers == null) ? rcinfo.isDefaultEnabled : fEnabledRecognizers[i];\n            if (active) {\n                csnames.add(rcinfo.recognizer.getName());\n            }\n        }\n        return csnames.toArray(new String[0]);\n    }\n\n    /**\n     * Enable or disable individual charset encoding.\n     * A name of charset encoding must be included in the names returned by\n     * {@link #getAllDetectableCharsets()}.\n     *\n     * @param encoding the name of charset encoding.\n     * @param enabled <code>true</code> to enable, or <code>false</code> to disable the\n     * charset encoding.\n     * @return A reference to this <code>CharsetDetector</code>.\n     * @throws IllegalArgumentException when the name of charset encoding is\n     * not supported.\n     * internal\n     * @deprecated This API is ICU internal only.\n     */\n    public CharsetDetector setDetectableCharset(String encoding, boolean enabled) {\n        int modIdx = -1;\n        boolean isDefaultVal = false;\n        for (int i = 0; i < ALL_CS_RECOGNIZERS.size(); i++) {\n            CSRecognizerInfo csrinfo = ALL_CS_RECOGNIZERS.get(i);\n            if (csrinfo.recognizer.getName().equals(encoding)) {\n                modIdx = i;\n                isDefaultVal = (csrinfo.isDefaultEnabled == enabled);\n                break;\n            }\n        }\n        if (modIdx < 0) {\n            // No matching encoding found\n            throw new IllegalArgumentException(\"Invalid encoding: \" + \"\\\"\" + encoding + \"\\\"\");\n        }\n        if (fEnabledRecognizers == null && !isDefaultVal) {\n            // create an array storing the non default setting\n            fEnabledRecognizers = new boolean[ALL_CS_RECOGNIZERS.size()];\n\n            // Initialize the array with default info\n            for (int i = 0; i < ALL_CS_RECOGNIZERS.size(); i++) {\n                fEnabledRecognizers[i] = ALL_CS_RECOGNIZERS.get(i).isDefaultEnabled;\n            }\n        }\n        if (fEnabledRecognizers != null) {\n            fEnabledRecognizers[modIdx] = enabled;\n        }\n        return this;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/ibm/icu/text/CharsetMatch.java",
    "content": "/*\n*******************************************************************************\n* Copyright (C) 2005-2012, International Business Machines Corporation and    *\n* others. All Rights Reserved.                                                *\n*******************************************************************************\n*/\npackage com.ibm.icu.text;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\n\n\n/**\n * This class represents a charset that has been identified by a CharsetDetector\n * as a possible encoding for a set of input data.  From an instance of this\n * class, you can ask for a confidence level in the charset identification,\n * or for Java Reader or String to access the original byte data in Unicode form.\n * <p>\n * Instances of this class are created only by CharsetDetectors.\n * <p>\n * Note:  this class has a natural ordering that is inconsistent with equals.\n *        The natural ordering is based on the match confidence value.\n *\n * stable ICU 3.4\n */\npublic class CharsetMatch implements Comparable<CharsetMatch> {\n\n    \n    /**\n     * create a java.io.Reader for reading the Unicode character data corresponding\n     * to the original byte data supplied to the Charset detect operation.\n     * <p>\n     * CAUTION:  if the source of the byte data was an InputStream, a Reader\n     * can be created for only one matching char set using this method.  If more \n     * than one charset needs to be tried, the caller will need to reset\n     * the InputStream and create InputStreamReaders itself, based on the charset name.\n     *\n     * @return the Reader for the Unicode character data.\n     *\n     */\n    public Reader getReader() {\n        InputStream inputStream = fInputStream;\n        \n        if (inputStream == null) {\n            inputStream = new ByteArrayInputStream(fRawInput, 0, fRawLength);\n        }\n        \n        try {\n            inputStream.reset();\n            return new InputStreamReader(inputStream, getName());\n        } catch (IOException e) {\n            return null;\n        }\n    }\n\n    /**\n     * reate a Java String from Unicode character data corresponding\n     * to the original byte data supplied to the Charset detect operation.\n     *\n     * @return a String created from the converted input data.\n     *\n     * @throws IOException if an IO error occurs.\n     *\n     */\n    public String getString()  throws IOException {\n        return getString(-1);\n\n    }\n\n    /**\n     * create a Java String from Unicode character data corresponding\n     * to the original byte data supplied to the Charset detect operation.\n     * The length of the returned string is limited to the specified size;\n     * the string will be trunctated to this length if necessary.  A limit value of\n     * zero or less is ignored, and treated as no limit.\n     *\n     * @param maxLength The maximium length of the String to be created when the\n     *                  source of the data is an input stream, or -1 for\n     *                  unlimited length.\n     * @return a String created from the converted input data.\n     *\n     * @throws IOException if an IO error occurs.\n     *\n     */\n    String getString(int maxLength) throws IOException {\n        String result;\n        if (fInputStream != null) {\n            StringBuilder sb = new StringBuilder();\n            char[] buffer = new char[1024];\n            Reader reader = getReader();\n            int max = maxLength < 0? Integer.MAX_VALUE : maxLength;\n            int bytesRead;\n            \n            while ((bytesRead = reader.read(buffer, 0, Math.min(max, 1024))) >= 0) {\n                sb.append(buffer, 0, bytesRead);\n                max -= bytesRead;\n            }\n            \n            reader.close();\n            \n            return sb.toString();\n        } else {\n            String name = getName();\n            /*\n             * getName() may return a name with a suffix 'rtl' or 'ltr'. This cannot\n             * be used to open a charset (e.g. IBM424_rtl). The ending '_rtl' or 'ltr'\n             * should be stripped off before creating the string.\n             */\n            int startSuffix = !name.contains(\"_rtl\") ? name.indexOf(\"_ltr\") : name.indexOf(\"_rtl\");\n            if (startSuffix > 0) {\n                name = name.substring(0, startSuffix);\n            }\n            result = new String(fRawInput, name);\n        }\n        return result;\n\n    }\n    \n    /**\n     * Get an indication of the confidence in the charset detected.\n     * Confidence values range from 0-100, with larger numbers indicating\n     * a better match of the input data to the characteristics of the charset.\n     * @return the confidence in the charset match\n     */\n    public int getConfidence() {\n        return fConfidence;\n    }\n\n    /**\n     * Get the name of the detected charset.  \n     * The name will be one that can be used with other APIs on the\n     * platform that accept charset names.  It is the \"Canonical name\"\n     * as defined by the class java.nio.charset.Charset; for\n     * charsets that are registered with the IANA charset registry,\n     * this is the MIME-preferred registered name.\n     *\n     * @see java.nio.charset.Charset\n     * @see java.io.InputStreamReader\n     *\n     * @return The name of the charset.\n     *\n     */\n    public String getName() {\n        return fCharsetName;\n    }\n    \n    /**\n     * Get the ISO code for the language of the detected charset.  \n     *\n     * @return The ISO code for the language or <code>null</code> if the language cannot be determined.\n     *\n     */\n    public String getLanguage() {\n        return fLang;\n    }\n\n    /**\n     * Compare to other CharsetMatch objects.\n     * Comparison is based on the match confidence value, which \n     *   allows CharsetDetector.detectAll() to order its results. \n     *\n     * @param other the CharsetMatch object to compare against.\n     * @return  a negative integer, zero, or a positive integer as the \n     *          confidence level of this CharsetMatch\n     *          is less than, equal to, or greater than that of\n     *          the argument.\n     * @throws ClassCastException if the argument is not a CharsetMatch.\n     */\n    public int compareTo (@NotNull CharsetMatch other) {\n        if (this.fConfidence > other.fConfidence) {\n            return 1;\n        } else if (this.fConfidence < other.fConfidence) {\n            return -1;\n        }\n        return 0;\n    }\n\n    /*\n     *  Constructor.  Implementation internal\n     */\n    CharsetMatch(CharsetDetector det, CharsetRecognizer rec, int conf) {\n        fConfidence = conf;\n        \n        // The references to the original application input data must be copied out\n        //   of the charset recognizer to here, in case the application resets the\n        //   recognizer before using this CharsetMatch.\n        if (det.fInputStream == null) {\n            // We only want the existing input byte data if it came straight from the user,\n            //   not if is just the head of a stream.\n            fRawInput    = det.fRawInput;\n            fRawLength   = det.fRawLength;\n        }\n        fInputStream = det.fInputStream;\n        fCharsetName = rec.getName();\n        fLang = rec.getLanguage();\n    }\n\n    /*\n     *  Constructor.  Implementation internal\n     */\n    CharsetMatch(CharsetDetector det, int conf, String csName, String lang) {\n        fConfidence = conf;\n        // The references to the original application input data must be copied out  of the charset recognizer to here,\n        // in case the application resets the recognizer before using this CharsetMatch.\n        if (det.fInputStream == null) {\n            // We only want the existing input byte data if it came straight from the user,\n            //   not if is just the head of a stream.\n            fRawInput    = det.fRawInput;\n            fRawLength   = det.fRawLength;\n        }\n        fInputStream = det.fInputStream;\n        fCharsetName = csName;\n        fLang = lang;\n    }\n\n    private final int fConfidence;\n    private byte[] fRawInput;  // Original, untouched input bytes. If user gave us a byte array, this is it.\n    private int fRawLength;    // Length of data in fRawInput array.\n    private final InputStream fInputStream;  // User's input stream, or null if the user gave us a byte array.\n    private final String fCharsetName;  // The name of the charset this CharsetMatch  represents.  Filled in by the recognizer.\n    private final String fLang;  // The language, if one was determined by the recognizer during the detect operation.\n}\n"
  },
  {
    "path": "src/main/java/com/ibm/icu/text/CharsetRecog_2022.java",
    "content": "/*\n *******************************************************************************\n * Copyright (C) 2005 - 2012, International Business Machines Corporation and  *\n * others. All Rights Reserved.                                                *\n *******************************************************************************\n */\npackage com.ibm.icu.text;\n\n/**\n * class CharsetRecog_2022  part of the ICU charset detection implementation.\n * This is a superclass for the individual detectors for each of the detectable members of the ISO 2022 family of encodings.\n * The separate classes are nested within this class.\n */\nabstract class CharsetRecog_2022 extends CharsetRecognizer {\n\n    /**\n     * Matching function shared among the 2022 detectors JP, CN and KR Counts up the number of legal an\n     * unrecognized escape sequences in the sample of text, and computes a score based on the total number &\n     * the proportion that fit the encoding.\n     *\n     * @param text            the byte buffer containing text to analyse\n     * @param textLen         the size of the text in the byte.\n     * @param escapeSequences the byte escape sequences to test for.\n     * @return match quality, in the range of 0-100.\n     */\n    int match(byte[] text, int textLen, byte[][] escapeSequences) {\n        int hits = 0;\n        int misses = 0;\n        int shifts = 0;\n        int quality;\n        scanInput:\n        for (int i = 0; i < textLen; i++) {\n            if (text[i] == 0x1b) {\n                checkEscapes:\n                for (byte[] seq : escapeSequences) {\n                    if ((textLen - i) < seq.length) {\n                        continue checkEscapes;\n                    }\n                    for (int j = 1; j < seq.length; j++) {\n                        if (seq[j] != text[i + j]) {\n                            continue checkEscapes;\n                        }\n                    }\n                    hits++;\n                    i += seq.length - 1;\n                    continue scanInput;\n                }\n                misses++;\n            }\n\n            if (text[i] == 0x0e || text[i] == 0x0f) {\n                // Shift in/out\n                shifts++;\n            }\n        }\n        if (hits == 0) {\n            return 0;\n        }\n        // Initial quality is based on relative proportion of recognized vs.\n        //   unrecognized escape sequences. \n        //   All good:  quality = 100;\n        //   half or less good: quality = 0;\n        //   linear inbetween.\n        quality = (100 * hits - 100 * misses) / (hits + misses);\n\n        // Back off quality if there were too few escape sequences seen.\n        //   Include shifts in this computation, so that KR does not get penalized\n        //   for having only a single Escape sequence, but many shifts.\n        if (hits + shifts < 5) {\n            quality -= (5 - (hits + shifts)) * 10;\n        }\n        if (quality < 0) {\n            quality = 0;\n        }\n        return quality;\n    }\n\n\n    static class CharsetRecog_2022JP extends CharsetRecog_2022 {\n        private final byte[][] escapeSequences = {\n                {0x1b, 0x24, 0x28, 0x43},   // KS X 1001:1992\n                {0x1b, 0x24, 0x28, 0x44},   // JIS X 212-1990\n                {0x1b, 0x24, 0x40},         // JIS C 6226-1978\n                {0x1b, 0x24, 0x41},         // GB 2312-80\n                {0x1b, 0x24, 0x42},         // JIS X 208-1983\n                {0x1b, 0x26, 0x40},         // JIS X 208 1990, 1997\n                {0x1b, 0x28, 0x42},         // ASCII\n                {0x1b, 0x28, 0x48},         // JIS-Roman\n                {0x1b, 0x28, 0x49},         // Half-width katakana\n                {0x1b, 0x28, 0x4a},         // JIS-Roman\n                {0x1b, 0x2e, 0x41},         // ISO 8859-1\n                {0x1b, 0x2e, 0x46}          // ISO 8859-7\n        };\n\n        String getName() {\n            return \"ISO-2022-JP\";\n        }\n\n        CharsetMatch match(CharsetDetector det) {\n            int confidence = match(det.fInputBytes, det.fInputLen, escapeSequences);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n\n    static class CharsetRecog_2022KR extends CharsetRecog_2022 {\n        private final byte[][] escapeSequences = {\n                {0x1b, 0x24, 0x29, 0x43}\n        };\n\n        String getName() {\n            return \"ISO-2022-KR\";\n        }\n\n        CharsetMatch match(CharsetDetector det) {\n            int confidence = match(det.fInputBytes, det.fInputLen, escapeSequences);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n\n    static class CharsetRecog_2022CN extends CharsetRecog_2022 {\n        private final byte[][] escapeSequences = {\n                {0x1b, 0x24, 0x29, 0x41},   // GB 2312-80\n                {0x1b, 0x24, 0x29, 0x47},   // CNS 11643-1992 Plane 1\n                {0x1b, 0x24, 0x2A, 0x48},   // CNS 11643-1992 Plane 2\n                {0x1b, 0x24, 0x29, 0x45},   // ISO-IR-165\n                {0x1b, 0x24, 0x2B, 0x49},   // CNS 11643-1992 Plane 3\n                {0x1b, 0x24, 0x2B, 0x4A},   // CNS 11643-1992 Plane 4\n                {0x1b, 0x24, 0x2B, 0x4B},   // CNS 11643-1992 Plane 5\n                {0x1b, 0x24, 0x2B, 0x4C},   // CNS 11643-1992 Plane 6\n                {0x1b, 0x24, 0x2B, 0x4D},   // CNS 11643-1992 Plane 7\n                {0x1b, 0x4e},               // SS2\n                {0x1b, 0x4f},               // SS3\n        };\n\n        String getName() {\n            return \"ISO-2022-CN\";\n        }\n\n        CharsetMatch match(CharsetDetector det) {\n            int confidence = match(det.fInputBytes, det.fInputLen, escapeSequences);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n\n}\n\n"
  },
  {
    "path": "src/main/java/com/ibm/icu/text/CharsetRecog_UTF8.java",
    "content": "/*\n *******************************************************************************\n * Copyright (C) 2005 - 2012, International Business Machines Corporation and  *\n * others. All Rights Reserved.                                                *\n *******************************************************************************\n */\npackage com.ibm.icu.text;\n\n/**\n * Charset recognizer for UTF-8\n */\nclass CharsetRecog_UTF8 extends CharsetRecognizer {\n\n    String getName() {\n        return \"UTF-8\";\n    }\n\n    /* (non-Javadoc)\n     * @see com.ibm.icu.text.CharsetRecognizer#match(com.ibm.icu.text.CharsetDetector)\n     */\n    CharsetMatch match(CharsetDetector det) {\n        boolean hasBOM = false;\n        int numValid = 0;\n        int numInvalid = 0;\n        byte[] input = det.fRawInput;\n        int trailBytes;\n\n        if (det.fRawLength >= 3 &&\n                (input[0] & 0xFF) == 0xef && (input[1] & 0xFF) == 0xbb && (input[2] & 0xFF) == 0xbf) {\n            hasBOM = true;\n        }\n\n        // Scan for multibyte sequences\n        for (int i = 0; i < det.fRawLength; i++) {\n            int b = input[i];\n            if ((b & 0x80) == 0) {\n                continue;   // ASCII\n            }\n\n            // High bit on char found.  Figure out how long the sequence should be\n            if ((b & 0x0e0) == 0x0c0) {\n                trailBytes = 1;\n            } else if ((b & 0x0f0) == 0x0e0) {\n                trailBytes = 2;\n            } else if ((b & 0x0f8) == 0xf0) {\n                trailBytes = 3;\n            } else {\n                numInvalid++;\n                if (numInvalid > 5) {\n                    break;\n                }\n                trailBytes = 0;\n            }\n\n            // Verify that we've got the right number of trail bytes in the sequence\n            for (; ; ) {\n                i++;\n                if (i >= det.fRawLength) {\n                    break;\n                }\n                b = input[i];\n                if ((b & 0xc0) != 0x080) {\n                    numInvalid++;\n                    break;\n                }\n                if (--trailBytes == 0) {\n                    numValid++;\n                    break;\n                }\n            }\n        }\n        // Cook up some sort of confidence score, based on presense of a BOM  and the existence of valid\n        // and/or invalid multibyte sequences.\n        int confidence = 0;\n        if (hasBOM && numInvalid == 0) {\n            confidence = 100;\n        } else if (hasBOM && numValid > numInvalid * 10) {\n            confidence = 80;\n        } else if (numValid > 3 && numInvalid == 0) {\n            confidence = 100;\n        } else if (numValid > 0 && numInvalid == 0) {\n            confidence = 80;\n        } else if (numValid == 0 && numInvalid == 0) {\n            // Plain ASCII.  \n            confidence = 10;\n        } else if (numValid > numInvalid * 10) {\n            // Probably corrupt utf-8 data.  Valid sequences aren't likely by chance.\n            confidence = 25;\n        }\n        return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/ibm/icu/text/CharsetRecog_Unicode.java",
    "content": "/*\n *******************************************************************************\n * Copyright (C) 1996-2012, International Business Machines Corporation and    *\n * others. All Rights Reserved.                                                *\n *******************************************************************************\n *\n */\n\npackage com.ibm.icu.text;\n\n/**\n * This class matches UTF-16 and UTF-32, both big- and little-endian. The BOM will be used if it is present.\n */\nabstract class CharsetRecog_Unicode extends CharsetRecognizer {\n\n    @Override\n    abstract String getName();\n\n    @Override\n    abstract CharsetMatch match(CharsetDetector det);\n\n    static class CharsetRecog_UTF_16_BE extends CharsetRecog_Unicode {\n        String getName() {\n            return \"UTF-16BE\";\n        }\n\n        CharsetMatch match(CharsetDetector det) {\n            byte[] input = det.fRawInput;\n\n            if (input.length >= 2 && ((input[0] & 0xFF) == 0xFE && (input[1] & 0xFF) == 0xFF)) {\n                int confidence = 100;\n                return new CharsetMatch(det, this, confidence);\n            }\n\n            // TODO: Do some statistics to check for unsigned UTF-16BE\n            return null;\n        }\n    }\n\n    static class CharsetRecog_UTF_16_LE extends CharsetRecog_Unicode {\n        String getName() {\n            return \"UTF-16LE\";\n        }\n\n        CharsetMatch match(CharsetDetector det) {\n            byte[] input = det.fRawInput;\n\n            if (input.length >= 2 && ((input[0] & 0xFF) == 0xFF && (input[1] & 0xFF) == 0xFE)) {\n                // An LE BOM is present.\n                if (input.length >= 4 && input[2] == 0x00 && input[3] == 0x00) {\n                    // It is probably UTF-32 LE, not UTF-16\n                    return null;\n                }\n                int confidence = 100;\n                return new CharsetMatch(det, this, confidence);\n            }\n            // TODO: Do some statistics to check for unsigned UTF-16LE\n            return null;\n        }\n    }\n\n    static abstract class CharsetRecog_UTF_32 extends CharsetRecog_Unicode {\n        abstract int getChar(byte[] input, int index);\n\n        abstract String getName();\n\n        CharsetMatch match(CharsetDetector det) {\n            byte[] input = det.fRawInput;\n            int limit = (det.fRawLength / 4) * 4;\n            int numValid = 0;\n            int numInvalid = 0;\n            boolean hasBOM = false;\n            int confidence = 0;\n\n            if (limit == 0) {\n                return null;\n            }\n            if (getChar(input, 0) == 0x0000FEFF) {\n                hasBOM = true;\n            }\n\n            for (int i = 0; i < limit; i += 4) {\n                int ch = getChar(input, i);\n\n                if (ch < 0 || ch >= 0x10FFFF || (ch >= 0xD800 && ch <= 0xDFFF)) {\n                    numInvalid += 1;\n                } else {\n                    numValid += 1;\n                }\n            }\n\n            // Cook up some sort of confidence score, based on presence of a BOM\n            // and the existence of valid and/or invalid multibyte sequences.\n            if (hasBOM && numInvalid == 0) {\n                confidence = 100;\n            } else if (hasBOM && numValid > numInvalid * 10) {\n                confidence = 80;\n            } else if (numValid > 3 && numInvalid == 0) {\n                confidence = 100;\n            } else if (numValid > 0 && numInvalid == 0) {\n                confidence = 80;\n            } else if (numValid > numInvalid * 10) {\n                // Probably corrupt UTF-32BE data.  Valid sequences aren't likely by chance.\n                confidence = 25;\n            }\n\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n\n    static class CharsetRecog_UTF_32_BE extends CharsetRecog_UTF_32 {\n        int getChar(byte[] input, int index) {\n            return (input[index + 0] & 0xFF) << 24 | (input[index + 1] & 0xFF) << 16 |\n                    (input[index + 2] & 0xFF) << 8 | (input[index + 3] & 0xFF);\n        }\n\n        String getName() {\n            return \"UTF-32BE\";\n        }\n    }\n\n\n    static class CharsetRecog_UTF_32_LE extends CharsetRecog_UTF_32 {\n        int getChar(byte[] input, int index) {\n            return (input[index + 3] & 0xFF) << 24 | (input[index + 2] & 0xFF) << 16 |\n                    (input[index + 1] & 0xFF) << 8 | (input[index + 0] & 0xFF);\n        }\n\n        String getName() {\n            return \"UTF-32LE\";\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/ibm/icu/text/CharsetRecog_mbcs.java",
    "content": "/*\n ****************************************************************************\n * Copyright (C) 2005-2012, International Business Machines Corporation and *\n * others. All Rights Reserved.                                             *\n ****************************************************************************\n *\n */\npackage com.ibm.icu.text;\n\nimport java.util.Arrays;\n\n/**\n * CharsetRecognizer implemenation for Asian  - double or multi-byte - charsets.\n *                   Match is determined mostly by the input data adhering to the\n *                   encoding scheme for the charset, and, optionally,\n *                   frequency-of-occurence of characters.\n * <p/>\n *                   Instances of this class are singletons, one per encoding\n *                   being recognized.  They are created in the main\n *                   CharsetDetector class and kept in the global list of available\n *                   encodings to be checked.  The specific encoding being recognized\n *                   is determined by subclass.\n */\nabstract class CharsetRecog_mbcs extends CharsetRecognizer {\n\n   /**\n     * Get the IANA name of this charset.\n     * @return the charset name.\n     */\n    abstract String      getName() ;\n    \n    \n    /**\n     * Test the match of this charset with the input text data\n     *      which is obtained via the CharsetDetector object.\n     * \n     * @param det  The CharsetDetector, which contains the input text\n     *             to be checked for being in this charset.\n     * @return     Two values packed into one int  (Damn java, anyhow)\n     *             <br/>\n     *             bits 0-7:  the match confidence, ranging from 0-100\n     *             <br/>\n     *             bits 8-15: The match reason, an enum-like value.\n     */\n    int match(CharsetDetector det, int [] commonChars) {\n        @SuppressWarnings(\"unused\")\n        int   singleByteCharCount = 0;  //TODO Do we really need this?\n        int   doubleByteCharCount = 0;\n        int   commonCharCount     = 0;\n        int   badCharCount        = 0;\n        int   totalCharCount      = 0;\n        int   confidence          = 0;\n        iteratedChar   iter       = new iteratedChar();\n        \n        detectBlock: {\n            for (iter.reset(); nextChar(iter, det);) {\n                totalCharCount++;\n                if (iter.error) {\n                    badCharCount++; \n                } else {\n                    long cv = iter.charValue & 0xFFFFFFFFL;\n                                        \n                    if (cv <= 0xff) {\n                        singleByteCharCount++;\n                    } else {\n                        doubleByteCharCount++;\n                        if (commonChars != null) {\n                            // NOTE: This assumes that there are no 4-byte common chars.\n                            if (Arrays.binarySearch(commonChars, (int) cv) >= 0) {\n                                commonCharCount++;\n                            }\n                        }\n                    }\n                }\n                if (badCharCount >= 2 && badCharCount*5 >= doubleByteCharCount) {\n                    // Bail out early if the byte data is not matching the encoding scheme.\n                    break detectBlock;\n                }\n            }\n            \n            if (doubleByteCharCount <= 10 && badCharCount== 0) {\n                // Not many multi-byte chars.\n                if (doubleByteCharCount == 0 && totalCharCount < 10) {\n                    // There weren't any multibyte sequences, and there was a low density of non-ASCII single bytes.\n                    // We don't have enough data to have any confidence.\n                    // Statistical analysis of single byte non-ASCII charcters would probably help here.\n                    confidence = 0;\n                }\n                else {\n                    //   ASCII or ISO file?  It's probably not our encoding,\n                    //   but is not incompatible with our encoding, so don't give it a zero.\n                    confidence = 10;\n                }\n                \n                break detectBlock;\n            }\n            \n            //\n            //  No match if there are too many characters that don't fit the encoding scheme.\n            //    (should we have zero tolerance for these?)\n            //\n            if (doubleByteCharCount < 20*badCharCount) {\n                confidence = 0;\n                break detectBlock;\n            }\n            \n            if (commonChars == null) {\n                // We have no statistics on frequently occuring characters.\n                //  Assess confidence purely on having a reasonable number of\n                //  multi-byte characters (the more the better\n                confidence = 30 + doubleByteCharCount - 20*badCharCount;\n                if (confidence > 100) {\n                    confidence = 100;\n                }\n            }else {\n                //\n                // Frequency of occurence statistics exist.\n                //\n                double maxVal = Math.log((float)doubleByteCharCount / 4);\n                double scaleFactor = 90.0 / maxVal;\n                confidence = (int)(Math.log(commonCharCount+1) * scaleFactor + 10);\n                confidence = Math.min(confidence, 100);\n            }\n        }   // end of detectBlock:\n        \n        return confidence;\n    }\n    \n     // \"Character\"  iterated character class.\n     //    Recognizers for specific mbcs encodings make their \"characters\" available\n     //    by providing a nextChar() function that fills in an instance of iteratedChar\n     //    with the next char from the input.\n     //    The returned characters are not converted to Unicode, but remain as the raw\n     //    bytes (concatenated into an int) from the codepage data.\n     //\n     //  For Asian charsets, use the raw input rather than the input that has been\n     //   stripped of markup.  Detection only considers multi-byte chars, effectively\n     //   stripping markup anyway, and double byte chars do occur in markup too.\n     //\n     static class iteratedChar {\n         int             charValue = 0;             // 1-4 bytes from the raw input data\n         int             index     = 0;\n         int             nextIndex = 0;\n         boolean         error     = false;\n         boolean         done      = false;\n         \n         void reset() {\n             charValue = 0;\n             index     = -1;\n             nextIndex = 0;\n             error     = false;\n             done      = false;\n         }\n         \n         int nextByte(CharsetDetector det) {\n             if (nextIndex >= det.fRawLength) {\n                 done = true;\n                 return -1;\n             }\n             return (int)det.fRawInput[nextIndex++] & 0x00ff;\n         }       \n     }\n     \n     /**\n      * Get the next character (however many bytes it is) from the input data\n      *    Subclasses for specific charset encodings must implement this function\n      *    to get characters according to the rules of their encoding scheme.\n      *  This function is not a method of class iteratedChar only because\n      *   that would require a lot of extra derived classes, which is awkward.\n      * @param it  The iteratedChar \"struct\" into which the returned char is placed.\n      * @param det The charset detector, which is needed to get at the input byte data\n      *            being iterated over.\n      * @return    True if a character was returned, false at end of input.\n      */\n     abstract boolean nextChar(iteratedChar it, CharsetDetector det);\n     \n\n\n     \n     \n     /**\n      *   Shift-JIS charset recognizer.   \n      *\n      */\n     static class CharsetRecog_sjis extends CharsetRecog_mbcs {\n         static int [] commonChars = \n             // TODO:  This set of data comes from the character frequency-\n             //        of-occurence analysis tool.  The data needs to be moved\n             //        into a resource and loaded from there.\n            {0x8140, 0x8141, 0x8142, 0x8145, 0x815b, 0x8169, 0x816a, 0x8175, 0x8176, 0x82a0, \n             0x82a2, 0x82a4, 0x82a9, 0x82aa, 0x82ab, 0x82ad, 0x82af, 0x82b1, 0x82b3, 0x82b5, \n             0x82b7, 0x82bd, 0x82be, 0x82c1, 0x82c4, 0x82c5, 0x82c6, 0x82c8, 0x82c9, 0x82cc, \n             0x82cd, 0x82dc, 0x82e0, 0x82e7, 0x82e8, 0x82e9, 0x82ea, 0x82f0, 0x82f1, 0x8341, \n             0x8343, 0x834e, 0x834f, 0x8358, 0x835e, 0x8362, 0x8367, 0x8375, 0x8376, 0x8389, \n             0x838a, 0x838b, 0x838d, 0x8393, 0x8e96, 0x93fa, 0x95aa};\n         \n         boolean nextChar(iteratedChar it, CharsetDetector det) {\n             it.index = it.nextIndex;\n             it.error = false;\n             int firstByte;\n             firstByte = it.charValue = it.nextByte(det);\n             if (firstByte < 0) {\n                 return false;\n             }\n             \n             if (firstByte <= 0x7f || (firstByte>0xa0 && firstByte<=0xdf)) {\n                 return true;\n             }\n             \n             int secondByte = it.nextByte(det);\n             if (secondByte < 0)  {\n                 return false;          \n             }\n             it.charValue = (firstByte << 8) | secondByte;\n             if (! ((secondByte>=0x40 && secondByte<=0x7f) || (secondByte>=0x80 && secondByte<=0xff))) {\n                 // Illegal second byte value.\n                 it.error = true;\n             }\n             return true;\n         }\n         \n         CharsetMatch match(CharsetDetector det) {\n             int confidence = match(det, commonChars);\n             return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n         }\n         \n         String getName() {\n             return \"Shift_JIS\";\n         }\n         \n         public String getLanguage()\n         {\n             return \"ja\";\n         }\n\n         \n     }\n     \n     \n     /**\n      *   Big5 charset recognizer.   \n      *\n      */\n     static class CharsetRecog_big5 extends CharsetRecog_mbcs {\n         static int [] commonChars = \n             // TODO:  This set of data comes from the character frequency-\n             //        of-occurence analysis tool.  The data needs to be moved\n             //        into a resource and loaded from there.\n            {0xa140, 0xa141, 0xa142, 0xa143, 0xa147, 0xa149, 0xa175, 0xa176, 0xa440, 0xa446, \n             0xa447, 0xa448, 0xa451, 0xa454, 0xa457, 0xa464, 0xa46a, 0xa46c, 0xa477, 0xa4a3, \n             0xa4a4, 0xa4a7, 0xa4c1, 0xa4ce, 0xa4d1, 0xa4df, 0xa4e8, 0xa4fd, 0xa540, 0xa548, \n             0xa558, 0xa569, 0xa5cd, 0xa5e7, 0xa657, 0xa661, 0xa662, 0xa668, 0xa670, 0xa6a8, \n             0xa6b3, 0xa6b9, 0xa6d3, 0xa6db, 0xa6e6, 0xa6f2, 0xa740, 0xa751, 0xa759, 0xa7da, \n             0xa8a3, 0xa8a5, 0xa8ad, 0xa8d1, 0xa8d3, 0xa8e4, 0xa8fc, 0xa9c0, 0xa9d2, 0xa9f3, \n             0xaa6b, 0xaaba, 0xaabe, 0xaacc, 0xaafc, 0xac47, 0xac4f, 0xacb0, 0xacd2, 0xad59, \n             0xaec9, 0xafe0, 0xb0ea, 0xb16f, 0xb2b3, 0xb2c4, 0xb36f, 0xb44c, 0xb44e, 0xb54c, \n             0xb5a5, 0xb5bd, 0xb5d0, 0xb5d8, 0xb671, 0xb7ed, 0xb867, 0xb944, 0xbad8, 0xbb44, \n             0xbba1, 0xbdd1, 0xc2c4, 0xc3b9, 0xc440, 0xc45f};\n          \n         boolean nextChar(iteratedChar it, CharsetDetector det) {\n             it.index = it.nextIndex;\n             it.error = false;\n             int firstByte;\n             firstByte = it.charValue = it.nextByte(det);\n             if (firstByte < 0) {\n                 return false;\n             }\n             \n             if (firstByte <= 0x7f || firstByte==0xff) {\n                 // single byte character.\n                 return true;\n             }\n             \n             int secondByte = it.nextByte(det);\n             if (secondByte < 0)  {\n                 return false;          \n             }\n             it.charValue = (it.charValue << 8) | secondByte;\n\n             if (secondByte < 0x40 ||\n                 secondByte ==0x7f ||\n                 secondByte == 0xff) {\n                     it.error = true;\n             }\n             return true;\n         }\n         \n         CharsetMatch match(CharsetDetector det) {\n             int confidence = match(det, commonChars);\n             return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n         }\n         \n         String getName() {\n             return \"Big5\";\n         }\n         \n         \n         public String getLanguage()\n         {\n             return \"zh\";\n         }\n     }\n     \n     \n     /**\n      *   EUC charset recognizers.  One abstract class that provides the common function\n      *             for getting the next character according to the EUC encoding scheme,\n      *             and nested derived classes for EUC_KR, EUC_JP, EUC_CN.   \n      *\n      */\n     abstract static class CharsetRecog_euc extends CharsetRecog_mbcs {\n         \n         /*\n          *  (non-Javadoc)\n          *  Get the next character value for EUC based encodings.\n          *  Character \"value\" is simply the raw bytes that make up the character\n          *     packed into an int.\n          */\n         boolean nextChar(iteratedChar it, CharsetDetector det) {\n             it.index = it.nextIndex;\n             it.error = false;\n             int firstByte;\n             int secondByte;\n             int thirdByte;\n\n             buildChar: {\n                 firstByte = it.charValue = it.nextByte(det);                 \n                 if (firstByte < 0) {\n                     // Ran off the end of the input data\n                     it.done = true;\n                     break buildChar;\n                 }\n                 if (firstByte <= 0x8d) {\n                     // single byte char\n                     break buildChar;\n                 }\n                 \n                 secondByte = it.nextByte(det);\n                 it.charValue = (it.charValue << 8) | secondByte;\n                 \n                 if (firstByte >= 0xA1 && firstByte <= 0xfe) {\n                     // Two byte Char\n                     if (secondByte < 0xa1) {\n                         it.error = true;\n                     }\n                     break buildChar;\n                 }\n                 if (firstByte == 0x8e) {\n                     // Code Set 2.\n                     //   In EUC-JP, total char size is 2 bytes, only one byte of actual char value.\n                     //   In EUC-TW, total char size is 4 bytes, three bytes contribute to char value.\n                     // We don't know which we've got.\n                     // Treat it like EUC-JP.  If the data really was EUC-TW, the following two\n                     //   bytes will look like a well formed 2 byte char.  \n                     if (secondByte < 0xa1) {\n                         it.error = true;\n                     }\n                     break buildChar;                     \n                 }\n                 \n                 if (firstByte == 0x8f) {\n                     // Code set 3.\n                     // Three byte total char size, two bytes of actual char value.\n                     thirdByte    = it.nextByte(det);\n                     it.charValue = (it.charValue << 8) | thirdByte;\n                     if (thirdByte < 0xa1) {\n                         it.error = true;\n                     }\n                 }\n              }\n             \n             return !it.done;\n         }\n         \n         /**\n          * The charset recognize for EUC-JP.  A singleton instance of this class\n          *    is created and kept by the public CharsetDetector class\n          */\n         static class CharsetRecog_euc_jp extends CharsetRecog_euc {\n             static int [] commonChars = \n                 // TODO:  This set of data comes from the character frequency-\n                 //        of-occurrence analysis tool.  The data needs to be moved\n                 //        into a resource and loaded from there.\n                {0xa1a1, 0xa1a2, 0xa1a3, 0xa1a6, 0xa1bc, 0xa1ca, 0xa1cb, 0xa1d6, 0xa1d7, 0xa4a2, \n                 0xa4a4, 0xa4a6, 0xa4a8, 0xa4aa, 0xa4ab, 0xa4ac, 0xa4ad, 0xa4af, 0xa4b1, 0xa4b3, \n                 0xa4b5, 0xa4b7, 0xa4b9, 0xa4bb, 0xa4bd, 0xa4bf, 0xa4c0, 0xa4c1, 0xa4c3, 0xa4c4, \n                 0xa4c6, 0xa4c7, 0xa4c8, 0xa4c9, 0xa4ca, 0xa4cb, 0xa4ce, 0xa4cf, 0xa4d0, 0xa4de, \n                 0xa4df, 0xa4e1, 0xa4e2, 0xa4e4, 0xa4e8, 0xa4e9, 0xa4ea, 0xa4eb, 0xa4ec, 0xa4ef, \n                 0xa4f2, 0xa4f3, 0xa5a2, 0xa5a3, 0xa5a4, 0xa5a6, 0xa5a7, 0xa5aa, 0xa5ad, 0xa5af, \n                 0xa5b0, 0xa5b3, 0xa5b5, 0xa5b7, 0xa5b8, 0xa5b9, 0xa5bf, 0xa5c3, 0xa5c6, 0xa5c7, \n                 0xa5c8, 0xa5c9, 0xa5cb, 0xa5d0, 0xa5d5, 0xa5d6, 0xa5d7, 0xa5de, 0xa5e0, 0xa5e1, \n                 0xa5e5, 0xa5e9, 0xa5ea, 0xa5eb, 0xa5ec, 0xa5ed, 0xa5f3, 0xb8a9, 0xb9d4, 0xbaee, \n                 0xbbc8, 0xbef0, 0xbfb7, 0xc4ea, 0xc6fc, 0xc7bd, 0xcab8, 0xcaf3, 0xcbdc, 0xcdd1};             \n             String getName() {\n                 return \"EUC-JP\";\n             }\n             \n             CharsetMatch match(CharsetDetector det) {\n                 int confidence = match(det, commonChars);\n                 return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n             }\n             \n             public String getLanguage()\n             {\n                 return \"ja\";\n             }\n         }\n         \n         /**\n          * The charset recognize for EUC-KR.  A singleton instance of this class\n          *    is created and kept by the public CharsetDetector class\n          */\n         static class CharsetRecog_euc_kr extends CharsetRecog_euc {\n             static int [] commonChars = \n                 // TODO:  This set of data comes from the character frequency-\n                 //        of-occurrence analysis tool.  The data needs to be moved\n                 //        into a resource and loaded from there.\n                {0xb0a1, 0xb0b3, 0xb0c5, 0xb0cd, 0xb0d4, 0xb0e6, 0xb0ed, 0xb0f8, 0xb0fa, 0xb0fc, \n                 0xb1b8, 0xb1b9, 0xb1c7, 0xb1d7, 0xb1e2, 0xb3aa, 0xb3bb, 0xb4c2, 0xb4cf, 0xb4d9, \n                 0xb4eb, 0xb5a5, 0xb5b5, 0xb5bf, 0xb5c7, 0xb5e9, 0xb6f3, 0xb7af, 0xb7c2, 0xb7ce, \n                 0xb8a6, 0xb8ae, 0xb8b6, 0xb8b8, 0xb8bb, 0xb8e9, 0xb9ab, 0xb9ae, 0xb9cc, 0xb9ce, \n                 0xb9fd, 0xbab8, 0xbace, 0xbad0, 0xbaf1, 0xbbe7, 0xbbf3, 0xbbfd, 0xbcad, 0xbcba, \n                 0xbcd2, 0xbcf6, 0xbdba, 0xbdc0, 0xbdc3, 0xbdc5, 0xbec6, 0xbec8, 0xbedf, 0xbeee, \n                 0xbef8, 0xbefa, 0xbfa1, 0xbfa9, 0xbfc0, 0xbfe4, 0xbfeb, 0xbfec, 0xbff8, 0xc0a7, \n                 0xc0af, 0xc0b8, 0xc0ba, 0xc0bb, 0xc0bd, 0xc0c7, 0xc0cc, 0xc0ce, 0xc0cf, 0xc0d6, \n                 0xc0da, 0xc0e5, 0xc0fb, 0xc0fc, 0xc1a4, 0xc1a6, 0xc1b6, 0xc1d6, 0xc1df, 0xc1f6, \n                 0xc1f8, 0xc4a1, 0xc5cd, 0xc6ae, 0xc7cf, 0xc7d1, 0xc7d2, 0xc7d8, 0xc7e5, 0xc8ad};\n             \n             String getName() {\n                 return \"EUC-KR\";\n             }\n             \n             CharsetMatch match(CharsetDetector det) {\n                 int confidence = match(det, commonChars);\n                 return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n             }\n             \n             public String getLanguage()\n             {\n                 return \"ko\";\n             }\n         }\n     }\n     \n     /**\n      * \n      *   GB-18030 recognizer. Uses simplified Chinese statistics.   \n      *\n      */\n     static class CharsetRecog_gb_18030 extends CharsetRecog_mbcs {\n         \n         /*\n          *  (non-Javadoc)\n          *  Get the next character value for EUC based encodings.\n          *  Character \"value\" is simply the raw bytes that make up the character\n          *     packed into an int.\n          */\n         boolean nextChar(iteratedChar it, CharsetDetector det) {\n             it.index = it.nextIndex;\n             it.error = false;\n             int firstByte;\n             int secondByte;\n             int thirdByte;\n             int fourthByte;\n             \n             buildChar: {\n                 firstByte = it.charValue = it.nextByte(det); \n                 \n                 if (firstByte < 0) {\n                     // Ran off the end of the input data\n                     it.done = true;\n                     break buildChar;\n                 }\n                 \n                 if (firstByte <= 0x80) {\n                     // single byte char\n                     break buildChar;\n                 }\n                 \n                 secondByte = it.nextByte(det);\n                 it.charValue = (it.charValue << 8) | secondByte;\n                 \n                 if (firstByte >= 0x81 && firstByte <= 0xFE) {\n                     // Two byte Char\n                     if ((secondByte >= 0x40 && secondByte <= 0x7E) || (secondByte >=80 && secondByte <=0xFE)) {\n                         break buildChar;\n                     }\n                     \n                     // Four byte char\n                     if (secondByte >= 0x30 && secondByte <= 0x39) {\n                         thirdByte = it.nextByte(det);\n                         \n                         if (thirdByte >= 0x81 && thirdByte <= 0xFE) {\n                             fourthByte = it.nextByte(det);\n                             \n                             if (fourthByte >= 0x30 && fourthByte <= 0x39) {\n                                 it.charValue = (it.charValue << 16) | (thirdByte << 8) | fourthByte;\n                                 break buildChar;\n                             }\n                         }\n                     }\n                     \n                     it.error = true;\n                     break buildChar;\n                 }\n             }\n                 \n             return !it.done;\n         }\n         \n         static int [] commonChars = \n             // TODO:  This set of data comes from the character frequency-\n             //        of-occurrence analysis tool.  The data needs to be moved\n             //        into a resource and loaded from there.\n            {0xa1a1, 0xa1a2, 0xa1a3, 0xa1a4, 0xa1b0, 0xa1b1, 0xa1f1, 0xa1f3, 0xa3a1, 0xa3ac, \n             0xa3ba, 0xb1a8, 0xb1b8, 0xb1be, 0xb2bb, 0xb3c9, 0xb3f6, 0xb4f3, 0xb5bd, 0xb5c4, \n             0xb5e3, 0xb6af, 0xb6d4, 0xb6e0, 0xb7a2, 0xb7a8, 0xb7bd, 0xb7d6, 0xb7dd, 0xb8b4, \n             0xb8df, 0xb8f6, 0xb9ab, 0xb9c9, 0xb9d8, 0xb9fa, 0xb9fd, 0xbacd, 0xbba7, 0xbbd6, \n             0xbbe1, 0xbbfa, 0xbcbc, 0xbcdb, 0xbcfe, 0xbdcc, 0xbecd, 0xbedd, 0xbfb4, 0xbfc6, \n             0xbfc9, 0xc0b4, 0xc0ed, 0xc1cb, 0xc2db, 0xc3c7, 0xc4dc, 0xc4ea, 0xc5cc, 0xc6f7, \n             0xc7f8, 0xc8ab, 0xc8cb, 0xc8d5, 0xc8e7, 0xc9cf, 0xc9fa, 0xcab1, 0xcab5, 0xcac7, \n             0xcad0, 0xcad6, 0xcaf5, 0xcafd, 0xccec, 0xcdf8, 0xceaa, 0xcec4, 0xced2, 0xcee5, \n             0xcfb5, 0xcfc2, 0xcfd6, 0xd0c2, 0xd0c5, 0xd0d0, 0xd0d4, 0xd1a7, 0xd2aa, 0xd2b2, \n             0xd2b5, 0xd2bb, 0xd2d4, 0xd3c3, 0xd3d0, 0xd3fd, 0xd4c2, 0xd4da, 0xd5e2, 0xd6d0};\n\n         \n         String getName() {\n             return \"GB18030\";\n         }\n         \n         CharsetMatch match(CharsetDetector det) {\n             int confidence = match(det, commonChars);\n             return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n         }\n         \n         public String getLanguage()\n         {\n             return \"zh\";\n         }\n     }\n     \n     \n}\n"
  },
  {
    "path": "src/main/java/com/ibm/icu/text/CharsetRecog_sbcs.java",
    "content": "/*\n ****************************************************************************\n * Copyright (C) 2005-2013, International Business Machines Corporation and *\n * others. All Rights Reserved.                                             *\n ************************************************************************** *\n *\n * Modified by Oleg Trifonov\n * added cp866 support\n */\n\npackage com.ibm.icu.text;\n\n\n/**\n * This class recognizes single-byte encodings. Because the encoding scheme is so\n * simple, language statistics are used to do the matching.\n */\nabstract class CharsetRecog_sbcs extends CharsetRecognizer {\n\n    /* (non-Javadoc)\n     * @see com.ibm.icu.text.CharsetRecognizer#getName()\n     */\n    abstract String getName();\n\n    static class NGramParser\n    {\n//        private static final int N_GRAM_SIZE = 3;\n        private static final int N_GRAM_MASK = 0xFFFFFF;\n\n        protected int byteIndex = 0;\n        private int ngram;\n        \n        private final int[] ngramList;\n        protected byte[] byteMap;\n        \n        private int ngramCount;\n        private int hitCount;\n        \n        protected byte spaceChar;\n        \n        public NGramParser(int[] theNgramList, byte[] theByteMap)\n        {\n            ngramList = theNgramList;\n            byteMap   = theByteMap;\n            \n            ngram = 0;\n            \n            ngramCount = hitCount = 0;\n        }\n        \n        /*\n         * Binary search for value in table, which must have exactly 64 entries.\n         */\n        private static int search(int[] table, int value)\n        {\n            int index = 0;\n            \n            if (table[index + 32] <= value) {\n                index += 32;\n            }\n            \n            if (table[index + 16] <= value) {\n                index += 16;\n            }\n\n            if (table[index + 8] <= value) {\n                index += 8;\n            }\n\n            if (table[index + 4] <= value) {\n                index += 4;\n            }\n\n            if (table[index + 2] <= value) {\n                index += 2;\n            }\n\n            if (table[index + 1] <= value) {\n                index += 1;\n            }\n\n            if (table[index] > value) {\n                index -= 1;\n            }\n            \n            if (index < 0 || table[index] != value) {\n                return -1;\n            }\n            \n            return index;\n        }\n\n        private void lookup(int thisNgram)\n        {\n            ngramCount += 1;\n            \n            if (search(ngramList, thisNgram) >= 0) {\n                hitCount += 1;\n            }\n            \n        }\n        \n        protected void addByte(int b)\n        {\n            ngram = ((ngram << 8) + (b & 0xFF)) & N_GRAM_MASK;\n            lookup(ngram);\n        }\n        \n        private int nextByte(CharsetDetector det)\n        {\n            if (byteIndex >= det.fInputLen) {\n                return -1;\n            }\n            \n            return det.fInputBytes[byteIndex++] & 0xFF;\n        }\n        \n        protected void parseCharacters(CharsetDetector det)\n        {\n            int b;\n            boolean ignoreSpace = false;\n            \n            while ((b = nextByte(det)) >= 0) {\n                byte mb = byteMap[b];\n                \n                // TODO: 0x20 might not be a space in all character sets...\n                if (mb != 0) {\n                    if (!(mb == spaceChar && ignoreSpace)) {\n                        addByte(mb);                    \n                    }\n                    \n                    ignoreSpace = (mb == spaceChar);\n                }\n            }\n            \n        }\n\n        public int parse(CharsetDetector det)\n        {\n            return parse (det, (byte)0x20);\n        }\n        public int parse(CharsetDetector det, byte spaceCh)\n        {\n            \n            this.spaceChar = spaceCh;\n            \n            parseCharacters(det);\n            \n            // TODO: Is this OK? The buffer could have ended in the middle of a word...\n            addByte(spaceChar);\n\n            double rawPercent = (double) hitCount / (double) ngramCount;\n            \n//                if (rawPercent <= 2.0) {\n//                    return 0;\n//                }\n            \n            // TODO - This is a bit of a hack to take care of a case\n            // were we were getting a confidence of 135...\n            if (rawPercent > 0.33) {\n                return 98;\n            }\n            \n            return (int) (rawPercent * 300.0);\n        }\n    }\n        \n    static class NGramParser_IBM420 extends NGramParser\n    {\n        private byte alef = 0x00;\n        \n        protected static byte[] unshapeMap = {\n/*                 -0           -1           -2           -3           -4           -5           -6           -7           -8           -9           -A           -B           -C           -D           -E           -F   */\n/* 0- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* 1- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* 2- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* 3- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* 4- */    (byte) 0x40, (byte) 0x40, (byte) 0x42, (byte) 0x42, (byte) 0x44, (byte) 0x45, (byte) 0x46, (byte) 0x47, (byte) 0x47, (byte) 0x49, (byte) 0x4A, (byte) 0x4B, (byte) 0x4C, (byte) 0x4D, (byte) 0x4E, (byte) 0x4F, \n/* 5- */    (byte) 0x50, (byte) 0x49, (byte) 0x52, (byte) 0x53, (byte) 0x54, (byte) 0x55, (byte) 0x56, (byte) 0x56, (byte) 0x58, (byte) 0x58, (byte) 0x5A, (byte) 0x5B, (byte) 0x5C, (byte) 0x5D, (byte) 0x5E, (byte) 0x5F, \n/* 6- */    (byte) 0x60, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x63, (byte) 0x65, (byte) 0x65, (byte) 0x67, (byte) 0x67, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F, \n/* 7- */    (byte) 0x69, (byte) 0x71, (byte) 0x71, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, (byte) 0x77, (byte) 0x79, (byte) 0x7A, (byte) 0x7B, (byte) 0x7C, (byte) 0x7D, (byte) 0x7E, (byte) 0x7F, \n/* 8- */    (byte) 0x80, (byte) 0x81, (byte) 0x82, (byte) 0x83, (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, (byte) 0x88, (byte) 0x89, (byte) 0x80, (byte) 0x8B, (byte) 0x8B, (byte) 0x8D, (byte) 0x8D, (byte) 0x8F, \n/* 9- */    (byte) 0x90, (byte) 0x91, (byte) 0x92, (byte) 0x93, (byte) 0x94, (byte) 0x95, (byte) 0x96, (byte) 0x97, (byte) 0x98, (byte) 0x99, (byte) 0x9A, (byte) 0x9A, (byte) 0x9A, (byte) 0x9A, (byte) 0x9E, (byte) 0x9E, \n/* A- */    (byte) 0x9E, (byte) 0xA1, (byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, (byte) 0xA8, (byte) 0xA9, (byte) 0x9E, (byte) 0xAB, (byte) 0xAB, (byte) 0xAD, (byte) 0xAD, (byte) 0xAF, \n/* B- */    (byte) 0xAF, (byte) 0xB1, (byte) 0xB2, (byte) 0xB3, (byte) 0xB4, (byte) 0xB5, (byte) 0xB6, (byte) 0xB7, (byte) 0xB8, (byte) 0xB9, (byte) 0xB1, (byte) 0xBB, (byte) 0xBB, (byte) 0xBD, (byte) 0xBD, (byte) 0xBF, \n/* C- */    (byte) 0xC0, (byte) 0xC1, (byte) 0xC2, (byte) 0xC3, (byte) 0xC4, (byte) 0xC5, (byte) 0xC6, (byte) 0xC7, (byte) 0xC8, (byte) 0xC9, (byte) 0xCA, (byte) 0xBF, (byte) 0xCC, (byte) 0xBF, (byte) 0xCE, (byte) 0xCF, \n/* D- */    (byte) 0xD0, (byte) 0xD1, (byte) 0xD2, (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0xD7, (byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0xDA, (byte) 0xDC, (byte) 0xDC, (byte) 0xDC, (byte) 0xDF, \n/* E- */    (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF, \n/* F- */    (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7, (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0xFF, \n        };\n    \n\n        public NGramParser_IBM420(int[] theNgramList, byte[] theByteMap)\n        {\n           super(theNgramList, theByteMap);\n        }\n        \n        private byte isLamAlef(byte b) {\n             if(b == (byte)0xb2 || b == (byte)0xb3){\n                 return (byte)0x47;\n             }else if(b == (byte)0xb4 || b == (byte)0xb5){\n                 return (byte)0x49;\n             }else if(b == (byte)0xb8 || b == (byte)0xb9){\n                 return (byte)0x56;\n             }else\n                 return (byte)0x00;\n         }\n        \n        /*\n         * Arabic shaping needs to be done manually. Cannot call ArabicShaping class\n         * because CharsetDetector is dealing with bytes not Unicode code points. We could\n         * convert the bytes to Unicode code points but that would leave us dependent\n         * on CharsetICU which we try to avoid. IBM420 converter amongst different versions\n         * of JDK can produce different results and therefore is also avoided.\n         */\n         private int nextByte(CharsetDetector det)\n         {\n             if (byteIndex >= det.fInputLen || det.fInputBytes[byteIndex] == 0) {\n                 return -1;\n             }              \n            int next;\n             \n            alef = isLamAlef(det.fInputBytes[byteIndex]);\n            if(alef != (byte)0x00)\n                next = 0xB1 & 0xFF;\n            else\n                next = unshapeMap[det.fInputBytes[byteIndex]& 0xFF] & 0xFF;\n            \n            byteIndex++;\n             \n            return next;\n         }\n         \n         protected void parseCharacters(CharsetDetector det)\n         {\n              int b;\n             boolean ignoreSpace = false;\n             \n             while ((b = nextByte(det)) >= 0) {\n                 byte mb = byteMap[b];\n                 \n                 // TODO: 0x20 might not be a space in all character sets...\n                 if (mb != 0) {\n                     if (!(mb == spaceChar && ignoreSpace)) {\n                         addByte(mb);                    \n                     }\n                     \n                     ignoreSpace = (mb == spaceChar);\n                 }\n                 if(alef != (byte)0x00){\n                     mb = byteMap[alef & 0xFF];\n                     \n                     // TODO: 0x20 might not be a space in all character sets...\n                     if (mb != 0) {\n                         if (!(mb == spaceChar && ignoreSpace)) {\n                             addByte(mb);                    \n                         }\n                         \n                         ignoreSpace = (mb == spaceChar);\n                     }\n                     \n                 }\n             }\n        }\n    }\n        \n     \n    int match(CharsetDetector det, int[] ngrams,  byte[] byteMap)\n    {\n        return match(det, ngrams, byteMap, (byte)0x20);\n    }\n    \n    int match(CharsetDetector det, int[] ngrams,  byte[] byteMap, byte spaceChar) {\n        NGramParser parser = new NGramParser(ngrams, byteMap);\n        return parser.parse(det, spaceChar);\n    }\n    \n    int matchIBM420(CharsetDetector det, int[] ngrams,  byte[] byteMap, byte spaceChar){\n        NGramParser_IBM420 parser = new NGramParser_IBM420(ngrams, byteMap);\n        return parser.parse(det, spaceChar);\n    }\n    \n    static class NGramsPlusLang {\n        int[] fNGrams;\n        String  fLang;\n        NGramsPlusLang(String la, int [] ng) {\n            fLang   = la;\n            fNGrams = ng;\n        }\n    }\n\n    static class CharsetRecog_8859_1 extends CharsetRecog_sbcs\n    {\n        protected static byte[] byteMap = {\n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, \n            (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F, \n            (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, \n            (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, \n            (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F, \n            (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, \n            (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0xAA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xB5, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0xBA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, \n            (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF, \n            (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0x20, \n            (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0xDF, \n            (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, \n            (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF, \n            (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0x20, \n            (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0xFF, \n        };\n        \n       \n        private static final NGramsPlusLang[] ngrams_8859_1 = new NGramsPlusLang[] {\n            new NGramsPlusLang(\n                    \"da\", \n                    new int[] {\n                            // ' af'\n                            0x206166, 0x206174, 0x206465, 0x20656E, 0x206572, 0x20666F, 0x206861, 0x206920, 0x206D65, 0x206F67, 0x2070E5, 0x207369, 0x207374, 0x207469, 0x207669, 0x616620, \n                            0x616E20, 0x616E64, 0x617220, 0x617420, 0x646520, 0x64656E, 0x646572, 0x646574, 0x652073, 0x656420, 0x656465, 0x656E20, 0x656E64, 0x657220, 0x657265, 0x657320, \n                            0x657420, 0x666F72, 0x676520, 0x67656E, 0x676572, 0x696765, 0x696C20, 0x696E67, 0x6B6520, 0x6B6B65, 0x6C6572, 0x6C6967, 0x6C6C65, 0x6D6564, 0x6E6465, 0x6E6520, \n                            0x6E6720, 0x6E6765, 0x6F6720, 0x6F6D20, 0x6F7220, 0x70E520, 0x722064, 0x722065, 0x722073, 0x726520, 0x737465, 0x742073, 0x746520, 0x746572, 0x74696C, 0x766572, \n                    }),\n            new NGramsPlusLang(\n                    \"de\",\n                    new int[] {\n                            // ' an'\n                            0x20616E, 0x206175, 0x206265, 0x206461, 0x206465, 0x206469, 0x206569, 0x206765, 0x206861, 0x20696E, 0x206D69, 0x207363, 0x207365, 0x20756E, 0x207665, 0x20766F, \n                            0x207765, 0x207A75, 0x626572, 0x636820, 0x636865, 0x636874, 0x646173, 0x64656E, 0x646572, 0x646965, 0x652064, 0x652073, 0x65696E, 0x656974, 0x656E20, 0x657220, \n                            0x657320, 0x67656E, 0x68656E, 0x687420, 0x696368, 0x696520, 0x696E20, 0x696E65, 0x697420, 0x6C6963, 0x6C6C65, 0x6E2061, 0x6E2064, 0x6E2073, 0x6E6420, 0x6E6465, \n                            0x6E6520, 0x6E6720, 0x6E6765, 0x6E7465, 0x722064, 0x726465, 0x726569, 0x736368, 0x737465, 0x742064, 0x746520, 0x74656E, 0x746572, 0x756E64, 0x756E67, 0x766572,                             \n                    }),\n            new NGramsPlusLang(\n                    \"en\",\n                    new int[] {\n                            0x206120, 0x20616E, 0x206265, 0x20636F, 0x20666F, 0x206861, 0x206865, 0x20696E, 0x206D61, 0x206F66, 0x207072, 0x207265, 0x207361, 0x207374, 0x207468, 0x20746F, \n                            0x207768, 0x616964, 0x616C20, 0x616E20, 0x616E64, 0x617320, 0x617420, 0x617465, 0x617469, 0x642061, 0x642074, 0x652061, 0x652073, 0x652074, 0x656420, 0x656E74, \n                            0x657220, 0x657320, 0x666F72, 0x686174, 0x686520, 0x686572, 0x696420, 0x696E20, 0x696E67, 0x696F6E, 0x697320, 0x6E2061, 0x6E2074, 0x6E6420, 0x6E6720, 0x6E7420, \n                            0x6F6620, 0x6F6E20, 0x6F7220, 0x726520, 0x727320, 0x732061, 0x732074, 0x736169, 0x737420, 0x742074, 0x746572, 0x746861, 0x746865, 0x74696F, 0x746F20, 0x747320, \n                    }),\n\n            new NGramsPlusLang(\n                    \"es\",\n                    new int[] {\n                            0x206120, 0x206361, 0x20636F, 0x206465, 0x20656C, 0x20656E, 0x206573, 0x20696E, 0x206C61, 0x206C6F, 0x207061, 0x20706F, 0x207072, 0x207175, 0x207265, 0x207365, \n                            0x20756E, 0x207920, 0x612063, 0x612064, 0x612065, 0x61206C, 0x612070, 0x616369, 0x61646F, 0x616C20, 0x617220, 0x617320, 0x6369F3, 0x636F6E, 0x646520, 0x64656C, \n                            0x646F20, 0x652064, 0x652065, 0x65206C, 0x656C20, 0x656E20, 0x656E74, 0x657320, 0x657374, 0x69656E, 0x69F36E, 0x6C6120, 0x6C6F73, 0x6E2065, 0x6E7465, 0x6F2064, \n                            0x6F2065, 0x6F6E20, 0x6F7220, 0x6F7320, 0x706172, 0x717565, 0x726120, 0x726573, 0x732064, 0x732065, 0x732070, 0x736520, 0x746520, 0x746F20, 0x756520, 0xF36E20, \n                    }),\n                                            \n            new NGramsPlusLang(\n                    \"fr\",\n                    new int[] {\n                            0x206175, 0x20636F, 0x206461, 0x206465, 0x206475, 0x20656E, 0x206574, 0x206C61, 0x206C65, 0x207061, 0x20706F, 0x207072, 0x207175, 0x207365, 0x20736F, 0x20756E, \n                            0x20E020, 0x616E74, 0x617469, 0x636520, 0x636F6E, 0x646520, 0x646573, 0x647520, 0x652061, 0x652063, 0x652064, 0x652065, 0x65206C, 0x652070, 0x652073, 0x656E20, \n                            0x656E74, 0x657220, 0x657320, 0x657420, 0x657572, 0x696F6E, 0x697320, 0x697420, 0x6C6120, 0x6C6520, 0x6C6573, 0x6D656E, 0x6E2064, 0x6E6520, 0x6E7320, 0x6E7420, \n                            0x6F6E20, 0x6F6E74, 0x6F7572, 0x717565, 0x72206C, 0x726520, 0x732061, 0x732064, 0x732065, 0x73206C, 0x732070, 0x742064, 0x746520, 0x74696F, 0x756520, 0x757220,\n                    }),\n\n            new NGramsPlusLang(\n                    \"it\",\n                    new int[] {\n                            0x20616C, 0x206368, 0x20636F, 0x206465, 0x206469, 0x206520, 0x20696C, 0x20696E, 0x206C61, 0x207065, 0x207072, 0x20756E, 0x612063, 0x612064, 0x612070, 0x612073, \n                            0x61746F, 0x636865, 0x636F6E, 0x64656C, 0x646920, 0x652061, 0x652063, 0x652064, 0x652069, 0x65206C, 0x652070, 0x652073, 0x656C20, 0x656C6C, 0x656E74, 0x657220, \n                            0x686520, 0x692061, 0x692063, 0x692064, 0x692073, 0x696120, 0x696C20, 0x696E20, 0x696F6E, 0x6C6120, 0x6C6520, 0x6C6920, 0x6C6C61, 0x6E6520, 0x6E6920, 0x6E6F20, \n                            0x6E7465, 0x6F2061, 0x6F2064, 0x6F2069, 0x6F2073, 0x6F6E20, 0x6F6E65, 0x706572, 0x726120, 0x726520, 0x736920, 0x746120, 0x746520, 0x746920, 0x746F20, 0x7A696F, \n                    }),\n                    \n            new NGramsPlusLang(\n                    \"nl\",\n                    new int[] {\n                            0x20616C, 0x206265, 0x206461, 0x206465, 0x206469, 0x206565, 0x20656E, 0x206765, 0x206865, 0x20696E, 0x206D61, 0x206D65, 0x206F70, 0x207465, 0x207661, 0x207665, \n                            0x20766F, 0x207765, 0x207A69, 0x61616E, 0x616172, 0x616E20, 0x616E64, 0x617220, 0x617420, 0x636874, 0x646520, 0x64656E, 0x646572, 0x652062, 0x652076, 0x65656E, \n                            0x656572, 0x656E20, 0x657220, 0x657273, 0x657420, 0x67656E, 0x686574, 0x696520, 0x696E20, 0x696E67, 0x697320, 0x6E2062, 0x6E2064, 0x6E2065, 0x6E2068, 0x6E206F, \n                            0x6E2076, 0x6E6465, 0x6E6720, 0x6F6E64, 0x6F6F72, 0x6F7020, 0x6F7220, 0x736368, 0x737465, 0x742064, 0x746520, 0x74656E, 0x746572, 0x76616E, 0x766572, 0x766F6F, \n                    }),\n                    \n            new NGramsPlusLang(\n                    \"no\",\n                    new int[] {\n                            0x206174, 0x206176, 0x206465, 0x20656E, 0x206572, 0x20666F, 0x206861, 0x206920, 0x206D65, 0x206F67, 0x2070E5, 0x207365, 0x20736B, 0x20736F, 0x207374, 0x207469, \n                            0x207669, 0x20E520, 0x616E64, 0x617220, 0x617420, 0x646520, 0x64656E, 0x646574, 0x652073, 0x656420, 0x656E20, 0x656E65, 0x657220, 0x657265, 0x657420, 0x657474, \n                            0x666F72, 0x67656E, 0x696B6B, 0x696C20, 0x696E67, 0x6B6520, 0x6B6B65, 0x6C6520, 0x6C6C65, 0x6D6564, 0x6D656E, 0x6E2073, 0x6E6520, 0x6E6720, 0x6E6765, 0x6E6E65, \n                            0x6F6720, 0x6F6D20, 0x6F7220, 0x70E520, 0x722073, 0x726520, 0x736F6D, 0x737465, 0x742073, 0x746520, 0x74656E, 0x746572, 0x74696C, 0x747420, 0x747465, 0x766572, \n                    }),\n                    \n            new NGramsPlusLang(\n                    \"pt\",\n                    new int[] {\n                            0x206120, 0x20636F, 0x206461, 0x206465, 0x20646F, 0x206520, 0x206573, 0x206D61, 0x206E6F, 0x206F20, 0x207061, 0x20706F, 0x207072, 0x207175, 0x207265, 0x207365, \n                            0x20756D, 0x612061, 0x612063, 0x612064, 0x612070, 0x616465, 0x61646F, 0x616C20, 0x617220, 0x617261, 0x617320, 0x636F6D, 0x636F6E, 0x646120, 0x646520, 0x646F20, \n                            0x646F73, 0x652061, 0x652064, 0x656D20, 0x656E74, 0x657320, 0x657374, 0x696120, 0x696361, 0x6D656E, 0x6E7465, 0x6E746F, 0x6F2061, 0x6F2063, 0x6F2064, 0x6F2065, \n                            0x6F2070, 0x6F7320, 0x706172, 0x717565, 0x726120, 0x726573, 0x732061, 0x732064, 0x732065, 0x732070, 0x737461, 0x746520, 0x746F20, 0x756520, 0xE36F20, 0xE7E36F, \n\n                    }),\n                    \n            new NGramsPlusLang(\n                    \"sv\",\n                    new int[] {\n                            0x206174, 0x206176, 0x206465, 0x20656E, 0x2066F6, 0x206861, 0x206920, 0x20696E, 0x206B6F, 0x206D65, 0x206F63, 0x2070E5, 0x20736B, 0x20736F, 0x207374, 0x207469, \n                            0x207661, 0x207669, 0x20E472, 0x616465, 0x616E20, 0x616E64, 0x617220, 0x617474, 0x636820, 0x646520, 0x64656E, 0x646572, 0x646574, 0x656420, 0x656E20, 0x657220, \n                            0x657420, 0x66F672, 0x67656E, 0x696C6C, 0x696E67, 0x6B6120, 0x6C6C20, 0x6D6564, 0x6E2073, 0x6E6120, 0x6E6465, 0x6E6720, 0x6E6765, 0x6E696E, 0x6F6368, 0x6F6D20, \n                            0x6F6E20, 0x70E520, 0x722061, 0x722073, 0x726120, 0x736B61, 0x736F6D, 0x742073, 0x746120, 0x746520, 0x746572, 0x74696C, 0x747420, 0x766172, 0xE47220, 0xF67220, \n                    }),\n                    \n        };\n\n        \n        public CharsetMatch match(CharsetDetector det)\n        {\n            String name = det.fC1Bytes ? \"windows-1252\" : \"ISO-8859-1\";\n            int bestConfidenceSoFar = -1;\n            String lang = null;\n            for (NGramsPlusLang ngl: ngrams_8859_1) {\n                int confidence = match(det, ngl.fNGrams, byteMap);\n                if (confidence > bestConfidenceSoFar) {\n                    bestConfidenceSoFar = confidence;\n                    lang = ngl.fLang;\n                }\n            }\n            return bestConfidenceSoFar <= 0 ? null : new CharsetMatch(det, bestConfidenceSoFar, name, lang);\n        }\n\n            \n        public String getName()\n        {\n            return \"ISO-8859-1\";\n        }\n    }\n\n    \n    static class CharsetRecog_8859_2 extends CharsetRecog_sbcs\n    {\n        protected static byte[] byteMap = {\n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, \n            (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F, \n            (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, \n            (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, \n            (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F, \n            (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, \n            (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0xB1, (byte) 0x20, (byte) 0xB3, (byte) 0x20, (byte) 0xB5, (byte) 0xB6, (byte) 0x20, \n            (byte) 0x20, (byte) 0xB9, (byte) 0xBA, (byte) 0xBB, (byte) 0xBC, (byte) 0x20, (byte) 0xBE, (byte) 0xBF, \n            (byte) 0x20, (byte) 0xB1, (byte) 0x20, (byte) 0xB3, (byte) 0x20, (byte) 0xB5, (byte) 0xB6, (byte) 0xB7, \n            (byte) 0x20, (byte) 0xB9, (byte) 0xBA, (byte) 0xBB, (byte) 0xBC, (byte) 0x20, (byte) 0xBE, (byte) 0xBF, \n            (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, \n            (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF, \n            (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0x20, \n            (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0xDF, \n            (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, \n            (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF, \n            (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0x20, \n            (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0x20, \n        };\n\n        private static final NGramsPlusLang[] ngrams_8859_2 = new NGramsPlusLang[] {\n            new NGramsPlusLang(\n                    \"cs\", \n                    new int[] {\n                            0x206120, 0x206279, 0x20646F, 0x206A65, 0x206E61, 0x206E65, 0x206F20, 0x206F64, 0x20706F, 0x207072, 0x2070F8, 0x20726F, 0x207365, 0x20736F, 0x207374, 0x20746F, \n                            0x207620, 0x207679, 0x207A61, 0x612070, 0x636520, 0x636820, 0x652070, 0x652073, 0x652076, 0x656D20, 0x656EED, 0x686F20, 0x686F64, 0x697374, 0x6A6520, 0x6B7465, \n                            0x6C6520, 0x6C6920, 0x6E6120, 0x6EE920, 0x6EEC20, 0x6EED20, 0x6F2070, 0x6F646E, 0x6F6A69, 0x6F7374, 0x6F7520, 0x6F7661, 0x706F64, 0x706F6A, 0x70726F, 0x70F865, \n                            0x736520, 0x736F75, 0x737461, 0x737469, 0x73746E, 0x746572, 0x746EED, 0x746F20, 0x752070, 0xBE6520, 0xE16EED, 0xE9686F, 0xED2070, 0xED2073, 0xED6D20, 0xF86564, \n                    }),\n            new NGramsPlusLang(\n                    \"hu\", \n                    new int[] {\n                            0x206120, 0x20617A, 0x206265, 0x206567, 0x20656C, 0x206665, 0x206861, 0x20686F, 0x206973, 0x206B65, 0x206B69, 0x206BF6, 0x206C65, 0x206D61, 0x206D65, 0x206D69, \n                            0x206E65, 0x20737A, 0x207465, 0x20E973, 0x612061, 0x61206B, 0x61206D, 0x612073, 0x616B20, 0x616E20, 0x617A20, 0x62616E, 0x62656E, 0x656779, 0x656B20, 0x656C20, \n                            0x656C65, 0x656D20, 0x656E20, 0x657265, 0x657420, 0x657465, 0x657474, 0x677920, 0x686F67, 0x696E74, 0x697320, 0x6B2061, 0x6BF67A, 0x6D6567, 0x6D696E, 0x6E2061, \n                            0x6E616B, 0x6E656B, 0x6E656D, 0x6E7420, 0x6F6779, 0x732061, 0x737A65, 0x737A74, 0x737AE1, 0x73E967, 0x742061, 0x747420, 0x74E173, 0x7A6572, 0xE16E20, 0xE97320, \n                    }),\n            new NGramsPlusLang(\n                    \"pl\", \n                    new int[] {\n                            0x20637A, 0x20646F, 0x206920, 0x206A65, 0x206B6F, 0x206D61, 0x206D69, 0x206E61, 0x206E69, 0x206F64, 0x20706F, 0x207072, 0x207369, 0x207720, 0x207769, 0x207779, \n                            0x207A20, 0x207A61, 0x612070, 0x612077, 0x616E69, 0x636820, 0x637A65, 0x637A79, 0x646F20, 0x647A69, 0x652070, 0x652073, 0x652077, 0x65207A, 0x65676F, 0x656A20, \n                            0x656D20, 0x656E69, 0x676F20, 0x696120, 0x696520, 0x69656A, 0x6B6120, 0x6B6920, 0x6B6965, 0x6D6965, 0x6E6120, 0x6E6961, 0x6E6965, 0x6F2070, 0x6F7761, 0x6F7769, \n                            0x706F6C, 0x707261, 0x70726F, 0x70727A, 0x727A65, 0x727A79, 0x7369EA, 0x736B69, 0x737461, 0x776965, 0x796368, 0x796D20, 0x7A6520, 0x7A6965, 0x7A7920, 0xF37720, \n                    }),\n            new NGramsPlusLang(\n                    \"ro\", \n                    new int[] {\n                            0x206120, 0x206163, 0x206361, 0x206365, 0x20636F, 0x206375, 0x206465, 0x206469, 0x206C61, 0x206D61, 0x207065, 0x207072, 0x207365, 0x2073E3, 0x20756E, 0x20BA69, \n                            0x20EE6E, 0x612063, 0x612064, 0x617265, 0x617420, 0x617465, 0x617520, 0x636172, 0x636F6E, 0x637520, 0x63E320, 0x646520, 0x652061, 0x652063, 0x652064, 0x652070, \n                            0x652073, 0x656120, 0x656920, 0x656C65, 0x656E74, 0x657374, 0x692061, 0x692063, 0x692064, 0x692070, 0x696520, 0x696920, 0x696E20, 0x6C6120, 0x6C6520, 0x6C6F72, \n                            0x6C7569, 0x6E6520, 0x6E7472, 0x6F7220, 0x70656E, 0x726520, 0x726561, 0x727520, 0x73E320, 0x746520, 0x747275, 0x74E320, 0x756920, 0x756C20, 0xBA6920, 0xEE6E20, \n                    })\n        };\n\n        public CharsetMatch match(CharsetDetector det)\n        {\n            String name = det.fC1Bytes ? \"windows-1250\" : \"ISO-8859-2\";\n            int bestConfidenceSoFar = -1;\n            String lang = null;\n            for (NGramsPlusLang ngl: ngrams_8859_2) {\n                int confidence = match(det, ngl.fNGrams, byteMap);\n                if (confidence > bestConfidenceSoFar) {\n                    bestConfidenceSoFar = confidence;\n                    lang = ngl.fLang;\n                }\n            }\n            return bestConfidenceSoFar <= 0 ? null : new CharsetMatch(det, bestConfidenceSoFar, name, lang);\n        }\n\n        public String getName()\n        {\n            return \"ISO-8859-2\";\n        }\n\n    }\n    \n    \n    abstract static class CharsetRecog_8859_5 extends CharsetRecog_sbcs\n    {\n        protected static byte[] byteMap = {\n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, \n            (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F, \n            (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, \n            (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, \n            (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F, \n            (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, \n            (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7, \n            (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0x20, (byte) 0xFE, (byte) 0xFF, \n            (byte) 0xD0, (byte) 0xD1, (byte) 0xD2, (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0xD7, \n            (byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0xDB, (byte) 0xDC, (byte) 0xDD, (byte) 0xDE, (byte) 0xDF, \n            (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, \n            (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF, \n            (byte) 0xD0, (byte) 0xD1, (byte) 0xD2, (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0xD7, \n            (byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0xDB, (byte) 0xDC, (byte) 0xDD, (byte) 0xDE, (byte) 0xDF, \n            (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, \n            (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF, \n            (byte) 0x20, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7, \n            (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0x20, (byte) 0xFE, (byte) 0xFF, \n        };\n\n        public String getName()\n        {\n            return \"ISO-8859-5\";\n        }\n    }\n    \n    static class CharsetRecog_8859_5_ru extends CharsetRecog_8859_5\n    {\n        private static final int[] ngrams = {\n            0x20D220, 0x20D2DE, 0x20D4DE, 0x20D7D0, 0x20D820, 0x20DAD0, 0x20DADE, 0x20DDD0, 0x20DDD5, 0x20DED1, 0x20DFDE, 0x20DFE0, 0x20E0D0, 0x20E1DE, 0x20E1E2, 0x20E2DE, \n            0x20E7E2, 0x20EDE2, 0xD0DDD8, 0xD0E2EC, 0xD3DE20, 0xD5DBEC, 0xD5DDD8, 0xD5E1E2, 0xD5E220, 0xD820DF, 0xD8D520, 0xD8D820, 0xD8EF20, 0xDBD5DD, 0xDBD820, 0xDBECDD, \n            0xDDD020, 0xDDD520, 0xDDD8D5, 0xDDD8EF, 0xDDDE20, 0xDDDED2, 0xDE20D2, 0xDE20DF, 0xDE20E1, 0xDED220, 0xDED2D0, 0xDED3DE, 0xDED920, 0xDEDBEC, 0xDEDC20, 0xDEE1E2, \n            0xDFDEDB, 0xDFE0D5, 0xDFE0D8, 0xDFE0DE, 0xE0D0D2, 0xE0D5D4, 0xE1E2D0, 0xE1E2D2, 0xE1E2D8, 0xE1EF20, 0xE2D5DB, 0xE2DE20, 0xE2DEE0, 0xE2EC20, 0xE7E2DE, 0xEBE520, \n        };\n\n        public String getLanguage()\n        {\n            return \"ru\";\n        }\n        \n        public CharsetMatch match(CharsetDetector det)\n        {\n            int confidence = match(det, ngrams, byteMap);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n    \n    abstract static class CharsetRecog_8859_6 extends CharsetRecog_sbcs\n    {\n        protected static byte[] byteMap = {\n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, \n            (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F, \n            (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, \n            (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, \n            (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F, \n            (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, \n            (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0xC1, (byte) 0xC2, (byte) 0xC3, (byte) 0xC4, (byte) 0xC5, (byte) 0xC6, (byte) 0xC7, \n            (byte) 0xC8, (byte) 0xC9, (byte) 0xCA, (byte) 0xCB, (byte) 0xCC, (byte) 0xCD, (byte) 0xCE, (byte) 0xCF, \n            (byte) 0xD0, (byte) 0xD1, (byte) 0xD2, (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0xD7, \n            (byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, \n            (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n        };\n\n        public String getName()\n        {\n            return \"ISO-8859-6\";\n        }\n    }\n    \n    static class CharsetRecog_8859_6_ar extends CharsetRecog_8859_6\n    {\n        private static final int[] ngrams = {\n            0x20C7E4, 0x20C7E6, 0x20C8C7, 0x20D9E4, 0x20E1EA, 0x20E4E4, 0x20E5E6, 0x20E8C7, 0xC720C7, 0xC7C120, 0xC7CA20, 0xC7D120, 0xC7E420, 0xC7E4C3, 0xC7E4C7, 0xC7E4C8, \n            0xC7E4CA, 0xC7E4CC, 0xC7E4CD, 0xC7E4CF, 0xC7E4D3, 0xC7E4D9, 0xC7E4E2, 0xC7E4E5, 0xC7E4E8, 0xC7E4EA, 0xC7E520, 0xC7E620, 0xC7E6CA, 0xC820C7, 0xC920C7, 0xC920E1, \n            0xC920E4, 0xC920E5, 0xC920E8, 0xCA20C7, 0xCF20C7, 0xCFC920, 0xD120C7, 0xD1C920, 0xD320C7, 0xD920C7, 0xD9E4E9, 0xE1EA20, 0xE420C7, 0xE4C920, 0xE4E920, 0xE4EA20, \n            0xE520C7, 0xE5C720, 0xE5C920, 0xE5E620, 0xE620C7, 0xE720C7, 0xE7C720, 0xE8C7E4, 0xE8E620, 0xE920C7, 0xEA20C7, 0xEA20E5, 0xEA20E8, 0xEAC920, 0xEAD120, 0xEAE620, \n        };\n\n        public String getLanguage()\n        {\n            return \"ar\";\n        }\n        \n        public CharsetMatch match(CharsetDetector det)\n        {\n            int confidence = match(det, ngrams, byteMap);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n    \n    abstract static class CharsetRecog_8859_7 extends CharsetRecog_sbcs\n    {\n        protected static byte[] byteMap = {\n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, \n            (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F, \n            (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, \n            (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, \n            (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F, \n            (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, \n            (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0xA1, (byte) 0xA2, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xDC, (byte) 0x20, \n            (byte) 0xDD, (byte) 0xDE, (byte) 0xDF, (byte) 0x20, (byte) 0xFC, (byte) 0x20, (byte) 0xFD, (byte) 0xFE, \n            (byte) 0xC0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, \n            (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF, \n            (byte) 0xF0, (byte) 0xF1, (byte) 0x20, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7, \n            (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xDC, (byte) 0xDD, (byte) 0xDE, (byte) 0xDF, \n            (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, \n            (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF, \n            (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7, \n            (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0x20, \n        };\n\n        public String getName()\n        {\n            return \"ISO-8859-7\";\n        }\n    }\n    \n    static class CharsetRecog_8859_7_el extends CharsetRecog_8859_7\n    {\n        private static final int[] ngrams = {\n            0x20E1ED, 0x20E1F0, 0x20E3E9, 0x20E4E9, 0x20E5F0, 0x20E720, 0x20EAE1, 0x20ECE5, 0x20EDE1, 0x20EF20, 0x20F0E1, 0x20F0EF, 0x20F0F1, 0x20F3F4, 0x20F3F5, 0x20F4E7, \n            0x20F4EF, 0xDFE120, 0xE120E1, 0xE120F4, 0xE1E920, 0xE1ED20, 0xE1F0FC, 0xE1F220, 0xE3E9E1, 0xE5E920, 0xE5F220, 0xE720F4, 0xE7ED20, 0xE7F220, 0xE920F4, 0xE9E120, \n            0xE9EADE, 0xE9F220, 0xEAE1E9, 0xEAE1F4, 0xECE520, 0xED20E1, 0xED20E5, 0xED20F0, 0xEDE120, 0xEFF220, 0xEFF520, 0xF0EFF5, 0xF0F1EF, 0xF0FC20, 0xF220E1, 0xF220E5, \n            0xF220EA, 0xF220F0, 0xF220F4, 0xF3E520, 0xF3E720, 0xF3F4EF, 0xF4E120, 0xF4E1E9, 0xF4E7ED, 0xF4E7F2, 0xF4E9EA, 0xF4EF20, 0xF4EFF5, 0xF4F9ED, 0xF9ED20, 0xFEED20, \n        };\n\n        public String getLanguage()\n        {\n            return \"el\";\n        }\n        \n        public CharsetMatch match(CharsetDetector det)\n        {\n            String name = det.fC1Bytes ?  \"windows-1253\" : \"ISO-8859-7\";\n            int confidence = match(det, ngrams, byteMap);\n            return confidence == 0 ? null : new CharsetMatch(det, confidence, name, \"el\");\n        }\n    }\n    \n    abstract static class CharsetRecog_8859_8 extends CharsetRecog_sbcs {\n        protected static byte[] byteMap = {\n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, \n            (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F, \n            (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, \n            (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, \n            (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F, \n            (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, \n            (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xB5, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, \n            (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF, \n            (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7, \n            (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n        };\n\n        public String getName()\n        {\n            return \"ISO-8859-8\";\n        }\n    }\n    \n    static class CharsetRecog_8859_8_I_he extends CharsetRecog_8859_8\n    {\n        private static final int[] ngrams = {\n            0x20E0E5, 0x20E0E7, 0x20E0E9, 0x20E0FA, 0x20E1E9, 0x20E1EE, 0x20E4E0, 0x20E4E5, 0x20E4E9, 0x20E4EE, 0x20E4F2, 0x20E4F9, 0x20E4FA, 0x20ECE0, 0x20ECE4, 0x20EEE0, \n            0x20F2EC, 0x20F9EC, 0xE0FA20, 0xE420E0, 0xE420E1, 0xE420E4, 0xE420EC, 0xE420EE, 0xE420F9, 0xE4E5E0, 0xE5E020, 0xE5ED20, 0xE5EF20, 0xE5F820, 0xE5FA20, 0xE920E4, \n            0xE9E420, 0xE9E5FA, 0xE9E9ED, 0xE9ED20, 0xE9EF20, 0xE9F820, 0xE9FA20, 0xEC20E0, 0xEC20E4, 0xECE020, 0xECE420, 0xED20E0, 0xED20E1, 0xED20E4, 0xED20EC, 0xED20EE, \n            0xED20F9, 0xEEE420, 0xEF20E4, 0xF0E420, 0xF0E920, 0xF0E9ED, 0xF2EC20, 0xF820E4, 0xF8E9ED, 0xF9EC20, 0xFA20E0, 0xFA20E1, 0xFA20E4, 0xFA20EC, 0xFA20EE, 0xFA20F9, \n        };\n\n        public String getName()\n        {\n            return \"ISO-8859-8-I\";\n        }\n\n        public String getLanguage()\n        {\n            return \"he\";\n        }\n        \n        public CharsetMatch match(CharsetDetector det) {\n            String name = det.fC1Bytes ? \"windows-1255\" : \"ISO-8859-8-I\";\n            int confidence = match(det, ngrams, byteMap);\n            return confidence == 0 ? null : new CharsetMatch(det, confidence, name, \"he\");\n        }\n    }\n    \n    static class CharsetRecog_8859_8_he extends CharsetRecog_8859_8\n    {\n        private static final int[] ngrams = {\n            0x20E0E5, 0x20E0EC, 0x20E4E9, 0x20E4EC, 0x20E4EE, 0x20E4F0, 0x20E9F0, 0x20ECF2, 0x20ECF9, 0x20EDE5, 0x20EDE9, 0x20EFE5, 0x20EFE9, 0x20F8E5, 0x20F8E9, 0x20FAE0, \n            0x20FAE5, 0x20FAE9, 0xE020E4, 0xE020EC, 0xE020ED, 0xE020FA, 0xE0E420, 0xE0E5E4, 0xE0EC20, 0xE0EE20, 0xE120E4, 0xE120ED, 0xE120FA, 0xE420E4, 0xE420E9, 0xE420EC, \n            0xE420ED, 0xE420EF, 0xE420F8, 0xE420FA, 0xE4EC20, 0xE5E020, 0xE5E420, 0xE7E020, 0xE9E020, 0xE9E120, 0xE9E420, 0xEC20E4, 0xEC20ED, 0xEC20FA, 0xECF220, 0xECF920, \n            0xEDE9E9, 0xEDE9F0, 0xEDE9F8, 0xEE20E4, 0xEE20ED, 0xEE20FA, 0xEEE120, 0xEEE420, 0xF2E420, 0xF920E4, 0xF920ED, 0xF920FA, 0xF9E420, 0xFAE020, 0xFAE420, 0xFAE5E9, \n        };\n\n        public String getLanguage()\n        {\n            return \"he\";\n        }\n        \n        public CharsetMatch match(CharsetDetector det)\n        {\n            String name = det.fC1Bytes ? \"windows-1255\" : \"ISO-8859-8\";\n            int confidence = match(det, ngrams, byteMap);\n            return confidence == 0 ? null : new CharsetMatch(det, confidence, name, \"he\");\n\n        }\n    }\n    \n    abstract static class CharsetRecog_8859_9 extends CharsetRecog_sbcs\n    {\n        protected static byte[] byteMap = {\n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, \n            (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F, \n            (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, \n            (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, \n            (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F, \n            (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, \n            (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0xAA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xB5, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0xBA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, \n            (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF, \n            (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0x20, \n            (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0x69, (byte) 0xFE, (byte) 0xDF, \n            (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, \n            (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF, \n            (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0x20, \n            (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0xFF, \n        };\n\n        public String getName()\n        {\n            return \"ISO-8859-9\";\n        }\n    }\n    \n    static class CharsetRecog_8859_9_tr extends CharsetRecog_8859_9\n    {\n        private static final int[] ngrams = {\n            0x206261, 0x206269, 0x206275, 0x206461, 0x206465, 0x206765, 0x206861, 0x20696C, 0x206B61, 0x206B6F, 0x206D61, 0x206F6C, 0x207361, 0x207461, 0x207665, 0x207961, \n            0x612062, 0x616B20, 0x616C61, 0x616D61, 0x616E20, 0x616EFD, 0x617220, 0x617261, 0x6172FD, 0x6173FD, 0x617961, 0x626972, 0x646120, 0x646520, 0x646920, 0x652062, \n            0x65206B, 0x656469, 0x656E20, 0x657220, 0x657269, 0x657369, 0x696C65, 0x696E20, 0x696E69, 0x697220, 0x6C616E, 0x6C6172, 0x6C6520, 0x6C6572, 0x6E2061, 0x6E2062, \n            0x6E206B, 0x6E6461, 0x6E6465, 0x6E6520, 0x6E6920, 0x6E696E, 0x6EFD20, 0x72696E, 0x72FD6E, 0x766520, 0x796120, 0x796F72, 0xFD6E20, 0xFD6E64, 0xFD6EFD, 0xFDF0FD, \n        };\n\n        public String getLanguage()\n        {\n            return \"tr\";\n        }\n        \n        public CharsetMatch match(CharsetDetector det)\n        {\n            String name = det.fC1Bytes ? \"windows-1254\" : \"ISO-8859-9\";\n            int confidence = match(det, ngrams, byteMap);\n            return confidence == 0 ? null : new CharsetMatch(det, confidence, name, \"tr\");\n        }\n    }\n    \n    static class CharsetRecog_windows_1251 extends CharsetRecog_sbcs\n    {\n        private static final int[] ngrams = {\n         // ' в '     ' во'     ' до'     ' за'     ' и '     ' ка'     ' ко'     ' на'     ' не'     ' об'     ' по'     ' пр'     ' ра'     ' со'     ' ст'     ' то'\n            0x20E220, 0x20E2EE, 0x20E4EE, 0x20E7E0, 0x20E820, 0x20EAE0, 0x20EAEE, 0x20EDE0, 0x20EDE5, 0x20EEE1, 0x20EFEE, 0x20EFF0, 0x20F0E0, 0x20F1EE, 0x20F1F2, 0x20F2EE,\n         // ' чт'     ' эт'     'ани'     'ать'     'го '     'ель'     'ени'     'ест'     'ет '     'и п'     'ие '     'ии '     'ия '     'лен'     'ли '     'льн'\n            0x20F7F2, 0x20FDF2, 0xE0EDE8, 0xE0F2FC, 0xE3EE20, 0xE5EBFC, 0xE5EDE8, 0xE5F1F2, 0xE5F220, 0xE820EF, 0xE8E520, 0xE8E820, 0xE8FF20, 0xEBE5ED, 0xEBE820, 0xEBFCED,\n         // 'на '     'не '     'ние'     'ния'     'но '     'нов'     'о в'     'о п'     'о с'     'ов '     'ова'     'ого'     'ой '     'оль'     'ом '     'ост'\n            0xEDE020, 0xEDE520, 0xEDE8E5, 0xEDE8FF, 0xEDEE20, 0xEDEEE2, 0xEE20E2, 0xEE20EF, 0xEE20F1, 0xEEE220, 0xEEE2E0, 0xEEE3EE, 0xEEE920, 0xEEEBFC, 0xEEEC20, 0xEEF1F2,\n         // 'пол'     'пре'     'при'     'про'     'рав'     'ред'     'ста'     'ств'     'сти'     'ся '     'тел'     'то '     'тор'     'ть '     'что'     'ых '\n            0xEFEEEB, 0xEFF0E5, 0xEFF0E8, 0xEFF0EE, 0xF0E0E2, 0xF0E5E4, 0xF1F2E0, 0xF1F2E2, 0xF1F2E8, 0xF1FF20, 0xF2E5EB, 0xF2EE20, 0xF2EEF0, 0xF2FC20, 0xF7F2EE, 0xFBF520, \n        };\n\n        private static final byte[] byteMap = {\n/*                 -0           -1           -2           -3           -4           -5           -6           -7           -8           -9           -A           -B           -C           -D           -E           -F   */\n/* 0- */    (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n/* 1- */    (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n/* 2- */    (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n/* 3- */    (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n/* 4- */    (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n/* 5- */    (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n/* 6- */    (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n/* 7- */    (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n/* 8- */    (byte) 0x90, (byte) 0x83, (byte) 0x20, (byte) 0x83, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x9A, (byte) 0x20, (byte) 0x9C, (byte) 0x9D, (byte) 0x9E, (byte) 0x9F,\n/* 9- */    (byte) 0x90, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xA8, (byte) 0x20, (byte) 0x9A, (byte) 0x20, (byte) 0x9C, (byte) 0x9D, (byte) 0x9E, (byte) 0x9F,\n/* A- */    (byte) 0x20, (byte) 0xA2, (byte) 0xA2, (byte) 0xBC, (byte) 0x20, (byte) 0xB4, (byte) 0x20, (byte) 0x20, (byte) 0xA8, (byte) 0x20, (byte) 0xBA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xBF,\n/* B- */    (byte) 0x20, (byte) 0x20, (byte) 0xB3, (byte) 0xB3, (byte) 0xB4, (byte) 0xB5, (byte) 0x20, (byte) 0x20, (byte) 0xB8, (byte) 0x20, (byte) 0xBA, (byte) 0x20, (byte) 0xBC, (byte) 0xBE, (byte) 0xBE, (byte) 0xBF,\n/* C- */    (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,\n/* D- */    (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7, (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0xFF,\n/* E- */    (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,\n/* F- */    (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7, (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0xFF,\n        };\n\n        public String getName()\n        {\n            return  \"windows-1251\";\n        }\n        \n        public String getLanguage()\n        {\n            return \"ru\";\n        }\n        \n        public CharsetMatch match(CharsetDetector det)\n        {\n            int confidence = match(det, ngrams, byteMap);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n    \n    static class CharsetRecog_windows_1256 extends CharsetRecog_sbcs\n    {\n        private static final int[] ngrams = {\n            0x20C7E1, 0x20C7E4, 0x20C8C7, 0x20DAE1, 0x20DDED, 0x20E1E1, 0x20E3E4, 0x20E6C7, 0xC720C7, 0xC7C120, 0xC7CA20, 0xC7D120, 0xC7E120, 0xC7E1C3, 0xC7E1C7, 0xC7E1C8, \n            0xC7E1CA, 0xC7E1CC, 0xC7E1CD, 0xC7E1CF, 0xC7E1D3, 0xC7E1DA, 0xC7E1DE, 0xC7E1E3, 0xC7E1E6, 0xC7E1ED, 0xC7E320, 0xC7E420, 0xC7E4CA, 0xC820C7, 0xC920C7, 0xC920DD, \n            0xC920E1, 0xC920E3, 0xC920E6, 0xCA20C7, 0xCF20C7, 0xCFC920, 0xD120C7, 0xD1C920, 0xD320C7, 0xDA20C7, 0xDAE1EC, 0xDDED20, 0xE120C7, 0xE1C920, 0xE1EC20, 0xE1ED20, \n            0xE320C7, 0xE3C720, 0xE3C920, 0xE3E420, 0xE420C7, 0xE520C7, 0xE5C720, 0xE6C7E1, 0xE6E420, 0xEC20C7, 0xED20C7, 0xED20E3, 0xED20E6, 0xEDC920, 0xEDD120, 0xEDE420, \n        };\n\n        private static final byte[] byteMap = {\n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, \n            (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F, \n            (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, \n            (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, \n            (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F, \n            (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, \n            (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x81, (byte) 0x20, (byte) 0x83, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x88, (byte) 0x20, (byte) 0x8A, (byte) 0x20, (byte) 0x9C, (byte) 0x8D, (byte) 0x8E, (byte) 0x8F, \n            (byte) 0x90, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x98, (byte) 0x20, (byte) 0x9A, (byte) 0x20, (byte) 0x9C, (byte) 0x20, (byte) 0x20, (byte) 0x9F, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0xAA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xB5, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0xC0, (byte) 0xC1, (byte) 0xC2, (byte) 0xC3, (byte) 0xC4, (byte) 0xC5, (byte) 0xC6, (byte) 0xC7, \n            (byte) 0xC8, (byte) 0xC9, (byte) 0xCA, (byte) 0xCB, (byte) 0xCC, (byte) 0xCD, (byte) 0xCE, (byte) 0xCF, \n            (byte) 0xD0, (byte) 0xD1, (byte) 0xD2, (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0x20, \n            (byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0xDB, (byte) 0xDC, (byte) 0xDD, (byte) 0xDE, (byte) 0xDF, \n            (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, \n            (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xF4, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0xF9, (byte) 0x20, (byte) 0xFB, (byte) 0xFC, (byte) 0x20, (byte) 0x20, (byte) 0xFF, \n        };\n\n        public String getName()\n        {\n            return  \"windows-1256\";\n        }\n        \n        public String getLanguage()\n        {\n            return \"ar\";\n        }\n        \n        public CharsetMatch match(CharsetDetector det)\n        {\n            int confidence = match(det, ngrams, byteMap);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n        \n    static class CharsetRecog_KOI8_R extends CharsetRecog_sbcs\n    {\n        private static final int[] ngrams = {\n            0x20C4CF, 0x20C920, 0x20CBC1, 0x20CBCF, 0x20CEC1, 0x20CEC5, 0x20CFC2, 0x20D0CF, 0x20D0D2, 0x20D2C1, 0x20D3CF, 0x20D3D4, 0x20D4CF, 0x20D720, 0x20D7CF, 0x20DAC1, \n            0x20DCD4, 0x20DED4, 0xC1CEC9, 0xC1D4D8, 0xC5CCD8, 0xC5CEC9, 0xC5D3D4, 0xC5D420, 0xC7CF20, 0xC920D0, 0xC9C520, 0xC9C920, 0xC9D120, 0xCCC5CE, 0xCCC920, 0xCCD8CE, \n            0xCEC120, 0xCEC520, 0xCEC9C5, 0xCEC9D1, 0xCECF20, 0xCECFD7, 0xCF20D0, 0xCF20D3, 0xCF20D7, 0xCFC7CF, 0xCFCA20, 0xCFCCD8, 0xCFCD20, 0xCFD3D4, 0xCFD720, 0xCFD7C1, \n            0xD0CFCC, 0xD0D2C5, 0xD0D2C9, 0xD0D2CF, 0xD2C1D7, 0xD2C5C4, 0xD3D120, 0xD3D4C1, 0xD3D4C9, 0xD3D4D7, 0xD4C5CC, 0xD4CF20, 0xD4CFD2, 0xD4D820, 0xD9C820, 0xDED4CF, \n        };\n\n        private static final byte[] byteMap = {\n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, \n            (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F, \n            (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, \n            (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, \n            (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F, \n            (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, \n            (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xA3, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xA3, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, \n            (byte) 0xC0, (byte) 0xC1, (byte) 0xC2, (byte) 0xC3, (byte) 0xC4, (byte) 0xC5, (byte) 0xC6, (byte) 0xC7, \n            (byte) 0xC8, (byte) 0xC9, (byte) 0xCA, (byte) 0xCB, (byte) 0xCC, (byte) 0xCD, (byte) 0xCE, (byte) 0xCF, \n            (byte) 0xD0, (byte) 0xD1, (byte) 0xD2, (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0xD7, \n            (byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0xDB, (byte) 0xDC, (byte) 0xDD, (byte) 0xDE, (byte) 0xDF, \n            (byte) 0xC0, (byte) 0xC1, (byte) 0xC2, (byte) 0xC3, (byte) 0xC4, (byte) 0xC5, (byte) 0xC6, (byte) 0xC7, \n            (byte) 0xC8, (byte) 0xC9, (byte) 0xCA, (byte) 0xCB, (byte) 0xCC, (byte) 0xCD, (byte) 0xCE, (byte) 0xCF, \n            (byte) 0xD0, (byte) 0xD1, (byte) 0xD2, (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0xD7, \n            (byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0xDB, (byte) 0xDC, (byte) 0xDD, (byte) 0xDE, (byte) 0xDF, \n        };\n        \n        public String getName()\n        {\n            return  \"KOI8-R\";\n        }\n        \n        public String getLanguage()\n        {\n            return \"ru\";\n        }\n        \n        public CharsetMatch match(CharsetDetector det)\n        {\n            int confidence = match(det, ngrams, byteMap);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n    \n    abstract static class CharsetRecog_IBM424_he extends CharsetRecog_sbcs\n    {\n        protected static byte[] byteMap = {\n/*                 -0           -1           -2           -3           -4           -5           -6           -7           -8           -9           -A           -B           -C           -D           -E           -F   */\n/* 0- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* 1- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* 2- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* 3- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* 4- */    (byte) 0x40, (byte) 0x41, (byte) 0x42, (byte) 0x43, (byte) 0x44, (byte) 0x45, (byte) 0x46, (byte) 0x47, (byte) 0x48, (byte) 0x49, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* 5- */    (byte) 0x40, (byte) 0x51, (byte) 0x52, (byte) 0x53, (byte) 0x54, (byte) 0x55, (byte) 0x56, (byte) 0x57, (byte) 0x58, (byte) 0x59, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* 6- */    (byte) 0x40, (byte) 0x40, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, (byte) 0x68, (byte) 0x69, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* 7- */    (byte) 0x40, (byte) 0x71, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x00, (byte) 0x40, (byte) 0x40, \n/* 8- */    (byte) 0x40, (byte) 0x81, (byte) 0x82, (byte) 0x83, (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, (byte) 0x88, (byte) 0x89, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* 9- */    (byte) 0x40, (byte) 0x91, (byte) 0x92, (byte) 0x93, (byte) 0x94, (byte) 0x95, (byte) 0x96, (byte) 0x97, (byte) 0x98, (byte) 0x99, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* A- */    (byte) 0xA0, (byte) 0x40, (byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, (byte) 0xA8, (byte) 0xA9, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* B- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* C- */    (byte) 0x40, (byte) 0x81, (byte) 0x82, (byte) 0x83, (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, (byte) 0x88, (byte) 0x89, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* D- */    (byte) 0x40, (byte) 0x91, (byte) 0x92, (byte) 0x93, (byte) 0x94, (byte) 0x95, (byte) 0x96, (byte) 0x97, (byte) 0x98, (byte) 0x99, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* E- */    (byte) 0x40, (byte) 0x40, (byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, (byte) 0xA8, (byte) 0xA9, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* F- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n        };\n\n        public String getLanguage()\n        {\n            return \"he\";\n        }\n    }\n    static class CharsetRecog_IBM424_he_rtl extends CharsetRecog_IBM424_he \n    {\n        public String getName()\n        {\n            return \"IBM424_rtl\";\n        }\n        private static final int[] ngrams = {\n            0x404146, 0x404148, 0x404151, 0x404171, 0x404251, 0x404256, 0x404541, 0x404546, 0x404551, 0x404556, 0x404562, 0x404569, 0x404571, 0x405441, 0x405445, 0x405641, \n            0x406254, 0x406954, 0x417140, 0x454041, 0x454042, 0x454045, 0x454054, 0x454056, 0x454069, 0x454641, 0x464140, 0x465540, 0x465740, 0x466840, 0x467140, 0x514045, \n            0x514540, 0x514671, 0x515155, 0x515540, 0x515740, 0x516840, 0x517140, 0x544041, 0x544045, 0x544140, 0x544540, 0x554041, 0x554042, 0x554045, 0x554054, 0x554056, \n            0x554069, 0x564540, 0x574045, 0x584540, 0x585140, 0x585155, 0x625440, 0x684045, 0x685155, 0x695440, 0x714041, 0x714042, 0x714045, 0x714054, 0x714056, 0x714069, \n        };\n        public CharsetMatch match(CharsetDetector det)\n        {\n            int confidence = match(det, ngrams, byteMap, (byte)0x40);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n    static class CharsetRecog_IBM424_he_ltr extends CharsetRecog_IBM424_he \n    {\n        public String getName()\n        {\n            return \"IBM424_ltr\";\n        }\n        private static final int[] ngrams = {\n            0x404146, 0x404154, 0x404551, 0x404554, 0x404556, 0x404558, 0x405158, 0x405462, 0x405469, 0x405546, 0x405551, 0x405746, 0x405751, 0x406846, 0x406851, 0x407141,\n            0x407146, 0x407151, 0x414045, 0x414054, 0x414055, 0x414071, 0x414540, 0x414645, 0x415440, 0x415640, 0x424045, 0x424055, 0x424071, 0x454045, 0x454051, 0x454054,\n            0x454055, 0x454057, 0x454068, 0x454071, 0x455440, 0x464140, 0x464540, 0x484140, 0x514140, 0x514240, 0x514540, 0x544045, 0x544055, 0x544071, 0x546240, 0x546940,\n            0x555151, 0x555158, 0x555168, 0x564045, 0x564055, 0x564071, 0x564240, 0x564540, 0x624540, 0x694045, 0x694055, 0x694071, 0x694540, 0x714140, 0x714540, 0x714651\n\n        };\n        public CharsetMatch match(CharsetDetector det)\n        {\n            int confidence = match(det, ngrams, byteMap, (byte)0x40);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n    \n    abstract static class CharsetRecog_IBM420_ar extends CharsetRecog_sbcs\n    {\n\n        protected static byte[] byteMap = {\n/*                 -0           -1           -2           -3           -4           -5           -6           -7           -8           -9           -A           -B           -C           -D           -E           -F   */\n/* 0- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* 1- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* 2- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* 3- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* 4- */    (byte) 0x40, (byte) 0x40, (byte) 0x42, (byte) 0x43, (byte) 0x44, (byte) 0x45, (byte) 0x46, (byte) 0x47, (byte) 0x48, (byte) 0x49, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* 5- */    (byte) 0x40, (byte) 0x51, (byte) 0x52, (byte) 0x40, (byte) 0x40, (byte) 0x55, (byte) 0x56, (byte) 0x57, (byte) 0x58, (byte) 0x59, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* 6- */    (byte) 0x40, (byte) 0x40, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, (byte) 0x68, (byte) 0x69, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* 7- */    (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, (byte) 0x78, (byte) 0x79, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, \n/* 8- */    (byte) 0x80, (byte) 0x81, (byte) 0x82, (byte) 0x83, (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, (byte) 0x88, (byte) 0x89, (byte) 0x8A, (byte) 0x8B, (byte) 0x8C, (byte) 0x8D, (byte) 0x8E, (byte) 0x8F, \n/* 9- */    (byte) 0x90, (byte) 0x91, (byte) 0x92, (byte) 0x93, (byte) 0x94, (byte) 0x95, (byte) 0x96, (byte) 0x97, (byte) 0x98, (byte) 0x99, (byte) 0x9A, (byte) 0x9B, (byte) 0x9C, (byte) 0x9D, (byte) 0x9E, (byte) 0x9F, \n/* A- */    (byte) 0xA0, (byte) 0x40, (byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, (byte) 0xA8, (byte) 0xA9, (byte) 0xAA, (byte) 0xAB, (byte) 0xAC, (byte) 0xAD, (byte) 0xAE, (byte) 0xAF, \n/* B- */    (byte) 0xB0, (byte) 0xB1, (byte) 0xB2, (byte) 0xB3, (byte) 0xB4, (byte) 0xB5, (byte) 0x40, (byte) 0x40, (byte) 0xB8, (byte) 0xB9, (byte) 0xBA, (byte) 0xBB, (byte) 0xBC, (byte) 0xBD, (byte) 0xBE, (byte) 0xBF, \n/* C- */    (byte) 0x40, (byte) 0x81, (byte) 0x82, (byte) 0x83, (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, (byte) 0x88, (byte) 0x89, (byte) 0x40, (byte) 0xCB, (byte) 0x40, (byte) 0xCD, (byte) 0x40, (byte) 0xCF, \n/* D- */    (byte) 0x40, (byte) 0x91, (byte) 0x92, (byte) 0x93, (byte) 0x94, (byte) 0x95, (byte) 0x96, (byte) 0x97, (byte) 0x98, (byte) 0x99, (byte) 0xDA, (byte) 0xDB, (byte) 0xDC, (byte) 0xDD, (byte) 0xDE, (byte) 0xDF, \n/* E- */    (byte) 0x40, (byte) 0x40, (byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, (byte) 0xA8, (byte) 0xA9, (byte) 0xEA, (byte) 0xEB, (byte) 0x40, (byte) 0xED, (byte) 0xEE, (byte) 0xEF, \n/* F- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0x40, \n        };\n        \n\n        public String getLanguage()\n        {\n            return \"ar\";\n        }\n                \n    }\n    static class CharsetRecog_IBM420_ar_rtl extends CharsetRecog_IBM420_ar \n    {\n        private static final int[] ngrams = {\n            0x4056B1, 0x4056BD, 0x405856, 0x409AB1, 0x40ABDC, 0x40B1B1, 0x40BBBD, 0x40CF56, 0x564056, 0x564640, 0x566340, 0x567540, 0x56B140, 0x56B149, 0x56B156, 0x56B158,\n            0x56B163, 0x56B167, 0x56B169, 0x56B173, 0x56B178, 0x56B19A, 0x56B1AD, 0x56B1BB, 0x56B1CF, 0x56B1DC, 0x56BB40, 0x56BD40, 0x56BD63, 0x584056, 0x624056, 0x6240AB,\n            0x6240B1, 0x6240BB, 0x6240CF, 0x634056, 0x734056, 0x736240, 0x754056, 0x756240, 0x784056, 0x9A4056, 0x9AB1DA, 0xABDC40, 0xB14056, 0xB16240, 0xB1DA40, 0xB1DC40,\n            0xBB4056, 0xBB5640, 0xBB6240, 0xBBBD40, 0xBD4056, 0xBF4056, 0xBF5640, 0xCF56B1, 0xCFBD40, 0xDA4056, 0xDC4056, 0xDC40BB, 0xDC40CF, 0xDC6240, 0xDC7540, 0xDCBD40,\n        };\n\n        public String getName()\n        {\n            return \"IBM420_rtl\";\n        }\n        public CharsetMatch match(CharsetDetector det)\n        {\n            int confidence =  matchIBM420(det, ngrams, byteMap, (byte)0x40);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n        \n    }\n    static class CharsetRecog_IBM420_ar_ltr extends CharsetRecog_IBM420_ar \n    {\n        private static final int[] ngrams = {\n            0x404656, 0x4056BB, 0x4056BF, 0x406273, 0x406275, 0x4062B1, 0x4062BB, 0x4062DC, 0x406356, 0x407556, 0x4075DC, 0x40B156, 0x40BB56, 0x40BD56, 0x40BDBB, 0x40BDCF, \n            0x40BDDC, 0x40DAB1, 0x40DCAB, 0x40DCB1, 0x49B156, 0x564056, 0x564058, 0x564062, 0x564063, 0x564073, 0x564075, 0x564078, 0x56409A, 0x5640B1, 0x5640BB, 0x5640BD,\n            0x5640BF, 0x5640DA, 0x5640DC, 0x565840, 0x56B156, 0x56CF40, 0x58B156, 0x63B156, 0x63BD56, 0x67B156, 0x69B156, 0x73B156, 0x78B156, 0x9AB156, 0xAB4062, 0xADB156,\n            0xB14062, 0xB15640, 0xB156CF, 0xB19A40, 0xB1B140, 0xBB4062, 0xBB40DC, 0xBBB156, 0xBD5640, 0xBDBB40, 0xCF4062, 0xCF40DC, 0xCFB156, 0xDAB19A, 0xDCAB40, 0xDCB156\n        };\n\n        public String getName()\n        {\n            return \"IBM420_ltr\";\n        }\n        public CharsetMatch match(CharsetDetector det)\n        {\n            int confidence = matchIBM420(det, ngrams, byteMap, (byte)0x40);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n        \n    }\n\n\n\n\n    static class CharsetRecog_cp866 extends CharsetRecog_sbcs\n    {\n        private static final int[] ngrams = {\n             // ' в '     ' во'     ' до'     ' за'     ' и '     ' ка'     ' ко'     ' на'     ' не'     ' об'     ' по'     ' пр'     ' ра'     ' со'     ' ст'     ' то'\n                0x20A220, 0x20A2AE, 0x20A4AE, 0x20A7A0, 0x20A820, 0x20AAA0, 0x20AAAE, 0x20ADA0, 0x20ADA5, 0x20AEA1, 0x20AFAE, 0x20AFE0, 0x20E0A0, 0x20E1AE, 0x20E1E2, 0x20E2AE,\n                // ' чт'     ' эт'     'ани'     'ать'     'го '     'ель'     'ени'     'ест'     'ет '     'и п'     'ие '     'ии '     'ия '     'лен'     'ли '     'льн'\n                0x20E7E2, 0x20EDE2, 0xA0ADA8, 0xA0E2EC, 0xA3AE20, 0xA5ABEC, 0xA5ADA8, 0xA5E1E2, 0xA5E220, 0xA820AF, 0xA8A520, 0xA8A820, 0xA8EF20, 0xABA5AD, 0xABA820, 0xABECAD,\n                // 'на '     'не '     'ние'     'ния'     'но '     'нов'     'о в'     'о п'     'о с'     'ов '     'ова'     'ого'     'ой '     'оль'     'ом '     'ост'\n                0xADA020, 0xADA520, 0xADA8A5, 0xADA8EF, 0xADAE20, 0xADAEA2, 0xAE20A2, 0xAE20AF, 0xAE20E1, 0xAEA220, 0xAEA2A0, 0xAEA3AE, 0xAEA920, 0xAEABEC, 0xAEAC20, 0xAEE1E2,\n                // 'пол'     'пре'     'при'     'про'     'рав'     'ред'     'ста'     'ств'     'сти'     'ся '     'тел'     'то '     'тор'     'ть '     'что'     'ых '\n                0xAFAEAB, 0xAFE0A5, 0xAFE0A8, 0xAFE0AE, 0xE0A0A2, 0xE0A5A4, 0xE1E2A0, 0xE1E2A2, 0xE1E2A8, 0xE1EF20, 0xE2A5AB, 0xE2AE20, 0xE2AEE0, 0xE2EC20, 0xE7E2AE, 0xEBE520\n        };\n\n        private static final byte[] byteMap = {\n/*                     -0           -1           -2           -3           -4           -5           -6           -7           -8           -9           -A           -B           -C           -D           -E           -F   */\n/* 0- */        (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n/* 1- */        (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n/* 2- */        (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n/* 3- */        (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n/* 4- */        (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n/* 5- */        (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n/* 6- */        (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n/* 7- */        (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n/* 8- */        (byte) 0xA0, (byte) 0xA1, (byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, (byte) 0xA8, (byte) 0xA9, (byte) 0xAA, (byte) 0xAB, (byte) 0xAC, (byte) 0xAD, (byte) 0xAE, (byte) 0xAF,\n/* 9- */        (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,\n/* A- */        (byte) 0xA0, (byte) 0xA1, (byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, (byte) 0xA8, (byte) 0xA9, (byte) 0xAA, (byte) 0xAB, (byte) 0xAC, (byte) 0xAD, (byte) 0xAE, (byte) 0xAF,\n/* B- */        (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n/* C- */        (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n/* D- */        (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n/* E- */        (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,\n/* F- */        (byte) 0xF0, (byte) 0xF0, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n        };\n\n        public String getName()\n        {\n            return  \"cp866\";\n        }\n\n        public String getLanguage()\n        {\n            return \"ru\";\n        }\n\n        public CharsetMatch match(CharsetDetector det)\n        {\n            int confidence = match(det, ngrams, byteMap);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/ibm/icu/text/CharsetRecognizer.java",
    "content": "/*\n*******************************************************************************\n* Copyright (C) 2005-2012, International Business Machines Corporation and    *\n* others. All Rights Reserved.                                                *\n*******************************************************************************\n*/\npackage com.ibm.icu.text;\n\n/**\n * Abstract class for recognizing a single charset.\n * Part of the implementation of ICU's CharsetDetector.\n\n * Each specific charset that can be recognized will have an instance of some subclass of this class.\n * All interaction between the overall CharsetDetector and the stuff specific to an individual charset happens\n * via the interface provided here.\n\n * Instances of CharsetDetector DO NOT have or maintain state pertaining to a specific match or detect operation.\n * The WILL be shared by multiple instances of CharsetDetector. They encapsulate const charset-specific information.\n */\nabstract class CharsetRecognizer {\n    /**\n     * Get the IANA name of this charset.\n     * @return the charset name.\n     */\n    abstract String      getName();\n    \n    /**\n     * Get the ISO language code for this charset.\n     * @return the language code, or <code>null</code> if the language cannot be determined.\n     */\n    public   String      getLanguage()\n    {\n        return null;\n    }\n    \n    /**\n     * Test the match of this charset with the input text data\n     *      which is obtained via the CharsetDetector object.\n     * \n     * @param det  The CharsetDetector, which contains the input text\n     *             to be checked for being in this charset.\n     * @return     A CharsetMatch object containing details of match\n     *             with this charset, or null if there was no match.\n     */\n    abstract CharsetMatch  match(CharsetDetector det);\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/PlatformManager.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander;\n\nimport java.io.File;\nimport java.io.IOException;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.runtime.OsFamily;\n\n/**\n * This class takes care of platform-specific issues, such as getting screen dimensions and issuing commands.\n *\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic class PlatformManager {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(PlatformManager.class);\n\t\n\n    /** Folder in which to store the preferences. */\n    private static AbstractFile prefFolder;\n\n    /**\n     * Returns the path to the default muCommander preferences folder.\n     * <p>\n     * This folder is:\n     * <ul>\n     *  <li><code>~/Library/Preferences/trolCommander/</code> under MAC OS X.</li>\n     *  <li><code>~/.trolcommander/</code> under all other OSes.</li>\n     * </ul>\n     * <p>\n     * If the default preferences folder doesn't exist, this method will create it.\n     *\n     * @return the path to the default trolCommander preferences folder.\n     */\n    public static AbstractFile getDefaultPreferencesFolder() {\n        return FileFactory.getFile(getDefaultPreferencesFolderPath());\n    }\n\n    /**\n     * Returns the path to the default muCommander preferences folder.\n     * <p>\n     * This folder is:\n     * <ul>\n     *  <li><code>~/Library/Preferences/trolCommander/</code> under MAC OS X.</li>\n     *  <li><code>~/.trolcommander/</code> under all other OSes.</li>\n     * </ul>\n     * <p>\n     * If the default preferences folder doesn't exist, this method will create it.\n     *\n     * @return the path to the default trolCommander preferences folder.\n     */\n    public static String getDefaultPreferencesFolderPath() {\n        File folder;\n\n        // Mac OS X specific folder (~/Library/Preferences/trolCommander)\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            folder = new File(System.getProperty(\"user.home\") + \"/Library/Preferences/trolCommander\");\n            // For all other platforms, use generic folder (~/.trolcommander)\n        } else {\n            folder = new File(System.getProperty(\"user.home\"), \"/.trolcommander\");\n        }\n\n        // Makes sure the folder exists.\n        if (!folder.exists()) {\n            if (!folder.mkdir()) {\n                LOGGER.warn(\"Could not create preference folder: {}\", folder.getAbsolutePath());\n            }\n        }\n        return folder.getAbsolutePath();\n    }\n\n    /**\n     * Returns the path to the folder that contains all the user's data.\n     * <p>\n     * All modules that save user data to a file should do so in a file located in\n     * the folder returned by this method.\n     * <p>\n     * The value returned by this method can be set through {@link #setPreferencesFolder(File)}.\n     * Otherwise, the {@link #getDefaultPreferencesFolder() default preference folder} will be\n     * used.\n     *\n     * @return the path to the user's preference folder.\n     * @see    #setPreferencesFolder(AbstractFile)\n     */\n    public static AbstractFile getPreferencesFolder() {\n        // If the preferences folder has been set, use it.\n        if (prefFolder != null) {\n            return prefFolder;\n        }\n        return getDefaultPreferencesFolder();\n    }\n\n    /**\n     * Sets the path to the folder in which trolCommander will look for its preferences.\n     * <p>\n     * If <code>folder</code> is a file, its parent folder will be used instead. If it doesn't exist,\n     * this method will create it.\n     *\n     * @param  folder      path to the folder in which trolCommander will look for its preferences.\n     * @throws IOException if an IO error occurs.\n     * @see                #getPreferencesFolder()\n     * @see                #setPreferencesFolder(String)\n     * @see                #setPreferencesFolder(AbstractFile)\n     */\n    public static void setPreferencesFolder(File folder) throws IOException {\n        AbstractFile file = FileFactory.getFile(folder.getAbsolutePath());\n        if (file != null) {\n            setPreferencesFolder(file);\n        }\n    }\n\n    /**\n     * Sets the path to the folder in which trolCommander will look for its preferences.\n     * <p>\n     * If <code>folder</code> is a file, its parent folder will be used instead. If it doesn't exist,\n     * this method will create it.\n     *\n     * @param  path        path to the folder in which trolCommander will look for its preferences.\n     * @throws IOException if an IO error occurs.\n     * @see                #getPreferencesFolder()\n     * @see                #setPreferencesFolder(File)\n     * @see                #setPreferencesFolder(AbstractFile)\n     */\n    public static void setPreferencesFolder(String path) throws IOException {\n        AbstractFile folder = FileFactory.getFile(path);\n\n        if (folder == null) {\n            setPreferencesFolder(new File(path));\n        } else {\n            setPreferencesFolder(folder);\n        }\n    }\n\n    /**\n     * Sets the path to the folder in which trolCommander will look for its preferences.\n     * <p>\n     * If <code>folder</code> is a file, its parent folder will be used instead. If it doesn't exist,\n     * this method will create it.\n     *\n     * @param  folder      path to the folder in which trolCommander will look for its preferences.\n     * @throws IOException if an IO error occurs.\n     * @see                #getPreferencesFolder()\n     * @see                #setPreferencesFolder(String)\n     * @see                #setPreferencesFolder(File)\n     */\n    public static void setPreferencesFolder(AbstractFile folder) throws IOException {\n        if (!folder.exists()) {\n            folder.mkdir();\n        } else if (!folder.isBrowsable()) {\n            folder = folder.getParent();\n        }\n        prefFolder = folder;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/RuntimeConstants.java",
    "content": "/*\r\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\r\n * Copyright (C) 2013-2020 Oleg Trifonov\r\n *\r\n * trolCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * trolCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander;\r\n\r\nimport java.awt.*;\r\nimport java.io.IOException;\r\nimport java.io.InputStream;\r\nimport java.util.Calendar;\r\nimport java.util.jar.Attributes;\r\nimport java.util.jar.Manifest;\r\n\r\nimport org.jetbrains.annotations.Nullable;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport com.mucommander.commons.file.util.ResourceLoader;\r\n\r\n/**\r\n * Defines various generic trolCommander constants.\r\n * @author Nicolas Rinaudo\r\n */\r\npublic class RuntimeConstants {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(RuntimeConstants.class);\r\n\t\r\n    /** Path to the themes directory. */\r\n    public static final String THEMES_PATH     = \"/themes\";\r\n    /** Path to the viewer/editor themes directory. */\r\n    public static final String TEXT_SYNTAX_THEMES_PATH = \"/themes/editor\";\r\n    /** Path to the trolCommander license file. */\r\n    public static final String LICENSE         = \"/license.txt\";\r\n    /** Default trolCommander theme. */\r\n    public static final String DEFAULT_THEME   = \"Native\";\r\n    public static final boolean DISPLAY_4K = is4KDisplay();\r\n\r\n\r\n    /** Homepage URL. */\r\n    public static final String HOMEPAGE_URL       = \"http://trolsoft.ru/en/soft/trolcommander\";\r\n\r\n    public static final String DEFAULT_VERSION_URL = \"http://trolsoft.ru/content/soft/trolcommander/version.xml\";\r\n    /** URL at which to download the latest version description. */\r\n    public static final String VERSION_URL;\r\n    /** URL of the trolCommander forums. */\r\n    public static final String FORUMS_URL         = HOMEPAGE_URL + \"/forums/\";\r\n    /** URL at which to see the donation information. */\r\n    public static final String DONATION_URL       = HOMEPAGE_URL + \"/#donate\";\r\n    /** Bug tracker URL. */\r\n    public static final String BUG_REPOSITORY_URL = \"https://github.com/trol73/mucommander/issues\"; //HOMEPAGE_URL + \"/bugs/\";\r\n    /** Documentation URL. */\r\n    public static final String DOCUMENTATION_URL  = HOMEPAGE_URL;// + \"/documentation/\";\r\n\r\n\r\n\r\n    /**\r\n     * Release date to use in case the JAR file doesn't contain the information.\r\n     * This is guaranteed to trigger a software update - the JAR file is corrupt, so we might as well get the latest\r\n     * version.\r\n     */\r\n    private static final String DEFAULT_RELEASE_DATE = \"20020101\";\r\n    /** Current trolCommander version (<code>MAJOR.MINOR.DEV</code>). */\r\n    public  static final String VERSION;\r\n    /** Date at which the build was generated (<code>YYYYMMDD</code>). */\r\n    public  static final String BUILD_DATE;\r\n    /** Time at which the build was generated (<code>HHMM</code>). */\r\n    public  static final String BUILD_TIME;\r\n    /** Copyright information (<code>YYYY-YYYY</code>). */\r\n    public  static final String COPYRIGHT;\r\n    /** String describing the software (<code>trolCommander vMAJOR.MINOR.DEV</code>). */\r\n    public  static final String APP_STRING;\r\n    /** String describing the trolCommander build number. */\r\n    public static final String BUILD_NUMBER;\r\n    /** YYYYMMDDHHmm   */\r\n    public static final String BUILD_CODE;\r\n\r\n    \r\n\r\n    static {\r\n        Attributes attributes = getManifestAttributes();\r\n\r\n        if (attributes == null) {   // No MANIFEST.MF found, use default values.\r\n            VERSION = \"?\";\r\n            COPYRIGHT    = \"2013-\" + Calendar.getInstance().get(Calendar.YEAR);\r\n            // We use a date that we are sure is later than the latest version to trigger the version checker.\r\n            // After all, the JAR appears to be corrupt and should be upgraded.\r\n            BUILD_DATE = DEFAULT_RELEASE_DATE;\r\n            BUILD_TIME = null;\r\n            VERSION_URL  = DEFAULT_VERSION_URL;\r\n            BUILD_NUMBER = \"?\";\r\n            BUILD_CODE = null;\r\n        } else {    // A MANIFEST.MF file was found, extract data from it.\r\n            VERSION = getAttribute(attributes, \"Specification-Version\");\r\n            BUILD_DATE = getAttribute(attributes, \"Build-Date\");\r\n            BUILD_TIME = getAttribute(attributes, \"Build-Time\");\r\n            VERSION_URL = getAttribute(attributes, \"Build-URL\");\r\n            BUILD_NUMBER = getAttribute(attributes, \"Implementation-Version\");\r\n            // Protection against corrupt manifest files.\r\n            COPYRIGHT = BUILD_DATE.length() > 4 ? BUILD_DATE.substring(0, 4) : DEFAULT_RELEASE_DATE;\r\n            BUILD_CODE = BUILD_DATE + BUILD_TIME;\r\n        }\r\n        APP_STRING = \"trolCommander v\" + VERSION;\r\n    }\r\n\r\n    @Nullable\r\n    private static Attributes getManifestAttributes() {\r\n        try (InputStream in = ResourceLoader.getResourceAsStream(\"META-INF/MANIFEST.MF\", ResourceLoader.getDefaultClassLoader(), ResourceLoader.getRootPackageAsFile(RuntimeConstants.class))) {\r\n            if (in != null) {\r\n                Manifest manifest = new Manifest();\r\n                manifest.read(in);\r\n                return manifest.getMainAttributes();\r\n            }\r\n        } catch (IOException e) {\r\n            LOGGER.warn(\"Failed to read MANIFEST.MF, default values will be used\", e);\r\n        }\r\n        return null;\r\n    }\r\n\r\n    /**\r\n     * Extract the requested attribute value.\r\n     * @param  attributes attributes from which to extract the requested value.\r\n     * @param  name       name of the attribute to retrieve.\r\n     * @return            the requested attribute value.\r\n     */\r\n    private static String getAttribute(Attributes attributes, String name) {\r\n        String buffer = attributes.getValue(name);\r\n        return buffer == null ? \"?\" : buffer;\r\n    }\r\n\r\n    private static boolean is4KDisplay() {\r\n        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();\r\n        return screenSize.width*screenSize.height > 3500*3500;\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/StressTester.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander;\n\nimport java.awt.Dimension;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.util.Random;\n\nimport javax.swing.JButton;\nimport javax.swing.JDialog;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.action.ActionManager;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.WindowManager;\nimport com.mucommander.ui.main.table.FileTable;\n\n/**\n * Used to start muCommander in stress-test mode.\n * @author Maxence Bernard\n */\npublic class StressTester implements Runnable, ActionListener {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(StressTester.class);\n\t\n    private boolean run;\n\n    public StressTester() {\n        run = true;\n    }\n\n    /**\n     * Stops the current stress test.\n     */\n    public void stop() {\n        run = false;\n    }\n\n    public void run() {\n        Random random = new Random();\n        MainFrame mainFrame = WindowManager.getCurrentMainFrame();\n\n        while (run) {\n            if(random.nextInt(2)==0) {\n                ActionManager.performAction(com.mucommander.ui.action.impl.SwitchActiveTableAction.Descriptor.ACTION_ID, mainFrame);\n            }\n\n            FolderPanel folderPanel = mainFrame.getActivePanel();\n            FileTable fileTable = mainFrame.getActiveTable();\n            AbstractFile currentFolder = folderPanel.getCurrentFolder();\n\n            try {\n                AbstractFile parentFolder = currentFolder.getParent();\n                AbstractFile[] children = currentFolder.ls();\n                // 1 in 3 chance to go up if folder has children\n                if (children.length==0 || (random.nextInt(3)==0 && parentFolder!=null)) {\n                    fileTable.selectFile(0);\n                    ActionManager.performAction(com.mucommander.ui.action.impl.OpenAction.Descriptor.ACTION_ID, mainFrame);\n                } else {\n                    AbstractFile randomChild = children[random.nextInt(children.length)];\n                    if(!randomChild.isBrowsable())\n                        continue;\n                    // Try to ls() in RandomChild to trigger an IOException if folder is not readable\n                    // so that no error dialog pops up when calling tryChangeCurrentFolder()\n                    randomChild.ls();\n                    fileTable.selectFile(randomChild);\n                    ActionManager.performAction(com.mucommander.ui.action.impl.OpenAction.Descriptor.ACTION_ID, mainFrame);\n                    //\t\t\t\t\tfolderPanel.tryChangeCurrentFolder(randomChild, true);\n                }\n            } catch(Exception e) {\n                LOGGER.debug(\"Caught Exception\", e);\n            }\n\n            LOGGER.trace(\"Sleeping for a bit...\");\n            try {\n                Thread.sleep(100+random.nextInt(200));\n            } catch(InterruptedException e) {\n                LOGGER.debug(\"Caught InterruptedException\", e);\n            }\n        }\n    }\n\n    public void actionPerformed(ActionEvent e) {\n        stop();\n    }\n\n    /**\n     * Method used to start the stress tester.\n     * @param args command line arguments.\n     */\n    public static void main(String[] args) {\n        TrolCommander.main(args);\n\n        StressTester instance = new StressTester();\n        JDialog stopDialog = new JDialog();\n        JButton stopButton = new JButton(\"Stop\");\n        new Thread(instance).start();\n        stopButton.addActionListener(instance);\n        stopDialog.getContentPane().add(stopButton);\n        stopDialog.setSize(new Dimension(80, 60));\n        stopDialog.setVisible(true);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/TrolCommander.java",
    "content": "/*\r\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\r\n * Copyright (C) 2013-2020 Oleg Trifonov\r\n *\r\n * trolCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * trolCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander;\r\n\r\nimport com.mucommander.command.Command;\r\nimport com.mucommander.command.CommandManager;\r\nimport com.mucommander.command.CommandType;\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.conf.TcConfigurations;\r\nimport com.mucommander.conf.TcPreference;\r\nimport com.mucommander.conf.TcPreferences;\r\nimport com.mucommander.launcher.LauncherCmdHelper;\r\nimport com.mucommander.launcher.LauncherExecutor;\r\nimport com.mucommander.launcher.LauncherTask;\r\nimport com.mucommander.profiler.Profiler;\r\nimport com.mucommander.ui.dialog.InformationDialog;\r\nimport com.mucommander.ui.dialog.startup.CheckVersionDialog;\r\nimport com.mucommander.ui.main.WindowManager;\r\nimport com.mucommander.updates.VersionChecker;\r\nimport com.mucommander.utils.text.Translator;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport javax.swing.SwingUtilities;\r\nimport java.util.*;\r\nimport java.util.List;\r\n\r\nimport static com.mucommander.launcher.TasksKt.prepareLauncherTasks;\r\n\r\n/**\r\n * trolCommander launcher.\r\n * <p>\r\n * This class is used to start muCommander. It will analyse command line\r\n * arguments, initialize the whole software and start the main window.\r\n *\r\n * @author Maxence Bernard, Nicolas Rinaudo, Oleg Trifonov\r\n */\r\npublic class TrolCommander {\r\n\tprivate static Logger logger;\r\n\r\n    /** true while the application is launching, false after it has finished launching */\r\n    private static boolean isLaunching = true;\r\n    /** Launch lock. */\r\n    private static final Object LAUNCH_LOCK = new Object();\r\n\r\n\r\n    /**\r\n     * Prevents initialization of the <code>Launcher</code>.\r\n     */\r\n    private TrolCommander() {}\r\n\r\n\r\n    /**\r\n     * This method can be called to wait until the application has been launched. The caller thread will be blocked\r\n     * until the application has been launched.\r\n     * This method will return immediately if the application has already been launched when it is called.\r\n     */\r\n    public static void waitUntilLaunched() {\r\n        getLogger().debug(\"called, thread {}\", Thread.currentThread());\r\n        synchronized(LAUNCH_LOCK) {\r\n            while (isLaunching) {\r\n                try {\r\n                    getLogger().debug(\"waiting\");\r\n                    LAUNCH_LOCK.wait();\r\n                } catch (InterruptedException e) {\r\n                    // will loop\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    // - Boot code --------------------------------------------------------------\r\n    // --------------------------------------------------------------------------\r\n    /**\r\n     * Method used to migrate commands that used to be defined in the configuration but were moved to <code>commands.xml</code>.\r\n     * @param useName     name of the <code>use custom command</code> configuration variable.\r\n     * @param commandName name of the <code>custom command</code> configuration variable.\r\n     */\r\n    public static void migrateCommand(String useName, String commandName, String alias) {\r\n        String command;\r\n\r\n        if (TcConfigurations.getPreferences().getBooleanVariable(useName) && (command = TcConfigurations.getPreferences().getVariable(commandName)) != null) {\r\n            CommandManager.registerCommand(new Command(alias, command, CommandType.SYSTEM_COMMAND));\r\n            TcConfigurations.getPreferences().removeVariable(useName);\r\n            TcConfigurations.getPreferences().removeVariable(commandName);\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Main method used to startup muCommander.\r\n     * @param args command line arguments.\r\n     */\r\n    public static void main(String[] args) {\r\n        if (OsFamily.MAC_OS_X.isCurrent()) {\r\n            System.setProperty(\"com.apple.mrj.application.apple.menu.about.name\", \"trolCommander\");\r\n\t\t\t// disable openGL in javaFX (used for HtmlViewer) as it cashes JVM under vmWare\r\n\t\t\tSystem.setProperty(\"prism.order\", \"sw\");\r\n        }\r\n\r\n        Profiler.start(\"init\");\r\n        Profiler.start(\"loading\");\r\n\r\n        int processors = Runtime.getRuntime().availableProcessors();\r\n        System.out.println(\"Current OS family: \" + OsFamily.getCurrent());\r\n        System.out.println(\"Processors: \" + processors);\r\n\r\n        try (LauncherExecutor executor = new LauncherExecutor(processors <= 0 ? 1 : processors)) {\r\n            LauncherCmdHelper helper = new LauncherCmdHelper(args, true, false);\r\n            // Whether, or not to ignore warnings when booting.\r\n            helper.parseArgs();\r\n\r\n            List<LauncherTask> tasks = prepareLauncherTasks(helper);\r\n            if (processors <= 1) {\r\n                for (LauncherTask t : tasks) {\r\n                    t.call();\r\n                }\r\n            } else {\r\n                while (!executor.isFull()) {\r\n                    executor.executeFirst(tasks);\r\n                }\r\n                while (!tasks.isEmpty()) {\r\n                    executor.executeFirst(tasks);\r\n                    if (executor.isFull()) {\r\n                        try {\r\n                            Thread.sleep(1);\r\n                        } catch (Exception ignore) {}\r\n                    } else {\r\n                        if (executor.executeFirst(tasks)) {\r\n                            continue;\r\n                        }\r\n                        LauncherTask t = tasks.getFirst();\r\n                        executor.execute(t, true);\r\n                        tasks.remove(t);\r\n                    }\r\n                }\r\n            }\r\n//            executor.shutdown();\r\n            System.out.println(\"finished\");\r\n        } catch(Throwable t) {\r\n            // Startup failed, dispose the splash screen\r\n//            if (splashScreen != null) {\r\n//                splashScreen.dispose();\r\n//            }\r\n\r\n            getLogger().error(\"Startup failed\", t);\r\n            \r\n            // Display an error dialog with a proper message and error details\r\n            InformationDialog.showErrorDialog(null, null, Translator.get(\"startup_error\"), null, t);\r\n\r\n            // Quit the application\r\n            WindowManager.quit();\r\n        }\r\n\r\n\r\n/*\r\n        try {\r\n            executor.awaitTermination(100, TimeUnit.SECONDS);\r\n        } catch (InterruptedException e) {\r\n            e.printStackTrace();\r\n        }\r\n*/\r\n        // Done launching, wake up threads waiting for the application being launched.\r\n        // Important: this must be done before disposing the splash screen, as this would otherwise create a deadlock\r\n        // if the AWT event thread were waiting in #waitUntilLaunched .\r\n        synchronized(LAUNCH_LOCK) {\r\n            isLaunching = false;\r\n            LAUNCH_LOCK.notifyAll();\r\n        }\r\n\r\n        // Check for newer version unless it was disabled\r\n        if (TcConfigurations.getPreferences().getVariable(TcPreference.CHECK_FOR_UPDATE, TcPreferences.DEFAULT_CHECK_FOR_UPDATE)) {\r\n            SwingUtilities.invokeLater(() -> {\r\n                try {\r\n                    VersionChecker versionChecker = VersionChecker.getInstance();\r\n                    if (versionChecker != null && versionChecker.isNewVersionAvailable()) {\r\n                        new CheckVersionDialog(WindowManager.getCurrentMainFrame(), versionChecker, false);\r\n                    }\r\n                } catch (Exception e) {\r\n                    getLogger().error(\"Check version error\", e);\r\n                }\r\n            });\r\n        }\r\n        Profiler.stop(\"init\");\r\n\r\n        //Profiler.print();\r\n        //Profiler.hide(\"launcher.\");\r\n        //Profiler.printThreads();\r\n        //Profiler.initThreads();\r\n    }\r\n\r\n    private static Logger getLogger() {\r\n        if (logger == null) {\r\n            logger = LoggerFactory.getLogger(TrolCommander.class);\r\n        }\r\n        return logger;\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/adb/AdbUtils.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.adb;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.process.ExecutionFinishListener;\nimport com.mucommander.process.ExecutorUtils;\nimport com.mucommander.ui.tools.ToolsEnvironment;\nimport se.vidstige.jadb.JadbConnection;\nimport se.vidstige.jadb.JadbDevice;\nimport se.vidstige.jadb.JadbException;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * @author Oleg Trifonov\n * Created on 25/12/15.\n */\npublic class AdbUtils {\n\n\n    private static Map<String, String> lastDeviceNames;\n\n    /**\n     * Get list of connected ADB devices\n     * @return null if adb doesn't found\n     */\n    public static List<String> getDevices()  {\n        try {\n            JadbConnection connection = new JadbConnection();\n            List<JadbDevice> devices = connection.getDevices();\n            List<String> names = new ArrayList<>();\n            for (JadbDevice device : devices) {\n                names.add(device.getSerial());\n            }\n            return names;\n        } catch (JadbException | IOException e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    /**\n     *\n     * @return true if adb found\n     */\n    public static boolean checkAdb() {\n        AbstractFile adbPath = getAdbPath();\n        if (OsFamily.getCurrent().isUnixBased() && adbPath != null) {\n            try {\n                int result = ExecutorUtils.execute(\"./adb devices -l\", adbPath);\n                return result == 0;\n            } catch (IOException | InterruptedException ignore) {}\n        }\n        try {\n            int result = ExecutorUtils.execute(\"adb devices -l\", adbPath);\n            return result == 0;\n        } catch (IOException | InterruptedException ignore) {}\n        return false;\n    }\n\n    /**\n     * Tried to found adb utility location\n     *\n     * @return path to adb utility or null\n     */\n    private static AbstractFile getAdbPath() {\n        String path;\n        try {\n            path = ToolsEnvironment.getEnv(\"ANDROID_HOME\");\n        } catch (SecurityException ignore) {\n            path = null;\n        }\n        if (path != null) {\n            String adb = path + (OsFamily.WINDOWS .isCurrent() ? \"\\\\platform-tools\\\\adb.exe\" : \"/platform-tools/adb\");\n            AbstractFile result = FileFactory.getFile(adb);\n            if (result != null && result.exists() && !result.isDirectory()) {\n                return result.getParent();\n            }\n        }\n        try {\n            path = ToolsEnvironment.getEnv(\"ADB_HOME\");\n        } catch (SecurityException ignore) {\n            path = null;\n        }\n        if (path != null) {\n            String adb = path + (OsFamily.getCurrent() == OsFamily.WINDOWS ? \"\\\\adb.exe\" : \"/adb\");\n            AbstractFile result = FileFactory.getFile(adb);\n            if (result != null && result.exists() && !result.isDirectory()) {\n                return result.getParent();\n            }\n        }\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            String defaultPath = System.getProperty(\"user.home\") + \"/Library/Android/sdk/platform-tools/adb\";\n            AbstractFile result = FileFactory.getFile(defaultPath);\n            if (result != null && result.exists() && !result.isDirectory()) {\n                return result.getParent();\n            }\n        }\n        return null;\n    }\n\n    /**\n     *\n     * @param serial the device serial number\n     *\n     * @return device name (or null if unknown)\n     */\n    public static String getDeviceName(String serial) {\n        if (lastDeviceNames == null || !lastDeviceNames.containsKey(serial)) {\n            lastDeviceNames = getDeviceNames();\n        }\n        return lastDeviceNames.get(serial);\n    }\n\n\n    /**\n     *\n     * @return serial to name\n     */\n    public static Map<String, String> getDeviceNames() {\n        final Map<String, String> result = new HashMap<>();\n        ExecutionFinishListener listener = (exitCode, output) -> {\n            parseDevicesList(result, output);\n        };\n        AbstractFile adbPath = getAdbPath();\n        if (OsFamily.getCurrent().isUnixBased() && adbPath != null) {\n            try {\n                ExecutorUtils.executeAndGetOutput(\"./adb devices -l\", adbPath, listener);\n            } catch (IOException | InterruptedException e) {\n                try {\n                    ExecutorUtils.executeAndGetOutput(\"adb devices -l\", adbPath, listener);\n                } catch (IOException | InterruptedException e2) {\n                    e2.printStackTrace();\n                }\n            }\n        }\n        return result;\n    }\n\n    private static void parseDevicesList(Map<String, String> result, String output) {\n        String[] lines = output.split(\"\\\\r?\\\\n\");\n        for (String s : lines) {\n            String[] columns = s.split(\"\\\\s+\");\n            for (String val : columns) {\n                if (val.startsWith(\"model:\")) {\n                    String serial = columns[0];\n                    String name = val.substring(6); // \"model:\"\n                    name = name.replace('_', ' ');\n                    result.put(serial, name);\n                }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/adb/AndroidMenu.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.adb;\n\n\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.helper.MnemonicHelper;\nimport com.mucommander.ui.icon.IconManager;\n\nimport javax.swing.JMenu;\nimport javax.swing.JMenuItem;\nimport javax.swing.event.MenuEvent;\nimport javax.swing.event.MenuListener;\nimport java.util.List;\n\n/**\n * An abstract JMenu that contains an item for each Android ADB devices available\n *\n * <p>Note: the items list is refreshed each time the menu is selected. In other words, a new instance of AdbMenu\n * does not have to be created in order to see new devices.\n *\n * Created on 28/12/15.\n * @author Oleg Trifonov\n */\npublic abstract class AndroidMenu extends JMenu implements MenuListener {\n\n    /**\n     * Creates a new instance of <code>AndroidMenu</code>.\n     */\n    public AndroidMenu() {\n        super(Translator.get(\"adb.android_devices\"));\n\n        setIcon(IconManager.getIcon(IconManager.IconSet.FILE, \"android.png\"));\n\n        // Menu items will be added when menu gets selected\n        addMenuListener(this);\n    }\n\n    /**\n     * Returns the action to perform for the given item.\n     *\n     * @param deviceSerial the serial number of the device\n     * @return the action to perform for the given Android device\n     */\n    public abstract TcAction getMenuItemAction(String deviceSerial);\n\n\n    @Override\n    public void menuSelected(MenuEvent e) {\n        // Remove previous menu items (if any)\n        removeAll();\n\n        List<String> androidDevices = AdbUtils.getDevices();\n        if (androidDevices == null) {\n            return;\n        }\n        if (androidDevices.isEmpty()) {\n            add(new JMenuItem(Translator.get(\"adb.no_devices\"))).setEnabled(false);\n            return;\n        }\n        MnemonicHelper mnemonicHelper = new MnemonicHelper();\n        for (String serial : androidDevices) {\n            JMenuItem menuItem = new JMenuItem(getMenuItemAction(serial));\n            menuItem.setMnemonic(mnemonicHelper.getMnemonic(menuItem.getText()));\n            String name = AdbUtils.getDeviceName(serial);\n            menuItem.setText(name == null ? serial : name);\n            menuItem.setIcon(IconManager.getIcon(IconManager.IconSet.FILE, \"android.png\"));\n\n            add(menuItem);\n        }\n    }\n\n    @Override\n    public void menuDeselected(MenuEvent e) {\n\n    }\n\n    @Override\n    public void menuCanceled(MenuEvent e) {\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/auth/CredentialsConstants.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.auth;\n\n/**\n * Contains XML elements and attributes used to parse and write the credentials file.\n *\n * @author Maxence Bernard\n */\ninterface CredentialsConstants {\n\n    /** Root element */\n    String ELEMENT_ROOT     = \"credentials_list\";\n\n    /** Element for each credential item, containing a URL, login and password */\n    String ELEMENT_CREDENTIALS = \"credentials\";\n\n    /** Element containing the credentials' URL */\n    String ELEMENT_URL      = \"url\";\n\n    /** Element containing the credentials' login */\n    String ELEMENT_LOGIN    = \"login\";\n\n    /** Element containing the credentials' (encrypted) password*/\n    String ELEMENT_PASSWORD = \"password\";\n\n    /** Element that defines a property (name/value pair) */\n    String ELEMENT_PROPERTY = \"property\";\n\n    /** Name attribute of the property element */\n\n    String ATTRIBUTE_NAME = \"name\";\n\n    /** Value attribute of the property element */\n    String ATTRIBUTE_VALUE = \"value\";\n\n    /** Name of the root element's attribute containing the muCommander version that was used to create the credentials file */\n    String ATTRIBUTE_VERSION = \"version\";\n\n    /** Root element's attribute containing the encryption method used for passwords */\n    String ATTRIBUTE_ENCRYPTION = \"encryption\";\n\n    /** Weak password encryption method */\n    String WEAK_ENCRYPTION_METHOD = \"weak\";\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/auth/CredentialsManager.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.auth;\n\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.util.*;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.PlatformManager;\nimport com.mucommander.commons.collections.AlteredVector;\nimport com.mucommander.commons.collections.VectorChangeListener;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.Authenticator;\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.util.Chmod;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.io.backup.BackupOutputStream;\n\n\n/**\n * This class manages {@link CredentialsMapping} instances (login/password pairs associated with a server realm) used to\n * connect to authenticated file systems. It provides methods to find credentials matching a particular location and to\n * read and write credentials to an XML file.\n *\n * <p>\n * Two types of {@link CredentialsMapping} are used:\n * <ul>\n *  <li>persistent credentials: stored in an XML file when the application terminates, and loaded the next time the\n * application is started.</li>\n *  <li>volatile credentials: lost when the application terminates.</li>\n * </ul>\n *\n * @author Maxence Bernard\n */\npublic class CredentialsManager {\n\tprivate static Logger logger;\n\t\n    /** Contains volatile CredentialsMapping instances, lost when the application terminates */\n    private static final List<CredentialsMapping> volatileCredentialMappings = new Vector<>();\n\n    /** Contains persistent CredentialsMapping instances, stored to an XML file when the application\n     * terminates, and loaded the next time the application is started */\n    private static final AlteredVector<CredentialsMapping> persistentCredentialMappings = new AlteredVector<>();\n\n    /** Singleton CredentialsManagerAuthenticator instance */\n    private final static Authenticator AUTHENTICATOR = new CredentialsManagerAuthenticator();\n\n    /** Credentials file location */\n    private static AbstractFile credentialsFile;\n\n    /** Default credentials file name */\n    private static final String DEFAULT_CREDENTIALS_FILE_NAME = \"credentials.xml\";\n\n    /** Tracks changes made to the persistent credentials vector.\n     * We keep a reference to the listener so it doesn't get garbage collected. */\n    private static final VectorChangeListener PERSISTENT_CREDENTIALS_VECTOR_CHANGE_LISTENER;    // Don't remove me!\n\n    /** True when changes were made after the credentials file was last saved */\n    private static boolean saveNeeded;\n\n    /** create a singleton instance, needs to be referenced so that it's not garbage collected (AlteredVector\n      * stores VectorChangeListener as weak references) */\n    private static final CredentialsManager singleton = new CredentialsManager();\n\n    static {\n        // Listen to changes made to the persistent entries vector.\n        // Note: we must keep a reference to the listener, as it would otherwise be garbage collected.\n        persistentCredentialMappings.addVectorChangeListener(PERSISTENT_CREDENTIALS_VECTOR_CHANGE_LISTENER = new VectorChangeListener() {\n            public void elementsAdded(int startIndex, int nbAdded) {\n                saveNeeded = true;\n            }\n\n            public void elementsRemoved(int startIndex, int nbRemoved) {\n                saveNeeded = true;\n            }\n\n            public void elementChanged(int index) {\n                saveNeeded = true;\n            }\n        });\n    }\n\n\n    /**\n     * Returns the path to the credentials file.\n     *\n     * @return the path to the credentials file.\n     * @throws IOException if there was some problem locating the default credentials file.\n     */\n    private static AbstractFile getCredentialsFile() throws IOException {\n        if(credentialsFile == null)\n            return PlatformManager.getPreferencesFolder().getChild(DEFAULT_CREDENTIALS_FILE_NAME);\n        return credentialsFile;\n    }\n\n    /**\n     * Sets the path to the credentials file.\n     *\n     * @param  path                  path to the credentials file\n     * @throws FileNotFoundException if <code>path</code> is not available.\n     */\n    public static void setCredentialsFile(String path) throws FileNotFoundException {\n        AbstractFile file = FileFactory.getFile(path);\n\n        if (file == null) {\n            setCredentialsFile(new File(path));\n        } else {\n            setCredentialsFile(file);\n        }\n    }\n\n    /**\n     * Sets the path to the credentials file.\n     *\n     * @param  file                  path to the credentials file\n     * @throws FileNotFoundException if <code>path</code> is not available.\n     */\n    public static void setCredentialsFile(File file) throws FileNotFoundException {\n        setCredentialsFile(FileFactory.getFile(file.getAbsolutePath()));\n    }\n\n    /**\n     * Sets the path to the credentials file.\n     *\n     * @param  file                  path to the credentials file\n     * @throws FileNotFoundException if <code>path</code> is not available.\n     */\n    public static void setCredentialsFile(AbstractFile file) throws FileNotFoundException {\n        if (file.isBrowsable()) {\n            throw new FileNotFoundException(\"Not a valid file: \" + file);\n        }\n        credentialsFile = file;\n    }\n\n    /**\n     * Tries to load credentials from the credentials file if it exists. Does nothing if it doesn't.\n     *\n     * @throws Exception if an error occurs while loading the credentials file.\n     */\n    public static void loadCredentials() throws Exception {\n        AbstractFile credentialsFile = getCredentialsFile();\n        if (credentialsFile.exists()) {\n            getLogger().debug(\"Found credentials file: \"+credentialsFile.getAbsolutePath());\n            // Parse the credentials file\n            new CredentialsParser().parse(credentialsFile);\n            getLogger().debug(\"Credentials file loaded.\");\n        } else {\n            getLogger().debug(\"No credentials file found at \" + credentialsFile.getAbsolutePath());\n        }\n    }\n\n    /**\n     * Tries to write the credentials file. Unless the 'forceWrite' is set to true, the credentials file will be written\n     * only if changes were made to persistent entries since last write.\n     *\n     * @param forceWrite if false, the credentials file will be written only if changes were made to persistent entries\n     *  since last write, if true the file will always be written.\n     * @throws IOException if an I/O error occurs.\n     */\n    public static void writeCredentials(boolean forceWrite) throws IOException {\n        // Write credentials file only if changes were made to persistent entries since last write, or if write is forced\n        if (!(forceWrite || saveNeeded)) {\n            return;\n        }\n\n        BackupOutputStream out = null;\n        try {\n            credentialsFile = getCredentialsFile();\n            CredentialsWriter.write(out = new BackupOutputStream(credentialsFile));\n            saveNeeded = false;\n        } finally {\n            if (out != null) {\n                // TODO autoclosable\n                try {\n                    out.close();\n                } catch(Exception ignore) {}\n            }\n        }\n\n        // Under UNIX-based systems, change the credentials file's permissions so that the file can't be read by\n        // 'group' and 'other'.\n        boolean fileSecured = !OsFamily.getCurrent().isUnixBased() || Chmod.chmod(credentialsFile, 0600);     // rw-------\n\n        if (fileSecured) {\n            getLogger().debug(\"Credentials file saved successfully.\");\n        } else {\n            getLogger().warn(\"Credentials file could not be chmod!\");\n        }\n    }\n\n\n    /**\n     * Returns an array of {@link CredentialsMapping} that match the location designated by the given {@link FileURL}\n     * and which can be used to authenticate. The location is compared against all known credentials, both volatile and\n     * persistent.\n     *\n     * <p>The returned credentials will match the given URL's scheme and host, but the path may differ so there is\n     * no guarantee that the credentials will successfully authenticate the location.\n     *\n     * <p>The best match (credentials with the 'closest' path to the provided location's path) is returned at the first\n     * position ([0]), if there is at least one matching credentials instance. The returned array can be empty\n     * (zero length) but never null.\n     * \n     * @param location the location to be compared against known credentials instances, both volatile and persistent\n     * @return an array of CredentialsMapping matching the given URL's scheme and host, best match at the first position\n     */\n    public static CredentialsMapping[] getMatchingCredentials(FileURL location) {\n        // Retrieve matches\n        List<CredentialsMapping> matchesV = getMatchingCredentialsV(location);\n\n        // Transform vector into an array\n        CredentialsMapping[] matches = new CredentialsMapping[matchesV.size()];\n        matchesV.toArray(matches);\n\n        return matches;\n    }\n\n    /**\n     * Returns an {@link Authenticator} that authenticates {@link FileURL} instances using the credentials and realm\n     * properties stored by {@link CredentialsManager}.\n     *\n     * @return an {@link Authenticator} that authenticates {@link FileURL} instances using the credentials and realm\n     * properties stored by {@link CredentialsManager}.\n     */\n    public static Authenticator getAuthenticator() {\n        return AUTHENTICATOR;\n    }\n\n    /**\n     * Returns a Vector of CredentialsMapping matching the given URL's scheme and host, best match at the first position.\n     * The returned Vector may be empty but never null.\n     *\n     * @param location the location to be compared against known credentials instances, both volatile and persistent\n     * @return a Vector of CredentialsMapping matching the given URL's scheme and host, best match at the first position\n     */\n    private static List<CredentialsMapping> getMatchingCredentialsV(FileURL location) {\n        List<CredentialsMapping> matchesV = new ArrayList<>();\n\n        findMatches(location, volatileCredentialMappings, matchesV);\n        findMatches(location, persistentCredentialMappings, matchesV);\n\n        // Find the best match and move it at the first position in the vector\n        int bestMatchIndex = getBestMatchIndex(location, matchesV);\n        if (bestMatchIndex >= 0) {\n            matchesV.addFirst(matchesV.remove(bestMatchIndex));\n        }\n\n        return matchesV;\n    }\n\n\n    /**\n     * Adds the given credentials to the list of known credentials.\n     *\n     * <p>Depending on value returned by {@link CredentialsMapping#isPersistent()}, the credentials will either be stored\n     * in the volatile credentials list or the persistent one. Any existing credentials mapped to the same realm\n     * will be replaced by the provided ones.\n     *\n     * <p>This method should be called when new credentials have been entered by the user, after they have been validated\n     * by the application (i.e. access was granted to the location).\n     *\n     * @param credentialsMapping credentials to be added to the list of known credentials\n     */\n    public static void addCredentials(CredentialsMapping credentialsMapping) {\n\n        // Do not add if the credentials are empty\n        if (credentialsMapping.getCredentials().isEmpty()) {\n            return;\n        }\n\n        boolean persist = credentialsMapping.isPersistent();\n\n        getLogger().trace(\"called, realm=\"+ credentialsMapping.getRealm()+\" isPersistent=\"+ credentialsMapping.isPersistent());\n        getLogger().trace(\"before, persistentCredentials=\"+ persistentCredentialMappings);\n        getLogger().trace(\"before, volatileCredentials=\"+ volatileCredentialMappings);\n\n        if (persist) {\n            replaceListElement(persistentCredentialMappings, credentialsMapping);\n            volatileCredentialMappings.remove(credentialsMapping);\n        } else {\n            replaceListElement(volatileCredentialMappings, credentialsMapping);\n            persistentCredentialMappings.removeElement(credentialsMapping);\n        }\n\n        getLogger().trace(\"after, persistentCredentials=\"+ persistentCredentialMappings);\n        getLogger().trace(\"after, volatileCredentials=\"+ volatileCredentialMappings);\n    }\n\n\n    /**\n     * Use the credentials and realm properties of the specified <code>CredentialsMapping</code> to authenticate the\n     * given {@link FileURL}.\n\n     * <p>Any credentials contained by the <code>FileURL</code> will be lost and replaced with the new ones.\n     * If properties with the same key are defined both in the realm and the given FileURL, the ones from the FileURL\n     * will be preserved.\n     *\n     * @param location the FileURL to authenticate\n     * @param credentialsMapping the credentials to use to authenticate the given FileURL\n     */\n    public static void authenticate(FileURL location, CredentialsMapping credentialsMapping) {\n        location.setCredentials(credentialsMapping.getCredentials());\n\n        FileURL realm = credentialsMapping.getRealm();\n        Set<String> propertyKeys = realm.getPropertyNames();\n        for (String key : propertyKeys) {\n            if (location.getProperty(key) == null) {\n                location.setProperty(key, realm.getProperty(key));\n            }\n        }\n    }\n\n\n    /**\n     * Looks for the best set of credentials matching the given location (if any) and use it to authenticate the\n     * URL by calling {@link #authenticate(com.mucommander.commons.file.FileURL, CredentialsMapping)}.\n     * Returns <code>true</code> if a set of credentials was found and used to authenticate the URL, <code>false</code>\n     * otherwise.\n     *\n     * <p>Credentials are first looked for using {@link #getMatchingCredentials(com.mucommander.commons.file.FileURL)}.\n     * If there is no match, guest credentials are retrieved from the URL and used (if any).\n     *\n     * @param location the FileURL to authenticate\n     */\n    private static void authenticateImplicit(FileURL location) {\n        getLogger().trace(\"called, fileURL=\"+ location +\" containsCredentials=\"+ location.containsCredentials());\n\n        CredentialsMapping[] creds = getMatchingCredentials(location);\n        if (creds.length > 0) {\n            authenticate(location, creds[0]);\n        } else {\n            Credentials guestCredentials = location.getGuestCredentials();\n            if(guestCredentials!=null) {\n                authenticate(location, new CredentialsMapping(guestCredentials, location.getRealm(), false));\n            }\n        }\n    }\n\n\n    /**\n     * Looks for credentials matching the specified location in the given credentials Vector and adds them to the given\n     * matches Vector.\n     *\n     * @param location the location to find matching credentials for\n     * @param credentials the Vector containing the CredentialsMapping instances to compare to the given location\n     * @param matches the Vector where matching CredentialsMapping instances will be added\n     */\n    private static void findMatches(FileURL location, List<CredentialsMapping> credentials, List<CredentialsMapping> matches) {\n        for (CredentialsMapping tempCredentialsMapping: credentials) {\n            FileURL tempRealm = tempCredentialsMapping.getRealm();\n            if (location.schemeEquals(tempRealm) && location.portEquals(tempRealm) && location.hostEquals(tempRealm)) {\n                matches.add(tempCredentialsMapping);\n            }\n        }\n\n        getLogger().trace(\"returning matches=\"+matches);\n    }\n\n    /**\n     * Finds are returns the index of the CredentialsMapping instance that best matches the given location\n     * amongst the provided matching CredentialsMapping Vector, or -1 if the matches Vector is empty.\n     *\n     * <p>\n     * The path of each matching CredentialsMapping' location is compared to the provided location's path: the more\n     * folder parts match, the better. If both paths are equal, then the CredentialsMapping index is returned (perfect match).\n     *\n     * @param location the location to be compared against CredentialsMapping matches\n     * @param matches CredentialsMapping instances matching the given location\n     * @return the CredentialsMapping instance that best matches the given location, -1 if the given matches Vector is empty.\n     */\n    private static int getBestMatchIndex(FileURL location, List<CredentialsMapping> matches) {\n        if (matches.isEmpty()) {\n            return -1;\n        }\n\n        // Splits the provided location's path into an array of folder tokens (e.g. \"/home/maxence\" -> [\"home\",\"maxence\"])\n        String path = location.getPath();\n        List<String> pathTokensV = new ArrayList<>();\n        StringTokenizer st = new StringTokenizer(path, \"/\\\\\");\n        while(st.hasMoreTokens()) {\n            pathTokensV.add(st.nextToken());\n        }\n        int nbTokens = pathTokensV.size();\n        String[] pathTokens = new String[nbTokens];\n        pathTokensV.toArray(pathTokens);\n\n        CredentialsMapping tempCredentialsMapping;\n        FileURL tempURL;\n        String tempPath;\n        int nbMatchingToken;\n        int maxTokens = 0;\n        int bestMatchIndex = 0;\n\n        // Compares the location's path against all the one of all CredentialsMapping instances\n        int nbMatches = matches.size();\n        for (int i=0; i<nbMatches; i++) {\n            tempCredentialsMapping = matches.get(i);\n            tempURL = tempCredentialsMapping.getRealm();\n            tempPath = tempURL.getPath();\n\n            // We found a perfect match (same path), it can't get any better than this, return the CredentialsMapping' index\n            if (tempPath.equalsIgnoreCase(path)) {\n                return i;\n            }\n\n            // Split the current CredentialsMapping' location into folder tokens and count the ones that match\n            // the target location's tokens.\n            // A few examples to illustrate:\n            // /home and /home/maxence -> nbMatchingToken = 1\n            // /var/log and /usr -> nbMatchingToken = 0\n            st = new StringTokenizer(tempPath, \"/\\\\\");\n            nbMatchingToken = 0;\n            for (int j=0; j<nbTokens && st.hasMoreTokens(); j++) {\n                if (st.nextToken().equalsIgnoreCase(pathTokens[nbMatchingToken])) {\n                    nbMatchingToken++;\n                } else {\n                    break;\n                }\n            }\n\n            if (nbMatchingToken>maxTokens) {\n                // We just found a better match\n                maxTokens = nbMatchingToken;\n                bestMatchIndex = i;\n            }\n        }\n\n        getLogger().trace(\"returning bestMatchIndex=\"+bestMatchIndex);\n\n        return bestMatchIndex;\n    }\n\n    /**\n     * Replaces any object that's equal to the given one in the <code>Vector</code>, preserving its position. If the\n     * vector contains no such object, it is added to the end of the vector.\n     *\n     * @param list the <code>List</code> to replace/add the object to\n     * @param o the object to replace/add\n     */\n    private static void replaceListElement(List<CredentialsMapping> list, CredentialsMapping o) {\n        int index = list.indexOf(o);\n        if (index < 0) {\n            list.add(o);\n        } else {\n            list.set(index, o);\n        }\n    }\n\n    /**\n     * Returns the list of known volatile {@link CredentialsMapping}, stored in a Vector.\n     * <p>\n     * The returned Vector instance is the one actually used by CredentialsManager, so use it with caution.\n     *\n     * @return the list of known volatile {@link CredentialsMapping}.\n     */\n    public static List<CredentialsMapping> getVolatileCredentialMappings() {\n        return volatileCredentialMappings;\n    }\n\n\n    /**\n     * Returns the list of known persistent {@link CredentialsMapping}, stored in an {@link AlteredVector}.\n     * <p>\n     * Any changes made to the Vector will be detected and will yield to writing the credentials file when\n     * {@link #writeCredentials(boolean)} is called with false.\n     *\n     * @return the list of known persistent {@link CredentialsMapping}.\n     */\n    public static AlteredVector<CredentialsMapping> getPersistentCredentialMappings() {\n        return persistentCredentialMappings;\n    }\n\n\n    ///////////////////\n    // Inner classes //\n    ///////////////////\n\n    /**\n     * An {@link Authenticator} implementation that uses {@link CredentialsManager#authenticateImplicit(FileURL)} to\n     * authenticate the specified {@link FileURL} instances.\n     *\n     * @author Maxence Bernard\n     */\n    private static class CredentialsManagerAuthenticator implements Authenticator {\n        public void authenticate(FileURL fileURL) {\n            CredentialsManager.authenticateImplicit(fileURL);\n        }\n    }\n\n    private static Logger getLogger() {\n        if (logger == null) {\n            logger = LoggerFactory.getLogger(CredentialsManager.class);\n        }\n        return logger;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/auth/CredentialsMapping.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.auth;\n\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileURL;\n\n/**\n * CredentialsMapping associates a {@link Credentials} instance with a 'realm' , that is the location to a server.\n * It also adds the notion of persistence, allowing to specify whether the credentials should be saved to disk when the\n * application quits and restored next time the application starts.\n *\n * @see CredentialsManager \n * @author Maxence Bernard\n */\npublic final class CredentialsMapping {\n\n    /** User credentials */\n    private final Credentials credentials;\n\n    /** The location credentials are associated with */\n    private final FileURL realm;\n\n    /** Should these credentials be saved to disk ? */\n    private final boolean isPersistent;\n\n    \n    /**\n     * Creates a new CredentialsMapping instance that associates the specified credentials with the given location.\n     *\n     * @param credentials user credentials\n     * @param realm the location to associate the credentials with\n     * @param isPersistent if true, indicates to CredentialsManager that the credentials should be saved when the\n     * application terminates.\n     */\n    public CredentialsMapping(Credentials credentials, FileURL realm, boolean isPersistent) {\n        this.credentials = credentials;\n        this.isPersistent = isPersistent;\n        this.realm = realm.getRealm();\n    }\n\n    /**\n     * Returns the credentials.\n     *\n     * @return the credentials\n     */\n    public Credentials getCredentials() {\n        return credentials;\n    }\n\n    /**\n     * Returns the location associated with the credentials.\n     * <p>\n     * Note: the returned {@link FileURL} does not contain any credentials.\n     *\n     * @return the location associated with the credentials.\n     */\n    public FileURL getRealm() {\n        return realm;\n    }\n\n    /**\n     * Returns <code>true</code> if these credentials should be saved when the application terminates.\n     *\n     * @return <code>true</code> if these credentials should be saved when the application terminates, <code>false</code> otherwise.\n     */\n    public boolean isPersistent() {\n        return isPersistent;\n    }\n\n\n    /**\n     * Returns <code>true</code> if the given Object is a {@link com.mucommander.auth.CredentialsMapping} instance\n     * whose credentials and realm are equals to those of this instance.\n     *\n     * @param o the Object to test for equality\n     * @return true if both CredentialsMapping instances are equal\n     */\n    @Override\n    public boolean equals(Object o) {\n        if (!(o instanceof CredentialsMapping cm)) { // Note: CredentialsMapping is final, no need to test classes\n            return false;\n        }\n        return cm.credentials.equals(this.credentials, false) && cm.realm.equals(this.realm, false, true);\n    }\n\n    @Override\n    public String toString() {\n        return credentials.toString()+\" \"+realm.toString(false);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/auth/CredentialsParser.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.auth;\r\n\r\nimport com.mucommander.bookmark.XORCipher;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.Credentials;\r\nimport com.mucommander.commons.file.FileURL;\r\nimport com.mucommander.io.backup.BackupInputStream;\r\nimport java.io.IOException;\r\nimport java.io.InputStream;\r\nimport java.net.MalformedURLException;\r\nimport java.util.Hashtable;\r\nimport java.util.Map;\r\nimport javax.xml.parsers.SAXParserFactory;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.xml.sax.Attributes;\r\nimport org.xml.sax.helpers.DefaultHandler;\r\n\r\n/**\r\n * This class takes care of parsing the credentials XML file and adding parsed {@link CredentialsMapping} instances\r\n * to {@link CredentialsManager}.\r\n *\r\n * @author Maxence Bernard\r\n * @see CredentialsWriter\r\n */\r\nclass CredentialsParser extends DefaultHandler implements CredentialsConstants {\r\n\tprivate static Logger logger;\r\n\t\r\n    // Variables used for XML parsing\r\n    private FileURL url;\r\n    private Map<String, String> urlProperties;\r\n    private String login;\r\n    private String password;\r\n    private StringBuilder characters;\r\n\r\n    /** muCommander version that was used to write the credentials file */\r\n    private String version;\r\n\r\n    /** Contains the encryption method used to encrypt/decrypt passwords */\r\n    private String encryptionMethod;\r\n\r\n\r\n    /**\r\n     * Creates a new CredentialsParser.\r\n     */\r\n    public CredentialsParser() {\r\n    }\r\n\r\n\r\n    /**\r\n     * Parses the given XML credentials file. Should only be called by CredentialsManager.\r\n     */\r\n    void parse(AbstractFile file) throws Exception {\r\n        characters = new StringBuilder();\r\n        try (InputStream in  = new BackupInputStream(file)) {\r\n            SAXParserFactory.newInstance().newSAXParser().parse(in, this);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Returns the muCommander version that was used to write the credentials file, <code>null</code> if it is unknown.\r\n     * <p>\r\n     * Note: the version attribute was introduced in muCommander 0.8.4.\r\n     *\r\n     * @return the muCommander version that was used to write the credentials file, <code>null</code> if it is unknown.\r\n     */\r\n    public String getVersion() {\r\n        return version;\r\n    }\r\n\r\n\r\n    ///////////////////////////////////\r\n    // ContentHandler implementation //\r\n    ///////////////////////////////////\r\n\r\n    @Override\r\n    public void startElement(String uri, String localName, String qName, Attributes attributes) {\r\n        characters.setLength(0);\r\n\r\n        switch (qName) {\r\n            case ELEMENT_CREDENTIALS:\r\n            // Reset parsing variables\r\n            url = null;\r\n            urlProperties = null;\r\n            login = null;\r\n            password = null;\r\n                break;\r\n        // Property element (properties will be set when credentials element ends\r\n            case ELEMENT_PROPERTY:\r\n                if (urlProperties == null)\r\n                    urlProperties = new Hashtable<>();\r\n                urlProperties.put(attributes.getValue(ATTRIBUTE_NAME), attributes.getValue(ATTRIBUTE_VALUE));\r\n                break;\r\n        // Root element, the 'encryption' attribute specifies which encoding was used to encrypt passwords\r\n            case ELEMENT_ROOT:\r\n            encryptionMethod = attributes.getValue(\"encryption\");\r\n            version = attributes.getValue(ATTRIBUTE_VERSION);\r\n                break;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void endElement(String uri, String localName, String qName) {\r\n        switch (qName) {\r\n            case ELEMENT_CREDENTIALS:\r\n                if (url == null || login == null || password == null) {\r\n                    getLogger().info(\"Missing value, credentials ignored: url=\" + url + \" login=\" + login);\r\n                return;\r\n            }\r\n\r\n            // Copy properties into FileURL instance (if any)\r\n                if (urlProperties != null) {\r\n                    for (String key : urlProperties.keySet())\r\n                        url.setProperty(key, urlProperties.get(key));\r\n            }\r\n\r\n            // Decrypt password\r\n                try {\r\n                    password = XORCipher.decryptXORBase64(password);\r\n                } catch (IOException e) {\r\n                    getLogger().info(\"Password could not be decrypted: \" + password + \", credentials will be ignored\");\r\n                return;\r\n            }\r\n\r\n            // Add credentials to persistent credentials list\r\n            CredentialsManager.getPersistentCredentialMappings().add(new CredentialsMapping(new Credentials(login, password), url, true));\r\n                break;\r\n            case ELEMENT_URL:\r\n                try {\r\n                    url = FileURL.getFileURL(characters.toString().trim());\r\n                } catch (MalformedURLException e) {\r\n                    getLogger().info(\"Malformed URL: \" + characters + \", location will be ignored\");\r\n        }\r\n                break;\r\n            case ELEMENT_LOGIN:\r\n            login = characters.toString().trim();\r\n                break;\r\n            case ELEMENT_PASSWORD:\r\n            password = characters.toString().trim();\r\n                break;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void characters(char[] ch, int offset, int length) {\r\n        characters.append(ch, offset, length);\r\n    }\r\n\r\n\r\n    private static Logger getLogger() {\r\n        if (logger == null) {\r\n            logger = LoggerFactory.getLogger(CredentialsParser.class);\r\n        }\r\n        return logger;\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/auth/CredentialsWriter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.auth;\n\nimport com.mucommander.RuntimeConstants;\nimport com.mucommander.bookmark.XORCipher;\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.utils.xml.XmlAttributes;\nimport com.mucommander.utils.xml.XmlWriter;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.Iterator;\nimport java.util.Set;\n\n/**\n * This class provides a method to write persistent credentials contained by {@link CredentialsManager} to an XML file.\n *\n * @author Maxence Bernard\n * @see CredentialsParser\n */\npublic class CredentialsWriter implements CredentialsConstants {\n\n    /**\n     * Writes the credentials XML file in the user's preferences folder.\n     * This method should only be called by {@link CredentialsManager}.\n     */\n    static void write(OutputStream stream) throws IOException {\n\n        XmlWriter out  = new XmlWriter(stream);\n\n        // Root element, add the encryption method used\n        XmlAttributes attributes = new XmlAttributes();\n        attributes.add(ATTRIBUTE_ENCRYPTION, WEAK_ENCRYPTION_METHOD);\n        // Version the file\n        attributes.add(ATTRIBUTE_VERSION, RuntimeConstants.VERSION);\n        out.startElement(ELEMENT_ROOT, attributes);\n        out.println();\n\n        Iterator<CredentialsMapping> iterator = CredentialsManager.getPersistentCredentialMappings().iterator();\n        CredentialsMapping credentialsMapping;\n        FileURL realm;\n        Set<String> propertyKeys;\n\n        while(iterator.hasNext()) {\n            credentialsMapping = iterator.next();\n            realm = credentialsMapping.getRealm();\n\n            // Start credentials element\n            out.startElement(ELEMENT_CREDENTIALS);\n            out.println();\n\n            // Write URL\n            out.startElement(ELEMENT_URL);\n            out.writeCData(realm.toString(false));\n            out.endElement(ELEMENT_URL);\n\n            Credentials credentials = credentialsMapping.getCredentials();\n\n            // Write login\n            out.startElement(ELEMENT_LOGIN);\n            out.writeCData(credentials.getLogin());\n            out.endElement(ELEMENT_LOGIN);\n\n            // Write password (XOR encrypted)\n            out.startElement(ELEMENT_PASSWORD);\n            out.writeCData(XORCipher.encryptXORBase64(credentials.getPassword()));\n            out.endElement(ELEMENT_PASSWORD);\n\n            // Write properties, each property is stored in a separate 'property' element\n            propertyKeys = realm.getPropertyNames();\n            for (String name : propertyKeys) {\n                attributes = new XmlAttributes();\n                attributes.add(ATTRIBUTE_NAME, name);\n                attributes.add(ATTRIBUTE_VALUE, realm.getProperty(name));\n                out.startElement(ELEMENT_PROPERTY, attributes);\n                out.endElement(ELEMENT_PROPERTY);\n            }\n\n            // End credentials element\n            out.endElement(ELEMENT_CREDENTIALS);\n        }\n\n        // End root element\n        out.endElement(ELEMENT_ROOT);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/auth/package.html",
    "content": "<body>\n  API for dealing with authentication and user credentials.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/bonjour/BonjourDirectory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.bonjour;\n\nimport java.io.IOException;\nimport java.net.Inet6Address;\nimport java.net.MalformedURLException;\nimport java.util.List;\nimport java.util.Vector;\n\nimport javax.jmdns.JmDNS;\nimport javax.jmdns.ServiceEvent;\nimport javax.jmdns.ServiceInfo;\nimport javax.jmdns.ServiceListener;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.FileProtocols;\nimport com.mucommander.commons.file.FileURL;\n\n/**\n * Collects and maintains a list of available Bonjour/Zeroconf services using the JmDNS library.\n * Newly discovered services are added to the list and removed as they become unavailable.\n *\n * <p>Use {@link #getServices()} to get a list of currently available Bonjour services.\n *\n * @author Maxence Bernard\n * @see BonjourMenu\n */\npublic class BonjourDirectory implements ServiceListener {\n\tprivate static Logger logger;\n\t\n    /** Singleton instance held to prevent garbage collection and also used for synchronization */\n    private final static BonjourDirectory instance = new BonjourDirectory();\n    /** Does all the hard work */\n    private static JmDNS jmDNS;\n    /** List of discovered and currently active Bonjour services */\n    private static final List<BonjourService> services = new Vector<>();\n\n    /** Known Bonjour/Zeroconf service types and their corresponding protocol */\n    private final static String[][] KNOWN_SERVICE_TYPES = {\n        {\"_http._tcp.local.\", FileProtocols.HTTP},\n        {\"_ftp._tcp.local.\", FileProtocols.FTP},\n        {\"_ssh._tcp.local.\", FileProtocols.SFTP},\n        {\"_smb._tcp.local.\", FileProtocols.SMB}\n    };\n\n    /** Number of milliseconds to wait for service info resolution before giving up */\n    private final static int SERVICE_RESOLUTION_TIMEOUT = 10000;\n\n\n    /**\n     * No-arg contructor made private so that only one instance can exist.\n     */\n    private BonjourDirectory() {\n    }\n\n\n    /**\n     * Enables/disables Bonjour services discovery. If currently active and false is specified, current services\n     * will be lost and {@link #getServices()} will return an empty array. If currently inactive and true is specified,\n     * services discovery will be immediately started but it may take a while (a few seconds at least) to\n     * collect services.\n     * @param enabled whether Bonjour services discovery should be enabled.\n     */\n    public static void setActive(boolean enabled) {\n        if (enabled && jmDNS==null) {\n            // Start JmDNS\n            try {\n                jmDNS = JmDNS.create();\n\n                // Listens to service events for known service types\n                for (String[] KNOWN_SERVICE_TYPE : KNOWN_SERVICE_TYPES)\n                    jmDNS.addServiceListener(KNOWN_SERVICE_TYPE[0], instance);\n            } catch(IOException e) {\n            \tgetLogger().warn(\"Could not instantiate jmDNS, Bonjour not enabled\", e);\n            }\n        } else if(!enabled && jmDNS != null) {\n            // Shutdown JmDNS\n            try {\n\t\t\t\tjmDNS.close();\n                if (1==0) throw new IOException();  // FIXME looks like that different versions of Bonjour library have different Bonjour.close() method  - with declared IOException and without it\n\t\t\t} catch (IOException e) {\n\t\t\t\tthrow new RuntimeException(e);\n\t\t\t}\n            services.clear();\n            jmDNS = null;\n        }\n    }\n\n    /**\n     * Returns <code>true</code> if Bonjour services discovery is currently running.\n     * @return <code>true</code> if Bonjour services discovery is currently running, <code>false</code> otherwise.\n     */\n    public static boolean isActive() {\n        return jmDNS != null;\n    }\n\n\n    /**\n     * Returns all currently available Bonjour services. The returned array may be empty but never null.\n     * If BonjourDirectory is not currently active ({@link #isActive()}, an empty array will be returned.\n     * @return all currently available Bonjour services\n     */\n    public static BonjourService[] getServices() {\n        BonjourService[] servicesArray = new BonjourService[services.size()];\n        services.toArray(servicesArray);\n        return servicesArray;\n    }\n\n\n    /**\n     * Wraps a Bonjour service into a {@link BonjourService} object and returns it. Returns <code>null</code> if\n     * the service type doesn't correspond to any of the supported protocols, or if the service URL is malformed.\n     *\n     * @param serviceInfo the ServiceInfo to wrap into a BonjourService\n     * @return a BonjourService instance corresponding to the given ServiceInfo\n     */\n    private static BonjourService createBonjourService(ServiceInfo serviceInfo) {\n        try {\n            String type = serviceInfo.getType();\n            // Looks for the file protocol corresponding to the service type\n            for (String[] KNOWN_SERVICE_TYPE : KNOWN_SERVICE_TYPES) {\n                if (KNOWN_SERVICE_TYPE[0].equals(type)) {\n                    return new BonjourService(serviceInfo.getName(), FileURL.getFileURL(serviceInfo.getURL(KNOWN_SERVICE_TYPE[1])), serviceInfo.getQualifiedName());\n                }\n            }\n        } catch (MalformedURLException e) {\n            // Null will be returned\n        }\n\n        return null;\n    }\n\n\n    ////////////////////////////////////\n    // ServiceListener implementation //\n    ////////////////////////////////////\n\n    public void serviceAdded(final ServiceEvent serviceEvent) {\n        getLogger().trace(\"name=\"+serviceEvent.getName()+\" type=\"+serviceEvent.getType());\n        \n        // Ignore if Bonjour has been disabled\n        if (!isActive()) {\n            return;\n        }\n        // Resolve service info in a separate thread, serviceResolved() will be called once service info has been resolved.\n        // Not spawning a thread often leads to service info loss (serviceResolved() not called).\n        new Thread(() -> jmDNS.requestServiceInfo(serviceEvent.getType(), serviceEvent.getName(), SERVICE_RESOLUTION_TIMEOUT)).start();\n    }\n\n    public void serviceResolved(ServiceEvent serviceEvent) {\n        getLogger().trace(\"name=\"+serviceEvent.getName()+\" type=\"+serviceEvent.getType()+\" info=\"+serviceEvent.getInfo());\n\n        // Ignore if Bonjour has been disabled\n        if (!isActive())\n            return;\n\n        // Creates a new BonjourService corresponding to the new service and add it to the list of current Bonjour services\n        ServiceInfo serviceInfo = serviceEvent.getInfo();\n        if(serviceInfo!=null) {\n            if(serviceInfo.getInetAddress() instanceof Inet6Address) {\n                // IPv6 addresses not supported at this time + they seem not to be correctly handled by ServiceInfo\n                getLogger().debug(\"ignoring IPv6 service\");\n                return;\n            }\n\n            BonjourService bs = createBonjourService(serviceInfo);\n            // Synchronized to properly handle duplicate calls\n            synchronized(instance) {\n                if(bs!=null && !services.contains(bs)) {\n                    getLogger().debug(\"BonjourService \"+bs+\" added\");\n                    services.add(bs);\n                }\n            }\n        }\n    }\n\n    public void serviceRemoved(ServiceEvent serviceEvent) {\n        getLogger().trace(\"name=\"+serviceEvent.getName()+\" type=\"+serviceEvent.getType());\n\n        // Ignore if Bonjour has been disabled\n        if(!isActive())\n            return;\n\n        // Looks for an existing BonjourService instance corresponding to the service being removed and removes it from\n        // the list of current Bonjour services.\n        // ServiceInfo should be available in JmDNS's cache.\n        ServiceInfo serviceInfo = jmDNS.getServiceInfo(serviceEvent.getType(), serviceEvent.getName()); \n        if (serviceInfo != null) {\n            if(serviceInfo.getInetAddress() instanceof Inet6Address) {\n                // IPv6 addresses not supported at this time + they seem not to be correctly handled by ServiceInfo\n                getLogger().debug(\"ignoring IPv6 service\");\n                return;\n            }\n\n            BonjourService bs = createBonjourService(serviceInfo);\n            // Synchronized to properly handle duplicate calls\n            synchronized(instance) {\n                // Note: BonjourService#equals() uses the service's fully qualified name as the discriminator.\n                if (bs != null && services.contains(bs)) {\n                    getLogger().debug(\"BonjourService \"+bs+\" removed\");\n                    services.remove(bs);\n                }\n            }\n        }\n    }\n\n    private static Logger getLogger() {\n        if (logger == null) {\n            logger = LoggerFactory.getLogger(BonjourDirectory.class);\n        }\n        return logger;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/bonjour/BonjourMenu.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.bonjour;\n\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.helper.MnemonicHelper;\nimport com.mucommander.ui.icon.IconManager;\n\nimport javax.swing.*;\nimport javax.swing.event.MenuEvent;\nimport javax.swing.event.MenuListener;\n\n/**\n * An abstract JMenu that contains an item for each Bonjour service available\n * (as returned {@link BonjourDirectory#getServices()} displaying the Bonjour service's name). When an item is clicked,\n * the action returned by {@link #getMenuItemAction(BonjourService)} is returned.\n *\n * <p>Note: the items list is refreshed each time the menu is selected. In other words, a new instance of BonjourMenu\n * does not have to be created in order to see new Bonjour services.\n *\n * @author Maxence Bernard\n */\npublic abstract class BonjourMenu extends JMenu implements MenuListener {\n\n    /**\n     * Creates a new instance of <code>BonjourMenu</code>.\n     */\n    public BonjourMenu() {\n        super(Translator.get(\"bonjour.bonjour_services\"));\n\n        setIcon(IconManager.getIcon(IconManager.IconSet.FILE, \"bonjour.png\"));\n\n        // Menu items will be added when menu gets selected\n        addMenuListener(this);\n    }\n\n\n    /**\n     * Returns the action to perform for the given {@link BonjourService}. This method is called for every\n     * BonjourService available when this menu is selected.\n     *\n     * @param bs the BonjourService\n     * @return the action to perform for the given BonjourService\n     */\n    public abstract TcAction getMenuItemAction(BonjourService bs);\n\n\n    /////////////////////////////////\n    // MenuListener implementation //\n    /////////////////////////////////\n    @Override\n    public void menuSelected(MenuEvent menuEvent) {\n        // Remove previous menu items (if any)\n        removeAll();\n\n        if (!BonjourDirectory.isActive()) {\n            // Inform that Bonjour support has been disabled\n            add(new JMenuItem(Translator.get(\"bonjour.bonjour_disabled\"))).setEnabled(false);\n            return;\n        }\n        BonjourService[] services = BonjourDirectory.getServices();\n\n        if (services.length > 0) {\n            // Add a menu item for each Bonjour service.\n            // When clicked, the corresponding URL will be opened in the active table.\n            MnemonicHelper mnemonicHelper = new MnemonicHelper();\n\n            for (BonjourService service : services) {\n                JMenuItem menuItem = new JMenuItem(getMenuItemAction(service));\n                menuItem.setMnemonic(mnemonicHelper.getMnemonic(menuItem.getText()));\n\n                add(menuItem);\n            }\n        } else {\n            // Inform that no service have been discovered\n            add(new JMenuItem(Translator.get(\"bonjour.no_service_discovered\"))).setEnabled(false);\n        }\n    }\n\n    @Override\n    public void menuDeselected(MenuEvent menuEvent) {\n    }\n\n    @Override\n    public void menuCanceled(MenuEvent menuEvent) {\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/bonjour/BonjourService.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.bonjour;\n\nimport com.mucommander.commons.file.FileURL;\nimport lombok.Getter;\n\n/**\n * A simple container for a Bonjour service described by a name and URL.\n *\n * @author Maxence Bernard\n */\npublic class BonjourService {\n\n    /** the unqualified name of the service, e.g. 'foobar'\n     * -- GETTER --\n     *  Returns the unqualified name of this service, e.g. 'foobar'.\n     */\n    @Getter\n    private final String name;\n\n    /** the url pointing to the service's location */\n    private final FileURL url;\n\n    /** the fully qualified name of the service, e.g. 'foobar._http._tcp.local'\n     * -- GETTER --\n     *  Returns the fully qualified name of this service, e.g. 'foobar._http._tcp.local'\n     */\n    @Getter\n    private final String fullyQualifiedName;\n\n\n    /**\n     * Creates a new BonjourService instance using the given name and URL.\n     *\n     * @param name the unqualified name of the service, e.g. 'foobar'\n     * @param url the url pointing to the service's location\n     * @param fullyQualifiedName the fully qualified name of the service, e.g. 'foobar._http._tcp.local'\n     */\n    public BonjourService(String name, FileURL url, String fullyQualifiedName) {\n        this.name = name;\n        this.url = url;\n        this.fullyQualifiedName = fullyQualifiedName;\n    }\n\n\n    /**\n     * Returns the name appended with the URL's scheme.\n     *\n     * @return the name appended with the URL's scheme.\n     */\n    public String getNameWithProtocol() {\n        return name+\" [\"+url.getScheme().toUpperCase()+\"]\";\n    }\n\n    /**\n     * Returns the location of this service.\n     *\n     * @return the location of this service.\n     */\n    public FileURL getURL() {\n        return url;\n    }\n\n\n    /**\n     * Returns <code>true</code> if the given Object is a BonjourService instance with the same fully qualified name.\n     */\n    @Override\n    public boolean equals(Object o) {\n        return o instanceof BonjourService && fullyQualifiedName.equals(((BonjourService) o).fullyQualifiedName);\n    }\n\n\n    /**\n     * Returns a String representation of this BonjourService in the form name / url.\n     */\n    @Override\n    public String toString() {\n        return name + \" / \" + url.toString(false);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/bonjour/package.html",
    "content": "<body>\n  Wrapper for the JmDNS library, provides Bonjour/Zeroconf support.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/bookmark/Bookmark.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.bookmark;\n\n/**\n * Represents a bookmark.\n * <p>Bookmarks are simple name/location pairs:\n * <ul>\n *   <li>The name is a String describing the bookmark.</li>\n *   <li>\n *     The location should designate a path or file URL. The designated location may not exist or may not even be\n *     a valid path or URL, so it is up to classes that call {@link #getLocation()} to deal with it appropriately.\n *   </li>\n * </ul>\n *\n * @author Maxence Bernard\n */\npublic class Bookmark implements Cloneable {\n    private String name;\n    private String location;\n    private String parent;\n\n\n    /**\n     * Creates a new Bookmark using the given name and location.\n     *\n     * @param name name given to this bookmark\n     * @param location location (path or URL) this bookmark points to\n     */\n    public Bookmark(String name, String location, String parent) {\n        // Use setters to checks for null values\n        setName(name);\n        setLocation(location);\n        setParent(parent);\n    }\n\n\n    /**\n     * Returns this bookmark's name.\n     * @return this bookmark's name.\n     * @see    #setName(String)\n     */\n    public String getName() {\n        return name;\n    }\n\n\n    /**\n     * Changes this bookmark's name to the given one and fires an event to registered {@link BookmarkListener}\n     * instances.\n     * @param newName bookmark's new name.\n     * @see           #getName()\n     */\n    public void setName(String newName) {\n        if (newName == null) {\n            newName = \"\";\n        }\n\n        if (!newName.equals(this.name)) {\n            this.name = newName;\n            // Notify registered listeners of the change\n            BookmarkManager.fireBookmarksChanged();\n        }\n    }\n\n\n    /**\n     * Returns this bookmark's location which should normally designate a path or file URL, but which isn't\n     * necessarily valid nor exists.\n     * @return this bookmark's location.\n     * @see    #setLocation(String)\n     */\n    public String getLocation() {\n        return location;\n    }\n\n\n    /**\n     * Changes this bookmark's location to the given one and fires an event to registered {@link BookmarkListener}\n     * instances.\n     * @param newLocation bookmark's new location.\n     * @see               #getLocation()\n     */\n    public void setLocation(String newLocation) {\n        // Replace null values by empty strings\n        if (newLocation == null) {\n            newLocation = \"\";\n        }\n\n        if (!newLocation.equals(this.location)) {\n            this.location = newLocation;\n\n            // Notify registered listeners of the change\n            BookmarkManager.fireBookmarksChanged();\n        }\n    }\n\n    public String getParent() {\n        return parent;\n    }\n\n    public void setParent(String parent) {\n        if (parent != null && parent.trim().isEmpty()) {\n            parent = null;\n        }\n        this.parent = parent;\n    }\n\n\n    /**\n     * Returns a clone of this bookmark.\n     */\n    @Override\n    public Object clone() throws CloneNotSupportedException {\n        return super.clone();\n    }\n\n\n    /**\n     * Returns the bookmark's name.\n     */\n    public String toString() {\n        if (parent != null) {\n            return parent + \" -> \" + name;\n        }\n        return name;\n    }\n\n    public boolean equals(Object object) {\n        if (!(object instanceof Bookmark bookmark)) {\n            return false;\n        }\n        return bookmark.getName().equals(name);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/bookmark/BookmarkBuilder.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.bookmark;\n\n/**\n * Implementations of this interface are used to build bookmark sets.\n * @author Nicolas Rinaudo\n */\npublic interface BookmarkBuilder {\n    /**\n     * Notifies the builder that the bookmark list is starting.\n     * @throws BookmarkException if an error occurs.\n     */\n    void startBookmarks() throws BookmarkException;\n\n    /**\n     * Notifies the builder of a new bookmark in the list.\n     * @param  name              bookmark's name.\n     * @param  location          bookmark's location.\n     * @param  parent            bookmark's parent name.\n     * @throws BookmarkException if an error occurs.\n     */\n    void addBookmark(String name, String location, String parent) throws BookmarkException;\n\n    /**\n     * Notifies the builder that the bookmark list is finished.\n     * @throws BookmarkException if an error occurs.\n     */\n    void endBookmarks() throws BookmarkException;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/bookmark/BookmarkConstants.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.bookmark;\n\n/**\n * Defines bookmarks XML file structure.\n * @author Nicolas Rinaudo\n */\ninterface BookmarkConstants {\n\n    /** Root element */\n    String ELEMENT_ROOT      = \"bookmarks\";\n\n    /** Name of the root element's attribute containing the muCommander version that was used to create the bookmarks file */\n    String ATTRIBUTE_VERSION = \"version\";\n\n    /** Element describing one of the bookmarks in the list */\n    String ELEMENT_BOOKMARK  = \"bookmark\";\n\n    /** Bookmark name */\n    String ELEMENT_NAME      = \"name\";\n\n    /** Bookmark location */\n    String ELEMENT_LOCATION  = \"location\";\n\n    /** Bookmark parent name */\n    String ELEMENT_PARENT  = \"parent\";\n\n//    /** Bookmark URL: was used up until 0.8 beta3 nightly builds and replaced by 'location' element. Kept\n//     * for upward compatibility */\n//    String ELEMENT_URL       = \"url\";\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/bookmark/BookmarkException.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.bookmark;\n\n/**\n * Exception thrown when bookmark related errors occur.\n * @author Nicolas Rinaudo\n */\npublic class BookmarkException extends Exception {\n    /**\n     * Creates a new exception with the specified message.\n     * @param message exception's message.\n     */\n    public BookmarkException(String message) {super(message);}\n\n    /**\n     * Creates a new exception wrapping the specified error.\n     * @param cause root cause of the new exception.\n     */\n    public BookmarkException(Throwable cause) {super(cause);}\n\n    /**\n     * Creates a new exception with the specified message and cause.\n     * @param message exception's message.\n     * @param cause   root cause of the new exception.\n     */\n    public BookmarkException(String message, Throwable cause) {super(message, cause);}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/bookmark/BookmarkListener.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.bookmark;\n\n\n/**\n * Interface to be implemented by classes that wish to be notified when changes are made to the bookmarks list.\n * Those classes need to be registered to receive those events, this can be done by calling\n * {@link BookmarkManager#addBookmarkListener(BookmarkListener)}.\n *\n * @author Maxence Bernard\n */\npublic interface BookmarkListener {\n\t\n    /**\n     * This method is invoked when a bookmark has been added, removed or modified.\n     */\n    void bookmarksChanged();\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/bookmark/BookmarkManager.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.bookmark;\r\n\r\nimport com.mucommander.PlatformManager;\r\nimport com.mucommander.bookmark.file.BookmarkProtocolProvider;\r\nimport com.mucommander.commons.collections.AlteredVector;\r\nimport com.mucommander.commons.collections.VectorChangeListener;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileFactory;\r\nimport com.mucommander.commons.file.FileURL;\r\nimport com.mucommander.io.backup.BackupInputStream;\r\nimport com.mucommander.io.backup.BackupOutputStream;\r\n\r\nimport java.io.*;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.WeakHashMap;\r\n\r\n/**\r\n * This class manages the bookmark list and its parsing and storage as an XML file.\r\n * <p>\r\n * It monitors any changes made to the bookmarks and when changes are made, fires change events to registered\r\n * listeners.\r\n *\r\n * @author Maxence Bernard, Nicolas Rinaudo\r\n */\r\npublic class BookmarkManager implements VectorChangeListener {\r\n    /** Whether we're currently loading the bookmarks or not. */\r\n    private static boolean isLoading = false;\r\n\r\n    /** Bookmarks file location */\r\n    private static AbstractFile bookmarksFile;\r\n\r\n    /** Default bookmarks file name */\r\n    private static final String DEFAULT_BOOKMARKS_FILE_NAME = \"bookmarks.xml\";\r\n\r\n    /** Bookmark instances */\r\n    private static final AlteredVector<Bookmark> bookmarks = new AlteredVector<>();\r\n\r\n    /** Contains all registered bookmark listeners, stored as weak references */\r\n    private static final WeakHashMap<BookmarkListener, ?> listeners = new WeakHashMap<>();\r\n\r\n    /** Specifies whether bookmark events should be fired when a change to the bookmarks is detected */\r\n    private static boolean fireEvents = true;\r\n\r\n    /** True when changes were made after the bookmarks file was last saved */\r\n    private static boolean saveNeeded;\r\n\r\n    /** Last bookmark change timestamp */\r\n    private static long lastBookmarkChangeTime;\r\n\r\n    /** Last event pause timestamp */\r\n    private static long lastEventPauseTime;\r\n\r\n    /** create a singleton instance, needs to be referenced so that it's not garbage collected (AlteredVector\r\n     * stores VectorChangeListener as weak references) */\r\n    private static final BookmarkManager singleton = new BookmarkManager();\r\n\r\n    /** Value of bookmark's name that make the bookmark treated as a separator */\r\n    public static final String BOOKMARKS_SEPARATOR = \"-\";\r\n\r\n\r\n    static {\r\n        // Listen to changes made to the bookmarks vector\r\n        bookmarks.addVectorChangeListener(singleton);\r\n    }\r\n\r\n    /**\r\n     * Prevents instanciation of <code>BookmarkManager</code>.\r\n     */\r\n    private BookmarkManager() {}\r\n\r\n\r\n\r\n    // - Bookmark building -----------------------------------------------------\r\n    // -------------------------------------------------------------------------\r\n    /**\r\n     * Passes messages about all known bookmarks to the specified builder.\r\n     * @param  builder           where to send bookmark building messages.\r\n     * @throws BookmarkException if an error occurs.\r\n     */\r\n    private static synchronized void buildBookmarks(BookmarkBuilder builder) throws BookmarkException {\r\n        builder.startBookmarks();\r\n        for (Bookmark bookmark : bookmarks) {\r\n            builder.addBookmark(bookmark.getName(), bookmark.getLocation(), bookmark.getParent());\r\n        }\r\n        builder.endBookmarks();\r\n    }\r\n\r\n\r\n\r\n    // - Bookmark file access --------------------------------------------------\r\n    // -------------------------------------------------------------------------\r\n    /**\r\n     * Returns the path to the bookmark file.\r\n     * <p>\r\n     * If it hasn't been changed through a call to {@link #setBookmarksFile(String)},\r\n     * this method will return the default, system dependant bookmarks file.\r\n     *\r\n     * @return             the path to the bookmark file.\r\n     * @see    #setBookmarksFile(String)\r\n     * @throws IOException if there was a problem locating the default bookmarks file.\r\n     */\r\n    private static synchronized AbstractFile getBookmarksFile() throws IOException {\r\n        if (bookmarksFile == null) {\r\n            return PlatformManager.getPreferencesFolder().getChild(DEFAULT_BOOKMARKS_FILE_NAME);\r\n        }\r\n        return bookmarksFile;\r\n    }\r\n\r\n    /**\r\n     * Sets the path to the bookmarks file.\r\n     * <p>\r\n     * This is a convenience method and is strictly equivalent to calling <code>setBookmarksFile(FileFactory.getFile(file))</code>.\r\n     *\r\n     * @param     path                  path to the bookmarks file\r\n     * @exception FileNotFoundException if <code>path</code> is not accessible.\r\n     * @see       #getBookmarksFile()\r\n     */\r\n    public static void setBookmarksFile(String path) throws FileNotFoundException {\r\n        AbstractFile file = FileFactory.getFile(path);\r\n\r\n        if (file == null) {\r\n            setBookmarksFile(new File(path));\r\n        } else {\r\n            setBookmarksFile(file);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Sets the path to the bookmarks file.\r\n     * <p>\r\n     * This is a convenience method and is strictly equivalent to calling <code>setBookmarksFile(FileFactory.getFile(file.getAbsolutePath()))</code>.\r\n     *\r\n     * @param     file                  path to the bookmarks file\r\n     * @exception FileNotFoundException if <code>path</code> is not accessible.\r\n     * @see       #getBookmarksFile()\r\n     */\r\n    private static void setBookmarksFile(File file) throws FileNotFoundException {\r\n        setBookmarksFile(FileFactory.getFile(file.getAbsolutePath()));\r\n    }\r\n\r\n    /**\r\n     * Sets the path to the bookmarks file.\r\n     * @param     file                  path to the bookmarks file\r\n     * @exception FileNotFoundException if <code>path</code> is not accessible.\r\n     * @see       #getBookmarksFile()\r\n     */\r\n\r\n    private static synchronized void setBookmarksFile(AbstractFile file) throws FileNotFoundException {\r\n        if (file.isBrowsable()) {\r\n            throw new FileNotFoundException(\"Not a valid file: \" + file.getAbsolutePath());\r\n        }\r\n        bookmarksFile = file;\r\n    }\r\n\r\n\r\n\r\n    // - Bookmarks loading -----------------------------------------------------\r\n    // -------------------------------------------------------------------------\r\n    /**\r\n     * Loads all available bookmarks.\r\n     * @throws Exception if an error occurs.\r\n     */\r\n    public static synchronized void loadBookmarks() throws Exception {\r\n        // Parse the bookmarks file\r\n        isLoading = true;\r\n        try (InputStream in = new BackupInputStream(getBookmarksFile())) {\r\n            readBookmarks(in, new Loader());\r\n            isLoading = false;\r\n        } catch (IOException e) {\r\n            isLoading = false;\r\n            throw e;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Reads bookmarks from the specified <code>InputStream</code>.\r\n     * @param  in        where to read bookmarks from.\r\n     * @throws Exception if an error occurs.\r\n     */\r\n    public static void readBookmarks(InputStream in) throws Exception {\r\n        readBookmarks(in, new Loader());\r\n    }\r\n\r\n    /**\r\n     * Reads bookmarks from the specified <code>InputStream</code> and passes messages to the specified {@link BookmarkBuilder}.\r\n     * @param  in        where to read bookmarks from.\r\n     * @param  builder   where to send builing messages to.\r\n     * @throws Exception if an error occurs.\r\n     */\r\n    public static synchronized void readBookmarks(InputStream in, BookmarkBuilder builder) throws Exception {\r\n        new BookmarkParser().parse(in, builder);\r\n    }\r\n\r\n\r\n\r\n    // - Bookmarks writing -----------------------------------------------------\r\n    // -------------------------------------------------------------------------\r\n    /**\r\n     * Returns a {@link BookmarkBuilder} that will write all building messages as XML to the specified output stream.\r\n     * @param out where to write the bookmarks' XML content.\r\n     * @return             a {@link BookmarkBuilder} that will write all building messages as XML to the specified output stream.\r\n     * @throws IOException if an IO related error occurs.\r\n     */\r\n    public static BookmarkBuilder getBookmarkWriter(OutputStream out) throws IOException {\r\n        return new BookmarkWriter(out);\r\n    }\r\n\r\n    /**\r\n     * Writes all known bookmarks to the bookmark {@link #getBookmarksFile() file}.\r\n     * @param forceWrite if false, the bookmarks file will be written only if changes were made to bookmarks since\r\n     * last write, if true the file will always be written\r\n     * @throws IOException if an I/O error occurs.\r\n     * @throws BookmarkException if an error occurs.\r\n     */\r\n    public static synchronized void writeBookmarks(boolean forceWrite) throws IOException, BookmarkException {\r\n        // Write bookmarks file only if changes were made to the bookmarks since last write, or if write is forced.\r\n        if (!forceWrite && !saveNeeded) {\r\n            return;\r\n        }\r\n        try (OutputStream out  = new BackupOutputStream(getBookmarksFile())) {\r\n            buildBookmarks(getBookmarkWriter(out));\r\n            saveNeeded = false;\r\n        }\r\n    }\r\n\r\n\r\n\r\n    // - Bookmarks access ------------------------------------------------------\r\n    // -------------------------------------------------------------------------\r\n    /**\r\n     * Returns an {@link AlteredVector} that contains all bookmarks.\r\n     * \r\n     * <p>Important: the returned Vector should not directly be used to\r\n     * add or remove bookmarks, doing so won't trigger any event to registered bookmark listeners.\r\n     * However, it is safe to modify bookmarks individually, events will be properly fired.\r\n     * @return an {@link AlteredVector} that contains all bookmarks.\r\n     */\r\n    public static synchronized AlteredVector<Bookmark> getBookmarks() {\r\n        return bookmarks;\r\n    }\r\n\r\n    /**\r\n     * Deletes the specified bookmark.\r\n     * @param bookmark bookmark to delete from the list.\r\n     */\r\n    public static synchronized void removeBookmark(Bookmark bookmark) {\r\n        bookmarks.remove(bookmark);\r\n    }\r\n\r\n    /**\r\n     * Convenience method that looks for a Bookmark with the given name (case ignored) and returns it,\r\n     * or null if none was found. If several bookmarks have the given name, the first one is returned.\r\n     *\r\n     * @param name the bookmark's name\r\n     * @return a Bookmark instance with the given name, null if none was found\r\n     */\r\n    public static synchronized Bookmark getBookmark(String name) {\r\n        for (Bookmark b : bookmarks) {\r\n            if (b.getName().equalsIgnoreCase(name)) {\r\n                return b;\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public static List<Bookmark> getParentBookmarks() {\r\n        List<Bookmark> result = new ArrayList<>();\r\n        for (Bookmark b : bookmarks) {\r\n            if (b.getLocation().isEmpty()) {\r\n                result.add(b);\r\n            }\r\n        }\r\n        return result;\r\n    }\r\n\r\n    /**\r\n     * Convenience method that adds a bookmark to the bookmark list.\r\n     *\r\n     * @param b the Bookmark instance to add to the bookmark list.\r\n     */\r\n    public static synchronized void addBookmark(Bookmark b) {\r\n        bookmarks.add(b);\r\n    }\r\n\r\n\r\n    /**\r\n     * Check if a given URL represents a bookmark.\r\n     *\r\n     * @param fileURL the URL to examine\r\n     * @return true if the given URL represents a bookmark, false otherwise\r\n     */\r\n    public static boolean isBookmark(FileURL fileURL) {\r\n        return fileURL != null && BookmarkProtocolProvider.BOOKMARK.equals(fileURL.getScheme());\r\n    }\r\n\r\n    /**\r\n     * Check if a given file represents a bookmark.\r\n     *\r\n     * @param file the URL to examine\r\n     * @return true if the given file represents a bookmark, false otherwise\r\n     */\r\n    public static boolean isBookmark(AbstractFile file) {\r\n        return file != null && BookmarkProtocolProvider.BOOKMARK.equals(file.getURL().getScheme());\r\n    }\r\n\r\n\r\n    // - Listeners -------------------------------------------------------------\r\n    // -------------------------------------------------------------------------\r\n    /**\r\n     * Adds the specified BookmarkListener to the list of registered listeners.\r\n     *\r\n     * <p>Listeners are stored as weak references so {@link #removeBookmarkListener(BookmarkListener)}\r\n     * doesn't need to be called for listeners to be garbage collected when they're not used anymore.\r\n     *\r\n     * @param listener the BookmarkListener to add to the list of registered listeners.\r\n     * @see   #removeBookmarkListener(BookmarkListener)\r\n     */\r\n    public static void addBookmarkListener(BookmarkListener listener) {\r\n        synchronized(listeners) {\r\n            listeners.put(listener, null);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Removes the specified BookmarkListener from the list of registered listeners.\r\n     *\r\n     * @param listener the BookmarkListener to remove from the list of registered listeners.\r\n     * @see   #addBookmarkListener(BookmarkListener)\r\n     */\r\n    private static void removeBookmarkListener(BookmarkListener listener) {\r\n        synchronized(listeners) {\r\n            listeners.remove(listener);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Notifies all the registered bookmark listeners of a bookmark change. This can be :\r\n     * <ul>\r\n     * <li>A new bookmark which has just been added\r\n     * <li>An existing bookmark which has been modified\r\n     * <li>An existing bookmark which has been removed\r\n     * </ul>\r\n     */\r\n    static void fireBookmarksChanged() {\r\n        // Bookmarks file will need to be saved\r\n        if (!isLoading) {\r\n            saveNeeded = true;\r\n        }\r\n\r\n        lastBookmarkChangeTime = System.currentTimeMillis();\r\n\r\n        // Do not fire event if events are currently disabled\r\n        if (!fireEvents) {\r\n            return;\r\n        }\r\n\r\n        synchronized(listeners) {\r\n            // Iterate on all listeners\r\n            for (BookmarkListener listener : listeners.keySet()) {\r\n                listener.bookmarksChanged();\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Specifies whether bookmark events should be fired when a change in the bookmarks is detected. This allows\r\n     * to temporarily suspend events firing when a lot of them are made, for example when editing the bookmarks list.\r\n     *\r\n     * <p>If true is specified, any subsequent calls to fireBookmarksChanged will be ignored, until this method is\r\n     * called again with false.\r\n     * @param b whether to fire events.\r\n     */\r\n    public static synchronized void setFireEvents(boolean b) {\r\n        if (b) {\r\n            // Fire a bookmarks changed event if bookmarks were modified during event pause\r\n            if (!fireEvents && lastBookmarkChangeTime >= lastEventPauseTime) {\r\n                fireEvents = true;\r\n                fireBookmarksChanged();\r\n            }\r\n        } else {\r\n            // Remember pause start time\r\n            if (fireEvents) {\r\n                fireEvents = false;\r\n                lastEventPauseTime = System.currentTimeMillis();\r\n            }\r\n        }\r\n    }\r\n\r\n    /////////////////////////////////////////\r\n    // VectorChangeListener implementation //\r\n    /////////////////////////////////////////\r\n\r\n    public void elementsAdded(int startIndex, int nbAdded) {\r\n        fireBookmarksChanged();\r\n    }\r\n\r\n    public void elementsRemoved(int startIndex, int nbRemoved) {\r\n        fireBookmarksChanged();\r\n    }\r\n\r\n    public void elementChanged(int index) {\r\n        fireBookmarksChanged();\r\n    }\r\n\r\n\r\n\r\n    // - Bookmark loading ------------------------------------------------------\r\n    // -------------------------------------------------------------------------\r\n    private static class Loader implements BookmarkBuilder {\r\n        public void startBookmarks() {\r\n\r\n        }\r\n\r\n        public void endBookmarks() {\r\n\r\n        }\r\n\r\n        public void addBookmark(String name, String location, String parent) {\r\n            BookmarkManager.addBookmark(new Bookmark(name, location, parent));\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/bookmark/BookmarkParser.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.bookmark;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.xml.sax.Attributes;\nimport org.xml.sax.SAXException;\nimport org.xml.sax.helpers.DefaultHandler;\n\nimport javax.xml.parsers.SAXParserFactory;\nimport java.io.InputStream;\n\n\n/**\n * This class takes care of parsing the bookmarks XML file and adding parsed {@link Bookmark} instances to {@link BookmarkManager}.\n *\n * @author Maxence Bernard\n */\nclass BookmarkParser extends DefaultHandler implements BookmarkConstants {\n\tprivate static Logger logger;\n\t\n    /** Variable used for XML parsing */\n    private String bookmarkName;\n    /** Variable used for XML parsing */\n    private String bookmarkLocation;\n    /** Variable used for XML parsing */\n    private String bookmarkParent;\n    /** Variable used for XML parsing */\n    private StringBuilder characters;\n    /** Receives bookmarks events. */\n    private BookmarkBuilder builder;\n    /** muCommander version that was used to write the bookmarks file */\n    private String version;\n\n\n    /**\n     * Creates a new BookmarkParser instance.\n     */\n    BookmarkParser() {}\n\n    /**\n     * Parses the given XML bookmarks file. Should only be called by BookmarkManager.\n     */\n    void parse(InputStream in, BookmarkBuilder builder) throws Exception {\n        this.builder = builder;\n        characters = new StringBuilder();\n        SAXParserFactory.newInstance().newSAXParser().parse(in, this);\n    }\n\n    /**\n     * Returns the muCommander version that was used to write the bookmarks file, <code>null</code> if it is unknown.\n     * <p>\n     * Note: the version attribute was introduced in muCommander 0.8.4.\n     *\n     * @return the muCommander version that was used to write the bookmarks file, <code>null</code> if it is unknown.\n     */\n    public String getVersion() {\n        return version;\n    }\n\n\n    /* ------------------------ */\n    /*  ContentHandler methods  */\n    /* ------------------------ */\n\n    @Override\n    public void startDocument() throws SAXException {\n        try {\n            builder.startBookmarks();\n        } catch (BookmarkException e) {\n            throw new SAXException(e);\n        }\n    }\n\n    @Override\n    public void endDocument() throws SAXException {\n        try {\n            builder.endBookmarks();\n        } catch (BookmarkException e) {\n            throw new SAXException(e);\n        }\n    }\n\n    /**\n     * Method called when some PCDATA has been found in an XML node.\n     */\n    @Override\n    public void characters(char[] ch, int start, int length) {\n        characters.append(ch, start, length);\n    }\n\n    /**\n     * Notifies the parser that a new XML node has been found.\n     */\n    @Override\n    public void startElement(String uri, String localName, String qName, Attributes attributes) {\n        characters.setLength(0);\n\n        if (qName.equals(ELEMENT_ROOT)) {\n            version = attributes.getValue(ATTRIBUTE_VERSION);\n        } else if (qName.equals(ELEMENT_BOOKMARK)) {\n            // Reset parsing variables\n            bookmarkName = null;\n            bookmarkLocation = null;\n            bookmarkParent = null;\n        }\n    }\n\n    /**\n     * Notifies the parser that an XML node has been closed.\n     */\n    @Override\n    public void endElement(String uri, String localName, String qName) throws SAXException {\n        switch (qName) {\n            case ELEMENT_BOOKMARK:\n                addBookmark();\n                break;\n            case ELEMENT_NAME:\n                bookmarkName = characters.toString().trim();\n                break;\n            case ELEMENT_LOCATION:\n                bookmarkLocation = characters.toString().trim();\n                break;\n            case ELEMENT_PARENT:\n                bookmarkParent = characters.toString().trim();\n                break;\n            // Note: url element has been deprecated in 0.8 beta3 but is still checked against for upward compatibility.\n//            case ELEMENT_URL:\n//                // Until early 0.8 beta3 nightly builds, credentials were stored directly in the bookmark's url.\n//                // Now bookmark locations are free of credentials, these are stored in a dedicated credentials file where\n//                // the password is encrypted.\n//                try {\n//                    FileURL url = FileURL.getFileURL(characters.toString().trim());\n//                    Credentials credentials = url.getCredentials();\n//\n//                    // If the URL contains credentials, import them into CredentialsManager and remove credentials\n//                    // from the bookmark's location\n//                    if (credentials != null) {\n//                        CredentialsManager.addCredentials(new CredentialsMapping(credentials, url, true));\n//                        bookmarkLocation = url.toString(false);\n//                    } else {\n//                        bookmarkLocation = characters.toString().trim();\n//                    }\n//                } catch (MalformedURLException e) {\n//                    bookmarkLocation = characters.toString().trim();\n//                }\n//                break;\n        }\n    }\n\n    private void addBookmark() throws SAXException {\n        if (bookmarkName == null || bookmarkLocation == null) {\n            getLogger().info(\"Missing value, bookmark ignored: name=\" + bookmarkName + \" location=\" + bookmarkLocation);\n            return;\n        }\n\n        try {\n            builder.addBookmark(bookmarkName, bookmarkLocation, bookmarkParent);\n        } catch (BookmarkException e) {\n            throw new SAXException(e);\n        }\n    }\n\n    private static Logger getLogger() {\n        if (logger == null) {\n            logger = LoggerFactory.getLogger(BookmarkParser.class);\n        }\n        return logger;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/bookmark/BookmarkWriter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.bookmark;\n\nimport com.mucommander.RuntimeConstants;\nimport com.mucommander.utils.xml.XmlAttributes;\nimport com.mucommander.utils.xml.XmlWriter;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n\n/**\n * This class provides a method to write bookmarks to an XML file.\n *\n * @author Maxence Bernard, Nicolas Rinaudo\n */\nclass BookmarkWriter implements BookmarkConstants, BookmarkBuilder {\n    private final XmlWriter out;\n\n    BookmarkWriter(OutputStream stream) throws IOException {\n        out = new XmlWriter(stream);\n    }\n\n    public void startBookmarks() throws BookmarkException {\n        // Root element\n        try {\n            // Version the file.\n            // Note: the version attribute was introduced in muCommander 0.8.4.\n            XmlAttributes attributes = new XmlAttributes();\n            attributes.add(\"version\", RuntimeConstants.VERSION);\n\n            out.startElement(ELEMENT_ROOT, attributes);\n            out.println();\n        } catch(IOException e) {\n            throw new BookmarkException(e);\n        }\n    }\n\n    public void endBookmarks() throws BookmarkException {\n        try {\n            out.endElement(ELEMENT_ROOT);\n        } catch(IOException e) {\n            throw new BookmarkException(e);\n        }\n    }\n\n    public void addBookmark(String name, String location, String parent) throws BookmarkException {\n        try {\n            out.startElement(ELEMENT_BOOKMARK);\n            out.println();\n\n            writeElement(ELEMENT_NAME, name);\n            writeElement(ELEMENT_LOCATION, location);\n            writeElement(ELEMENT_PARENT, parent);\n\n            out.endElement(ELEMENT_BOOKMARK);\n        } catch(IOException e) {\n            throw new BookmarkException(e);\n        }\n    }\n\n\n    private void writeElement(String name, String value) throws IOException {\n        if (value != null) {\n            out.startElement(name);\n            out.writeCData(value);\n            out.endElement(name);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/bookmark/XORCipher.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.bookmark;\n\nimport com.mucommander.commons.io.base64.Base64Decoder;\nimport com.mucommander.commons.io.base64.Base64Encoder;\n\nimport java.io.IOException;\n\n/**\n * This class provides simple XOR symmetrical encryption using a static hard-coded key, coupled with Base64\n * encoding so that encrypted strings only use alphanumeric characters and thus can be embedded in text formats such\n * as XML.\n *\n * <p><b>Disclaimer</b>: this obviously is weak encryption at most, the key used being static and public, and XOR\n * encryption being easy to crack. This doesn't aim or pretend to be anything more than a way to scramble text\n * without requiring a master password in the application.\n *\n * @author Maxence Bernard\n */\npublic class XORCipher {\n\n\n    /** Long enough key (256 bytes) to avoid having too much redundancy in small text strings. */\n    private final static int[] NOT_SO_PRIVATE_KEY = {\n        161, 220, 156, 76, 177, 174, 56, 37, 98, 93, 224, 19, 160, 95, 69, 140,\n        91, 138, 33, 114, 248, 57, 179, 17, 54, 172, 249, 58, 26, 181, 167, 231,\n        241, 185, 218, 174, 37, 102, 100, 26, 16, 214, 119, 29, 118, 151, 135, 175,\n        245, 247, 160, 188, 77, 173, 109, 255, 73, 44, 186, 211, 117, 236, 204, 58,\n        246, 210, 128, 33, 234, 218, 82, 188, 78, 229, 180, 108, 247, 200, 3, 142,\n        206, 45, 165, 111, 96, 72, 76, 81, 238, 186, 240, 167, 185, 152, 68, 228,\n        87, 142, 145, 7, 74, 12, 106, 94, 15, 218, 155, 71, 87, 136, 58, 40,\n        246, 94, 7, 89, 29, 0, 78, 204, 70, 220, 240, 127, 59, 184, 109, 106\n    };\n\n\n    /**\n     * Cyphers the given byte array using XOR symmetrical encryption with a static hard-coded key.\n     *\n     * @param b the byte array to encrypt/decrypt\n     * @return the encrypted/decrypted byte array\n     */\n    private static byte[] xor(byte[] b) {\n        int len = b.length;\n        int keyLen = NOT_SO_PRIVATE_KEY.length;\n\n        byte[] result = new byte[len];\n        for (int i = 0; i < len; i++) {\n            result[i] = (byte) (b[i] ^ NOT_SO_PRIVATE_KEY[i % keyLen]);\n        }\n\n        return result;\n    }\n\n    /**\n     * Encrypts the given String using XOR cipher followed by Base64 encoding. The returned String will only contain\n     * alphanumeric characters.\n     *\n     * @param s the String to encrypt\n     * @return an XOR-Base64 encrypted String\n     */\n    public static String encryptXORBase64(String s) {\n        // TODO:\n        // Important: String.getBytes() returns bytes in the platform's default encoding, which might vary across\n        // platforms. This may potentially cause problems when decrypting a string on a different platform from the one\n        // which served to encrypt it.\n        // It is however too late to change as it could prevent existing encrypted strings (credentials file) from being\n        // loaded after the application is updated.\n        return Base64Encoder.encode(xor(s.getBytes()));\n    }\n\n\n    /**\n     * Decrypts the given XOR-Base64 encrypted String and throws an IOException if the given String is not properly\n     * Base64-encoded.\n     *\n     * @param s a XOR-Base64 encrypted String\n     * @return the decrypted String\n     * @throws IOException if the given String is not properly Base64-encoded\n     */\n    public static String decryptXORBase64(String s) throws IOException {\n        // TODO:\n        // Important: new String() creates a string using the platform's default encoding, which might vary across\n        // platforms. This may potentially cause problems when decrypting a string on a different platform from the one\n        // which served to encrypt it.\n        // It is however too late to change as it could prevent existing encrypted strings (credentials file) from being\n        // loaded after the application is updated.\n        return new String(xor(Base64Decoder.decodeAsBytes(s)));\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/bookmark/file/BookmarkFile.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.bookmark.file;\n\nimport com.mucommander.bookmark.Bookmark;\nimport com.mucommander.bookmark.BookmarkBuilder;\nimport com.mucommander.bookmark.BookmarkManager;\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.io.FileTransferException;\nimport com.mucommander.commons.io.RandomAccessInputStream;\nimport com.mucommander.commons.io.RandomAccessOutputStream;\n\nimport java.io.*;\nimport java.nio.charset.StandardCharsets;\n\n/**\n * Represents a file in the <code>bookmark://</code> file system.\n * @author Nicolas Rinaudo\n */\npublic class BookmarkFile extends ProtocolFile {\n    // - Instance fields -------------------------------------------------------\n    // -------------------------------------------------------------------------\n    /** Bookmark wrapped by this abstract file. */\n    private final Bookmark bookmark;\n    /** Underlying abstract file. */\n    private AbstractFile file;\n\n    /** Permissions for all bookmark files: rw- (600 octal). Only the 'user' permissions bits are supported. */\n    final static FilePermissions PERMISSIONS = new SimpleFilePermissions(384, 448);\n\n    /**\n     * Creates a new bookmark file wrapping the specified bookmark.\n     * @param  bookmark    bookmark to wrap.\n     * @throws IOException if the specified bookmark's URL cannot be resolved.\n     */\n    BookmarkFile(Bookmark bookmark) throws IOException {\n        super(FileURL.getFileURL(BookmarkProtocolProvider.BOOKMARK + \"://\" + java.net.URLEncoder.encode(bookmark.getName(), StandardCharsets.UTF_8)));\n        this.bookmark = bookmark;\n    }\n\n\n    /**\n     * Returns the <code>AbstractFile</code> this instance wraps.\n     * <p>\n     * Some methods need to have access to the underlying file. This, however, requires\n     * resolving the path which can be time consuming. Using this method ensures that the\n     * path is only resolved if necessary, and at most once.\n     *\n     * @return the <code>AbstractFile</code> this instance wraps.\n     */\n    private synchronized AbstractFile getUnderlyingFile() {\n        // Resolves the file if necessary.\n        if (file == null) {\n            file = FileFactory.getFile(bookmark.getLocation());\n        }\n\n        return file;\n    }\n\n    /**\n     * Returns the underlying bookmark.\n     * @return the underlying bookmark.\n     */\n    public Bookmark getBookmark() {\n        return bookmark;\n    }\n\n\n\n    // - AbstractFile methods --------------------------------------------------\n    // -------------------------------------------------------------------------\n    /**\n     * Returns the underlying bookmark's name.\n     * @return the underlying bookmark's name.\n     */\n    @Override\n    public String getName() {\n        return bookmark.getName();\n    }\n\n    /**\n     * Returns the wrapped file's descendants.\n     * @return             the wrapped file's descendants.\n     * @throws IOException                       if an I/O error occurs.\n     * @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,\n     * or is not implemented.\n     */\n    @Override\n    public AbstractFile[] ls() throws IOException, UnsupportedFileOperationException {\n        return getUnderlyingFile().ls();\n    }\n\n    /**\n     * Returns the wrapped file's parent.\n     * @return             the wrapped file's parent.\n     * @see                #setParent(AbstractFile)\n     */\n    @Override\n    public AbstractFile getParent() {\n        try {\n            return new BookmarkRoot();\n        } catch(IOException e) {\n            return null;\n        }\n    }\n\n    /**\n     * Returns the result of the wrapped file's <code>getFreeSpace()</code> methods.\n     * @return the result of the wrapped file's <code>getFreeSpace()</code> methods.\n     * @throws IOException if an I/O error occurred\n     * @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,\n     * or is not implemented.\n     */\n    @Override\n    public long getFreeSpace() throws IOException, UnsupportedFileOperationException {\n        return getUnderlyingFile().getFreeSpace();\n    }\n\n    /**\n     * Returns the result of the wrapped file's <code>getTotalSpace()</code> methods.\n     * @return the result of the wrapped file's <code>getTotalSpace()</code> methods.\n     * @throws IOException if an I/O error occurred\n     * @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,\n     * or is not implemented.\n     */\n    @Override\n    public long getTotalSpace() throws IOException, UnsupportedFileOperationException {\n        return getUnderlyingFile().getTotalSpace();\n    }\n\n    /**\n     * Returns <code>false</code>.\n     * @return <code>false</code>.\n     */\n    @Override\n    public boolean isDirectory() {\n        return true;\n    }\n\n    /**\n     * Sets the wrapped file's parent.\n     * @param parent object to use as the wrapped file's parent.\n     * @see          AbstractFile#getParent()\n     */\n    @Override\n    public void setParent(AbstractFile parent) {\n        getUnderlyingFile().setParent(parent);}\n\n    /**\n     * Returns <code>true</code> if the specified bookmark exists.\n     * <p>\n     * A bookmark is said to exist if and only if it is known to the {@link com.mucommander.bookmark.BookmarkManager}.\n     *\n     * @return <code>true</code> if the specified bookmark exists, <code>false</code> otherwise.\n     */\n    @Override\n    public boolean exists() {\n        return BookmarkManager.getBookmark(bookmark.getName()) != null;\n    }\n\n    @Override\n    public void mkfile() {\n        BookmarkManager.addBookmark(bookmark);\n    }\n\n    public boolean equals(Object o) {\n        // Makes sure we're working with an abstract file.\n        if (!(o instanceof AbstractFile)) {\n            return false;\n        }\n\n        // Retrieves the actual file instance.\n        // We might have received a Proxied or Cached file, so we need to make sure\n        // we 'unwrap' that before comparing.\n        AbstractFile file = ((AbstractFile)o).getAncestor();\n\n        // We only know how to compare one bookmark file to the other.\n        if (file instanceof BookmarkFile) {\n            return bookmark.equals(((BookmarkFile)file).getBookmark());\n        }\n        return false;\n    }\n\n    @Override\n    public String getCanonicalPath() {return bookmark.getLocation();}\n\n\n\n    // - Bookmark renaming -----------------------------------------------------\n    // -------------------------------------------------------------------------\n\n    /**\n     * Attempts to rename the bookmark to the specified destination.\n     * The operation will only be carried out if the specified destination is a <code>BookmarkFile</code> or has an\n     * ancestor that is.\n     *\n     * @param  destination where to move the bookmark to.\n     * @throws IOException if the operation could not be carried out.\n     */\n    @Override\n    public void renameTo(AbstractFile destination) throws IOException {\n        checkRenamePrerequisites(destination, true, true);\n\n        destination = destination.getTopAncestor();\n\n        // Makes sure we're working with a bookmark.\n        if (!(destination instanceof BookmarkFile)) {\n            throw new IOException();\n        }\n\n        // Creates the new bookmark and checks for conflicts.\n        Bookmark newBookmark = new Bookmark(destination.getName(), bookmark.getLocation(), bookmark.getParent());\n        Bookmark oldBookmark = BookmarkManager.getBookmark(newBookmark.getName());\n        if (oldBookmark != null) {\n            BookmarkManager.removeBookmark(oldBookmark);\n        }\n\n        // Adds the new bookmark and deletes its 'old' version.\n        BookmarkManager.addBookmark(newBookmark);\n        BookmarkManager.removeBookmark(bookmark);\n    }\n\n    // TODO: bookmark deleting is currently disabled as a quick fix for #329    \n//    /**\n//     * Deletes the bookmark.\n//     * <p>\n//     * Deleting a bookmark means unregistering it from the {@link com.mucommander.bookmark.BookmarkManager}.\n//     *\n//     */\n//    @Override\n//    public void delete() {\n//        BookmarkManager.removeBookmark(bookmark);\n//    }\n\n    @Override\n    @UnsupportedFileOperation\n    public void delete() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.DELETE);\n    }\n\n\n\n    // - Bookmark duplication --------------------------------------------------\n    // -------------------------------------------------------------------------\n\n    /**\n     * Tries to copy the bookmark to the specified destination.\n     * <p>\n     * If the specified destination is an instance of <code>BookmarkFile</code>,\n     * this will duplicate the bookmark. Otherwise, this method will fail.\n     *\n     * @param  destination           where to copy the bookmark to.\n     * @throws FileTransferException if the specified destination is not an instance of <code>BookmarkFile</code>.\n     */\n    @Override\n    public void copyRemotelyTo(AbstractFile destination) throws IOException {\n        // Makes sure we're working with a bookmark.\n        destination = destination.getTopAncestor();\n        if (!(destination instanceof BookmarkFile)) {\n            throw new IOException();\n        }\n        // Copies this bookmark to the specified destination.\n        BookmarkManager.addBookmark(new Bookmark(destination.getName(), bookmark.getLocation(), bookmark.getParent()));\n    }\n\n\n\n    // - Permissions -----------------------------------------------------------\n    // -------------------------------------------------------------------------\n    /**\n     * Returns the same permissions for all boookmark files: rw- (600 octal).\n     * Only the 'user' permissions bits are supported.\n\n     * @return            this file's permissions.\n     * @see               #changePermission(int,int,boolean)\n     */\n    @Override\n    public FilePermissions getPermissions() {\n        return PERMISSIONS;\n    }\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException} when called: bookmarks always have all permissions,\n     * this is not changeable.\n     *\n     * @param  access     ignored.\n     * @param  permission ignored.\n     * @param  enabled    ignored.\n     * @see               #getPermissions()\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void changePermission(int access, int permission, boolean enabled) throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_PERMISSION);\n    }\n\n\n    // - Import / export -------------------------------------------------------\n    // -------------------------------------------------------------------------\n    @Override\n    public InputStream getInputStream() throws IOException {\n        ByteArrayOutputStream stream = new ByteArrayOutputStream();\n        BookmarkBuilder builder = BookmarkManager.getBookmarkWriter(stream);\n        try {\n            builder.startBookmarks();\n            builder.addBookmark(bookmark.getName(), bookmark.getLocation(), bookmark.getParent());\n            builder.endBookmarks();\n        } catch (Throwable e) {\n            // If an exception occurred, we have to look for its root cause.\n            Throwable e2;\n\n            // Looks for the cause.\n            while ((e2 = e.getCause()) != null) {\n                e = e2;\n            }\n\n            // If the cause is an IOException, thow it.\n            if (e instanceof IOException) {\n                throw (IOException)e;\n            }\n\n            // Otherwise, throw the exception as an IOException with a the underlying cause's message.\n            throw new IOException(e.getMessage());\n        }\n\n        return new ByteArrayInputStream(stream.toByteArray());\n    }\n\n    @Override\n    public OutputStream getOutputStream() throws IOException {return new BookmarkOutputStream();}\n\n\n    // - Unused methods --------------------------------------------------------\n    // -------------------------------------------------------------------------\n    // The following methods are not used by BookmarkFile. They will throw an exception or\n    // return an 'operation non supported' / default value.\n\n    @Override\n    @UnsupportedFileOperation\n    public void mkdir() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.CREATE_DIRECTORY);}\n    @Override\n    public long getLastModifiedDate() {return 0;}\n    @Override\n    public PermissionBits getChangeablePermissions() {return PermissionBits.EMPTY_PERMISSION_BITS;}\n    @Override\n    @UnsupportedFileOperation\n    public void setLastModifiedDate(long lastModified) throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.CHANGE_DATE);}\n    @Override\n    public long getSize() {return -1;}\n    @Override\n    @UnsupportedFileOperation\n    public RandomAccessInputStream getRandomAccessInputStream() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.RANDOM_READ_FILE);}\n    @Override\n    @UnsupportedFileOperation\n    public OutputStream getAppendOutputStream() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.APPEND_FILE);}\n    @Override\n    @UnsupportedFileOperation\n    public RandomAccessOutputStream getRandomAccessOutputStream() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.RANDOM_WRITE_FILE);}\n    @Override\n    public Object getUnderlyingFileObject() {return null;}\n    @Override\n    public boolean isSymlink() {return false;}\n    @Override\n    public boolean isSystem() {return false;}\n    @Override\n    public String getOwner() {return null;}\n    @Override\n    public boolean canGetOwner() {return false;}\n    @Override\n    public String getGroup() {return null;}\n    @Override\n    public boolean canGetGroup() {return false;}\n    @Override\n    @UnsupportedFileOperation\n    public short getReplication() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_REPLICATION);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public long getBlocksize() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_BLOCKSIZE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public void changeReplication(short replication) throws IOException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_REPLICATION);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/bookmark/file/BookmarkOutputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.bookmark.file;\n\nimport com.mucommander.bookmark.Bookmark;\nimport com.mucommander.bookmark.BookmarkBuilder;\nimport com.mucommander.bookmark.BookmarkManager;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\n\n/**\n * Class used to provide an output stream on a bookmark.\n * @author Nicolas Rinaudo\n */\nclass BookmarkOutputStream extends ByteArrayOutputStream implements BookmarkBuilder {\n    // - Stream methods --------------------------------------------------------\n    // -------------------------------------------------------------------------\n    /**\n     * Parses the content that has been written.\n     * @throws IOException if an error occurs.\n     */\n    @Override\n    public void close() throws IOException {\n        super.close();\n\n        try {\n            BookmarkManager.readBookmarks(new ByteArrayInputStream(toByteArray()), this);\n        } catch(IOException e) {\n            throw e;\n        } catch(Exception e) {\n            throw new IOException(e.getMessage());\n        }\n    }\n\n\n\n    // - Bookmark builder methods ----------------------------------------------\n    // -------------------------------------------------------------------------\n    /**\n     * Ignored.\n     */\n    public void startBookmarks() {}\n\n    /**\n     * Ignored.\n     */\n    public void endBookmarks() {}\n\n    /**\n     * Adds the specified bookmark to the bookmark manager\n     * <p>\n     * Note that this method will remove any previous bookmark of the same name.\n     *\n     * @param name     name of the new bookmark.\n     * @param location location of the new bookmark.\n     */\n    public void addBookmark(String name, String location, String parent) {\n        // Creates the new bookmark and checks for conflicts.\n        Bookmark newBookmark = new Bookmark(name, location, parent);\n        // Old bookmark of the same name, if any.\n        Bookmark oldBookmark = BookmarkManager.getBookmark(name);\n        if (oldBookmark != null) {\n            BookmarkManager.removeBookmark(oldBookmark);\n        }\n        // Adds the new bookmark.\n        BookmarkManager.addBookmark(newBookmark);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/bookmark/file/BookmarkProtocolProvider.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.bookmark.file;\n\nimport com.mucommander.bookmark.Bookmark;\nimport com.mucommander.bookmark.BookmarkManager;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.ProtocolProvider;\n\nimport java.io.IOException;\n\n/**\n * This class is the provider for the bookmark filesystem implemented by {@link com.mucommander.bookmark.file.BookmarkFile}.\n *\n * @author Nicolas Rinaudo\n * @see com.mucommander.bookmark.file.BookmarkFile\n */\npublic class BookmarkProtocolProvider implements ProtocolProvider {\n\n    /** Protocol for the virtual bookmarks file system. */\n    public static final String BOOKMARK = \"bookmark\";\n\n    public AbstractFile getFile(FileURL url, Object... instantiationParams) throws IOException {\n        // If the URL contains a path but no host, it's illegal.\n        // If it contains neither host nor path, we're browsing bookmarks://\n        if (url.getHost() == null) {\n            if (url.getPath().equals(\"/\")) {\n                return new BookmarkRoot(url);\n            }\n            throw new IOException();\n        }\n\n        // If the URL contains a host, look it up in the bookmark list and use that\n        // as the root of the returned path.\n        else {\n            Bookmark bookmark = BookmarkManager.getBookmark(url.getHost());\n            // If the bookmark doesn't exist, but a path is specified, throws an exception.\n            // Otherwise, returns the requested bookmark.\n            if (bookmark == null) {\n                if (!url.getPath().equals(\"/\")) {\n                    throw new IOException();\n                }\n                return new BookmarkFile(new Bookmark(url.getHost(), url.getPath(), null));\n            }\n\n            // If the bookmark exists, and a path is specified, creates a new path\n            // from the bookmark's location and the specified path.\n            if (!url.getPath().equals(\"/\")) {\n                return FileFactory.getFile(bookmark.getLocation() + url.getPath());\n            }\n\n            // Otherwise, creates a new bookmark file.\n            return new BookmarkFile(bookmark);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/bookmark/file/BookmarkRoot.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.bookmark.file;\n\nimport com.mucommander.bookmark.Bookmark;\nimport com.mucommander.bookmark.BookmarkListener;\nimport com.mucommander.bookmark.BookmarkManager;\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.io.RandomAccessInputStream;\nimport com.mucommander.commons.io.RandomAccessOutputStream;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n/**\n * Represents the root of the <code>bookmarks://</code> file system.\n * @author Nicolas Rinaudo\n */\nclass BookmarkRoot extends ProtocolFile implements BookmarkListener {\n    /** Time at which the bookmarks were last modified. */\n    private long lastModified;\n\n\n    BookmarkRoot() throws IOException {this(FileURL.getFileURL(BookmarkProtocolProvider.BOOKMARK + \"://\"));}\n\n    BookmarkRoot(FileURL url) {\n        super(url);\n        lastModified = System.currentTimeMillis();\n        BookmarkManager.addBookmarkListener(this);\n    }\n\n\n    @Override\n    public AbstractFile[] ls() throws IOException {\n        // Retrieves all available bookmarks.\n        Object[] buffer = BookmarkManager.getBookmarks().toArray();\n        AbstractFile[] files  = new AbstractFile[buffer.length];\n\n        // Creates the associated instances of BookmarkFile\n        for (int i = 0; i < files.length; i++) {\n            files[i] = new BookmarkFile((Bookmark)buffer[i]);\n        }\n        return files;\n    }\n\n    @Override\n    public String getName() {\n        return \"\";\n    }\n\n    @Override\n    public boolean isDirectory() {\n        return true;\n    }\n\n    /**\n     * Stores the current date as the date of last modification.\n     */\n    public void bookmarksChanged() {\n        lastModified = System.currentTimeMillis();\n    }\n\n    /**\n     * Returns the date at which the bookmark list was last modified.\n     * @return the date at which the bookmark list was last modified.\n     */\n    @Override\n    public long getLastModifiedDate() {\n        return lastModified;\n    }\n\n    // The following methods are not used by BookmarkFile. They will throw an exception,\n    // return an 'operation non supported' value or return a default value.\n\n    @Override\n    public AbstractFile getParent() {return null;}\n    @Override\n    @UnsupportedFileOperation\n    public void delete() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.DELETE);}\n    @Override\n    @UnsupportedFileOperation\n    public void copyRemotelyTo(AbstractFile destFile) throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.COPY_REMOTELY);}\n    @Override\n    @UnsupportedFileOperation\n    public void renameTo(AbstractFile destFile) throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.RENAME);}\n    @Override\n    @UnsupportedFileOperation\n    public void setLastModifiedDate(long lastModified) throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.CHANGE_DATE);}\n    @Override\n    public long getSize() {return -1;}\n    @Override\n    public void setParent(AbstractFile parent) {}\n    @Override\n    public boolean exists() {return true;}\n    @Override\n    public FilePermissions getPermissions() {return BookmarkFile.PERMISSIONS;}\n    @Override\n    @UnsupportedFileOperation\n    public void changePermission(int access, int permission, boolean enabled) throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.CHANGE_PERMISSION);}\n    @Override\n    public PermissionBits getChangeablePermissions() {return PermissionBits.EMPTY_PERMISSION_BITS;}\n    @Override\n    public boolean isSymlink() {return false;}\n    @Override\n    public boolean isSystem() {return false;}\n    @Override\n    @UnsupportedFileOperation\n    public void mkdir() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.CREATE_DIRECTORY);}\n    @Override\n    @UnsupportedFileOperation\n    public InputStream getInputStream() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.READ_FILE);}\n    @Override\n    @UnsupportedFileOperation\n    public OutputStream getOutputStream() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.WRITE_FILE);}\n    @Override\n    @UnsupportedFileOperation\n    public OutputStream getAppendOutputStream() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.APPEND_FILE);}\n    @Override\n    @UnsupportedFileOperation\n    public RandomAccessInputStream getRandomAccessInputStream() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.RANDOM_READ_FILE);}\n    @Override\n    @UnsupportedFileOperation\n    public RandomAccessOutputStream getRandomAccessOutputStream() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.RANDOM_WRITE_FILE);}\n    @Override\n    @UnsupportedFileOperation\n    public long getFreeSpace() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.GET_FREE_SPACE);}\n    @Override\n    @UnsupportedFileOperation\n    public long getTotalSpace() throws UnsupportedFileOperationException {throw new UnsupportedFileOperationException(FileOperation.GET_TOTAL_SPACE);}\n    @Override\n    public Object getUnderlyingFileObject() {return null;}\n    @Override\n    public String getOwner() {return null;}\n    @Override\n    public boolean canGetOwner() {return false;}\n    @Override\n    public String getGroup() {return null;}\n    @Override\n    public boolean canGetGroup() {return false;}\n    @Override\n    @UnsupportedFileOperation\n    public short getReplication() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_REPLICATION);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public long getBlocksize() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_BLOCKSIZE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public void changeReplication(short replication) throws IOException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_REPLICATION);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/bookmark/file/package.html",
    "content": "<body>\n  Provides an implementation of the bookmarks:// virtual file system.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/bookmark/package.html",
    "content": "<body>\n  API for bookmark management.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/cache/FastLRUCache.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.cache;\n\nimport java.util.Iterator;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\n\n/**\n * LRU cache implementation which uses <code>LinkedHashMap</code> which provides fast retrieval and insertion\n * operations.\n * \n * <p>The only area this implementation is slow at, is checking for and removing expired elements which\n * requires traversing all values and <code>LinkedHashMap</code> is slow at that. \n * To minimize the impact this could have on performance, this operation is not systematically performed\n * for each call to <code>get()</code> and <code>set()</code> methods, unless the cache is full. \n * That means this implementation is not as aggressive as it could be in terms of releasing expired items' memory\n * but favors performance instead, which is what caches are for.\n *\n * @author Maxence Bernard\n */\npublic class FastLRUCache<K, V> extends LRUCache<K,V> {\n\n    /** Cache key->value/expirationDate map */\n    private final LinkedHashMap<K, Value<V>> cacheMap;\n\n    /** Timestamp of last expired items purge */\n    private long lastExpiredPurge;\n\n    /** Number of millisecond to wait between 2 expired items purges, if cache is not full */\n    private final static int PURGE_EXPIRED_DELAY = 1000;\n\n    private static final class Value<V> {\n        private final V val;\n        private final Long expiration;\n\n        public Value(V value, Long expirationDate) {\n            this.val = value;\n            this.expiration = expirationDate;\n        }\n    }\n\t\t\n\n    public FastLRUCache(int capacity) {\n        super(capacity);\n        this.cacheMap = new LinkedHashMap<K, Value<V>>(16, 0.75f, true) {\n                // Override this method to automatically remove eldest entry before insertion when cache is full\n                @Override\n                protected boolean removeEldestEntry(Map.Entry<K, Value<V>> eldest) {\n                    return cacheMap.size() > FastLRUCache.this.capacity;\n                }\n            };\n    }\n\n\n    /**\n     * Returns a String representation of this cache.\n     */\n    public String toString() {\n        StringBuilder sb = new StringBuilder(super.toString()).\n                append(\" size=\").append(cacheMap.size()).\n                append(\" capacity=\").append(capacity).\n                append(\" eldestExpirationDate=\").append(eldestExpirationDate).append('\\n');\n\n        int i = 0;\n        for (Map.Entry<K, Value<V>> mapEntry : cacheMap.entrySet()) {\n            Object key = mapEntry.getKey();\n            Value<V> value = mapEntry.getValue();\n            sb.append(i++).append(\"-key=\").append(key).append(\" value=\").append(value.val).\n                    append(\" expirationDate=\").append(value.expiration).append('\\n');\n        }\n\t\t\n        if (UPDATE_CACHE_COUNTERS) {\n            sb.append(\"nbCacheHits=\").append(nbHits).append(\" nbCacheMisses=\").append(nbMisses).append('\\n');\n        }\n\t\t\n        return sb.toString();\n    }\n\n\n    /**\n     * Looks for cached items that have a passed expiration date and purge them.\n     */\n    private void purgeExpiredItems() {\n        long now = System.currentTimeMillis();\n        // No need to go any further if eldestExpirationDate is in the future.\n        // Also, since iterating on the values is an expensive operation (especially for LinkedHashMap),\n        // wait PURGE_EXPIRED_DELAY between two purges, unless cache is full\n        if (this.eldestExpirationDate > now || (cacheMap.size()<capacity && now-lastExpiredPurge<PURGE_EXPIRED_DELAY)) {\n            return;\n        }\n\n        // Look for expired items and remove them and recalculate eldestExpirationDate for next time\n        this.eldestExpirationDate = Long.MAX_VALUE;\n        Iterator<Value<V>> iterator = cacheMap.values().iterator();\n        // Iterate on all cached values\n        while (iterator.hasNext()) {\n            Long expirationDateL = iterator.next().expiration;\n            // No expiration date for this value\n            if (expirationDateL == null) {\n                continue;\n            }\n            long expirationDate = expirationDateL;\n            // Test if the item has an expiration date and check if has passed\n            if (expirationDate < now) {\n                // Remove expired item\n                iterator.remove();\n            } else if(expirationDate < this.eldestExpirationDate) {\n                // update eldestExpirationDate\n                this.eldestExpirationDate = expirationDate;\n            }\n        }\n\t\t\n        // Set last purge timestamp to now\n        lastExpiredPurge = now;\n    }\n\n\n    @Override\n    public synchronized V get(K key) {\n        // Look for expired items and purge them (if any)\n        purgeExpiredItems();\t\n\n        // Look for a value corresponding to the specified key in the cache map\n        Value<V> value = cacheMap.get(key);\n\n        if (value == null) {\n            // No value matching key, better luck next time!\n            if (UPDATE_CACHE_COUNTERS) {\n                nbMisses++;\t// Increase cache miss counter\n            }\n            return null;\n        }\n\n        // Since expired items purge is not performed on every call to this method for\n        // performance reason, we can end with an expired cached value so we need\n        // to check this\n        if (value.expiration != null && System.currentTimeMillis() > value.expiration) {\n            // Value has expired, let's remove it\n            if (UPDATE_CACHE_COUNTERS) {\n                nbMisses++;    // Increase cache miss counter\n            }\n            cacheMap.remove(key);\n            return null;\n        }\n        if (UPDATE_CACHE_COUNTERS) {\n            nbHits++;    // Increase cache hit counter\n        }\n        return value.val;\n    }\n\n\t\n    @Override\n    public synchronized void add(K key, V value, long timeToLive) {\n        // Look for expired items and purge them (if any)\n        purgeExpiredItems();\t\n\n        Long expirationDateL;\n        if (timeToLive == -1) {\n            expirationDateL = null;\n        } else {\n            long expirationDate = System.currentTimeMillis()+timeToLive;\n            // Update eledestExpirationDate if new element's expiration date is older\n            if (expirationDate<this.eldestExpirationDate) {\n                // update eldestExpirationDate\n                this.eldestExpirationDate = expirationDate;\n            }\n            expirationDateL = expirationDate;\n        }\n\n        cacheMap.put(key, new Value<>(value, expirationDateL));\n    }\n\n\n    @Override\n    public synchronized int size() {\n        return cacheMap.size();\n    }\n\n\t\n    @Override\n    public synchronized void clearAll() {\n        cacheMap.clear();\n        eldestExpirationDate = Long.MAX_VALUE;\n    }\n\t\n\t\n    //////////////////\n    // Test methods //\n    //////////////////\n\n    /**\n     * Tests this LRUCache for corruption and throws a RuntimeException if something is wrong.\n     */\n    @Override\n    protected void testCorruption() throws RuntimeException {\n        for (K key : cacheMap.keySet()) {\n            Value<V> value = cacheMap.get(key);\n            if (value == null) {\n                throw new RuntimeException(\"cache corrupted: value could not be found for key=\"+key);\n            }\n\n            if (value.expiration == null) {\n                continue;\n            }\n\t\t\t\n            Long expirationDate = value.expiration;\n            if (expirationDate < eldestExpirationDate) {\n                throw new RuntimeException(\"cache corrupted: expiration date for key=\"+key+\" older than eldestExpirationDate\");\n            }\n        }\n    }\n\t\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/cache/LRUCache.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.cache;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport java.util.Random;\r\n\r\n/**\r\n * An abstract LRU cache.\r\n *\r\n * <p>An LRU (Least Recently Used) cache can contain a fixed number of items (the capacity). When capacity is reached,\r\n * the least recently used is removed. Each object retrieved with the {@link #get(Object) get()} method\r\n * makes the requested item the most recently used one. Similarly, each object inserted using the \r\n * {@link #add(Object, Object) add()} method makes the added item the most recently used one.\r\n *\r\n * <p>This LRUCache provides an optional feature : the ability to assign a time-to-live for each or part of the\r\n * items added. When the time-to-live of an item expires, the item is automatically removed from the cache and won't\r\n * be returned by the {@link #get(Object) get()} method anymore.\r\n *\r\n * <p><b>Implementation note:</b> checking for expired items can be an expensive operation so it doesn't have\r\n * to be done as soon as the item has expired, the expired items can live a bit longer in the cache if necessary.\r\n * <br>The LRUCache implementation must however guarantee two things :\r\n * <ul>\r\n * <li>as soon as an item has expired, it cannot be returned by {@link #get(Object) get()}.\r\n * <li>when cache capacity is reached (cache is full) and a new item needs to be added, any expired item must be \r\n * immediately removed. This prevents least recently used items from being removed unnecessarily.\r\n * </ul>\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic abstract class LRUCache<K, V> {\r\n\tprivate static Logger logger;\r\n\t\r\n    /** Cache capacity: maximum number of items this cache can contain */\r\n    protected int capacity;\r\n\r\n    /** Current eldest expiration date amongst all items */\r\n    protected long eldestExpirationDate = Long.MAX_VALUE;\r\n\r\n    /** Specifies whether cache hit/miss counters should be updated (should be enabled for Debug purposes only) */ \r\n    protected final static boolean UPDATE_CACHE_COUNTERS = false;\t\r\n    /** Number of cache hits since this LRUCache was created */\r\n    protected int nbHits;\r\n    /** Number of cache misses since this LRUCache was created */\r\n    protected int nbMisses;\t\r\n\r\n\r\n    /**\r\n     * Creates an initially empty LRUCache with the specified maximum capacity.\r\n     * @param capacity initial capacity (he maximum number of items this cache can contain)\r\n     */\r\n    public LRUCache(int capacity) {\r\n        this.capacity = capacity;\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the maximum number of items this cache can contain.\r\n     * @return the maximum number of items this cache can contain\r\n     */\r\n    public int getCapacity() {\r\n        return capacity;\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the number of cache hits since this LRUCache was created.\r\n     *\r\n     * @return the number of cache hits since this LRUCache was created\r\n     */\r\n    public int getHitCount() {\r\n        return nbHits;\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the number of cache misses since this LRUCache was created.\r\n     *\r\n     * @return the number of cache misses since this LRUCache was created\r\n     */\r\n    public int getMissCount() {\r\n        return nbMisses;\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the cached object value corresponding to the given key and marks the cached item as the most\r\n     * recently used one.\r\n     *\r\n     * <p>This method will return <code>null</code> if:\r\n     * <ul>\r\n     * <li>the given key doesn't exist\r\n     * <li>the cached value corresponding to the key has expired\r\n     * </ul>\r\n     *\r\n     * @param key the cached item's key\r\n     * @return the cached value corresponding to the specified key, or <code>null</code> if a value could not\r\n     * found or has expired\r\n     */\r\n    public abstract V get(K key);\r\n\t\r\n    /**\r\n     * Adds a new key/value pair to the cache and marks it as the most recently used.\r\n     *\r\n     * <p>If the cache's capacity has been reached (cache is full):\r\n     * <ul>\r\n     * <li>any object with a past expiration date will be removed</li>\r\n     * <li>if no expired item could be removed, the least recently used item will be removed</li>\r\n     * </ul>\r\n     *\r\n     * @param key the key for the object to store\r\n     * @param value the value to cache\r\n     * @param timeToLive the time-to-live of the object in the cache in milliseconds, or -1 for no time-to-live,\r\n     * the object will just be removed when it becomes the least recently used one.\r\n     */\r\n    public abstract void add(K key, V value, long timeToLive);\r\n\r\n\r\n    /**\r\n     * Convenience method, equivalent to add(key, value, -1).\r\n     * @param key key\r\n     * @param value value\r\n     */\r\n    public synchronized void add(K key, V value) {\r\n        add(key, value, -1);\r\n    }\r\n\t\r\n\r\n    /**\r\n     * Removes all items from this cache, leaving the cache in the same state as when it was just created.\r\n     */\r\n    public abstract void clearAll();\r\n\t\t\r\n\r\n    /**\r\n     * Returns the current size of this cache, i.e. the number of cached elements it contains.\r\n     * <br><b>Note: </b>Some items that have expired and have not yet been removed might be accounted for\r\n     * in the returned size.\r\n     *\r\n     * @return the current size of this cache\r\n     */\r\n    public abstract int size();\r\n\r\n\r\n    /**\r\n     * Tests this LRUCache for corruption and throws a RuntimeException if something is wrong.\r\n     */\r\n    protected abstract void testCorruption() throws RuntimeException;\r\n\t\r\n\r\n    /**\r\n     * Test method : simple test case + stress/sanity test\r\n     *\r\n     * @param args command line arguments\r\n     */\r\n    public static void main(String[] args) {\r\n        LRUCache<Integer, Integer> cache;\r\n        /*\r\n        // Simple test case\r\n        cache = new FastLRUCache(3);\r\n\r\n        cache.add(\"orange\", \"ORANGE\");\r\n        System.out.println(cache.toString());\r\n\r\n        cache.add(\"apple\", \"APPLE\");\r\n        System.out.println(cache.toString());\r\n\r\n        System.out.println(\"get(orange)= \"+cache.get(\"orange\"));\r\n        System.out.println(cache.toString());\r\n\t\t\r\n        cache.add(\"apricot\", \"APRICOT\");\r\n        System.out.println(cache.toString());\r\n\r\n        cache.add(\"banana\", \"BANANA\", 1000);\r\n        System.out.println(cache.toString());\r\n\r\n        System.out.println(\"waiting for banana expiration\");\r\n        try { Thread.sleep(1050); } catch(InterruptedException e) {}\r\n        System.out.println(cache.toString());\r\n\r\n        System.out.println(\"get(banana)= \"+cache.get(\"banana\"));\r\n\r\n        System.out.println(cache.toString());\r\n        */\r\n\r\n        long timeStamp = System.currentTimeMillis();\r\n\r\n        // Stress test to see if everything looks OK after a few thousand iterations\r\n        int capacity = 1000;\r\n        cache = new FastLRUCache<>(capacity);\r\n        Random random = new Random();\r\n        for (int i=0; i<100000; i++) {\r\n            // 50% chance to add a new element with a random value and expiration date (50% chance for no expiration date)\r\n            if (cache.isEmpty() || random.nextBoolean()) {\r\n                //\t\t\t\tSystem.out.println(\"cache.add()\");\t\t\t\t\r\n                cache.add(random.nextInt(capacity), random.nextInt(), random.nextBoolean()?-1:random.nextInt(10));\r\n            }\r\n            // 50% chance to retrieve a random existing element\r\n            else {\r\n                //\t\t\t\tSystem.out.println(\"cache.get()\");\r\n                cache.get(random.nextInt(capacity));\r\n            }\r\n\t\t\r\n            try {\r\n                // Test the cache for corruption\r\n                cache.testCorruption();\r\n            } catch(RuntimeException e) {\r\n                getLogger().debug(\"Cache corrupted after \"+i+\" iterations, cache state=\"+cache);\r\n                return;\r\n            }\r\n\r\n            //\t\t\t// Print the cache's state\r\n            //\t\t\tSystem.out.println(cache.toString());\r\n        }\r\n\r\n        getLogger().debug(\"Stress test took \"+(System.currentTimeMillis()-timeStamp)+\" ms.\\n\");\r\n\r\n        // Print the cache's state\r\n        System.out.println(cache);\r\n    }\r\n\r\n    public boolean isEmpty() {\r\n        return size() != 0;\r\n    }\r\n\r\n    private static Logger getLogger() {\r\n        if (logger == null) {\r\n            logger = LoggerFactory.getLogger(LRUCache.class);\r\n        }\r\n        return logger;\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/cache/TextHistory.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.cache;\n\nimport com.mucommander.PlatformManager;\nimport com.mucommander.commons.file.AbstractFile;\n\nimport java.io.*;\nimport java.lang.ref.WeakReference;\nimport java.nio.charset.StandardCharsets;\nimport java.util.*;\n\n/**\n * @author Oleg Trifonov\n *\n * Stores history of search requests for text search, file search etc.\n */\npublic class TextHistory {\n\n    private static final int MAX_RECORDS = 500;\n\n    public enum Type {\n        TEXT_SEARCH(\"search-text.history\"),\n        HEX_DATA_SEARCH(\"search-hex.history\"),\n        FILE_NAME(\"search-files.history\"),\n        CALCULATOR(\"calculator.history\"),\n        EDITOR_BOOKMARKS(\"editor.bookmarks\");\n\n        private final String fileName;\n        Type(String fileName) {\n            this.fileName = fileName;\n        }\n    }\n    private final Map<Type, LinkedList<String>> history = new HashMap<>();\n\n    private static WeakReference<TextHistory> instance;\n\n    public static TextHistory getInstance() {\n        TextHistory textHistory = instance != null ? instance.get() : null;\n        if (textHistory == null) {\n            textHistory = new TextHistory();\n            instance = new WeakReference<>(textHistory);\n        }\n        return textHistory;\n    }\n\n\n    public LinkedList<String> getList(Type type) {\n        LinkedList<String> result = history.get(type);\n        if (result == null) {\n            try {\n                result = load(getHistoryFile(type));\n            } catch (IOException e) {\n                e.printStackTrace();\n                result = new LinkedList<>();\n            }\n            history.put(type, result);\n        }\n        return result;\n    }\n\n    public void add(Type type, String s, boolean save) {\n        LinkedList<String> list = getList(type);\n        int index = list.indexOf(s);\n\n        if (index >= 0) {\n            list.remove(index);\n            // remove other elements if they exists\n            while (list.remove(s)) {\n                //\n            }\n        }\n        if (s.trim().isEmpty()) {\n            return;\n        }\n        list.addFirst(s);\n        while (list.size() > MAX_RECORDS) {\n            list.removeLast();\n        }\n        // save only if new record was added\n        if (index != 0 && save) {\n            save(type);\n        }\n    }\n\n\n    public void save(Type type) {\n        try {\n            save(getHistoryFile(type), getList(type));\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n\n\n    private LinkedList<String> load(AbstractFile file) {\n        LinkedList<String> result = new LinkedList<>();\n        if (!file.exists()) {\n            return result;\n        }\n        try (BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) {\n            String line;\n            while ( (line = reader.readLine() ) != null) {\n                String trim = line.trim();\n                if (trim.isEmpty() || trim.startsWith(\"#\")) {\n                    continue;\n                }\n                result.add(line);\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return result;\n    }\n\n    private void save(AbstractFile file, List<String> list) throws  IOException {\n        try (Writer writer = new BufferedWriter(new OutputStreamWriter(file.getOutputStream(), StandardCharsets.UTF_8))) {\n            for (String s : list) {\n                writer.write(s);\n                writer.write('\\n');\n            }\n        }\n    }\n\n    /**\n     * Returns the path to the history file.\n     * <p>\n     * Will return the default, system dependant bookmarks file.\n     *\n     * @return             the path to the bookmark file.\n     * @throws java.io.IOException if there was a problem locating the default history file.\n     */\n    public static synchronized AbstractFile getHistoryFile(Type type) throws IOException {\n        return PlatformManager.getPreferencesFolder().getChild(type.fileName);\n    }\n\n\n    public void clear() {\n        history.clear();\n        instance = null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/cache/WindowsStorage.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.cache;\n\nimport com.mucommander.PlatformManager;\nimport com.mucommander.commons.file.AbstractFile;\n\nimport java.awt.Window;\nimport java.io.*;\nimport java.lang.ref.WeakReference;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Stores windows sizes and positions\n */\npublic class WindowsStorage {\n\n    private static final String STORAGE_FILE_NAME = \"windows.list\";\n\n    private static WeakReference<WindowsStorage> instance;\n\n    private Map<String, Record> records;\n\n    public static class Record {\n        public final int left, top, width, height;\n\n        Record(String s) {\n            String[] val = s.split(\",\");\n            this.left = Integer.parseInt(val[0].trim());\n            this.top = Integer.parseInt(val[1].trim());\n            this.width = Integer.parseInt(val[2].trim());\n            this.height = Integer.parseInt(val[3].trim());\n        }\n\n        public Record(int left, int top, int width, int height) {\n            this.left = left;\n            this.top = top;\n            this.width = width;\n            this.height = height;\n        }\n\n        @Override\n        public boolean equals(Object obj) {\n            if (obj == this) {\n                return true;\n            }\n            if (!(obj instanceof Record)) {\n                return false;\n            }\n            Record rec = (Record)obj;\n            return left == rec.left && top == rec.top && width == rec.width && height == rec.height;\n        }\n\n        @Override\n        public String toString() {\n            return String.valueOf(left) + ',' + top + ',' + width + ',' + height;\n        }\n\n        public void apply(Window window) {\n            window.setLocation(left, top);\n            window.setSize(width, height);\n        }\n\n        void applyPos(Window window) {\n            window.setLocation(left, top);\n        }\n    }\n\n    public static WindowsStorage getInstance() {\n        WindowsStorage windowsStorage = instance != null ? instance.get() : null;\n        if (windowsStorage == null) {\n            windowsStorage = new WindowsStorage();\n            instance = new WeakReference<>(windowsStorage);\n        }\n        return windowsStorage;\n    }\n\n    public Record get(String key) {\n        return getRecords().get(key);\n    }\n\n    public Record get(Window window, String suffix) {\n        String key = getKey(window, suffix);\n        return getRecords().get(key);\n    }\n\n    public Record get(Window frame) {\n        return get(frame, null);\n    }\n\n    public void put(String key, Record rec) {\n        Record prev = getRecords().put(key, rec);\n        if (prev == null || !prev.equals(rec)) {\n            try {\n                save(getHistoryFile());\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    public void put(Window window) {\n        put(window, null);\n    }\n\n    public void put(Window window, String suffix) {\n        Record rec = new Record(window.getLocation().x, window.getLocation().y, window.getWidth(), window.getHeight());\n        String key = getKey(window, suffix);\n        put(key, rec);\n    }\n\n    public boolean init(Window window, String suffix, boolean storeSizes) {\n        String key = getKey(window, suffix);\n        Record rec = getRecords().get(key);\n        if (rec != null && rec.width > 0 && rec.height > 40) {\n            if (storeSizes) {\n                rec.apply(window);\n            } else {\n                rec.applyPos(window);\n            }\n            return true;\n        }\n        return false;\n    }\n\n\n    private String getKey(Window window, String suffix) {\n        Class<?> c = window.getClass();\n        String key = c.getCanonicalName();\n        if (key == null) {\n            key = c.getPackage().getName() + '.' + c.getName();\n        }\n        if (suffix != null) {\n            key += '#' + suffix;\n        }\n        return key;\n    }\n\n    public boolean init(Window window) {\n        return init(window, null, true);\n    }\n\n\n    private Map<String, Record> getRecords() {\n        if (records == null) {\n            records = new HashMap<>();\n            try {\n                load(getHistoryFile());\n            } catch (FileNotFoundException ignore) {\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n        return records;\n    }\n\n\n    private void load(AbstractFile file) throws IOException {\n        try (BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream()))) {\n            String line;\n            while ((line = reader.readLine()) != null) {\n                loadRecord(line.trim());\n            }\n        }\n//        BufferedReader reader = null;\n//        try {\n//            reader = new BufferedReader(new InputStreamReader(file.getInputStream()));\n//            String line;\n//            while ( (line = reader.readLine() ) != null) {\n//                line = line.trim();\n//                if (line.isEmpty() || line.startsWith(\"#\")) {\n//                    continue;\n//                }\n//                int index = line.indexOf('=');\n//                if (index < 0) {\n//                    continue;\n//                }\n//                String key = line.substring(0, index);\n//                String val = line.substring(index + 1);\n//                try {\n//                    records.put(key, new Record(val));\n//                } catch (Exception e) {\n//                    e.printStackTrace();\n//                }\n//            }\n//        } catch (Exception e) {\n//            e.printStackTrace();\n//        } finally {\n//            if (reader != null) {\n//                reader.close();\n//            }\n//        }\n    }\n\n    private void loadRecord(String line) {\n        if (line.isEmpty() || line.startsWith(\"#\")) {\n            return;\n        }\n        int index = line.indexOf('=');\n        if (index < 0) {\n            return;\n        }\n        String key = line.substring(0, index);\n        String val = line.substring(index + 1);\n        try {\n            records.put(key, new Record(val));\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n\n    private void save(AbstractFile file) throws IOException {\n        try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(file.getOutputStream()))) {\n            for (String key : getRecords().keySet()) {\n                if (key != null) {\n                    saveRecord(writer, key, records.get(key));\n                }\n            }\n        }\n    }\n\n    private void saveRecord(Writer writer, String key, Record record) throws IOException {\n        writer.write(key);\n        writer.write('=');\n        writer.write(record.toString());\n        writer.write('\\n');\n    }\n\n    /**\n     * Returns the path to the history file.\n     * <p>\n     * Will return the default, system dependant bookmarks file.\n     *\n     * @return             the path to the bookmark file.\n     * @throws java.io.IOException if there was a problem locating the default history file.\n     */\n    private static synchronized AbstractFile getHistoryFile() throws IOException {\n        return PlatformManager.getPreferencesFolder().getChild(STORAGE_FILE_NAME);\n    }\n\n\n    public void clear() {\n        if (records != null) {\n            records.clear();\n        }\n        records = null;\n        instance = null;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/cache/package.html",
    "content": "<body>\n  Provides various cache implementations.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/command/AssociationBuilder.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.command;\n\n/**\n * Receive notification of the logical structure of a custom association list.\n * @author Nicolas Rinaudo\n */\npublic interface AssociationBuilder {\n    /**\n     * Notifies the builder that association building is about to start.\n     * @throws CommandException if an error occurs.\n     */\n    void startBuilding() throws CommandException;\n\n    /**\n     * Notifies the builder that association building is finished.\n     * @throws CommandException if an error occurs.\n     */\n    void endBuilding() throws CommandException;\n\n    /**\n     * Notifies the builder that a new association declaration is starting.\n     * @param  command          command to call when the association is matched.\n     * @throws CommandException if an error occurs.\n     */\n    void startAssociation(String command) throws CommandException;\n\n    /**\n     * Notifies the builder that the current association declaration is finished.\n     * @throws CommandException if an error occurs.\n     */\n    void endAssociation() throws CommandException;\n\n    /**\n     * Adds a mask to the current association.\n     * @param  mask             regular expression that a file name must match in order to match the association.\n     * @param  isCaseSensitive  whether the regular expression is case-sensitive.\n     * @throws CommandException if an error occurs.\n     */\n    void setMask(String mask, boolean isCaseSensitive) throws CommandException;\n\n    /**\n     * Adds a <i>symlink</i> IMAGE_FILTER on the current association.\n     * @param  isSymlink        whether symbolic links must be refused or accepted by the association.\n     * @throws CommandException if an error occurs.\n     */\n    void setIsSymlink(boolean isSymlink) throws CommandException;\n\n    /**\n     * Adds a <i>hidden</i> IMAGE_FILTER on the current association.\n     * @param  isHidden         whether hidden files must be refused or accepted by the association.\n     * @throws CommandException if an error occurs.\n     */\n    void setIsHidden(boolean isHidden) throws CommandException;\n\n    /**\n     * Adds a <i>readable</i> IMAGE_FILTER on the current association.\n     * @param  isReadable       whether readable files must be refused or accepted by the association.\n     * @throws CommandException if an error occurs.\n     */\n    void setIsReadable(boolean isReadable) throws CommandException;\n\n    /**\n     * Adds a <i>writable</i> IMAGE_FILTER on the current association.\n     * @param  isWritable       whether writable files must be refused or accepted by the association.\n     * @throws CommandException if an error occurs.\n     */\n    void setIsWritable(boolean isWritable) throws CommandException;\n\n    /**\n     * Adds an <i>executable</i> IMAGE_FILTER on the current association.\n     * @param  isExecutable     whether executable files must be refused or accepted by the association.\n     * @throws CommandException if an error occurs.\n     */\n    void setIsExecutable(boolean isExecutable) throws CommandException;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/command/AssociationFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.command;\n\nimport com.mucommander.commons.file.PermissionTypes;\nimport com.mucommander.commons.file.filter.AndFileFilter;\nimport com.mucommander.commons.file.filter.AttributeFileFilter;\nimport com.mucommander.commons.file.filter.AttributeFileFilter.FileAttribute;\nimport com.mucommander.commons.file.filter.RegexpFilenameFilter;\n\n/**\n * @author Nicolas Rinaudo\n */\nclass AssociationFactory implements AssociationBuilder {\n    private AndFileFilter filter;\n    private String        command;\n\n    public void startBuilding() {}\n    public void endBuilding() {}\n\n    public void startAssociation(String command) {\n        filter       = new AndFileFilter();\n        this.command = command;\n    }\n\n    public void endAssociation() throws CommandException {\n        // Skip empty file filters as they will break the whole\n        // association mechanism.\n        if(!filter.isEmpty())\n            CommandManager.registerAssociation(command, filter);\n    }\n\n    public void setMask(String mask, boolean isCaseSensitive) {\n        filter.addFileFilter(new RegexpFilenameFilter(mask, isCaseSensitive));\n    }\n\n    public void setIsDir(boolean isDir) {\n        filter.addFileFilter(new AttributeFileFilter(FileAttribute.DIRECTORY, isDir));\n    }\n\n    public void setIsSymlink(boolean isSymlink) {\n        filter.addFileFilter(new AttributeFileFilter(FileAttribute.SYMLINK, isSymlink));\n    }\n\n    public void setIsHidden(boolean isHidden) {\n        filter.addFileFilter(new AttributeFileFilter(FileAttribute.HIDDEN, isHidden));\n    }\n\n    public void setIsReadable(boolean isReadable) {\n        filter.addFileFilter(new PermissionsFileFilter(PermissionTypes.READ_PERMISSION, isReadable));\n    }\n\n    public void setIsWritable(boolean isWritable) {\n        filter.addFileFilter(new PermissionsFileFilter(PermissionTypes.WRITE_PERMISSION, isWritable));\n    }\n\n    public void setIsExecutable(boolean isExecutable) {\n        filter.addFileFilter(new PermissionsFileFilter(PermissionTypes.EXECUTE_PERMISSION, isExecutable));\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/command/AssociationReader.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.command;\n\nimport org.xml.sax.Attributes;\nimport org.xml.sax.SAXException;\nimport org.xml.sax.helpers.DefaultHandler;\n\nimport javax.xml.parsers.ParserConfigurationException;\nimport javax.xml.parsers.SAXParserFactory;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * Class used to parse custom associations XML files.\n * <p>\n * Association file parsing is done through the {@link #read(InputStream,AssociationBuilder) read} method, which is\n * the only way to interact with this class.\n * <p>\n * Note that while this class knows how to read the content of an association XML file, its role is not to interpret it. This\n * is done by instances of {@link AssociationBuilder}.\n *\n * @see    AssociationsXmlConstants\n * @see    AssociationBuilder\n * @see    AssociationWriter\n * @author Nicolas Rinaudo\n */\npublic class AssociationReader extends DefaultHandler implements AssociationsXmlConstants {\n    /** Where to send building messages. */\n    private final AssociationBuilder builder;\n    private boolean isInAssociation;\n\n\n\n    /**\n     * Creates a new command reader.\n     * @param b where to send custom command events.\n     */\n    private AssociationReader(AssociationBuilder b) {\n        builder = b;\n    }\n\n\n    /**\n     * Parses the content of the specified input stream.\n     * <p>\n     * This method will go through the specified input stream and notify the builder of any new association declaration it\n     * encounters. Note that parsing is done in a very lenient fashion, and perfectly invalid XML files might not raise\n     * an exception. This is not a flaw in the parser, and both allows muCommander to be error resilient and the associations\n     * file format to be extended without having to rewrite most of this code.\n     * <p>\n     * Note that even if an error occurs, both of the builder's {@link AssociationBuilder#startBuilding()} and\n     * {@link AssociationBuilder#endBuilding()} methods will still be called. Parsing will stop at the first error\n     * however, so while the builder is guaranteed to receive correct messages, it might not receive all declared\n     * associations.\n     *\n     * @param  in          where to read association data from.\n     * @param  b           where to send building events to.\n     * @throws IOException if any IO error occurs.\n     * @throws CommandException if any parse error occurs\n     * @see                #read(InputStream,AssociationBuilder)\n     */\n    public static void read(InputStream in, AssociationBuilder b) throws IOException, CommandException {\n        b.startBuilding();\n        try {\n            SAXParserFactory.newInstance().newSAXParser().parse(in, new AssociationReader(b));\n        } catch(ParserConfigurationException | SAXException e) {\n            throw new CommandException(e);\n        } finally {\n            b.endBuilding();\n        }\n    }\n\n\n    /**\n     * This method is public as an implementation side effect and should not be called directly.\n     */\n    @Override\n    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {\n        String buffer;\n\n        try {\n            if (!isInAssociation) {\n                if (qName.equals(ELEMENT_ASSOCIATION)) {\n                    // Makes sure the required attributes are present.\n                    if ((buffer = attributes.getValue(ATTRIBUTE_COMMAND)) == null) {\n                        return;\n                    }\n\n                    isInAssociation = true;\n                    builder.startAssociation(buffer);\n                }\n            } else {\n                switch (qName) {\n                    case ELEMENT_MASK:\n                        String caseSensitive;\n\n                        if ((buffer = attributes.getValue(ATTRIBUTE_VALUE)) == null) {\n                            return;\n                        }\n                        if ((caseSensitive = attributes.getValue(ATTRIBUTE_CASE_SENSITIVE)) != null) {\n                            builder.setMask(buffer, caseSensitive.equals(VALUE_TRUE));\n                        } else {\n                            builder.setMask(buffer, true);\n                        }\n                        break;\n                    case ELEMENT_IS_HIDDEN:\n                        if ((buffer = attributes.getValue(ATTRIBUTE_VALUE)) == null) {\n                            return;\n                        }\n                        builder.setIsHidden(buffer.equals(VALUE_TRUE));\n                        break;\n                    case ELEMENT_IS_SYMLINK:\n                        if ((buffer = attributes.getValue(ATTRIBUTE_VALUE)) == null) {\n                            return;\n                        }\n                        builder.setIsSymlink(buffer.equals(VALUE_TRUE));\n                        break;\n                    case ELEMENT_IS_READABLE:\n                        if ((buffer = attributes.getValue(ATTRIBUTE_VALUE)) == null) {\n                            return;\n                        }\n                        builder.setIsReadable(buffer.equals(VALUE_TRUE));\n                        break;\n                    case ELEMENT_IS_WRITABLE:\n                        if ((buffer = attributes.getValue(ATTRIBUTE_VALUE)) == null) {\n                            return;\n                        }\n                        builder.setIsWritable(buffer.equals(VALUE_TRUE));\n                        break;\n                    case ELEMENT_IS_EXECUTABLE:\n                        if ((buffer = attributes.getValue(ATTRIBUTE_VALUE)) == null) {\n                            return;\n                        }\n                        builder.setIsExecutable(buffer.equals(VALUE_TRUE));\n                        break;\n                }\n            }\n        } catch(CommandException e) {\n            throw new SAXException(e);\n        }\n    }\n\n    /**\n     * This method is public as an implementation side effect, but should not be called directly.\n     */\n    @Override\n    public void endElement(String uri, String localName, String qName) throws SAXException {\n        if (qName.equals(ELEMENT_ASSOCIATION) && isInAssociation) {\n            try {\n                builder.endAssociation();\n            } catch(CommandException e) {\n                throw new SAXException(e);\n            }\n            isInAssociation = false;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/command/AssociationWriter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.command;\n\nimport com.mucommander.utils.xml.XmlAttributes;\nimport com.mucommander.utils.xml.XmlWriter;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * Class used to write custom associations XML files.\n * <p>\n * <code>AssociationWriter</code> is an {@link AssociationBuilder} that will send\n * all build messages it receives into an XML stream (as defined in {@link AssociationsXmlConstants}).\n *\n * @author Nicolas Rinaudo\n */\npublic class AssociationWriter implements AssociationsXmlConstants, AssociationBuilder {\n    /** Where to write the custom command associations to. */\n    private final XmlWriter out;\n\n    /**\n     * Builds a new writer that will send data to the specified output stream.\n     * @param  stream      where to write the XML data.\n     * @throws IOException if an I/O error occurs.\n     */\n    AssociationWriter(OutputStream stream) throws IOException {out = new XmlWriter(stream);}\n\n\n\n    /**\n     * Opens the root XML element.\n     */\n    @Override\n    public void startBuilding() throws CommandException {\n        try {\n            out.startElement(ELEMENT_ROOT);\n            out.println();\n        } catch(IOException e) {\n            throw new CommandException(e);\n        }\n    }\n\n    /**\n     * Closes the root XML element.\n     */\n    @Override\n    public void endBuilding() throws CommandException {\n        try {\n            out.endElement(ELEMENT_ROOT);\n        } catch(IOException e) {\n            throw new CommandException(e);\n        }\n    }\n\n    @Override\n    public void startAssociation(String command) throws CommandException {\n        XmlAttributes attr = new XmlAttributes();\n        attr.add(ATTRIBUTE_COMMAND, command);\n\n        try {\n            out.startElement(ELEMENT_ASSOCIATION, attr);\n            out.println();\n        } catch(IOException e) {\n            throw new CommandException(e);\n        }\n    }\n\n    @Override\n    public void endAssociation() throws CommandException {\n        try {\n            out.endElement(ELEMENT_ASSOCIATION);\n        } catch(IOException e) {\n            throw new CommandException(e);\n        }\n    }\n\n    @Override\n    public void setMask(String mask, boolean isCaseSensitive) throws CommandException {\n        XmlAttributes attr = new XmlAttributes();\n        attr.add(ATTRIBUTE_VALUE, mask);\n        if (!isCaseSensitive) {\n            attr.add(ATTRIBUTE_CASE_SENSITIVE, VALUE_FALSE);\n        }\n        writeStandaloneElement(attr, ELEMENT_MASK);\n    }\n\n    @Override\n    public void setIsSymlink(boolean isSymlink) throws CommandException {\n        XmlAttributes attr = new XmlAttributes();\n        attr.add(ATTRIBUTE_VALUE, isSymlink ? VALUE_TRUE : VALUE_FALSE);\n        writeStandaloneElement(attr, ELEMENT_IS_SYMLINK);\n    }\n\n    @Override\n    public void setIsHidden(boolean isHidden) throws CommandException {\n        XmlAttributes attr = new XmlAttributes();\n        attr.add(ATTRIBUTE_VALUE, isHidden ? VALUE_TRUE : VALUE_FALSE);\n        writeStandaloneElement(attr, ELEMENT_IS_HIDDEN);\n    }\n\n    @Override\n    public void setIsReadable(boolean isReadable) throws CommandException {\n        XmlAttributes attr = new XmlAttributes();\n        attr.add(ATTRIBUTE_VALUE, isReadable ? VALUE_TRUE : VALUE_FALSE);\n        writeStandaloneElement(attr, ELEMENT_IS_READABLE);\n    }\n\n    @Override\n    public void setIsWritable(boolean isWritable) throws CommandException {\n        XmlAttributes attr = new XmlAttributes();\n        attr.add(ATTRIBUTE_VALUE, isWritable ? VALUE_TRUE : VALUE_FALSE);\n        writeStandaloneElement(attr, ELEMENT_IS_WRITABLE);\n    }\n\n    @Override\n    public void setIsExecutable(boolean isExecutable) throws CommandException {\n        XmlAttributes attr = new XmlAttributes();\n        attr.add(ATTRIBUTE_VALUE, isExecutable ? VALUE_TRUE : VALUE_FALSE);\n        writeStandaloneElement(attr, ELEMENT_IS_EXECUTABLE);\n    }\n\n    private void writeStandaloneElement(XmlAttributes attr, String name) throws CommandException {\n        try {\n            out.writeStandAloneElement(name, attr);\n        } catch (IOException e) {\n            throw new CommandException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/command/AssociationsXmlConstants.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.command;\n\n/**\n * Defines the structure of a custom associations XML file.\n * <p>\n * This interface is only meant as a convenient way of sharing the XML\n * file format between the {@link AssociationWriter} and {@link AssociationReader}. It will be removed\n * at bytecode optimisation time.\n * <p>\n * Associations XML files must match the following DTD:\n * <pre>\n * &lt;!ELEMENT associations (association*)&gt;\n * \n * &lt;!ELEMENT association (filename?,symlink?,hidden?,readable?,writable?,executable?)&gt;\n * &lt;!ATTLIST association command CDATA #REQUIRED&gt;\n *\n * &lt;!ELEMENT filename EMPTY&gt;\n * &lt;!ATTLIST filename value CDATA                 #REQUIRED&gt;\n * &lt;!ATTLIST filename case_sensitive (true|false) #IMPLIED&gt;\n *\n * &lt;!ELEMENT symlink EMPTY&gt;\n * &lt;!ATTLIST symlink value (true|false) #REQUIRED&gt;\n *\n * &lt;!ELEMENT hidden EMPTY&gt;\n * &lt;!ATTLIST hidden value (true|false) #REQUIRED&gt;\n *\n * &lt;!ELEMENT readable EMPTY&gt;\n * &lt;!ATTLIST readable value (true|false) #REQUIRED&gt;\n *\n * &lt;!ELEMENT writable EMPTY&gt;\n * &lt;!ATTLIST writable value (true|false) #REQUIRED&gt;\n *\n * &lt;!ELEMENT executable EMPTY&gt;\n * &lt;!ATTLIST executable value (true|false) #REQUIRED&gt;\n * </pre>\n *\n * @see AssociationReader\n * @see AssociationWriter\n * @author Nicolas Rinaudo\n */\ninterface AssociationsXmlConstants {\n    /** Root element. */\n    String ELEMENT_ROOT          = \"associations\";\n    /** Custom association definition element. */\n    String ELEMENT_ASSOCIATION   = \"association\";\n    String ELEMENT_MASK          = \"filename\";\n    String ELEMENT_IS_SYMLINK    = \"symlink\";\n    String ELEMENT_IS_HIDDEN     = \"hidden\";\n    String ELEMENT_IS_READABLE   = \"readable\";\n    String ELEMENT_IS_WRITABLE   = \"writable\";\n    String ELEMENT_IS_EXECUTABLE = \"executable\";\n\n    /** Name of the attribute containing the alias of the command to execute in this association. */\n    String ATTRIBUTE_COMMAND        = \"command\";\n    String ATTRIBUTE_VALUE          = \"value\";\n    String ATTRIBUTE_CASE_SENSITIVE = \"case_sensitive\";\n    String VALUE_TRUE               = \"true\";\n    String VALUE_FALSE              = \"false\";\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/command/Command.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.command;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.util.FileSet;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Compiled shell commands.\n * <p>\n * A command is composed of three elements:\n * <ul>\n *   <li>An {@link #getAlias() alias}, used to identify the command through the application.</li>\n *   <li>A {@link #getCommand() command}, which is what will be executed by the instance of <code>Command</code>.</li>\n *   <li>\n *     A {@link #getType() type}, which can be any of {@link CommandType#SYSTEM_COMMAND system} (invisible and immutable),\n *     {@link CommandType#INVISIBLE_COMMAND invisible} (invisible and mutable) or {@link CommandType#NORMAL_COMMAND} (visible and mutable).\n *   </li>\n * </ul>\n * <p>\n * The basic command syntax is fairly simple:\n * <ul>\n *  <li>Any non-escaped <code>\\</code> character will escape the following character and be removed from the tokens.</li>\n *  <li>Any non-escaped <code>\"</code> character will escape all characters until the next occurrence of <code>\"</code>, except for <code>\\</code>.</li>\n *  <li>Non-escaped space characters are used as token separators.</li>\n * </ul>\n * It is important to remember that <code>\"</code> characters are <b>not</b> removed from the resulting tokens.\n * <p>\n * It's also possible to include keywords in a command:\n * <ul>\n *  <li><code>$f</code> is replaced by a file's full path.</li>\n *  <li><code>$n</code> is replaced by a file's name.</li>\n *  <li><code>$e</code> is replaced by a file's extension.</li>\n *  <li><code>$N</code> is replaced by a file's name without its extension.</li>\n *  <li><code>$p</code> is replaced by a file's parent's path.</li>\n *  <li><code>$j</code> is replaced by the path of the folder in which the JVM was started.</li>\n * </ul>\n * <p>\n * Once a <code>Command</code> instance has been retrieved, execution tokens can be retrieved through the\n * {@link #getTokens(AbstractFile)} method. This will return a tokenized version of the command and replace any\n * keyword by the corresponding file value . It's also possible to skip keyword replacement through the {@link #getTokens()} method.\n * <p>\n * A command's executable tokens are typically meant to be used with {@link com.mucommander.process.ProcessRunner#execute(String[], AbstractFile)}\n * in order to generate instances of {@link com.mucommander.process.AbstractProcess}.\n *\n * @author Nicolas Rinaudo\n * @see CommandManager\n * @see com.mucommander.process.ProcessRunner\n * @see com.mucommander.process.AbstractProcess\n */\npublic class Command implements Comparable<Command> {\n    /**\n     * Header of replacement keywords.\n     */\n    private static final char KEYWORD_HEADER = '$';\n    /**\n     * Instances of this keyword will be replaced by the file's full path.\n     */\n    private static final char KEYWORD_PATH = 'f';\n    /**\n     * Instances of this keyword will be replaced by the file's name.\n     */\n    private static final char KEYWORD_NAME = 'n';\n    /**\n     * Instances of this keyword will be replaced by the file's parent directory.\n     */\n    private static final char KEYWORD_PARENT = 'p';\n    /**\n     * Instances of this keyword will be replaced by the JVM's current directory.\n     */\n    private static final char KEYWORD_VM_PATH = 'j';\n    /**\n     * Instances of this keyword will be replaced by the file's extension.\n     */\n    private static final char KEYWORD_EXTENSION = 'e';\n    /**\n     * Instances of this keyword will be replaced by the file's name without its extension.\n     */\n    private static final char KEYWORD_NAME_WITHOUT_EXTENSION = 'b';\n\n\n    /**\n     * Command's alias.\n     */\n    private final String alias;\n    /**\n     * Original command.\n     */\n    private final String command;\n    /**\n     * Name used to display the command to users.\n     */\n    private final String displayName;\n    /**\n     * Command type.\n     */\n    private final CommandType type;\n    /**\n     * Filemask\n     */\n    private final String fileMask;\n\n\n    /**\n     * Creates a new command.\n     *\n     * @param alias       alias of the command.\n     * @param command     command that will be executed.\n     * @param type        type of the command.\n     * @param displayName name of the command as seen by users (if <code>null</code>, defaults to <code>alias</code>).\n     * @param fileMask    mask for files specification\n     */\n    public Command(String alias, String command, CommandType type, String displayName, String fileMask) {\n        this.alias = alias;\n        this.type = type;\n        this.displayName = displayName;\n        this.command = command;\n        this.fileMask = fileMask;\n    }\n\n    /**\n     * Creates a new command.\n     * <p>\n     * This is a convenience constructor and is strictly equivalent to calling\n     * <code>\n     * {@link #Command(String, String, CommandType, String, String)\n     * Command(}alias, command, {@link CommandType#NORMAL_COMMAND}, null, null)\n     * </code>.\n     *\n     * @param alias   alias of the command.\n     * @param command command that will be executed.\n     */\n    public Command(String alias, String command) {\n        this(alias, command, CommandType.NORMAL_COMMAND, null, null);\n    }\n\n    /**\n     * Creates a new command.\n     * <p>\n     * This is a convenience constructor and is strictly equivalent to calling\n     * <code>{@link #Command(String, String, CommandType, String, String) Command(}alias, command, type, null, null)</code>.\n     *\n     * @param alias   alias of the command.\n     * @param command command that will be executed.\n     * @param type    type of the command.\n     */\n    public Command(String alias, String command, CommandType type) {\n        this(alias, command, type, null, null);\n    }\n\n\n    public Command(Command cmd) {\n        this.alias = cmd.getAlias();\n        this.type = cmd.getType();\n        this.displayName = cmd.getDisplayName();\n        this.command = cmd.getCommand();\n        this.fileMask = cmd.getFileMask();\n    }\n\n\n    /**\n     * Returns this command's tokens without performing keyword substitution.\n     *\n     * @return this command's tokens without performing keyword substitution.\n     */\n    public synchronized String[] getTokens() {\n        return getTokens(command, (AbstractFile[]) null);\n    }\n\n    /**\n     * Returns this command's tokens, replacing keywords by the corresponding values from the specified file.\n     *\n     * @param file file from which to retrieve keyword substitution values.\n     * @return this command's tokens, replacing keywords by the corresponding values from the specified file.\n     */\n    public synchronized String[] getTokens(AbstractFile file) {\n        return getTokens(command, file);\n    }\n\n    /**\n     * Returns this command's tokens, replacing keywords by the corresponding values from the specified fileset.\n     *\n     * @param files files from which to retrieve keyword substitution values.\n     * @return this command's tokens, replacing keywords by the corresponding values from the specified fileset.\n     */\n    public synchronized String[] getTokens(FileSet files) {\n        return getTokens(command, files);\n    }\n\n    /**\n     * Returns this command's tokens, replacing keywords by the corresponding values from the specified files.\n     *\n     * @param files files from which to retrieve keyword substitution values.\n     * @return this command's tokens, replacing keywords by the corresponding values from the specified files.\n     */\n    public synchronized String[] getTokens(AbstractFile[] files) {\n        return getTokens(command, files);\n    }\n\n    /**\n     * Returns the specified command's tokens without performing keyword substitution.\n     *\n     * @param command command to tokenize.\n     * @return the specified command's tokens without performing keyword substitution.\n     */\n    public static String[] getTokens(String command) {\n        return getTokens(command, (AbstractFile[]) null);\n    }\n\n    /**\n     * Returns the specified command's tokens after replacing keywords by the corresponding values from the specified file.\n     *\n     * @param command command to tokenize.\n     * @param file    file from which to retrieve keyword substitution values.\n     * @return the specified command's tokens after replacing keywords by the corresponding values from the specified file.\n     */\n    public static String[] getTokens(String command, AbstractFile file) {\n        return getTokens(command, new AbstractFile[]{file});\n    }\n\n    /**\n     * Returns the specified command's tokens after replacing keywords by the corresponding values from the specified fileset.\n     *\n     * @param command command to tokenize.\n     * @param files   file from which to retrieve keyword substitution values.\n     * @return the specified command's tokens after replacing keywords by the corresponding values from the specified fileset.\n     */\n    public static String[] getTokens(String command, FileSet files) {\n        return getTokens(command, files.toArray(new AbstractFile[0]));\n    }\n\n    /**\n     * Returns the specified command's tokens after replacing keywords by the corresponding values from the specified files.\n     *\n     * @param command command to tokenize.\n     * @param files   file from which to retrieve keyword substitution values.\n     * @return the specified command's tokens after replacing keywords by the corresponding values from the specified files.\n     */\n    public static String[] getTokens(String command, AbstractFile[] files) {\n        List<String> tokens = new ArrayList<>();                                // All tokens.\n        command = command.trim();\n        StringBuilder currentToken = new StringBuilder(command.length());       // Buffer for the current token.\n        char[] buffer = command.toCharArray();                                  // All the characters that compose command.\n        boolean isInQuotes = false;                                             // Whether we're currently within quotes or not.\n\n        // Parses the command.\n        for (int i = 0; i < command.length(); i++) {\n            // Quote escaping: toggle isInQuotes.\n            if (buffer[i] == '\\\"') {\n                currentToken.append(buffer[i]);\n                isInQuotes = !isInQuotes;\n            }\n\n            // Backslash escaping: the next character is not analyzed.\n            else if (buffer[i] == '\\\\') {\n                if (i + 1 != command.length()) {\n                    currentToken.append(buffer[++i]);\n                }\n            }\n\n            // Whitespace: end of token if we're not between quotes.\n            else if (buffer[i] == ' ' && !isInQuotes) {\n                // Skips un-escaped blocks of spaces.\n                while (i + 1 < command.length() && buffer[i + 1] == ' ') {\n                    i++;\n                }\n\n                // Stores the current token.\n                tokens.add(currentToken.toString());\n                currentToken.setLength(0);\n            }\n\n            // Keyword: perform keyword substitution.\n            else if (buffer[i] == KEYWORD_HEADER) {\n                // Skips keyword replacement if we're not interested in it.\n                if (files == null) {\n                    currentToken.append(KEYWORD_HEADER);\n                }\n\n                // If this is the last character, append it.\n                else if (++i == buffer.length)\n                    currentToken.append(KEYWORD_HEADER);\n\n                    // If we've found a legal keyword, perform keyword replacement\n                else if (isLegalKeyword(buffer[i])) {\n                    // Deals with the first file.\n                    currentToken.append(getKeywordReplacement(buffer[i], files[0]));\n\n                    // $j is a special case, we only ever insert it once.\n                    if (buffer[i] != KEYWORD_VM_PATH) {\n                        // If we're not between quotes and there's more than one file,\n                        // each file will be in its own token.\n                        if (!isInQuotes && files.length != 1) {\n                            tokens.add(currentToken.toString());\n                            currentToken.setLength(0);\n                        }\n\n                        // Deals with all subsequent files:\n                        // - if we're in quotes, separates each files by a space.\n                        // - if we're not in quotes, each new file is its own token.\n                        for (int j = 1; j < files.length; j++) {\n                            if (isInQuotes) {\n                                currentToken.append(' ');\n                                currentToken.append(getKeywordReplacement(buffer[i], files[j]));\n                            }\n\n                            // When not in quotes, the last file is the beginning of a new token\n                            // rather than a single one.\n                            else if (j != files.length - 1)\n                                tokens.add(getKeywordReplacement(buffer[i], files[j]));\n                            else\n                                currentToken.append(getKeywordReplacement(buffer[i], files[j]));\n                        }\n                    }\n                }\n\n                // If we've found an illegal keyword, ignore it.\n                else {\n                    currentToken.append(KEYWORD_HEADER);\n                    currentToken.append(buffer[i]);\n                }\n            }\n\n            // Nothing special about this character.\n            else {\n                currentToken.append(buffer[i]);\n            }\n        }\n\n        // Adds a possible last token.\n        if (!currentToken.isEmpty()) {\n            tokens.add(currentToken.toString());\n        }\n\n        // Empty commands are returned as an empty token rather than an empty array.\n        if (tokens.isEmpty()) {\n            return new String[]{\"\"};\n        }\n\n        return tokens.toArray(new String[0]);\n    }\n\n    /**\n     * Returns whether this command contains keywords referencing selected file.\n     * <p>\n     * Returns true if command contains keywords referencing selected file, e.g. $f,$n,$p,$e,$b.\n     * Returns false otherwise, e.g. $j, $xyz, etc.\n     *\n     * @return whether this command contains keywords referencing selected file.\n     */\n    public synchronized boolean hasSelectedFileKeyword() {\n        String[] tokens = getTokens();\n        for (String token : tokens) {\n            if (token.length() >= 2 && token.charAt(0) == KEYWORD_HEADER) {\n                char ch2 = token.charAt(1);\n                if (ch2 == KEYWORD_PATH || ch2 == KEYWORD_NAME || ch2 == KEYWORD_EXTENSION || ch2 == KEYWORD_NAME_WITHOUT_EXTENSION ||\n                        ch2 == KEYWORD_PARENT) {\n                    return true;\n                }\n            }\n        }\n        // No token with file referencing keyword found\n        return false;\n    }\n\n    /**\n     * Returns <code>true</code> if the specified character is a legal keyword.\n     *\n     * @param keyword character to check.\n     * @return <code>true</code> if the specified character is a legal keyword, <code>false</code> otherwise.\n     */\n    private static boolean isLegalKeyword(char keyword) {\n        return keyword == KEYWORD_PATH || keyword == KEYWORD_NAME || keyword == KEYWORD_PARENT ||\n                keyword == KEYWORD_VM_PATH || keyword == KEYWORD_EXTENSION || keyword == KEYWORD_NAME_WITHOUT_EXTENSION;\n    }\n\n    /**\n     * Gets the value from <code>file</code> that should be used to replace <code>keyword</code>.\n     *\n     * @param keyword character to replace.\n     * @param file    file from which to retrieve the replacement value.\n     * @return the requested replacement value.\n     */\n    private static String getKeywordReplacement(char keyword, AbstractFile file) {\n        return switch (keyword) {\n            case KEYWORD_PATH -> file.getAbsolutePath();\n            case KEYWORD_NAME -> file.getName();\n            case KEYWORD_PARENT -> {\n                AbstractFile parentFile = file.getParent();\n                yield parentFile == null ? \"\" : parentFile.getAbsolutePath();\n            }\n            case KEYWORD_VM_PATH -> new File(System.getProperty(\"user.dir\")).getAbsolutePath();\n            case KEYWORD_EXTENSION -> {\n                String extension = file.getExtension();\n                yield extension != null ? extension : \"\";\n            }\n            case KEYWORD_NAME_WITHOUT_EXTENSION -> file.getNameWithoutExtension();\n            default -> throw new IllegalArgumentException();\n        };\n    }\n\n\n    /**\n     * Returns the original, un-tokenized command.\n     *\n     * @return the original, un-tokenized command.\n     */\n    public synchronized String getCommand() {\n        return command;\n    }\n\n    /**\n     * Returns this command's alias.\n     *\n     * @return this command's alias.\n     */\n    public synchronized String getAlias() {\n        return alias;\n    }\n\n    /**\n     * Returns the command's type.\n     *\n     * @return the command's type.\n     */\n    public synchronized CommandType getType() {\n        return type;\n    }\n\n    public synchronized String getFileMask() {\n        return fileMask;\n    }\n\n    /**\n     * Returns the command's display name.\n     * <p>\n     * If it hasn't been set, returns this command's alias.\n     *\n     * @return the command's display name.\n     */\n    public synchronized String getDisplayName() {\n        return displayName != null ? displayName : alias;\n    }\n\n    /**\n     * Returns <code>true</code> if the command's display name has been set.\n     *\n     * @return <code>true</code> if the command's display name has been set, <code>false</code> otherwise.\n     */\n    synchronized boolean isDisplayNameSet() {\n        return displayName != null;\n    }\n\n    @Override\n    public int hashCode() {\n        int hashCode;\n\n        hashCode = alias.hashCode();\n        hashCode = hashCode * 31 + command.hashCode();\n        hashCode = hashCode * 31 + getDisplayName().hashCode();\n        hashCode = hashCode * 31 + type.hashCode();\n\n        return hashCode;\n    }\n\n    @Override\n    public boolean equals(Object object) {\n        if (!(object instanceof Command cmd)) {\n            return false;\n        }\n        return command.equals(cmd.command) && alias.equals(cmd.alias) && type == cmd.type &&\n                getDisplayName().equals(cmd.getDisplayName());\n    }\n\n    @Override\n    public int compareTo(@NotNull Command command) {\n        int buffer = getDisplayName().compareTo(command.getDisplayName());\n\n        if (buffer != 0) {\n            return buffer;\n        }\n        if ((buffer = getAlias().compareTo(command.getAlias())) != 0) {\n            return buffer;\n        }\n        return this.command.compareTo(command.command);\n    }\n\n\n    @Override\n    public String toString() {\n        return alias + (displayName == null ? \"\" : \":\" + displayName) + \":\" + command + (fileMask != null ? \"[\" + fileMask + \"]\" : \"\");\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/command/CommandAssociation.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.command;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.filter.FileFilter;\n\n/**\n * Associates a command to a set of file filters.\n * @author Nicolas Rinaudo\n */\nclass CommandAssociation {\n    /** Command associated to this file name IMAGE_FILTER. */\n    private Command    command;\n    private FileFilter fileFilter;\n\n\n\n    /**\n     * Creates a new <code>CommandAssociation</code>.\n     * @param command command that must be executed if the association is matched.\n     * @param filter  IMAGE_FILTER that files must match in order to be taken into account by the association.\n     */\n    CommandAssociation(Command command, FileFilter filter) {\n        this.command    = command;\n        this.fileFilter = filter;\n    }\n\n    /**\n     * Returns <code>true</code> if the specified file matches the association.\n     * @param  file file to match against the association.\n     * @return      <code>true</code> if the specified file matches the association, <code>false</code> otherwise.\n     */\n    public boolean accept(AbstractFile file) {return fileFilter.match(file);}\n\n    // - Command retrieval -----------------------------------------------------\n    // -------------------------------------------------------------------------\n    /**\n     * Returns the command used in the association.\n     * @return the command used in the association.\n     */\n    public Command getCommand() {return command;}\n\n    public FileFilter getFilter() {return fileFilter;}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/command/CommandBuilder.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.command;\n\n/**\n * Used to explore a list of commands in a format independent fashion.\n * <p>\n * Different classes, such as {@link CommandManager} or {@link CommandReader}, know how to\n * generate a list of commands - from instances loaded in memory or from a file, for example.\n * Implementing <code>CommandBuilder</code> allows classes to query these lists regardless of\n * their source.\n *\n * <p>\n * Instances of <code>CommandBuilder</code> can rely on their methods to be called in the proper order,\n * and on both their {@link #startBuilding()} and {@link #endBuilding()} methods to be called. Classes that\n * interact with such instances must make sure this contract is respected.\n *\n * <p>\n * A (fairly useless) implementation might look like:\n * <pre>\n * public class CommandPrinter implements CommandBuilder {\n *    public void startBuilding() {System.out.println(\"Beginning command list building...\");}\n *\n *    public void endBuilding() {System.out.println(\"Done.\");}\n *\n *    public void addCommand(Command command) throws CommandException {\n *        System.out.println(\" - creating command '\" + command.getCommand() + \"' with alias '\" + command.getAlias() + \"'\");\n *    }\n * }\n * </pre>\n * Passing an instance of <code>CommandPrinter</code> to {@link CommandManager#buildCommands(CommandBuilder, CommandType)}\n * will result in something like:\n * <pre>\n * Beginning command list building...\n * - creating command 'open $f' with alias 'open'\n * - creating command 'open -a Safari $f' with alias 'openURL'\n * Done.\n * </pre>\n *\n * @author Nicolas Rinaudo\n * @see    CommandReader\n * @see    CommandManager#buildCommands(CommandBuilder, CommandType)\n */\npublic interface CommandBuilder {\n    /**\n     * Notifies the builder that command building is about to start.\n     * @throws CommandException if an error occurs.\n     */\n    void startBuilding() throws CommandException;\n\n    /**\n     * Notifies the builder that command building is finished.\n     * @throws CommandException if an error occurs.\n     */\n    void endBuilding() throws CommandException;\n\n    /**\n     * Notifies the builder that a new command has been found.\n     * @param  command          command that has been found.\n     * @throws CommandException if an error occurs.\n     */\n    void addCommand(Command command) throws CommandException;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/command/CommandException.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.command;\n\n/**\n * Exception thrown when errors occur while building custom commands.\n * @author Nicolas Rinaudo\n */\npublic class CommandException extends Exception {\n    /**\n     * Builds a new exception.\n     */\n    public CommandException() {super();}\n\n    /**\n     * Builds a new exception with the specified message.\n     * @param message exception's message.\n     */\n    public CommandException(String message) {super(message);}\n\n    /**\n     * Builds a new exception with the specified cause.\n     * @param cause exception's cause.\n     */\n    public CommandException(Throwable cause) {super(cause);}\n\n    /**\n     * Builds a new exception with the specified message and cause.\n     * @param message exception's message.\n     * @param cause   exception's cause.\n     */\n    public CommandException(String message, Throwable cause) {super(message, cause);}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/command/CommandManager.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.command;\r\n\r\nimport java.io.File;\r\nimport java.io.FileNotFoundException;\r\nimport java.io.IOException;\r\nimport java.io.InputStream;\r\nimport java.util.*;\r\n\r\nimport com.mucommander.commons.file.filter.*;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport com.mucommander.PlatformManager;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileFactory;\r\nimport com.mucommander.commons.file.PermissionTypes;\r\nimport com.mucommander.io.backup.BackupInputStream;\r\nimport com.mucommander.io.backup.BackupOutputStream;\r\n\r\n/**\r\n * Manages custom commands and associations.\r\n * @author Nicolas Rinaudo\r\n */\r\npublic class CommandManager implements CommandBuilder {\r\n\tprivate static Logger logger;\r\n\t\r\n    // - Built-in commands -----------------------------------------------------\r\n    // -------------------------------------------------------------------------\r\n    /** Alias for the system file opener. */\r\n    public static final String FILE_OPENER_ALIAS           = \"open\";\r\n    /** Alias for the system URL opener. */\r\n    public static final String URL_OPENER_ALIAS            = \"openURL\";\r\n    /** Alias for the system file manager. */\r\n    public static final String FILE_MANAGER_ALIAS          = \"openFM\";\r\n    /** Alias for the system executable file opener. */\r\n    public static final String EXE_OPENER_ALIAS            = \"openEXE\";\r\n    /** Alias for the default text viewer. */\r\n    public static final String VIEWER_ALIAS                = \"view\";\r\n    /** Alias for the default text editor. */ \r\n    public static final String EDITOR_ALIAS                = \"edit\";\r\n    /** Alias for the default command prompt. */\r\n    public final static String CMD_OPENER_ALIAS            = \"openCmd\";\r\n\r\n\r\n\r\n    // - Self-open command -----------------------------------------------------\r\n    // -------------------------------------------------------------------------\r\n    /** Alias of the 'init as executable' command. */\r\n    private static final String  RUN_AS_EXECUTABLE_ALIAS   = \"execute\";\r\n    /** Command used to init a file as an executable. */\r\n    private static final Command RUN_AS_EXECUTABLE_COMMAND = new Command(RUN_AS_EXECUTABLE_ALIAS, \"$f\", CommandType.SYSTEM_COMMAND);\r\n\r\n\r\n\r\n    // - Association definitions -----------------------------------------------\r\n    // -------------------------------------------------------------------------\r\n    /** System dependent file associations. */\r\n    private static final List<CommandAssociation> systemAssociations = new ArrayList<>();\r\n    /** All known file associations. */\r\n    private static final List<CommandAssociation> associations = new ArrayList<>();\r\n    /** Path to the custom association file, <code>null</code> if the default one should be used. */\r\n    private static       AbstractFile             associationFile;\r\n    /** Whether the associations were modified since the last time they were saved. */\r\n    private static       boolean                  wereAssociationsModified;\r\n    /** Default name of the association XML file. */\r\n    private static final String                   DEFAULT_ASSOCIATION_FILE_NAME = \"associations.xml\";\r\n\r\n\r\n\r\n    // - Commands definition ---------------------------------------------------\r\n    // -------------------------------------------------------------------------\r\n\r\n    /** Default name of the custom commands file. */\r\n    private static final String DEFAULT_COMMANDS_FILE_NAME = \"commands.xml\";\r\n\r\n    /** All known commands. */\r\n    private static Map<String, List<Command>> commands = new HashMap<>();\r\n    /** Path to the custom commands XML file, <code>null</code> if the default one should be used. */\r\n    private static       AbstractFile         commandsFile;\r\n    /** Whether the custom commands have been modified since the last time they were saved. */\r\n    private static       boolean              wereCommandsModified;\r\n    /** Default command used when no other command is found for a specific file type. */\r\n    private static       Command              defaultCommand;\r\n\r\n\r\n    /**\r\n     * Prevents instances of CommandManager from being created.\r\n     */\r\n    private CommandManager() {}\r\n\r\n\r\n\r\n    // - Command handling ------------------------------------------------------\r\n    // -------------------------------------------------------------------------\r\n    /**\r\n     * Returns the tokens that compose the command that must be executed to open the specified file.\r\n     * <p>\r\n     * This is a convenience method and is strictly equivalent to calling\r\n     * <code>{@link #getTokensForFile(AbstractFile,boolean) getTokensForFile(}file, true)</code>.\r\n     *\r\n     * @param file file for which the opening command's tokens must be returned.\r\n     * @return the tokens that compose the command that must be executed to open the specified file.\r\n     */\r\n    public static String[] getTokensForFile(AbstractFile file) {\r\n        return getTokensForFile(file, true);\r\n    }\r\n\r\n    /**\r\n     * Returns the tokens that compose the command that must be executed to open the specified file.\r\n     * @param  file         file for which the opening command's tokens must be returned.\r\n     * @param  allowDefault whether to use the default command if none was found to match the specified file.\r\n     * @return              the tokens that compose the command that must be executed to open the specified file, <code>null</code> if not found.\r\n     */\r\n    public static String[] getTokensForFile(AbstractFile file, boolean allowDefault) {\r\n        Command command = getCommandForFile(file, allowDefault);\r\n        return command == null ? null : command.getTokens(file);\r\n    }\r\n\r\n    /**\r\n     * Returns the command that must be executed to open the specified file.\r\n     * <p>\r\n     * This is a convenience method and is stricly equivalent to calling\r\n     * <code>{@link #getCommandForFile(AbstractFile,boolean) getCommandForFile(}file, true)</code>.\r\n     *\r\n     * @param  file file for which the opening command must be returned.\r\n     * @return      the command that must be executed to open the specified file.\r\n     */\r\n    public static Command getCommandForFile(AbstractFile file) {\r\n        return getCommandForFile(file, true);\r\n    }\r\n\r\n    private static Command getCommandForFile(AbstractFile file, List<CommandAssociation> associations) {\r\n        for (CommandAssociation association : associations) {\r\n            if (association.accept(file))\r\n                return association.getCommand();\r\n        }\r\n        return null;\r\n    }\r\n\r\n    /**\r\n     * Returns the command that must be executed to open the specified file.\r\n     * @param  file         file for which the opening command must be returned.\r\n     * @param  allowDefault whether to use the default command if none was found to match the specified file.\r\n     * @return              the command that must be executed to open the specified file, <code>null</code> if not found.\r\n     */\r\n    public static Command getCommandForFile(AbstractFile file, boolean allowDefault) {\r\n        Command command  = getCommandForFile(file, associations);\r\n        // Goes through all known associations and checks whether file matches any.\r\n        if (command != null) {\r\n            return command;\r\n        }\r\n\r\n        // Goes through all system associations and checks whether file matches any.\r\n        command = getCommandForFile(file, systemAssociations);\r\n        if (command != null) {\r\n            return command;\r\n        }\r\n\r\n        // We haven't found a command explicitly associated with 'file', but we might have a generic file opener\r\n        if (defaultCommand != null) {\r\n            return defaultCommand;\r\n        }\r\n\r\n        // We don't have a generic file opener, return the 'self execute' command if we're allowed.\r\n        if (allowDefault) {\r\n            return RUN_AS_EXECUTABLE_COMMAND;\r\n        }\r\n        return null;\r\n    }\r\n\r\n    /**\r\n     * Returns a sorted collection of all registered commands.\r\n     * @return a sorted collection of all registered commands.\r\n     */\r\n    public static Collection<Command> commands() {\r\n        // Copy the registered commands to a new list\r\n    \tList<Command> list = new ArrayList<>();\r\n        for (List<Command> lst : commands.values()) {\r\n            list.addAll(lst);\r\n        }\r\n        Collections.sort(list);\r\n        return list;\r\n    }\r\n\r\n    /**\r\n     * Returns the command associated with the specified alias.\r\n     * @param  alias alias whose associated command should be returned.\r\n     * @return       the command associated with the specified alias if found, <code>null</code> otherwise.\r\n     */\r\n    public static Command getCommandForAlias(String alias, AbstractFile file) {\r\n        List<Command> list = commands.get(alias);\r\n        if (list == null || list.isEmpty()) {\r\n            return null;\r\n        }\r\n        // if we have command with specified filemask then return it\r\n        for (Command cmd : list) {\r\n            if (checkFileMask(cmd, file)) {\r\n                return cmd;\r\n            }\r\n        }\r\n        // else if we have command with empty filemask (default command) then return it\r\n        for (Command cmd : list) {\r\n            String fileMask = cmd.getFileMask();\r\n            if (fileMask == null || fileMask.isEmpty()) {\r\n                return cmd;\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    /**\r\n     *\r\n     * @param cmd command\r\n     * @param file file to check\r\n     * @return true if file corresponds to command filemask\r\n     */\r\n    public static boolean checkFileMask(Command cmd, AbstractFile file) {\r\n        String fileMask = cmd.getFileMask();\r\n        if (fileMask == null || fileMask.isEmpty()) {\r\n            return false;\r\n        }\r\n        String[] split = fileMask.split(\",\");\r\n        for (String aSplit : split) {\r\n            String mask = aSplit.trim().toLowerCase();\r\n            if (mask.isEmpty()) {\r\n                continue;\r\n            }\r\n            WildcardFileFilter filter = new WildcardFileFilter(mask);\r\n            if (filter.accept(file)) {\r\n                return true;\r\n            }\r\n        }\r\n\r\n        return false;\r\n    }\r\n\r\n    private static void setDefaultCommand(Command command) {\r\n        if (defaultCommand == null && command.getAlias().equals(FILE_OPENER_ALIAS)) {\r\n        \tgetLogger().debug(\"Registering '\" + command.getCommand() + \"' as default command.\");\r\n            defaultCommand = command;\r\n        }\r\n    }\r\n\r\n    private static void registerCommand(Command command, boolean mark)  {\r\n        // Registers the command and marks command as having been modified.\r\n        setDefaultCommand(command);\r\n\r\n        getLogger().debug(\"Registering '\" + command.getCommand() + \"' as '\" + command.getAlias() + \"'\");\r\n        final String alias = command.getAlias();\r\n        if (!commands.containsKey(alias)) {\r\n            commands.put(alias, new ArrayList<>());\r\n        }\r\n        commands.get(alias).add(command);\r\n        if (mark) {\r\n            wereCommandsModified = true;\r\n        }\r\n//        Command oldCommand = commands.put(command.getAlias(), command);\r\n//        if (mark && !command.equals(oldCommand)) {\r\n//            wereCommandsModified = true;\r\n//        }\r\n    }\r\n\r\n    public static void registerDefaultCommand(Command command) throws CommandException {\r\n        registerCommand(command, false);\r\n    }\r\n\r\n    /**\r\n     * Registers the specified command at the end of the command list.\r\n     * @param  command          command to register.\r\n     */\r\n    public static void registerCommand(Command command) {\r\n        registerCommand(command, true);\r\n    }\r\n\r\n\r\n\r\n    // - Associations handling -------------------------------------------------\r\n    // -------------------------------------------------------------------------\r\n\r\n    /**\r\n     * Registers the specified association.\r\n     * @param  command          command to execute when the association is matched.\r\n     * @param  filter           file filters that a file must match to be accepted by the association.\r\n     * @throws CommandException if an error occurs.\r\n     */\r\n    public static void registerAssociation(String command, FileFilter filter) throws CommandException {\r\n        associations.add(createAssociation(command, filter));\r\n    }\r\n    \r\n    private static CommandAssociation createAssociation(String cmd, FileFilter filter) throws CommandException {\r\n        Command command = getCommandForAlias(cmd, null);\r\n\r\n        if (command == null) {\r\n        \tgetLogger().debug(\"Failed to create association as '\" + command + \"' is not known.\");\r\n            throw new CommandException(cmd + \" not found\");\r\n        }\r\n\r\n        return new CommandAssociation(command, filter);\r\n    }\r\n\r\n    public static void registerDefaultAssociation(String command, FileFilter filter) throws CommandException {\r\n        systemAssociations.add(createAssociation(command, filter));\r\n    }\r\n\r\n\r\n\r\n    // - Command builder code --------------------------------------------------\r\n    // -------------------------------------------------------------------------\r\n    /**\r\n     * This method is public as an implementation side effect and must not be called directly.\r\n     */\r\n    public void addCommand(Command command) {\r\n        registerCommand(command, false);\r\n    }\r\n\r\n    /**\r\n     * Passes all known custom commands to the specified builder.\r\n     * <p>\r\n     * This method guarantees that the builder's {@link CommandBuilder#startBuilding() startBuilding()} and\r\n     * {@link CommandBuilder#endBuilding() endBuilding()} methods will both be called even if an error occurs.\r\n     * If that happens however, it is entirely possible that not all commands will be passed to\r\n     * the builder.\r\n     *\r\n     * @param  builder          object that will receive commands list building messages.\r\n     * @param type              if not null then build only commands with specified type\r\n     * @throws CommandException if anything goes wrong.\r\n     */\r\n    public static void buildCommands(CommandBuilder builder, CommandType type) throws CommandException {\r\n        builder.startBuilding();\r\n\r\n        try {\r\n        \t// Goes through all the registered commands.\r\n        \tfor (Command command : commands()) {\r\n                if (type == null || command.getType() == type) {\r\n                    builder.addCommand(command);\r\n                }\r\n            }\r\n        } finally {\r\n            builder.endBuilding();\r\n        }\r\n    }\r\n\r\n\r\n\r\n    // - Associations building -------------------------------------------------\r\n    // -------------------------------------------------------------------------\r\n    private static void buildFilter(FileFilter filter, AssociationBuilder builder) throws CommandException {\r\n        // Filter on the file type.\r\n        if (filter instanceof AttributeFileFilter) {\r\n            AttributeFileFilter attributeFilter = (AttributeFileFilter)filter;\r\n\r\n            switch (attributeFilter.getAttribute()) {\r\n            case HIDDEN:\r\n                builder.setIsHidden(!attributeFilter.isInverted());\r\n                break;\r\n\r\n            case SYMLINK:\r\n                builder.setIsSymlink(!attributeFilter.isInverted());\r\n                break;\r\n            }\r\n        } else if (filter instanceof PermissionsFileFilter) {\r\n            PermissionsFileFilter permissionFilter = (PermissionsFileFilter)filter;\r\n\r\n            switch(permissionFilter.getPermission()) {\r\n            case PermissionTypes.READ_PERMISSION:\r\n                builder.setIsReadable(permissionFilter.getFilter());\r\n                break;\r\n\r\n            case PermissionTypes.WRITE_PERMISSION:\r\n                builder.setIsWritable(permissionFilter.getFilter());\r\n                break;\r\n\r\n            case PermissionTypes.EXECUTE_PERMISSION:\r\n                builder.setIsExecutable(permissionFilter.getFilter());\r\n                break;\r\n            }\r\n        } else if (filter instanceof RegexpFilenameFilter) {\r\n            RegexpFilenameFilter regexpFilter = (RegexpFilenameFilter)filter;\r\n\r\n            builder.setMask(regexpFilter.getRegularExpression(), regexpFilter.isCaseSensitive());\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Passes all known file associations to the specified builder.\r\n     * <p>\r\n     * This method guarantees that the builder's {@link AssociationBuilder#startBuilding() startBuilding()} and\r\n     * {@link AssociationBuilder#endBuilding() endBuilding()} methods will both be called even if an error occurs.\r\n     * If that happens however, it is entirely possible that not all associations will be passed to\r\n     * the builder.\r\n     *\r\n     * @param  builder          object that will receive association list building messages.\r\n     * @throws CommandException if anything goes wrong.\r\n     */\r\n    public static void buildAssociations(AssociationBuilder builder) throws CommandException {\r\n        builder.startBuilding();\r\n\r\n        // Goes through all the registered associations.\r\n        try {\r\n            for (CommandAssociation current : associations) {\r\n                builder.startAssociation(current.getCommand().getAlias());\r\n\r\n                FileFilter filter = current.getFilter();\r\n                if (filter instanceof ChainedFileFilter) {\r\n                    Iterator<FileFilter> filters = ((ChainedFileFilter)filter).getFileFilterIterator();\r\n                    while (filters.hasNext()) {\r\n                        buildFilter(filters.next(), builder);\r\n                    }\r\n                } else {\r\n                    buildFilter(filter, builder);\r\n                }\r\n\r\n                builder.endAssociation();\r\n            }\r\n        } finally {\r\n            builder.endBuilding();\r\n        }\r\n    }\r\n\r\n\r\n\r\n    // - Associations reading/writing ------------------------------------------\r\n    // -------------------------------------------------------------------------\r\n    /**\r\n     * Returns the path to the custom associations XML file.\r\n     * <p>\r\n     * This method cannot guarantee the file's existence, and it's up to the caller\r\n     * to deal with the fact that the user might not actually have created custom\r\n     * associations.\r\n     * <p>\r\n     * This method's return value can be modified through {@link #setAssociationFile(String)}.\r\n     * If this wasn't called, the default path will be used: {@link #DEFAULT_ASSOCIATION_FILE_NAME}\r\n     * in the {@link com.mucommander.PlatformManager#getPreferencesFolder() preferences} folder.\r\n     *\r\n     * @return the path to the custom associations XML file.\r\n     * @see    #setAssociationFile(String)\r\n     * @see    #loadAssociations()\r\n     * @see    #writeAssociations()\r\n     * @throws IOException if there was an error locating the default commands file.\r\n     */\r\n    public static AbstractFile getAssociationFile() throws IOException {\r\n        if (associationFile == null) {\r\n            return PlatformManager.getPreferencesFolder().getChild(DEFAULT_ASSOCIATION_FILE_NAME);\r\n        }\r\n        return associationFile;\r\n    }\r\n\r\n    /**\r\n     * Sets the path to the custom associations file.\r\n     * <p>\r\n     * This is a convenience method and is strictly equivalent to calling <code>setAssociationFile(FileFactory.getFile(file))</code>.\r\n     *\r\n     * @param  path                  path to the custom associations file.\r\n     * @throws FileNotFoundException if <code>file</code> is not accessible.\r\n     * @see    #getAssociationFile()\r\n     * @see    #loadAssociations()\r\n     * @see    #writeAssociations()\r\n     */\r\n    public static void setAssociationFile(String path) throws FileNotFoundException {\r\n        AbstractFile file = FileFactory.getFile(path);\r\n        if (file == null) {\r\n            setAssociationFile(new File(path));\r\n        } else {\r\n            setAssociationFile(file);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Sets the path to the custom associations file.\r\n     * <p>\r\n     * This is a convenience method and is strictly equivalent to calling <code>setAssociationFile(FileFactory.getFile(file.getAbsolutePath()))</code>.\r\n     *\r\n     * @param  file                  path to the custom associations file.\r\n     * @throws FileNotFoundException if <code>file</code> is not accessible.\r\n     * @see    #getAssociationFile()\r\n     * @see    #loadAssociations()\r\n     * @see    #writeAssociations()\r\n     */\r\n    public static void setAssociationFile(File file) throws FileNotFoundException {\r\n        setAssociationFile(FileFactory.getFile(file.getAbsolutePath()));\r\n    }\r\n\r\n    /**\r\n     * Sets the path to the custom associations file.\r\n     * @param  file                  path to the custom associations file.\r\n     * @throws FileNotFoundException if <code>file</code> is not accessible.\r\n     * @see    #getAssociationFile()\r\n     * @see    #loadAssociations()\r\n     * @see    #writeAssociations()\r\n     */\r\n    public static void setAssociationFile(AbstractFile file) throws FileNotFoundException {\r\n        if (file.isBrowsable()) {\r\n            throw new FileNotFoundException(\"Not a valid file: \" + file.getAbsolutePath());\r\n        }\r\n\r\n        associationFile = file;\r\n    }\r\n\r\n    /**\r\n     * Loads the custom associations XML File.\r\n     * <p>\r\n     * The command files will be loaded as a <i>backed-up file</i> (see {@link BackupInputStream}).\r\n     * Its format is described {@link AssociationsXmlConstants here}.\r\n     *\r\n     * @throws IOException if an IO error occurs.\r\n     * @throws CommandException thrown when errors occur while building custom commands\r\n     * @see                #writeAssociations()\r\n     * @see                #getAssociationFile()\r\n     * @see                #setAssociationFile(String)\r\n     */\r\n    public static void loadAssociations() throws IOException, CommandException {\r\n        AbstractFile file = getAssociationFile();\r\n        getLogger().debug(\"Loading associations from file: \" + file.getAbsolutePath());\r\n\r\n        // Tries to load the associations file.\r\n        // Associations are not considered to be modified by this. \r\n        //InputStream in = null;\r\n        try (InputStream in = new BackupInputStream(file)) {\r\n            AssociationReader.read(in, new AssociationFactory());\r\n        } finally {\r\n            wereAssociationsModified = false;\r\n        }\r\n    }\r\n\r\n    /**,\r\n     * Writes all registered associations to the custom associations file.\r\n     * <p>\r\n     * Data will be written to the path returned by {@link #getAssociationFile()}. Note, however,\r\n     * that this method will not actually do anything if the association list hasn't been modified\r\n     * since the last time it was saved.\r\n     * <p>\r\n     * The association files will be saved as a <i>backed-up file</i> (see {@link BackupOutputStream}).\r\n     * Its format is described {@link AssociationsXmlConstants here}.\r\n     *\r\n     * @throws IOException      if an I/O error occurs.\r\n     * @throws CommandException if an error occurs.\r\n     * @see                     #loadAssociations()\r\n     * @see                     #getAssociationFile()\r\n     * @see                     #setAssociationFile(String)\r\n     */\r\n    public static void writeAssociations() throws CommandException, IOException {\r\n        // Do not save the associations if they were not modified.\r\n        if (!wereAssociationsModified) {\r\n            getLogger().debug(\"Custom file associations not modified, skip saving.\");\r\n            return;\r\n        }\r\n        getLogger().debug(\"Writing associations to file: \" + getAssociationFile());\r\n\r\n        // Writes the associations.\r\n        try (BackupOutputStream out = new BackupOutputStream(getAssociationFile())) {\r\n            buildAssociations(new AssociationWriter(out));\r\n            wereAssociationsModified = false;\r\n        }\r\n    }\r\n\r\n\r\n\r\n    // - Commands reading/writing ----------------------------------------------\r\n    // -------------------------------------------------------------------------\r\n    /**\r\n     * Returns the path to the custom commands XML file.\r\n     * <p>\r\n     * This method cannot guarantee the file's existence, and it's up to the caller\r\n     * to deal with the fact that the user might not actually have created custom\r\n     * commands.\r\n     * <p>\r\n     * This method's return value can be modified through {@link #setCommandFile(String)}.\r\n     * If this wasn't called, the default path will be used: {@link #DEFAULT_COMMANDS_FILE_NAME}\r\n     * in the {@link com.mucommander.PlatformManager#getPreferencesFolder() preferences} folder.\r\n     *\r\n     * @return the path to the custom commands XML file.\r\n     * @see    #setCommandFile(String)\r\n     * @see    #loadCommands()\r\n     * @see    #writeCommands()\r\n     * @throws IOException if there was some error locating the default commands file.\r\n     */\r\n    public static AbstractFile getCommandFile() throws IOException {\r\n        if (commandsFile == null) {\r\n            return PlatformManager.getPreferencesFolder().getChild(DEFAULT_COMMANDS_FILE_NAME);\r\n        }\r\n        return commandsFile;\r\n    }\r\n\r\n    /**\r\n     * Sets the path to the custom commands file.\r\n     * <p>\r\n     * This is a convenience method and is strictly equivalent to calling <code>setCommandFile(FileFactory.getFile(file));</code>.\r\n     *\r\n     * @param  path                  path to the custom commands file.\r\n     * @throws FileNotFoundException if <code>file</code> is not accessible.\r\n     * @see    #getCommandFile()\r\n     * @see    #loadCommands()\r\n     * @see    #writeCommands()\r\n     */\r\n    public static void setCommandFile(String path) throws FileNotFoundException {\r\n        AbstractFile file = FileFactory.getFile(path);\r\n\r\n        if (file == null) {\r\n            setCommandFile(new File(path));\r\n        } else {\r\n            setCommandFile(file);\r\n        }\r\n    }\r\n        \r\n\r\n    /**\r\n     * Sets the path to the custom commands file.\r\n     * <p>\r\n     * This is a convenience method and is strictly equivalent to calling <code>setCommandFile(FileFactory.getFile(file.getAbsolutePath()));</code>.\r\n     *\r\n     * @param  file                  path to the custom commands file.\r\n     * @throws FileNotFoundException if <code>file</code> is not accessible.\r\n     * @see    #getCommandFile()\r\n     * @see    #loadCommands()\r\n     * @see    #writeCommands()\r\n     */\r\n    public static void setCommandFile(File file) throws FileNotFoundException {setCommandFile(FileFactory.getFile(file.getAbsolutePath()));}\r\n\r\n    /**\r\n     * Sets the path to the custom commands file.\r\n     * @param  file                  path to the custom commands file.\r\n     * @throws FileNotFoundException if <code>file</code> is not accessible.\r\n     * @see    #getCommandFile()\r\n     * @see    #loadCommands()\r\n     * @see    #writeCommands()\r\n     */\r\n    public static void setCommandFile(AbstractFile file) throws FileNotFoundException {\r\n        // Makes sure file can be used as a command file.\r\n        if (file.isBrowsable()) {\r\n            throw new FileNotFoundException(\"Not a valid file: \" + file.getAbsolutePath());\r\n        }\r\n\r\n        commandsFile = file;\r\n    }\r\n\r\n    /**\r\n     * Writes Normal registered commands to the custom commands file.\r\n     * <p>\r\n     * Data will be written to the path returned by {@link #getCommandFile()}. Note, however,\r\n     * that this method will not actually do anything if the command list hasn't been modified\r\n     * since the last time it was saved.\r\n     * <p>\r\n     * The command files will be saved as a <i>backed-up file</i> (see {@link BackupOutputStream}).\r\n     * Its format is described {@link CommandsXmlConstants here}.\r\n     *\r\n     * @throws IOException      if an I/O error occurs.\r\n     * @throws CommandException if an error occurs.\r\n     * @see                     #loadCommands()\r\n     * @see                     #getCommandFile()\r\n     * @see                     #setCommandFile(String)\r\n     */\r\n    public static void writeCommands() throws IOException, CommandException {\r\n        // Only saves the command if they were modified since the last time they were written.\r\n        if (!wereCommandsModified) {\r\n            getLogger().debug(\"Custom commands not modified, skip saving.\");\r\n            return;\r\n        }\r\n        getLogger().debug(\"Writing custom commands to file: \" + getCommandFile());\r\n\r\n        // Writes the commands.\r\n        try (BackupOutputStream out = new BackupOutputStream(getCommandFile())) {\r\n            buildCommands(new CommandWriter(out), CommandType.NORMAL_COMMAND);\r\n            wereCommandsModified = false;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Loads the custom commands XML File.\r\n     * <p>\r\n     * The command files will be loaded as a <i>backed-up file</i> (see {@link BackupInputStream}).\r\n     * Its format is described {@link CommandsXmlConstants here}.\r\n     *\r\n     * @throws IOException if an I/O error occurs.\r\n     * @see                #writeCommands()\r\n     * @see                #getCommandFile()\r\n     * @see                #setCommandFile(String)\r\n     */\r\n    public static void loadCommands() throws IOException, CommandException {\r\n        AbstractFile file = getCommandFile();\r\n        getLogger().debug(\"Loading custom commands from: \" + file.getAbsolutePath());\r\n\r\n        // Tries to load the commands file.\r\n        // Commands are not considered to be modified by this.\r\n        try (InputStream in = new BackupInputStream(file)) {\r\n            CommandReader.read(in, new CommandManager());\r\n        } finally {\r\n            wereCommandsModified = false;\r\n        }\r\n    }\r\n\r\n    /*\r\n    private static void registerDefaultCommand(String alias, String command, String display) {\r\n        if(getCommandForAlias(alias) == null) {\r\n            if(command != null) {\r\n                //                try {registerCommand(CommandParser.getCommand(alias, command, Command.SYSTEM_COMMAND, display));}\r\n                try {registerCommand(new Command(alias, command, Command.SYSTEM_COMMAND, display));}\r\n                catch(Exception e) {AppLogger.fine(\"Failed to register \" + command + \": \" + e.getMessage());}\r\n            }\r\n        }\r\n    }\r\n    */\r\n\r\n\r\n    // - Unused methods --------------------------------------------------------\r\n    // -------------------------------------------------------------------------\r\n    /**\r\n     * This method is public as an implementation side effect and must not be called directly.\r\n     */\r\n    public void startBuilding() {}\r\n\r\n    /**\r\n     * This method is public as an implementation side effect and must not be called directly.\r\n     */\r\n    public void endBuilding() {}\r\n\r\n\r\n    public static List<Command> getCommands(String type) {\r\n        if (!commands.containsKey(type)) {\r\n            commands.put(type, new ArrayList<>());\r\n        }\r\n        return commands.get(type);\r\n    }\r\n\r\n\r\n    /**\r\n     * Removes all user defined commands with Normal type.\r\n     * Called before updating list in editor dialog\r\n     */\r\n    public static void removeAllNormalCommands() {\r\n        for (String type : commands.keySet()) {\r\n            List<Command> list = commands.get(type);\r\n            list.removeIf(cmd -> cmd.getType() == CommandType.NORMAL_COMMAND);\r\n        }\r\n    }\r\n\r\n\r\n    private static Logger getLogger() {\r\n        if (logger == null) {\r\n            logger = LoggerFactory.getLogger(CommandManager.class);\r\n        }\r\n        return logger;\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/command/CommandReader.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.command;\n\nimport org.xml.sax.Attributes;\nimport org.xml.sax.SAXException;\nimport org.xml.sax.helpers.DefaultHandler;\n\nimport javax.xml.parsers.ParserConfigurationException;\nimport javax.xml.parsers.SAXParserFactory;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * Class used to parse custom commands XML files.\n * <p>\n * Command file parsing is done through the {@link #read(InputStream,CommandBuilder) read} method, which is\n * the only way to interact with this class.\n * <p>\n * Note that while this class knows how to read the content of a command XML file, its role is not to interpret it. This\n * is done by instances of {@link CommandBuilder}.\n *\n * @see    CommandsXmlConstants\n * @see    CommandBuilder\n * @see    CommandWriter\n * @author Nicolas Rinaudo\n */\npublic class CommandReader extends DefaultHandler implements CommandsXmlConstants {\n    /** Where to send building messages. */\n    private final CommandBuilder builder;\n\n\n    /**\n     * Creates a new command reader.\n     * @param b where to send custom command events.\n     */\n    private CommandReader(CommandBuilder b) {\n        builder = b;\n    }\n\n\n\n    /**\n     * Parses the content of the specified input stream.\n     * <p>\n     * This method will go through the specified input stream and notify the builder of any new command declaration it\n     * encounters. Note that parsing is done in a very lenient fashion, and perfectly invalid XML files might not raise\n     * an exception. This is not a flaw in the parser, and both allows muCommander to be error resilient and the commands\n     * file format to be extended without having to rewrite most of this code.\n     * <p>\n     * Note that even if an error occurs, both of the builder's {@link CommandBuilder#startBuilding()} and\n     * {@link CommandBuilder#endBuilding()} methods will still be called. Parsing will stop at the first error\n     * however, so while the builder is guaranteed to receive correct messages, it might not receive all declared\n     * commands.\n     *\n     * @param  in        where to read command data from.\n     * @param  b         where to send building events to.\n     * @throws IOException thrown if any error occurs.\n     */\n    public static void read(InputStream in, CommandBuilder b) throws CommandException, IOException {\n        b.startBuilding();\n        try {\n            SAXParserFactory.newInstance().newSAXParser().parse(in, new CommandReader(b));\n        } catch(ParserConfigurationException | SAXException e) {\n            throw new CommandException(e);\n        } finally {\n            b.endBuilding();\n        }\n    }\n\n\n\n    /**\n     * This method is public as an implementation side effect and should not be called directly.\n     */\n    @Override\n    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {\n        // New custom command declaration.\n        if (qName.equals(ELEMENT_COMMAND)) {\n            String alias = attributes.getValue(ATTRIBUTE_ALIAS);\n            String command = attributes.getValue(ATTRIBUTE_VALUE);\n            String fileMask = attributes.getValue(ATTRIBUTE_FILEMASK);\n\n            // Makes sure the required attributes are there.\n            if (alias != null && command != null) {\n            \tCommandType type = CommandType.parseCommandType(attributes.getValue(ATTRIBUTE_TYPE));\n            \tString display = attributes.getValue(ATTRIBUTE_DISPLAY);\n\n            \t// Creates the command and passes it to the builder.\n            \ttry {\n                    builder.addCommand(new Command(alias, command, type, display, fileMask));\n                } catch (CommandException e) {\n                    throw new SAXException(e);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/command/CommandType.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.command;\n\n\n/**\n * Types of compiled shell commands.\n *\n * @author Arik Hadas\n */\npublic enum CommandType {\n\t/** Describes <i>normal</i> commands. */\n    NORMAL_COMMAND,\n    /** Describes <i>system</i> commands. */\n    SYSTEM_COMMAND(CommandsXmlConstants.VALUE_SYSTEM),\n    /** Describes <i>invisible</i> commands. */\n    INVISIBLE_COMMAND(CommandsXmlConstants.VALUE_INVISIBLE);\n\n    private String value;\n\n    CommandType() {\n    }\n\n    CommandType(String value) {\n    \tthis();\n    \tthis.value = value;\n    }\n\n    /**\n     * Returns the CommandType value according to specified String representation.\n     * <p>\n     * Note that this method is not strict in the arguments it receives:\n     * <ul>\n     *   <li>If <code>type</code> equals {CommandsXmlConstants#VALUE_SYSTEM}, {@link #SYSTEM_COMMAND} will be returned.</li>\n     *   <li>If <code>type</code> equals {CommandsXmlConstants#VALUE_INVISIBLE}, {@link #INVISIBLE_COMMAND} will be returned.</li>\n     *   <li>In any other case, {@link #NORMAL_COMMAND} will be returned.</li>\n     * </ul>\n     *\n     * @param  value String representation of type to analyze.\n     * @return <code>type</code>'s integer equivalent.\n     */\n    public static CommandType parseCommandType(String value) {\n    \tif (value == null) {\n            return NORMAL_COMMAND;\n        }\n\n    \tfor (CommandType type : CommandType.values()) {\n            if (value.equals(type.value)) {\n                return type;\n            }\n        }\n\n    \treturn NORMAL_COMMAND;\n    }\n\n    @Override\n    public String toString() {\n    \treturn value;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/command/CommandWriter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.command;\n\nimport com.mucommander.utils.xml.XmlAttributes;\nimport com.mucommander.utils.xml.XmlWriter;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * Class used to write custom commands XML files.\n * <p>\n * <code>CommandWriter</code> is a {@link CommandBuilder} that will send\n * all build messages it receives into an XML stream (as defined in {@link CommandsXmlConstants}).\n *\n * @author Nicolas Rinaudo\n */\npublic class CommandWriter implements CommandsXmlConstants, CommandBuilder {\n    /** Where to write the custom command associations to. */\n    private final XmlWriter out;\n\n\n    /**\n     * Builds a new writer that will send data to the specified output stream.\n     * @param  stream      where to write the XML data.\n     * @throws IOException if an IO error occurs.\n     */\n    CommandWriter(OutputStream stream) throws IOException {\n        out = new XmlWriter(stream);\n    }\n\n\n    /**\n     * Opens the root XML element.\n     */\n    public void startBuilding() throws CommandException {\n        try {\n            out.startElement(ELEMENT_ROOT);\n            out.println();\n        } catch(IOException e) {\n            throw new CommandException(e);\n        }\n    }\n\n    /**\n     * Closes the root XML element.\n     */\n    public void endBuilding() throws CommandException {\n        try {\n            out.endElement(ELEMENT_ROOT);\n        } catch(IOException e) {\n            throw new CommandException(e);\n        }\n    }\n\n    /**\n     * Writes the specified command's XML description.\n     * @param  command          command that should be written.\n     * @throws CommandException if an error occurs.\n     */\n    public void addCommand(Command command) throws CommandException {\n        // Builds the XML description of the command.\n        XmlAttributes attributes = new XmlAttributes();\n        attributes.add(ATTRIBUTE_ALIAS, command.getAlias());\n        attributes.add(ATTRIBUTE_VALUE, command.getCommand());\n        attributes.add(ATTRIBUTE_FILEMASK, command.getFileMask());\n        if (command.getType().toString() != null) {\n            attributes.add(ATTRIBUTE_TYPE, command.getType().toString());\n        }\n        if (command.isDisplayNameSet()) {\n            attributes.add(ATTRIBUTE_DISPLAY, command.getDisplayName());\n        }\n\n        // Writes the XML description.\n        try {\n            out.writeStandAloneElement(ELEMENT_COMMAND, attributes);\n        } catch(IOException e) {\n            throw new CommandException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/command/CommandsXmlConstants.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.command;\r\n\r\n/**\r\n * Defines the structure of a custom commands XML file.\r\n * <p>\r\n * This interface is only meant as a convenient way of sharing the XML\r\n * file format between the {@link com.mucommander.command.CommandWriter}\r\n * and {@link CommandReader}. It will be removed at bytecode optimisation time.\r\n * <p>\r\n * Commands XML files must match the following DTD:\r\n * <pre>\r\n * &lt;!ELEMENT commands (association*)&gt;\r\n * \r\n * &lt;!ELEMENT command EMPTY&gt;\r\n * &lt;!ATTLIST command value   CDATA              #REQUIRED&gt;\r\n * &lt;!ATTLIST command alias   CDATA              #REQUIRED&gt;\r\n * &lt;!ATTLIST command type    (system|invisible) #IMPLIED&gt;\r\n * &lt;!ATTLIST command display CDATA              #IMPLIED&gt;\r\n * </pre>\r\n * Where:\r\n * <ul>\r\n *  <li><i>value</i> is the command's value, in a format that can be understood by the {@link CommandReader}.</li>\r\n *  <li><i>alias</i> is the name under which the command will be known throughout trolCommander.</li>\r\n *  <li><i>type</i> is the command's type (<i>system</i>, <i>invisible</i> or <i>normal</i>). See {@link Command} for more information.</li>\r\n * </ul>\r\n *\r\n * @see CommandReader\r\n * @see CommandWriter\r\n * @author Nicolas Rinaudo\r\n */\r\ninterface CommandsXmlConstants {\r\n    /** Root element. */\r\n    String ELEMENT_ROOT     = \"commands\";\r\n    /** Custom command definition element. */\r\n    String ELEMENT_COMMAND  = \"command\";\r\n\r\n\r\n    /** Name of the attribute containing a command's display name. */\r\n    String ATTRIBUTE_DISPLAY = \"display\";\r\n    /** Name of the attribute containing a command's alias. */\r\n    String ATTRIBUTE_ALIAS   = \"alias\";\r\n    /** Name of the attribute containing a command's value. */\r\n    String ATTRIBUTE_VALUE   = \"value\";\r\n    String ATTRIBUTE_FILEMASK   = \"filemask\";\r\n    /** Name of the attribute containing a command's type. */\r\n    String ATTRIBUTE_TYPE    = \"type\";\r\n    /** Describes <i>system</i> commands. */\r\n    String VALUE_SYSTEM      = \"system\";\r\n    /** Describes <i>invisible</i> commands. */\r\n    String VALUE_INVISIBLE   = \"invisible\";\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/command/PermissionsFileFilter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.command;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.PermissionAccesses;\nimport com.mucommander.commons.file.PermissionTypes;\nimport com.mucommander.commons.file.filter.AbstractFileFilter;\n\n/**\n * Filter on a file's permissions.\n * @author Nicolas Rinaudo\n */\npublic class PermissionsFileFilter extends AbstractFileFilter implements PermissionTypes, PermissionAccesses {\n    private final int permission;\n    private final boolean filter;\n\n    /**\n     * Creates a new <code>PermissionsFileFilter</code>.\n     * @param permission permission that will be checked against as defined in {@link com.mucommander.commons.file.FilePermissions}.\n     * @param filter     whether the specified permission flag must be set for a file to be accepted.\n     */\n    PermissionsFileFilter(int permission, boolean filter) {\n        this.permission = permission;\n        this.filter = filter;\n    }\n\n    public boolean accept(AbstractFile file) {\n        return filter == file.getPermissions().getBitValue(USER_ACCESS, permission);\n    }\n\n    /**\n     * Returns the permission that this IMAGE_FILTER will check.\n     * @return the permission that this IMAGE_FILTER will check.\n     */\n    public int getPermission() {\n        return permission;\n    }\n\n    /**\n     * Returns <code>true</code> if files must have the IMAGE_FILTER's permission flag set in order to be accepted.\n     * @return <code>true</code> if files must have the IMAGE_FILTER's permission flag set in order to be accepted, <code>false</code> otherwise.\n     */\n    public boolean getFilter() {\n        return filter;\n    }\n}\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/command/package.html",
    "content": "<body>\n  Framework used to run system commands and associate them to file types.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/DummyDecoratedFile.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2018 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons;\n\nimport com.mucommander.commons.file.DummyFile;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.runtime.OsFamily;\n\n/**\n *\n *  This class is a child of <code>DummyFile</code> with overwritten {@link #toString()}} method that return a \"nice\" value\n *  for local files (without 'file://localhost/')\n *\n *  @author Oleg Trifonov\n */\npublic class DummyDecoratedFile extends DummyFile {\n\n    private static final String LOCAL_FILE_PREFIX;\n\n    static {\n        String prefix = \"file://\" + FileURL.LOCALHOST;\n        LOCAL_FILE_PREFIX = OsFamily.WINDOWS.isCurrent() ? prefix + '/' : prefix;\n    }\n\n    public DummyDecoratedFile(FileURL url) {\n        super(url);\n    }\n\n    @Override\n    public String toString() {\n        String result = super.toString();\n        if (result.startsWith(LOCAL_FILE_PREFIX)) {\n            return result.substring(LOCAL_FILE_PREFIX.length());\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/HasProgress.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons;\n\n/**\n * @author Oleg Trifonov\n * Created on 05/07/16.\n */\npublic interface HasProgress {\n    int getProgress();\n    boolean hasProgress();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/collections/AlteredVector.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.collections;\n\nimport java.util.Collection;\nimport java.util.Vector;\nimport java.util.WeakHashMap;\n\n/**\n * AlteredVector is a Vector that is able to notify registered listeners whenever its contents has changed.\n * <p>\n * Events are triggered when:\n * <ul>\n * <li>one or more elements has been added\n * <li>one or more elements has been removed\n * <li>an element has been changed\n * </ul>\n *\n * <p>It is however not aware of modifications that are made to the contained objects themselves.\n *\n * @author Maxence Bernard\n */\npublic class AlteredVector<E> extends Vector<E> {\n\n    /** Contains all registered listeners, stored as weak references */\n    private final WeakHashMap<VectorChangeListener, Object> listeners = new WeakHashMap<>();\n\n\n    public AlteredVector() {\n        super();\n    }\n\n    public AlteredVector(Collection<? extends E> collection) {\n        super(collection);\n    }\n\n    public AlteredVector(int initialCapacity, int capacityIncrement) {\n        super(initialCapacity, capacityIncrement);\n    }\n\n    public AlteredVector(int initialCapacity) {\n        super(initialCapacity);\n    }\n\n\n    /**\n     * Adds the specified VectorChangeListener to the list of registered listeners.\n     *\n     * <p>Listeners are stored as weak references so {@link #removeVectorChangeListener(VectorChangeListener)}\n     * doesn't need to be called for listeners to be garbage collected when they're not used anymore.\n     *\n     * @param listener the VectorChangeListener to add to the list of registered listeners.\n     * @see            #removeVectorChangeListener(VectorChangeListener)\n     */\n    public void addVectorChangeListener(VectorChangeListener listener) {\n        listeners.put(listener, null);\n    }\n\n    /**\n     * Removes the specified VectorChangeListener from the list of registered listeners.\n     *\n     * @param listener the VectorChangeListener to remove from the list of registered listeners.\n     * @see            #addVectorChangeListener(VectorChangeListener)\n     */\n    public void removeVectorChangeListener(VectorChangeListener listener) {\n        listeners.remove(listener);\n    }\n\n\n    /**\n     * This method is called when one or more elements has been added to this AlteredVector to notify listeners.\n     *\n     * @param startIndex index at which the first element has been added\n     * @param nbAdded number of elements added\n     */\n    private void fireElementsAddedEvent(int startIndex, int nbAdded) {\n        for (VectorChangeListener listener : listeners.keySet()) {\n            listener.elementsAdded(startIndex, nbAdded);\n        }\n    }\n\n    /**\n     * This method is called when one or more elements has been removed from this AlteredVector to notify listeners.\n     *\n     * @param startIndex index at which the first element has been removed\n     * @param nbRemoved number of elements removed\n     */\n    private void fireElementsRemovedEvent(int startIndex, int nbRemoved) {\n        for (VectorChangeListener listener : listeners.keySet()) {\n            listener.elementsRemoved(startIndex, nbRemoved);\n        }\n    }\n\n    /**\n     * This method is called when an element has been changed in this AlteredVector to notify listeners.\n     *\n     * @param index index of the element that has been changed\n     */\n    private void fireElementChangedEvent(int index) {\n        for (VectorChangeListener listener : listeners.keySet()) {\n            listener.elementChanged(index);\n        }\n    }\n\n\n    @Override\n    public void setElementAt(E o, int i) {\n        super.setElementAt(o, i);\n\n        fireElementChangedEvent(i);\n    }\n\n    @Override\n    public E set(int i, E o) {\n        o = super.set(i, o);\n\n        fireElementChangedEvent(i);\n\n        return o;\n    }\n\n    @Override\n    public void insertElementAt(E o, int i) {\n        super.insertElementAt(o, i);\n\n        fireElementsAddedEvent(i, 1);\n    }\n\n    @Override\n    public void add(int i, E o) {\n        insertElementAt(o, i);\n\n        fireElementsAddedEvent(i, 1);\n    }\n\n    @Override\n    public void addElement(E o) {\n        super.addElement(o);\n\n        fireElementsAddedEvent(size()-1, 1);\n    }\n\n    @Override\n    public boolean add(E o) {\n        addElement(o);\n\n        fireElementsAddedEvent(size()-1, 1);\n\n        return true;\n    }\n\n    @Override\n    public boolean addAll(Collection<? extends E> collection) {\n        int sizeBefore = size();\n\n        boolean b = super.addAll(collection);\n\n        fireElementsAddedEvent(sizeBefore, size()-sizeBefore);\n\n        return b;\n    }\n\n    @Override\n    public boolean addAll(int i, Collection<? extends E> collection) {\n        int sizeBefore = size();\n\n        boolean b = super.addAll(i, collection);\n\n        fireElementsAddedEvent(i, size()-sizeBefore);\n\n        return b;\n    }\n\n    @Override\n    public void removeElementAt(int i) {\n        super.removeElementAt(i);\n\n        fireElementsRemovedEvent(i, 1);\n    }\n\n    @Override\n    public E remove(int i) {\n        E o = super.remove(i);\n\n        fireElementsRemovedEvent(i, 1);\n\n        return o;\n    }\n\n    @Override\n    public boolean removeElement(Object o) {\n        int index = indexOf(o);\n\n        if(index==-1)\n            return false;\n\n        removeElementAt(index);\n\n        return true;\n    }\n\n    @Override\n    public boolean remove(Object o) {\n        return removeElement(o);\n    }\n\n    @Override\n    public void removeAllElements() {\n        int sizeBefore = size();\n\n        super.removeAllElements();\n\n        fireElementsRemovedEvent(0, sizeBefore);\n    }\n\n    @Override\n    public void clear() {\n        removeAllElements();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/collections/Enumerator.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.collections;\n\nimport java.util.Enumeration;\nimport java.util.Iterator;\nimport java.util.NoSuchElementException;\n\n/**\n * Converts an <code>Enumeration</code> into an <code>Iterator</code>.\n * @author Nicolas Rinaudo\n */\npublic class Enumerator<T> implements Iterator<T> {\n\n    /** Enumeration wrapped by this <code>Enumerator</code>. */\n    private final Enumeration<T> enumeration;\n\n\n    /**\n     * Creates a new enumerator from the specified enumeration.\n     * @param e enumeration that needs to be treated as an iterator.\n     */\n    public Enumerator(Enumeration<T> e) {\n        enumeration = e;\n    }\n\n\n\n    /**\n     * Returns <code>true</code> if the iterator has more elements.\n     * (In other words, returns <code>true</code> if {@link #next() next} would return an element rather than throwing an exception.)\n     * @return <code>true</code> if the iterator has more elements, <code>false</code> otherwise.\n     */\n    @Override\n    public boolean hasNext() {\n        return enumeration.hasMoreElements();\n    }\n\n    /**\n     * Returns the next element in the iteration.\n     * @return                        the next element in the iteration.\n     * @throws NoSuchElementException if there is no next element in the iteration.\n     */\n    @Override\n    public T next() throws NoSuchElementException {\n        return enumeration.nextElement();\n    }\n\n    /**\n     * Operation not supported.\n     * @throws UnsupportedOperationException whenever this method is called.\n     */\n    @Override\n    public void remove() {\n        throw new UnsupportedOperationException();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/collections/VectorChangeListener.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.collections;\n\n/**\n * Interface to be implemented by classes that wish to be notified of changes made to an {@link AlteredVector}.\n *\n * <p>Those classes need to be registered as listeners to receive those events, this can be done by calling\n * {@link AlteredVector#addVectorChangeListener(VectorChangeListener)}.\n *\n * @author Maxence Bernard\n */\npublic interface VectorChangeListener {\n\n    /**\n     * This method is called when one or more elements has been added to the AlteredVector.\n     *\n     * @param startIndex index at which the first element has been added\n     * @param nbAdded number of elements added\n     */\n    void elementsAdded(int startIndex, int nbAdded);\n\n    /**\n     * This method is called when one or more elements has been removed from the AlteredVector.\n     *\n     * @param startIndex index at which the first element has been removed\n     * @param nbRemoved number of elements removed\n     */\n    void elementsRemoved(int startIndex, int nbRemoved);\n\n    /**\n     * This method is called when an element has been changed in the AlteredVector.\n     *\n     * @param index index of the element that has been changed\n     */\n    void elementChanged(int index);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/BufferedConfigurationExplorer.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\nimport java.util.Stack;\n\n/**\n * Helper class meant for instances of {@link Configuration} to explore their own configuration tree.\n * <p>\n * This behaves exactly as a {@link ConfigurationExplorer}, but keeps track of its own path. This is meant\n * for instances of {@link Configuration} to prune empty branches.\n *\n * @author Nicolas Rinaudo\n */\nclass BufferedConfigurationExplorer extends ConfigurationExplorer {\n    /** Sections that have been passed through. */\n    private final Stack<ConfigurationSection> sections = new Stack<>();\n\n\n    /**\n     * Creates a new explorer on the specified section.\n     * @param root section from which to start exploring.\n     */\n    BufferedConfigurationExplorer(ConfigurationSection root) {\n        super(root);\n    }\n\n\n\n    /**\n     * Returns <code>true</code> if there are more sections in the history.\n     * @return <code>true</code> if there are more sections in the history.\n     */\n    boolean hasSections() {\n        return !sections.empty();\n    }\n\n    /**\n     * Returns the next section in history.\n     * @return the next section in history.\n     */\n    ConfigurationSection popSection() {\n        return sections.pop();\n    }\n\n\n    /**\n     * Move to the specified section.\n     * @param  name   name of the current section's subsection in which to move.\n     * @param  create if <code>true</code> and <code>name</code> doesn't exist, it will be created.\n     * @return        <code>true</code> if we could move to <code>name</code>, <code>false</code> otherwise.\n     */\n    @Override\n    public boolean moveTo(String name, boolean create) {\n        if (super.moveTo(name, create)) {\n            sections.push(getSection());\n            return true;\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/Configuration.java",
    "content": "package com.mucommander.commons.conf;\n\nimport java.io.*;\nimport java.nio.charset.StandardCharsets;\nimport java.util.*;\n\n/**\n * Base class for all configuration related tasks.\n * <p>\n * A <code>Configuration</code> instance's main goal is to act as a configuration data repository.\n * Once created, it can be used to {@link #getVariable(String) retrieve}, {@link #removeVariable(String) delete}\n * and {@link #setVariable(String,String) set} configuration variables.\n *\n * <h3>Naming conventions</h3>\n * <p>\n * Configuration variable names follow the same convention as Java System properties: a serie of strings\n * separated by periods. By convention, all but the last string are called configuration sections, while\n * the last one is the variable's name. When we refer to a variable's fully qualified name, we're talking\n * about the whole period-separated name.<br>\n * For example, <code>startup_folder.right.last_folder</code> is interpreted as a variable called\n * <code>last_folder</code> contained in a section called <code>right</code>, itself contained in\n * another section called <code>startup_folder</code>.<br>\n *\n * <h3>Variable types</h3>\n * <p>\n * While the <code>com.mucommander.commons.conf</code> really only handles one type of variables, strings, it offers\n * tools to cast them as primitive Java types (int, long, float, double, boolean). This is done through the use\n * of the various primitive types' class implementation <code>parseXXX</code> method.<br>\n * When a variable hasn't been set but ant attempt is made to cast it, the standard Java default value will\n * be returned:\n * <ul>\n *   <li>String: <code>null</code></li>\n *   <li>Integer: <code>0</code></li>\n *   <li>Long: <code>0</code></li>\n *   <li>Float: <code>0</code></li>\n *   <li>Double: <code>0</code></li>\n *   <li>Boolean: <code>false</code></li>\n * </ul>\n *\n * <h3>Configuration file format</h3>\n * <p>\n * By default, configuration data is assumed to be in the standard muCommander file format (described in\n * {@link XmlConfigurationReader}). However, application writers can modify that to any format they want\n * through the {@link #setReaderFactory(ConfigurationReaderFactory) setReaderFactory} and\n * {@link #setWriterFactory(ConfigurationWriterFactory) setWriterFactory} methods.\n *\n * <h3>Configuration data location</h3>\n * <p>\n * While <code>Configuration</code> provides read and write methods that accept streams as parameters, it's\n * also possible to set the data source once and for all and let the API deal with the details. This can\n * be achieved through the {@link #setSource(ConfigurationSource) setSource} method.<br>\n * Note that a default implementation, {@link FileConfigurationSource}, is provided. It covers the most\n * common case of configuration sources, a local configuration file.<br>\n * For application writers who wish to be able to retrieve configuration files through a variety of file systems,\n * we suggest creating a source using the <code>com.mucommander.file</code> API.\n *\n * <h3>Monitoring configuration changes</h3>\n * <p>\n * Classes that need to monitor the state of the configuration in order, for example, to react to changes\n * dynamically rather than wait for an application reboot can implement the {@link ConfigurationListener}\n * interface and register themselves through\n * {@link #addConfigurationListener(ConfigurationListener) addConfigurationListener}. This guarantees that they\n * will receive configuration events whenever a modification occurs.<br>\n * Note that LISTENERS are stored as weak references, meaning that application writers must ensure that they keep\n * direct references to the listener instances they register if they do not want them to be garbaged collected\n * out of existence randomly.\n *\n * @author Nicolas Rinaudo\n */\npublic class Configuration {\n    /** Used to get access to the configuration source's input and output streams. */\n    private ConfigurationSource source;\n    /** Used to create objects that will read from the configuration source. */\n    private ConfigurationReaderFactory readerFactory;\n    /** Used to create objects that will write to the configuration source. */\n    private ConfigurationWriterFactory writerFactory;\n    /** Holds the content of the configuration file. */\n    private final ConfigurationSection root = new ConfigurationSection();\n    /** Contains all registered configuration LISTENERS, stored as weak references. */\n    private final WeakHashMap<ConfigurationListener, ?> LISTENERS = new WeakHashMap<>();\n\n\n\n    /** Used to synchronize concurrent access of the configuration source. */\n    private final Object sourceLock = new Object();\n    /** Used to synchronize concurrent access of the reader factory. */\n    private final Object readerLock = new Object();\n    /** Used to synchronie concurrent access of the writer factory. */\n    private final Object writerLock = new Object();\n\n\n\n    /**\n     * Creates a new instance of <code>Configuration</code>.\n     * <p>\n     * The resulting instance will use default {@link XmlConfigurationReader readers} and\n     * {@link XmlConfigurationWriter writers}.\n     * <p>\n     * Note that until the {@link #setSource(ConfigurationSource) setSource} method has been\n     * invoked, calls to read or write methods without a stream parameter will fail.\n     */\n    public Configuration() {\n    }\n\n    /**\n     * Creates a new instance of <code>Configuration</code> using the specified source.\n     * <p>\n     * The resulting instance will use the default {@link XmlConfigurationReader readers} and\n     * {@link XmlConfigurationWriter writers}.\n     *\n     * @param source where the resulting instance will look for its configuration data.\n     */\n    public Configuration(ConfigurationSource source) {\n        setSource(source);\n    }\n\n    /**\n     * Creates a new instance of <code>Configuration</code> using the specified format.\n     * <p>\n     * Note that until the {@link #setSource(ConfigurationSource) setSource} method has been\n     * invoked, calls to read or write methods without a stream parameter will fail.\n     *\n     * @param reader factory for configuration readers.\n     * @param writer factory for configuration writers.\n     */\n    public Configuration(ConfigurationReaderFactory reader, ConfigurationWriterFactory writer) {\n        setReaderFactory(reader);\n        setWriterFactory(writer);\n    }\n\n    /**\n     * Creates a new instance of <code>Configuration</code> using the specified source and format.\n     * @param source where the resulting instance will look for its configuration data.\n     * @param reader factory for configuration readers.\n     * @param writer factory for configuration writers.\n     */\n    public Configuration(ConfigurationSource source, ConfigurationReaderFactory reader, ConfigurationWriterFactory writer) {\n        setSource(source);\n        setReaderFactory(reader);\n        setWriterFactory(writer);\n    }\n\n\n    /**\n     * Sets the source that will be used to read and write configuration information.\n     * @param s new configuration source.\n     * @see     #getSource()\n     */\n    public void setSource(ConfigurationSource s) {\n        synchronized(sourceLock) {\n            source = s;\n        }\n    }\n\n    /**\n     * Returns the current configuration source.\n     * @return the current configuration source, or <code>null</code> if it hasn't been set.\n     * @see    #setSource(ConfigurationSource)\n     */\n    public ConfigurationSource getSource() {\n        synchronized(sourceLock) {\n            return source;\n        }\n    }\n\n\n    /**\n     * Sets the factory that will be used to create {@link ConfigurationReader reader} instances.\n     * <p>\n     * In order to reset the configuration to its default reader factory, application writers can call this method\n     * with a <code>null</code> parameter.\n     *\n     * @param f factory that will be used to create reader instances.\n     * @see     #getReaderFactory()\n     */\n    void setReaderFactory(ConfigurationReaderFactory f) {\n        synchronized(readerLock) {\n            readerFactory = f;\n        }\n    }\n\n    /**\n     * Returns the factory that is being used to create {@link ConfigurationReader reader} instances.\n     * <p>\n     * By default, this method will return an {@link XmlConfigurationReader XML reader} factory.\n     * This can be modified by calling {@link #setReaderFactory(ConfigurationReaderFactory) setReaderFactory}.\n     *\n     * @return the factory that is being used to create reader instances.\n     * @see    #setReaderFactory(ConfigurationReaderFactory)\n     */\n    private ConfigurationReaderFactory getReaderFactory() {\n        synchronized(readerLock) {\n            if (readerFactory == null) {\n                return XmlConfigurationReader.FACTORY;\n            }\n            return readerFactory;\n        }\n    }\n\n\n    /**\n     * Sets the factory that will be used to create writer instances.\n     * <p>\n     * In order to reset the configuration to its default writer factory, application writers can call\n     * this method will a <code>null</code> parameter.\n     *\n     * @param f factory that will be used to create writer instances.\n     * @see     #getWriterFactory()\n     */\n    void setWriterFactory(ConfigurationWriterFactory f) {\n        synchronized(writerLock) {\n            writerFactory = f;\n        }\n    }\n\n    /**\n     * Returns the factory that is being used to create writer instances.\n     * <p>\n     * By default, this method will return an {@link XmlConfigurationWriter} factory. However, this\n     * can be modified by calling {@link #setWriterFactory(ConfigurationWriterFactory) setWriterFactory}.\n     *\n     * @return the factory that is being used to create writer instances.\n     * @see    #setWriterFactory(ConfigurationWriterFactory)\n     */\n    private ConfigurationWriterFactory getWriterFactory() {\n        synchronized(writerLock) {\n            if (writerFactory == null) {\n                return XmlConfigurationWriter.FACTORY;\n            }\n            return writerFactory;\n        }\n    }\n\n\n    /**\n     * Loads configuration from the specified input stream, using the specified configuration reader.\n     * @param  in                              where to read the configuration from.\n     * @param  reader                          reader that will be used to interpret the content of <code>in</code>.\n     * @throws IOException                     if an I/O error occurs.\n     * @throws ConfigurationException          if a configuration error occurs.\n     * @throws ConfigurationFormatException    if a syntax error occurs in the configuration data.\n     * @throws ConfigurationStructureException if the configuration data doesn't describe a valid configuration tree.\n     * @see                                    #read(InputStream)\n     * @see                                    #read(ConfigurationReader)\n     * @see                                    #read()\n     */\n    synchronized void read(Reader in, ConfigurationReader reader) throws IOException, ConfigurationException {\n        reader.read(in, new ConfigurationLoader(root));\n    }\n\n    /**\n     * Loads configuration from the specified input stream.\n     * <p>\n     * This method will use the configuration reader set by {@link #setReaderFactory(ConfigurationReaderFactory)} if any,\n     * or an {@link XmlConfigurationReader} instance if not.\n     *\n     * @param  in                              where to read the configuration from.\n     * @throws ConfigurationException          if a configuration error occurs.\n     * @throws ConfigurationFormatException    if a syntax error occurs in the configuration data.\n     * @throws ConfigurationStructureException if the configuration data doesn't describe a valid configuration tree.\n     * @throws ReaderConfigurationException    if the {@link ConfigurationReaderFactory} isn't properly configured.\n     * @throws IOException                     if an I/O error occurs.\n     * @see                                    #read()\n     * @see                                    #read(ConfigurationReader)\n     * @see                                    #read(Reader,ConfigurationReader)\n     * @deprecated Application developers should use {@link #read(Reader)} instead. This method assumes the specified\n     *             {@link InputStream} to be <code>UTF-8</code> encoded.\n     */\n    @Deprecated\n    public void read(InputStream in) throws ConfigurationException, IOException {\n        read(new InputStreamReader(in, StandardCharsets.UTF_8), getReaderFactory().getReaderInstance());\n    }\n\n    /**\n     * Loads configuration from the specified reader.\n     * <p>\n     * This method will use the configuration reader set by {@link #setReaderFactory(ConfigurationReaderFactory)} if any,\n     * or an {@link XmlConfigurationReader} instance if not.\n     *\n     * @param  in                              where to read the configuration from.\n     * @throws ConfigurationException          if a configuration error occurs.\n     * @throws ConfigurationFormatException    if a syntax error occurs in the configuration data.\n     * @throws ConfigurationStructureException if the configuration data doesn't describe a valid configuration tree.\n     * @throws ReaderConfigurationException    if the {@link ConfigurationReaderFactory} isn't properly configured.\n     * @throws IOException                     if an I/O error occurs.\n     * @see                                    #read()\n     * @see                                    #read(ConfigurationReader)\n     * @see                                    #read(Reader,ConfigurationReader)\n     */\n    public void read(Reader in) throws ConfigurationException, IOException {\n        read(in, getReaderFactory().getReaderInstance());\n    }\n\n    /**\n     * Loads configuration using the specified configuration reader.\n     * <p>\n     * This method will use the input stream provided by {@link #setSource(ConfigurationSource)} if any, or\n     * fail otherwise.\n     *\n     * @param  reader                          reader that will be used to interpret the content of <code>in</code>.\n     * @throws IOException                     if an I/O error occurs.\n     * @throws ConfigurationException          if a configuration error occurs.\n     * @throws SourceConfigurationException    if no {@link ConfigurationSource} has been set.\n     * @throws ConfigurationFormatException    if a syntax error occurs in the configuration data.\n     * @throws ConfigurationStructureException if the configuration data doesn't describe a valid configuration tree.\n     * @see                                    #read(InputStream)\n     * @see                                    #read()\n     * @see                                    #read(Reader,ConfigurationReader)\n     */\n    public void read(ConfigurationReader reader) throws IOException, ConfigurationException {\n        ConfigurationSource source = getSource(); // Configuration source.\n\n        // Makes sure the configuration source has been properly set.\n        if (source == null) {\n            throw new SourceConfigurationException(\"Configuration source hasn't been set.\");\n        }\n        // Reads the configuration data.\n        try (Reader in = source.getReader()) {\n            read(in, reader);\n        }\n    }\n\n    /**\n     * Loads configuration.\n     * <p>\n     * If a reader has been specified through {@link #setReaderFactory(ConfigurationReaderFactory)}, it\n     * will be used to analyse the configuration. Otherwise, an {@link XmlConfigurationReader}\n     * instance will be used.\n     *\n     * <p>\n     * If a configuration source has been specified through {@link #setSource(ConfigurationSource)}, it will be\n     * used. Otherwise, this method will fail.\n     *\n     * @throws IOException                     if an I/O error occurs.\n     * @throws ConfigurationException          if a configuration error occurs.\n     * @throws SourceConfigurationException    if no {@link ConfigurationSource} hasn't been set.\n     * @throws ConfigurationFormatException    if a syntax error occurs in the configuration data.\n     * @throws ConfigurationStructureException if the configuration data doesn't describe a valid configuration tree.\n     * @throws ReaderConfigurationException    if the {@link ConfigurationReaderFactory} isn't properly configured.\n     * @see                                    #write()\n     * @see                                    #read(InputStream)\n     * @see                                    #read(ConfigurationReader)\n     * @see                                    #read(Reader,ConfigurationReader)\n     */\n    public void read() throws ConfigurationException, IOException {\n        read(getReaderFactory().getReaderInstance());\n    }\n\n    /**\n     * Writes the configuration to the specified {@link Writer}.\n     * <p>\n     * This method will use {@link #getWriterFactory()} to create instances of configuration writer.\n     *\n     * @param out                              where to write the configuration to.\n     * @throws ConfigurationException          if any error occurs.\n     * @throws ConfigurationFormatException    if a syntax error occurs in the configuration data.\n     * @throws ConfigurationStructureException if the configuration data doesn't describe a valid configuration tree.\n     * @throws WriterConfigurationException    if the {@link ConfigurationWriterFactory} isn't properly configured.\n     * @see                                    #read(InputStream)\n     * @see                                    #write()\n     */\n    public void write(Writer out) throws ConfigurationException {\n        write(getWriterFactory().getWriterInstance(out));\n    }\n\n    /**\n     * Writes the configuration.\n     * <p>\n     * If a configuration source was specified through {@link #setSource(ConfigurationSource)}, it will be used\n     * to open an output stream. Otherwise, this method will fail.\n     *\n     * @throws ConfigurationException          if any error occurs.\n     * @throws SourceConfigurationException    if no {@link ConfigurationSource} has been set.\n     * @throws ConfigurationFormatException    if a syntax error occurs in the configuration data.\n     * @throws ConfigurationStructureException if the configuration data doesn't describe a valid configuration tree.\n     * @throws IOException                     if any I/O error occurs.\n     * @see                                    #read(ConfigurationReader)\n     * @see                                    #write()\n     */\n    public void write() throws IOException, ConfigurationException {\n        ConfigurationSource source = getSource(); // Configuration source.\n\n        // Makes sure the source has been set.\n        if (source == null) {\n            throw new SourceConfigurationException(\"No configuration source has been set\");\n        }\n\n        try (Writer out = source.getWriter()) {\n            write(out);\n        }\n    }\n\n    /**\n     * Writes the configuration data to the specified builder.\n     * @param  builder                object that will receive configuration building messages.\n     * @throws ConfigurationException if any error occurs while going through the configuration tree.\n     */\n    public void write(ConfigurationBuilder builder) throws ConfigurationException {\n        builder.startConfiguration();\n        build(builder, root);\n        builder.endConfiguration();\n    }\n\n\n    /**\n     * Recursively explores the specified section and invokes the specified builder's callback methods.\n     * @param  builder                object that will receive building events.\n     * @param  root                   section to explore.\n     * @throws ConfigurationException if any error occurs.\n     */\n    private synchronized void build(ConfigurationBuilder builder, ConfigurationSection root) throws ConfigurationException {\n        // Explores the section's variables.\n        Set<String> variables = root.variableNames();        // Enumeration on the section's variables, then subsections.\n        for (String name : variables) {\n            builder.addVariable(name, root.getVariable(name));\n        }\n\n        // Explores the section's subsections.\n        Set<String> sectionNames = root.sectionNames();\n        for (String name : sectionNames) {\n            ConfigurationSection section = root.getSection(name);\n\n            // We only go through subsections if contain either variables or subsections of their own.\n            if (section.hasSections() || section.hasVariables()) {\n                builder.startSection(name);\n                build(builder, section);\n                builder.endSection(name);\n            }\n        }\n    }\n\n\n\n    // - Variable setting ----------------------------------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------------------------------------\n    /**\n     * Moves the value of <code>fromVar</code> to <code>toVar</code>.\n     * <p>\n     * At the end of this call, <code>fromVar</code> will have been deleted. Note that if <code>fromVar</code> doesn't\n     * exist, but <code>toVar</code> does, <code>toVar</code> will be deleted.\n     *\n     * <p>\n     * This method might trigger as many as two {@link ConfigurationEvent events}:\n     * <ul>\n     *  <li>One when <code>fromVar</code> is removed.</li>\n     *  <li>One when <code>toVar</code> is set.</li>\n     * </ul>\n     * The removal event will always be triggered first.\n     *\n     * @param fromVar fully qualified name of the variable to rename.\n     * @param toVar   fully qualified name of the variable that will receive <code>fromVar</code>'s value.\n     */\n    public void renameVariable(String fromVar, String toVar) {\n        setVariable(toVar, removeVariable(fromVar));\n    }\n\n    /**\n     * Sets the value of the specified variable.\n     * <p>\n     * This method will return <code>false</code> if it didn't modify <code>name</code>'s value. Note that this doesn't\n     * mean the call failed, but that <code>name</code>'s value was already equal to <code>value</code>.\n     *\n     * <p>\n     * If the value of the specified variable is actually modified, an {@link ConfigurationEvent event} will be passed\n     * to all LISTENERS.\n     *\n     * @param  name  fully qualified name of the variable to set.\n     * @param  value new value for the variable.\n     * @return       <code>true</code> if this call resulted in a modification of the variable's value,\n     *               <code>false</code> otherwise.\n     * @see          #getVariable(String)\n     * @see          #getVariable(String,String)\n     */\n    public synchronized boolean setVariable(String name, String value) {\n        ConfigurationExplorer explorer = new ConfigurationExplorer(root); // Used to navigate to the variable's parent section.\n\n        // Moves to the parent section.\n        String buffer = moveToParent(explorer, name, true);   // Buffer for the variable's name trimmed of section information.\n\n        // If the variable's value was actually modified, triggers an event.\n        if (explorer.getSection().setVariable(buffer, value)) {\n            triggerEvent(new ConfigurationEvent(this, name, value));\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * Sets the value of the specified variable.\n     * <p>\n     * This method will return <code>false</code> if it didn't modify <code>name</code>'s value. This, however, is not a\n     * way of indicating that the call failed: <code>false</code> is only ever returned if the previous value is equal\n     * to the new value.\n     *\n     * <p>\n     * If the value of the specified variable is actually modified, an {@link ConfigurationEvent event} will be passed\n     * to all LISTENERS.\n     *\n     * @param  name  fully qualified name of the variable to set.\n     * @param  value new value for the variable.\n     * @return       <code>true</code> if this call resulted in a modification of the variable's value,\n     *               <code>false</code> otherwise.\n     * @see          #getIntegerVariable(String)\n     * @see          #getVariable(String,int)\n     */\n    public boolean setVariable(String name, int value) {\n        return setVariable(name, ConfigurationSection.getValue(value));\n    }\n\n    /**\n     * Sets the value of the specified variable.\n     * <p>\n     * This method will return <code>false</code> if it didn't modify <code>name</code>'s value. This, however, is not a\n     * way of indicating that the call failed: <code>false</code> is only ever returned if the previous value is equal\n     * to the new value.\n     *\n     * <p>\n     * If the value of the specified variable is actually modified, an {@link ConfigurationEvent event} will be passed to all\n     * LISTENERS.\n     *\n     * @param  name      fully qualified name of the variable to set.\n     * @param  value     new value for the variable.\n     * @param  separator string used to separate each element of the list.\n     * @return           <code>true</code> if this call resulted in a modification of the variable's value,\n     *                   <code>false</code> otherwise.\n     * @see              #getListVariable(String,String)\n     * @see              #getVariable(String,List,String)\n     */\n    public boolean setVariable(String name, List<String> value, String separator) {\n        return setVariable(name, ConfigurationSection.getValue(value, separator));\n    }\n\n    /**\n     * Sets the value of the specified variable.\n     * <p>\n     * This method will return <code>false</code> if it didn't modify <code>name</code>'s value. This, however, is not\n     * a way of indicating that the call failed: <code>false</code> is only ever returned if the previous value is equal\n     * to the new value.\n     *\n     * <p>\n     * If the value of the specified variable is actually modified, an {@link ConfigurationEvent event} will be passed\n     * to all LISTENERS.\n     *\n     * @param  name  fully qualified name of the variable to set.\n     * @param  value new value for the variable.\n     * @return       <code>true</code> if this call resulted in a modification of the variable's value,\n     *               <code>false</code> otherwise.\n     * @see          #getFloatVariable(String)\n     * @see          #getVariable(String,float)\n     */\n    public boolean setVariable(String name, float value) {return setVariable(name, ConfigurationSection.getValue(value));}\n\n    /**\n     * Sets the value of the specified variable.\n     * <p>\n     * This method will return <code>false</code> if it didn't modify <code>name</code>'s value. This, however, is not a\n     * way of indicating that the call failed: <code>false</code> is only ever returned if the previous value is equal\n     * to the new value.\n     *\n     * <p>\n     * If the value of the specified variable is actually modified, an {@link ConfigurationEvent event} will be passed\n     * to all LISTENERS.\n     *\n     * @param  name  fully qualified name of the variable to set.\n     * @param  value new value for the variable.\n     * @return       <code>true</code> if this call resulted in a modification of the variable's value,\n     *               <code>false</code> otherwise.\n     * @see          #getBooleanVariable(String)\n     * @see          #getVariable(String,boolean)\n     */\n    public boolean setVariable(String name, boolean value) {\n        return setVariable(name, ConfigurationSection.getValue(value));\n    }\n\n    /**\n     * Sets the value of the specified variable.\n     * <p>\n     * This method will return <code>false</code> if it didn't modify <code>name</code>'s value. This, however, is not a\n     * way of indicating that the call failed: <code>false</code> is only ever returned if the previous value is equal\n     * to the new value.\n     *\n     * <p>\n     * If the value of the specified variable is actually modified, an {@link ConfigurationEvent event} will be passed\n     * to all LISTENERS.\n     *\n     * @param  name  fully qualified name of the variable to set.\n     * @param  value new value for the variable.\n     * @return       <code>true</code> if this call resulted in a modification of the variable's value,\n     *               <code>false</code> otherwise.\n     * @see          #getLongVariable(String)\n     * @see          #getVariable(String,long)\n     */\n    public boolean setVariable(String name, long value) {\n        return setVariable(name, ConfigurationSection.getValue(value));\n    }\n\n    /**\n     * Sets the value of the specified variable.\n     * <p>\n     * This method will return <code>false</code> if it didn't modify <code>name</code>'s value. This, however, is not a\n     * way of indicating that the call failed: <code>false</code> is only ever returned if the previous value is equal\n     * to the new value.\n     *\n     * <p>\n     * If the value of the specified variable is actually modified, an {@link ConfigurationEvent event} will be passed\n     * to all LISTENERS.\n     *\n     * @param  name  fully qualified name of the variable to set.\n     * @param  value new value for the variable.\n     * @return       <code>true</code> if this call resulted in a modification of the variable's value,\n     *               <code>false</code> otherwise.\n     * @see          #getDoubleVariable(String)\n     * @see          #getVariable(String,double)\n     */\n    public boolean setVariable(String name, double value) {return setVariable(name, ConfigurationSection.getValue(value));}\n\n\n    /**\n     * Returns the value of the specified variable.\n     * @param  name fully qualified name of the variable whose value should be retrieved.\n     * @return      the variable's value if set, <code>null</code> otherwise.\n     * @see         #setVariable(String,String)\n     * @see         #getVariable(String,String)\n     */\n    public synchronized String getVariable(String name) {\n        // If the variable's 'path' doesn't exist, return null.\n        ConfigurationExplorer explorer = new ConfigurationExplorer(root);   // Used to navigate to the variable's parent section.\n        name = moveToParent(explorer, name, false);\n        return name == null ? null : explorer.getSection().getVariable(name);\n    }\n\n    /**\n     * Returns the value of the specified variable as a {@link ValueList}.\n     * @param  name      fully qualified name of the variable whose value should be retrieved.\n     * @param  separator character used to split the variable's value into a list.\n     * @return           the variable's value if set, <code>null</code> otherwise.\n     * @see              #setVariable(String,List,String)\n     * @see              #getVariable(String,List,String)\n     */\n    public ValueList getListVariable(String name, String separator) {\n        return ConfigurationSection.getListValue(getVariable(name), separator);\n    }\n\n    /**\n     * Returns the value of the specified variable as an integer.\n     * @param                        name fully qualified name of the variable whose value should be retrieved.\n     * @return                       the variable's value if set, <code>0</code> otherwise.\n     * @throws NumberFormatException if the variable's value cannot be cast to an integer.\n     * @see                          #setVariable(String,int)\n     * @see                          #getVariable(String,int)\n     */\n    public int getIntegerVariable(String name) {\n        return ConfigurationSection.getIntegerValue(getVariable(name));\n    }\n\n    /**\n     * Returns the value of the specified variable as a long.\n     * @param                        name fully qualified name of the variable whose value should be retrieved.\n     * @return                       the variable's value if set, <code>0</code> otherwise.\n     * @throws NumberFormatException if the variable's value cannot be cast to a long.\n     * @see                          #setVariable(String,long)\n     * @see                          #getVariable(String,long)\n     */\n    public long getLongVariable(String name) {\n        return ConfigurationSection.getLongValue(getVariable(name));\n    }\n\n    /**\n     * Returns the value of the specified variable as a float.\n     * @param                        name fully qualified name of the variable whose value should be retrieved.\n     * @return                       the variable's value if set, <code>0</code> otherwise.\n     * @throws NumberFormatException if the variable's value cannot be cast to a float.\n     * @see                          #setVariable(String,float)\n     * @see                          #getVariable(String,float)\n     */\n    public float getFloatVariable(String name) {\n        return ConfigurationSection.getFloatValue(getVariable(name));\n    }\n\n    /**\n     * Returns the value of the specified variable as a double.\n     * @param                        name fully qualified name of the variable whose value should be retrieved.\n     * @return                       the variable's value if set, <code>0</code> otherwise.\n     * @throws NumberFormatException if the variable's value cannot be cast to a double.\n     * @see                          #setVariable(String,double)\n     * @see                          #getVariable(String,double)\n     */\n    public double getDoubleVariable(String name) {\n        return ConfigurationSection.getDoubleValue(getVariable(name));\n    }\n\n    /**\n     * Returns the value of the specified variable as a boolean.\n     * @param  name fully qualified name of the variable whose value should be retrieved.\n     * @return the variable's value if set, <code>false</code> otherwise.\n     * @see                          #setVariable(String,boolean)\n     * @see                          #getVariable(String,boolean)\n     */\n    public boolean getBooleanVariable(String name) {\n        return ConfigurationSection.getBooleanValue(getVariable(name));\n    }\n\n    /**\n     * Checks whether the specified variable has been set.\n     * @param  name fully qualified name of the variable to check for.\n     * @return      <code>true</code> if the variable is set, <code>false</code> otherwise.\n     */\n    public boolean isVariableSet(String name) {\n        return getVariable(name) != null;\n    }\n\n\n\n    /**\n     * Prunes dead branches from the specified configuration tree.\n     * @param explorer used to backtrack through the configuration tree.\n     */\n    private void prune(BufferedConfigurationExplorer explorer) {\n        // If we're at the root level, nothing to prune.\n        if (!explorer.hasSections()) {\n            return;\n        }\n\n        ConfigurationSection current = explorer.popSection();\n\n        // Look for branches to prune until we've either found a non-empty one\n        // or reached the root of the three.\n        while (current.isEmpty() && current != root) {\n            // Gets the current section's parent and prune.\n            ConfigurationSection parent = explorer.hasSections() ? explorer.popSection() : root;\n            parent.removeSection(current);\n            current = parent;\n        }\n    }\n\n    /**\n     * Deletes the specified variable from the configuration.\n     * <p>\n     * If the variable was set, a configuration {@link ConfigurationEvent event} will be passed to\n     * all registered LISTENERS.\n     *\n     * @param  name name of the variable to remove.\n     * @return      the variable's old value, or <code>null</code> if it wasn't set.\n     */\n    public synchronized String removeVariable(String name) {\n        BufferedConfigurationExplorer explorer  = new BufferedConfigurationExplorer(root); // Used to navigate to the variable's parent section.\n        String buffer = moveToParent(explorer, name, false);   // Buffer for the variable's name trimmed of section information.\n\n        // If the variable's 'path' doesn't exist, return null.\n        if (buffer == null) {\n            return null;\n        }\n        // If the variable was actually set, triggers an event.\n        if ((buffer = explorer.getSection().removeVariable(buffer)) != null) {\n            prune(explorer);\n            triggerEvent(new ConfigurationEvent(this, name, null));\n        }\n\n        return buffer;\n    }\n\n    /**\n     * Deletes the specified variable from the configuration.\n     * <p>\n     * If the variable was set, a configuration {@link ConfigurationEvent event} will be passed to\n     * all registered LISTENERS.\n     *\n     * @param  name name of the variable to remove.\n     * @param  separator character used to split the variable's value into a list.\n     * @return      the variable's old value, or <code>null</code> if it wasn't set.\n     */\n    public ValueList removeListVariable(String name, String separator) {\n        return ConfigurationSection.getListValue(removeVariable(name), separator);\n    }\n\n    /**\n     * Deletes the specified variable from the configuration.\n     * <p>\n     * If the variable was set, a configuration {@link ConfigurationEvent event} will be passed to\n     * all registered LISTENERS.\n     *\n     * @param  name name of the variable to remove.\n     * @return      the variable's old value, or <code>0</code> if it wasn't set.\n     */\n    public int removeIntegerVariable(String name) {\n        return ConfigurationSection.getIntegerValue(removeVariable(name));\n    }\n\n    /**\n     * Deletes the specified variable from the configuration.\n     * <p>\n     * If the variable was set, a configuration {@link ConfigurationEvent event} will be passed to\n     * all registered LISTENERS.\n     *\n     * @param  name name of the variable to remove.\n     * @return      the variable's old value, or <code>0</code> if it wasn't set.\n     */\n    public long removeLongVariable(String name) {\n        return ConfigurationSection.getLongValue(removeVariable(name));\n    }\n\n    /**\n     * Deletes the specified variable from the configuration.\n     * <p>\n     * If the variable was set, a configuration {@link ConfigurationEvent event} will be passed to\n     * all registered LISTENERS.\n     *\n     * @param  name name of the variable to remove.\n     * @return      the variable's old value, or <code>0</code> if it wasn't set.\n     */\n    public float removeFloatVariable(String name) {\n        return ConfigurationSection.getFloatValue(removeVariable(name));\n    }\n\n    /**\n     * Deletes the specified variable from the configuration.\n     * <p>\n     * If the variable was set, a configuration {@link ConfigurationEvent event} will be passed to\n     * all registered LISTENERS.\n     *\n     * @param  name name of the variable to remove.\n     * @return      the variable's old value, or <code>0</code> if it wasn't set.\n     */\n    public double removeDoubleVariable(String name) {\n        return ConfigurationSection.getDoubleValue(removeVariable(name));\n    }\n\n    /**\n     * Deletes the specified variable from the configuration.\n     * <p>\n     * If the variable was set, a configuration {@link ConfigurationEvent event} will be passed to\n     * all registered LISTENERS.\n     *\n     * @param  name name of the variable to remove.\n     * @return      the variable's old value, or <code>false</code> if it wasn't set.\n     */\n    public boolean removeBooleanVariable(String name) {\n        return ConfigurationSection.getBooleanValue(removeVariable(name));\n    }\n\n    /**\n     * Remove all variables and sub-sections under the root section\n     */\n    public void clear() {\n\t\troot.clear();\n\t}\n\n\n    // - Advanced variable retrieval -----------------------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------------------------------------\n    /**\n     * Retrieves the value of the specified variable.\n     * <p>\n     * If the variable isn't set, this method will set it to <code>defaultValue</code> before\n     * returning it. If this happens, a configuration {@link ConfigurationEvent event} will\n     * be sent to all registered LISTENERS.\n     *\n     * @param  name         name of the variable to retrieve.\n     * @param  defaultValue value to use if <code>name</code> is not set.\n     * @return              the specified variable's value.\n     * @see                 #setVariable(String,String)\n     * @see                 #getVariable(String)\n     */\n    public synchronized String getVariable(String name, String defaultValue) {\n        ConfigurationExplorer explorer = new ConfigurationExplorer(root); // Used to navigate to the variable's parent section.\n\n        // Navigates to the parent section. We do not have to check for null values here,\n        // as the section will be created if it doesn't exist.\n        String buffer = moveToParent(explorer, name, true);   // Buffer for the variable's name trimmed of section information.\n\n        // If the variable isn't set, set it to defaultValue and triggers an event.\n        String value = explorer.getSection().getVariable(buffer);       // Buffer for the variable's value.\n        if (value == null) {\n            explorer.getSection().setVariable(buffer, defaultValue);\n            triggerEvent(new ConfigurationEvent(this, name, defaultValue));\n            return defaultValue;\n        }\n        return value;\n    }\n\n    /**\n     * Retrieves the value of the specified variable as a {@link ValueList}.\n     * <p>\n     * If the variable isn't set, this method will set it to <code>defaultValue</code> before\n     * returning it. If this happens, a configuration {@link ConfigurationEvent event} will\n     * be sent to all registered LISTENERS.\n     *\n     * @param  name         name of the variable to retrieve.\n     * @param  defaultValue value to use if variable <code>name</code> is not set.\n     * @param  separator    separator to use for <code>defaultValue</code> if variable <code>name</code> is not set.\n     * @return              the specified variable's value.\n     * @see                 #setVariable(String,List,String)\n     * @see                 #getListVariable(String,String)\n     */\n    public ValueList getVariable(String name, List<String> defaultValue, String separator) {\n        return ConfigurationSection.getListValue(getVariable(name, ConfigurationSection.getValue(defaultValue, separator)), separator);\n    }\n\n    /**\n     * Retrieves the value of the specified variable as an integer.\n     * <p>\n     * If the variable isn't set, this method will set it to <code>defaultValue</code> before\n     * returning it. If this happens, a configuration {@link ConfigurationEvent event} will\n     * be sent to all registered LISTENERS.\n     *\n     * @param  name                  name of the variable to retrieve.\n     * @param  defaultValue          value to use if <code>name</code> is not set.\n     * @return                       the specified variable's value.\n     * @throws NumberFormatException if the variable's value cannot be cast to an integer.\n     * @see                          #setVariable(String,int)\n     * @see                          #getIntegerVariable(String)\n     */\n    public int getVariable(String name, int defaultValue) {\n        return ConfigurationSection.getIntegerValue(getVariable(name, ConfigurationSection.getValue(defaultValue)));\n    }\n\n    /**\n     * Retrieves the value of the specified variable as a long.\n     * <p>\n     * If the variable isn't set, this method will set it to <code>defaultValue</code> before\n     * returning it. If this happens, a configuration {@link ConfigurationEvent event} will\n     * be sent to all registered LISTENERS.\n     *\n     * @param  name                  name of the variable to retrieve.\n     * @param  defaultValue          value to use if <code>name</code> is not set.\n     * @return                       the specified variable's value.\n     * @throws NumberFormatException if the variable's value cannot be cast to a long.\n     * @see                          #setVariable(String,long)\n     * @see                          #getLongVariable(String)\n     */\n    public long getVariable(String name, long defaultValue) {\n        return ConfigurationSection.getLongValue(getVariable(name, ConfigurationSection.getValue(defaultValue)));\n    }\n\n    /**\n     * Retrieves the value of the specified variable as a float.\n     * <p>\n     * If the variable isn't set, this method will set it to <code>defaultValue</code> before\n     * returning it. If this happens, a configuration {@link ConfigurationEvent event} will\n     * be sent to all registered LISTENERS.\n     *\n     * @param  name                  name of the variable to retrieve.\n     * @param  defaultValue          value to use if <code>name</code> is not set.\n     * @return                       the specified variable's value.\n     * @throws NumberFormatException if the variable's value cannot be cast to a float.\n     * @see                          #setVariable(String,float)\n     * @see                          #getFloatVariable(String)\n     */\n    public float getVariable(String name, float defaultValue) {\n        return ConfigurationSection.getFloatValue(getVariable(name, ConfigurationSection.getValue(defaultValue)));\n    }\n\n    /**\n     * Retrieves the value of the specified variable as a boolean.\n     * <p>\n     * If the variable isn't set, this method will set it to <code>defaultValue</code> before\n     * returning it. If this happens, a configuration {@link ConfigurationEvent event} will\n     * be sent to all registered LISTENERS.\n     *\n     * @param  name                  name of the variable to retrieve.\n     * @param  defaultValue          value to use if <code>name</code> is not set.\n     * @return                       the specified variable's value.\n     * @see                          #setVariable(String,boolean)\n     * @see                          #getBooleanVariable(String)\n     */\n    public boolean getVariable(String name, boolean defaultValue) {\n        return ConfigurationSection.getBooleanValue(getVariable(name, ConfigurationSection.getValue(defaultValue)));\n    }\n\n    /**\n     * Retrieves the value of the specified variable as a double.\n     * <p>\n     * If the variable isn't set, this method will set it to <code>defaultValue</code> before\n     * returning it. If this happens, a configuration {@link ConfigurationEvent event} will\n     * be sent to all registered LISTENERS.\n     *\n     * @param  name                  name of the variable to retrieve.\n     * @param  defaultValue          value to use if <code>name</code> is not set.\n     * @return                       the specified variable's value.\n     * @throws NumberFormatException if the variable's value cannot be cast to a double.\n     * @see                          #setVariable(String,double)\n     * @see                          #getDoubleVariable(String)\n     */\n    public double getVariable(String name, double defaultValue) {\n        return ConfigurationSection.getDoubleValue(getVariable(name, ConfigurationSection.getValue(defaultValue)));\n    }\n\n\n\n    // - Helper methods ------------------------------------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------------------------------------\n    /**\n     * Navigates the specified explorer to the parent section of the specified variable.\n     * @param  root where to start exploring from.\n     * @param  name name of the variable to seek.\n     * @param  create whether the path to the variable should be created if it doesn't exist.\n     * @return        the name of the variable trimmed of section information, <code>null</code> if not found.\n     */\n    private String moveToParent(ConfigurationExplorer root, String name, boolean create) {\n        // Goes through each element of the path.\n        StringTokenizer parser = new StringTokenizer(name, \".\");\n        while (parser.hasMoreTokens()) {\n            // If we've reached the variable's name, return it.\n            name = parser.nextToken();\n            if (!parser.hasMoreTokens())\n                return name;\n\n            // If we've reached a dead-end, return null.\n            if (!root.moveTo(name, create)) {\n                return null;\n            }\n        }\n        return name;\n    }\n\n\n\n    // - Configuration listening ---------------------------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------------------------------------\n    /**\n     * Adds the specified object to the list of registered configuration LISTENERS.\n     * @param listener object to register as a configuration listener.\n     * @see            #removeConfigurationListener(ConfigurationListener)\n     */\n    public void addConfigurationListener(ConfigurationListener listener) {\n        LISTENERS.put(listener, null);}\n\n    /**\n     * Removes the specified object from the list of registered configuration LISTENERS.\n     * @param listener object to remove from the list of registered configuration LISTENERS.\n     * @see            #addConfigurationListener(ConfigurationListener)\n     */\n    public void removeConfigurationListener(ConfigurationListener listener) {\n        LISTENERS.remove(listener);}\n\n    /**\n     * Passes the specified event to all registered configuration LISTENERS.\n     * @param event event to propagate.\n     */\n    private void triggerEvent(ConfigurationEvent event) {\n        for (ConfigurationListener listener : LISTENERS.keySet()) {\n            listener.configurationChanged(event);\n        }\n    }\n\n\n\n    /**\n     * Returns the configuration's root section.\n     * @return the configuration's root section.\n     */\n    ConfigurationSection getRoot() {\n        return root;\n    }\n\n\n\n    /**\n     * Used to load configuration.\n     * @author Nicolas Rinaudo\n     */\n    private class ConfigurationLoader implements ConfigurationBuilder {\n        /** Parents of {@link #currentSection}. */\n        private Stack<ConfigurationSection> sections;\n        /** Fully qualified names of {@link #currentSection}. */\n        private Stack<String>               sectionNames;\n        /** Section that we're currently building. */\n        private ConfigurationSection        currentSection;\n\n\n        /**\n         * Creates a new configuration loader.\n         * @param root where to create the configuration in.\n         */\n        ConfigurationLoader(ConfigurationSection root) {\n            currentSection = root;\n        }\n\n\n\n        /**\n         * Initializes the configuration building.\n         */\n        public void startConfiguration() {\n            sections = new Stack<>();\n            sectionNames = new Stack<>();\n        }\n\n        /**\n         * Ends the configuration building.\n         * @throws ConfigurationException if not all opened sections have been closed.\n         */\n        public void endConfiguration() throws ConfigurationException {\n            // Makes sure currentSection is the root section.\n            if (!sections.empty()) {\n                throw new ConfigurationStructureException(\"Not all sections have been closed.\");\n            }\n            sections = null;\n            sectionNames = null;\n        }\n\n        /**\n         * Creates a new sub-section to the current section.\n         * @param name name of the new section.\n         */\n        public void startSection(String name) {\n            ConfigurationSection buffer;\n\n            buffer = currentSection.addSection(name);\n            sections.push(currentSection);\n            if (sectionNames.empty()) {\n                sectionNames.push(name + '.');\n            } else {\n                sectionNames.push(sectionNames.peek() + name + '.');\n            }\n            currentSection = buffer;\n        }\n\n        /**\n         * Ends the current section.\n         * @param  name                   name of the section that's being closed.\n         * @throws ConfigurationException if we're not closing a legal section.\n         */\n        public void endSection(String name) throws ConfigurationException {\n            ConfigurationSection buffer;\n\n            // Makes sure there is a section to close.\n            try {\n                buffer = sections.pop();\n                sectionNames.pop();\n            }  catch(EmptyStackException e) {\n                throw new ConfigurationStructureException(\"Section \" + name + \" was already closed.\");\n            }\n\n            // Makes sure we're closing the right section.\n            if (buffer.getSection(name) != currentSection) {\n                throw new ConfigurationStructureException(\"Section \" + name + \" is not the currently opened section.\");\n            }\n            currentSection = buffer;\n        }\n\n        /**\n         * Adds the specified variable to the current section.\n         * @param name  name of the variable.\n         * @param value value of the variable.\n         */\n        public void addVariable(String name, String value) {\n            // If the variable's value was modified, trigger an event.\n            if (currentSection.setVariable(name, value)) {\n                if (sectionNames.empty()) {\n                    triggerEvent(new ConfigurationEvent(Configuration.this, name, value));\n                } else {\n                    triggerEvent(new ConfigurationEvent(Configuration.this, sectionNames.peek() + name, value));\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/ConfigurationBuilder.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\n/**\n * Receive notification of the logical structure of a {@link Configuration configuration} instance.\n * <p>\n * If a class needs to be informed of the logical structure of a configuration instance,\n * it implements this interface and registers an instance with the {@link Configuration} using\n * its {@link Configuration#write(ConfigurationBuilder) build} method. The {@link Configuration}\n * uses the instance to report configuration related events such as the start of sections and\n * variable declarations.\n *\n * <p>\n * The <code>com.mucommander.commons.conf</code> API comes with a default <i>no-op</i> implementation,\n * {@link DefaultConfigurationBuilder}. This can be used instead of <code>ConfigurationBuilder</code>\n * when only a subset of the possible events are of interest.\n *\n * @author Nicolas Rinaudo\n * @see    DefaultConfigurationBuilder\n */\npublic interface ConfigurationBuilder {\n    /**\n     * Receives notification at the beginning of the configuration.\n     * <p>\n     * This method will only be invoked once, before any other method in this interface.\n     *\n     * @throws ConfigurationException any Configuration error, possibly wrapping another exception.\n     */\n    void startConfiguration() throws ConfigurationException;\n\n    /**\n     * Receives notification at the end of the configuration.\n     * <p>\n     * This method will be invoked at most once, and if it is, it will be the last one. If an\n     * unrecoverable error happens, this method might never be called.\n     *\n     * @throws ConfigurationException any Configuration error, possibly wrapping another exception.\n     */\n    void endConfiguration() throws ConfigurationException;\n\n    /**\n     * Receives notification at the beginning of a section.\n     * <p>\n     * This method will be invoked once at the beginning of every configuration section. Unless an\n     * unrecoverable error happens, there will be an {@link #endSection(String) endSection} event for every\n     * <code>startSection</code> event, even if the section is empty. All the section's content will be\n     * reported, in order, before the corresponding {@link #endSection(String) endSection} event.\n     *\n     * @param  name                   name of the new section.\n     * @throws ConfigurationException any Configuration error, possibly wrapping another exception.\n     */\n    void startSection(String name) throws ConfigurationException;\n\n    /**\n     * Receives notification at the end of a section.\n     * <p>\n     * This method will be invoked once at the end of every configuration section. There will be a\n     * corresponding {@link #startSection(String) startSection} event for every <code>endSection</code>\n     * event, even if the section is empty.\n     *\n     * @param  name                   name of the finished section.\n     * @throws ConfigurationException any Configuration error, possibly wrapping another exception.\n     */\n    void endSection(String name) throws ConfigurationException;\n\n    /**\n     * Receives notification of variable definition.\n     * <p>\n     * This method will be invoked once per variable found in a section. The declared variable\n     * will always belong to the section defined in the last {@link #startSection(String) startSection}\n     * event which hasn't yet been closed by an {@link #endSection(String) endSection} event. If there is\n     * no such section, the variable belongs to the unnamed root section.\n     *\n     * @param  name                   name of the new variable.\n     * @param  value                  value of the new variable.\n     * @throws ConfigurationException any Configuration error, possibly wrapping another exception.\n     */\n    void addVariable(String name, String value) throws ConfigurationException;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/ConfigurationEvent.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\n/**\n * An event which indicates that configuration has been modified.\n * <p>\n * The event is passed to every {@link ConfigurationListener} object which registered to receive such events using the\n * {@link Configuration}'s {@link Configuration#addConfigurationListener(ConfigurationListener) addConfigurationListener} method.\n * Each such listener object gets this <code>ConfigurationEvent</code> when the event occurs.\n *\n * @see    ConfigurationListener\n * @see    Configuration\n * @author Nicolas Rinaudo\n */\npublic class ConfigurationEvent {\n    /** Name of the variable that has been modified. */\n    private final String        name;\n    /** Variable's new value. */\n    private final String        value;\n    /** Configuration to which the event relates. */\n    private  final Configuration configuration;\n\n\n    /**\n     * Creates a new configuration event.\n     * <p>\n     * The event will describe a modification of variable <code>name</code> in configuration\n     * <code>configuration</code>, and indicate that it has been set to <code>value</code>.\n     *\n     * <p>\n     * <code>null</code> is an accepted value for parameter <code>value</code>, and will be\n     * interpreted to mean that the variable has been deleted.\n     *\n     * @param configuration configuration to which the event relates.\n     * @param name          name of the variable that was modified.\n     * @param value         value of the variable that was modified.\n     */\n    public ConfigurationEvent(Configuration configuration, String name, String value) {\n        this.name          = name;\n        this.value         = value;\n        this.configuration = configuration;\n    }\n\n\n    /**\n     * Returns the configuration to which the event relates.\n     * @return the configuration to which the event relates.\n     */\n    public Configuration getConfiguration() {\n        return configuration;\n    }\n\n    /**\n     * Returns the name of the variable that was modified.\n     * <p>\n     * The returned value will be the variable's fully qualified name. If, for example, the\n     * modified variable is <code>test.somevar</code>, this is what this method will return,\n     * not <code>somevar</code>.\n     *\n     * @return the name of the variable that was modified.\n     */\n    public String getVariable() {\n        return name;\n    }\n\n    /**\n     * Returns the new value for the modified variable.\n     * <p>\n     * If the variable has been deleted, this method will return <code>null</code>.\n     *\n     * @return the new value for the modified variable.\n     */\n    public String getValue() {\n        return value;\n    }\n\n    /**\n     * Returns the new value for the modified variable cast as an integer.\n     * <p>\n     * If the variable has been deleted, this method will return 0.\n     *\n     * @return                       the new value for the modified variable.\n     * @throws NumberFormatException if {@link #getValue()} cannot be cast as an integer.\n     */\n    public int getIntegerValue() throws NumberFormatException {\n        return ConfigurationSection.getIntegerValue(value);\n    }\n\n    /**\n     * Returns the new value for the modified variable cast as a float.\n     * <p>\n     * If the variable has been deleted, this method will return 0.\n     *\n     * @return                       the new value for the modified variable.\n     * @throws NumberFormatException if {@link #getValue()} cannot be cast as a float\n     */\n    public float getFloatValue() throws NumberFormatException {\n        return ConfigurationSection.getFloatValue(value);\n    }\n\n    /**\n     * Returns the new value for the modified variable cast as a boolean.\n     * <p>\n     * If the variable has been deleted, this method will return <code>false</code>.\n     *\n     * @return the new value for the modified variable.\n     */\n    public boolean getBooleanValue() {\n        return ConfigurationSection.getBooleanValue(value);\n    }\n\n    /**\n     * Returns the new value for the modified variable as a long.\n     * <p>\n     * If the variable has been deleted, this method will return 0.\n     *\n     * @return                       the new value for the modified variable.\n     * @throws NumberFormatException if {@link #getValue()} cannot be cast as a long.\n     */\n    public long getLongValue() throws NumberFormatException {\n        return ConfigurationSection.getLongValue(value);\n    }\n\n    /**\n     * Returns the new value for the modified variable cast as a double.\n     * <p>\n     * If the variable has been deleted, this method will return 0.\n     *\n     * @return                       the new value for the modified variable.\n     * @throws NumberFormatException if {@link #getValue()} cannot be cast as a double.\n     */\n    public double getDoubleValue() {\n        return ConfigurationSection.getDoubleValue(value);\n    }\n\n    /**\n     * Returns the new value for the modified variable cast as a {@link ValueList}.\n     * <p>\n     * If the variable has been deleted, this method will return null.\n     *\n     * @param  separator string used to tokenise the variable's value.\n     * @return           the new value for the modified variable.\n     */\n    public ValueList getListValue(String separator) {\n        return ConfigurationSection.getListValue(value, separator);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/ConfigurationException.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\n/**\n * Encapsulates a general configuration error.\n * <p>\n * This class can contain basic error information from either the <code>com.mucommander.commons.conf</code> API\n * or the application. Application writers can subclass it to provide additional functionality. Different\n * classes of the <code>com.mucommander.commons.conf</code> API may throw this exception or any exception subclassed\n * from it.\n * <p>\n * If the application needs to pass through other types of exceptions, it must wrap them in a\n * <code>ConfigurationException</code> or an exception derived from it.\n *\n * @author Nicolas Rinaudo\n */\npublic class ConfigurationException extends Exception {\n    /**\n     * Creates a new configuration exception.\n     * @param message the error message.\n     */\n    public ConfigurationException(String message) {super(message);}\n\n    /**\n     * Creates a new configuration exception wrapping an existing exception.\n     * <p>\n     * The existing exception will be embedded in the new one, and its message will\n     * become the default message for the <code>ConfigurationException</code>.\n     *\n     * @param cause the exception to be wrapped in a <code>ConfigurationException</code>.\n     */\n    public ConfigurationException(Throwable cause) {super(cause);}\n\n    /**\n     * Creates a new configuration exception from an existing exception.\n     * <p>\n     * The existing exception will be embedded in the new one, but the new exception will have its own message.\n     *\n     * @param message the detail message.\n     * @param cause   the exception to be wrapped in a <code>ConfigurationException</code>.\n     */\n    public ConfigurationException(String message, Throwable cause) {super(message, cause);}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/ConfigurationExplorer.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\n/**\n * Helper class meant for instances of {@link Configuration} to explore their own configuration tree.\n * @author Nicolas Rinaudo\n */\nclass ConfigurationExplorer {\n    /** Current section. */\n    private ConfigurationSection section;\n\n\n    /**\n     * Creates a new explorer on the specified section.\n     * @param root section from which to start exploring.\n     */\n    public ConfigurationExplorer(ConfigurationSection root) {\n        section = root;\n    }\n\n\n    /**\n     * Returns the current section.\n     * @return the current section.\n     */\n    public ConfigurationSection getSection() {\n        return section;\n    }\n\n    /**\n     * Move to the specified section.\n     * @param  name   name of the current section's subsection in which to move.\n     * @param  create if <code>true</code> and <code>name</code> doesn't exist, it will be created.\n     * @return        <code>true</code> if we could move to <code>name</code>, <code>false</code> otherwise.\n     */\n    public boolean moveTo(String name, boolean create) {\n        ConfigurationSection buffer = section.getSection(name); // Buffer for the subsection.\n        // Checks whether the requested subsection exists.\n        if (buffer == null) {\n            // If it doesn't exist, either return false or create it depending on parameters.\n            if (create) {\n                section = section.addSection(name);\n                return true;\n            }\n            return false;\n        }\n\n        section = buffer;\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/ConfigurationFormatException.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\n/**\n * Encapsulate a configuration format error.\n * <p>\n * Within the scope of the <code>com.mucommander.commons.conf</code> API, format errors\n * are syntax errors in a configuration source.\n * <p>\n * This exception is mostly meant to be used by implementations of {@link ConfigurationReader},\n * as they're the ones who will analyse the syntax of a configuration stream.\n * <p>\n * When applicable, instances of <code>ConfigurationFormatException</code> might provide information\n * about the position in the source at which the error occurred. See the documentation of\n * {@link #getLineNumber() getLineNumber} and {@link #getColumnNumber() getColumnNumber} for more\n * information on location conventions.\n * <p>\n * Since <code>ConfigurationFormatException</code> subclasses {@link ConfigurationException}, it\n * inherits his capacity to wrap other exceptions.\n *\n * @author Nicolas Rinaudo\n */\npublic class ConfigurationFormatException extends ConfigurationException {\n    /** Describes an unknown {@link #getLineNumber() line} or {@link #getColumnNumber() column} value.*/\n    public static final int UNKNOWN_LOCATION = -1;\n\n\n    /** Line at which the error occurred. */\n    private int line   = UNKNOWN_LOCATION;\n    /** Column at which the error occurred. */\n    private int column = UNKNOWN_LOCATION;\n\n\n    /**\n     * Creates a new configuration format exception.\n     * @param message the error message.\n     */\n    public ConfigurationFormatException(String message) {super(message);}\n\n    /**\n     * Creates a new configuration format exception.\n     * <p>\n     * {@link #UNKNOWN_LOCATION} is a legal value for both <code>line</code> and <code>column</code>.\n     * See the documentation of {@link #getLineNumber()  getLineNumber} and\n     * {@link #getColumnNumber() getColumnNumber} for more information on location conventions.\n     *\n     * @param message the error message.\n     * @param line    line at which the error occurred.\n     * @param column  column at which the error occurred.\n     */\n    public ConfigurationFormatException(String message, int line, int column) {\n        this(message);\n        setLocationInformation(line, column);\n    }\n\n    /**\n     * Creates a new configuration format exception wrapping an existing exception.\n     * <p>\n     * The existing exception will be embedded in the new one, and its message will\n     * become the default message for the <code>ConfigurationFormatException</code>.\n     *\n     * @param cause the exception to be wrapped in a <code>ConfigurationFormatException</code>.\n     */\n    public ConfigurationFormatException(Throwable cause) {super(cause == null ? null : cause.getMessage(), cause);}\n\n    /**\n     * Creates a new configuration format exception wrapping an existing exception.\n     * <p>\n     * The existing exception will be embedded in the new one, and its message will\n     * become the default message for the <code>ConfigurationFormatException</code>.\n     * <p>\n     * {@link #UNKNOWN_LOCATION} is a legal value for both <code>line</code> and <code>column</code>.\n     * See the documentation of {@link #getLineNumber() getLineNumber} and\n     * {@link #getColumnNumber() getColumnNumber} for more information on location conventions.\n     *\n     * @param cause  the exception to be wrapped in a <code>ConfigurationFormatException</code>.\n     * @param line   line at which the error occurred.\n     * @param column column at which the error occurred.\n     */\n    public ConfigurationFormatException(Throwable cause, int line, int column) {\n        this(cause);\n        setLocationInformation(line, column);\n    }\n\n    /**\n     * Creates a new configuration format exception from an existing exception.\n     * <p>\n     * The existing exception will be embedded in the new one, but the new exception will have its own message.\n     *\n     * @param message the detail message.\n     * @param cause   the exception to be wrapped in a <code>ConfigurationFormatException</code>.\n     */\n    public ConfigurationFormatException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    /**\n     * Creates a new configuration format exception from an existing exception.\n     * <p>\n     * The existing exception will be embedded in the new one, but the new exception will have its own message.\n     * <p>\n     * {@link #UNKNOWN_LOCATION} is a legal value for both <code>line</code> and <code>column</code>.\n     * See the documentation of {@link #getLineNumber() getLineNumber} and\n     * {@link #getColumnNumber() getColumnNumber} for more information on location conventions.\n     *\n     * @param message the detail message.\n     * @param cause   the exception to be wrapped in a <code>ConfigurationFormatException</code>.\n     * @param line    line at which the error occurred.\n     * @param column  column at which the error occurred.\n     */\n    public ConfigurationFormatException(String message, Throwable cause, int line, int column) {\n        this(message, cause);\n        setLocationInformation(line, column);\n    }\n\n\n    /**\n     * Sets the position in the stream at which the error occurred.\n     * @param line   line at which the error occurred.\n     * @param column column at which the error occurred.\n     */\n    private void setLocationInformation(int line, int column) {\n        this.line   = line;\n        this.column = column;\n    }\n\n    /**\n     * Returns the line at which the error occurred.\n     * <p>\n     * By convention, the line at which an error occurs is equal to the number of line breaks encountered\n     * before the problem, plus one. This means that a line number of <code>1</code> will describe the\n     * first line in the configuration source.\n     *\n     * @return the line at which the error occurred, {@link #UNKNOWN_LOCATION} if the information is not\n     *         available or relevant.\n     * @see    #getColumnNumber()\n     */\n    public int getLineNumber() {return line;}\n\n    /**\n     * Returns the column at which the error occurred.\n     * <p>\n     * By convention, the column at which an error occurs is equal to the number of character encountered\n     * after the last line break and before the problem, plus one. This means that a column number of\n     * <code>1</code> will describe the first character in the current line.\n     *\n     * @return the column at which the error occurred, {@link #UNKNOWN_LOCATION} if the information is not\n     *         available or relevant.\n     * @see    #getLineNumber()\n     */\n    public int getColumnNumber() {return column;}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/ConfigurationListener.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\n/**\n * Listener interface for receiving configuration events.\n * <p>\n * Implementations of this interface can register themselves to a {@link Configuration configuration} instance through\n * its {@link Configuration#addConfigurationListener(ConfigurationListener) addConfigurationListener} method to be\n * notified of configuration changes.\n *\n * @author Nicolas Rinaudo\n */\npublic interface ConfigurationListener {\n    /**\n     * Invoked when the configuration changes.\n     * @param event describes the configuration modification.\n     */\n    void configurationChanged(ConfigurationEvent event);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/ConfigurationReader.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\nimport java.io.IOException;\nimport java.io.Reader;\n\n/**\n * Interface for reading from a configuration source using callbacks.\n * <p>\n * Application writers that need to implement a specific configuration format need to subclass this.\n * Reader implementations have the task of parsing an input stream for configuration data and invoking the\n * relevant callback methods of {@link ConfigurationBuilder}.\n * <p>\n * The <code>com.mucommander.commons.conf</code> package comes with a default implementation,\n * {@link XmlConfigurationReader}, which handles the standard muCommander configuration file format.\n * <p>\n * In order for an implementation of <code>ConfigurationReader</code> to be usable by\n * {@link Configuration configuration} instances, it must come with an associated implementation of\n * {@link ConfigurationReaderFactory}.\n * <p>\n * In addition, most readers will have an associated writer used to write configuration files in a\n * format that the reader will understand.\n *\n * @author Nicolas Rinaudo\n * @see    ConfigurationReaderFactory\n */\npublic interface ConfigurationReader {\n    /**\n     * Reads configuration information from the specified input stream and invokes the specified builder's callback methods.\n     * <p>\n     * When applicable, this method is expected to throw {@link ConfigurationFormatException format} exceptions rather\n     * than {@link ConfigurationException configuration} exceptions. This will allow applications to report errors in a\n     * way that is useful for users.\n     *\n     * @param in                      where to read the configuration information from.\n     * @param builder                 where to send configuration messages to.\n     * @throws IOException            if an I/O error occurs.\n     * @throws ConfigurationException if another type of error occurs, in which case that error must be returned by <code>ConfigurationException.getCause()</code>.\n     */\n    void read(Reader in, ConfigurationBuilder builder) throws ConfigurationException, IOException;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/ConfigurationReaderFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\n/**\n * Interface used to provide instances of {@link Configuration} with a way of creating {@link ConfigurationReader reader} instances.\n * <p>\n * A <code>ConfigurationReaderFactory</code>'s sole purpose is to create instances of {@link ConfigurationReader}. In most cases, a\n * factory class will be associated with a reader class, and its code will look something like:\n * <pre>\n * public class MyReaderFactory implements ConfigurationReaderFactory {\n *    public ConfigurationReader getReaderInstance() {return new MyReader();}\n * }\n * </pre>\n *\n * @author Nicolas Rinaudo\n * @see    ConfigurationReader\n */\npublic interface ConfigurationReaderFactory<T extends ConfigurationReader> {\n    /**\n     * Creates an instance of {@link ConfigurationReader}.\n     * @return                              an instance of {@link ConfigurationReader}.\n     * @throws ReaderConfigurationException if the factory wasn't properly configured.\n     */\n    T getReaderInstance() throws ReaderConfigurationException;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/ConfigurationSection.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\nimport java.util.*;\n\n/**\n * Represents a section in the configuration tree.\n * @author Nicolas Rinaudo\n */\nclass ConfigurationSection {\n    /** Contains all the variables defined in the section. */\n    private final Map<String, String> variables = new HashMap<>();\n    /** Contains all the subsections defined the section. */\n    private final Map<String, ConfigurationSection> sections = new HashMap<>();\n\n\n    /**\n     * Removes the specified variable from the section.\n     * @param name name of the variable to remove.\n     * @return the value to which this variable was previously set, <code>null</code> if none.\n     */\n    public String removeVariable(String name) {\n        return variables.remove(name);\n    }\n\n    /**\n     * Returns the value of the specified variable.\n     * @param name name of the variable whose value should be returned.\n     * @return the value of the specified variable, or <code>null</code> if it wasn't set.\n     */\n    public String getVariable(String name) {\n        return variables.get(name);\n    }\n\n    /**\n     * Sets the specified variable to the specified value.\n     * <p>\n     * If <code>value</code> is either <code>null</code> or an empty string,\n     * the call will be equivalent to {@link #removeVariable(String)}.\n     *\n     * @param  name  name of the variable to set.\n     * @param  value value for the variable.\n     * @return       <code>true</code> if the variable's value was changed as a result of this call, <code>false</code>\n     *               otherwise.\n     */\n    public boolean setVariable(String name, String value) {\n        // If the specified value is empty, deletes the variable.\n        if (value == null || value.trim().isEmpty()) {\n            // If the variable wasn't set, we haven't changed its value.\n            if (getVariable(name) == null) {\n                return false;\n            }\n\n            // Otherwise, deletes it and returns true.\n            removeVariable(name);\n            return true;\n        }\n\n        // Compares the variable's new and old values.\n        String buffer = variables.put(name, value);\n        return buffer == null || !buffer.equals(value);\n    }\n\n    /**\n     * Returns a set on the names of the variables that are defined in the section.\n     * <p>\n     * Note that the order in which variable names are returned needs not be that in which they were added to the\n     * section. Callers should not rely on the order being consistent over time.\n     *\n     * @return an iterator on the names of the variables that are defined in the section.\n     */\n    public Set<String> variableNames() {\n        return variables.keySet();\n    }\n\n    /**\n     * Returns <code>true</code> if the section contains any variable.\n     * @return <code>true</code> if the section contains any variable, <code>false</code> otherwise.\n     */\n    public boolean hasVariables() {\n        return !variables.isEmpty();\n    }\n\n\n\n    /**\n     * Casts the specified value into an integer.\n     * <p>\n     * If <code>value</code> is <code>null</code>, this method will return <code>0</code>.\n     *\n     * @param value value to cast to an integer.\n     * @return <code>value</code> as an integer.\n     */\n    public static int getIntegerValue(String value) {\n        return value == null ? 0 : Integer.parseInt(value);\n    }\n\n    /**\n     * Casts the specified value into a value list.\n     * <p>\n     * If <code>value</code> is <code>null</code>, this method will return <code>null</code>.\n     *\n     * @param  value     value to cast to a value list.\n     * @param  separator string used to separate data in tokens.\n     * @return           <code>value</code> as a value list.\n     */\n    public static ValueList getListValue(String value, String separator) {\n        return value == null ? null : new ValueList(value, separator);\n    }\n\n    /**\n     * Casts the specified value into a float.\n     * <p>\n     * If <code>value</code> is <code>null</code>, this method will return <code>0</code>.\n     *\n     * @param value value to cast to a float.\n     * @return <code>value</code> as a float.\n     */\n    public static float getFloatValue(String value) {\n        return value == null ? 0 : Float.parseFloat(value);\n    }\n\n    /**\n     * Casts the specified value into an boolean.\n     * <p>\n     * If <code>value</code> is <code>null</code>, this method will return <code>false</code>.\n     *\n     * @param value value to cast to an boolean.\n     * @return <code>value</code> as an boolean.\n     */\n    public static boolean getBooleanValue(String value) {\n        return Boolean.TRUE.toString().equals(value);\n    }\n\n    /**\n     * Casts the specified value into an long.\n     * <p>\n     * If <code>value</code> is <code>null</code>, this method will return <code>0</code>.\n     *\n     * @param value value to cast to an long.\n     * @return <code>value</code> as an long.\n     */\n    public static long getLongValue(String value) {\n        return value == null ? 0 : Long.parseLong(value);\n    }\n\n    /**\n     * Casts the specified value into an double.\n     * <p>\n     * If <code>value</code> is <code>null</code>, this method will return <code>0</code>.\n     *\n     * @param value value to cast to an double.\n     * @return <code>value</code> as an double.\n     */\n    public static double getDoubleValue(String value) {\n        return value == null ? 0 : Double.parseDouble(value);\n    }\n\n    /**\n     * Casts the specified value into a string.\n     * @param value value to cast as a string.\n     * @return <code>value</code> as a string.\n     */\n    public static String getValue(int value) {\n        return Integer.toString(value);\n    }\n\n    /**\n     * Casts the specified value into a string.\n     * @param  value     value to cast as a string.\n     * @param  separator string to use as a separator.\n     * @return           <code>value</code> as a string.\n     */\n    public static String getValue(List<String> value, String separator) {\n        return ValueList.toString(value, separator);\n    }\n\n    /**\n     * Casts the specified value into a string.\n     * @param value value to cast as a string.\n     * @return <code>value</code> as a string.\n     */\n    public static String getValue(float value) {\n        return Float.toString(value);\n    }\n\n    /**\n     * Casts the specified value into a string.\n     * @param value value to cast as a string.\n     * @return <code>value</code> as a string.\n     */\n    public static String getValue(boolean value) {\n        return Boolean.toString(value);\n    }\n\n    /**\n     * Casts the specified value into a string.\n     * @param value value to cast as a string.\n     * @return <code>value</code> as a string.\n     */\n    public static String getValue(long value) {\n        return Long.toString(value);\n    }\n\n    /**\n     * Casts the specified value into a string.\n     * @param value value to cast as a string.\n     * @return <code>value</code> as a string.\n     */\n    public static String getValue(double value) {\n        return Double.toString(value);\n    }\n\n\n\n    // - Section access ------------------------------------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------------------------------------\n    /**\n     * Creates a subsection wit the specified name in the section.\n     * <p>\n     * If a subsection with the specified name already exists, it will be returned.\n     *\n     * @param  name name of the new section.\n     * @return      the subsection with the specified name.\n     */\n    public ConfigurationSection addSection(String name) {\n        ConfigurationSection section = getSection(name);\n\n        // The section already exists, returns it.\n        if (section != null) {\n            return section;\n        }\n        // Creates the new section.\n        section = new ConfigurationSection();\n        sections.put(name, section);\n        return section;\n    }\n\n    /**\n     * Deletes the specified section.\n     * @param  name name of the section to delete.\n     * @return      the section that was deleted if any, <code>null</code> otherwise.\n     */\n    public ConfigurationSection removeSection(String name) {\n        return sections.remove(name);\n    }\n\n    /**\n     * Deletes the specified section.\n     * <p>\n     * Note that this method is very inefficient and should only be called when strictly necessary.\n     *\n     * @param  section section to remove.\n     * @return         <code>true</code> if the specified section was removed, <code>false</code> if it didn't exist.\n     */\n    public boolean removeSection(ConfigurationSection section) {\n        Set<String> sectionNames = sectionNames();\n\n        // Goes through each key / value pair and checks whether we've found the section\n        // we were looking for.\n        for (String name : sectionNames) {\n            // If we have, remove it and break.\n            if (getSection(name).equals(section)) {\n                removeSection(name);\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns the subsection with the specified name.\n     * @param  name name of the section to retrieve.\n     * @return      the requested section if found, <code>null</code> otherwise.\n     */\n    public ConfigurationSection getSection(String name) {\n        return sections.get(name);\n    }\n\n    /**\n     * Returns an set on all of this section's subsections' names.\n     * <p>\n     * Note that the order in which section names are returned needs not be that in which they were added to the\n     * section. Callers should not rely on the order being consistent over time.\n     *\n     * @return an enumeration on all of this section's subsections' names.\n     */\n    public Set<String> sectionNames() {\n        return sections.keySet();\n    }\n\n    /**\n     * Returns <code>true</code> if this section has subsections.\n     * @return <code>true</code> if this section has subsections, <code>false</code> otherwise.\n     */\n    public boolean hasSections() {\n        return !sections.isEmpty();\n    }\n\n\n    /**\n     * Returns <code>true</code> if the section doesn't contain either variables or sub-sections.\n     * <p>\n     * This method is meant for {@link Configuration} instances to prune dead branches.\n     *\n     * @return <code>true</code> if the section doesn't contain either variables or sub-sections, <code>false</code>\n     *         otherwise.\n     */\n    public boolean isEmpty() {\n        return !hasSections() && !hasVariables();\n    }\n    \n    /**\n     * Remove all variables and sub-sections of the section\n     */\n    public void clear() {\n\t\tvariables.clear();\n        sections.clear();\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/ConfigurationSource.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.io.Writer;\n\n/**\n * Provides instances of {@link Configuration} with streams to configuration data.\n * <p>\n * Application writers that need to retrieve configuration data from a non-standard source (over the network, from a\n * database, ...) need to subclass this.\n * <p>\n * Implementations of this interface can be registered through {@link Configuration}'s\n * {@link Configuration#setSource(ConfigurationSource) setSource} method. Their purpose is\n * to provide the system with streams to a configuration source. This system allows applications\n * to retrieve their configuration information from non-standard sources, such as over the network,\n * in a database, ...\n * <p>\n * The <code>com.mucommander.commons.conf</code> package comes with a default implementation,\n * {@link FileConfigurationSource},\n * which will open input and output streams on a local file.\n *\n * @author Nicolas Rinaudo\n * @see    FileConfigurationSource\n */\npublic interface ConfigurationSource {\n    /**\n     * Returns an input stream on the configuration source.\n     * @return             an input stream on the configuration source.\n     * @throws IOException if any I/O error occurs.\n     */\n    Reader getReader() throws IOException;\n\n    /**\n     * Returns an output stream on the configuration source.\n     * @return             an output stream on the configuration source.\n     * @throws IOException if any I/O error occurs.\n     */\n    Writer getWriter() throws IOException;\n    \n    /**\n     * Returns whether this source exists\n     * @return true if the source exists, false otherwise.\n     *\n     * @throws IOException if any I/O error occurs.\n     */\n    boolean isExists() throws IOException;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/ConfigurationStructureException.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\n/**\n * Encapsulate a configuration structure error.\n * <p>\n * Within the scope of the <code>com.mucommander.commons.conf</code> API, structure errors\n * are inconsistencies in the structure of the configuration tree - a section closed more\n * than once, for example, or never closed at all.\n * <p>\n * This exception is mostly meant to be used by implementations of {@link ConfigurationBuilder},\n * as they have to analyse the structure of the configuration they're receiving events for.\n * <p>\n * Since <code>ConfigurationStructureException</code> subclasses {@link ConfigurationException}, it\n * inherits his capacity to wrap other exceptions.\n *\n * @author Nicolas Rinaudo\n */\npublic class ConfigurationStructureException extends ConfigurationException {\n    /**\n     * Creates a new configuration structure exception.\n     * @param message the error message.\n     */\n    public ConfigurationStructureException(String message) {super(message);}\n\n    /**\n     * Creates a new configuration structure exception wrapping an existing exception.\n     * <p>\n     * The existing exception will be embedded in the new one, and its message will\n     * become the default message for the <code>ConfigurationStructureException</code>.\n     *\n     * @param cause the exception to be wrapped in a <code>ConfigurationStructureException</code>.\n     */\n    public ConfigurationStructureException(Throwable cause) {super(cause);}\n\n    /**\n     * Creates a new configuration structure exception from an existing exception.\n     * <p>\n     * The existing exception will be embedded in the new one, but the new exception will have its own message.\n     *\n     * @param message the detail message.\n     * @param cause   the exception to be wrapped in a <code>ConfigurationStructureException</code>.\n     */\n    public ConfigurationStructureException(String message, Throwable cause) {super(message, cause);}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/ConfigurationWriterFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\nimport java.io.Writer;\n\n/**\n * Interface used to provide interfaces of {@link Configuration} with a way of creating writer instances.\n * <p>\n * A <code>ConfigurationWriterFactory</code>'s sole purpose is to create instances of writer. In most cases, a\n * factory class will be associated with a writer class, and its code will look something like:\n * <pre>\n * public class MyWriterFactory implements ConfigurationWriterFactory {\n *    public ConfigurationWriter getWriterInstance() {return new MyWriter();}\n * }\n * </pre>\n *\n * @author Nicolas Rinaudo\n */\npublic abstract class ConfigurationWriterFactory<T extends ConfigurationBuilder> {\n\t\n\t/** The name of the root element of the XML file */\n\tprivate final String rootElementName;\n\t\n\t/**\n\t * Constructor\n\t * For backward compatibility using root element \"prefs\" it is not mentioned otherwise\n\t */\n\tpublic ConfigurationWriterFactory() {\n\t\tthis(\"prefs\");\n\t}\n\t\n\t/**\n\t * Constructor\n\t * \n\t * @param rootElementName the name of the root element in the XML file\n\t */\n\tpublic ConfigurationWriterFactory(String rootElementName) {\n\t\tthis.rootElementName = rootElementName;\n\t}\n\t\n\t/**\n\t * Returns the name of the root element of the XML file\n\t * @return the name of the root element of the XML file\n\t */\n\tprotected String getRootElementName() {\n\t\treturn rootElementName;\n\t}\n\t\n    /**\n     * Creates an instance of {@link ConfigurationBuilder}.\n     * <p>\n     * The returned builder instance will serialize configuration events to the specified writer.\n     *\n     * @param  out                          where to write the configuration data.\n     * @return                              an instance of {@link ConfigurationBuilder}.\n     * @throws WriterConfigurationException if the factory wasn't properly configured.\n     */\n    public abstract T getWriterInstance(Writer out) throws WriterConfigurationException;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/DefaultConfigurationBuilder.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\n/**\n * Default base class for the analysis of the logical structure of a {@link Configuration}.\n * <p>\n * This class is available as a convenience for applications that need to explore the content\n * of a configuration tree. It provides <i>no-op</i> implementations for the methods defined in\n * {@link ConfigurationBuilder}, and application writers can override the ones that are of use\n * to them and ignore the others.\n *\n * @author Nicolas Rinaudo\n */\npublic class DefaultConfigurationBuilder implements ConfigurationBuilder {\n    /**\n     * Receive notification at the beginning of the configuration.\n     * <p>\n     * By default, do nothing. Application writers may override this method in a subclass to take\n     * specific actions at the beginning of a document (such as allocating the root node of a tree\n     * or creating an output file).\n     *\n     */\n    public void startConfiguration() {}\n\n    /**\n     * Receive notification at the end of the configuration.\n     * <p>\n     * By default, do nothing. Application writers may override this method in a subclass to take\n     * specific actions at the end of a document (such as finalising a tree or closing an output file).\n     *\n     */\n    public void endConfiguration() {}\n\n    /**\n     * Receive notification at the beginning of a section.\n     * <p>\n     * By default, do nothing. Application writers may override this method in a subclass to take\n     * specific actions at the start of each element (such as allocating a new tree node or writing\n     * output to a file).\n     *\n     * @param  name                   name of the new section.\n     */\n    public void startSection(String name) {}\n\n    /**\n     * Receive notification at the end of a section.\n     * <p>\n     * By default, do nothing. Application writers may override this method in a subclass to take\n     * specific actions at the end of each element (such as finalising a tree node or writing output\n     * to a file).\n     *\n     * @param  name                   name of the finished section.\n     */\n    public void endSection(String name) {}\n\n    /**\n     * Receive notification of variable definition.\n     * <p>\n     * By default, do nothing. Application writers may override this method to take specific actions for\n     * each variable definition (such as adding a leaf to a tree node, or printing it to a file).\n     *\n     * @param  name                   name of the new variable.\n     * @param  value                  value of the new variable.\n     */\n    public void addVariable(String name, String value) {}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/FileConfigurationSource.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\nimport java.io.*;\nimport java.nio.charset.Charset;\n\n/**\n * Configuration source that work on a {@link File} instance.\n * @author Nicolas Rinaudo\n */\npublic class FileConfigurationSource implements ConfigurationSource {\n    /** Path to the file on which to open input and output streams. */\n    private final File file;\n    /** File's charset. */\n    private final Charset charset;\n\n\n    /**\n     * Creates a source that will open streams on the specified file.\n     * @param file file in which the configuration data is located.\n     * @deprecated Application developers should use {@link #FileConfigurationSource(File, Charset)} instead. This\n     *             constructor assumes the specified file to be <code>UTF-8</code> encoded.\n     */\n    @Deprecated\n    public FileConfigurationSource(File file) {\n        this(file, \"utf-8\");\n    }\n\n    /**\n     * Creates a source on the specified file and charset.\n     * @param file file in which the configuration data is located.\n     * @param charset charset in which the file is encoded.\n     */\n    public FileConfigurationSource(File file, Charset charset) {\n        this.file = file;\n        this.charset = charset;\n    }\n\n    /**\n     * Creates a source on the specified file and charset.\n     * @param file file in which the configuration data is located.\n     * @param charset charset in which the file is encoded.\n     */\n    FileConfigurationSource(File file, String charset) {\n        this(file, Charset.forName(charset));\n    }\n\n    /**\n     * Creates a source that will open streams on the specified file.\n     * @param path  path to the file in which the configuration data is located.\n     * @deprecated Application developers should use {@link #FileConfigurationSource(String, Charset)} instead. This\n     *             constructor assumes the specified file to be <code>UTF-8</code> encoded.\n     */\n    @Deprecated\n    public FileConfigurationSource(String path) {\n        this(new File(path));\n    }\n\n    /**\n     * Creates a source on the specified file and charset.\n     * @param path  path to the file in which the configuration data is located.\n     * @param charset charset in which the file is encoded.\n     */\n    FileConfigurationSource(String path, String charset) {\n        this(new File(path), charset);\n    }\n\n    /**\n     * Creates a source on the specified file and charset.\n     * @param path  path to the file in which the configuration data is located.\n     * @param charset charset in which the file is encoded.\n     */\n    public FileConfigurationSource(String path, Charset charset) {\n        this(new File(path), charset);\n    }\n\n\n\n    /**\n     * Returns the file on which input and output streams are opened.\n     * @return the file on which input and output streams are opened.\n     */\n    public File getFile() {\n        return file;\n    }\n\n    /**\n     * Returns the charset in which the {@link #getFile() configuration file} is encoded.\n     * @return the charset in which the {@link #getFile() configuration file} is encoded.\n     */\n    public Charset getCharset() {\n        return charset;\n    }\n\n\n\n    @Override\n    public Reader getReader() throws IOException {\n        InputStream is = new FileInputStream(file);\n        return new BufferedReader(new InputStreamReader(is, charset));\n    }\n\n    @Override\n    public Writer getWriter() throws IOException {\n        return new OutputStreamWriter(new FileOutputStream(file), charset);\n    }\n\n\t@Override\n\tpublic boolean isExists() {\n\t\treturn file.exists();\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/ReaderConfigurationException.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\n/**\n * Encapsulate a reader configuration error.\n * <p>\n * This exception is mostly meant to be used by implementations of {@link ConfigurationReaderFactory},\n * as they're the ones who will configure instances of {@link ConfigurationReader}.\n * <p>\n * Since <code>ReaderConfigurationException</code> subclasses {@link ConfigurationException}, it\n * inherits his capacity to wrap other exceptions.\n *\n * @author Nicolas Rinaudo\n */\npublic class ReaderConfigurationException extends ConfigurationException {\n    /**\n     * Creates a new reader configuration exception.\n     * @param message the error message.\n     */\n    public ReaderConfigurationException(String message) {super(message);}\n\n    /**\n     * Creates a new reader configuration exception wrapping an existing exception.\n     * <p>\n     * The existing exception will be embedded in the new one, and its message will\n     * become the default message for the <code>ReaderConfigurationException</code>.\n     *\n     * @param cause the exception to be wrapped in a <code>ReaderConfigurationException</code>.\n     */\n    public ReaderConfigurationException(Throwable cause) {super(cause);}\n\n    /**\n     * Creates a new reader configuration exception from an existing exception.\n     * <p>\n     * The existing exception will be embedded in the new one, but the new exception will have its own message.\n     *\n     * @param message the detail message.\n     * @param cause   the exception to be wrapped in a <code>ReaderConfigurationException</code>.\n     */\n    public ReaderConfigurationException(String message, Throwable cause) {super(message, cause);}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/SourceConfigurationException.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\n/**\n * Encapsulate a source configuration error.\n * <p>\n * This exception is meant to be thrown by {@link Configuration} whenever a method that requires a\n * {@link ConfigurationSource} to have been set is called.\n * <p>\n * Since <code>SourceConfigurationException</code> subclasses {@link ConfigurationException}, it\n * inherits his capacity to wrap other exceptions.\n *\n * @author Nicolas Rinaudo\n */\npublic class SourceConfigurationException extends ConfigurationException {\n    /**\n     * Creates a new source configuration exception.\n     * @param message the error message.\n     */\n    public SourceConfigurationException(String message) {super(message);}\n\n    /**\n     * Creates a new source configuration exception wrapping an existing exception.\n     * <p>\n     * The existing exception will be embedded in the new one, and its message will\n     * become the default message for the <code>SourceConfigurationException</code>.\n     *\n     * @param cause the exception to be wrapped in a <code>SourceConfigurationException</code>.\n     */\n    public SourceConfigurationException(Throwable cause) {super(cause);}\n\n    /**\n     * Creates a new source configuration exception from an existing exception.\n     * <p>\n     * The existing exception will be embedded in the new one, but the new exception will have its own message.\n     *\n     * @param message the detail message.\n     * @param cause   the exception to be wrapped in a <code>SourceConfigurationException</code>.\n     */\n    public SourceConfigurationException(String message, Throwable cause) {super(message, cause);}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/ValueIterator.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\nimport java.util.Iterator;\nimport java.util.NoSuchElementException;\n\n/**\n * Iterator with support for value casting.\n * <p>\n * Instances of this class can only be retrieved through {@link ValueList#valueIterator()}.\n *\n * @author Nicolas Rinaudo\n */\npublic class ValueIterator implements Iterator<String> {\n    /** Wrapped iterator. */\n    private final Iterator<String> iterator;\n\n\n    /**\n     * Creates a new <code>ValueIterator</code> wrapping the specified <code>iterator</code>.\n     * @param iterator iterator to wrap.\n     */\n    ValueIterator(Iterator<String> iterator) {\n        this.iterator = iterator;\n    }\n\n\n    /**\n     * Returns <code>true</code> if the iteration has more elements.\n     * (In other words, returns <code>true</code> if next would return an element rather than throwing an exception.)\n     * @return <code>true</code> if the iteration has more elements.\n     */\n    @Override\n    public boolean hasNext() {\n        return iterator.hasNext();\n    }\n\n    /**\n     * Returns the next element in the iteration.\n     * @return                        the next element in the iteration.\n     * @throws NoSuchElementException if the iteration has no more elements.\n     */\n    @Override\n    public String next() {\n        return iterator.next();\n    }\n\n    /**\n     * Throws an <code>UnsupportedOperationException</code>.\n     */\n    @Override\n    public void remove() {\n        throw new UnsupportedOperationException();\n    }\n\n\n\n    /**\n     * Returns the next value in the iterator as a string.\n     * @return                        the next value in the iterator as a string.\n     * @throws NoSuchElementException if the iteration has no more elements.\n     */\n    public String nextValue() {\n        return iterator.next();\n    }\n\n    /**\n     * Returns the next value in the iterator as a integer.\n     * @return                        the next value in the iterator as a integer.\n     * @throws NoSuchElementException if the iteration has no more elements.\n     * @throws NumberFormatException  if the value cannot be cast to an integer.\n     */\n    public int nextIntegerValue() {\n        return ConfigurationSection.getIntegerValue(nextValue());\n    }\n\n    /**\n     * Returns the next value in the iterator as a float.\n     * @return                        the next value in the iterator as a float.\n     * @throws NoSuchElementException if the iteration has no more elements.\n     * @throws NumberFormatException  if the value cannot be cast to a float.\n     */\n    public float nextFloatValue() {\n        return ConfigurationSection.getFloatValue(nextValue());\n    }\n\n    /**\n     * Returns the next value in the iterator as a long.\n     * @return                        the next value in the iterator as a long.\n     * @throws NoSuchElementException if the iteration has no more elements.\n     * @throws NumberFormatException  if the value cannot be cast to a long.\n     */\n    public long nextLongValue() {\n        return ConfigurationSection.getLongValue(nextValue());\n    }\n\n    /**\n     * Returns the next value in the iterator as a double.\n     * @return                        the next value in the iterator as a double.\n     * @throws NoSuchElementException if the iteration has no more elements.\n     * @throws NumberFormatException  if the value cannot be cast to a double.\n     */\n    public double nextDoubleValue() {\n        return ConfigurationSection.getDoubleValue(nextValue());\n    }\n\n    /**\n     * Returns the next value in the iterator as a boolean.\n     * @return                        the next value in the iterator as a boolean.\n     * @throws NoSuchElementException if the iteration has no more elements.\n     */\n    public boolean nextBooleanValue() {\n        return ConfigurationSection.getBooleanValue(nextValue());\n    }\n\n    /**\n     * Returns the next value in the iterator as a {@link ValueList}.\n     * @param  separator              stirng used to tokenise the next value.\n     * @return                        the next value in the iterator as a {@link ValueList}.\n     * @throws NoSuchElementException if the iteration has no more elements.\n     */\n    public ValueList nextListValue(String separator) {\n        return ConfigurationSection.getListValue(nextValue(), separator);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/ValueList.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.StringTokenizer;\n\n/**\n * Provides support for variables whose value is a list of tokens.\n * <p>\n * Such values will simply be split using a <code>StringTokenizer</code> and stored as a <code>java.util.List</code>.\n * <p>\n * In addition to the regular <code>List</code> methods, this class provides the same value casting mechanisms as\n * {@link Configuration} and {@link ConfigurationEvent}. These have been extended to iterators through the\n * {@link #valueIterator()} method.\n *\n * @author Nicolas Rinaudo\n */\npublic class ValueList extends ArrayList<String> {\n    /**\n     * Creates a new <code>ValueList</code> initialised with the specified data.\n     * @param data      data contained by the list.\n     * @param separator string used to separate <code>data</code> in tokens.\n     */\n    public ValueList(String data, String separator) {\n        StringTokenizer tokenizer = new StringTokenizer(data, separator);\n        while(tokenizer.hasMoreTokens()) {\n            add(tokenizer.nextToken());\n        }\n    }\n\n\n    /**\n     * Returns the value found at the specified index of the list as a string.\n     * @param  index index of the value to retrieve.\n     * @return       the value found at the specified index of the list as a string.\n     */\n    public String valueAt(int index) {\n        return get(index);\n    }\n\n    /**\n     * Returns the value found at the specified index of the list as an integer.\n     * @param  index                 index of the value to retrieve.\n     * @return                       the value found at the specified index of the list as an integer.\n     * @throws NumberFormatException if the value cannot be cast to an integer.\n     */\n    public int integerValueAt(int index) {\n        return ConfigurationSection.getIntegerValue(valueAt(index));\n    }\n\n    /**\n     * Returns the value found at the specified index of the list as a float.\n     * @param  index                 index of the value to retrieve.\n     * @return                       the value found at the specified index of the list as a float.\n     * @throws NumberFormatException if the value cannot be cast to a float.\n     */\n    public float floatValueAt(int index) {\n        return ConfigurationSection.getFloatValue(valueAt(index));\n    }\n\n    /**\n     * Returns the value found at the specified index of the list as a double.\n     * @param  index                 index of the value to retrieve.\n     * @return                       the value found at the specified index of the list as a double.\n     * @throws NumberFormatException if the value cannot be cast to a double.\n     */\n    public double doubleValueAt(int index) {\n        return ConfigurationSection.getDoubleValue(valueAt(index));\n    }\n\n    /**\n     * Returns the value found at the specified index of the list as a long.\n     * @param  index                 index of the value to retrieve.\n     * @return                       the value found at the specified index of the list as a long.\n     * @throws NumberFormatException if the value cannot be cast to a long.\n     */\n    public long longValueAt(int index) {\n        return ConfigurationSection.getLongValue(valueAt(index));\n    }\n\n    /**\n     * Returns the value found at the specified index of the list as an boolean.\n     * @param  index index of the value to retrieve.\n     * @return       the value found at the specified index of the list as an boolean.\n     */\n    public boolean booleanValueAt(int index) {\n        return ConfigurationSection.getBooleanValue(valueAt(index));\n    }\n\n    /**\n     * Returns the value found at the specified index of the list as a {@link ValueList}.\n     * @param  index     index of the value to retrieve.\n     * @param  separator string used to split the value into tokens.\n     * @return           the value found at the specified index of the list as a {@link ValueList}.\n     */\n    public ValueList listValueAt(int index, String separator) {\n        return ConfigurationSection.getListValue(valueAt(index), separator);\n    }\n\n    /**\n     * Returns a {@link ValueIterator} on the list.\n     * @return a {@link ValueIterator} on the list.\n     */\n    public ValueIterator valueIterator() {\n        return new ValueIterator(iterator());\n    }\n\n\n\n    /**\n     * Returns a string representation of the specified list.\n     * @param  data      values to represent as a string.\n     * @param  separator string used to separate one element from the other.\n     * @return           a string representation of the specified list.\n     */\n    public static String toString(List<?> data, String separator) {\n        StringBuilder buffer = new StringBuilder();\n        Iterator<?> values = data.iterator();\n\n        // Deals with the first value separately.\n        if (values.hasNext()) {\n            buffer.append(values.next().toString());\n        }\n\n        // All subsequent values will be concatenated after a separator.\n        while (values.hasNext()) {\n            buffer.append(separator);\n            buffer.append(values.next().toString());\n        }\n\n        // Returns the final value.\n        return buffer.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/WriterConfigurationException.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\n/**\n * Encapsulate a writer configuration error.\n * <p>\n * This exception is mostly meant to be used by implementations of writer,\n * as they're the ones who will configure instances of {@link ConfigurationBuilder}.\n * <p>\n * Since <code>WriterConfigurationException</code> subclasses {@link ConfigurationException}, it\n * inherits his capacity to wrap other exceptions.\n * @author Nicolas Rinaudo\n */\npublic class WriterConfigurationException extends ConfigurationException {\n    /**\n     * Creates a new writer configuration exception.\n     * @param message the error message.\n     */\n    public WriterConfigurationException(String message) {\n        super(message);\n    }\n\n    /**\n     * Creates a new writer configuration exception wrapping an existing exception.\n     * <p>\n     * The existing exception will be embedded in the new one, and its message will\n     * become the default message for the <code>WriterConfigurationException</code>.\n     *\n     * @param cause the exception to be wrapped in a <code>WriterConfigurationException</code>.\n     */\n    public WriterConfigurationException(Throwable cause) {\n        super(cause);\n    }\n\n    /**\n     * Creates a new writer configuration exception from an existing exception.\n     * <p>\n     * The existing exception will be embedded in the new one, but the new exception will have its own message.\n     *\n     * @param message the detail message.\n     * @param cause   the exception to be wrapped in a <code>WriterConfigurationException</code>.\n     */\n    public WriterConfigurationException(String message, Throwable cause) {\n        super(message, cause);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/XmlConfigurationReader.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\nimport org.xml.sax.*;\nimport org.xml.sax.helpers.DefaultHandler;\n\nimport javax.xml.parsers.ParserConfigurationException;\nimport javax.xml.parsers.SAXParserFactory;\nimport java.io.IOException;\nimport java.io.Reader;\n\n/**\n * Implementation of {@link ConfigurationReader} used to read XML configuration streams.\n * <p>\n * The format of XML files parsed by instances of <code>XmlConfigurationReader</code> is fairly simple:\n * <ul>\n *   <li>\n *     Any element that doesn't contain other elements is considered to be a variable. Its value will be\n *     the CDATA contained by the element.\n *   </li>\n *   <li>\n *     Any element that contains other elements is considered to be a section. Any CDATA it might contain\n *     will be ignored.\n *   </li>\n *   <li>\n *     The XML file's first element is traditionally called <code>prefs</code>, but this isn't enforced. It\n *     will be excluded from section names.\n *   </li>\n * </ul>\n *\n * <p>\n * For example:\n * <pre>\n * &lt;prefs&gt;\n *   &lt;some&gt;\n *     Random CDATA\n *     &lt;section&gt;\n *       &lt;var1&gt;value1&lt;/var1&gt;\n *       &lt;var2&gt;value2&lt;/var2&gt;\n *     &lt;/section&gt;\n *   &lt;/some&gt;\n * &lt;/prefs&gt;\n * </pre>\n * This will be interpreted as follows:\n * <ul>\n *   <li><code>Random CDATA</code> will be ignored.</li>\n *   <li>A variable called <code>some.section.var1</code> will be created with a value of <code>value1</code>.</li>\n *   <li>A variable called <code>some.section.var2</code> will be created with a value of <code>value2</code>.</li>\n * </ul>\n *\n * @author Nicolas Rinaudo\n * @see    XmlConfigurationWriter\n */\npublic class XmlConfigurationReader extends DefaultHandler implements ConfigurationReader {\n    /** Factory used to create {@link XmlConfigurationReader} instances. */\n    public static final ConfigurationReaderFactory<XmlConfigurationReader> FACTORY;\n\n\n    /** Current depth in the configuration tree. */\n    private       int                  depth;\n    /** Buffer for each element's CDATA. */\n    private final StringBuilder        buffer;\n    /** Name of the item being parsed. */\n    private       String               itemName;\n    /** Class notified whenever a new configuration item is found. */\n    protected     ConfigurationBuilder builder;\n    /** Whether the current element is a variable. */\n    private       boolean              isVariable;\n    /** Used to track the parser's position in the XML file. */\n    private       Locator              locator;\n\n    static {\n        FACTORY = XmlConfigurationReader::new;\n    }\n\n    /**\n     * Creates a new instance of XML configuration reader.\n     */\n    public XmlConfigurationReader() {\n        buffer = new StringBuilder();\n    }\n\n\n\n    // - Reader methods ------------------------------------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------------------------------------\n    /**\n     * Reads the content of <code>in</code> a passes build messages to <code>builder</code>.\n     * @param in input stream from which to read the configuration data.\n     * @param  builder                      object to notify of build events.\n     * @throws IOException                  if an I/O error occurs.\n     * @throws ConfigurationFormatException if a configuration file format occurs.\n     * @throws ConfigurationException       if a non-specific error occurs.\n     */\n    public void read(Reader in, ConfigurationBuilder builder) throws IOException, ConfigurationException {\n        this.builder = builder;\n        locator = null;\n        try {\n            SAXParserFactory.newInstance().newSAXParser().parse(new InputSource(in), this);\n        } catch (ParserConfigurationException e) {\n            throw new ConfigurationException(\"Failed to create a SAX parser\", e);\n        } catch (SAXParseException e) {\n            throw new ConfigurationFormatException(e.getMessage(), e.getLineNumber(), e.getColumnNumber());\n        } catch (SAXException e) {\n            throw new ConfigurationFormatException(e.getException() == null ? e : e.getException());\n        }\n    }\n\n\n\n    // - XML handling --------------------------------------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------------------------------------\n    /**\n     * This method is public as an implementation side effect and should never be called directly.\n     */\n    @Override\n    public void characters(char[] ch, int start, int length) {buffer.append(ch, start, length);}\n\n    /**\n     * This method is public as an implementation side effect and should never be called directly.\n     */\n    @Override\n    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {\n        depth++;\n        if (depth == 1) {\n            return;\n        }\n\n        if (itemName != null) {\n            try {\n                builder.startSection(itemName);\n            } catch(Exception e) {\n                throw new SAXParseException(e.getMessage(), locator, e);\n            }\n        }\n        buffer.setLength(0);\n        itemName = qName;\n        isVariable = true;\n    }\n\n    /**\n     * This method is public as an implementation side effect and should never be called directly.\n     */\n    @Override\n    public void endElement(String uri, String localName, String qName) throws SAXException {\n        depth--;\n        if (depth == 0) {\n            return;\n        }\n\n        // If the current element doesn't have subsections, considers it to be a variable.\n        if(isVariable) {\n            String value = buffer.toString().trim();\n\n            // Ignores empty values, otherwise notifies the builder of a new variable.\n            if(!value.isEmpty()) {\n                try {\n                    builder.addVariable(qName, value);\n                } catch(Exception e) {\n                    throw new SAXParseException(e.getMessage(), locator, e);\n                }\n            }\n        }\n\n        // The current element is a container, closes it.\n        else {\n            try {\n                builder.endSection(qName);\n            } catch(Exception e) {\n                throw new SAXParseException(e.getMessage(), locator, e);\n            }\n        }\n\n        isVariable = false;\n        itemName = null;\n    }\n\n    /**\n     * This method is public as an implementation side effect and should never be called directly.\n     */\n    @Override\n    public void startDocument() throws SAXException {\n        try {\n            builder.startConfiguration();\n        } catch(Exception e) {\n            throw new SAXParseException(e.getMessage(), locator, e);\n        }\n    }\n\n    /**\n     * This method is public as an implementation side effect and should never be called directly.\n     */\n    @Override\n    public void endDocument() throws SAXException {\n        try {\n            builder.endConfiguration();\n        } catch(Exception e) {\n            throw new SAXParseException(e.getMessage(), locator, e);\n        }\n    }\n\n    /**\n     * This method is public as an implementation side effect and should never be called directly.\n     */\n    @Override\n    public void setDocumentLocator(Locator locator) {\n        this.locator = locator;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/XmlConfigurationWriter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\nimport org.xml.sax.Attributes;\nimport org.xml.sax.ContentHandler;\nimport org.xml.sax.SAXException;\nimport org.xml.sax.helpers.AttributesImpl;\n\nimport javax.xml.transform.OutputKeys;\nimport javax.xml.transform.TransformerConfigurationException;\nimport javax.xml.transform.sax.SAXTransformerFactory;\nimport javax.xml.transform.sax.TransformerHandler;\nimport javax.xml.transform.stream.StreamResult;\nimport java.io.Writer;\n\n/**\n * Implementation of {@link ConfigurationBuilder} used to write XML configuration streams.\n * <p>\n * Information on the XML file format can be found {@link XmlConfigurationReader here}.\n *\n * @author Nicolas Rinaudo\n */\npublic class XmlConfigurationWriter implements ConfigurationBuilder {\n    /** Factory used to create instances of {@link XmlConfigurationWriter}. */\n    public static final ConfigurationWriterFactory<XmlConfigurationWriter> FACTORY;\n\n    /** Writer on the destination XML stream. */\n    protected final ContentHandler out;\n    /** Empty XML attributes (avoids creating a new instance on each <code>startElement</code> call). */\n    private   final Attributes     emptyAttributes = new AttributesImpl();\n    /** Root element name. */\n    protected   final String rootElementName;\n\n\n    static {\n        FACTORY = new ConfigurationWriterFactory<XmlConfigurationWriter>() {\n            public XmlConfigurationWriter getWriterInstance(Writer out) {\n                return new XmlConfigurationWriter(out, getRootElementName());\n            }\n        };\n    }\n\n    /**\n     * Creates a new instance of XML configuration writer.\n     * @param out where to write the configuration data.\n     * @param rootElementName the name of root element\n     */\n    protected XmlConfigurationWriter(Writer out, String rootElementName) {\n    \tthis.rootElementName = rootElementName;\n        this.out = createHandler(out);\n    }\n\n\n    private static ContentHandler createHandler(Writer out) {\n        // Initializes the transformer factory.\n        SAXTransformerFactory factory = (SAXTransformerFactory)SAXTransformerFactory.newInstance();\n        //factory.setAttribute(\"indent-number\", 4);\n\n        // Creates a new transformer.\n        TransformerHandler    transformer;\n        try {\n            transformer = factory.newTransformerHandler();\n        } catch(TransformerConfigurationException e) {\n            throw new IllegalStateException(e);\n        }\n\n        // Enables indentation.\n        transformer.getTransformer().setOutputProperty(OutputKeys.INDENT, \"yes\");\n\n        // Sets the standalone property.\n        transformer.getTransformer().setOutputProperty(OutputKeys.STANDALONE, \"yes\");\n\n        // Plugs the transformer into the specified stream.\n        transformer.setResult(new StreamResult(out));\n\n        return transformer;\n    }\n\n\n    protected void startElement(String name) throws ConfigurationException {\n        try {\n            out.startElement(\"\", name, name, emptyAttributes);\n        } catch(SAXException e) {\n            throw new ConfigurationException(e);\n        }\n    }\n\n    protected void endElement(String name) throws ConfigurationException {\n        try {\n            out.endElement(\"\", name, name);\n        } catch(SAXException e) {\n            throw new ConfigurationException(e);\n        }\n    }\n\n    /**\n     * Starts a new configuration section.\n     * @param  name                   name of the new section.\n     * @throws ConfigurationException as a wrapper for any <code>IOException</code> that might have occurred.\n     */\n    public void startSection(String name) throws ConfigurationException {\n        startElement(name);\n    }\n\n    /**\n     * Ends a configuration section.\n     * @param  name                   name of the closed section.\n     * @throws ConfigurationException as a wrapper for any <code>IOException</code> that might have occurred.\n     */\n    public void endSection(String name) throws ConfigurationException {\n        endElement(name);\n    }\n\n    /**\n     * Creates a new variable in the current section.\n     * @param  name                   name of the new variable.\n     * @param  value                  value of the new variable.\n     * @throws ConfigurationException as a wrapper for any <code>IOException</code> that might have occurred.\n     */\n    public void addVariable(String name, String value) throws ConfigurationException {\n        char[] data;\n\n        try {\n            startElement(name);\n            data = value.toCharArray();\n            out.characters(data, 0, data.length);\n            endElement(name);\n        } catch(SAXException e) {\n            throw new ConfigurationException(e);\n        }\n    }\n\n    /**\n     * Writes the XML header.\n     * @throws ConfigurationException as a wrapper for any exception that might have occurred.\n     */\n    public void startConfiguration() throws ConfigurationException {\n        try {\n            out.startDocument();\n            startElement(rootElementName);\n        } catch(SAXException e) {\n            throw new ConfigurationException(e);\n        }\n    }\n\n    /**\n     * Writes the XML footer.\n     * @throws ConfigurationException as a wrapper for any <code>IOException</code> that might have occurred.\n     */\n    public void endConfiguration() throws ConfigurationException {\n        try {\n            endElement(rootElementName);\n            out.endDocument();\n        } catch(SAXException e) {\n            throw new ConfigurationException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/conf/package-info.java",
    "content": "/**\n * Provides classes to deal with software configuration.\n * <h3>Configuration variables</h3>\n * <p>\n * Configuration data is stored as a set of variables organised in sections. A typical variable\n * name is: <code>section.subsection.name</code> where:\n * <ul>\n * <li><code>section</code> and <code>subsection</code> are both sections.</li>\n * <li><code>name</code> is the variable's name.</li>\n * </ul>\n * <p>\n * Configuration data is stored in instances of {@link com.mucommander.commons.conf.Configuration}, which offers a set\n * of methods manipulate variables:\n * <ul>\n * <li>\n * {@link com.mucommander.commons.conf.Configuration#getVariable(String) Basic retrieval}, which returns a\n * variable's value if known.\n * </li>\n * <li>\n * {@link com.mucommander.commons.conf.Configuration#getVariable(String,String) Advanced retrieval}, which returns\n * a variable's value and set it to a default value if not known.\n * </li>\n * <li>\n * Existence {@link com.mucommander.commons.conf.Configuration#isVariableSet(String) checking}, which checks\n * whether a variable exists or not.\n * </li>\n * <li>\n * {@link com.mucommander.commons.conf.Configuration#renameVariable(String,String) Renaming}, which changes a\n * variable's name as well as the section it belongs to.\n * </li>\n * <li>\n * {@link com.mucommander.commons.conf.Configuration#removeVariable(String) Removal}, which deletes a variable from\n * the configuration.\n * </li>\n * <li>\n * {@link com.mucommander.commons.conf.Configuration#setVariable(String,String) Setting}, which sets a variable's\n * value.\n * </li>\n * </ul>\n * <h3>Loading and storing configuration</h3>\n * <p>\n * The <code>com.mucommander.commons.conf</code> package offers various ways of loading and storing configuration.<br>\n * The most obvious way is by using the {@link com.mucommander.commons.conf.Configuration#read(java.io.Reader) read} and\n * {@link com.mucommander.commons.conf.Configuration#write(java.io.Writer)} methods, but this has the disadvantage\n * of forcing application writers to manage streams themselves.<br>\n * The preferred method is to create a dedicated {@link com.mucommander.commons.conf.ConfigurationSource} class and\n * register it through {@link com.mucommander.commons.conf.Configuration#setSource(ConfigurationSource) setSource}.\n * This allows an instance of {@link com.mucommander.commons.conf.Configuration} to know how to read from and write to\n * its configuration file (or socket or any other medium that provides input and output streams).\n *\n * <h3>Changing the default configuration format</h3>\n * <p>\n * The default configuration format is described in {@link com.mucommander.commons.conf.XmlConfigurationReader}.\n * Application writers who wish to change this can do so by:\n * <ul>\n * <li>\n * Creating custom {@link com.mucommander.commons.conf.ConfigurationBuilder writers} and\n * {@link com.mucommander.commons.conf.ConfigurationReader readers}.\n * </li>\n * <li>\n * Creating associated {@link com.mucommander.commons.conf.ConfigurationWriterFactory writer factories} and\n * {@link com.mucommander.commons.conf.ConfigurationReaderFactory reader factories}.\n * </li>\n * <li>\n * Registering them through\n * {@link com.mucommander.commons.conf.Configuration#setWriterFactory(ConfigurationWriterFactory) setWriterFactory}\n * and\n * {@link com.mucommander.commons.conf.Configuration#setReaderFactory(ConfigurationReaderFactory) setReaderFactory}.\n * </li>\n * </ul>\n *\n * <h3>Listening to the configuration</h3>\n * <p>\n * Classes that need to be notified when the configuration has changed can do so by:\n * <ul>\n * <li>Implementing the {@link com.mucommander.commons.conf.ConfigurationListener} interface.</li>\n * <li>\n * Registering themselves through\n * {@link com.mucommander.commons.conf.Configuration#addConfigurationListener(ConfigurationListener)\n * addConfigurationLister}.\n * </li>\n * </ul>\n */\npackage com.mucommander.commons.conf;"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/AbstractArchiveEntryFile.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.commons.file;\n\nimport com.mucommander.commons.file.filter.FileFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\nimport com.mucommander.commons.io.ByteUtils;\nimport com.mucommander.commons.io.RandomAccessInputStream;\nimport com.mucommander.commons.io.RandomAccessOutputStream;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n\n/**\n * <code>AbstractArchiveEntryFile</code> represents a file entry inside an archive.\n * An <code>AbstractArchiveEntryFile</code> is always associated with an {@link ArchiveEntry} object which contains\n * information about the entry (name, size, date, ...) and with an {@link AbstractArchiveFile} which acts as an entry\n * repository and provides operations such as listing a directory entry's files, adding or removing entries\n * (if the archive is writable), etc...\n *\n * <p>\n * <code>AbstractArchiveEntryFile</code> implements {@link com.mucommander.commons.file.AbstractFile} by delegating methods to\n * the <code>ArchiveEntry</code> and <code>AbstractArchiveFile</code> instances.\n * <code>AbstractArchiveEntryFile</code> is agnostic to the actual archive format. In other words, there is no need to\n * extend this class for a particular archive format, <code>ArchiveEntry</code> and <code>AbstractArchiveFile</code>\n * provide a generic framework that isolates from the archive format's specifics.\n * <p>\n * This class is abstract (as the name implies) and implemented by two subclasses:\n * <ul>\n *   <li>{@link ROArchiveEntryFile}: represents an entry inside a read-only archive</li>\n *   <li>{@link RWArchiveEntryFile}: represents an entry inside a {@link AbstractArchiveFile#isWritable() read-write} archive</li>\n * </ul>\n *\n * @see AbstractArchiveFile\n * @see ArchiveEntry\n * @author Maxence Bernard\n */\npublic abstract class AbstractArchiveEntryFile extends AbstractFile {\n\n    /** The archive file that contains this entry */\n    final AbstractArchiveFile archiveFile;\n\n    /** This entry file's parent, can be the archive file itself if this entry is located at the top level */\n    protected AbstractFile parent;\n\n    /** The ArchiveEntry object that contains information about this entry */\n    final protected ArchiveEntry entry;\n\n\n    /**\n     * Creates a new AbstractArchiveEntryFile.\n     *\n     * @param url the FileURL instance that represents this file's location\n     * @param archiveFile the AbstractArchiveFile instance that contains this entry\n     * @param entry the ArchiveEntry object that contains information about this entry\n     */\n    protected AbstractArchiveEntryFile(FileURL url, AbstractArchiveFile archiveFile, ArchiveEntry entry) {\n        super(url);\n        this.archiveFile = archiveFile;\n        this.entry = entry;\n    }\n\n\n    /**\n     * Returns the ArchiveEntry instance that contains information about the archive entry (path, size, date, ...).\n     *\n     * @return the ArchiveEntry instance that contains information about the archive entry (path, size, date, ...)\n     */\n    public ArchiveEntry getEntry() {\n        return entry;\n    }\n\n    /**\n     * Returns the {@link AbstractArchiveFile} that contains the entry represented by this file.\n     *\n     * @return the AbstractArchiveFile that contains the entry represented by this file\n     */\n    AbstractArchiveFile getArchiveFile() {\n        return archiveFile;\n    }\n\n\n    /**\n     * Returns the relative path of this entry, with respect to the archive file. The path separator of the returned\n     * path is the one returned by {@link #getSeparator()}. As a relative path, the returned path does not start\n     * with a separator character.\n     *\n     * @return the relative path of this entry, with respect to the archive file.\n     */\n    private String getRelativeEntryPath() {\n        String path = entry.getPath();\n\n        // Replace all occurrences of the entry's separator by the archive file's separator, only if the separator is\n        // not \"/\" (i.e. the entry path separator).\n        String separator = getSeparator();\n        if (!separator.equals(\"/\")) {\n            path = path.replace(\"/\", separator);\n        }\n\n        return path;\n    }\n\n\n    /////////////////////////////////\n    // AbstractFile implementation //\n    /////////////////////////////////\n\n    @Override\n    public long getLastModifiedDate() {\n        return entry.getLastModifiedDate();\n    }\n\n    @Override\n    public long getSize() {\n        return entry.getSize();\n    }\n\t\n    @Override\n    public boolean isDirectory() {\n        return entry.isDirectory();\n    }\n\n    @Override\n    public boolean isArchive() {\n        // Archive entries files may be wrapped by archive files but they are not archive files per se\n        return false;\n    }\n\n    @Override\n    public AbstractFile[] ls() throws IOException {\n        return archiveFile.ls(this, null, null);\n    }\n\n    @Override\n    public AbstractFile[] ls(FilenameFilter filter) throws IOException {\n        return archiveFile.ls(this, filter, null);\n    }\n\t\n    @Override\n    public AbstractFile[] ls(FileFilter filter) throws IOException {\n        return archiveFile.ls(this, null, filter);\n    }\n\n    @Override\n    public AbstractFile getParent() {\n        return parent;\n    }\n\t\n    @Override\n    public void setParent(AbstractFile parent) {\n        this.parent = parent;\t\n    }\n\n    /**\n     * Returns <code>true</code> if this entry exists within the archive file.\n     *\n     * @return true if this entry exists within the archive file\n     */\n    @Override\n    public boolean exists() {\n        return entry.exists();\n    }\n\t\n    @Override\n    public FilePermissions getPermissions() {\n        // Return the entry's permissions\n        return entry.getPermissions();\n    }\n\n    @Override\n    public void changePermission(int access, int permission, boolean enabled) throws IOException {\n        changePermissions(ByteUtils.setBit(getPermissions().getIntValue(), (permission << (access*3)), enabled));\n    }\n\n    @Override\n    public String getOwner() {\n        return entry.getOwner();\n    }\n\n    @Override\n    public boolean canGetOwner() {\n        return entry.getOwner()!=null;\n    }\n\n    @Override\n    public String getGroup() {\n        return entry.getGroup();\n    }\n\n    @Override\n    public boolean canGetGroup() {\n        return entry.getGroup()!=null;\n    }\n\n    /**\n     * Always returns <code>false</code>.\n     */\n    @Override\n    public boolean isSymlink() {\n        return false;\n    }\n\n    /**\n     * Always returns <code>false</code>.\n     */\n    @Override\n    public boolean isSystem() {\n        return false;\n    }\n\n    /**\n     * Delegates to the archive file's {@link AbstractArchiveFile#getFreeSpace()} method.\n     *\n     * @throws IOException if an I/O error occurred\n     * @throws UnsupportedFileOperationException if the underlying archive file does not support\n     * {@link FileOperation#GET_FREE_SPACE} operations.\n     */\n    @Override\n    public long getFreeSpace() throws IOException {\n        return archiveFile.getFreeSpace();\n    }\n\n    /**\n     * Delegates to the archive file's {@link AbstractArchiveFile#getTotalSpace()} method.\n     *\n     * @throws IOException if an I/O error occurred\n     * @throws UnsupportedFileOperationException if the underlying archive file does not support\n     * {@link FileOperation#GET_TOTAL_SPACE} operations.\n     */\n    @Override\n    public long getTotalSpace() throws IOException {\n        return archiveFile.getTotalSpace();\n    }\n\n    /**\n     * Delegates to the archive file's {@link AbstractArchiveFile#getEntryInputStream(ArchiveEntry,ArchiveEntryIterator)}}\n     * method.\n     *\n     * @throws UnsupportedFileOperationException if the underlying archive file does not support\n     * {@link FileOperation#READ_FILE} operations.\n     */\n    @Override\n    public InputStream getInputStream() throws IOException {\n        return archiveFile.getEntryInputStream(entry, null);\n    }\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException}: append is not available for archive entries.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public OutputStream getAppendOutputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.APPEND_FILE);\n    }\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException}: random read access is not available for archive\n     * entries.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public RandomAccessInputStream getRandomAccessInputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.RANDOM_READ_FILE);\n    }\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException}: random write access is not available for archive\n     * entries.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public RandomAccessOutputStream getRandomAccessOutputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.RANDOM_WRITE_FILE);\n    }\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException} when called.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void copyRemotelyTo(AbstractFile destFile) throws UnsupportedFileOperationException {\n        // TODO: we could consider adding remote copy support to RWArchiveEntryFile\n        throw new UnsupportedFileOperationException(FileOperation.COPY_REMOTELY);\n    }\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException} when called.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void renameTo(AbstractFile destFile) throws UnsupportedFileOperationException {\n        // TODO: we could consider adding renaming support to RWArchiveEntryFile\n        throw new UnsupportedFileOperationException(FileOperation.RENAME);\n    }\n\n    /**\n     * Returns the same ArchiveEntry instance as {@link #getEntry()}.\n     */\n    @Override\n    public Object getUnderlyingFileObject() {\n        return entry;\n    }\n\n    \n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    /**\n     * This method is overridden to return the separator of the {@link #getArchiveFile() archive file} that contains\n     * this entry.\n     *\n     * @return the separator of the archive file that contains this entry\n     */\n    @Override\n    public String getSeparator() {\n        return archiveFile.getSeparator();\n    }\n\n    /**\n     * This method is overridden to use the archive file's absolute path as the base path of this entry file.\n     */\n    @Override\n    public String getAbsolutePath() {\n        // Use the archive file's absolute path and append the entry's relative path to it\n        return archiveFile.getAbsolutePath(true)+getRelativeEntryPath();\n    }\n\n    /**\n     * This method is overridden to use the archive file's canonical path as the base path of this entry file.\n     */\n    @Override\n    public String getCanonicalPath() {\n        // Use the archive file's canonical path and append the entry's relative path to it\n        return archiveFile.getCanonicalPath(true)+getRelativeEntryPath();\n    }\n\n    /**\n     * This method is overridden to return the archive's root folder.\n     */\n    @Override\n    public AbstractFile getRoot() {\n        return archiveFile.getRoot();\n    }\n\n    /**\n     * This method is overridden to blindly return <code>false</code>, an archive entry cannot be a root folder.\n     *\n     * @return <code>false</code>, always\n     */\n    @Override\n    public boolean isRoot() {\n        return false;\n    }\n\n    /**\n     * This method is overridden to return the archive's volume folder.\n     */\n    @Override\n    public AbstractFile getVolume() {\n        return archiveFile.getVolume();\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public short getReplication() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_REPLICATION);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public long getBlocksize() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_BLOCKSIZE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public void changeReplication(short replication) throws IOException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_REPLICATION);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/AbstractArchiveFile.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.commons.file;\n\nimport com.mucommander.commons.file.filter.FileFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\nimport com.mucommander.commons.file.impl.ProxyFile;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javax.swing.tree.DefaultMutableTreeNode;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.List;\nimport java.util.Vector;\nimport java.util.WeakHashMap;\n\n/**\n * <code>AbstractArchiveFile</code> is the superclass of all archive files. It allows archive file to be browsed as if\n * they were regular directories, independently of the underlying protocol used to access the actual file.\n * <p>\n * <code>AbstractArchiveFile</code> extends {@link ProxyFile} to delegate the <code>AbstractFile</code>\n * implementation to the actual archive file and overrides some methods to provide the added functionality.<br>\n * There are two kinds of <code>AbstractArchiveFile</code>, both of which extend this class:\n * <ul>\n *  <li>{@link AbstractROArchiveFile}: read-only archives, these are only able to perform read operations such as\n * listing the archive's contents or retrieving a particular entry's contents.\n *  <li>{@link AbstractRWArchiveFile}: read-write archives, these are also able to modify the archive by adding or\n * deleting an entry from the archive. These operations usually require random access to the underlying file,\n * so write operations may not be available on all underlying file types. The {@link #isWritable()} method allows\n * to determine whether the archive file is able to carry out write operations or not.\n * </ul>\n * When implementing a new archive file/format, either <code>AbstractROArchiveFile</code> or <code>AbstractRWArchiveFile</code>\n * should be subclassed, but not this class.\n *\n * <p>The first time one of the <code>ls()</code> methods is called to list the archive's contents,\n * {@link #getEntryIterator()} is called to retrieve a list of *all* the entries contained by the archive, not only the\n * ones at the top level but also the ones nested one of several levels below. Using this list of entries, it creates\n * a tree to map the structure of the archive and list the content of any particular directory within the archive.\n * This tree is recreated (<code>getEntryIterator()</code> is called again) only if the archive file has changed, i.e.\n * if its date has changed since the tree was created.\n *\n * <p>Files returned by the <code>ls()</code> are {@link AbstractArchiveEntryFile} instances which use an {@link ArchiveEntry}\n * object to retrieve the entry's attributes. In turn, these <code>AbstractArchiveEntryFile</code> instances query the\n * associated <code>AbstractArchiveFile</code> to list their content.\n * <br>From an implementation perspective, one only needs to deal with {@link ArchiveEntry} instances, all the nuts\n * and bolts are taken care of by this class.\n *\n * <p>Note that an instance of <code>AbstractArchiveFile</code> may or may not actually be an archive:\n * {@link #isArchive()} returns <code>true</code> only if the file currently exists and is not a directory. The value\n * returned by {@link #isArchive()} may change over time as the file is modified. When an\n * <code>AbstractArchiveFile</code> is not currently an archive, it acts just as a 'normal' file and delegates\n * <code>ls()</code> methods to the underlying {@link AbstractFile}\n *\n * @see com.mucommander.commons.file.FileFactory\n * @see com.mucommander.commons.file.ArchiveFormatProvider\n * @see com.mucommander.commons.file.ArchiveEntry\n * @see AbstractArchiveEntryFile\n * @see com.mucommander.commons.file.archiver.Archiver\n * @author Maxence Bernard\n */\npublic abstract class AbstractArchiveFile extends ProxyFile {\n    private static Logger logger;\n\n    /** Archive entries tree */\n    private ArchiveEntryTree entryTreeRoot;\n\n    /** Date this file had when the entries tree was created. Used to detect if the archive file has changed and entries\n     * need to be reloaded */\n    private long entryTreeDate;\n\n    /** The password to use for a password-protected archive */\n    protected String password;\n\n    /** Caches {@link AbstractArchiveEntryFile} instances so that there is only one AbstractArchiveEntryFile\n     * corresponding to the same entry at any given time, to avoid attribute inconsistencies. The key is the\n     * corresponding ArchiveEntry. */\n    private WeakHashMap<ArchiveEntry, AbstractArchiveEntryFile> archiveEntryFiles;\n\n    /**\n     * Creates an AbstractArchiveFile on top of the given file.\n     *\n     * @param file the file on top of which to create the archive\n     */\n    protected AbstractArchiveFile(AbstractFile file) {\n        super(file);\n    }\n\n    /**\n     * Creates the entries tree, used by {@link #ls(AbstractArchiveEntryFile , com.mucommander.commons.file.filter.FilenameFilter, com.mucommander.commons.file.filter.FileFilter)}\n     * to quickly list the contents of an archive's subfolder.\n     *\n     * @throws IOException if an error occurred while retrieving this archive's entries\n     * @throws UnsupportedFileOperationException if {@link FileOperation#READ_FILE} operations are not supported by the\n     * underlying file protocol.\n     */\n    private void createEntriesTree() throws IOException {\n        // TODO: this method is not thread-safe and needs to be synchronized\n        ArchiveEntryTree treeRoot = new ArchiveEntryTree();\n        archiveEntryFiles = new WeakHashMap<>();\n\n        long start = System.currentTimeMillis();\n        ArchiveEntryIterator entries = getEntryIterator();\n        try {\n            ArchiveEntry entry;\n            while ((entry = entries.nextEntry()) != null) {\n                treeRoot.addArchiveEntry(entry);\n            }\n\n            getLogger().info(\"entries tree created in \"+(System.currentTimeMillis()-start)+\" ms\");\n\n            this.entryTreeRoot = treeRoot;\n            declareEntriesTreeUpToDate();\n        }\n        finally {\n            try {\n                entries.close();\n            } catch (IOException e) {\n                // Not much we can do about it\n            }\n        }\n    }\n\n    /**\n     * Checks if the entries tree exists and if this file hasn't been modified since the tree was last created.\n     * If any of those 2 conditions isn't met, the entries tree is (re)created.\n     *\n     * @throws IOException if an error occurred while creating the tree\n     * @throws UnsupportedFileOperationException if {@link FileOperation#READ_FILE} operations are not supported by the\n     * underlying file protocol.\n     */\n    private void checkEntriesTree() throws IOException {\n        if (this.entryTreeRoot == null || getLastModifiedDate() != this.entryTreeDate) {\n            createEntriesTree();\n        }\n    }\n\n    /**\n     * Declares the entries tree up-to-date by setting the current tree date to the archive file's.\n     * This method should be called by {@link AbstractRWArchiveFile} implementations when the archive file has been\n     * modified and the entries propagated in the tree, to avoid the tree from being automatically re-created when\n     * {@link #checkEntriesTree()} is called.\n     */\n    protected void declareEntriesTreeUpToDate() {\n        this.entryTreeDate = getLastModifiedDate();\n    }\n\n    /**\n     * Adds the given {@link ArchiveEntry} to the entries tree. This method will create the tree if it doesn't already\n     * exist, or re-create it if the archive file has changed since it was last created.\n     *\n     * @param entry the ArchiveEntry to add to the tree\n     * @throws IOException if an error occurred while creating the entries tree\n     * @throws UnsupportedFileOperationException if {@link FileOperation#READ_FILE} operations are not supported by the\n     * underlying file protocol.\n     */\n    protected void addToEntriesTree(ArchiveEntry entry) throws IOException {\n        checkEntriesTree();\n        entryTreeRoot.addArchiveEntry(entry);\n    }\n\n    /**\n     * Removes the given {@link ArchiveEntry} from the entries tree. This method will create the tree if it doesn't\n     * already exist, or re-create it if the archive file has changed since it was last created.\n     *\n     * @param entry the ArchiveEntry to remove from the tree\n     * @throws IOException if an error occurred while creating the entries tree\n     * @throws UnsupportedFileOperationException if {@link FileOperation#READ_FILE} operations are not supported by the\n     * underlying file protocol.\n     */\n    protected void removeFromEntriesTree(ArchiveEntry entry) throws IOException {\n        checkEntriesTree();\n        DefaultMutableTreeNode entryNode = entryTreeRoot.findEntryNode(entry.getPath());\n\n        if (entryNode != null) {\n            DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)entryNode.getParent();\n            parentNode.remove(entryNode);\n        }\n    }\n\n    /**\n     * Returns the {@link ArchiveEntryTree} instance corresponding to the root of the archive entry tree.\n     * The returned value can be <code>null</code> if the tree hasn't been intialized yet.\n     *\n     * @return the ArchiveEntryTree instance corresponding to the root of the archive entry tree\n     */\n    ArchiveEntryTree getArchiveEntryTree() {\n        return entryTreeRoot;\n    }\n\n    /**\n     * Returns the contents of the specified folder entry.\n     *\n     * @throws UnsupportedFileOperationException if {@link FileOperation#READ_FILE} operations are not supported by the\n     * underlying file protocol.\n     */\n    protected AbstractFile[] ls(AbstractArchiveEntryFile entryFile, FilenameFilter filenameFilter, FileFilter fileFilter) throws IOException {\n        // Make sure the entries tree is created and up-to-date\n        checkEntriesTree();        \n\n        if (!entryFile.isBrowsable()) {\n            throw new IOException();\n        }\n\n        DefaultMutableTreeNode matchNode = entryTreeRoot.findEntryNode(entryFile.getEntry().getPath());\n        if (matchNode == null) {\n            throw new IOException();\n        }\n\n        return ls(matchNode, entryFile, filenameFilter, fileFilter);\n    }\n\n    /**\n     * Returns the contents (direct children) of the specified tree node.\n     *\n     * @throws UnsupportedFileOperationException if {@link FileOperation#READ_FILE} operations are not supported by the\n     * underlying file protocol.\n     */\n    private AbstractFile[] ls(DefaultMutableTreeNode treeNode, AbstractFile parentFile, FilenameFilter filenameFilter, FileFilter fileFilter) throws IOException {\n        AbstractFile[] files;\n        int nbChildren = treeNode.getChildCount();\n\n        // No FilenameFilter, create entry files and store them directly into an array\n        if (filenameFilter == null) {\n            files = new AbstractFile[nbChildren];\n\n            for (int c=0; c < nbChildren; c++) {\n                files[c] = getArchiveEntryFile((ArchiveEntry)(((DefaultMutableTreeNode)treeNode.getChildAt(c)).getUserObject()), parentFile);\n            }\n        }\n        // Use provided FilenameFilter and temporarily store created entry files that match the filter in a Vector\n        else {\n            List<AbstractFile> filesV = new Vector<>();\n            for(int c=0; c<nbChildren; c++) {\n                ArchiveEntry entry = (ArchiveEntry)(((DefaultMutableTreeNode)treeNode.getChildAt(c)).getUserObject());\n                if(!filenameFilter.accept(entry.getName()))\n                    continue;\n\n                filesV.add(getArchiveEntryFile(entry, parentFile));\n            }\n\n            files = new AbstractFile[filesV.size()];\n            filesV.toArray(files);\n        }\n\n        return fileFilter==null?files:fileFilter.filter(files);\n    }\n\n    /**\n     * Creates and returns an AbstractFile using the provided entry and parent file. This method takes care of\n     * creating the proper AbstractArchiveFile instance if the entry is itself an archive.\n     * The entry file's path will use the separator of the underlying file, as returned by {@link #getSeparator()}.\n     * That means entries paths of archives located on Windows local filesystems will use '\\' as a separator, and\n     * '/' for Unix local archives.\n     */\n    private AbstractFile getArchiveEntryFile(ArchiveEntry entry, AbstractFile parentFile) throws IOException {\n\n        String entryPath = entry.getPath();\n\n        // If the parent file's separator is not '/' (the default entry separator), replace '/' occurrences by\n        // the parent file's separator. For local files Under Windows, this allows entries' path to have '\\' separators.\n        String fileSeparator = getSeparator();\n        if(!fileSeparator.equals(\"/\"))\n            entryPath = entryPath.replace(\"/\", fileSeparator);\n\n        // Cache AbstractArchiveEntryFile instances so that there is only one AbstractArchiveEntryFile corresponding to \n        // the same entry at any given time, to avoid attribute inconsistencies.\n\n        AbstractArchiveEntryFile entryFile = archiveEntryFiles.get(entry);\n        if (entryFile == null) {\n            FileURL archiveURL = getURL();\n            FileURL entryURL = (FileURL)archiveURL.clone();\n            entryURL.setPath(addTrailingSeparator(archiveURL.getPath()) + entryPath);\n\n            // create an RO and RW entry file, depending on whether this archive file is RO or RW\n            entryFile = this instanceof AbstractRWArchiveFile\n                ? new RWArchiveEntryFile(entryURL, this, entry)\n                : new ROArchiveEntryFile(entryURL,this, entry);\n\n            entryFile.setParent(parentFile);\n\n            archiveEntryFiles.put(entry, entryFile);\n        }\n        return FileFactory.wrapArchive(entryFile);\n    }\n\n\n    /**\n     * Shorthand for {@link #getArchiveEntryFile(String)} called with the given entry's path.\n     *\n     * @param entry an entry contained by this archive\n     * @return an AbstractFile that corresponds to the given entry\n     * @throws IOException if neither the entry nor its parent exist within the archive\n     * @throws UnsupportedFileOperationException if {@link FileOperation#READ_FILE} operations are not supported by the\n     * underlying file protocol.\n     */\n    public AbstractFile getArchiveEntryFile(ArchiveEntry entry) throws IOException {\n        return getArchiveEntryFile(entry.getPath());\n    }\n\n    /**\n     * Creates and returns an AbstractFile that corresponds to the given entry path within the archive.\n     * The requested entry may or may not exist in the archive, the {@link #exists()} method of the returned entry file\n     * can be used to find this out. However, if the requested entry does not exist in the archive and is\n     * not located at the top level (i.e. is located in a subfolder), its parent folder must exist in the archive or\n     * else an <code>IOException</code> will be thrown.\n     *\n     * <p>Important note: the given path's separator character must be '/' and the path must be relative to the\n     * archive's root, i.e. not start with a leading '/', otherwise the entry will not be found.\n     *\n     * @param entryPath path to an entry within this archive\n     * @return an AbstractFile that corresponds to the given entry path\n     * @throws IOException if neither the entry nor its parent exist within the archive\n     * @throws UnsupportedFileOperationException if {@link FileOperation#READ_FILE} operations are not supported by the\n     * underlying file protocol.\n     */\n    public AbstractFile getArchiveEntryFile(String entryPath) throws IOException {\n        // Make sure the entries tree is created and up-to-date\n        checkEntriesTree();\n\n        // Todo: check if that's really necessary / if there is a way to remove this\n        entryPath = entryPath.replace(File.separatorChar, ArchiveEntry.SEPARATOR_CHAR);\n\n        // Find the entry node corresponding to the given path\n        DefaultMutableTreeNode entryNode = entryTreeRoot.findEntryNode(entryPath);\n\n        if(entryNode==null) {\n            int depth = ArchiveEntry.getDepth(entryPath);\n\n            AbstractFile parentFile;\n            if(depth==1)\n                parentFile = this;\n            else {\n                String parentPath = entryPath;\n                if(parentPath.endsWith(\"/\"))\n                    parentPath = parentPath.substring(0, parentPath.length()-1);\n\n                parentPath = parentPath.substring(0, parentPath.lastIndexOf('/'));\n\n                parentFile = getArchiveEntryFile(parentPath);\n                if(parentFile==null)    // neither the entry nor the parent exist\n                    throw new IOException();\n            }\n\n            return getArchiveEntryFile(new ArchiveEntry(entryPath, false, 0, 0, false), parentFile);\n        }\n\n        return getArchiveEntryFile(entryNode);\n    }\n\n    /**\n     * Creates and returns an {@link AbstractFile} instance corresponding to the given entry node.\n     * This method recurses to resolve the entry's parent file.\n     *\n     * @param entryNode tree node corresponding to the entry for which to return a file\n     * @return an {@link AbstractFile} instance corresponding to the given entry node\n     */\n    private AbstractFile getArchiveEntryFile(DefaultMutableTreeNode entryNode) throws IOException {\n        DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)entryNode.getParent();\n        return getArchiveEntryFile(\n                (ArchiveEntry)entryNode.getUserObject(),\n                parentNode == entryTreeRoot ? this : getArchiveEntryFile(parentNode)\n        );\n    }\n\n\n    /**\n     * Returns an iterator of {@link ArchiveEntry} that iterates through all the entries of this archive.\n     * Implementations of this method should as much as possible return entries in their \"natural order\", i.e. the order\n     * in which they are stored in the archive.\n     * <p>\n     * This method is called the first time one of the <code>ls()</code> is called. It will not be called anymore,\n     * unless the file's date has changed since the last time one of the <code>ls()</code> methods was called.\n     *\n     * @return an iterator of {@link ArchiveEntry} that iterates through all the entries of this archive\n     * @throws IOException if an error occurred while reading the archive, either because the archive is corrupt or\n     * because of an I/O error\n     * @throws UnsupportedFileOperationException if {@link FileOperation#READ_FILE} operations are not supported by the\n     * underlying file protocol.\n     */\n    public abstract ArchiveEntryIterator getEntryIterator() throws IOException;\n\n    /**\n     * Returns an <code>InputStream</code> to read from the given archive entry. The specified {@link ArchiveEntry}\n     * instance must be one of the entries that were returned by the {@link ArchiveEntryIterator} returned by\n     * {@link #getEntryIterator()}.\n     *\n     * @param entry the archive entry to read\n     * @param entryIterator the iterator that is used to iterate through entries by the caller (if any). This parameter\n     * may be <code>null</code>, but when it is known, specifying may improve the performance of this method\n     * by an order of magnitude.\n     * @return an <code>InputStream</code> to read from the given archive entry\n     * @throws IOException if an error occurred while reading the archive, either because the archive is corrupt or\n     * because of an I/O error, or if the given entry wasn't found in the archive\n     * @throws UnsupportedFileOperationException if {@link FileOperation#READ_FILE} operations are not supported by the \n     * underlying file protocol.\n     */\n    public abstract InputStream getEntryInputStream(ArchiveEntry entry, ArchiveEntryIterator entryIterator) throws IOException;\n\n    /**\n     * Returns <code>true</code> if this archive file is writable, i.e. is capable of adding and deleting entries from\n     * the underlying archive file.\n     *\n     * <p>\n     * This method is implemented by {@link com.mucommander.commons.file.AbstractROArchiveFile} and\n     * {@link com.mucommander.commons.file.AbstractRWArchiveFile} to respectively return <code>false</code> and\n     * <code>true</code>. This method may be overridden by <code>AbstractRWArchiveFile</code> implementations if write\n     * access is only available under certain conditions, for example if it requires random write access to the\n     * proxied archive file (which may not always be available).\n     * Therefore, this method should be used to test if an <code>AbstractArchiveFile</code> is writable, rather than\n     * testing if it is an instance of <code>AbstractRWArchiveFile</code>.\n     *\n     * @return <code>true</code> if this archive is writable, i.e. is capable of adding and deleting entries from\n     * the underlying archive file.\n     */\n    public abstract boolean isWritable();\n\n\n    @Override\n    public boolean isArchive() {\n        return exists() && !isDirectory();\n    }\n\n\n    /**\n     * This method is overridden to list and return the topmost entries contained by this archive.\n     * The returned files are {@link AbstractArchiveEntryFile} instances.\n     *\n     * @return the topmost entries contained by this archive\n     * @throws IOException if the archive entries could not be listed\n     * @throws UnsupportedFileOperationException if {@link FileOperation#READ_FILE} operations are not supported by the\n     * underlying file protocol.\n     */\n    @Override\n    public AbstractFile[] ls() throws IOException {\n        // Delegate to the ancestor if this file isn't actually an archive\n        if (!isArchive())\n            return super.ls();\n\n        // Make sure the entries tree is created and up-to-date\n        checkEntriesTree();\n\n        return ls(entryTreeRoot, this, null, null);\n    }\n\n    /**\n     * This method is overridden to list and return the topmost entries contained by this archive, filtering out\n     * the ones that do not match the specified {@link FilenameFilter}. The returned files are {@link AbstractArchiveEntryFile}\n     * instances.\n     *\n     * @param filter the FilenameFilter to be used to filter files out from the list, may be <code>null</code>\n     * @return the topmost entries contained by this archive\n     * @throws IOException if the archive entries could not be listed\n     * @throws UnsupportedFileOperationException if {@link FileOperation#READ_FILE} operations are not supported by the\n     * underlying file protocol.\n     */\n    @Override\n    public AbstractFile[] ls(FilenameFilter filter) throws IOException {\n        // Delegate to the ancestor if this file isn't actually an archive\n        if (!isArchive())\n            return super.ls(filter);\n\n        // Make sure the entries tree is created and up-to-date\n        checkEntriesTree();\n\n        return ls(entryTreeRoot, this, filter, null);\n    }\n\n    /**\n     * This method is overridden to list and return the topmost entries contained by this archive, filtering out\n     * the ones that do not match the specified {@link FileFilter}. The returned files are {@link AbstractArchiveEntryFile} instances.\n     *\n     * @param filter the FilenameFilter to be used to filter files out from the list, may be <code>null</code>\n     * @return the topmost entries contained by this archive\n     * @throws IOException if the archive entries could not be listed\n     * @throws UnsupportedFileOperationException if {@link FileOperation#READ_FILE} operations are not supported by the\n     * underlying file protocol.\n     */\n    @Override\n    public AbstractFile[] ls(FileFilter filter) throws IOException {\n        // Delegate to the ancestor if this file isn't actually an archive\n        if (!isArchive()) {\n            return super.ls(filter);\n        }\n\n        // Make sure the entries tree is created and up-to-date\n        checkEntriesTree();\n\n        return ls(entryTreeRoot, this, null, filter);\n    }\n\n    public String getPassword() {\n        return password;\n    }\n\n    public void setPassword(String password) {\n        this.password = password;\n    }\n\n    // Note: do not override #isDirectory() to always return true, as AbstractArchiveFile instances may be created when\n    // the file does not exist yet, and then be mkdir(): in that case, the file will be a directory and not an archive.\n\n    private static Logger getLogger() {\n        if (logger == null) {\n            logger = LoggerFactory.getLogger(AbstractArchiveFile.class);\n        }\n        return logger;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/AbstractFile.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2010 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU Lesser General Public License as published by\r\n * the Free Software Foundation, either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU Lesser General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU Lesser General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\n\r\npackage com.mucommander.commons.file;\r\n\r\nimport java.awt.Dimension;\r\nimport java.io.*;\r\nimport java.lang.reflect.Method;\r\nimport java.net.MalformedURLException;\r\nimport java.net.URL;\r\nimport java.security.MessageDigest;\r\nimport java.security.NoSuchAlgorithmException;\r\n\r\nimport javax.swing.Icon;\r\n\r\nimport com.mucommander.commons.HasProgress;\r\nimport com.mucommander.commons.file.compat.CompatURLStreamHandler;\r\nimport com.mucommander.commons.file.filter.FileFilter;\r\nimport com.mucommander.commons.file.filter.FilenameFilter;\r\nimport com.mucommander.commons.file.impl.ProxyFile;\r\nimport com.mucommander.commons.file.impl.local.LocalFile;\r\nimport com.mucommander.commons.io.BufferPool;\r\nimport com.mucommander.commons.io.ChecksumInputStream;\r\nimport com.mucommander.commons.io.FileTransferException;\r\nimport com.mucommander.commons.io.RandomAccessInputStream;\r\nimport com.mucommander.commons.io.RandomAccessOutputStream;\r\nimport com.mucommander.commons.io.StreamUtils;\r\nimport com.mucommander.commons.runtime.OsFamily;\r\n\r\n/**\r\n * <code>AbstractFile</code> is the superclass of all files.\r\n *\r\n * <p>AbstractFile classes should never be instantiated directly. Instead, the {@link FileFactory} <code>getFile</code>\r\n * methods should be used to get a file instance from a path or {@link FileURL} location.\r\n *\r\n * @see com.mucommander.commons.file.FileFactory\r\n * @see com.mucommander.commons.file.impl.ProxyFile\r\n * @author Maxence Bernard\r\n */\r\npublic abstract class AbstractFile implements FileAttributes, PermissionTypes, PermissionAccesses {\r\n\r\n    /** URL representing this file */\r\n    protected final FileURL fileURL;\r\n\r\n    /** Default path separator */\r\n    protected final static String DEFAULT_SEPARATOR = \"/\";\r\n\r\n    /** Size of the read/write buffer */\r\n    // Note: raising buffer size from 8192 to 65536 makes a huge difference in SFTP read transfer rates but beyond\r\n    // 65536, no more gain (not sure why).\r\n    protected final static int IO_BUFFER_SIZE = 65536;\r\n\r\n\r\n    /**\r\n     * Used for method <code>getPushBackInputStream()</code>\r\n     */\r\n    private MuPushbackInputStream pushbackInputStream;\r\n\r\n\r\n    /**\r\n     * Creates a new file instance with the given URL.\r\n     *\r\n     * @param url the FileURL instance that represents this file's location\r\n     */\r\n    protected AbstractFile(FileURL url) {\r\n        this.fileURL = url;\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the {@link FileURL} instance that represents this file's location.\r\n     *\r\n     * @return the FileURL instance that represents this file's location\r\n     */\r\n    public FileURL getURL() {\r\n        return fileURL;\r\n    }\r\n\r\n\r\n    /**\r\n     * Creates and returns a <code>java.net.URL</code> referring to the same location as the {@link FileURL} associated\r\n     * with this <code>AbstractFile</code>.\r\n     * The <code>java.net.URL</code> is created from the string representation of this file's <code>FileURL</code>.\r\n     * Thus, any credentials this <code>FileURL</code> contains are preserved, but properties are lost.\r\n     *\r\n     * <p>The returned <code>URL</code> uses this {@link AbstractFile} to access the associated resource, via the\r\n     * underlying <code>URLConnection</code> which delegates to this class.\r\n     *\r\n     * <p>It is important to note that this method is provided for interoperability purposes, for the sole purpose of\r\n     * connecting to APIs that require a <code>java.net.URL</code>.\r\n     *\r\n     * @return a <code>java.net.URL</code> referring to the same location as this <code>FileURL</code>\r\n     * @throws java.net.MalformedURLException if the java.net.URL could not parse the location of this FileURL\r\n     */\r\n    public URL getJavaNetURL() throws MalformedURLException {\r\n        return new URL(null, getURL().toString(true), new CompatURLStreamHandler(this));\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns this file's name.\r\n     *\r\n     * <p>The returned name is the filename extracted from this file's <code>FileURL</code>\r\n     * as returned by {@link FileURL#getFilename()}. If the filename is <code>null</code> (e.g. http://google.com), the\r\n     * <code>FileURL</code>'s host will be returned instead. If the host is <code>null</code> (e.g. smb://), an empty\r\n     * String will be returned. Thus, the returned name will never be <code>null</code>.\r\n     *\r\n     * <p>This method should be overridden if a special processing (e.g. URL-decoding) needs to be applied to the\r\n     * returned filename.\r\n     *\r\n     * @return this file's name\r\n     */\r\n    public String getName() {\r\n        String name = fileURL.getFilename();\r\n        // If filename is null, use host instead\r\n        if (name == null) {\r\n            name = fileURL.getHost();\r\n            // If host is null, return an empty string\r\n            if (name == null) {\r\n                return \"\";\r\n            }\r\n        }\r\n\r\n        return name;\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns this file's extension, <code>null</code> if this file's name doesn't have an extension.\r\n     *\r\n     * <p>\r\n     * A filename has an extension if and only if:<br>\r\n     * - it contains at least one <code>.</code> character<br>\r\n     * - the last <code>.</code> is not the last character of the filename<br>\r\n     * - the last <code>.</code> is not the first character of the filename\r\n     *\r\n     * @return this file's extension, <code>null</code> if this file's name doesn't have an extension\r\n     */\r\n    public String getExtension() {\r\n        return getExtension(getName());\r\n    }\r\n\r\n    \r\n    /**\r\n     * Returns the absolute path to this file:\r\n     * <ul>\r\n     * <li>For local filesystems, the local file's path should be returned, and <b>not</b> a full URL with the scheme\r\n     * and host parts (e.g. /path/to/file, not file://localhost/path/to/file)</li>\r\n     * <li>For any other filesystems, the full URL including the protocol and host parts should be returned\r\n     * (e.g. smb://192.168.1.1/root/blah)</li>\r\n     * </ul>\r\n     * <p>\r\n     * This default implementation returns the string representation of this file's {@link #getURL() url}, without\r\n     * the login and password parts. File implementations overriding this method should always return a path free of\r\n     * any login and password, so that it can safely be displayed to the end user or stored, without risking to\r\n     * compromise sensitive information.\r\n     *\r\n     *\r\n     * @return the absolute path to this file\r\n     */\r\n    public String getAbsolutePath() {\r\n        return getURL().toString(false);\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the canonical path to this file, resolving any symbolic links or '..' and '.' occurrences.\r\n     *\r\n     * <p>This implementation simply returns the value of {@link #getAbsolutePath()}, and thus should be overridden\r\n     * if canonical path resolution is available.\r\n     *\r\n     * @return the canonical path to this file\r\n     */\r\n    public String getCanonicalPath() {\r\n        return getAbsolutePath();\r\n    }\r\n\r\n    /**\r\n     * Returns an <code>AbstractFile</code> representing the canonical path of this file, or <code>this</code> if the\r\n     * absolute and canonical path of this file are identical.<br>\r\n     * Note that the returned file may or may not exist, for example if this file is a symlink to a file that doesn't \r\n     * exist.\r\n     *\r\n     * @return an <code>AbstractFile</code> representing the canonical path of this file, or this if the absolute and canonical\r\n     * path of this file are identical.\r\n     */\r\n    public AbstractFile getCanonicalFile() {\r\n        String canonicalPath = getCanonicalPath(false);\r\n        if (canonicalPath.equals(getAbsolutePath(false))) {\r\n            return this;\r\n        }\r\n\r\n        try {\r\n            FileURL canonicalURL = FileURL.getFileURL(canonicalPath);\r\n            canonicalURL.setCredentials(fileURL.getCredentials());\r\n\r\n            return FileFactory.getFile(canonicalURL);\r\n        } catch (IOException e) {\r\n            return this;\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the path separator used by this file.\r\n     *\r\n     * <p>This default implementation returns the default separator \"/\", this method should be overridden if the path\r\n     * separator used by the file implementation is different.\r\n     *\r\n     * @return the path separator used by this file\r\n     */\r\n    public String getSeparator() {\r\n        return DEFAULT_SEPARATOR;\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns <code>true</code> if this file is hidden.\r\n     *\r\n     * <p>This default implementation is solely based on the filename and returns <code>true</code> if this\r\n     * file's name starts with '.'. This method should be overridden if the underlying filesystem has a notion\r\n     * of hidden files.\r\n     *\r\n     * @return true if this file is hidden\r\n     */\t\r\n    public boolean isHidden() {\r\n        return getName().startsWith(\".\");\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if this file is executable.\r\n     *\r\n     * @return true if this file is executable\r\n     */\r\n    public boolean isExecutable() {\r\n        if (OsFamily.WINDOWS.isCurrent()) {\r\n            if (isDirectory()) {\r\n                return false;\r\n            }\r\n            String ext = getExtension();\r\n            return ext != null && (ext.equalsIgnoreCase(\"exe\") || ext.equalsIgnoreCase(\"com\") || ext.equalsIgnoreCase(\"bat\") || ext.equalsIgnoreCase(\"cmd\"));\r\n        }\r\n        return !isDirectory() && getPermissions().getBitValue(USER_ACCESS, EXECUTE_PERMISSION);\r\n    }\r\n\r\n    /**\r\n     * Return <code>true</code> if the application can read this file.\r\n     * TODO: need to be overridden with a correct check for each file type.\r\n     *\r\n     * @return true if the application can read this file.\r\n     * **/\r\n    public boolean canRead() {\r\n        return true;\r\n    }\r\n\r\n    /**\r\n     * Returns the root folder of this file, i.e. the top-level parent folder that has no parent folder. The returned\r\n     * folder necessarily contains this file, directly or indirectly. If this file already is a root folder, the same\r\n     * file will be returned.\r\n     * <p>\r\n     * This default implementation returns the file whose URL has the same scheme as this one, same credentials (if any),\r\n     * and a path equal to <code>/</code>.\r\n     *\r\n     * @return the root folder that contains this file\r\n     */\r\n    public AbstractFile getRoot() {\r\n        FileURL rootURL = (FileURL)getURL().clone();\r\n        rootURL.setPath(\"/\");\r\n\r\n        return FileFactory.getFile(rootURL);\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if this file is a root folder.\r\n     * <p>\r\n     * This default implementation returns <code>true</code> if this file's URL path is <code>/</code>.\r\n     *\r\n     * @return <code>true</code> if this file is a root folder\r\n     */\r\n    public boolean isRoot() {\r\n        return getURL().getPath().equals(\"/\");\r\n    }\r\n\r\n    /**\r\n     * Returns the volume on which this file is located, or <code>this</code> if this file is itself a volume.\r\n     * The returned file may never be <code>null</code>. Furthermore, the returned file may not always\r\n     * {@link #exists() exist}, for instance if the returned volume corresponds to a removable drive that's currently\r\n     * unavailable. If the returned file does exist, it must always be a {@link #isDirectory() directory}.\r\n     * In other words, archive files may not be considered as volumes.\r\n     * <p>\r\n     * The notion of volume may or may not have a meaning depending on the kind of filesystem. On local filesystems,\r\n     * the notion of volume can be assimilated into that of <i>mount point</i> for UNIX-based OSes, or <i>drive</i>\r\n     * for the Windows platform. Volumes may also have a meaning for certain network filesystems such as SMB, for which\r\n     * shares can be considered as volumes. Filesystems that don't have a notion of volume should return the\r\n     * {@link #getRoot() root folder}.\r\n     * <p>\r\n     * This default implementation returns this file's {@link #getRoot() root folder}. This method should be overridden\r\n     * if this is not adequate.\r\n     *\r\n     * @return the volume on which this file is located.\r\n     */\r\n    public AbstractFile getVolume() {\r\n        return getRoot();\r\n    }\r\n\r\n    /**\r\n     * Returns an <code>InputStream</code> to read this file's contents, starting at the specified offset (in bytes).\r\n     * A <code>java.io.IOException</code> is thrown if the file doesn't exist.\r\n     *\r\n     * <p>This implementation starts by checking whether the {@link FileOperation#RANDOM_READ_FILE} operation is\r\n     * supported or not.\r\n     * If it is, a {@link #getRandomAccessInputStream() random input stream} to this file is retrieved and used to seek\r\n     * to the specified offset. If it's not, a regular {@link #getInputStream() input stream} is retrieved, and\r\n     * {@link java.io.InputStream#skip(long)} is used to position the stream to the specified offset, which on most\r\n     * <code>InputStream</code> implementations is very slow as it causes the bytes to be read and discarded.\r\n     * For this reason, file implementations that do not provide random read access may want to override this method\r\n     * if a more efficient implementation can be provided.\r\n     *\r\n     * @param offset the offset in bytes from the beginning of the file, must be not negative\r\n     * @throws IOException if this file cannot be read or is a folder.\r\n     * @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported\r\n     * or not implemented by the underlying filesystem.\r\n     * @return an <code>InputStream</code> to read this file's contents, skipping the specified number of bytes\r\n     */\r\n    public InputStream getInputStream(long offset) throws IOException {\r\n        // Use random access input stream when available\r\n        if (isFileOperationSupported(FileOperation.RANDOM_READ_FILE)) {\r\n            RandomAccessInputStream rais = getRandomAccessInputStream();\r\n            rais.seek(offset);\r\n\r\n            return rais;\r\n        }\r\n\r\n        InputStream in = getInputStream();\r\n\r\n        // Skip exactly the specified number of bytes\r\n        StreamUtils.skipFully(in, offset);\r\n\r\n        return in;\r\n    }\r\n\t\r\n\r\n    /**\r\n     * Copies the contents of the given <code>InputStream</code> to this file, appending or overwriting the file\r\n     * if it exists. It is noteworthy that the provided <code>InputStream</code> will <b>not</b> be closed by this method.\r\n     * \r\n     * <p>This method should be overridden by filesystems that do not offer a {@link #getOutputStream()}\r\n     * implementation, but that can take an <code>InputStream</code> and use it to write the file.\r\n     * For this reason, it is recommended to use this method to write a file, rather than copying streams manually using\r\n     * {@link #getOutputStream()}\r\n     *\r\n     * <p>The <code>length</code> parameter is optional. Setting its value help certain protocols which need to know\r\n     * the length in advance. This is the case for instance for some HTTP-based protocols like Amazon S3, which require\r\n     * the <code>Content-Length</code> header to be set in the request. Callers should thus set the length if it is\r\n     * known.\r\n     *\r\n     * <p>Read and write operations are buffered, with a buffer of {@link #IO_BUFFER_SIZE} bytes. For performance\r\n     * reasons, this buffer is provided by {@link BufferPool}. Thus, there is no need to surround the InputStream\r\n     * with a {@link java.io.BufferedInputStream}.\r\n     *\r\n     * <p>Copy progress can optionally be monitored by supplying a {@link com.mucommander.commons.io.CounterInputStream}.\r\n     *\r\n     * @param in the InputStream to read from\r\n     * @param append if true, data written to the OutputStream will be appended to the end of this file. If false, any\r\n     * existing data will be overwritten.\r\n     * @param length length of the stream before EOF is reached, <code>-1</code> if unknown.\r\n     * @throws FileTransferException if something went wrong while reading from the InputStream or writing to this file\r\n     */\r\n    public void copyStream(InputStream in, boolean append, long length) throws FileTransferException {\r\n        OutputStream out;\r\n\r\n        try {\r\n            out = append ? getAppendOutputStream() : getOutputStream();\r\n        } catch (UnsupportedFileOperationException e) {\r\n            throw new FileTransferException(FileTransferException.UNSUPPORTED_OPERATION, e);\r\n        } catch (IOException e) {\r\n            throw new FileTransferException(FileTransferException.OPENING_DESTINATION, e);\r\n        }\r\n\r\n        try {\r\n            StreamUtils.copyStream(in, out, IO_BUFFER_SIZE);\r\n        } finally {\r\n            // Close stream even if copyStream() threw an IOException\r\n            try {\r\n                out.close();\r\n            } catch(IOException e) {\r\n                throw new FileTransferException(FileTransferException.CLOSING_DESTINATION, e);\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Copies this file to a specified destination file, overwriting the destination if it exists. If this file is a\r\n     * directory, any file or directory it contains will also be copied.\r\n     *\r\n     * <p>This method throws an {@link IOException} if the operation failed, for any of the following reasons:\r\n     * <ul>\r\n     *  <li>this file and the destination file are the same</li>\r\n     *  <li>this file is a directory and a parent of the destination file (the operation would otherwise loop indefinitely)</li>\r\n     *  <li>this file (or one if its children) cannot be read</li>\r\n     *  <li>the destination file (or one of its children) can not be written</li>\r\n     *  <li>an I/O error occurred</li>\r\n     * </ul>\r\n     *\r\n     * <p>If this file supports the {@link FileOperation#COPY_REMOTELY} file operation, an attempt to perform a\r\n     * {@link #copyRemotelyTo(AbstractFile) remote copy} of the file to the destination is made. If the operation isn't\r\n     * supported or wasn't successful, the file is copied manually, by transferring its contents to the destination \r\n     * using {@link #copyRecursively(AbstractFile, AbstractFile)}.<br>\r\n     * In that case, no clean up is performed if an error occurs in the midst of a transfer: files that have been copied\r\n     * (even partially) are left in the destination.<br>\r\n     * It is also worth noting that symbolic links are not copied to the destination when encountered: neither the link\r\n     * nor the linked file is copied\r\n     *\r\n     * @param destFile the destination file to copy this file to\r\n     * @throws IOException in any of the error cases listed above\r\n     */\r\n    public final void copyTo(AbstractFile destFile) throws IOException {\r\n        // First, try to perform a remote copy of the file if the operation is supported\r\n        if (isFileOperationSupported(FileOperation.COPY_REMOTELY)) {\r\n            try {\r\n                copyRemotelyTo(destFile);\r\n                return; // Operation was a success, all done.\r\n            } catch (IOException ignore) {}\r\n        }\r\n\r\n        // Fall back to copying the file manually\r\n        checkCopyPrerequisites(destFile, false);\r\n\r\n        // Copy the file and its contents if the file is a directory\r\n        copyRecursively(this, destFile);\r\n    }\r\n\r\n    /**\r\n     * Moves this file to a specified destination file, overwriting the destination if it exists. If this file is a\r\n     * directory, any file or directory it contains will also be moved.\r\n     * After normal completion, this file will not exist anymore: {@link #exists()} will return <code>false</code>.\r\n     *\r\n     * <p>This method throws an {@link IOException} if the operation failed, for any of the following reasons:\r\n     * <ul>\r\n     *  <li>this file and the destination file are the same</li>\r\n     *  <li>this file is a directory and a parent of the destination file (the operation would otherwise loop indefinitely)</li>\r\n     *  <li>this file (or one if its children) cannot be read</li>\r\n     *  <li>this file (or one of its children) cannot be written</li>\r\n     *  <li>the destination file (or one of its children) can not be written</li>\r\n     *  <li>an I/O error occurred</li>\r\n     * </ul>\r\n     *\r\n     * <p>If this file supports the {@link FileOperation#RENAME} file operation, an attempt to\r\n     * {@link #renameTo(AbstractFile) rename} the file to the destination is made. If the operation isn't supported\r\n     * or wasn't successful, the file is moved manually, by transferring its contents to the destination using\r\n     * {@link #copyTo(AbstractFile)} and then deleting the source.<br>\r\n     * In that case, deletion of the source occurs only after all files have been successfully transferred.\r\n     * No clean up is performed if an error occurs in the midst of a transfer: files that have been copied\r\n     * (even partially) are left in the destination.<br>\r\n     * It is also worth noting that symbolic links are not moved to the destination when encountered: neither the link\r\n     * nor the linked file is moved, and the symlink file is deleted.\r\n     *\r\n     * @param destFile the destination file to move this file to\r\n     * @throws IOException in any of the error cases listed above\r\n     */\r\n    public final void moveTo(AbstractFile destFile) throws IOException {\r\n        // First, try to rename the file if the operation is supported\r\n        if (isFileOperationSupported(FileOperation.RENAME)) {\r\n            try {\r\n                renameTo(destFile);\r\n                // Rename was a success, all done.\r\n                return;\r\n            } catch (IOException ignore) {}\r\n        }\r\n\r\n        // Fall back to moving the file manually\r\n\r\n        copyTo(destFile);\r\n\r\n        // Delete the source file and its contents now that it has been copied OK.\r\n        // Note that the file won't be deleted if copyTo() failed (threw an IOException)\r\n        try {\r\n            deleteRecursively();\r\n        } catch(IOException e) {\r\n            throw new FileTransferException(FileTransferException.DELETING_SOURCE);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Creates this file as an empty, non-directory file. This method will fail (throw an <code>IOException</code>)\r\n     * if this file already exists. Note that this method may not always yield a zero-byte file (see below).\r\n     *\r\n     * <p>This generic implementation simply creates a zero-byte file. {@link AbstractRWArchiveFile} implementations\r\n     * may want to override this method so that it creates a valid archive with no entry. To illustrate, an empty Zip\r\n     * file with proper headers is 22-byte long.\r\n     *\r\n     * @throws IOException if the file could not be created, either because it already exists or because of an I/O error\r\n     * @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported\r\n     * or not implemented by the underlying filesystem.\r\n     */\r\n    public void mkfile() throws IOException {\r\n        if (exists()) {\r\n            throw new IOException();\r\n        }\r\n\r\n        if (isFileOperationSupported(FileOperation.WRITE_FILE)) {\r\n            getOutputStream().close();\r\n        } else {\r\n            copyStream(new ByteArrayInputStream(new byte[]{}), false, 0);\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the children files that this file contains, filtering out files that do not match the specified FileFilter.\r\n     * For this operation to be successful, this file must be 'browsable', i.e. {@link #isBrowsable()} must return\r\n     * <code>true</code>.\r\n     *\r\n     * @param filter the FileFilter to be used to filter files out from the list, may be <code>null</code>\r\n     * @return the children files that this file contains\r\n     * @throws IOException if this operation is not possible (file is not browsable) or if an error occurred.\r\n     * @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported\r\n     * or not implemented by the underlying filesystem.\r\n     */\r\n    public AbstractFile[] ls(FileFilter filter) throws IOException {\r\n        return filter == null ? ls() : filter.filter(ls());\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the children files that this file contains, filtering out files that do not match the specified FilenameFilter.\r\n     * For this operation to be successful, this file must be 'browsable', i.e. {@link #isBrowsable()} must return\r\n     * <code>true</code>.\r\n     *\r\n     * <p>This default implementation filters out files *after* they have been created. This method\r\n     * should be overridden if a more efficient implementation can be provided by subclasses.\r\n     *\r\n     * @param filter the FilenameFilter to be used to filter out files from the list, may be <code>null</code>\r\n     * @return the children files that this file contains\r\n     * @throws IOException if this operation is not possible (file is not browsable) or if an error occurred.\r\n     * @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported\r\n     * or not implemented by the underlying filesystem.\r\n     */\r\n    public AbstractFile[] ls(FilenameFilter filter) throws IOException {\r\n        return filter == null ? ls() : filter.filter(ls());\r\n    }\r\n\r\n\r\n    /**\r\n     * Changes this file's permissions to the specified permissions int.\r\n     * The permissions int should be constructed using the permission types and accesses defined in\r\n     * {@link com.mucommander.commons.file.PermissionTypes} and {@link com.mucommander.commons.file.PermissionAccesses}.\r\n     *\r\n     * <p>Implementation note: the default implementation of this method calls sequentially {@link #changePermission(int, int, boolean)},\r\n     * for each permission and access (that's a total 9 calls). This may affect performance on filesystems which need\r\n     * to perform an I/O request to change each permission individually. In that case, and if the filesystem allows\r\n     * to change all permissions at once, this method should be overridden.\r\n     *\r\n     * @param permissions new permissions for this file\r\n     * @throws IOException if the permissions couldn't be changed, either because of insufficient permissions or because\r\n     * of an I/O error.\r\n     * @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported\r\n     * or not implemented by the underlying filesystem.\r\n     */\r\n    public void changePermissions(int permissions) throws IOException {\r\n        int bitShift = 0;\r\n\r\n        PermissionBits mask = getChangeablePermissions();\r\n        for (int a = OTHER_ACCESS; a <= USER_ACCESS; a++) {\r\n            for (int p = EXECUTE_PERMISSION; p <= READ_PERMISSION; p = p<<1) {\r\n                if (mask.getBitValue(a, p)) {\r\n                    changePermission(a, p, (permissions & (1 << bitShift)) != 0);\r\n                }\r\n\r\n                bitShift++;\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Returns a string representation of this file's permissions.\r\n     *\r\n     * <p>The first character is 'l' if this file is a symbolic link,'d' if it is a directory, '-' otherwise. Then\r\n     * the string contains up to 3 character triplets, for each of the 'user', 'group' and 'other' access types, each\r\n     * containing the following characters:\r\n     * <ul>\r\n     *  <li>'r' if this file has read permission, '-' otherwise\r\n     *  <li>'w' if this file has write permission, '-' otherwise\r\n     *  <li>'x' if this file has executable permission, '-' otherwise\r\n     * </ul>\r\n     *\r\n     * <p>The first character triplet for 'user' access will always be added to the permissions. Then the 'group' and\r\n     * 'other' triplets will only be added if at least one of the user permission bits is supported, as tested with\r\n     * this file's permissions mask.\r\n     * Here are a couple examples to illustrate:\r\n     * <ul>\r\n     *  <li>a directory for which the file permissions' mask is 0 will return the string <code>d---</code>, no matter\r\n     * what permission values the FilePermissions returned by {@link #getPermissions()} contains</li>\r\n     *  <li>a regular file for which the file permissions' mask returns 777 (full permissions support) and which\r\n     * has read/write/executable permissions for all three 'user', 'group' and 'other' access types will return\r\n     * <code>-rwxrwxrwx</code></li>\r\n     * </ul>\r\n     *\r\n     * @return a string representation of this file's permissions\r\n     */\r\n    public String getPermissionsString() {\r\n        FilePermissions permissions = getPermissions();\r\n        if (permissions == null) {\r\n            return isSymlink() ? \"l???\" : isDirectory() ? \"d???\" : \"-???\";\r\n        }\r\n        int supportedPerms = permissions.getMask().getIntValue();\r\n\r\n        StringBuilder sb = new StringBuilder();\r\n        sb.append(isSymlink() ? 'l' : isDirectory() ? 'd' : '-');\r\n\r\n        int perms = permissions.getIntValue();\r\n\r\n        int bitShift = USER_ACCESS *3;\r\n\r\n        // Permissions go by triplets (rwx), there are 3 of them for respectively 'owner', 'group' and 'other' accesses.\r\n        // The first one ('owner') will always be displayed, regardless of the permission bit mask. 'Group' and 'other'\r\n        // will be displayed only if the permission mask contains information about them (at least one permission bit).\r\n        for (int a = USER_ACCESS; a >= OTHER_ACCESS; a--) {\r\n            if (a == USER_ACCESS || (supportedPerms & (7<<bitShift)) != 0) {\r\n                for (int p = READ_PERMISSION; p >= EXECUTE_PERMISSION; p = p >> 1) {\r\n                    if ((perms & (p<<bitShift)) == 0) {\r\n                        sb.append('-');\r\n                    } else {\r\n                        sb.append(p == READ_PERMISSION ? 'r' : p == WRITE_PERMISSION ? 'w' : 'x');\r\n                    }\r\n                }\r\n            }\r\n            bitShift -= 3;\r\n        }\r\n\r\n        return sb.toString();\r\n    }\r\n\r\n\r\n    /**\r\n     * Deletes this file. If the file is a directory, enclosing files are deleted recursively.\r\n     * Symbolic links to directories are simply deleted, without deleting the contents of the linked directory.\r\n     *\r\n     * @throws IOException if an error occurred while deleting a file or listing a directory's contents\r\n     * @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported \r\n     * or not implemented by the underlying filesystem.\r\n     */\r\n    public void deleteRecursively() throws IOException {\r\n        deleteRecursively(this);\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns <code>true</code> if the specified file operation and corresponding method is supported by this\r\n     * file implementation. See the {@link FileOperation} enum for a complete list of file operations and their\r\n     * corresponding <code>AbstractFile</code> methods.\r\n     * <p>\r\n     * Note that even if <code>true</code> is returned, this doesn't ensure that the file operation will succeed:\r\n     * additional conditions may be required for the operation to succeed and the corresponding method may throw an\r\n     * <code>IOException</code> if those conditions are not met.\r\n     *\r\n     * @param op a file operation\r\n     * @return <code>true</code> if the specified file operation is supported by this filesystem.\r\n     * @see FileOperation\r\n     */\r\n    public boolean isFileOperationSupported(FileOperation op) {\r\n        return isFileOperationSupported(op, getClass());\r\n    }\r\n\r\n\r\n\r\n    /**\r\n     * Returns <code>true</code> if this file is browsable. A file is considered browsable if it contains children files\r\n     * that can be retrieved by calling the <code>ls()</code> methods. Archive files will usually return\r\n     * <code>true</code>, as will directories (directories are always browsable).\r\n     *\r\n     * @return true if this file is browsable\r\n     */\r\n    public final boolean isBrowsable() {\r\n        return isDirectory() || isArchive();\r\n    }\r\n\r\n    /**\r\n     * Returns the name of the file without its extension.\r\n     *\r\n     * <p>A filename has an extension if and only if:<br>\r\n     * - it contains at least one <code>.</code> character<br>\r\n     * - the last <code>.</code> is not the last character of the filename<br>\r\n     * - the last <code>.</code> is not the first character of the filename<br>\r\n     * If this file has no extension, its full name is returned.\r\n     *\r\n     * @return this file's name, without its extension.\r\n     * @see    #getName()\r\n     * @see    #getExtension()\r\n     */\r\n    public final String getNameWithoutExtension() {\r\n        String name = getName();\r\n        int position = name.lastIndexOf('.');\r\n\r\n        if (position <= 0 || position == name.length() - 1) {\r\n            return name;\r\n        }\r\n\r\n        return name.substring(0, position);\r\n    }\r\n\r\n    /**\r\n     * Shorthand for {@link #getAbsolutePath()}.\r\n     *\r\n     * @return the value returned by {@link #getAbsolutePath()}.\r\n     */\r\n    public final String getPath() {\r\n        return getAbsolutePath();\r\n    }\r\n\r\n    /**\r\n     * Returns the absolute path to this file.\r\n     * A separator character will be appended to the returned path if <code>true</code> is passed.\r\n     *\r\n     * @param appendSeparator if true, a separator will be appended to the returned path\r\n     * @return the absolute path to this file\r\n     */\r\n    public final String getAbsolutePath(boolean appendSeparator) {\r\n        String path = getAbsolutePath();\r\n        return appendSeparator?addTrailingSeparator(path): removeTrailingSeparator(path);\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the canonical path to this file, resolving any symbolic links or '..' and '.' occurrences.\r\n     * A separator character will be appended to the returned path if <code>true</code> is passed.\r\n     *\r\n     * @param appendSeparator if true, a separator will be appended to the returned path\r\n     * @return the canonical path to this file\r\n     */\r\n    public final String getCanonicalPath(boolean appendSeparator) {\r\n        String path = getCanonicalPath();\r\n        return appendSeparator ? addTrailingSeparator(path) : removeTrailingSeparator(path);\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns a child of this file, whose path is the concatenation of this file's path and the given relative path.\r\n     * Although this method does not enforce it, the specified path should be relative, i.e. should not start with\r\n     * a separator.<br>\r\n     * An <code>IOException</code> may be thrown if the child file could not be instantiated but the returned file\r\n     * instance should never be <code>null</code>.\r\n     *\r\n     * @param relativePath the child's path, relative to this file's path\r\n     * @return an AbstractFile representing the requested child file, never null\r\n     * @throws IOException if the child file could not be instantiated\r\n     */\r\n    public final AbstractFile getChild(String relativePath) throws IOException {\r\n        FileURL childURL = (FileURL)getURL().clone();\r\n        childURL.setPath(addTrailingSeparator(childURL.getPath()) + relativePath);\r\n\r\n        return FileFactory.getFile(childURL, true);\r\n    }\r\n\r\n    /**\r\n     * Convenience method that acts as {@link #getChild(String)} except that it does not throw {@link IOException} but\r\n     * returns <code>null</code> if the child could not be instantiated.\r\n     *\r\n     * @param relativePath the child's path, relative to this file's path\r\n     * @return an AbstractFile representing the requested child file, <code>null</code> if it could not be instantiated\r\n     */\r\n    public final AbstractFile getChildSilently(String relativePath) {\r\n        try {\r\n            return getChild(relativePath);\r\n        } catch(IOException e) {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Returns a direct child of this file, whose path is the concatenation of this file's path and the given filename.\r\n     * An <code>IOException</code> will be thrown in any of the following cases:\r\n     * <ul>\r\n     *  <li>if the filename contains one or several path separator (the file would not be a direct child)</li>\r\n     *  <li>if the child file could not be instantiated</li>\r\n     * </ul>\r\n     * This method never returns <code>null</code>.\r\n     *\r\n     * <p>Although {@link #getChild} can be used to retrieve a direct child file, this method should be favored because\r\n     * it allows to use this file instance as the parent of the returned child file.\r\n     *\r\n     * @param filename the name of the child file to be created\r\n     * @return an AbstractFile representing the requested direct child file, never null\r\n     * @throws IOException in any of the cases listed above\r\n     */\r\n    public final AbstractFile getDirectChild(String filename) throws IOException {\r\n        if (filename.contains(getSeparator())) {\r\n            throw new IOException();\r\n        }\r\n\r\n        AbstractFile childFile = getChild(filename);\r\n\r\n        // Use this file as the child's parent, it avoids creating a new AbstractFile instance when getParent() is called\r\n        childFile.setParent(this);\r\n\r\n        return childFile;\r\n    }\r\n\r\n\r\n    /**\r\n     * Convenience method that creates a directory as a direct child of this directory.\r\n     * This method will fail if this file is not a directory.\r\n     *\r\n     * @param name name of the directory to create\r\n     * @throws IOException if the directory could not be created, either because the file already exists or for any\r\n     * other reason.\r\n     * @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported\r\n     * or not implemented by the underlying filesystem.\r\n     */\r\n    public final void mkdir(String name) throws IOException {\r\n        getChild(name).mkdir();\r\n    }\r\n\r\n\r\n    /**\r\n     * Creates this file as a directory and any parent directory that does not already exist. This method will fail\r\n     * (throw an <code>IOException</code>) if this file already exists. It may also fail because of an I/O error ;\r\n     * in this case, this method will not remove the parent directories it has created (if any).\r\n     *\r\n     * @throws IOException if this file already exists or if an I/O error occurred.\r\n     * @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported\r\n     * or not implemented by the underlying filesystem.\r\n     */\r\n    public final void mkdirs() throws IOException {\r\n        AbstractFile parent = getParent();\r\n        if (parent != null && !parent.exists()) {\r\n            parent.mkdirs();\r\n        }\r\n\r\n        mkdir();\r\n    }\r\n\r\n\r\n    /**\r\n     * Convenience method that creates a file as a direct child of this directory.\r\n     * This method will fail if this file is not a directory.\r\n     *\r\n     * @param name name of the file to create\r\n     * @throws IOException if the file could not be created, either because the file already exists or for any\r\n     * other reason.\r\n     * @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported\r\n     * or not implemented by the underlying filesystem.\r\n     */\r\n    public final void mkfile(String name) throws IOException {\r\n        getChild(name).mkfile();\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the immediate ancestor of this <code>AbstractFile</code> if it has one, <code>this</code> otherwise:\r\n     * <ul>\r\n     *  <li>if this file is a {@link ProxyFile}, returns the return value of {@link ProxyFile#getProxiedFile()}\r\n     *  <li>if this file is not a <code>ProxyFile</code>, returns <code>this</code>\r\n     * </ul>\r\n     *\r\n     * @return the immediate ancestor of this <code>AbstractFile</code> if it has one, <code>this</code> otherwise\r\n     */\r\n    public final AbstractFile getAncestor() {\r\n        if (this instanceof ProxyFile) {\r\n            return ((ProxyFile) this).getProxiedFile();\r\n        }\r\n        return this;\r\n    }\r\n\r\n    /**\r\n     * Returns the first ancestor of this file that is an instance of the given Class or of a subclass of it,\r\n     * or <code>this</code> if this instance's class matches those criteria. Returns <code>null</code> if this\r\n     * file has no such ancestor.\r\n     * <br>\r\n     * Note that this method will always return <code>this</code> if <code>AbstractFile.class</code> is specified.\r\n     *\r\n     * @param abstractFileClass a Class corresponding to an AbstractFile subclass\r\n     * @return the first ancestor of this file that is an instance of the given Class or of a subclass of the given\r\n     * Class, or <code>this</code> if this instance's class matches those criteria. Returns <code>null</code> if this\r\n     * file has no such ancestor.\r\n     */\r\n    public final <T extends AbstractFile> T getAncestor(Class<T> abstractFileClass) {\r\n    \tAbstractFile ancestor = this;\r\n        AbstractFile lastAncestor;\r\n\r\n        do {\r\n            if (abstractFileClass.isAssignableFrom(ancestor.getClass())) {\r\n                return (T) ancestor;\r\n            }\r\n            lastAncestor = ancestor;\r\n            ancestor = ancestor.getAncestor();\r\n        } while (lastAncestor != ancestor);\r\n\r\n        return null;\r\n    }\r\n\r\n    /**\r\n     * Iterates through the ancestors returned by {@link #getAncestor()} until the top-most ancestor is reached and\r\n     * returns it. If this file has no ancestor, <code>this</code> will be returned.\r\n     *\r\n     * @return returns the top-most ancestor of this file, <code>this</code> if this file has no ancestor\r\n     */\r\n    public final AbstractFile getTopAncestor() {\r\n        AbstractFile topAncestor = this;\r\n        while (topAncestor.hasAncestor()) {\r\n            topAncestor = topAncestor.getAncestor();\r\n        }\r\n\r\n        return topAncestor;\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if this <code>AbstractFile</code> has an ancestor, i.e. if this file is a\r\n     * {@link ProxyFile}, <code>false</code> otherwise.\r\n     *\r\n     * @return <code>true</code> if this <code>AbstractFile</code> has an ancestor, <code>false</code> otherwise.\r\n     */\r\n    public final boolean hasAncestor() {\r\n        return this instanceof ProxyFile;\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if this file is or has an ancestor (immediate or not) that is an instance of the given\r\n     * <code>Class</code> or of a subclass of the <code>Class</code>. Note that the specified must correspond to an\r\n     * <code>AbstractFile</code> subclass. Specifying any other Class will always yield to this method returning\r\n     * <code>false</code>. Also note that this method will always return <code>true</code> if\r\n     * <code>AbstractFile.class</code> is specified.\r\n     *\r\n     * @param abstractFileClass a Class corresponding to an AbstractFile subclass\r\n     * @return <code>true</code> if this file has an ancestor (immediate or not) that is an instance of the given Class\r\n     * or of a subclass of the given Class.\r\n     */\r\n    public final boolean hasAncestor(Class<? extends AbstractFile> abstractFileClass) {\r\n        AbstractFile ancestor = this;\r\n        AbstractFile lastAncestor;\r\n\r\n        do {\r\n            if (abstractFileClass.isAssignableFrom(ancestor.getClass())) {\r\n                return true;\r\n            }\r\n            lastAncestor = ancestor;\r\n            ancestor = ancestor.getAncestor();\r\n        } while (lastAncestor != ancestor);\r\n\r\n        return false;\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns <code>true</code> if this file is a parent folder of the given file, or if the two files are equal.\r\n     *\r\n     * @param file the AbstractFile to test\r\n     * @return true if this file is a parent folder of the given file, or if the two files are equal\r\n     */\r\n    public final boolean isParentOf(AbstractFile file) {\r\n        return isBrowsable() && file.getCanonicalPath(true).startsWith(getCanonicalPath(true));\r\n    }\r\n\r\n    /**\r\n     * Convenience method that returns the parent {@link AbstractArchiveFile} that contains this file. If this file\r\n     * is an {@link AbstractArchiveFile} or an ancestor of {@link AbstractArchiveFile}, <code>this</code> is returned.\r\n     * If this file is neither contained by an archive nor is an archive, <code>null</code> is returned.\r\n     *\r\n     * <p>\r\n     * <b>Important note:</b> the returned {@link AbstractArchiveFile}, if any, may not necessarily be an\r\n     * archive, as specified by {@link #isArchive()}. This is the case for files that were resolved as\r\n     * {@link AbstractArchiveFile} instances based on their path, but that do not yet exist or were created as\r\n     * directories. On the contrary, an existing archive will necessarily return a non-null value.\r\n     *\r\n     * @return the parent {@link AbstractArchiveFile} that contains this file\r\n     */\r\n    public final AbstractArchiveFile getParentArchive() {\r\n        if (hasAncestor(AbstractArchiveFile.class)) {\r\n            return getAncestor(AbstractArchiveFile.class);\r\n        } else if (hasAncestor(AbstractArchiveEntryFile.class)) {\r\n            AbstractArchiveEntryFile ancestor = getAncestor(AbstractArchiveEntryFile.class);\r\n            return ancestor != null ? ancestor.getArchiveFile() : null;\r\n        }\r\n\r\n        return null;\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns an icon representing this file, using the default {@link com.mucommander.commons.file.icon.FileIconProvider}\r\n     * registered in {@link FileFactory}. The specified preferred resolution will be used as a hint, but the returned\r\n     * icon may have different dimension; see {@link com.mucommander.commons.file.icon.FileIconProvider#getFileIcon(AbstractFile, java.awt.Dimension)}\r\n     * for full details.\r\n     * This method may return <code>null</code> if the JVM is running on a headless environment.\r\n     *\r\n     * @param preferredResolution the preferred icon resolution\r\n     * @return an icon representing this file, <code>null</code> if the JVM is running on a headless environment\r\n     * @see com.mucommander.commons.file.FileFactory#getDefaultFileIconProvider()\r\n     * @see com.mucommander.commons.file.icon.FileIconProvider#getFileIcon(AbstractFile, java.awt.Dimension)\r\n     */\r\n    public final Icon getIcon(Dimension preferredResolution) {\r\n        return FileFactory.getDefaultFileIconProvider().getFileIcon(this, preferredResolution);\r\n    }\r\n\r\n    /**\r\n     * Returns an icon representing this file, using the default {@link com.mucommander.commons.file.icon.FileIconProvider}\r\n     * registered in {@link FileFactory}. The default preferred resolution for the icon is 16x16 pixels.\r\n     * This method may return <code>null</code> if the JVM is running on a headless environment.\r\n     *\r\n     * @return an icon representing this file, <code>null</code> if the JVM is running on a headless environment\r\n     * @see com.mucommander.commons.file.FileFactory#getDefaultFileIconProvider()\r\n     * @see com.mucommander.commons.file.icon.FileIconProvider#getFileIcon(AbstractFile, java.awt.Dimension)\r\n     */\r\n    public final Icon getIcon() {\r\n        // Note: the Dimension object is created here instead of returning a final static field, because creating\r\n        // a Dimension object triggers the AWT and Swing classes loading. Since these classes are not\r\n        // needed in a headless environment, we want them to be loaded only if strictly necessary.\r\n        return getIcon(new java.awt.Dimension(16, 16));\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns a checksum of this file (also referred to as <i>hash</i> or <i>digest</i>) calculated by reading this\r\n     * file's contents and feeding the bytes to the given <code>MessageDigest</code>, until EOF is reached.\r\n     *\r\n     * <p>The checksum is returned as an hexadecimal string, such as \"6d75636f0a\". The length of this string depends on\r\n     * the kind of algorithm.\r\n     *\r\n     * <p>Note: this method does not reset the <code>MessageDigest</code> after the checksum has been calculated.\r\n     *\r\n     * @param algorithm the algorithm to use for calculating the checksum\r\n     * @return this file's checksum, as an hexadecimal string\r\n     * @throws IOException if an I/O error occurred while calculating the checksum\r\n     * @throws NoSuchAlgorithmException if the specified algorithm does not correspond to any MessageDigest registered\r\n     * with the Java Cryptography Extension.\r\n     * @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported\r\n     * or not implemented by the underlying filesystem.\r\n     */\r\n    public final String calculateChecksum(String algorithm) throws IOException, NoSuchAlgorithmException {\r\n        return calculateChecksum(MessageDigest.getInstance(algorithm));\r\n    }\r\n\r\n    /**\r\n     * Returns a checksum of this file (also referred to as <i>hash</i> or <i>digest</i>) calculated by reading this\r\n     * file's contents and feeding the bytes to the given <code>MessageDigest</code>, until EOF is reached.\r\n     *\r\n     * <p>The checksum is returned as an hexadecimal string, such as \"6d75636f0a\". The length of this string depends on\r\n     * the kind of <code>MessageDigest</code>.\r\n     *\r\n     * <p>Note: this method does not reset the <code>MessageDigest</code> after the checksum has been calculated.\r\n     *\r\n     * @param messageDigest the MessageDigest to use for calculating the checksum\r\n     * @return this file's checksum, as an hexadecimal string\r\n     * @throws IOException if an I/O error occurred while calculating the checksum\r\n     * @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported\r\n     * or not implemented by the underlying filesystem.\r\n     */\r\n    public final String calculateChecksum(MessageDigest messageDigest) throws IOException {\r\n        try (InputStream in = getInputStream()) {\r\n            return calculateChecksum(in, messageDigest);\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Tests if the given path contains a trailing separator, and if not, adds one to the returned path.\r\n     * The separator used is the one returned by {@link #getSeparator()}.\r\n     *\r\n     * @param path the path for which to add a trailing separator\r\n     * @return the path with a trailing separator\r\n     */\r\n    public final String addTrailingSeparator(String path) {\r\n        // Even though getAbsolutePath() is not supposed to return a trailing separator, root folders ('/', 'c:\\' ...)\r\n        // are exceptions that's why we still have to test if path ends with a separator\r\n        String separator = getSeparator();\r\n        if (!path.endsWith(separator)) {\r\n            return path + separator;\r\n        }\r\n        return path;\r\n    }\r\n\r\n    /**\r\n     * Tests if the given path contains a trailing separator, and if it does, removes it from the returned path.\r\n     * The separator used is the one returned by {@link #getSeparator()}.\r\n     *\r\n     * @param path the path for which to remove the trailing separator\r\n     * @return the path free of a trailing separator\r\n     */\r\n    protected final String removeTrailingSeparator(String path) {\r\n        // Remove trailing slash if path is not '/' or trailing backslash if path does not end with ':\\'\r\n        // (Reminder: C: is C's current folder, while C:\\ is C's root)\r\n        String separator = getSeparator();\r\n        if (path.endsWith(separator)\r\n           && !((separator.equals(\"/\") && path.length() == 1) || (separator.equals(\"\\\\\") && path.charAt(path.length()-2)==':')))\r\n            path = path.substring(0, path.length()-1);\r\n        return path;\r\n    }\r\n\r\n\r\n    /**\r\n     * Checks the prerequisites of a copy (or move) operation.\r\n     * Throws a {@link FileTransferException} if any of the following conditions are true, does nothing otherwise:\r\n     * <ul>\r\n     *   <li>this file does not exist</li>\r\n     *   <li>this file and the destination file are the same, unless <code>allowCaseVariations</code> is <code>true</code>\r\n     * and the destination filename is a case variation of the source</li>\r\n     *   <li>this file is a parent of the destination file</li>\r\n     * </ul>\r\n     *\r\n     * @param destFile the destination file to copy this file to\r\n     * @param allowCaseVariations prevents throwing an exception if both file names are a case variation of one another\r\n     * @throws FileTransferException in any of the cases listed above, use {@link FileTransferException#getReason()} to\r\n     * know the reason.\r\n     */\r\n    protected final void checkCopyPrerequisites(AbstractFile destFile, boolean allowCaseVariations) throws FileTransferException {\r\n        boolean isAllowedCaseVariation = false;\r\n\r\n        // Throw an exception of a specific kind if the source and destination files refer to the same file\r\n        boolean filesEqual = this.equalsCanonical(destFile);\r\n        if (filesEqual) {\r\n            // If case variations are allowed and the destination filename is a case variation of the source,\r\n            // do not throw an exception.\r\n            if (allowCaseVariations) {\r\n                String sourceFileName = getName();\r\n                String destFileName = destFile.getName();\r\n                if (sourceFileName.equalsIgnoreCase(destFileName) && !sourceFileName.equals(destFileName)) {\r\n                    isAllowedCaseVariation = true;\r\n                }\r\n            }\r\n\r\n            if (!isAllowedCaseVariation) {\r\n                throw new FileTransferException(FileTransferException.SOURCE_AND_DESTINATION_IDENTICAL);\r\n            }\r\n        }\r\n\r\n        // Throw an exception if source is a parent of destination\r\n        if (!filesEqual && isParentOf(destFile)) {      // Note: isParentOf(destFile) returns true if both files are equal\r\n            throw new FileTransferException(FileTransferException.SOURCE_PARENT_OF_DESTINATION);\r\n        }\r\n\r\n        // Throw an exception if the source file does not exist\r\n        if (!exists()) {\r\n            throw new FileTransferException(FileTransferException.FILE_NOT_FOUND);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Checks the prerequisites of a {@link #copyRemotelyTo(AbstractFile)} operation.\r\n     * This method starts by verifying the following requirements and throws an <code>IOException</code> if one of them\r\n     * isn't met:\r\n     * <ul>\r\n     *   <li>both files' schemes are equal</li>\r\n     *   <li>both files' {@link #getTopAncestor() top ancestors} are equal</li>\r\n     *   <li>both files' hosts are equal, or <code>allowDifferentHosts</code> is <code>true</code></li>\r\n     * </ul>\r\n     * If all those requirements are met, {@link #checkCopyPrerequisites(AbstractFile, boolean)} is called with the\r\n     * destination file and <code>allowCaseVariations</code> flag to perform prerequisites verifications.\r\n     *\r\n     * @param destFile the destination file to copy this file to\r\n     * @param allowCaseVariations prevents throwing an exception if both file names are a case variation of one another\r\n     * @param allowDifferentHosts prevents throwing an exception if both files have the same host\r\n     * @throws FileTransferException in any of the cases listed above, use {@link FileTransferException#getReason()} to\r\n     * know the reason.\r\n     * @see #checkCopyPrerequisites(AbstractFile, boolean)\r\n     */\r\n    protected final void checkCopyRemotelyPrerequisites(AbstractFile destFile, boolean allowCaseVariations, boolean allowDifferentHosts) throws IOException {\r\n        if (!fileURL.schemeEquals(fileURL)\r\n            || !destFile.getTopAncestor().getClass().equals(getTopAncestor().getClass())\r\n            || (!allowDifferentHosts && !destFile.getURL().hostEquals(fileURL)))\r\n            throw new IOException();\r\n\r\n        checkCopyPrerequisites(destFile, allowCaseVariations);\r\n    }\r\n\r\n    /**\r\n     * Checks the prerequisites of a {@link #renameTo(AbstractFile)} operation.\r\n     * This method starts by verifying the following requirements and throws an <code>IOException</code> if one of them\r\n     * isn't met:\r\n     * <ul>\r\n     *   <li>both files' schemes are equal</li>\r\n     *   <li>both files' {@link #getTopAncestor() top ancestors} are equal</li>\r\n     *   <li>both files' hosts are equal, or <code>allowDifferentHosts</code> is <code>true</code></li>\r\n     * </ul>\r\n     * If all those requirements are met, {@link #checkCopyPrerequisites(AbstractFile, boolean)} is called with the\r\n     * destination file and <code>allowCaseVariations</code> flag to perform further prerequisites verifications.\r\n     *\r\n     * @param destFile the destination file to copy this file to\r\n     * @param allowCaseVariations prevents throwing an exception if both file names are a case variation of one another\r\n     * @param allowDifferentHosts prevents throwing an exception if both files have the same host\r\n     * @throws FileTransferException in any of the cases listed above, use {@link FileTransferException#getReason()} to\r\n     * know the reason.\r\n     * @see #checkCopyPrerequisites(AbstractFile, boolean)\r\n     */\r\n    protected final void checkRenamePrerequisites(AbstractFile destFile, boolean allowCaseVariations, boolean allowDifferentHosts) throws IOException {\r\n        checkCopyRemotelyPrerequisites(destFile, allowCaseVariations, allowDifferentHosts);\r\n    }\r\n\r\n    /**\r\n     * Copies the source file to the destination one and recurses on directory contents.\r\n     * This method assumes that the destination file does not exists, this must be checked prior to calling this method.\r\n     * Symbolic links are skipped when encountered: neither the link nor the linked file are copied.\r\n     *\r\n     * @param sourceFile the file to copy\r\n     * @param destFile the destination file\r\n     * @throws FileTransferException if an error occurred while copying the file\r\n     */\r\n    protected final void copyRecursively(AbstractFile sourceFile, AbstractFile destFile) throws FileTransferException {\r\n        if (sourceFile.isSymlink()) {\r\n            return;\r\n        }\r\n\r\n        if (sourceFile.isDirectory()) {\r\n            try {\r\n                destFile.mkdir();\r\n            } catch(IOException e) {\r\n                throw new FileTransferException(FileTransferException.WRITING_DESTINATION);\r\n            }\r\n\r\n            AbstractFile[] children;\r\n            try {\r\n                children = sourceFile.ls();\r\n            } catch(IOException e) {\r\n                throw new FileTransferException(FileTransferException.READING_SOURCE);\r\n            }\r\n\r\n            AbstractFile destChild;\r\n            for (AbstractFile child : children) {\r\n                try {\r\n                    destChild = destFile.getDirectChild(child.getName());\r\n                } catch (IOException e) {\r\n                    throw new FileTransferException(FileTransferException.OPENING_DESTINATION);\r\n                }\r\n                copyRecursively(child, destChild);\r\n            }\r\n        } else {\r\n//            try (InputStream in = sourceFile.getInputStream()) {\r\n//                destFile.copyStream(in, false, sourceFile.getSize());\r\n//            } catch (IOException e) {\r\n//                throw new FileTransferException(FileTransferException.OPENING_SOURCE);\r\n//            }\r\n            InputStream in;\r\n\r\n            try {\r\n                in = sourceFile.getInputStream();\r\n            } catch(IOException e) {\r\n                throw new FileTransferException(FileTransferException.OPENING_SOURCE);\r\n            }\r\n\r\n            try {\r\n                destFile.copyStream(in, false, sourceFile.getSize());\r\n            } finally {\r\n                // Close stream even if copyStream() threw an IOException\r\n                try {\r\n                    in.close();\r\n                } catch (IOException e) {\r\n                    throw new FileTransferException(FileTransferException.CLOSING_SOURCE);\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Deletes the given file. If the file is a directory, enclosing files are deleted recursively.\r\n     * Symbolic links to directories are simply deleted, without deleting the contents of the linked directory.\r\n     *\r\n     * @param file the file to delete\r\n     * @throws IOException if an error occurred while deleting a file or listing a directory's contents\r\n     * @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported\r\n     * or not implemented by the underlying filesystem.\r\n     */\r\n    private void deleteRecursively(AbstractFile file) throws IOException {\r\n        if (file.isDirectory() && !file.isSymlink()) {\r\n            AbstractFile[] children = file.ls();\r\n            for (AbstractFile child : children) {\r\n                deleteRecursively(child);\r\n            }\r\n        }\r\n\r\n        file.delete();\r\n    }\r\n\r\n    /**\r\n     * Convenience method that calls {@link #changePermissions(int)} with the given permissions' int value.\r\n     *\r\n     * @param permissions new permissions for this file\r\n     * @throws IOException if the permissions couldn't be changed, either because of insufficient permissions or because\r\n     * of an I/O error.\r\n     * @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported\r\n     * or not implemented by the underlying filesystem.\r\n     */\r\n    public final void changePermissions(FilePermissions permissions) throws IOException {\r\n        changePermissions(permissions.getIntValue());\r\n    }\r\n\r\n    /**\r\n     * This method is a shorthand for {@link #importPermissions(AbstractFile, FilePermissions)} called with\r\n     * {@link FilePermissions#DEFAULT_DIRECTORY_PERMISSIONS} if this file is a directory or\r\n     * {@link FilePermissions#DEFAULT_FILE_PERMISSIONS} if this file is a regular file.\r\n     *\r\n     * @param sourceFile the file from which to import permissions\r\n     * @throws IOException if the permissions couldn't be changed, either because of insufficient permissions or because\r\n     * of an I/O error.\r\n     * @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported\r\n     * or not implemented by the underlying filesystem.\r\n     */\r\n    public final void importPermissions(AbstractFile sourceFile) throws IOException {\r\n        importPermissions(sourceFile, isDirectory()\r\n                ? FilePermissions.DEFAULT_DIRECTORY_PERMISSIONS\r\n                : FilePermissions.DEFAULT_FILE_PERMISSIONS);\r\n    }\r\n\r\n    /**\r\n     * Imports the given source file's permissions, overwriting this file's permissions. Only the bits that are\r\n     * supported by the source file (as reported by the permissions' mask) are preserved. Other bits are be\r\n     * set to those of the specified default permissions.\r\n     * See {@link SimpleFilePermissions#padPermissions(FilePermissions, FilePermissions)} for more information about\r\n     * permissions padding.\r\n     *\r\n     * @param sourceFile the file from which to import permissions\r\n     * @param defaultPermissions default permissions to use\r\n     * @throws IOException if the permissions couldn't be changed, either because of insufficient permissions or because\r\n     * of an I/O error.\r\n     * @throws UnsupportedFileOperationException if this method relies on a file operation that is not supported\r\n     * or not implemented by the underlying filesystem.\r\n     * @see SimpleFilePermissions#padPermissions(FilePermissions, FilePermissions)\r\n     */\r\n    public final void importPermissions(AbstractFile sourceFile, FilePermissions defaultPermissions) throws IOException {\r\n        changePermissions(SimpleFilePermissions.padPermissions(sourceFile.getPermissions(), defaultPermissions).getIntValue());\r\n    }\r\n\r\n\r\n    ////////////////////\r\n    // Static methods //\r\n    ////////////////////\r\n\r\n    /**\r\n     * Returns <code>true</code> if the specified file operation and corresponding method is supported by the\r\n     * given <code>AbstractFile</code> implementation.<br>\r\n     * See the {@link FileOperation} enum for a complete list of file operations and their corresponding\r\n     * <code>AbstractFile</code> methods.\r\n     *\r\n     * @param op a file operation\r\n     * @param c the file implementation to test\r\n     * @return <code>true</code> if the specified file operation is supported by this filesystem.\r\n     * @see FileOperation\r\n     */\r\n    public static boolean isFileOperationSupported(FileOperation op, Class<? extends AbstractFile> c) {\r\n        Method method = op.getCorrespondingMethod(c);\r\n        return method != null && !method.isAnnotationPresent(UnsupportedFileOperation.class);\r\n    }\r\n\r\n    /**\r\n     * Returns the given filename's extension, <code>null</code> if the filename doesn't have an extension.\r\n     *\r\n     * <p>A filename has an extension if and only if:<br>\r\n     * - it contains at least one <code>.</code> character<br>\r\n     * - the last <code>.</code> is not the last character of the filename<br>\r\n     * - the last <code>.</code> is not the first character of the filename\r\n     *\r\n     * <p>\r\n     * The returned extension (if any) is free of any extension separator character (<code>.</code>). For instance,\r\n     * this method will return <code>\"ext\"</code> for a file named <code>\"name.ext\"</code>, <b>not</b> <code>\".ext\"</code>.\r\n     *\r\n     * @param filename a filename, not a full path\r\n     * @return the given filename's extension, <code>null</code> if the filename doesn't have an extension\r\n     */\r\n    public static String getExtension(String filename) {\r\n        int lastDotPos = filename.lastIndexOf('.');\r\n        if (lastDotPos <= 0) {\r\n            return null;\r\n        }\r\n        int len = filename.length();\r\n        if (lastDotPos == len-1) {\r\n            return null;\r\n        }\r\n        return filename.substring(lastDotPos+1, len);\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the given filename without its extension (base name). if the filename doesn't have an extension, returns the filename as received\r\n     *\r\n     * <p>A filename has an extension if and only if:<br>\r\n     * - it contains at least one <code>.</code> character<br>\r\n     * - the last <code>.</code> is not the last character of the filename<br>\r\n     * - the last <code>.</code> is not the first character of the filename\r\n     *\r\n     * @return the file's base name - without its extension, if the filename doesn't have an extension returns the filename as received\r\n     */\r\n    public String getBaseName() {\r\n        String fileName = getName();\r\n        int lastDotPos = fileName.lastIndexOf('.');\r\n        if (lastDotPos <= 0 || lastDotPos == fileName.length()-1) {\r\n            return fileName;\r\n        }\r\n         \r\n        return fileName.substring(0, lastDotPos);\r\n    }\r\n    \r\n    /**\r\n     * Returns the checksum (also referred to as <i>hash</i> or <i>digest</i>) of the given <code>InputStream</code>\r\n     * calculated by reading the stream and feeding the bytes to the given <code>MessageDigest</code> until EOF is\r\n     * reached.\r\n     *\r\n     * <p><b>Important:</b> this method does not close the <code>InputStream</code>, and does not reset the\r\n     * <code>MessageDigest</code> after the checksum has been calculated.\r\n     *\r\n     * @param in the InputStream for which to calculate the checksum\r\n     * @param messageDigest the MessageDigest to use for calculating the checksum\r\n     * @return the given InputStream's checksum, as an hexadecimal string\r\n     * @throws IOException if an I/O error occurred while calculating the checksum\r\n     */\r\n    public static String calculateChecksum(InputStream in, MessageDigest messageDigest) throws IOException {\r\n        ChecksumInputStream cin = new ChecksumInputStream(in, messageDigest);\r\n        try {\r\n            StreamUtils.readUntilEOF(cin);\r\n            return cin.getChecksumString();\r\n        } catch (IOException e) {\r\n            throw new FileTransferException(FileTransferException.READING_SOURCE);\r\n        }\r\n    }\r\n\r\n    \r\n    ////////////////////////\r\n    // Overridden methods //\r\n    ////////////////////////\r\n\r\n    /**\r\n     * Tests a file for equality by comparing both files' {@link #getURL() URL}. Returns <code>true</code> if the URL\r\n     * of this file and the specified one are equal according to {@link FileURL#equals(Object, boolean, boolean)} called\r\n     * with credentials and properties comparison enabled.\r\n     *\r\n     * <p>\r\n     * Unlike {@link #equalsCanonical(Object)}, this method <b>is not</b> allowed to perform I/O operations and block\r\n     * the caller thread.\r\n     *\r\n     * @param o the object to compare against this instance\r\n     * @return Returns <code>true</code> if the URL of this file and the specified one are equal\r\n     * @see FileURL#equals(Object, boolean, boolean)\r\n     * @see #equalsCanonical(Object)\r\n     */\r\n    public boolean equals(Object o) {\r\n        return o instanceof AbstractFile && getURL().equals(((AbstractFile) o).getURL(), true, true);\r\n    }\r\n\r\n    /**\r\n     * Tests a file for equality by comparing both files' {@link #getCanonicalPath() canonical path}.\r\n     * Returns <code>true</code> if the canonical path of this file and the specified one are equal.\r\n     *\r\n     * <p>It is noteworthy that this method uses <code>java.lang.String#equals(Object)</code> to compare paths, which\r\n     * in some rare cases may return <code>false</code> for non-ascii/Unicode paths that have the same written\r\n     * representation but are not equal according to <code>java.lang.String#equals(Object)</code>. Handling such cases\r\n     * would require a locale-aware String comparison which is not an option here.\r\n     *\r\n     * <p>It is also worth noting that hostnames are not resolved, which means this method does not consider\r\n     * a hostname and its corresponding IP address as being equal.\r\n     *\r\n     * <p>Unlike {@link #equals(Object)}, this method <b>is</b> allowed to perform I/O operations and block\r\n     * the caller thread.\r\n     *\r\n     * @param o the object to compare against this instance\r\n     * @return <code>true</code> if the canonical path of this file and the specified one are equal.\r\n     * @see #equals(Object)\r\n     */\r\n    public boolean equalsCanonical(Object o) {\r\n        if (o instanceof AbstractFile) {\r\n            // TODO: resolve hostnames ?\r\n            return getCanonicalPath(false).equals(((AbstractFile)o).getCanonicalPath(false));\r\n        }\r\n        return false;\r\n    }\r\n\r\n    /**\r\n     * Returns the hashCode of this file's {@link #getURL() URL}.\r\n     *\r\n     * @return the hashCode of this file's {@link #getURL() URL}.\r\n     */\r\n    public int hashCode() {\r\n        return getURL().hashCode();\r\n    }\r\n\r\n    /**\r\n     * Returns a String representation of this file. The returned String is this file's path as returned by\r\n     * {@link #getAbsolutePath()}.\r\n     */\r\n    public String toString() {\r\n        return getAbsolutePath();\r\n    }\r\n\r\n\r\n    //////////////////////\r\n    // Abstract methods //\r\n    //////////////////////\r\n\r\n    /**\r\n     * Returns this file's last modified date, in milliseconds since the epoch (00:00:00 GMT, January 1, 1970).\r\n     *\r\n     * @return this file's last modified date, in milliseconds since the epoch (00:00:00 GMT, January 1, 1970)\r\n     */\r\n    public abstract long getLastModifiedDate();\r\n\r\n    /**\r\n     * Returns this file's creation date, in milliseconds since the epoch (00:00:00 GMT, January 1, 1970).\r\n     *\r\n     * @throws IOException if an I/O error occurred\r\n     * @return creation date\r\n     */\r\n    public long getCreationDate() throws IOException {\r\n        throw new IOException(\"operation not supported\");\r\n    }\r\n\r\n    /**\r\n     * Returns this file's last access date, in milliseconds since the epoch (00:00:00 GMT, January 1, 1970).\r\n     *\r\n     * @throws IOException if an I/O error occurred\r\n     * @return last access date\r\n     */\r\n    public long getLastAccessDate() throws IOException {\r\n        throw new IOException(\"operation not supported\");\r\n    }\r\n\r\n    /**\r\n     * Changes this file's last modified date to the specified one. Throws an <code>IOException</code> if the date\r\n     * couldn't be changed, either because of insufficient permissions or because of an I/O error.\r\n     *\r\n     * <p>This {@link FileOperation#CHANGE_DATE file operation} may or may not be supported by the underlying filesystem\r\n     * -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't\r\n     * supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.\r\n     *\r\n     * @param lastModified last modified date, in milliseconds since the epoch (00:00:00 GMT, January 1, 1970)\r\n     * @throws IOException if the date couldn't be changed, either because of insufficient permissions or because of\r\n     * an I/O error.\r\n     * @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,\r\n     * or is not implemented.\r\n     */\r\n    public abstract void setLastModifiedDate(long lastModified) throws IOException;\r\n\r\n    public abstract void changeReplication(short replication) throws IOException;\r\n    /**\r\n     * Returns this file's size in bytes, <code>0</code> if this file doesn't exist, <code>-1</code> if the size is\r\n     * undetermined.\r\n     *\r\n     * @return this file's size in bytes, 0 if this file doesn't exist, -1 if the size is undetermined\r\n     */\r\n    public abstract long getSize();\r\n\t\r\n    /**\r\n     * Returns this file's parent, <code>null</code> if it doesn't have one.\r\n     *\r\n     * @return this file's parent, <code>null</code> if it doesn't have one\r\n     */\r\n    public abstract AbstractFile getParent();\r\n\t\r\n    /**\r\n     * Sets this file's parent. <code>null</code> can be specified if this file doesn't have a parent.\r\n     *\r\n     * @param parent the new parent of this file\r\n     */\r\n    public abstract void setParent(AbstractFile parent);\r\n\r\n    /**\r\n     * Returns <code>true</code> if this file exists.\r\n     *\r\n     * @return <code>true</code> if this file exists\r\n     */\r\n    public abstract boolean exists();\r\n\r\n    /**\r\n     * Returns this file's permissions, as a {@link FilePermissions} object. Note that this file may only support\r\n     * certain permission bits, use the {@link com.mucommander.commons.file.FilePermissions#getMask() permission mask} to find\r\n     * out which bits are supported.\r\n     *\r\n     * <p>This method may return permissions for which none of the bits are supported, but may never return\r\n     * <code>null</code>.\r\n     *\r\n     * @return this file's permissions, as a FilePermissions object\r\n     */\r\n    public abstract FilePermissions getPermissions();\r\n\r\n    /**\r\n     * Returns a bit mask describing the permission bits that can be changed on this file when calling\r\n     * {@link #changePermission(int, int, boolean)} and {@link #changePermissions(int)}.\r\n     *\r\n     * @return a bit mask describing the permission bits that can be changed on this file\r\n     */\r\n    public abstract PermissionBits getChangeablePermissions();\r\n\r\n    /**\r\n     * Changes the specified permission bit.\r\n     *\r\n     * <p>This {@link FileOperation#CHANGE_PERMISSION file operation} may or may not be supported by the underlying filesystem\r\n     * -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't\r\n     * supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.\r\n     *\r\n     * @param access see {@link PermissionTypes} for allowed values\r\n     * @param permission see {@link PermissionAccesses} for allowed values\r\n     * @param enabled true to enable the flag, false to disable it\r\n     * @throws IOException if the permission couldn't be changed, either because of insufficient permissions or because\r\n     * of an I/O error.\r\n     * @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,\r\n     * or is not implemented.\r\n     * @see #getChangeablePermissions()\r\n     */\r\n    public abstract void changePermission(int access, int permission, boolean enabled) throws IOException;\r\n\r\n    /**\r\n     * Returns information about the owner of this file. The kind of information that is returned is implementation-dependant.\r\n     * It may typically be a username (e.g. 'bob') or a user ID (e.g. '501').\r\n     * If the owner information is not available to the <code>AbstractFile</code> implementation (cannot be retrieved or\r\n     * the filesystem doesn't have any notion of owner) or not available for this particular file, <code>null</code>\r\n     * will be returned.\r\n     *\r\n     * @return information about the owner of this file\r\n     */\r\n    public abstract String getOwner();\r\n\r\n\r\n    /**\r\n     * Returns information about the owner of this file. The kind of information that is returned is implementation-dependant.\r\n     * It may typically be a username (e.g. 'bob') or a user ID (e.g. '501').\r\n     * If the owner information is not available to the <code>AbstractFile</code> implementation (cannot be retrieved or\r\n     * the filesystem doesn't have any notion of owner) or not available for this particular file, <code>null</code>\r\n     * will be returned.\r\n     *\r\n     * @return information about the owner of this file\r\n     * @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,\r\n     * or is not implemented.\r\n     */\r\n    public abstract short getReplication() throws UnsupportedFileOperationException;\r\n\r\n\r\n    /**\r\n     * Returns information about the owner of this file. The kind of information that is returned is implementation-dependant.\r\n     * It may typically be a username (e.g. 'bob') or a user ID (e.g. '501').\r\n     * If the owner information is not available to the <code>AbstractFile</code> implementation (cannot be retrieved or\r\n     * the filesystem doesn't have any notion of owner) or not available for this particular file, <code>null</code>\r\n     * will be returned.\r\n     *\r\n     * @return information about the owner of this file\r\n     * @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,\r\n     * or is not implemented.\r\n     */\r\n    public abstract long getBlocksize() throws UnsupportedFileOperationException;\r\n\r\n    /**\r\n     * Returns <code>true</code> if this file implementation is able to return some information about file owners, not\r\n     * necessarily for all files or this file in particular but at least for some of them. In other words, a\r\n     * <code>true</code> return value doesn't mean that {@link #getOwner()} will necessarily return a non-null value,\r\n     * but rather that there is a chance that it does.\r\n     *\r\n     * @return true if this file implementation is able to return information about file owners  \r\n     */\r\n    public abstract boolean canGetOwner();\r\n\r\n    /**\r\n     * Returns information about the group this file belongs to. The kind of information that is returned is implementation-dependant.\r\n     * It may typically be a group name (e.g. 'www-data') or a group ID (e.g. '501').\r\n     * If the group information is not available to the <code>AbstractFile</code> implementation (cannot be retrieved or\r\n     * the filesystem doesn't have any notion of owner) or not available for this particular file, <code>null</code>\r\n     * will be returned.\r\n     *\r\n     * @return information about the owner of this file\r\n     */\r\n    public abstract String getGroup();\r\n\r\n    /**\r\n     * Returns <code>true</code> if this file implementation is able to return some information about file groups, not\r\n     * necessarily for all files or this file in particular but at least for some of them. In other words, a\r\n     * <code>true</code> return value doesn't mean that {@link #getGroup()} will necessarily return a non-null value,\r\n     * but rather that there is a chance that it does.\r\n     *\r\n     * @return true if this file implementation is able to return information about file groups\r\n     */\r\n    public abstract boolean canGetGroup();\r\n\r\n    /**\r\n     * Returns <code>true</code> if this file is a directory, <code>false</code> in any of the following cases:\r\n     * <ul>\r\n     *  <li>this file does not exist</li>\r\n     *  <li>this file is a regular file</li>\r\n     *  <li>this file is an {@link #isArchive() archive}</li>\r\n     * </ul> \r\n     *\r\n     * @return <code>true</code> if this file is a directory, <code>false</code> in any of the cases listed above\r\n     */\r\n    public abstract boolean isDirectory();\r\n\r\n    /**\r\n     * Returns <code>true</code> if this file is an archive.\r\n     * <p>\r\n     * An archive is a file container that can be {@link #isBrowsable() browsed}.  Archive files may not be\r\n     * {@link #isDirectory() directories}, and vice-versa.\r\n     *\r\n     * @return <code>true</code> if this file is an archive.\r\n     */\r\n    public abstract boolean isArchive();\r\n\r\n    /**\r\n     * Returns <code>true</code> if this file is a symbolic link. Symbolic links need to be handled with special care,\r\n     * especially when manipulating files recursively.\r\n     *\r\n     * @return <code>true</code> if this file is a symbolic link\r\n     */\r\n    public abstract boolean isSymlink();\r\n\r\n    /**\r\n     * Returns <code>true</code> if this file is a system file.\r\n     * Note that system file attribute depends on the OS, so we can know it only for local files:\r\n     * - For MAC OS, {@link MacOsSystemFolder} defines the group of system files\r\n     * - On Windows, files has special attribute that mark them as system files\r\n     *\r\n     * @return <code>true</code> if this file is a system file\r\n     */\r\n    public abstract boolean isSystem();\r\n\t\r\n    /**\r\n     * Returns the children files that this file contains. For this operation to be successful, this file must be\r\n     * 'browsable', i.e. {@link #isBrowsable()} must return <code>true</code>.\r\n     * This method may return a zero-length array if it has no children but may never return <code>null</code>.\r\n     *\r\n     * <p>This {@link FileOperation#LIST_CHILDREN file operation} may or may not be supported by the underlying filesystem\r\n     * -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't\r\n     * supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.\r\n     *\r\n     * @return the children files that this file contains\r\n     * @throws IOException if this operation is not possible (file is not browsable) or if an error occurred.\r\n     * @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,\r\n     * or is not implemented.\r\n     */\r\n    public abstract AbstractFile[] ls() throws IOException;\r\n\r\n    /**\r\n     * Creates this file as a directory. This method will fail (throw an <code>IOException</code>) if this file\r\n     * already exists.\r\n     *\r\n     * <p>This {@link FileOperation#CREATE_DIRECTORY file operation} may or may not be supported by the underlying filesystem\r\n     * -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't\r\n     * supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.\r\n     *\r\n     * @throws IOException if the directory could not be created, either because this file already exists or for any\r\n     * other reason.\r\n     * @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,\r\n     * or is not implemented.\r\n     */\r\n    public abstract void mkdir() throws IOException;\r\n\r\n    /**\r\n     * Returns an <code>InputStream</code> to read the contents of this file.\r\n     * Throws an <code>IOException</code> in any of the following cases:\r\n     * <ul>\r\n     *  <li>this file does not exist</li>\r\n     *  <li>this file is a directory</li>\r\n     *  <li>this file cannot be read</li>\r\n     *  <li>an I/O error occurs</li>\r\n     * </ul>\r\n     * This method may never return <code>null</code>.\r\n     *\r\n     * <p>This {@link FileOperation#READ_FILE file operation} may or may not be supported by the underlying filesystem\r\n     * -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't\r\n     * supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.\r\n     *\r\n     * @return an <code>InputStream</code> to read the contents of this file\r\n     * @throws IOException in any of the cases listed above\r\n     * @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,\r\n     * or is not implemented.\r\n     */\r\n    public abstract InputStream getInputStream() throws IOException;\r\n\r\n    /**\r\n     * Returns an <code>OutputStream</code> to write the contents of this file, overwriting the existing contents, if any.\r\n     * This file will be created as a zero-byte file if it does not yet exist.\r\n     * <p>\r\n     * This method may throw an <code>IOException</code> in any of the following cases, but may never return\r\n     * <code>null</code>:\r\n     * <ul>\r\n     *   <li>this file is a directory</li>\r\n     *   <li>this file cannot be written</li>\r\n     *   <li>an I/O error occurs</li>\r\n     * </ul>\r\n     *\r\n     * <p>This {@link FileOperation#WRITE_FILE file operation} may or may not be supported by the underlying filesystem\r\n     * -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't\r\n     * supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.\r\n     *\r\n     * @return an <code>OutputStream</code> to write the contents of this file\r\n     * @throws IOException in any of the cases listed above\r\n     * @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,\r\n     * or is not implemented.\r\n     */\r\n    public abstract OutputStream getOutputStream() throws IOException;\r\n\r\n    /**\r\n     * Returns an <code>OutputStream</code> to write the contents of this file, appending the existing contents, if any.\r\n     * This file will be created as a zero-byte file if it does not yet exist.\r\n     * <p>\r\n     * This method may throw an <code>IOException</code> in any of the following cases, but may never return\r\n     * <code>null</code>:\r\n     * <ul>\r\n     *   <li>this file is a directory</li>\r\n     *   <li>this file cannot be written</li>\r\n     *   <li>an I/O error occurs</li>\r\n     * </ul>\r\n     *\r\n     * <p>This {@link FileOperation#APPEND_FILE file operation} may or may not be supported by the underlying filesystem\r\n     * -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't\r\n     * supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.\r\n     *\r\n     * @return an <code>OutputStream</code> to write the contents of this file\r\n     * @throws IOException in any of the cases listed above\r\n     * @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,\r\n     * or is not implemented.\r\n     */\r\n    public abstract OutputStream getAppendOutputStream() throws IOException;\r\n\r\n    /**\r\n     * Returns a {@link RandomAccessInputStream} to read the contents of this file with random access.\r\n     * Throws an <code>IOException</code> in any of the following cases:\r\n     * <ul>\r\n     *  <li>this file does not exist</li>\r\n     *  <li>this file is a directory</li>\r\n     *  <li>this file cannot be read</li>\r\n     *  <li>an I/O error occurs</li>\r\n     * </ul>\r\n     * This method may never return <code>null</code>.\r\n     *\r\n     * <p>This {@link FileOperation#RANDOM_READ_FILE file operation} may or may not be supported by the underlying filesystem\r\n     * -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't\r\n     * supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.\r\n     *\r\n     * @return a <code>RandomAccessInputStream</code> to read the contents of this file with random access\r\n     * @throws IOException in any of the cases listed above\r\n     * @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,\r\n     * or is not implemented.\r\n     */\r\n    public abstract RandomAccessInputStream getRandomAccessInputStream() throws IOException;\r\n\r\n    /**\r\n     * Returns a {@link RandomAccessOutputStream} to write the contents of this file with random access.\r\n     * This file will be created as a zero-byte file if it does not yet exist.\r\n     * Throws an <code>IOException</code> in any of the following cases:\r\n     * <ul>\r\n     *  <li>this file is a directory</li>\r\n     *  <li>this file cannot be written</li>\r\n     *  <li>an I/O error occurs</li>\r\n     * </ul>\r\n     * This method may never return <code>null</code>.\r\n     *\r\n     * <p>This {@link FileOperation#RANDOM_WRITE_FILE file operation} may or may not be supported by the underlying filesystem\r\n     * -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't\r\n     * supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.\r\n     *\r\n     * @return a <code>RandomAccessOutputStream</code> to write the contents of this file with random access\r\n     * @throws IOException in any of the cases listed above\r\n     * @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,\r\n     * or is not implemented.\r\n     */\r\n    public abstract RandomAccessOutputStream getRandomAccessOutputStream() throws IOException;\r\n\r\n    /**\r\n     * Deletes this file and this file only (does not recurse on folders).\r\n     * Throws an <code>IOException</code> in any of the following cases:\r\n     * <ul>\r\n     *  <li>if this file does not exist</li>\r\n     *  <li>if this file is a non-empty directory</li>\r\n     *  <li>if this file could not be deleted, for example because of insufficient permissions</li>\r\n     *  <li>if an I/O error occurred</li>\r\n     * </ul>\r\n     *\r\n     * <p>This {@link FileOperation#DELETE file operation} may or may not be supported by the underlying filesystem\r\n     * -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't\r\n     * supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.\r\n     *\r\n     * @throws IOException if this file does not exist or could not be deleted\r\n     * @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,\r\n     * or is not implemented.\r\n     */\r\n    public abstract void delete() throws IOException;\r\n\r\n    /**\r\n     * Renames this file to a specified destination file, overwriting the destination if it exists. If this file is a\r\n     * directory, any file or directory it contains will also be moved.\r\n     * After normal completion, this file will not exist anymore: {@link #exists()} will return <code>false</code>.\r\n     *\r\n     * <p>This method throws an {@link IOException} if the operation failed, for any of the following reasons:\r\n     * <ul>\r\n     *  <li>this file and the destination file are the same</li>\r\n     *  <li>this file is a directory and a parent of the destination file (the operation would otherwise loop indefinitely)</li>\r\n     *  <li>this file cannot be read</li>\r\n     *  <li>this file cannot be written</li>\r\n     *  <li>the destination file can not be written</li>\r\n     *  <li>an I/O error occurred</li>\r\n     * </ul>\r\n     *\r\n     * <p>This {@link FileOperation#RENAME file operation} may or may not be supported by the underlying filesystem\r\n     * -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't\r\n     * supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.\r\n     *\r\n     * @param destFile file to rename this file to\r\n     * @throws IOException in any of the error cases listed above\r\n     * @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,\r\n     * or is not implemented.\r\n     */\r\n    public abstract void renameTo(AbstractFile destFile) throws IOException;\r\n\r\n    /**\r\n     * Remotely copies this file to a specified destination file, overwriting the destination if it exists.\r\n     * If this file is a directory, any file or directory it contains will also be copied.\r\n     *\r\n     * <p>This method differs from {@link #copyTo(AbstractFile)} in that it performs a server-to-server copy of the\r\n     * file(s), without having the file's contents go through to the local process. This operation should only be\r\n     * implemented if it offers a performance advantage over a regular client-driven copy like\r\n     * {@link #copyTo(AbstractFile)}, or if {@link FileOperation#WRITE_FILE} is not supported (output streams cannot be\r\n     * retrieved) and thus a regular copy cannot succeed.\r\n     *\r\n     * <p>This method throws an {@link IOException} if the operation failed, for any of the following reasons:\r\n     * <ul>\r\n     *  <li>this file and the destination file are the same</li>\r\n     *  <li>this file is a directory and a parent of the destination file (the operation would otherwise loop indefinitely)</li>\r\n     *  <li>this file (or one if its children) cannot be read</li>\r\n     *  <li>the destination file (or one of its children) can not be written</li>\r\n     *  <li>an I/O error occurred</li>\r\n     * </ul>\r\n     *\r\n     * <p>The behavior in the case of an error occurring in the midst of the transfer is unspecified: files that have\r\n     * been copied (even partially) may or may not be left in the destination.\r\n     *\r\n     * @param destFile the destination file to copy this file to\r\n     * @throws IOException in any of the error cases listed above\r\n     * @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,\r\n     * or is not implemented.\r\n     */\r\n    public abstract void copyRemotelyTo(AbstractFile destFile) throws IOException;\r\n\r\n    /**\r\n     * Returns the free space (in bytes) on the disk/volume where this file is, <code>-1</code> if this information is\r\n     * not available.\r\n     *\r\n     * <p>This {@link FileOperation#GET_FREE_SPACE file operation} may or may not be supported by the underlying filesystem\r\n     * -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't\r\n     * supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.\r\n     *\r\n     * @return the free space (in bytes) on the disk/volume where this file is, <code>-1</code> if this information is\r\n     * not available.\r\n     * @throws IOException if an I/O error occurred\r\n     * @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,\r\n     * or is not implemented.\r\n     */\r\n    public abstract long getFreeSpace() throws IOException;\r\n\r\n    /**\r\n     * Returns the total space (in bytes) of the disk/volume where this file is.\r\n     *\r\n     * <p>This {@link FileOperation#GET_TOTAL_SPACE file operation} may or may not be supported by the underlying filesystem\r\n     * -- {@link #isFileOperationSupported(FileOperation)} can be called to find out if it is. If the operation isn't\r\n     * supported, a {@link UnsupportedFileOperation} will be thrown when this method is called.\r\n     *\r\n     * @return the total space (in bytes) of the disk/volume where this file is\r\n     * @throws IOException if an I/O error occurred\r\n     * @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,\r\n     * or is not implemented.\r\n     */\r\n    public abstract long getTotalSpace() throws IOException;\r\n\r\n    /**\r\n     * Returns the file Object of the underlying API providing access to the filesystem. The returned Object may expose\r\n     * filesystem-specific functionality that are not available in <code>AbstractFile</code>. Note however that the\r\n     * returned Object type may change over time, if the underlying API used to provide access to the filesystem\r\n     * changes, so this method should be used only as a last resort.\r\n     *\r\n     * <p>If the implemented filesystem has no such Object, <code>null</code> is returned.\r\n     *\r\n     * @return the file Object of the underlying API providing access to the filesystem, <code>null</code> if there\r\n     * is none\r\n     */\r\n    public abstract Object getUnderlyingFileObject();\r\n\r\n\r\n\r\n    /**\r\n     * Returns the stream which can be re-used for file reading.\r\n     * All method calls will return the same class until the stream was closed.\r\n     * If the stream has been created but has insufficient buffer size it will be recreated.\r\n     * This method used for the file viewer and editor etc. to prevent multiple re-opening of files (and archives).\r\n     *\r\n     * @param bufferSize minimum size of buffer for <code>PushbackInputStream</code>.\r\n     * @return he stream which can be re-used for file reading\r\n     * @throws IOException if an I/O error occurred\r\n     */\r\n    public synchronized PushbackInputStream getPushBackInputStream(final int bufferSize) throws IOException {\r\n        if (pushbackInputStream == null) {\r\n            pushbackInputStream = new MuPushbackInputStream(getInputStream(), bufferSize);\r\n        } else if (pushbackInputStream.getBufferSize() < bufferSize) {\r\n            pushbackInputStream.close();\r\n            pushbackInputStream = new MuPushbackInputStream(getInputStream(), bufferSize);\r\n        }\r\n        return pushbackInputStream;\r\n    }\r\n\r\n\r\n    /**\r\n     * Closes PushbackStream if it exists\r\n     * @throws IOException if an I/O error occurred\r\n     */\r\n    public void closePushbackInputStream() throws IOException {\r\n        if (pushbackInputStream != null) {\r\n            pushbackInputStream.close();\r\n        }\r\n    }\r\n\r\n    public boolean isLocalFile() {\r\n        return FileProtocols.FILE.equals(fileURL.getScheme()) && hasAncestor(LocalFile.class);\r\n    }\r\n\r\n\r\n    /**\r\n     *\r\n     */\r\n    private class MuPushbackInputStream extends PushbackInputStream implements HasProgress {\r\n\r\n        private final InputStream src;\r\n\r\n//        public MuPushbackInputStream(InputStream in) {\r\n//            super(in);\r\n//            src = in;\r\n//        }\r\n\r\n        MuPushbackInputStream(InputStream in, int size) {\r\n            super(in, size);\r\n            src = in;\r\n        }\r\n\r\n        @Override\r\n        public void close() throws IOException {\r\n            synchronized (AbstractFile.this) {\r\n                super.close();\r\n                src.close();\r\n                pushbackInputStream = null;\r\n            }\r\n        }\r\n\r\n        int getBufferSize() {\r\n            return buf.length;\r\n        }\r\n\r\n        @Override\r\n        public int getProgress() {\r\n            return hasProgress() ? ((HasProgress)in).getProgress() : -1;\r\n        }\r\n\r\n        @Override\r\n        public boolean hasProgress() {\r\n            return in instanceof HasProgress;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/AbstractFileClassLoader.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.commons.file;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URL;\nimport java.util.*;\n\n/**\n * <code>ClassLoader</code> implementation capable of loading classes from instances of {@link AbstractFile}.\n * <p>It's possible to modify this loader's classpath at runtime through the {@link #addFile(AbstractFile)} method.\n *\n * @author Nicolas Rinaudo\n */\npublic class AbstractFileClassLoader extends ClassLoader {\n\n    private final static org.slf4j.Logger LOGGER = null;//org.slf4j.LoggerFactory.getLogger(AbstractFileClassLoader.class);\n\n\t\n    /** All abstract files in which to look for classes and resources. */\n    private final Vector<AbstractFile> files;\n\n\n    /**\n     * Creates a new <code>AbstractFileClassLoader</code>.\n     * @param parent parent of the class loader.\n     */\n    public AbstractFileClassLoader(ClassLoader parent) {\n        super(parent);\n        files = new Vector<>();\n    }\n\n    /**\n     * Creates a new <code>AbstractFileClassLoader</code> that uses the system classloader as a parent.\n     */\n    public AbstractFileClassLoader() {\n        this(ClassLoader.getSystemClassLoader());\n    }\n\n\n    /**\n     * Adds the specified <code>file</code> to the class loader's classpath.\n     * <p>\n     * Note that the file will <b>not</b> be added if it's already in the classpath.\n     *\n     * @param  file                     file to add the class loader's classpath.\n     * @throws IllegalArgumentException if <code>file</code> is not browsable.\n     */\n    public void addFile(AbstractFile file) {\n        // Makes sure the specified file is browsable.\n        if (!file.isBrowsable()) {\n            throw new IllegalArgumentException();\n        }\n        // Only adds the file if it's not already there.\n        if (!contains(file)) {\n            files.add(file);\n        }\n    }\n\n    /**\n     * Returns an iterator on all files in this loader's classpath.\n     * @return an iterator on all files in this loader's classpath.\n     */\n    public Iterator<AbstractFile> files() {\n        return files.iterator();\n    }\n\n    /**\n     * Returns <code>true</code> if this loader's classpath already contains the specified file.\n     * @param  file file to look for.\n     * @return      <code>true</code> if this loader's classpath already contains the specified file.\n     */\n    public boolean contains(AbstractFile file) {\n        return files.contains(file);\n    }\n\n\n\n    // - Resource access -------------------------------------------------------\n    // -------------------------------------------------------------------------\n    /**\n     * Tries to locate the specified resource and returns an AbstractFile instance on it.\n     * @param  name name of the resource to locate.\n     * @return      an {@link AbstractFile} instance describing the requested resource if found, <code>null</code> otherwise.\n     */\n    private AbstractFile findResourceAsFile(String name) {\n        for (AbstractFile file : files) {\n            try {\n                // If the requested resource could be found, returns it.\n                final AbstractFile child = file.getChild(name);\n                if (child.exists()) {\n                    return child;\n                }\n            } catch(IOException ignore) {\n                if (LOGGER != null) {\n                    LOGGER.info(\"\");\n                }\n            }\n            // Treats error as a simple 'resource not found' case and keeps looking for\n            // one with the correct name that will load.\n        }\n\n        // The requested resource wasn't found.\n        return null;\n    }\n\n    /**\n     * Returns an input stream on the requested resource.\n     * @param  name name of the resource to open.\n     * @return      an input stream on the requested resource, <code>null</code> if not found.\n     */\n    @Override\n    public InputStream getResourceAsStream(String name) {\n        InputStream  in = getParent().getResourceAsStream(name);   // Input stream on the resource.\n\n        // Tries the parent first, to respect the delegation model.\n        if (in != null) {\n            return in;\n        }\n\n        // Tries to locate the resource in the extended classpath if it wasn't found in the parent.\n        AbstractFile file = findResourceAsFile(name); // File representing the resource.\n        if (file != null) {\n            try {\n                return file.getInputStream();\n            } catch(Exception e) {\n                if (LOGGER != null) {\n                    LOGGER.info(\"\", e);\n                }\n            }\n        }\n\n        // Couldn't find the resource.\n        return null;\n    }\n\n    /**\n     * Tries to find the requested resource.\n     * @param  name name of the resource to locate.\n     * @return      the URL of the requested resource if found, <code>null</code> otherwise.\n     */\n    @Override\n    protected URL findResource(String name) {\n        AbstractFile file = findResourceAsFile(name); // Path to the requested resource.\n\n        // Tries to find the resource.\n        if (file == null) {\n            return null;\n        }\n\n        // Tries to retrieve an URL on the resource.\n        try {\n            return file.getJavaNetURL();\n        } catch(Exception e) {\n            return null;\n        }\n    }\n\n    /**\n     * Tries to find all the resources with the specified name.\n     * @param  name of the resources to find.\n     * @return      an enumeration containing the URLs of all the resources that match <code>name</code>.\n     */\n    @Override\n    protected Enumeration<URL> findResources(String name) {\n        Vector<URL> resources = new Vector<>();    // All resources that match 'name'.\n        // Goes through all files in the classpath to find the resource.\n        for (AbstractFile file : files) {\n            try {\n                if (file.getChild(name).exists()) {\n                    resources.add(file.getJavaNetURL());\n                }\n            } catch(IOException e) {\n                if (LOGGER != null) {\n                    LOGGER.info(\"\", e);\n                }\n            }\n        }\n        return resources.elements();\n    }\n\n    /**\n     * Returns the absolute path of the requested library.\n     * @param name name of the library to load.\n     * @return the absolute path of the requested library if found, <code>null</code> otherwise.\n     */\n    @Override\n    protected String findLibrary(String name) {\n        AbstractFile file = findResourceAsFile(name); // Path of the requested library.\n        return file == null ? null : file.getAbsolutePath();\n    }\n\n\n\n    // - Class loading ---------------------------------------------------------\n    // -------------------------------------------------------------------------\n    /**\n     * Loads and returns the class defined by the specified name and path.\n     * @param  name        name of the class to load.\n     * @param  file        file containing the class' bytecode.\n     * @return the class defined by the specified name and path.\n     * @throws IOException if an error occurs.\n     */\n    private Class<?> loadClass(String name, AbstractFile file) throws IOException {\n        byte[] buffer = new byte[(int)file.getSize()];      // Buffer for the class' bytecode.\n        int offset = 0;                                     // Current offset in buffer.\n\n        try (InputStream in = file.getInputStream()) {\n            // Loads the content of file in buffer.\n            while (offset != buffer.length) {\n                offset += in.read(buffer, offset, buffer.length - offset);\n            }\n            // Loads the class.\n            return defineClass(name, buffer, 0, buffer.length);\n        }\n    }\n\n    /**\n     * Tries to find and load the specified class.\n     * @param name                    fully qualified name of the class to load.\n     * @return                        the requested <code>Class</code> if found, <code>null</code> otherwise.\n     * @throws ClassNotFoundException if the requested class was not found.\n     */\n    @Override\n    protected Class<?> findClass(String name) throws ClassNotFoundException {\n        AbstractFile file = findResourceAsFile(name.replace('.', '/') + \".class\"); // File containing the class' bytecode.\n\n        // Tries to locate the specified class and, if found, load it.\n        if (file != null) {\n            try {\n                return loadClass(name, file);\n            } catch(IOException e) {\n                if (LOGGER != null) {\n                    LOGGER.info(\"\", e);\n                }\n            }\n        }\n        throw new ClassNotFoundException(name);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/AbstractROArchiveFile.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.commons.file;\n\n/**\n * <code>AbstractROArchiveFile</code> represents a read-only archive file. This class is abstract and implemented\n * by read-only archive files.\n *\n * <p>\n * <code>AbstractROArchiveFile</code> implementations only have to provide two methods:\n * <ul>\n *  <li>{@link #getEntryIterator()} to list the entries contained by the archive in a flat, non-hierarchical way\n *  <li>{@link AbstractArchiveFile#getEntryInputStream(ArchiveEntry, ArchiveEntryIterator)} to retrieve a particular entry's content.\n * </ul>\n * The {@link #isWritable()} method is implemented to always returns <code>false</code>.\n *\n * @author Maxence Bernard\n */\npublic abstract class AbstractROArchiveFile extends AbstractArchiveFile {\n\n    /**\n     * Creates an AbstractROArchiveFile on top of the given file.\n     *\n     * @param file the file on top of which to create the archive\n     */\n    protected AbstractROArchiveFile(AbstractFile file) {\n        super(file);\n    }\n\n\n    /**\n     * Returns <code>false</code>: <code>AbstractROArchiveFile</code> implementations are not capable of adding or\n     * deleting entries.\n     *\n     * @return false\n     */\n    @Override\n    public final boolean isWritable() {\n        return false;\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/AbstractRWArchiveFile.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.commons.file;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * <code>AbstractRWArchiveFile</code> represents a read-write archive file. This class is abstract and implemented by\n * all read-write archive files.\n * In addition to the read-only operations defined by {@link com.mucommander.commons.file.AbstractArchiveFile}, it provides\n * abstract methods for adding and deleting entries from the archive.\n *\n * The {@link #isWritable()} method implemented by this class always returns <code>true</code>. However,\n * write operations may not always be available depending on the underlying file (e.g. if random file access is\n * required). In that case, {@link #isWritable ()} should be overridden to return <code>true</code> only when\n * write operations are available.\n *\n * @author Maxence Bernard\n */\npublic abstract class AbstractRWArchiveFile extends AbstractArchiveFile {\n\n    /**\n     * Creates an AbstractRWArchiveFile on top of the given file.\n     *\n     * @param file the file on top of which to create the archive\n     */\n    protected AbstractRWArchiveFile(AbstractFile file) {\n        super(file);\n    }\n\n\n\n    /**\n     * Returns <code>true</code>: <code>AbstractRWArchiveFile</code> implementations are by definition capable of adding\n     * or deleting entries. This method should be overridden if the implementation is capable of providing write access\n     * only under certain conditions, for example if it requires random access to the proxied archive file which may not\n     * always be available depending on the underlying file. If that is the case, this method should return\n     * <code>true</code> only when all conditions for providing write operations are met.\n     *\n     * @return <code>true</code>, always\n     */\n    @Override\n    public boolean isWritable() {\n        return true;\n    }\n\n\n    /**\n     * Adds the given entry to the archive and returns an <code>OutputStream</code> to write the entry's contents\n     * if the entry is a regular file, <code>null</code> if the entry is a directory.\n     * Throws an <code>IOException</code> if the entry already exists in the archive or if an I/O error occurs.\n     *\n     * @param entry the entry to add to the archive\n     * @return an OutputStream to write the entry's contents if the entry is a regular file, null if the entry is a directory\n     * @throws IOException if the entry already exists in the archive or if an I/O error occurs\n     * @throws UnsupportedFileOperationException if {@link FileOperation#WRITE_FILE} operations are not supported by\n     * the underlying file protocol.\n     */\n    public abstract OutputStream addEntry(ArchiveEntry entry) throws IOException, UnsupportedFileOperationException;\n\n    /**\n     * Deletes the specified entry from the archive. Throws an <code>IOException</code> if the entry doesn't exist\n     * in the archive or if an I/O error occurs.\n     *\n     * @param entry the entry to delete from the archive\n     * @throws IOException if the entry doesn't exist in the archive or if an I/O error occurs\n     * @throws UnsupportedFileOperationException if {@link FileOperation#WRITE_FILE} operations are not supported by\n     * the underlying file protocol.\n     */\n    public abstract void deleteEntry(ArchiveEntry entry) throws IOException, UnsupportedFileOperationException;\n\n    /**\n     * Updates the specified entry in the archive with the attributes contained in the {@link ArchiveEntry} object.\n     * Throws an <code>IOException</code> if the entry doesn't exist in the archive or if an I/O error occurs.\n     *\n     * <p>This methods can be used to update the entry's date and permissions for instance.\n     *\n     * @param entry the entry to update in the archive\n     * @throws IOException if the entry doesn't exist in the archive or if an I/O error occurs\n     * @throws UnsupportedFileOperationException if {@link FileOperation#WRITE_FILE} operations are not supported by\n     * the underlying file protocol.\n     */\n    public abstract void updateEntry(ArchiveEntry entry) throws IOException, UnsupportedFileOperationException;\n\n    /**\n     * Processes the archive file to leave it in an optimal form. This method should be called after a writable archive\n     * has been modified (entries added or removed).\n     *\n     * <p>The actual effect of this method on the archive file depends on the kind of archive. It may be implemented\n     * as a no-op if there is no use for it.\n     * To illustrate, in the case of a {@link com.mucommander.commons.file.impl.zip.ZipArchiveFile}, this method removes chunks\n     * of free space that are left when entries are deleted.\n     *\n     * @throws IOException if an I/O error occurs\n     * @throws UnsupportedFileOperationException if {@link FileOperation#WRITE_FILE} operations are not supported by \n     * the underlying file protocol.\n     */\n    public abstract void optimizeArchive() throws IOException, UnsupportedFileOperationException;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/ArchiveEntry.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\npackage com.mucommander.commons.file;\n\nimport com.mucommander.commons.file.util.PathUtils;\n\n/**\n * This class represents a generic archive entry. It provides getters and setters for common archive entry attributes\n * and allows to encapsulate the entry object of a 3rd party library.\n *\n * <p><b>Important</b>: the path of archive entries must use the '/' character as a path delimiter, and be relative\n * to the archive's root, i.e. must not start with a leading '/'.\n *\n * @author Maxence Bernard \n */\npublic class ArchiveEntry extends SimpleFileAttributes {\n\n    public static final char SEPARATOR_CHAR = '/';\n    private static final String SEPARATOR_STRING = String.valueOf(SEPARATOR_CHAR);\n\n    /** Encapsulated entry object */\n    private Object entryObject;\n\n    /** Caches the computed hashcode */\n    private int hashCode;\n\n\n    /**\n     * Creates a new ArchiveEntry with all attributes set to their default value.\n     */\n    public ArchiveEntry() {\n    }\n\n    /**\n     * Creates a new ArchiveEntry using the values of the supplied attributes.\n     *\n     * @param path the entry's path with {@link #SEPARATOR_CHAR} as path delimiter\n     * @param directory true if the entry is a directory\n     * @param date the entry's date\n     * @param size the entry's size\n     * @param exists <code>true</code> if the entry exists in the archive\n     */\n    public ArchiveEntry(String path, boolean directory, long date, long size, boolean exists) {\n        setPath(path);\n        setDate(date);\n        setSize(size);\n        setDirectory(directory);\n        setExists(exists);\n    }\n\n\n    /**\n     * Returns the depth of this entry based on the number of path delimiters ({@link #SEPARATOR_CHAR}) its path\n     * contains. Top-level entries have a depth of 1.\n     *\n     * @return the depth of this entry\n     */\n    public int getDepth() {\n        return getDepth(getPath());\n    }\n\n    /**\n     * Returns the depth of the specified entry path, based on the number of path delimiters ({@link #SEPARATOR_CHAR})\n     * it contains. Top-level entries have a depth of 1.\n     *\n     * @param entryPath the path for which to calculate the depth\n     * @return the depth of the given entry path\n     */\n    static int getDepth(String entryPath) {\n        return PathUtils.getDepth(entryPath, SEPARATOR_STRING);\n    }\n\n    /**\n     * Extracts this entry's filename from its path and returns it.\n     *\n     * @return this entry's filename\n     */\n    public String getName() {\n        String path = getPath();\n        int len = path.length();\n        // Remove trailing '/' if any\n        if (path.charAt(len-1) == SEPARATOR_CHAR) {\n            path = path.substring(0, --len);\n        }\n\n        int lastSlash = path.lastIndexOf(SEPARATOR_CHAR);\n        return lastSlash == -1 ? path : path.substring(lastSlash+1, len);\n    }\n\n    /**\n     * Returns an archive format-dependent object providing extra information about this entry, typically an object from\n     * a 3rd party library ; <code>null</code> if this entry has none.\n     *\n     * @return an object providing extra information about this entry, null if this entry has none\n     */\n    public Object getEntryObject() {\n        return entryObject;\n    }\n\n    /**\n     * Sets an archive format-dependent object providing extra information about this entry, typically an object from\n     * a 3rd party library ; <code>null</code> for none.\n     *\n     * @param entryObject an object providing extra information about this entry, null for none\n     */\n    public void setEntryObject(Object entryObject) {\n        this.entryObject = entryObject;\n    }\n\n\n    /**\n     * Returns the file permissions of this entry. This method is overridden to return default permissions\n     * ({@link FilePermissions#DEFAULT_DIRECTORY_PERMISSIONS} for directories, {@link FilePermissions#DEFAULT_FILE_PERMISSIONS}\n     * for regular files), when none have been set.\n     *\n     * @return the file permissions of this entry\n     */\n    @Override\n    public FilePermissions getPermissions() {\n        FilePermissions permissions = super.getPermissions();\n        if (permissions == null) {\n            return isDirectory() ? FilePermissions.DEFAULT_DIRECTORY_PERMISSIONS : FilePermissions.DEFAULT_FILE_PERMISSIONS;\n        }\n\n        return permissions;\n    }\n\n    /**\n     * Overridden to invalidates any previously computed hash code.\n     *\n     * @param path new path to set\n     */\n    @Override\n    public void setPath(String path) {\n        super.setPath(path);\n        // Invalidate any previously\n        hashCode = 0;\n    }\n\n    /**\n     * Returns <code>true</code> if the given object is an <code>ArchiveEntry</code> whose path is equal to this one,\n     * according to {@link PathUtils#pathEquals(String, String, String)} (trailing slash-insensitive comparison).\n     *\n     * @param o the object to test\n     * @return <code>true</code> if the given object is an <code>ArchiveEntry</code> whose path is equal to this one\n     * @see PathUtils#pathEquals(String, String, String)\n     */\n    public boolean equals(Object o) {\n        return o instanceof ArchiveEntry && PathUtils.pathEquals(getPath(), ((ArchiveEntry) o).getPath(), SEPARATOR_STRING);\n\n    }\n\n    /**\n     * This method is overridden to return a hash code that is consistent with {@link #equals(Object)},\n     * so that <code>url1.equals(url2)</code> implies <code>url1.hashCode()==url2.hashCode()</code>.\n     */\n    public int hashCode() {\n        if (hashCode != 0) {         // Return any previously computed hashCode. Note that setPath invalidates the hashCode.\n            return hashCode;\n        }\n\n        String path = getPath();\n\n        // #equals(Object) is trailing separator insensitive, so the hashCode must be trailing separator invariant\n        hashCode = path.endsWith(SEPARATOR_STRING) ? path.substring(0, path.length()-1).hashCode() : path.hashCode();\n\n        return hashCode;\n    }\n\n    @Override\n    public String toString() {\n        return \" ArchiveEntry(\" + entryObject + \")\";\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/ArchiveEntryIterator.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.file;\n\nimport java.io.IOException;\n\n/**\n * This class allows to iterate the entries of an archive. It mimics the behavior of an <code>Iterator</code>, with\n * several differences:\n *\n * <ul>\n *   <li>its methods are allowed to throw <code>IOException</code></li>\n *   <li>there is no <code>hasNext</code> method, because it wouldn't map very well onto certain formats that don't know\n * if there is a next entry until the current entry has been consumed.</li>\n *   <li>{@link #close()} needs to be called when the Iterator is not needed anymore, allowing implementations to release\n * any resources that they hold.</li>\n * </ul>\n *\n * @see com.mucommander.commons.file.SingleArchiveEntryIterator\n * @see com.mucommander.commons.file.WrapperArchiveEntryIterator\n * @author Maxence Bernard\n */\npublic interface ArchiveEntryIterator {\n\n    /**\n     * Returns the next entry in this iterator, <code>null</code> if this iterator has no more entries.\n     *\n     * @return <code>true</code> if this iterator has a next entry\n     * @throws IOException if an error occurred while reading the archive, either because the archive is corrupt or\n     * because of an I/O error\n     */\n    ArchiveEntry nextEntry() throws IOException;\n\n    /**\n     * Closes this iterator and releases all the resources it holds. This method must be called when this iterator\n     * is not needed anymore.\n     *\n     * @throws IOException if an error occurred while closing the resources\n     */\n    void close() throws IOException;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/ArchiveEntryTree.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.commons.file;\n\nimport com.mucommander.commons.file.util.PathUtils;\nimport org.jetbrains.annotations.Nullable;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javax.swing.tree.DefaultMutableTreeNode;\n\n/**\n * Stores archive entries and organizes them in a tree structure that maps entries in the way they are organized\n * inside the archive. An instance of <code>ArchiveEntryTree</code> also acts as the root node: all entry nodes\n * are children of it (direct or indirect).\n *\n * @author Maxence Bernard\n */\nclass ArchiveEntryTree extends DefaultMutableTreeNode {\n    private static Logger logger;\n\n    /**\n     * Creates a new empty tree.\n     */\n    ArchiveEntryTree() {\n    }\n\n    /**\n     * Adds the given entry to the archive tree, creating parent nodes as necessary.\n     *\n     * @param entry the entry to add to the tree\n     */\n    void addArchiveEntry(ArchiveEntry entry) {\n        String entryPath = entry.getPath();\n        int entryDepth = entry.getDepth();\n        int slashPos = 0;\n        DefaultMutableTreeNode node = this;\n        for (int d = 1; d <= entryDepth; d++) {\n            if (d == entryDepth && !entry.isDirectory()) {\n                // create a leaf node for the entry\n                entry.setExists(true);      // the entry has to exist\n                node.add(new DefaultMutableTreeNode(entry, true));\n                break;\n            }\n\n            String subPath = d==entryDepth?entryPath:entryPath.substring(0, (slashPos=entryPath.indexOf('/', slashPos)+1));\n\n            int nbChildren = node.getChildCount();\n            DefaultMutableTreeNode childNode = null;\n            boolean matchFound = false;\n            for (int c = 0; c < nbChildren; c++) {\n                childNode = (DefaultMutableTreeNode)node.getChildAt(c);\n                // Path comparison is 'trailing slash insensitive'\n                if (PathUtils.pathEquals(((ArchiveEntry)childNode.getUserObject()).getPath(), subPath, \"/\")) {\n                    // Found a match\n                    matchFound = true;\n                    break;\n                }\n            }\n\n            if (matchFound) {\n                if (d == entryDepth) {\n                    getLogger().trace(\"Replacing entry for node {}\", childNode);\n                    childNode.setUserObject(entry); // Replace existing entry\n                } else {\n                    node = childNode;\n                }\n            } else {\n                if (d == entryDepth) {\n                    // create a leaf node for the entry\n                    entry.setExists(true);      // the entry has to exist\n                    node.add(new DefaultMutableTreeNode(entry, true));\n                } else {\n                    getLogger().trace(\"Creating node for {}\", subPath);\n                    childNode = new DefaultMutableTreeNode(new ArchiveEntry(subPath, true, entry.getLastModifiedDate(), 0, true), true);\n                    node.add(childNode);\n                    node = childNode;\n                }\n            }\n        }\n    }\n\n\n    /**\n     * Finds and returns the node that corresponds to the specified entry path, <code>null</code> if no entry matching\n     * the path could be found.\n     *\n     * <p>Important note: the given path's separator character must be '/' and the path must be relative to the\n     * archive's root, i.e. not start with a leading '/', otherwise the entry will not be found. Trailing separators\n     * are ignored when paths are compared, for example the path 'temp' will match the entry 'temp/'.\n     *\n     * @param entryPath the path to the entry to look up in this tree\n     * @return the node that corresponds to the specified entry path\n     */\n    DefaultMutableTreeNode findEntryNode(String entryPath) {\n        int entryDepth = ArchiveEntry.getDepth(entryPath);\n        int slashPos = 0;\n        DefaultMutableTreeNode currentNode = this;\n        for (int d = 1; d <= entryDepth; d++) {\n            String subPath = d == entryDepth ?\n                    entryPath :\n                    entryPath.substring(0, (slashPos = entryPath.indexOf('/', slashPos)+1));\n            DefaultMutableTreeNode matchNode = getDefaultMutableTreeNode(currentNode, subPath);\n            if (matchNode == null) {\n                return null;    // No node matching the provided path, return null\n            }\n\n            currentNode = matchNode;\n        }\n\n        return currentNode;\n    }\n\n    @Nullable\n    private DefaultMutableTreeNode getDefaultMutableTreeNode(DefaultMutableTreeNode currentNode, String subPath) {\n        int nbChildren = currentNode.getChildCount();\n        for (int c = 0; c < nbChildren; c++) {\n            DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)currentNode.getChildAt(c);\n\n            // Path comparison is 'trailing slash insensitive'\n            if (PathUtils.pathEquals(((ArchiveEntry)childNode.getUserObject()).getPath(), subPath, \"/\")) {\n                // Found the node, let's return it\n                return childNode;\n            }\n        }\n        return null;\n    }\n\n    private static Logger getLogger() {\n        if (logger == null) {\n            logger = LoggerFactory.getLogger(ArchiveEntryTree.class);\n        }\n        return logger;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/ArchiveFormatProvider.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.commons.file;\n\nimport com.mucommander.commons.file.filter.FilenameFilter;\n\nimport java.io.IOException;\n\n/**\n * This interface allows {@link FileFactory} to instantiate {@link AbstractArchiveFile} implementations and associate\n * them with the filenames matched by a {@link FilenameFilter}.\n * <p>\n * For {@link AbstractArchiveFile} implementations to be automatically instantiated by {@link FileFactory},\n * this interface needs to be implemented and an instance registered with {@link FileFactory}.\n *\n * @author Nicolas Rinaudo, Maxence Bernard\n * @see AbstractArchiveFile\n * @see FileFactory\n */\npublic interface ArchiveFormatProvider {\n\n    /**\n     * Creates a new instance of <code>AbstractArchiveFile</code> .\n     *\n     * @param  file        file to map as an <code>AbstractArchiveFile</code>.\n     * @return             a new instance of <code>AbstractArchiveFile</code> that matches the specified URL.\n     * @throws IOException if an error occurs.\n     */\n    AbstractArchiveFile getFile(AbstractFile file) throws IOException;\n\n\n    /**\n     * Returns the <code>FilenameFilter</code> that matches filenames to be associated with this archive format.\n     *\n     * @return the <code>FilenameFilter</code> that matches filenames to be associated with this archive format\n     */\n    FilenameFilter getFilenameFilter();\n\n    String[] getFileExtensions();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/AuthException.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\npackage com.mucommander.commons.file;\n\nimport java.io.IOException;\n\n\n/**\n * AuthException is an <code>IOException</code> that is thrown whenever an operation failed due to the lack of,\n * invalid or insufficient credentials. A URL associated with the exception gives the location where the error\n * occurred, and the set of credentials that were used (if any).\n *\n * @author Maxence Bernard\n */\npublic class AuthException extends IOException {\n\n    protected FileURL fileURL;\n    protected String msg;\n\n    /**\n     * Creates a new AuthException instance, without any associated exception.\n     *\n     * @param fileURL the location where the error occurred, with the set of credentials that were used (if any).\n     */\n    public AuthException(FileURL fileURL) {\n        this(fileURL, null);\n    }\n\t\n    /**\n     * Creates a new AuthException instance that was caused by the given exception.\n     *\n     * @param fileURL the location where the error occurred, with the set of credentials that were used (if any)\n     * @param msg a message describing the error, <code>null</code> if there is none\n     */\n    public AuthException(FileURL fileURL, String msg) {\n        super(msg);\n\n        this.fileURL = fileURL;\n        if (msg != null) {\n            this.msg = msg.trim();\n        }\n    }\n\t\n\n    /**\n     * Returns the location where the error occurred, with the set of credentials that were used (if any).\n     *\n     * @return the location where the error occurred, with the set of credentials that were used (if any)\n     */\n    public FileURL getURL() {\n        return fileURL;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/AuthenticationType.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.commons.file;\n\n/**\n * Defines the different types of authentication a file protocol may use.\n *\n * @see FileURL#getAuthenticationType()\n * @author Maxence Bernard\n */\npublic enum AuthenticationType {\n\n    /** Indicates that the file protocol does not use any kind of authentication. */\n    NO_AUTHENTICATION,\n\n    /** Indicates that the file protocol can use authentication but does not require it. */\n    AUTHENTICATION_OPTIONAL,\n\n    /** Indicates that the file protocol requires authentication. */\n    AUTHENTICATION_REQUIRED\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/Authenticator.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.commons.file;\n\n/**\n * This interface is used by{@link FileFactory} to authenticate {@link FileURL} instances prior to resolving\n * corresponding {@link AbstractFile} instances. This interface provides the necessary hooks for interacting with an\n * application or system keystore.\n * <p>\n * A typical implementation of {@link #authenticate(FileURL)} will look for {@link Credentials} matching the\n * specified URL and, if one set (or more) is found, call {@link FileURL#setCredentials(Credentials)} to set them.\n * Likewise, this method may also look for and set {@link FileURL#setProperty(String, String) URL properties},\n * that will be used by the corresponding {@link AbstractFile} during or after resolution.\n * <p>\n * {@link #authenticate(FileURL)} should normally be called only for {@link FileURL} schemes that\n * {@link FileURL#getAuthenticationType() support authentication}. Implementations should however not rely on that and\n * handle non-authenticated URLs as a no-op.\n * <p>\n * A default authenticator can be registered at {@link FileFactory#setDefaultAuthenticator(Authenticator)}.\n *\n * @see FileURL#getAuthenticationType()\n * @see FileFactory#setDefaultAuthenticator(Authenticator)\n * @see FileFactory#getFile(FileURL, AbstractFile, Authenticator, Object...)\n * @author Maxence Bernard\n */\npublic interface Authenticator {\n\n    /**\n     * Authenticates the specified {@link FileURL} instance.\n     *\n     * @param fileURL the file URL to authenticate\n     */\n    void authenticate(FileURL fileURL);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/Credentials.java",
    "content": "package com.mucommander.commons.file;\n\n/**\n * This class is a container for a login and password pair, used to authenticate a location on a filesystem.\n *\n * @see com.mucommander.commons.file.FileURL\n * @author Maxence Bernard\n */\npublic final class Credentials {\n\n    private final String login;\n    private final String password;\n\t\n    /**\n     * Creates a new instance with the supplied login and password.\n     * Any provided null values will be replaced by empty strings.\n     *\n     * @param login    the login part as a string\n     * @param password the password part as a string\n     */\n    public Credentials(String login, String password) {\n        this.login = login == null ? \"\" : login;\n        this.password = password == null ? \"\" : password;\n    }\n\n    /**\n     * Returns the login part. The returned login may be an empty string but never <code>null</code>.\n     *\n     * @return the login part.\n     */\n    public String getLogin() {\n        return login;\n    }\n\t\n    /**\n     * Returns the password part. The returned password may be an empty string but never <code>null</code>.\n     *\n     * @return the password part.\n     */\n    public String getPassword() {\n        return password;\n    }\n\n    /**\n     * Returns the password as a masked string, each of the characters replaced by '*' characters.\n     *\n     * @return the password as a masked string.\n     */\n    public String getMaskedPassword() {\n        int passwordLength = password.length();\n        return \"*\".repeat(passwordLength);\n    }\n\n    /**\n     * Returns <code>true</code> if these credentials are empty.\n     * <p>\n     * Credentials are said to be empty if both login and password are empty strings.\n     *\n     * @return <code>true</code> if these credentials are empty, <code>false</code> otherwise.\n     */\n    public boolean isEmpty() {\n        return \"\".equals(login) && \"\".equals(password);\n    }\n\n\n    /**\n     * This method is equivalent to calling {@link #equals(Object, boolean)} with <code>false</code>:\n     * two Credentials instances with the same login but a different password are considered equal.\n     *\n     * @param o the Object to test for equality\n     * @return true if this and the specified instance are equal\n     * @see #equals(Object, boolean)\n     */\n    @Override\n    public boolean equals(Object o) {\n        return equals(o, false);\n    }\n\n    /**\n     * Returns <code>true</code> if these Credentials and the specified instance are equal. For credentials to be equal,\n     * their login (as returned by {@link #getLogin()} must be equal. If the password-sensitive parameter is enabled,\n     * their passwords (as returned by {@link #getPassword()} must also match.\n     *\n     * <p>\n     * Empty Credentials and <code>null</code> are considered equal: if a <code>null</code> instance is specified,\n     * <code>true</code> is returned if these Credentials are {@link #isEmpty() empty}).\n     *\n     * @param o the Object to test for equality\n     * @param passwordSensitive true if passwords need to be equal for credentials instances to match\n     * @return true if this and the specified instance are equal\n     */\n    public boolean equals(Object o, boolean passwordSensitive) {\n        // Empty Credentials and null are equivalent\n        if (o == null) {\n            return isEmpty();\n        }\n        if (!(o instanceof Credentials)) { // Note: this class is declared final so we don't need to worry about subclasses\n            return false;\n        }\n\n        Credentials credentials = (Credentials)o;\n        return credentials.login.equals(this.login) && (!passwordSensitive || credentials.password.equals(this.password));\n    }\n\n    /**\n     * Returns a cloned instance of these Credentials.\n     *\n     * @return a cloned instance of these Credentials\n     */\n    @Override\n    public Object clone() {\n        try {\n            return super.clone();\n        } catch (CloneNotSupportedException e) {\n            // Should never happen\n            return null;\n        }\n    }\n\n    public String toString() {\n        return login;\n    }\n\n    public int hashCode() {\n        // Do not take into account the password, as #equals(Object) is password-insensitive\n        return login.hashCode();\n    }\n}\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/DefaultPathCanonizer.java",
    "content": "package com.mucommander.commons.file;\n\nimport java.util.Vector;\n\n/**\n * @author Maxence Bernard\n */\npublic class DefaultPathCanonizer implements PathCanonizer {\n\n    /** Path separator */\n    private final String separator;\n\n    /** The string replacement for '~' path fragments, null for no tilde replacement */\n    private final String tildeReplacement;\n\n\n    /**\n     * Creates a new path canonizer using the specified path separator and no tilde replacement.\n     *\n     * @param separator the path separator that delimits path fragments\n     */\n    public DefaultPathCanonizer(String separator) {\n        this(separator, null);\n    }\n\n    /**\n     * Creates a new path canonizer using the specified path separator and tilde replacement string\n     * (if not <code>null</code>).\n     *\n     * @param separator the path separator that delimits path fragments\n     * @param tildeReplacement if not <code>null</code>, path fragments equal to '~' will be replaced by this string.\n     */\n    public DefaultPathCanonizer(String separator, String tildeReplacement) {\n        this.separator = separator;\n        this.tildeReplacement = tildeReplacement;\n    }\n\n\n    /**\n     * Returns a canonical value of the given path, where '.' and '..' path fragments are factored out,\n     * and '~' fragments replaced by the string specified in the constructor (if not <code>null</code>).\n     *\n     * @param path the path to canonize\n     * @return the canonized path\n     */\n    @Override\n    public String canonize(String path) {\n        // Todo: use PathTokenizer?\n\n        if (!path.equals(\"/\")) {\n            int pos;\t    // position of current path separator\n            int pos2 = 0;\t// position of next path separator\n            int separatorLen = separator.length();\n            String dir;\t\t// Current directory\n            String dirWS;\t// Current directory without trailing separator\n            Vector<String> pathV = new Vector<>();\t// Will contain directory hierachy\n            while ((pos = pos2) != -1) {\n                // Get the index of the next path separator occurrence\n                pos2 = path.indexOf(separator, pos);\n\n                if (pos2 == -1) {\t// Last dir (or empty string)\n                    dir = path.substring(pos);\n                    dirWS = dir;\n                } else {\n                    pos2 += separatorLen;\n                    dir = path.substring(pos, pos2);\t\t// Dir name includes trailing separator\n                    dirWS = dir.substring(0, dir.length()-separatorLen);\n                }\n\n                // Discard '.' and empty directories\n                if ((dirWS.isEmpty() && !pathV.isEmpty()) || dirWS.equals(\".\")) {\n                    continue;\n                }\n                // Remove last directory\n                else if(dirWS.equals(\"..\")) {\n                    if (!pathV.isEmpty()) {\n                        pathV.removeElementAt(pathV.size()-1);\n                    }\n                    continue;\n                }\n                // Replace '~' by the provided replacement string, only if one was specified\n                else if(tildeReplacement!=null && dirWS.equals(\"~\")) {\n                    path = path.substring(0, pos) + tildeReplacement + path.substring(pos+1);\n                    // Will perform another pass at the same position\n                    pos2 = pos;\n                    continue;\n                }\n\n                // Add directory to the end of the list\n                pathV.add(dir);\n            }\n\n            // Reconstruct path from directory list\n            StringBuilder result = new StringBuilder();\n            int nbDirs = pathV.size();\n            for (int i = 0; i < nbDirs; i++) {\n                result.append(pathV.elementAt(i));\n            }\n            return result.toString();\n            // We now have a path free of \".\" and \"..\" (and \"~\" if tilde replacement is enabled)  \n        }\n\n        return path;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/DefaultSchemeHandler.java",
    "content": "package com.mucommander.commons.file;\n\n/**\n * This class provides a default {@link SchemeHandler} implementation. The no-arg constructor creates an\n * instance with default values that suit most schemes. This is how the default URL handler returned by\n * {@link FileURL#getDefaultHandler()} is created.<br>\n * The multi-arg constructor allows to create a scheme handler with specific values.\n * <p>\n * The {@link #getRealm(FileURL)} implementation returns a URL with the same scheme and host (if any) as the specified\n * URL, and a path set to <code>\"/\"</code>. This behavior can be modified by overriding <code>getRealm</code>.\n *\n * @see com.mucommander.commons.file.FileURL#getDefaultHandler()\n * @see com.mucommander.commons.file.SchemeHandler\n * @author Maxence Bernard\n */\npublic class DefaultSchemeHandler implements SchemeHandler {\n\n    protected SchemeParser parser;\n    protected int standardPort;\n    protected String pathSeparator;\n    protected AuthenticationType authenticationType;\n    protected Credentials guestCredentials;\n\n    /**\n     * Creates a DefaultSchemeHandler with default values that suit schemes in which the scheme name is not included\n\t * in the URL (local and unc locations):\n     * <ul>\n     *  <li>the parser is a DefaultSchemeParser instance created with the no-arg constructor</li>\n     *  <li>the scheme's standard port is <code>-1</code></li>\n     *  <li>the scheme's path separator is operating system's path separator</li>\n     *  <li>authentication type is {@link AuthenticationType#NO_AUTHENTICATION}</li>\n     *  <li>guest credentials are <code>null</code></li>\n     * </ul>\n     */\n    public DefaultSchemeHandler() {\n        this(new DefaultSchemeParser(), -1, System.getProperty(\"file.separator\"), AuthenticationType.NO_AUTHENTICATION, null);\n    }\n\n    /**\n     * Creates a DefaultSchemeHandler with the specified values.\n     *\n     * @param parser the parser that takes care of parsing URL strings and turning them into FileURL\n     * @param standardPort the scheme's standard port, <code>-1</code> for none\n     * @param pathSeparator the scheme's path separator, cannot be <code>null</code>\n     * @param authenticationType the type of authentication used by the scheme's file protocol\n     * @param guestCredentials the scheme's guest credentials, <code>null</code> for none\n     */\n    public DefaultSchemeHandler(SchemeParser parser, int standardPort, String pathSeparator, AuthenticationType authenticationType, Credentials guestCredentials) {\n        this.parser = parser;\n        this.standardPort = standardPort;\n        this.pathSeparator = pathSeparator;\n        this.authenticationType = authenticationType;\n        this.guestCredentials = guestCredentials;\n    }\n\n\n    //////////////////////////////////\n    // SchemeHandler implementation //\n    //////////////////////////////////\n\n    /**\n     * Returns the parser that was passed to the constructor.\n     *\n     * @return the parser that was passed to the constructor\n     */\n    public SchemeParser getParser() {\n        return parser;\n    }\n\n    /**\n     * Returns the authentication type that was passed to the constructor.\n     *\n     * @return the authentication type that was passed to the constructor\n     */\n    public AuthenticationType getAuthenticationType() {\n        return authenticationType;\n    }\n\n    /**\n     * Returns the set of guest credentials that was passed to the constructor.\n     *\n     * @return the set of guest credentials that was passed to the constructor\n     */\n    public Credentials getGuestCredentials() {\n        return guestCredentials;\n    }\n\n    /**\n     * Returns the path separator that was passed to the constructor.\n     *\n     * @return the path separator that was passed to the constructor\n     */\n    public String getPathSeparator() {\n        return pathSeparator;\n    }\n\n    /**\n     * Returns the standard port that was passed to the constructor.\n     *\n     * @return the standard port that was passed to the constructor\n     */\n    public int getStandardPort() {\n        return standardPort;\n    }\n\n    /**\n     * Returns a URL with the same scheme, host and port (if any) as the specified URL, and a path set to \n\t * <code>\"/\"</code> or <code>\"\\\"</code> depending on the URL format.\n     * The login, password, query and fragment parts of the returned URL are always <code>null</code>.\n     * For example, when called with {@code http://www.mucommander.com:8080/path/to/file?query&param=value},\n     * this method returns <code>http://www.mucommander.com:8080/</code>.\n     * \n     * @param location the location for which to return the authentication realm\n     * @return the authentication realm of the specified location\n     */\n    public FileURL getRealm(FileURL location) {\n        // Start by cloning the given URL and then modify the parts that need it\n        FileURL realm = (FileURL)location.clone();\n\n        realm.setPath(location.getPathSeparator());\n        realm.setCredentials(null);\n        realm.setQuery(null);\n        // Todo\n             // realm.setFragment(null)\n\n        return realm;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/DefaultSchemeParser.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.commons.file;\n\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport com.mucommander.commons.runtime.OsFamily;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.net.MalformedURLException;\nimport java.net.URLDecoder;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.FileSystems;\n\n/**\n * This class provides a default {@link SchemeParser} implementation. Certain scheme-specific features of the parser\n * can be turned on or off in the constructor, allowing this parser to be used with most schemes.\n *\n * <p>This parser can not only parse URLs but also local absolute paths and UNC paths. Upon parsing, these paths are\n * turned into equivalent, fully qualified URLs.\n *\n * <h3>Local paths</h3>\n * <p>\n * Local absolute paths are turned into corresponding 'file' URLs. Local paths are system-dependent, their form and\n * path separator vary from one OS to the other. Only native paths are supported, i.e. Windows-style paths are supported\n * only when running on Windows (or OS/2), Unix-style paths only on an OS that uses them natively...\n * Here are a couple example of how local paths are parsed and turned into FileURL instances:   \n * <ul>\n *  <li>Under Windows or OS/2, <code>C:\\Windows\\System32\\</code> will be parsed and turned into a FileURL whose path\n * separator is \"\\\" and representation <code>file://localhost/C:\\Windows\\System32\\</code></li>\n *  <li>Under a Unix-style OS (Linux, Mac OS X, Solaris...), <code>C:\\Windows\\System32\\</code> will be parsed and turned\n * into a FileURL whose path separator is \"\\\" and representation <code>file://localhost/C:\\Windows\\System32\\</code></li>\n * </ul>\n *\n * <h3>UNC paths</h3>\n * <p>\n * Windows-style UNC paths such as <code>\\\\Server\\Volume\\File</code> are supported on all OSes but the FileURL\n * resulting from the parsing varies will not be the same whether they are created on a Windows environment or\n * on another:\n * <ul>\n *  <li>On Windows (any version), <code>\\\\Server\\Volume\\File</code> will be turned into a FileURL whose string\n * representation is <code>file://Server/\\Volume\\File</code></li>\n *  <li>On any other kind of OS, <code>\\\\Server\\Volume\\File</code> will be turned into a FileURL whose string\n * representation is <code>smb://Server/Volume/File</code></li>\n * </ul>\n *\n * @see PathCanonizer\n * @author Maxence Bernard\n */\n@Slf4j\npublic class DefaultSchemeParser implements SchemeParser {\n    /** True if query should be parsed and not considered as part of the path */\n    private final boolean parseQuery;\n\n    /** <code>PathCanonizer</code> instance to be used for canonizing the path part */\n    private final PathCanonizer pathCanonizer;\n\n\n    /**\n     * Creates a DefaultSchemeParser with a {@link DefaultPathCanonizer} that uses the operating system's default \n     * path separator as the path separator and no tilde replacement, and query parsing disabled.\n     */\n    DefaultSchemeParser() {\n        this(false);\n    }\n\n    /**\n     * Creates a DefaultSchemeParser with a {@link DefaultPathCanonizer} that uses the operating system's\n\t * default path separator as the path separator and no tilde replacement.\n     * If <code>parseQuery</code> is <code>true</code>, any query part (delimited by '?') will be parsed as such,\n     * or considered as part of the path otherwise.\n     *\n     * @param parseQuery <code>true</code>, any query part (delimited by '?') will be parsed as such, or considered\n     * as part of the path otherwise\n     */\n    DefaultSchemeParser(boolean parseQuery) {\n        this(new DefaultPathCanonizer(FileSystems.getDefault().getSeparator(), null), parseQuery);\n    }\n\n    /**\n     * Creates a DefaultSchemeParser using the specified {@link PathCanonizer} for canonizing the path part.\n     * If <code>parseQuery</code> is <code>true</code>, any query part (delimited by '?') will be parsed as such,\n     * or considered as part of the path otherwise.\n     *\n     * @param pathCanonizer <code>PathCanonizer</code> instance to be used for canonizing the path part\n     * @param parseQuery <code>true</code>, any query part (delimited by '?') will be parsed as such, or considered\n     * as part of the path otherwise\n     */\n    DefaultSchemeParser(PathCanonizer pathCanonizer, boolean parseQuery) {\n        this.parseQuery = parseQuery;\n        this.pathCanonizer = pathCanonizer;\n    }\n\n    /**\n     * Handles the parsing of the given local file URL.\n     *\n     * @param url the URL to parse\n     * @param fileURL the FileURL instance in which to set the different parsed parts\n     */\n    private void handleLocalFilePath(String url, FileURL fileURL) {\n        SchemeHandler handler = FileURL.getRegisteredHandler(FileProtocols.FILE);\n        SchemeParser parser = handler.getParser();\n\n        fileURL.setHandler(handler);\n        fileURL.setScheme(FileProtocols.FILE);\n        fileURL.setHost(FileURL.LOCALHOST);\n        fileURL.setPath((parser instanceof DefaultSchemeParser?((DefaultSchemeParser)parser).getPathCanonizer():pathCanonizer).canonize(url));\n    }\n\n    /**\n     * Returns the {@link PathCanonizer} instance that is used by this {@link DefaultSchemeParser}.\n     *\n     * @return the {@link PathCanonizer} instance that is used by this {@link DefaultSchemeParser}\n     */\n    private PathCanonizer getPathCanonizer() {\n        return pathCanonizer;\n    }\n\n\n    @Override\n    public void parse(String url, FileURL fileURL) throws MalformedURLException {\n        // The general form of a URI is:\n\n        //      foo://example.com:8042/over/there?name=ferret#nose\n        //      \\_/   \\______________/\\_________/ \\_________/ \\__/\n        //       |           |            |            |        |\n        //    scheme     authority       path        query   fragment\n        //       |   _____________________|__\n        //      / \\ /                        \\\n        //      urn:example:animal:ferret:nose\n\n\n        // See http://labs.apache.org/webarch/uri/rfc/rfc3986.html for full specs\n\n        try {\n            int pos;\n            int schemeDelimPos = url.indexOf(\"://\");\n            int urlLen = url.length();\n\n            // If the given url contains no scheme, consider that it is a local path and transform it into a file:// URL\n            if (schemeDelimPos == -1) {\n                // Treat the URL as local file path if it starts with:\n                // - '/' and OS doesn't use root drives (Unix-style path)\n                // - a drive letter and OS uses root drives (Windows-style) [support both C:\\ and C:/ style]\n                // - a ~ character (refers to the user home folder)\n                if ((!LocalFile.USES_ROOT_DRIVES && url.startsWith(\"/\")) || url.startsWith(\"~/\") || url.equals(\"~\")) {\n                    handleLocalFilePath(url, fileURL);\n\n                    // All done, return\n                    return;\n                } else if (LocalFile.USES_ROOT_DRIVES && (url.indexOf(\":\\\\\") == 1 || url.indexOf(\":/\") == 1)) {\n                    // Turn forward slash-separated paths into their backslash-separated counterparts.\n                    if (url.charAt(2) == '/') {\n                        url = url.replace('/', '\\\\');\n                    }\n\n                    handleLocalFilePath(url, fileURL);\n\n                    // All done, return\n                    return;\n                }\n\n                // Handle Windows-style UNC network paths ( \\\\hostname\\path ):\n                // - under Windows, transform it into a URL in the file://hostname/path form,\n                //   LocalProtocolProvider will translate it back into a UNC network path\n                // - under other OS, conveniently transform it into smb://hostname/path to be nice with folks\n                //   who've spent too much time using Windows\n                else if (url.startsWith(\"\\\\\\\\\") && urlLen > 2) {\n                    if (OsFamily.WINDOWS.isCurrent()) {\n                        pos = url.indexOf('\\\\', 2);\n                        url = FileProtocols.FILE+\"://\"+ \n                \t\t\t\t(pos==-1?url.substring(2):url.substring(2, pos)+\"/\"+(pos==urlLen-1?\"\":url.substring(pos+1)));\n\n                        // Update scheme delimiter position\n                        schemeDelimPos = FileProtocols.FILE.length();\n                    }\n                    else {\n                        url = FileProtocols.SMB+\"://\"+url.substring(2).replace('\\\\', '/');\n\n                        // Update scheme delimiter position\n                        schemeDelimPos = FileProtocols.SMB.length();\n                    }\n\n                    // Update URL's length\n                    urlLen = url.length();\n                } else { // This doesn't look like a valid path, throw an MalformedURLException\n                    throw new MalformedURLException(\"Path not absolute or malformed: \"+url);\n                }\n            }\n\n            // Start URL parsing\n\n            String scheme = url.substring(0, schemeDelimPos);\n            fileURL.setScheme(scheme);\n            // Advance string index\n            pos = schemeDelimPos+3;\n\n            int separatorPos = url.indexOf('/', pos);\n\n            // The question mark character (if any) marks the beginning of the query part, only if it should be parsed.\n            int questionMarkPos = parseQuery?url.indexOf('?', pos):-1;\n            int hostEndPos;         // Contains the position of the beginning of the path/query part\n            if (separatorPos != -1) {    // Separator is necessarily before question mark\n                hostEndPos = separatorPos;\n            } else if (questionMarkPos != -1) {\n                hostEndPos = questionMarkPos;\n            } else {\n                hostEndPos = urlLen;\n            }\n\n            // The authority part is the one between scheme:// and the path/query. It includes the user information\n            // (login/password), host and port. \n            String authority = url.substring(pos, hostEndPos);\n            pos = 0;\n\n            // Parse login and password (if specified).\n            // They may contain non-URL safe characters that are decoded here, and re-encoded by FileURL#toString.\n            int atPos = authority.lastIndexOf('@');\n            int colonPos;\n            // Filenames may contain @ chars, so atPos must be lower than next separator's position (if any)\n            if (atPos != -1 && (separatorPos == -1 || atPos < separatorPos)) {\n                colonPos = authority.indexOf(':');\n                String login = URLDecoder.decode(authority.substring(0, colonPos == -1 ? atPos : colonPos), StandardCharsets.UTF_8);\n                String password;\n                if (colonPos != -1) {\n                    password = URLDecoder.decode(authority.substring(colonPos+1, atPos), StandardCharsets.UTF_8);\n                } else {\n                    password = null;\n                }\n                if (!login.isEmpty() || !(password == null || password.isEmpty())) {\n                    fileURL.setCredentials(new Credentials(login, password));\n                }\n\n                // Advance string index\n                pos = atPos+1;\n            }\n\n            // Parse host and port (if specified)\n            colonPos = authority.indexOf(':', pos);\n\n            String host;\n            if (colonPos != -1) {\n                host = authority.substring(pos, colonPos);\n                String portString = authority.substring(colonPos+1);\n                if (!portString.isEmpty()) {        // Tolerate an empty port part (e.g. http://mucommander.com:/)\n                    try {\n                        fileURL.setPort(Integer.parseInt(portString));\n                    } catch(NumberFormatException e) {\n                        throw new MalformedURLException(\"URL contains an invalid port\");\n                    }\n                }\n            } else {\n                host = authority.substring(pos);\n            }\n\n            if (host.isEmpty()) {\n                host = null;\n            }\n\n            fileURL.setHost(host);\n\n            // Parse path part excluding query part\n            pos = hostEndPos;\n            String path = url.substring(pos, questionMarkPos==-1?urlLen:questionMarkPos);\n\n            // Empty path means '/'\n            if (path.isEmpty()) {\n                path = \"/\";\n            }\n\n            // Canonize path: factor out '.' and '..' and replace '~' by the replacement string (if any)\n            fileURL.setPath(pathCanonizer.canonize(path));\n\n//            log.debug(\"Warning: path should not be empty, url={}\", url);\n\n            // Parse query part (if any)\n            if (questionMarkPos != -1) {\n                fileURL.setQuery(url.substring(questionMarkPos+1));     // Do not include the question mark\n            }\n        } catch (MalformedURLException e) {\n            throw e;\n        } catch (Exception e2) {\n            log.info(\"Unexpected exception in FileURL() with {}\", url, e2);\n            throw new MalformedURLException();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/DummyFile.java",
    "content": "package com.mucommander.commons.file;\n\nimport com.mucommander.commons.io.RandomAccessInputStream;\nimport com.mucommander.commons.io.RandomAccessOutputStream;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n/**\n * This class is an implementation of <code>AbstractFile</code> which implements all methods as no-op (that do nothing)\n * that return default values. It makes it easy to quickly create a <code>AbstractFile</code> implementation by simply\n * overriding the methods that are needed, for example as an anonymous class inside a method.\n *\n * <p>This class should NOT be subclassed for proper AbstractFile implementations. It should only be used in certain\n * circumstances that require creating a quick AbstractFile implementation where only a few methods will be used.\n *\n * @author Maxence Bernard\n */\npublic class DummyFile extends AbstractFile {\n\n    public DummyFile(FileURL url) {\n        super(url);\n    }\n\n    /**\n     * Implementation notes: always returns <code>0</code>.\n     */\n    @Override\n    public long getLastModifiedDate() {\n        return 0;\n    }\n\n    /**\n     * Implementation notes: always throws {@link UnsupportedFileOperationException}.\n     *\n     * @throws UnsupportedFileOperationException always.\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void setLastModifiedDate(long lastModified) throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_DATE);\n    }\n\n    /**\n     * Implementation notes: always returns <code>-1</code>.\n     */\n    @Override\n    public long getSize() {\n        return -1;\n    }\n\n    /**\n     * Implementation notes: always returns <code>null</code>.\n     */\n    @Override\n    public AbstractFile getParent() {\n        return null;\n    }\n\n    /**\n     * Implementation notes: no-op, does nothing with the specified parent.\n     */\n    @Override\n    public void setParent(AbstractFile parent) {\n    }\n\n    /**\n     * Implementation notes: always returns <code>false</code>.\n     */\n    @Override\n    public boolean exists() {\n        return false;\n    }\n\n    /**\n     * Implementation notes: always returns {@link FilePermissions#EMPTY_FILE_PERMISSIONS}.\n     */\n    @Override\n    public FilePermissions getPermissions() {\n        return FilePermissions.EMPTY_FILE_PERMISSIONS;\n    }\n\n    /**\n     * Implementation notes: returns {@link PermissionBits#EMPTY_PERMISSION_BITS}, none of the permission bits can be\n     * changed.\n     */\n    @Override\n    public PermissionBits getChangeablePermissions() {\n        return PermissionBits.EMPTY_PERMISSION_BITS;\n    }\n\n    /**\n     * Implementation notes: always returns <code>false</code>.\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void changePermission(int access, int permission, boolean enabled) throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_PERMISSION);\n    }\n\n    /**\n     * Implementation notes: always returns <code>null</code>.\n     */\n    @Override\n    public String getOwner() {\n        return null;\n    }\n\n    /**\n     * Implementation notes: always returns <code>false</code>.\n     */\n    @Override\n    public boolean canGetOwner() {\n        return false;\n    }\n\n    /**\n     * Implementation notes: always returns <code>null</code>.\n     */\n    @Override\n    public String getGroup() {\n        return null;\n    }\n\n    /**\n     * Implementation notes: always returns <code>false</code>.\n     */\n    @Override\n    public boolean canGetGroup() {\n        return false;\n    }\n\n    /**\n     * Implementation notes: always returns <code>false</code>.\n     */\n    @Override\n    public boolean isDirectory() {\n        return false;\n    }\n\n    /**\n     * Implementation notes: always returns <code>false</code>.\n     */\n    @Override\n    public boolean isArchive() {\n        return false;\n    }\n\n    /**\n     * Implementation notes: always returns <code>false</code>.\n     */\n    @Override\n    public boolean isSymlink() {\n        return false;\n    }\n\n    /**\n     * Implementation notes: always returns <code>false</code>.\n     */\n    @Override\n    public boolean isSystem() {\n        return false;\n    }\n\n    /**\n     * Implementation notes: always throws an {@link UnsupportedFileOperationException}.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public AbstractFile[] ls() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.LIST_CHILDREN);\n    }\n\n    /**\n     * Implementation notes: always throws an {@link UnsupportedFileOperationException}.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void mkdir() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.CREATE_DIRECTORY);\n    }\n\n    /**\n     * Implementation notes: always throws an {@link UnsupportedFileOperationException}.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public InputStream getInputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.READ_FILE);\n    }\n\n    /**\n     * Implementation notes: always throws an {@link UnsupportedFileOperationException}.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public OutputStream getOutputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.WRITE_FILE);\n    }\n\n    /**\n     * Implementation notes: always throws an {@link UnsupportedFileOperationException}.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public OutputStream getAppendOutputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.APPEND_FILE);\n    }\n\n    /**\n     * Implementation notes: always throws an {@link UnsupportedFileOperationException}.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public RandomAccessInputStream getRandomAccessInputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.RANDOM_READ_FILE);\n    }\n\n    /**\n     * Implementation notes: always throws an {@link UnsupportedFileOperationException}.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public RandomAccessOutputStream getRandomAccessOutputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.RANDOM_WRITE_FILE);\n    }\n\n    /**\n     * Implementation notes: always throws an {@link UnsupportedFileOperationException}.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void delete() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.DELETE);\n    }\n\n    /**\n     * Implementation notes: always throws an {@link UnsupportedFileOperationException}.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void copyRemotelyTo(AbstractFile destFile) throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.COPY_REMOTELY);\n    }\n\n    /**\n     * Implementation notes: always throws an {@link UnsupportedFileOperationException}.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void renameTo(AbstractFile destFile) throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.RENAME);\n    }\n\n    /**\n     * Implementation notes: always throws {@link UnsupportedFileOperationException}.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public long getFreeSpace() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_FREE_SPACE);\n    }\n\n    /**\n     * Implementation notes: always throws {@link UnsupportedFileOperationException}.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public long getTotalSpace() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_TOTAL_SPACE);\n    }\n\n    /**\n     * Implementation notes: always returns <code>null</code>.\n     */\n    @Override\n    public Object getUnderlyingFileObject() {\n        return null;\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public short getReplication() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_REPLICATION);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public long getBlocksize() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_BLOCKSIZE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public void changeReplication(short replication) throws IOException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_REPLICATION);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/FileAccessDeniedException.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file;\n\nimport java.io.IOException;\n\n/**\n * @author Oleg Trifonov\n * Created on 28/10/16.\n */\npublic class FileAccessDeniedException extends IOException {\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/FileAttributes.java",
    "content": "package com.mucommander.commons.file;\n\n/**\n * This interface defines getters for the following file attributes:\n * <dl>\n *   <dt>path</dt>\n *   <dd>the file's path, <code>null</code> by default. The type of path (relative or absolute) separator character\n * are unspecified and context-dependant.</dd>\n *\n *   <dt>exists</dt>\n *   <dd>specifies whether the file exists physically on the underlying filesystem, <code>false</code> by default</dd>\n *\n *   <dt>date</dt>\n *   <dd>the file's date in milliseconds since the epoch (00:00:00 GMT, January 1, 1970),\n * <code>0 (00:00:00 GMT, January 1, 1970)</code> by default</dd>\n *\n *   <dt>size</dt>\n *   <dd>the file's size in bytes, <code>0</code> by default</dd>\n *\n *   <dt>isDirectory</dt>\n *   <dd>specifies whether the file is a directory or a regular file, <code>false</code> by default</dd>\n *\n *   <dt>permissions</dt>\n *   <dd>represents the file permissions as a {@link com.mucommander.commons.file.FilePermissions} object, <code>null</code> if\n * undefined</dd>\n *\n *   <dt>owner</dt>\n *   <dd>the file's owner, <code>null</code> by default</dd>\n *\n *   <dt>group</dt>\n *   <dd>the file's group, <code>null</code> by default</dd>\n * </dl>\n *\n * <p>See the {@link MutableFileAttributes} for an extended interface that include file attribute setters.\n *\n * @see MutableFileAttributes\n * @see SimpleFileAttributes\n * @author Maxence Bernard\n */\npublic interface FileAttributes {\n\n    /**\n     * Returns the file's path, <code>null</code> by default.\n     *\n     * <p>The format and separator character of the path are filesystem-dependent.\n     *\n     * @return the file's path, <code>null</code> by default\n     */\n    String getPath();\n\n    /**\n     * Returns <code>true</code> if the file exists physically on the underlying filesystem, <code>false</code>\n     * by default.\n     *\n     * @return <code>true</code> if the file exists physically on the underlying filesystem, <code>false</code> by default\n     */\n    boolean exists();\n\n    /**\n     * Returns the file's date in milliseconds since the epoch (00:00:00 GMT, January 1, 1970), <code>0</code> by default\n     *\n     * @return the file's date in milliseconds since the epoch (00:00:00 GMT, January 1, 1970), <code>0</code> by default\n     */\n    long getLastModifiedDate();\n\n    /**\n     * Returns the file's size in bytes.\n     *\n     * @return the file's size in bytes\n     */\n    long getSize();\n\n    /**\n     * Returns <code>true</code> if the file is a directory, <code>false</code> if it is a regular file\n     * (defaults to <code>false</code>).\n     *\n     * @return <code>true</code> if the file is a directory, <code>false</code> if it is a regular file or undefined\n     */\n    boolean isDirectory();\n\n    /**\n     * Returns the file's permissions, <code>null</code> by default.\n     *\n     * @return the file's permissions, <code>null</code> by default\n     */\n    FilePermissions getPermissions();\n\n    /**\n     * Returns the file's owner, <code>null</code> by default.\n     *\n     * @return the file's owner, <code>null</code> by default\n     */\n    String getOwner();\n\n    /**\n     * Returns the file's group, <code>null</code> by default.\n     *\n     * @return the file's group, <code>null</code> by default\n     */\n    String getGroup();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/FileFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.commons.file;\n\nimport java.io.IOException;\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\nimport com.mucommander.commons.file.impl.avrdude.AvrdudeProtocolProvider;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.icon.FileIconProvider;\nimport com.mucommander.commons.file.icon.impl.SwingFileIconProvider;\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport com.mucommander.commons.file.impl.local.LocalProtocolProvider;\nimport com.mucommander.commons.file.util.FilePool;\nimport com.mucommander.commons.file.util.PathTokenizer;\nimport com.mucommander.commons.file.util.PathUtils;\nimport com.mucommander.commons.runtime.OsFamily;\n\n/**\n * FileFactory is an abstract class that provides static methods to get a {@link AbstractFile} instance for\n * a specified path or {@link FileURL} location.\n * <h3>Protocols</h3>\n * <p>\n * In order to allow the <code>com.mucommander.commons.file</code> API to access new file protocols, developers must create\n * an implementation of {@link AbstractFile} that handles that protocol and register it to <code>FileFactory</code>.\n * This registration requires an implementation of {@link ProtocolProvider}, an instance of which will be passed to\n * {@link #registerProtocol(String,ProtocolProvider) registerProtocol}.\n *\n * <p>\n * Built-in file protocols are:\n * <ul>\n *   <li>{@link FileProtocols#FILE Local} files.</li>\n *   <li>{@link FileProtocols#FTP FTP}.</li>\n *   <li>{@link FileProtocols#SFTP SFTP}.</li>\n *   <li>{@link FileProtocols#HTTP HTTP}.</li>\n *   <li>{@link FileProtocols#HTTPS HTTPS}.</li>\n *   <li>{@link FileProtocols#NFS NFS}.</li>\n *   <li>{@link FileProtocols#SMB SMB}.</li>\n * </ul>\n *\n * <h3>Archive formats</h3>\n * <p>\n * In order to allow the <code>com.mucommander.commons.file</code> API to access new archive formats, developers must create\n * an implementation of {@link AbstractArchiveFile} that handles that format and register it to <code>FileFactory</code>.\n * This registration requires an implementation of {@link ArchiveFormatProvider}, an instance of which will be passed to\n * {@link #registerArchiveFormat(ArchiveFormatProvider)}.\n *\n * <p>\n * Built-in file formats are:\n * <ul>\n *   <li><code>ZIP</code>, registered to zip, jar, war, wal, wmz, xpi, ear, odt, ods and odp files.</li>\n *   <li><code>TAR</code>, registered to tar, tar.gz, tgz, tar.bz2 and tbz2 files.</li>\n *   <li><code>GZIP</code>, registered to gz files.</li>\n *   <li><code>BZip2</code>, registered to bz2 files.</li>\n *   <li><code>ISO</code>, registered to iso and nrg files.</li>\n *   <li><code>AR</code>, registered to ar, a and deb files.</li>\n *   <li><code>LST</code>, registered to lst files.</li>\n *   <li><code>RAR</code>, registered to rar files.</li>\n *   <li><code>SEVENZIP</code>, registered to 7z files.</li>\n * </ul>\n *\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic class FileFactory {\n    private static Logger logger;\n\n    /** All registered protocol providers. */\n    private static final Map<String, ProtocolProvider> protocolProviders = new ConcurrentHashMap<>();\n\n    /** Local file provider to avoid hashtable lookups (faster). */\n    private static ProtocolProvider localFileProvider;\n\n    /** List of registered ArchiveFormatMapping instances */\n    private static final List<ArchiveFormatProvider> archiveFormatProvidersV = new CopyOnWriteArrayList<>();\n\n    /** Array of registered FileProtocolMapping instances, for quicker access */\n    private static ArchiveFormatProvider[] archiveFormatProviders;\n\n    /** Contains a FilePool instance for each registered scheme */\n    private static final Map<String, FilePool> FILE_POOL_MAP = new HashMap<>();\n\n    /** System temp directory */\n    private static final AbstractFile TEMP_DIRECTORY;\n\n    /** Default file icon provider, initialized in static block */\n    private static FileIconProvider defaultFileIconProvider;\n\n    /** Default authenticator, used when none is specified */\n    private static Authenticator defaultAuthenticator;\n\n    private static Set<String> archiveExtensions;\n\n\n    public static void registerProtocolNetworks() {\n        ProtocolProvider protocolProvider;\n\n        registerProtocol(FileProtocols.SMB, new com.mucommander.commons.file.impl.smb.SMBProtocolProvider());\n        registerProtocol(FileProtocols.HTTP, protocolProvider = new com.mucommander.commons.file.impl.http.HTTPProtocolProvider()); // !!! очень долго грузится\n        registerProtocol(FileProtocols.HTTPS, protocolProvider);\n        registerProtocol(FileProtocols.FTP, new com.mucommander.commons.file.impl.ftp.FTPProtocolProvider());\n        registerProtocol(FileProtocols.NFS, new com.mucommander.commons.file.impl.nfs.NFSProtocolProvider());\n        registerProtocol(FileProtocols.SFTP, new com.mucommander.commons.file.impl.sftp.SFTPProtocolProvider());\n        registerProtocol(FileProtocols.HDFS, new com.mucommander.commons.file.impl.hadoop.HDFSProtocolProvider());\n        //registerProtocol(FileProtocols.S3, new com.mucommander.commons.file.impl.hadoop.S3ProtocolProvider());\n        registerProtocol(FileProtocols.S3, new com.mucommander.commons.file.impl.s3.S3ProtocolProvider());\n        registerProtocol(FileProtocols.WEBDAV, new com.mucommander.commons.file.impl.webdav.WebDAVProvider());\n        registerProtocol(FileProtocols.VSPHERE, new com.mucommander.commons.file.impl.vsphere.VSphereProtocolProvider());\n    }\n\n    public static void registerProtocolArchives() {\n        // Register built-in archive file formats, order for TarArchiveFile and GzipArchiveFile/Bzip2ArchiveFile is important:\n        // TarArchiveFile must match 'tar.gz'/'tar.bz2' files before GzipArchiveFile/Bzip2ArchiveFile does.\n        registerArchiveFormat(new com.mucommander.commons.file.impl.zip.ZipFormatProvider());\n        registerArchiveFormat(new com.mucommander.commons.file.impl.tar.TarFormatProvider());\n        registerArchiveFormat(new com.mucommander.commons.file.impl.gzip.GzipFormatProvider());\n        registerArchiveFormat(new com.mucommander.commons.file.impl.bzip2.Bzip2FormatProvider());\n        registerArchiveFormat(new com.mucommander.commons.file.impl.iso.IsoFormatProvider());\n        registerArchiveFormat(new com.mucommander.commons.file.impl.ar.ArFormatProvider());\n        registerArchiveFormat(new com.mucommander.commons.file.impl.lst.LstFormatProvider());\n        registerArchiveFormat(new com.mucommander.commons.file.impl.rar.RarFormatProvider());\n        registerArchiveFormat(new com.mucommander.commons.file.impl.sevenzip.SevenZipFormatProvider());\n        registerArchiveFormat(new com.mucommander.commons.file.impl.rpm.RpmFormatProvider());\n\n        registerArchiveFormat(new com.mucommander.commons.file.impl.arj.ArjFormatProvider());\n        registerArchiveFormat(new com.mucommander.commons.file.impl.cab.CabFormatProvider());\n        registerArchiveFormat(new com.mucommander.commons.file.impl.cpio.CpioFormatProvider());\n        registerArchiveFormat(new com.mucommander.commons.file.impl.deb.DebFormatProvider());\n        registerArchiveFormat(new com.mucommander.commons.file.impl.lzh.LzhFormatProvider());\n        registerArchiveFormat(new com.mucommander.commons.file.impl.lzma.LzmaFormatProvider());\n        registerArchiveFormat(new com.mucommander.commons.file.impl.udf.UdfFormatProvider());\n        registerArchiveFormat(new com.mucommander.commons.file.impl.wim.WimFormatProvider());\n        registerArchiveFormat(new com.mucommander.commons.file.impl.xar.XarFormatProvider());\n        registerArchiveFormat(new com.mucommander.commons.file.impl.z.ZFormatProvider());\n\n        /*SevenZipJBindings RPM support lacks RPM Metadata - only payload would be available - there are better java\n         *  libs available for handling RPM */\n//        registerArchiveFormat(new com.mucommander.commons.file.impl.rpm.RpmFormatProvider());\n    }\n\n\n    public static void registerProtocolOthers() {\n        // Hadoop requires Java 1.6\n        registerProtocol(FileProtocols.HDFS, new com.mucommander.commons.file.impl.hadoop.HDFSProtocolProvider());\n//            registerProtocol(FileProtocols.S3, new com.mucommander.commons.file.impl.hadoop.S3ProtocolProvider());\n\n        registerProtocol(FileProtocols.S3, new com.mucommander.commons.file.impl.s3.S3ProtocolProvider());\n        registerProtocol(FileProtocols.VSPHERE, new com.mucommander.commons.file.impl.vsphere.VSphereProtocolProvider());\n\n        // TODO !!! check that adb installed\n        registerProtocol(FileProtocols.ADB, new com.mucommander.commons.file.impl.adb.AdbProtocolProvider());\n        registerProtocol(FileProtocols.AVR, new AvrdudeProtocolProvider());\n    }\n\n    static {\n        registerProtocol(FileProtocols.FILE, new LocalProtocolProvider());\n\n        // Set the default FileIconProvider instance\n        defaultFileIconProvider = new SwingFileIconProvider();\n\n        // create the temp directory folder\n        TEMP_DIRECTORY = getFile(System.getProperty(\"java.io.tmpdir\"));\n    }\n\n\n    /**\n     * Makes sure no instance of <code>FileFactory</code> is created.\n     */\n    private FileFactory() {\n    }\n\n\n    /**\n     * Registers a new file protocol.\n     * <p>\n     * If a {@link ProtocolProvider} was already registered to the specified protocol, it will automatically be\n     * unregistered.\n     *\n     * <p>\n     * The <code>protocol</code> argument is expected to be the protocol identifier, without the trailing <code>://</code>.\n     * For example, the identifier of the HTTP protocol would be <code>http</code>. This parameter's case is irrelevant,\n     * as it will be stored in all lower-case.\n     *\n     * <p>\n     * After this call, the various {@link #getFile(String) getFile} methods will be able to resolve files using the\n     * specified protocol.\n     *\n     * <p>\n     * Built-in file protocols are listed in {@link FileProtocols}.\n     *\n     * @param  protocol identifier of the protocol to register.\n     * @param  provider object used to create instances of files using the specified protocol.\n     * @return          the previously registered protocol provider if any, <code>null</code> otherwise.\n     */\n    public static ProtocolProvider registerProtocol(String protocol, ProtocolProvider provider) {\n        protocol = protocol.toLowerCase();\n\n        // create raw and archive file pools\n        synchronized (FILE_POOL_MAP) {\n            FILE_POOL_MAP.put(protocol, new FilePool());\n        }\n\n        // Special case for local file provider.\n        // Note that the local file provider is also added to the provider hashtable.\n        if (protocol.equals(FileProtocols.FILE)) {\n            localFileProvider = provider;\n        }\n\n        return protocolProviders.put(protocol, provider);\n    }\n\n    /**\n     * Unregisters the provider associated with the specified protocol.\n     *\n     * @param  protocol identifier of the protocol whose provider should be unregistered.\n     * @return          the provider that has been unregistered, or <code>null</code> if none.\n     */\n    public static ProtocolProvider unregisterProtocol(String protocol) {\n        protocol = protocol.toLowerCase();\n\n        // Remove raw and archive file pools\n        synchronized (FILE_POOL_MAP) {\n            FILE_POOL_MAP.remove(protocol);\n        }\n\n        // Special case for local file provider\n        if (protocol.equals(FileProtocols.FILE)) {\n            localFileProvider = null;\n        }\n\n        return protocolProviders.remove(protocol);\n    }\n\n    /**\n     * Returns the protocol provider associated with the specified protocol identifier, or <code>null</code> if there\n     * is none.\n     *\n     * @param  protocol identifier of the protocol whose provider should be retrieved.\n     * @return the protocol provider registered to the specified protocol identifier, or <code>null</code> if none.\n     */\n    public static ProtocolProvider getProtocolProvider(String protocol) {\n        return protocolProviders.get(protocol.toLowerCase());\n    }\n\n    /**\n     * Returns <code>true</code> if the given protocol has a registered {@link ProtocolProvider}.\n     *\n     * @param protocol identifier of the protocol to test\n     * @return <code>true</code> if the given protocol has a registered {@link ProtocolProvider}.\n     */\n    public static boolean isRegisteredProtocol(String protocol) {\n        return getProtocolProvider(protocol)!=null;\n    }\n\n    /**\n     * Returns an iterator on all known protocol names.\n     *\n     * <p>All objects returned by the iterator's <code>nextElement()</code> method will be string instances. These can\n     * then be passed to {@link #getProtocolProvider(String) getProtocolProvider} to retrieve the associated\n     * {@link ProtocolProvider}.\n     *\n     * @return an iterator on all known protocol names.\n     */\n    public static Iterator<String> protocols() {\n        return protocolProviders.keySet().iterator();\n    }\n\n    /**\n     * Registers a new <code>ArchiveFormatProvider</code>.\n     *\n     * @param provider the <code>ArchiveFormatProvider</code> to register.\n     */\n    public static void registerArchiveFormat(ArchiveFormatProvider provider) {\n        archiveFormatProvidersV.add(provider);\n        updateArchiveFormatProviderArray();\n    }\n\n    /**\n     * Removes a previously-registered <code>ArchiveFormatProvider</code>.\n     * <p>\n     * To unregister the provider of a particular archive format without knowing the associated provider instance, use\n     * {@link #getArchiveFormatProvider(String)} with a known archive filename to retrieve the provider instance.\n     * For example, <code>FileFactory.unregisterArchiveFormat(FileFactory.getArchiveFormatProvider(\"file.zip\"))</code>\n     * will unregister the (first, if any) Zip provider.\n     *\n     * @param provider the <code>ArchiveFormatProvider</code> to unregister.\n     * @see #getArchiveFormatProvider(String)\n     */\n    public static void unregisterArchiveFormat(ArchiveFormatProvider provider) {\n        int index = archiveFormatProvidersV.indexOf(provider);\n\n        if (index != -1) {\n            archiveFormatProvidersV.remove(index);\n            updateArchiveFormatProviderArray();\n        }\n    }\n\n    /**\n     * Updates the <code>ArchiveFormatProvider</code> array to reflect the contents of the Vector.\n     */\n    private static void updateArchiveFormatProviderArray() {\n        archiveFormatProviders = new ArchiveFormatProvider[archiveFormatProvidersV.size()];\n        archiveFormatProvidersV.toArray(archiveFormatProviders);\n    }\n\n    /**\n     * Returns the first <code>ArchiveFormatProvider</code> that matches the specified filename, <code>null</code>\n     * if there is none. Note that if a filename matches the {@link java.io.FilenameFilter} of several registered\n     * providers, the first provider matching the filename will be returned.\n     *\n     * @param filename an archive filename that potentially matches one of the registered <code>ArchiveFormatProvider</code>\n     * @return the first <code>ArchiveFormatProvider</code> that matches the specified filename, <code>null</code> if there is none\n     */\n    private static ArchiveFormatProvider getArchiveFormatProvider(String filename) {\n        if (filename == null || archiveFormatProviders == null) {\n            return null;\n        }\n\n        for (ArchiveFormatProvider provider : archiveFormatProviders) {\n            if (provider != null && provider.getFilenameFilter() != null && provider.getFilenameFilter().accept(filename)) {\n                return provider;\n            }\n        }\n        return null;\n    }\n\n    /**\n     * Returns an iterator on all known archive formats.\n     *\n     * @return an iterator on all known archive formats.\n     */\n    public static Iterator<ArchiveFormatProvider> archiveFormats() {\n        return archiveFormatProvidersV.iterator();\n    }\n\n\n    /**\n     * Returns an instance of AbstractFile for the given absolute path.\n     *\n     * <p>This method does not throw any IOException but returns <code>null</code> if the file could not be created.\n     *\n     * @param absPath the absolute path to the file\n     * @return <code>null</code> if the given path is not absolute or incorrect (doesn't correspond to any file) or\n     * if something went wrong during file creation.\n     */\n    public static AbstractFile getFile(String absPath) {\n        try {\n            return getFile(absPath, null);\n        } catch(IOException e) {\n            getLogger().info(\"Caught an exception (file {})\", absPath, e);\n            return null;\n        }\n    }\n\n    /**\n     * Returns an instance of AbstractFile for the given absolute path.\n     *\n     * <p>This method does not throw any IOException but returns <code>null</code> if the file could not be created.\n     *\n     * @param absPath the absolute path to the file\n     * @param throwException if set to <code>true</code>, an IOException will be thrown if something went wrong during file creation\n     * @return <code>null</code> if the given path is not absolute or incorrect (doesn't correspond to any file)\n     * @throws java.io.IOException  and throwException param was set to <code>true</code>.\n     * @throws AuthException if additional authentication information is required to create the file\n     */\n    public static AbstractFile getFile(String absPath, boolean throwException) throws AuthException, IOException {\n        try {\n            return getFile(absPath, null);\n        } catch(IOException e) {\n            getLogger().info(\"Caught an exception\", e);\n\n            if (throwException) {\n                throw e;\n            }\n            return null;\n        }\n    }\n\n    /**\n     * Returns an instance of AbstractFile for the given absolute path and use the given parent for the new file if\n     * not null. AbstractFile subclasses should as much as possible call this method rather than {@link #getFile(String)} \n     * because it is more efficient.\n     *\n     * @param absPath the absolute path to the file\n     * @param parent the returned file's parent\n     * @return an instance of <code>AbstractFile</code> for the specified absolute path.\n     * @throws java.io.IOException if something went wrong during file or file url creation.\n     * @throws AuthException if additional authentication information is required to create the file\n     */\n    public static AbstractFile getFile(String absPath, AbstractFile parent) throws AuthException, IOException {\n        return getFile(FileURL.getFileURL(absPath), parent);\n    }\n\n    /**\n     * Returns an instance of AbstractFile for the given FileURL instance.\n     *\n     * @param fileURL the file URL\n     * @return the created file or null if something went wrong during file creation\n     */\n    public static AbstractFile getFile(FileURL fileURL) {\n        try {\n            return getFile(fileURL, null);\n        } catch(IOException e) {\n            getLogger().info(\"Caught an exception\", e);\n            return null;\n        }\n    }\n\n    /**\n     * Returns an instance of AbstractFile for the given FileURL instance.\n     *\n     * @param fileURL the file URL\n     * @param throwException if set to <code>true</code>, an IOException will be thrown if something went wrong during file creation\n     * @return the created file\n     * @throws java.io.IOException if something went wrong during file creation\n     */\n    public static AbstractFile getFile(FileURL fileURL, boolean throwException) throws IOException {\n        try {\n            return getFile(fileURL, null);\n        } catch(IOException e) {\n            getLogger().info(\"Caught an exception\", e);\n\n            if (throwException) {\n                throw e;\n            }\n            return null;\n        }\n    }\n\n    /**\n     * Shorthand for {@link #getFile(FileURL, AbstractFile, Authenticator, Object...)} called with the\n     * {@link #getDefaultAuthenticator() default authenticator}.\n     *\n     * @param fileURL the file URL representing the file to be created\n     * @param parent the parent AbstractFile to use as the created file's parent, can be <code>null</code>\n     * @return an instance of {@link AbstractFile} for the given {@link FileURL}.\n     * @throws java.io.IOException if something went wrong during file creation.\n     */\n    public static AbstractFile getFile(FileURL fileURL, AbstractFile parent, Object... instantiationParams) throws IOException {\n        return getFile(fileURL, parent, defaultAuthenticator, instantiationParams);\n    }\n\n    /**\n     * Creates and returns an instance of AbstractFile for the given FileURL and uses the specified parent file (if any)\n     * as the created file's parent.\n     *\n     * <p>Specifying the file parent if an instance already exists allows to recycle the AbstractFile instance\n     * instead of creating a new one when the parent file is requested.\n     *\n     * @param fileURL the file URL representing the file to be created\n     * @param authenticator used to authenticate the specified location if its protocol\n     * {@link FileURL#getAuthenticationType() is authenticated} and the location contains no credentials already.\n     * If the value is <code>null</code>, no {@link Authenticator} will be used, not even the default one.\n     * @param parent the parent AbstractFile to use as the created file's parent, can be <code>null</code>\n     * @return an instance of {@link AbstractFile} for the given {@link FileURL}.\n     * @throws java.io.IOException if something went wrong during file creation.\n     */\n    public static AbstractFile getFile(FileURL fileURL, AbstractFile parent, Authenticator authenticator, Object... instantiationParams) throws IOException {\n        String protocol = fileURL.getScheme();\n        if (!isRegisteredProtocol(protocol)) {\n            throw new IOException(\"Unsupported file protocol: \" + protocol);\n        }\n\n        // Lookup the pool for an existing AbstractFile instance, only if there are no instantiationParams.\n        // If there are instantiationParams (the file was created by the AbstractFile implementation directly, that is\n        // by ls()), any existing file in the pool must be replaced with a new, more up-to-date one.\n        FilePool filePool = FILE_POOL_MAP.get(fileURL.getScheme().toLowerCase());\n        if (instantiationParams.length == 0) {\n            // Note: FileURL#equals(Object) and #hashCode() take into account credentials and properties and are\n            // trailing slash insensitive (e.g. '/root' and '/root/' URLS are one and the same)\n            AbstractFile file = filePool.get(fileURL);\n            if (file != null) {\n                return file;\n            }\n        }\n\n        String filePath = fileURL.getPath();\n        // For local paths under Windows (e.g. \"/C:\\temp\"), remove the leading '/' character\n        if (OsFamily.WINDOWS.isCurrent() && FileProtocols.FILE.equals(protocol)) {\n            filePath = PathUtils.removeLeadingSeparator(filePath, \"/\");\n        }\n\n        String pathSeparator = fileURL.getPathSeparator();\n\n        PathTokenizer pt = new PathTokenizer(filePath, pathSeparator, false);\n\n        AbstractFile currentFile = null;\n        boolean lastFileResolved = false;\n\n        // Extract every filename from the path from left to right and for each of them, see if it looks like an archive.\n        // If it does, create the appropriate protocol file and wrap it with an archive file.\n        while (pt.hasMoreFilenames()) {\n            // Test if the filename's extension looks like a supported archive format...\n            // Note that the archive can also be a directory with an archive extension.\n            if (isArchiveFilename(pt.nextFilename())) {\n                // Remove trailing separator of file, some file protocols such as SFTP don't like trailing separators.\n                // On the contrary, directories without a trailing slash are fine.\n                String currentPath = PathUtils.removeTrailingSeparator(pt.getCurrentPath(), pathSeparator);\n\n                // Test if current file is an archive and if it is, create an archive entry file instead of a raw\n                // protocol file\n                if (currentFile == null || !currentFile.isArchive()) {\n                    // create a fresh FileURL with the current path\n                    FileURL clonedURL = (FileURL)fileURL.clone();\n                    clonedURL.setPath(currentPath);\n\n                    // Look for a cached file instance before creating a new one\n                    currentFile = filePool.get(clonedURL);\n                    if (currentFile == null) {\n                        currentFile = wrapArchive(createRawFile(clonedURL, authenticator, instantiationParams));\n                        // Add the intermediate file instance to the cache\n                        filePool.put(clonedURL, currentFile);\n                    }\n\n                    lastFileResolved = true;\n                } else {          // currentFile is an AbstractArchiveFile\n                    // Note: wrapArchive() is already called by AbstractArchiveFile#createArchiveEntryFile()\n                    AbstractFile tempEntryFile = ((AbstractArchiveFile)currentFile).getArchiveEntryFile(PathUtils.removeLeadingSeparator(currentPath.substring(currentFile.getURL().getPath().length()), pathSeparator));\n                    if (tempEntryFile.isArchive()) {\n                        currentFile = tempEntryFile;\n                        lastFileResolved = true;\n                    } else {\n                        lastFileResolved = false;\n                    }\n                    // Note: don't cache the entry file\n                }\n            } else {\n                lastFileResolved = false;\n            }\n        }\n\n        // create last file if it hasn't been already (if the last filename was not an archive), same routine as above\n        // except that it doesn't wrap the file with an archive file\n        if (!lastFileResolved) {\n            // Note: DON'T strip out the trailing separator, as this would cause problems with root resources\n            String currentPath = pt.getCurrentPath();\n\n            if (currentFile == null || !currentFile.isArchive()) {\n                FileURL clonedURL = (FileURL)fileURL.clone();\n                clonedURL.setPath(currentPath);\n\n                // Note: no need to look a cached file instance, we have already looked for it at the very beginning.\n                currentFile = createRawFile(clonedURL, authenticator, instantiationParams);\n                // Add the final file instance to the cache\n                filePool.put(currentFile.getURL(), currentFile);\n            } else {          // currentFile is an AbstractArchiveFile\n                currentFile = ((AbstractArchiveFile)currentFile).getArchiveEntryFile(PathUtils.removeLeadingSeparator(currentPath.substring(currentFile.getURL().getPath().length()), pathSeparator));\n                // Note: don't cache the entry file\n            }\n        }\n\n        // Reuse existing parent file instance if one was specified\n        if (parent != null)\n            currentFile.setParent(parent);\n\n        return currentFile;\n    }\n\n    private static AbstractFile createRawFile(FileURL fileURL, Authenticator authenticator, Object... instantiationParams) throws IOException {\n        String scheme = fileURL.getScheme().toLowerCase();\n\n        // Special case for local files to avoid provider hashtable lookup and other unnecessary checks\n        // (for performance reasons)\n        if (scheme.equals(FileProtocols.FILE)) {\n            if (localFileProvider == null) {\n                throw new IOException(\"Unknown file protocol: \" + scheme);\n            }\n\n            return localFileProvider.getFile(fileURL, instantiationParams);\n\n            // Uncomment this line and comment the previous one to simulate a slow filesystem\n            //file = new DebugFile(file, 0, 50);\n        }\n        // Use the protocol hashtable for any other file protocol\n        else {\n            // If an Authenticator has been specified and the specified FileURL's protocol is authenticated and the\n            // FileURL doesn't contain any credentials, use it to authenticate the FileURL.\n            if (authenticator != null && fileURL.getAuthenticationType() != AuthenticationType.NO_AUTHENTICATION && !fileURL.containsCredentials()) {\n                authenticator.authenticate(fileURL);\n            }\n\n            // Finds the right file protocol provider\n            ProtocolProvider provider = getProtocolProvider(scheme);\n            if (provider == null) {\n                throw new IOException(\"Unknown file protocol: \" + scheme);\n            }\n\n            return provider.getFile(fileURL, instantiationParams);\n        }\n    }\n\n    /**\n     * Returns a variation of the given filename, appending a pseudo-unique ID to the filename's prefix while keeping\n     * the same filename extension.\n     *\n     * @param filename base filename\n     */\n    private static String getFilenameVariation(String filename) {\n        int lastDotPos = filename.lastIndexOf('.');\n        int len = filename.length();\n        String nameSuffix = \"_\" + System.currentTimeMillis() + (new Random().nextInt(10000));\n\n        if (lastDotPos == -1) {\n            filename += nameSuffix;\n        } else {\n            filename = filename.substring(0, lastDotPos) + nameSuffix + filename.substring(lastDotPos, len);\n        }\n\n        return filename;\n    }\n\n    /**\n     * Creates and returns a temporary local file using the desired filename. If a file with this name already exists\n     * in the temp directory, the filename's prefix (name without extension) will be appended an ID. The filename's\n     * extension will however always be preserved.\n     *\n     * <p>The returned file may be a {@link LocalFile} or a {@link AbstractArchiveFile} if the extension corresponds\n     * to a registered archive format.\n     *\n     * @param desiredFilename the desired filename for the temporary file. If a file with this name already exists\n     * in the temp directory, the filename's prefix (name without extension) will be appended an ID, but the filename's\n     * extension will always be preserved.\n     * @param deleteOnExit if <code>true</code>, the temporary file will be deleted upon normal termination of the JVM\n     * @return the temporary file, may be a LocalFile or an AbstractArchiveFile if the filename's extension corresponds\n     * to a registered archive format.\n     * @throws IOException if an error occurred while instantiating the temporary file. This should not happen under\n     * normal circumstances.\n     */\n    public static AbstractFile getTemporaryFile(String desiredFilename, boolean deleteOnExit) throws IOException {\n        if (desiredFilename == null || desiredFilename.isEmpty()) {\n            desiredFilename = \"temp\";\n        }\n        \n        // Attempt to use the desired name\n        AbstractFile tempFile = TEMP_DIRECTORY.getDirectChild(desiredFilename);\n\n        if (tempFile.exists()) {\n            tempFile = TEMP_DIRECTORY.getDirectChild(getFilenameVariation(desiredFilename));\n        }\n\n        if (deleteOnExit) {\n            ((java.io.File)tempFile.getUnderlyingFileObject()).deleteOnExit();\n        }\n\n        return tempFile;\n    }\n\n    /**\n     * Creates a temporary file with a default filename. This method is a shorthand for\n     * {@link #getTemporaryFile(String, boolean)} called with a <code>null</code> name.\n     *\n     * @param deleteOnExit if <code>true</code>, the temporary file will be deleted upon normal termination of the JVM\n     * @return the temporary file, may be a LocalFile or an AbstractArchiveFile if the filename's extension corresponds\n     * to a registered archive format.\n     * @throws IOException if an error occurred while instantiating the temporary file. This should not happen under\n     * normal circumstances.\n     */\n    public static AbstractFile getTemporaryFile(boolean deleteOnExit) throws IOException {\n        return getTemporaryFile(null, deleteOnExit);\n    }\n\n    /**\n     * Returns the temporary folder, i.e. the parent folder of temporary files returned by\n     * {@link #getTemporaryFile(String, boolean)}.\n     *\n     * @return the temporary folder\n     */\n    public static AbstractFile getTemporaryFolder() {\n        return TEMP_DIRECTORY;\n    }\n\n\n    /**\n     * Returns true if the given filename's extension matches one of the registered archive formats.\n     *\n     * @param filename the filename to test\n     * @return <code>true</code> if the specified filename is a known archive file name, <code>false</code> otherwise.\n     */\n    public static boolean isArchiveFilename(String filename) {\n        if (archiveExtensions == null) {\n            if (archiveFormatProviders == null) {\n                return false;\n            }\n            archiveExtensions = new HashSet<>();\n            for (ArchiveFormatProvider provider : archiveFormatProviders) {\n                if (provider != null) {\n                    String[] extensions = provider.getFileExtensions();\n                    for (String ext : extensions) {\n                        String extWithoutDot = ext.startsWith(\".\") ? ext.substring(1) : ext;\n                        archiveExtensions.add(extWithoutDot.toLowerCase());\n                    }\n                }\n            }\n        }\n        String ext = AbstractFile.getExtension(filename);\n        return ext != null && archiveExtensions.contains(ext.toLowerCase());\n        //return getArchiveFormatProvider(filename) != null;\n    }\n\n    /**\n     * Tests if the given file's extension matches that of one of the registered archive formats.\n     * If it does, a corresponding {@link AbstractArchiveFile} instance is created on top of the provided file\n     * and returned. If it doesn't, the provided <code>AbstractFile</code> instance is simply returned.\n     * <p>\n     * Note that return {@link AbstractArchiveFile} instances may not actually be archives according to\n     * {@link AbstractFile#isArchive()}. An {@link AbstractArchiveFile} instance that is not currently an archive \n     * (either non-existent or a directory) will behave as a regular (non-archive) file. This allows file instances to\n     * go from being an archive to not being an archive (and vice-versa), without having to re-resolve the file.\n     */\n    public static AbstractFile wrapArchive(AbstractFile file) throws IOException {\n        String filename = file.getName();\n\n        // Looks for an archive FilenameFilter that matches the given filename.\n        // Comparing the filename against each and every archive extension has a cost, so we only perform the test if\n        // the filename contains a dot '.' character, since most of the time this method is called with a filename that\n        // doesn't match any of the filters.\n        if (filename.indexOf('.') >= 0) {\n            ArchiveFormatProvider provider = getArchiveFormatProvider(filename);\n            if (provider != null) {\n                return provider.getFile(file);\n            }\n        }\n\n        return file;\n    }\n\n    /**\n     * Same as wrapArchive(AbstractFile) but using the given extension rather than the file's extension.\n     */\n    public static AbstractFile wrapArchive(AbstractFile file, String extension) throws IOException {\n        ArchiveFormatProvider provider = getArchiveFormatProvider(file.getBaseName() + extension);\n        return provider != null ? provider.getFile(file) : file;\n    }\n\n\n    /**\n     * Returns the default {@link com.mucommander.commons.file.icon.FileIconProvider} instance. The default provider class\n     * (before {@link #setDefaultFileIconProvider(com.mucommander.commons.file.icon.FileIconProvider)} is called) is\n     * platform-dependent and as such may vary across platforms.\n     *\n     * <p>It is noteworthy that the provider returned by this method is used by {@link com.mucommander.commons.file.AbstractFile#getIcon()}\n     * to create and return the icon.\n     *\n     * @return the default FileIconProvider implementation\n     */\n    public static FileIconProvider getDefaultFileIconProvider() {\n        return defaultFileIconProvider;\n    }\n\n    /**\n     * Sets the default {@link com.mucommander.commons.file.icon.FileIconProvider} implementation.\n     *\n     * <p>It is noteworthy that the provider returned by this method is used by {@link com.mucommander.commons.file.AbstractFile#getIcon()}\n      * to create and return the icon.\n      *\n     * @param fip the new value for the default FileIconProvider\n     */\n    public static void setDefaultFileIconProvider(FileIconProvider fip) {\n        defaultFileIconProvider = fip;\n    }\n\n    /**\n     * Returns the default {@link Authenticator} that is used for authenticating {@link FileURL} instances prior\n     * to resolving the corresponding file.\n     *\n     * @return the default {@link Authenticator} that is used for authenticating {@link FileURL} instances prior\n     * to resolving the corresponding file\n     * @see Authenticator\n     */\n    public static Authenticator getDefaultAuthenticator() {\n        return defaultAuthenticator;\n    }\n\n    /**\n     * Sets the default {@link Authenticator} that is used for authenticating {@link FileURL} instances prior\n     * to resolving the corresponding file.\n     *\n     * @param authenticator the new default {@link Authenticator}\n     * @see Authenticator\n     */\n    public static void setDefaultAuthenticator(Authenticator authenticator) {\n        defaultAuthenticator = authenticator;\n    }\n\n    private static Logger getLogger() {\n        if (logger == null) {\n            logger = LoggerFactory.getLogger(FileFactory.class);\n        }\n        return logger;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/FileOperation.java",
    "content": "package com.mucommander.commons.file;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.lang.reflect.Method;\n\n/**\n * @author Maxence Bernard\n * @see UnsupportedFileOperationException\n * @see AbstractFile\n */\npublic enum FileOperation {\n    /**\n     * Represents a 'read' operation, as specified by {@link AbstractFile#getInputStream()}.\n     *\n     * @see AbstractFile#getInputStream()\n     **/\n    READ_FILE,\n\n    /**\n     * Represents a 'random read' operation, as specified by {@link AbstractFile#getRandomAccessInputStream()}.\n     *\n     * @see AbstractFile#getRandomAccessInputStream()\n     **/\n    RANDOM_READ_FILE,\n\n    /**\n     * Represents a 'write' operation, as specified by {@link AbstractFile#getOutputStream()}.\n     *\n     * @see AbstractFile#getOutputStream()\n     **/\n    WRITE_FILE,\n\n    /**\n     * Represents an 'append' operation, as specified by {@link AbstractFile#getAppendOutputStream()}.\n     *\n     * @see AbstractFile#getAppendOutputStream()\n     **/\n    APPEND_FILE,\n\n    /**\n     * Represents a 'random write' operation, as specified by {@link AbstractFile#getRandomAccessOutputStream()}.\n     *\n     * @see AbstractFile#getRandomAccessOutputStream()\n     **/\n    RANDOM_WRITE_FILE,\n\n    /**\n     * Represents an 'mkdir' operation, as specified by {@link AbstractFile#mkdir()}.\n     *\n     * @see AbstractFile#mkdir()\n     **/\n    CREATE_DIRECTORY,\n\n    /**\n     * Represents an 'ls' operation, as specified by {@link AbstractFile#ls()}.\n     *\n     * @see AbstractFile#ls()\n     **/\n    LIST_CHILDREN,\n\n    /**\n     * Represents a 'delete' operation, as specified by {@link AbstractFile#delete()}.\n     *\n     * @see AbstractFile#delete()\n     **/\n    DELETE,\n\n    /**\n     * Represents a 'remove copy' operation, as specified by {@link AbstractFile#copyRemotelyTo(AbstractFile)}.\n     */\n    COPY_REMOTELY,\n\n    /**\n     * Represents a 'rename' operation, as specified by {@link AbstractFile#renameTo(AbstractFile)}.\n     */\n    RENAME,\n\n    /**\n     * Represents a 'change date' operation, as specified by {@link AbstractFile#setLastModifiedDate(long)}.\n     *\n     * @see AbstractFile#setLastModifiedDate(long)\n     **/\n    CHANGE_DATE,\n\n    /**\n     * Represents a 'change permission' operation, as specified by {@link AbstractFile#changePermission(int, int, boolean)}.\n     */\n    CHANGE_PERMISSION,\n\n    /**\n     * Represents a 'get free space' operation, as specified by {@link AbstractFile#getFreeSpace()}.\n     */\n    GET_FREE_SPACE,\n\n    /**\n     * Represents a 'change replication factor' operation, as specified by {@link AbstractFile#changeReplication(short)}}.\n     */\n    GET_BLOCKSIZE,\n\n    /**\n     * Represents a 'change replication factor' operation, as specified by {@link AbstractFile#changeReplication(short)}}.\n     */\n    GET_REPLICATION,\n\n    /**\n     * Represents a 'change replication factor' operation, as specified by {@link AbstractFile#changeReplication(short)}}.\n     */\n    CHANGE_REPLICATION,\n\n    /**\n     * Represents a 'get total space' operation, as specified by {@link AbstractFile#getTotalSpace()}.\n     */\n    GET_TOTAL_SPACE;\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(FileOperation.class);\n\n    /**\n     * Returns the {@link AbstractFile} method corresponding to this file operation.\n     *\n     * @param c the AbstractFile class for which to return a <code>Method</code> object.\n     * @return the {@link AbstractFile} method corresponding to this file operation.\n     */\n    public Method getCorrespondingMethod(Class<? extends AbstractFile> c) {\n        try {\n            switch(this) {\n                case READ_FILE:\n                    return c.getMethod(\"getInputStream\");\n\n                case RANDOM_READ_FILE:\n                    return c.getMethod(\"getRandomAccessInputStream\");\n\n                case WRITE_FILE:\n                    return c.getMethod(\"getOutputStream\");\n\n                case APPEND_FILE:\n                    return c.getMethod(\"getAppendOutputStream\");\n\n                case RANDOM_WRITE_FILE:\n                    return c.getMethod(\"getRandomAccessOutputStream\");\n\n                case CREATE_DIRECTORY:\n                    return c.getMethod(\"mkdir\");\n\n                case LIST_CHILDREN:\n                    return c.getMethod(\"ls\");\n\n                case CHANGE_DATE:\n                    return c.getMethod(\"setLastModifiedDate\", Long.TYPE);\n\n                case GET_BLOCKSIZE:\n                    return c.getMethod(\"getBlocksize\");\n\n                case GET_REPLICATION:\n                    return c.getMethod(\"getReplication\");\n\n                case CHANGE_REPLICATION:\n                    return c.getMethod(\"changeReplication\", Short.TYPE);\n\n                case CHANGE_PERMISSION:\n                    return c.getMethod(\"changePermission\", Integer.TYPE, Integer.TYPE, Boolean.TYPE);\n\n                case DELETE:\n                    return c.getMethod(\"delete\");\n\n                case RENAME:\n                    return c.getMethod(\"renameTo\", AbstractFile.class);\n\n                case COPY_REMOTELY:\n                    return c.getMethod(\"copyRemotelyTo\", AbstractFile.class);\n\n                case GET_FREE_SPACE:\n                    return c.getMethod(\"getFreeSpace\");\n\n                case GET_TOTAL_SPACE:\n                    return c.getMethod(\"getTotalSpace\");\n\n                default:\n                    // This should never be reached, unless method signatures have changed and this method hasn't been updated.\n                    LOGGER.warn(\"this line should not have been executed\");\n                    return null;\n            }\n        } catch (Exception e) {\n            // Should never happen, unless method signatures have changed and this method hasn't been updated.\n            LOGGER.warn(\"this line should not have been executed\", e);\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/FilePermissions.java",
    "content": "package com.mucommander.commons.file;\n\n/**\n * FilePermissions is an interface that represents the permissions of an {@link com.mucommander.commons.file.AbstractFile}.\n * The actual permission values can be retrieved by the methods inherited from the\n * {@link com.mucommander.commons.file.PermissionBits} interface. The permissions mask returned by {@link #getMask()} allows\n * to determine which permission bits are significant, i.e. should be taken into account. That way, certain\n * {@link AbstractFile} implementations that have limited permissions support can set those supported permission bits\n * while making it clear that other bits should be ignored, and not simply be considered as being disabled.\n * For instance, a file implementation with support for the sole 'user' permissions (read/write/execute) will return a\n * mask whose int value is 448 (700 octal).\n *\n * <p>This interface also defines constants for commonly used file permissions.\n *\n * @see com.mucommander.commons.file.AbstractFile#getPermissions()\n * @author Maxence Bernard\n */\npublic interface FilePermissions extends PermissionBits {\n\n    /** Empty file permissions: read/write/execute permissions cleared for user/group/other (0), none of the permission\n     * bits are supported (mask is 0) */\n    FilePermissions EMPTY_FILE_PERMISSIONS = new SimpleFilePermissions(0, 0);\n\n    /** Default file permissions used by {@link AbstractFile#importPermissions(AbstractFile)} for permission bits that\n     * are not available in the source: rw-r--r-- (644 octal). All the permission bits are marked as supported. */\n    FilePermissions DEFAULT_FILE_PERMISSIONS = new SimpleFilePermissions(420, FULL_PERMISSION_BITS);\n\n    /** Default directory permissions used by {@link AbstractFile#importPermissions(AbstractFile)} for permission bits that\n     * are not available in the source: rwxr-xr-x (755 octal). All the permission bits are marked as supported. */\n    FilePermissions DEFAULT_DIRECTORY_PERMISSIONS = new SimpleFilePermissions(493, FULL_PERMISSION_BITS);\n\n    FilePermissions DEFAULT_EXECUTABLE_PERMISSIONS = new SimpleFilePermissions(493, FULL_PERMISSION_BITS);\n\n\n    /**\n     * Returns the mask that indicates which permission bits are significant and should be taken into account.\n     * Permission bits that are unsupported have no meaning and their value should simply be ignored.\n     *\n     * @return the mask that indicates which permission bits are significant and should be taken into account.\n     */\n    PermissionBits getMask();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/FileProtocols.java",
    "content": "package com.mucommander.commons.file;\n\n/**\n * This interface contains a set of known protocol names, that can be found in {@link FileURL}. \n *\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic interface FileProtocols {\n\n    /** Protocol for local or locally mounted files. */\n    String FILE      = \"file\";\n\n    /** Protocol for files served by an FTP server. */\n    String FTP       = \"ftp\";\n\n    /** Protocol for files served by a web server using HTTP. */\n    String HTTP      = \"http\";\n\n    /** Protocol for files served by an HDFS (Hadoop distributed filesystem) cluster. */\n    String HDFS      = \"hdfs\";\n\n    /** Protocol for files served by a web server using HTTPS. */\n    String HTTPS     = \"https\";\n\n    /** Protocol for files served by an NFS server. */\n    String NFS       = \"nfs\";\n\n    /** Protocol for files served by an Amazon S3 (or protocol-compatible) server. */\n    String S3        = \"s3\";\n\n    /** Protocol for files served by an SFTP server (not to be confused with FTPS or SCP). */\n    String SFTP      = \"sftp\";\n\n    /** Protocol for files served by an SMB/CIFS server. */\n    String SMB       = \"smb\";\n\n    /** Protocol for files served by a web server using WebDAV/HTTP. */\n    String WEBDAV    = \"webdav\";\n\n    /** Protocol for files served by a web server using WebDAV/HTTPS. */\n    String WEBDAVS   = \"webdavs\";\n    \n    /** Protocol for files served by a web server using vSphere. */\n    String VSPHERE   = \"vsphere\";\n\n    /** Protocol for files on android devices. */\n    String ADB       = \"adb\";\n\n    /** Protocol for avrdude programmer. */\n    String AVR       = \"avr\";\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/FileURL.java",
    "content": "package com.mucommander.commons.file;\r\n\r\nimport com.mucommander.commons.file.compat.CompatURLStreamHandler;\r\nimport com.mucommander.commons.file.impl.local.LocalFile;\r\nimport com.mucommander.commons.file.util.PathUtils;\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.commons.util.StringUtils;\r\n\r\nimport java.io.UnsupportedEncodingException;\r\nimport java.net.MalformedURLException;\r\nimport java.net.URL;\r\nimport java.net.URLEncoder;\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.util.HashMap;\r\nimport java.util.HashSet;\r\nimport java.util.Map;\r\nimport java.util.Set;\r\n\r\n/**\r\n * This class represents a Uniform Resource Locator (URL). The general format of a URL is as follows:\r\n * <pre>\r\n * \tscheme://[login[:password]@]host[:port][/path][?query]\r\n * </pre>\r\n *\r\n * <h3>Instantiation</h3>\r\n * <p>\r\n * FileURL cannot be instantiated directly, instances can be created using {@link #getFileURL(String)}.\r\n * Unlike the <code>java.net.URL</code> and <code>java.net.URI</code> classes, FileURL instances are mutable --\r\n * all URL parts can be freely modified. FileURL instances can also be cloned using the standard {@link #clone()} method.\r\n *\r\n * <h3>Handlers and Scheme-specific attributes</h3>\r\n * <p>\r\n * In addition to standard URL features, FileURL gives access to scheme-specific attributes:\r\n * <dl>\r\n *  <dt>{@link #getStandardPort() standard port}</dt><dd>the standard port implied when no port is defined in the URL,\r\n * e.g. 21 for FTP</dd>\r\n *  <dt>{@link #getPathSeparator() path separator}</dt><dd>the character(s) that separates path fragments, e.g. '/' for\r\n * most schemes, '\\' for local paths under certain OSes like Windows.</dd>\r\n *  <dt>{@link #getGuestCredentials() guest credentials}</dt><dd>credentials to authenticate as a guest, e.g. 'GUEST'\r\n * for SMB, 'anonymous' for FTP.</dd>\r\n *  <dt>{@link #getRealm() authentication realm}</dt><dd>the base URL throughout which a set of credentials can be used.\r\n * </dd>\r\n * </dl>\r\n * These attribute values are provided by the {@link SchemeHandler} registered with the scheme, if any.\r\n * <p>\r\n * In addition to providing those attributes, a SchemeHandler provides a {@link SchemeParser}\r\n * instance which takes care of the actual parsing of URLs of a particular scheme when {@link #getFileURL(String)} is\r\n * invoked. This allows for scheme-specific parsing, like for example for the query part which should only be parsed\r\n * and considered as a separate part for certain schemes such as HTTP.\r\n * <p>\r\n * This class registers a number of handlers for the schemes/protocols supported by the muCommander file API.\r\n * Additional handlers can be registered dynamically using {@link #registerHandler(String, SchemeHandler)}. Likewise,\r\n * existing handlers can be unregistered or replaced at runtime using <code>registerHandler</code> and\r\n * <code>unregisterHandler</code>.\r\n * <p>\r\n * A {@link #getDefaultHandler() default handler} is used for schemes that do not have a specific handler registered.\r\n * It provides default values for the above-mentioned attributes and provides a parser that parses those scheme URLs.\r\n * The default handler's parser is also used for parsing locations passed to {@link #getFileURL(String)} that do not\r\n * contain a scheme (i.e. without the leading <code>scheme://</code>). Those locations can be system-dependent,\r\n * local and absolute paths, or UNC paths. These paths are turned by the parser into an equivalent, fully-qualified URL.\r\n *\r\n * <h3>Properties</h3>\r\n * <p>\r\n * This class provides methods to attach properties to a FileURL instance. These properties are not part of the URL\r\n * itself and are absent from its string representation. They allow protocol-specific properties like connection\r\n * settings to be passed along, to {@link AbstractFile} instances in particular.\r\n *\r\n * <h3>Limitations</h3>\r\n * <p>\r\n * This class has the several limitations that are worth noting:\r\n * <ul>\r\n *  <li>URL syntax is not strictly enforced: some invalid URLs (as per RFC) will be parsed without throwing an exception</li>\r\n *  <li>relative URLs are not supported</li>\r\n *  <li>no proper percent encoding/decoding </li>\r\n *  <li>no support for the fragment part</li>\r\n * </ul>\r\n * Some of these limitations will be addressed in upcoming revisions of this class.\r\n *\r\n * @see SchemeHandler\r\n * @see SchemeParser\r\n * @author Maxence Bernard\r\n */\r\npublic class FileURL implements Cloneable {\r\n\r\n    // Todo: add support for the fragment part\r\n    // Todo: add percent encoding/decoding\r\n\r\n    /** Handler instance that provides the scheme-specific features of this FileURL */\r\n    private SchemeHandler handler;\r\n\r\n    /** Scheme part */\r\n    private String scheme;\r\n    /** Port part, -1 if this URL has none */\r\n    private int port = -1;\r\n    /** Host part, null if this URL has none */\r\n    private String host;\r\n    /** Path part */\r\n    private String path;\r\n    /** Filename, extracted from the path, null if the path has none */\r\n    private String filename;\r\n    /** Query part, null if this URL has none */\r\n    private String query;\r\n\r\n    /** Properties, null if none have been set thus far */\r\n    private Map<String, String> properties;\r\n    /** Credentials (login and password parts), null if this URL has none */\r\n    private Credentials credentials;\r\n\r\n    /** Caches the value returned by #hashCode() for as long as this instance is not modified */\r\n    private int hashCode;\r\n\r\n    /** Default handler for schemes that do not have a specific handler */\r\n    private static final SchemeHandler DEFAULT_HANDLER = new DefaultSchemeHandler();\r\n\r\n    /** Maps schemes (String) onto SchemeHandler instances */\r\n    private static final Map<String, SchemeHandler> handlers = new HashMap<>();\r\n\r\n    /** String designating the localhost */\r\n    public final static String LOCALHOST = \"localhost\";\r\n\r\n\r\n    static {\r\n        // Register custom handlers for known schemes\r\n\r\n        registerHandler(FileProtocols.FILE, new DefaultSchemeHandler(new DefaultSchemeParser(new DefaultPathCanonizer(LocalFile.SEPARATOR, System.getProperty(\"user.home\")), false), -1, System.getProperty(\"file.separator\"), AuthenticationType.NO_AUTHENTICATION, null));\r\n        registerHandler(FileProtocols.FTP, new DefaultSchemeHandler(new DefaultSchemeParser(), 21, \"/\", AuthenticationType.AUTHENTICATION_REQUIRED, new Credentials(\"anonymous\", \"anonymous_coward@mucommander.com\")));\r\n        registerHandler(FileProtocols.SFTP, new DefaultSchemeHandler(new DefaultSchemeParser(), 22, \"/\", AuthenticationType.AUTHENTICATION_REQUIRED, null));\r\n        registerHandler(FileProtocols.HDFS, new DefaultSchemeHandler(new DefaultSchemeParser(true), 8020, \"/\", AuthenticationType.AUTHENTICATION_OPTIONAL, null));\r\n        registerHandler(FileProtocols.HTTP, new DefaultSchemeHandler(new DefaultSchemeParser(true), 80, \"/\", AuthenticationType.AUTHENTICATION_OPTIONAL, null));\r\n        registerHandler(FileProtocols.S3, new DefaultSchemeHandler(new DefaultSchemeParser(true), 443, \"/\", AuthenticationType.AUTHENTICATION_REQUIRED, null));\r\n        registerHandler(FileProtocols.WEBDAV, new DefaultSchemeHandler(new DefaultSchemeParser(true), 80, \"/\", AuthenticationType.AUTHENTICATION_REQUIRED, null));\r\n        registerHandler(FileProtocols.HTTPS, new DefaultSchemeHandler(new DefaultSchemeParser(true), 443, \"/\", AuthenticationType.AUTHENTICATION_OPTIONAL, null));\r\n        registerHandler(FileProtocols.WEBDAVS, new DefaultSchemeHandler(new DefaultSchemeParser(true), 443, \"/\", AuthenticationType.AUTHENTICATION_REQUIRED, null));\r\n        registerHandler(FileProtocols.NFS, new DefaultSchemeHandler(new DefaultSchemeParser(), 2049, \"/\", AuthenticationType.NO_AUTHENTICATION, null));\r\n        registerHandler(FileProtocols.VSPHERE, new DefaultSchemeHandler(new DefaultSchemeParser(true), 443, \"/\", AuthenticationType.AUTHENTICATION_REQUIRED, null));\r\n\r\n        registerHandler(FileProtocols.SMB, new DefaultSchemeHandler(new DefaultSchemeParser(), -1, \"/\", AuthenticationType.AUTHENTICATION_REQUIRED, new Credentials(\"GUEST\", \"\")) {\r\n            @Override\r\n            public FileURL getRealm(FileURL location) {\r\n                FileURL realm = new FileURL(this);\r\n\r\n                String newPath = location.getPath();\r\n                // Find first path token (share)\r\n                int pos = newPath.indexOf('/', 1);\r\n                newPath = newPath.substring(0, pos < 0 ? newPath.length() : pos+1);\r\n\r\n                realm.setPath(newPath);\r\n                realm.setScheme(location.getScheme());\r\n                realm.setHost(location.getHost());\r\n                realm.setPort(location.getPort());\r\n\r\n                // Copy properties (if any)\r\n                realm.importProperties(location);\r\n\r\n                return realm;\r\n            }\r\n        });\r\n    }\r\n\r\n\r\n    /**\r\n     * Private constructor. Creates an empty FileURL that uses the given handler, all parts have to be manually set.\r\n     *\r\n     * @param handler the handler to have this FileURL use\r\n     */\r\n    private FileURL(SchemeHandler handler) {\r\n        this.handler = handler;\r\n    }\r\n\r\n    /**\r\n     * This method is called whenever this instance is modified to invalidate caches.\r\n     */\r\n    private void urlModified() {\r\n        hashCode = 0;\r\n    }\r\n\r\n    /**\r\n     * Creates and returns a new FileURL instance from the given location, throws a <code>MalformedURLException</code>\r\n     * if the specified location is not a valid URL or path and cannot be resolved. The {@link SchemeParser parser}\r\n     * of the {@link SchemeHandler handler} registered for the location's scheme is used to parse the given location.\r\n     * If the scheme specified in the location does not have a specific handler, or if the location does not contain a\r\n     * scheme (i.e. is local or UNC path, not a URL) then the default handler's parser is used.\r\n     *\r\n     * @param location the URL or path for which to get a <code>FileURL</code> instance\r\n     * @throws MalformedURLException if the specified string isn't a valid URL, according to the scheme's parser used\r\n     * @return a FileURL corresponding to the given location\r\n     */\r\n    public static FileURL getFileURL(String location) throws MalformedURLException {\r\n        if (location == null) {\r\n            return null;\r\n        }\r\n        int schemeDelimPos = location.indexOf(\"://\");\r\n        SchemeHandler handler;\r\n\r\n        if (schemeDelimPos == -1) {\r\n            // No scheme: the location is a local or UNC path, not a URL\r\n            handler = getDefaultHandler();\r\n        } else {\r\n            handler = getSchemeHandler(location.substring(0, schemeDelimPos));\r\n        }\r\n\r\n        FileURL fileURL = new FileURL(handler);\r\n        try {\r\n            handler.getParser().parse(location, fileURL);\r\n        } catch (Exception e) {\r\n            // Catch any unexpected exception thrown by the SchemeParser and turn it into a MalformedURLException\r\n            // with a specific error message.\r\n            if (e instanceof MalformedURLException) {\r\n                throw (MalformedURLException) e;\r\n            }\r\n\r\n            throw new MalformedURLException(\"URL parser error\");\r\n        }\r\n\r\n        return fileURL;\r\n    }\r\n\r\n    /**\r\n     * Returns the handler registered the specified scheme if there is one, the default handler otherwise.\r\n     *\r\n     * @param scheme the scheme for which to return a handler\r\n     * @return a handler for the specified scheme\r\n     */\r\n    private static SchemeHandler getSchemeHandler(String scheme) {\r\n        SchemeHandler handler = getRegisteredHandler(scheme);\r\n        if (handler == null) {\r\n            return getDefaultHandler();\r\n        }\r\n\r\n        return handler;\r\n    }\r\n\r\n    /**\r\n     * Returns the <code>SchemeHandler</code> instance that provides the scheme-specific features of this FileURL.\r\n     *\r\n     * @return the <code>SchemeHandler</code> instance that provides the scheme-specific features of this FileURL\r\n     */\r\n    public SchemeHandler getHandler() {\r\n        return handler;\r\n    }\r\n\r\n    /**\r\n     * Sets the <code>SchemeHandler</code> that provides the scheme-specific features of this FileURL.\r\n     * <p>\r\n     * <b>Important:</b> after calling this method, the scheme should also be changed to match the new handler --\r\n     * changing the handler without changing the scheme to an appropriate one will result in inconsistent\r\n     * scheme-specific attributes to be returned.\r\n     *\r\n     * @param handler the <code>SchemeHandler</code> instance that provides the scheme-specific features of this FileURL\r\n     */\r\n    public void setHandler(SchemeHandler handler) {\r\n        this.handler = handler;\r\n    }\r\n\r\n    /**\r\n     * Registers a handler for the specified scheme, replacing any handler previously registered\r\n     * for the same scheme.\r\n     *\r\n     * @param scheme the scheme to associate the handler with (case-insensitive)\r\n     * @param handler the new handler in charge of the scheme\r\n     */\r\n    static void registerHandler(String scheme, SchemeHandler handler) {\r\n        handlers.put(scheme.toLowerCase(), handler);\r\n    }\r\n\r\n    /**\r\n     * Removes any handler associated with the specified scheme, leaving the default handler in charge of the scheme.\r\n     * This method has no effect if there is no handler registered for the scheme.\r\n     *\r\n     * @param scheme the scheme to remove the handler for\r\n     */\r\n    static void unregisterHandler(String scheme) {\r\n        handlers.remove(scheme.toLowerCase());\r\n    }\r\n\r\n    /**\r\n     * Returns the handler registered for the specified scheme, <code>null</code> if there isn't any.\r\n     *\r\n     * @param scheme the scheme for which to return the handler\r\n     * @return the handler registered for the specified scheme\r\n     */\r\n    public static SchemeHandler getRegisteredHandler(String scheme) {\r\n        return handlers.get(scheme.toLowerCase());\r\n    }\r\n\r\n    /**\r\n     * Returns the default handler, which handles schemes which do not have a specific handler.\r\n     * The returned instance is a {@link DefaultSchemeHandler} created with the no-arg constructor. \r\n     *\r\n     * @return the default handler\r\n     */\r\n    static SchemeHandler getDefaultHandler() {\r\n        return DEFAULT_HANDLER;\r\n    }\r\n\r\n    /**\r\n     * Extracts the filename from the given path and returns it, or <code>null</code> if the path does not contain\r\n     * a filename.\r\n     *\r\n     * @param path the path from which to extract a filename\r\n     * @param separator the path separator\r\n     * @return the filename extracted from the given path, <code>null</code> if the path doesn't contain any\r\n     */\r\n    public static String getFilenameFromPath(String path, String separator) {\r\n        if (path.isEmpty() || path.equals(\"/\")) {\r\n            return null;\r\n        }\r\n\r\n        // Remove any trailing separator\r\n        path = PathUtils.removeTrailingSeparator(path, separator);\r\n\r\n        if (!separator.equals(\"/\")) {\r\n            path = PathUtils.removeLeadingSeparator(path, \"/\");\r\n        }\r\n\r\n        // Extract filename\r\n        int pos = path.lastIndexOf(separator);\r\n        if (pos < 0) {\r\n            return null;\r\n        }\r\n\r\n        return path.substring(pos+1);\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the scheme part of this URL. The returned scheme may never be <code>null</code>.\r\n     *\r\n     * @return the scheme part of this <code>FileURL</code>.\r\n     * @see #setScheme(String)\r\n     */\r\n    public String getScheme() {\r\n        return scheme;\r\n    }\r\n\r\n    /**\r\n     * Sets the scheme part of this URL. An <code>IllegalArgumentException</code> will be thrown if the specified scheme\r\n     * is <code>null</code> or an empty string.\r\n     * <p>\r\n     * <b>Important:</b> after calling this method, the handler should also be changed to match the new scheme --\r\n     * changing the scheme without changing the handler to an appropriate one will result in inconsistent\r\n     * scheme-specific attributes to be returned.\r\n     *\r\n     * @param scheme new scheme part of this URL.\r\n     * @throws IllegalArgumentException if the specified is null or an empty string\r\n     * @see #getScheme()\r\n     */\r\n    public void setScheme(String scheme) {\r\n        if (scheme == null) {\r\n            throw new IllegalArgumentException();\r\n        }\r\n\r\n        this.scheme = scheme;\r\n\r\n        urlModified();\r\n    }\r\n\r\n    /**\r\n     * Returns the host part of this URL, <code>null</code> if it doesn't contain any.\r\n     *\r\n     * @return the host part of this URL.\r\n     * @see #setHost(String)\r\n     */\r\n    public String getHost() {\r\n        return host;\r\n    }\r\n\r\n    /**\r\n     * Sets the host part of this URL, <code>null</code> for no host.\r\n     *\r\n     * @param host new host part of this URL.\r\n     * @see #getHost()\r\n     */\r\n    public void setHost(String host) {\r\n        this.host = host;\r\n\r\n        urlModified();\r\n    }\r\n\r\n    /**\r\n     * Returns the port part of this URL, <code>-1</code> if none was specified in the URL.\r\n     *\r\n     * @return the port part of this URL, -1 if there isn't any.\r\n     * @see #setPort(int)\r\n     * @see #getDefaultHandler()\r\n     */\r\n    public int getPort() {\r\n        return port;\r\n    }\r\n\t\r\n    /**\r\n     * Sets the port part of this URL, <code>-1</code> for no specific port.\r\n     *\r\n     * @param port new port part of this URL.\r\n     * @see #getPort()\r\n     * @see #getDefaultHandler()\r\n     */\r\n    public void setPort(int port) {\r\n        this.port = port;\r\n\r\n        urlModified();\r\n    }\r\n\r\n    /**\r\n     * Returns this scheme's standard port, <code>-1</code> if the scheme doesn't have any.\r\n     * If this URL doesn't have a specific port part, the return value should be considered as being this URL's port.\r\n     *\r\n     * <p>Some file protocols may not have a notion of standard port or even no use for the port part at all, for\r\n     * example those that are not TCP or UDP based such as the local 'file' scheme.\r\n     *\r\n     * <p>This method is just a shorthand for <code>getHandler().getStandardPort()</code>.\r\n     *\r\n     * @return the scheme's standard port\r\n     * @see #getPort()\r\n     */\r\n    public int getStandardPort() {\r\n        return handler.getStandardPort();\r\n    }\r\n    \r\n\r\n    /**\r\n     * Returns the login part of this URL, <code>null</code> if there isn't any.\r\n     *\r\n     * @return the login part of this URL, <code>null</code> if there isn't any\r\n     * @see #getCredentials()\r\n     */\r\n    public String getLogin() {\r\n        return credentials==null?null:credentials.getLogin();\r\n    }\r\n\r\n    /**\r\n     * Returns the password part of this URL, <code>null</code> if there isn't any.\r\n     *\r\n     * @return the password part of this URL, <code>null</code> if there isn't any\r\n     * @see #getCredentials()\r\n     */\r\n    public String getPassword() {\r\n        return credentials==null?null:credentials.getPassword();\r\n    }\r\n\r\n    /**\r\n     * Returns the type of authentication used by the scheme's file protocol. The returned value is one of the constants\r\n     * defined in the {@link AuthenticationType} enum.\r\n     *\r\n     * <p>This method is just a shorthand for <code>getHandler().getAuthenticationType()</code>.\r\n     *\r\n     * @return the type of authentication used by the scheme's file protocol\r\n     */\r\n    public AuthenticationType getAuthenticationType() {\r\n        return handler.getAuthenticationType();\r\n    }\r\n\r\n    /**\r\n     * Returns true if this URL contains credentials, i.e. a login and/or password part. If <code>true</code> is\r\n     * returned, {@link #getCredentials()} will return a non-null value.\r\n     *\r\n     * @return <code>true</code> if this URL contains credentials, <code>false</code> otherwise.\r\n     */\r\n    public boolean containsCredentials() {\r\n        return credentials!=null;\r\n    }\r\n\r\n    /**\r\n     * Returns the credentials (login and password) contained by this URL, wrapped in an {@link Credentials} object.\r\n     * Returns <code>null</code> if this URL doesn't have a login or password part.\r\n     *\r\n     * <p>The returned credentials may or may be of any use for the scheme's file protocol depending on the value\r\n     * returned by {@link #getAuthenticationType()}.\r\n     *\r\n     * @return the credentials contained by this URL, <code>null</code> if this URL doesn't have a login or password part.\r\n     * @see #setCredentials(Credentials)\r\n     * @see #getAuthenticationType()\r\n     */\r\n    public Credentials getCredentials() {\r\n        return credentials;\r\n    }\r\n\r\n    /**\r\n     * Sets the login and password parts of this URL. Any credentials contained by this FileURL will be replaced.\r\n     *  <code>null</code> can be passed to discard existing credentials.\r\n     *\r\n     * <p>Credentials may or may not be of any use for the scheme's file protocol depending on the value\r\n     * returned by {@link #getAuthenticationType()}.\r\n     *\r\n     * @param credentials the new login and password parts, replacing any existing credentials. If null is passed,\r\n     * existing credentials will be discarded.\r\n     * @see #getCredentials()\r\n     */\r\n    public void setCredentials(Credentials credentials) {\r\n        // Empty credentials are equivalent to null credentials\r\n        this.credentials = credentials == null || credentials.isEmpty() ? null : credentials;\r\n        urlModified();\r\n    }\r\n\r\n    /**\r\n     * Returns this scheme's guest credentials, <code>null</code> if the scheme doesn't have any.\r\n     * <p>\r\n     * Guest credentials offer a way to authenticate a URL as a 'guest' on file protocols that require a set of\r\n     * credentials to establish a connection. The returned credentials are provided with no guarantee that the filesystem\r\n     * will actually accept them and allow the request/connection. The notion of 'guest' credentials may or may not\r\n     * have a meaning depending on the underlying file protocol.\r\n     *\r\n     * <p>This method is just a shorthand for <code>getHandler().getGuestCredentials()</code>.\r\n     *\r\n     * @return the scheme's guest credentials, <code>null</code> if the scheme doesn't have any\r\n     */\r\n    public Credentials getGuestCredentials() {\r\n        return handler.getGuestCredentials();\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the path part of this URL. The returned value will never be <code>null</code> and always start with a\r\n     * leading '/' character.\r\n     *\r\n     * @return the path part of this URL.\r\n     * @see    #setPath(String)\r\n     */\r\n    public String getPath() {\r\n        return path;\r\n    }\r\n\r\n    /**\r\n     * Sets the path part of this URL. The specified path cannot be <code>null</code> and must start with a leading \r\n     * '/' character. If the specified path value is <code>null</code>, then the path will be set to \"/\".\r\n     * If the path does not start with a leading separator, one will be added.\r\n     *\r\n     * @param path new path part of this URL\r\n     * @see #getPath()\r\n     */\r\n    public void setPath(String path) {\r\n        if (path == null || path.isEmpty()) {\r\n            path = \"/\";\r\n        }\r\n\r\n        if (!path.startsWith(\"/\")) {\r\n            path = \"/\" + path;\r\n        }\r\n\r\n        this.path = path;\r\n        // Extract new filename from path\r\n        this.filename = getFilenameFromPath(path, getPathSeparator());\r\n\r\n        urlModified();\r\n    }\r\n\r\n    /**\r\n     * Returns this scheme's path separator, which serves as a delimiter for path fragments. For most schemes, this is\r\n     * the forward slash character.\r\n     *\r\n     * <p>This method is just a shorthand for <code>getHandler().getPathSeparator()</code>.\r\n     *\r\n     * @return this scheme's path separator\r\n     */\r\n    public String getPathSeparator() {\r\n        return handler.getPathSeparator();\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the parent of this URL according to its path, <code>null</code> if this URL has no parent (its path is \"/\").\r\n     * <p>\r\n     * The returned FileURL will have the same handler, scheme, host, port, credentials and properties as this one.\r\n     * The query part of the returned parent URL will always be <code>null</code>, even if this URL had one.\r\n     *\r\n     * <p>Note: this method returns a new FileURL instance every time it is called, and all mutable fields of this FileURL\r\n     * are cloned. Therefore, the returned URL can be safely modified without any risk of side effects.\r\n     *\r\n     * @return this URL's parent, <code>null</code> if it doesn't have one.\r\n     */\r\n    public FileURL getParent() {\r\n        // If path equals '/', url has no parent\r\n        if (!(path.equals(\"/\") || path.isEmpty())) {\r\n            String separator = getPathSeparator();\r\n\r\n            // Remove any trailing separator\r\n            String parentPath = path.endsWith(separator)?path.substring(0, path.length()-separator.length()):path;\r\n\r\n            // Resolve parent folder's path and reconstruct parent URL\r\n            int lastSeparatorPos = parentPath.lastIndexOf(separator);\r\n            if (lastSeparatorPos >= 0) {\r\n                FileURL parentURL = new FileURL(handler);\r\n\r\n                parentURL.scheme = scheme;\r\n                parentURL.host = host;\r\n                parentURL.port = port;\r\n                parentURL.path = parentPath.substring(0, lastSeparatorPos+1);  // Keep trailing slash\r\n                parentURL.filename = getFilenameFromPath(parentURL.path, separator);\r\n\r\n                // Set same credentials for parent, (if any)\r\n                // Note: Credentials are immutable.\r\n                parentURL.credentials = credentials;\r\n\r\n                // Copy properties to parent (if any)\r\n                if (properties != null)\r\n                    parentURL.properties = new HashMap<>(properties);\r\n\r\n                return parentURL;\r\n            }\r\n        }\r\n\r\n        return null;    // URL has no parent\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the authentication realm corresponding to this URL, i.e. the base location throughout which credentials\r\n     * can be used. Any property contained by the specified FileURL will be carried over in the returned FileURL.\r\n     * On the contrary, credentials will not be copied, the returned URL always has no credentials.\r\n     *\r\n     * <p>Note: this method returns a new FileURL instance every time it is called. Therefore the returned FileURL can\r\n     * safely be modified without any risk of side effects.\r\n\r\n     * <p>This method is just a shorthand for <code>getHandler().getRealm(this)</code>.\r\n     *\r\n     * @return this url's authentication realm\r\n     */\r\n    public FileURL getRealm() {\r\n        return handler.getRealm(this);\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the filename of this URL , <code>null</code> if doesn't have one (e.g. if the path is \"/\").\r\n     * <p>\r\n     * There is no <code>setFilename</code> as the filename is simply extrapolated from the path.\r\n     * Use {@link #setPath(String)} to change the path and its filename.\r\n     *\r\n     * @return the filename of this URL, <code>null</code> if it doesn't have one.\r\n     * @see    #setPath(String)\r\n     */\r\n    public String getFilename() {\r\n        return filename;\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the query part of this URL if it has one, <code>null</code> otherwise.\r\n     *\r\n     * @return the query part of this URL if it has one, <code>null</code> otherwise\r\n     * @see    #setQuery(String)\r\n     */\r\n    public String getQuery() {\r\n        return query;\r\n    }\r\n\r\n    /**\r\n     * Sets the query part of this URL, <code>null</code> for no query part.\r\n     *\r\n     * @param query new query part of this URL, <code>null</code> for no query part\r\n     * @see #getQuery()\r\n     */\r\n    public void setQuery(String query) {\r\n        this.query = query;\r\n\r\n        urlModified();\r\n    }\r\n\r\n\t\r\n    /**\r\n     * Returns the value corresponding to the given property name, <code>null</code> if the property has no value.\r\n     *\r\n     * @param name name of the property whose value is to be retrieved\r\n     * @return the value associated with the specified property name, <code>null</code> if it has no value\r\n     * @see #setProperty(String,String)\r\n     */\r\n    public String getProperty(String name) {\r\n        return properties==null?null:properties.get(name);\r\n    }\r\n\t\r\n    /**\r\n     * Sets the given property (name/value pair) in the FileURL instance. A <code>null</code> property value has the\r\n     * effect of removing the property.\r\n     *\r\n     * @param name name of the property to set\r\n     * @param value value of the property\r\n     * @see #getProperty(String)\r\n     */\r\n    public void setProperty(String name, String value) {\r\n        // create the property map only when a property is set for the first time\r\n        if (properties == null) {\r\n            properties = new HashMap<>();\r\n        }\r\n\r\n        if (value == null) {\r\n            properties.remove(name);\r\n        } else {\r\n            properties.put(name, value);\r\n        }\r\n\r\n        urlModified();\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns an <code>Enumeration</code> of all property names this FileURL contains.\r\n     *\r\n     * @return an <code>Enumeration</code> of all property names this FileURL contains\r\n     */\r\n    public Set<String> getPropertyNames() {\r\n        return properties == null ? new HashSet<>() : properties.keySet();\r\n        }\r\n\r\n    /**\r\n     * Copy the properties of the given FileURL into this FileURL.\r\n     *\r\n     * @param url FileURL instance whose properties should be imported into this one.\r\n     */\r\n    public void importProperties(FileURL url) {\r\n        // Slight optimization to avoid creating an enumeration if the FileURL doesn't have any property\r\n        if (url.properties == null) {\r\n            return;\r\n        }\r\n\r\n        Set<String> propertyKeys = url.getPropertyNames();\r\n        for (String key : propertyKeys) {\r\n            setProperty(key, url.getProperty(key));\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Returns a String representation of this FileURL, including the login and password parts (credentials) only if\r\n     * specified, and masking the password as requested. \r\n     *\r\n     * @param includeCredentials if <code>true</code>, the login and password parts (if any) will be included in the\r\n     * returned URL.\r\n     * @param maskPassword if <code>true</code> and the includeCredentials parameter is also true, the password's\r\n     * characters (if any) will be replaced by '*' characters. This allows a URL containing credentials to be displayed\r\n     * to the end user without revealing the actual password.\r\n     * @return a string representation of this <code>FileURL</code>\r\n     */\r\n    public String toString(boolean includeCredentials, boolean maskPassword) {\r\n        StringBuilder sb = new StringBuilder(scheme);\r\n        sb.append(\"://\");\r\n\r\n        if (includeCredentials && credentials != null) {\r\n            sb.append(URLEncoder.encode(credentials.getLogin(), StandardCharsets.UTF_8));\r\n\r\n            String password = credentials.getPassword();\r\n            if (!password.isEmpty()) {\r\n                sb.append(':');\r\n                if (maskPassword) {\r\n                    sb.append(credentials.getMaskedPassword());\r\n                } else {\r\n                    sb.append(URLEncoder.encode(password, StandardCharsets.UTF_8));\r\n                }\r\n            }\r\n            sb.append('@');\r\n        }\r\n\r\n        if (host != null) {\r\n            sb.append(host);\r\n        }\r\n\r\n        // Set the port only if it has a value that is different from the standard port\r\n        if (port != -1 && port != handler.getStandardPort()) {\r\n            sb.append(':');\r\n            sb.append(port);\r\n        }\r\n\r\n        if (host != null || !path.equals(\"/\"))\t{ // Test to avoid URLs like 'smb:///'\r\n            if (path.startsWith(\"/\")) {\r\n                sb.append(path);\r\n            } else {\r\n                // Add a leading '/' if path doesn't already start with one, needed for scheme paths that are not\r\n                // forward slash-separated\r\n                sb.append('/');\r\n                sb.append(path);\r\n            }\r\n        }\r\n\r\n        if (query != null) {\r\n            sb.append('?');\r\n            sb.append(query);\r\n        }\r\n\r\n        return sb.toString();\r\n    }\r\n\r\n    /**\r\n     * Returns a String representation of this FileURL, including the login and password parts (credentials) only if\r\n     * requested.\r\n     *\r\n     * @param includeCredentials if <code>true</code>, the login and password parts (if any) will be included in the\r\n     * returned URL.\r\n     * @return a string representation of this <code>FileURL</code>.\r\n     */\r\n    public String toString(boolean includeCredentials) {\r\n        return toString(includeCredentials, false);\r\n    }\r\n\r\n\r\n    /**\r\n     * Creates and returns a <code>java.net.URL</code> referring to the same location as this <code>FileURL</code>.\r\n     * The <code>java.net.URL</code> is created from the string representation of this <code>FileURL</code>.\r\n     * Thus, any credentials this <code>FileURL</code> contains are preserved, but properties are lost.\r\n     *\r\n     * <p>The returned <code>URL</code> uses an {@link AbstractFile} to access the associated resource.\r\n     * An {@link AbstractFile} instance is created by the underlying <code>URLConnection</code> when the URL is\r\n     * connected.\r\n     *\r\n     * <p>It is important to note that this method is provided for interoperability purposes, for the sole purpose of\r\n     * connecting to APIs that require a <code>java.net.URL</code>.\r\n     *\r\n     * @return a <code>java.net.URL</code> referring to the same location as this <code>FileURL</code>\r\n     * @throws MalformedURLException if the java.net.URL could not parse the location of this FileURL\r\n     */\r\n    public URL getJavaNetURL() throws MalformedURLException {\r\n        return new URL(null, toString(true), new CompatURLStreamHandler());\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the scheme part of this URL and the given URL are equal.\r\n     * The comparison is case-sensitive.\r\n     *\r\n     * @param url the URL to test for scheme equality\r\n     * @return <code>true</code> if the scheme part of this URL and the given URL are equal\r\n     */\r\n    public boolean schemeEquals(FileURL url) {\r\n        return this.scheme.equalsIgnoreCase(url.scheme);\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the host part of this URL and the given URL are equal.\r\n     * The comparison is case-insensitive.\r\n     *\r\n     * @param url the URL to test for host equality\r\n     * @return <code>true</code> if the host part of this URL and the given URL are equal\r\n     */\r\n    public boolean hostEquals(FileURL url) {\r\n        // Note: StringUtils#equals is null-safe \r\n        return StringUtils.equals(this.host, url.host, false);\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the port of this URL and the given URL's are equal. Ports are said to be equal if\r\n     * the values returned by {@link #getPort()} are equal, or if both URLs have the same standard port\r\n     * (as returned by {@link #getStandardPort()} and one of the port value is <code>-1</code> (undefined) and the other\r\n     * is the standard port.\r\n     *\r\n     * @param url the URL to test for port equality\r\n     * @return <code>true</code> if the port of this URL and the given one are equal\r\n     */\r\n    public boolean portEquals(FileURL url) {\r\n        int port1 = this.port;\r\n        int port2 = url.port;\r\n        int standardPort = getStandardPort();\r\n\r\n        return port1==port2 ||\r\n            (standardPort==url.getStandardPort() && ((port1==-1 && port2==standardPort || (port2==-1 && port1==standardPort))));\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the path of this URL and the given URL are equal. The comparison is case-sensitive.\r\n     * If the sole difference between two paths is a trailing path separator (and both URLs have the same path separator),\r\n     * they will be considered as equal.\r\n     * For example, <code>/path</code> and <code>/path/</code> are considered equal, assuming the path separator is '/'.\r\n     *\r\n     * <p>It is noteworthy that this method uses <code>java.lang.String#equals(Object)</code> to compare URL paths,\r\n     * which in some rare cases may return <code>false</code> for non-ascii/Unicode paths that have the same written\r\n     * representation but are not equal according to <code>java.lang.String#equals(Object)</code>. Handling such cases\r\n     * would require a locale-aware String comparison which is not an option here.\r\n     *\r\n     * @param url the URL to test for path equality\r\n     * @return <code>true</code> if the path of this URL and the given URL are equal\r\n     */\r\n    public boolean pathEquals(FileURL url) {\r\n    \tboolean isCaseSensitiveOS = !(OsFamily.WINDOWS.isCurrent() || OsFamily.OS_2.isCurrent());\r\n    \t\r\n        String path1 = isCaseSensitiveOS ? this.getPath() : this.getPath().toLowerCase();\r\n        String path2 = isCaseSensitiveOS ? url.getPath() : url.getPath().toLowerCase();\r\n\r\n        if (path1.equals(path2))\r\n            return true;\r\n\r\n        String separator = getPathSeparator();\r\n\r\n        if (separator.equals(url.getPathSeparator())) {\r\n            int separatorLen = separator.length();\r\n            int len1 = path1.length();\r\n            int len2 = path2.length();\r\n\r\n            // If the difference between the 2 strings is just a trailing path separator, we consider the paths as equal\r\n            if (Math.abs(len1-len2) == separatorLen && (len1 > len2 ? path1.startsWith(path2) : path2.startsWith(path1))) {\r\n                String diff = len1>len2 ? path1.substring(len1-separatorLen) : path2.substring(len2-separatorLen);\r\n                return separator.equals(diff);\r\n            }\r\n        }\r\n\r\n        return false;\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the query part of this URL and the given URL are equal.\r\n     * The comparison is case-sensitive.\r\n     *\r\n     * @param url the URL to test for query equality\r\n     * @return <code>true</code> if the query part of this URL and the given URL are equal\r\n     */\r\n    public boolean queryEquals(FileURL url) {\r\n        return StringUtils.equals(this.query, url.query, true);\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the credentials (login and password) of this URL and the given URL are equal.\r\n     * The comparison is case-sensitive.\r\n     *\r\n     * @param url the URL to test for credentials equality\r\n     * @return <code>true</code> if the credentials of this URL and the given URL are equal\r\n     */\r\n    public boolean credentialsEquals(FileURL url) {\r\n        Credentials creds1 = this.credentials;\r\n        Credentials creds2 = url.credentials;\r\n\r\n        return (creds1 == null && creds2 == null)\r\n            || (creds1 != null && creds1.equals(creds2, true))\r\n            || (creds2 != null && creds2.equals(creds1, true));\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the properties contained by this URL and the given URL are equal.\r\n     * The comparison of each property is case-sensitive.\r\n     *\r\n     * @param url the URL to test for properties equality\r\n     * @return <code>true</code> if the properties contained by this URL and the given URL are equal\r\n     */\r\n    private boolean propertiesEquals(FileURL url) {\r\n        return (this.properties == null && url.properties == null)\r\n           ||  (this.properties != null && this.properties.equals(url.properties))\r\n           ||  (url.properties != null && url.properties.equals(this.properties));\r\n    }\r\n\r\n\r\n    ////////////////////////\r\n    // Overridden methods //\r\n    ////////////////////////\r\n\r\n    /**\r\n     * Returns a String representation of this FileURL, without including the login and password parts it may have.\r\n     */\r\n    public String toString() {\r\n        return toString(false);\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns a clone of this FileURL. The returned instance can safely be modified without any impact on this FileURL\r\n     * or any previously cloned URL.\r\n     */\r\n    @Override\r\n    public Object clone() {\r\n        // create a new FileURL return it, instead of using Object.clone() which is probably way slower;\r\n        // most FileURL fields are immutable and as such reused in cloned instance\r\n        FileURL clonedURL = new FileURL(handler);\r\n\r\n        // Immutable fields\r\n        clonedURL.scheme = scheme;\r\n        clonedURL.host = host;\r\n        clonedURL.port = port;\r\n        clonedURL.path = path;\r\n        clonedURL.filename = filename;\r\n        clonedURL.query = query;\r\n        clonedURL.credentials = credentials;  // Note: Credentials are immutable.\r\n\r\n        // Mutable fields\r\n        if (properties != null) {    // Copy properties (if any)\r\n            clonedURL.properties = new HashMap<>(properties);\r\n        }\r\n\r\n        // Caches\r\n        clonedURL.hashCode = hashCode;\r\n\r\n        return clonedURL;\r\n    }\r\n\r\n    /**\r\n     * This method is equivalent to calling {@link #equals(Object, boolean, boolean)} with credentials and properties\r\n     * comparisons enabled.\r\n     *\r\n     * @param o object to compare against this FileURL instance.\r\n     * @return true if both FileURL instances are equal.\r\n     */\r\n    public boolean equals(Object o) {\r\n        return equals(o, true, true);\r\n    }\r\n\r\n    /**\r\n     * Tests the specified FileURL for equality with this FileURL. <code>false</code> is systematically returned if the\r\n     * specified object is not a FileURL instance or is <code>null</code>.\r\n     * <p>\r\n     * Two <code>FileURL</code> instances are said to be equal if:\r\n     * <ul>\r\n     *  <li>schemes are equal (case-insensitive)</li>\r\n     *  <li>hosts are equal (case-insensitive)</li>\r\n     *  <li>ports are equal. The default port is taken into account when comparing ports: a non specified port part (-1)\r\n     * is equivalent to the scheme's standard port. For instance, <code>http://mucommander.com:80/</code>\r\n     * and <code>http://mucommander.com/</code> are considered equal.</li>\r\n     *  <li>paths are equal (case-sensitive). There can be a trailing separator difference in the two paths, they will\r\n     * still be considered as equal. For example, <code>/path</code> and <code>/path/</code> are considered equal\r\n     * (assuming the path separator is '/').</li>\r\n     *  <li>queries are equal (case-sensitive)</li>\r\n     * </ul>\r\n     * <p>\r\n     * Credentials (login and password parts) are compared only if requested. The comparison for both the login and\r\n     * password is case-sensitive.<br>\r\n     * Likewise, properties are compared only if requested: the comparison of all properties is case-sensitive.\r\n     *\r\n     * @param o object to compare against this FileURL instance\r\n     * @param compareCredentials if <code>true</code>, the login and password parts of both FileURL need to be\r\n     * equal (case-sensitive) for the FileURL instances to be equal\r\n     * @param compareProperties if <code>true</code>, all properties need to be equal (case-sensitive) in both\r\n     * FileURL for them to be equal\r\n     * @return true if both FileURL instances are equal\r\n     */\r\n    public boolean equals(Object o, boolean compareCredentials, boolean compareProperties) {\r\n        if (!(o instanceof FileURL)) {\r\n            return false;\r\n        }\r\n\r\n        FileURL url = (FileURL)o;\r\n\r\n        return pathEquals(url)      // Compare the path first as it is the most likely to be different\r\n            && schemeEquals(url)\r\n            && hostEquals(url)\r\n            && portEquals(url)\r\n            && queryEquals(url)\r\n            && (!compareCredentials || credentialsEquals(url))\r\n            && (!compareProperties || propertiesEquals(url));\r\n    }\r\n\r\n    /**\r\n     * This method is overridden to return a hash code that takes into account the behavior of {@link FileURL#equals(Object)},\r\n     * so that <code>url1.equals(url2)</code> implies <code>url1.hashCode()==url2.hashCode()</code>.\r\n     */\r\n    public int hashCode() {\r\n        if (hashCode == 0) {\r\n            String separator = handler.getPathSeparator();\r\n\r\n            // #equals(Object) is trailing separator insensitive, so the hashCode must be trailing separator invariant\r\n            int h = PathUtils.getPathHashCode(path, separator);\r\n\r\n            h = 31* h + scheme.toLowerCase().hashCode();\r\n            h = 31* h + (port==-1?handler.getStandardPort():port);\r\n\r\n            if (host != null)\r\n                h = 31* h + host.toLowerCase().hashCode();\r\n\r\n            if (query != null)\r\n                h = 31* h + query.hashCode();\r\n\r\n            if (credentials != null)\r\n                h = 31* h + credentials.hashCode();\r\n\r\n            if (properties != null)\r\n                h = 31* h + properties.hashCode();\r\n\r\n            // Cache the value until for as long as this instance is not modified\r\n            hashCode = h;\r\n        }\r\n\r\n        return hashCode;\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/GroupedPermissionBits.java",
    "content": "package com.mucommander.commons.file;\n\n/**\n * GroupedPermissionBits is an implementation of {@link com.mucommander.commons.file.PermissionBits} using a given UNIX-style\n * permission int: {@link #getIntValue()} returns the specified int, and {@link #getBitValue(int, int)} isolates a\n * specified value.\n *\n * @see com.mucommander.commons.file.IndividualPermissionBits\n * @author Maxence Bernard\n */\npublic class GroupedPermissionBits implements PermissionBits {\n\n    /** UNIX-style permission int */\n    protected int permissions;\n\n    /**\n     * Creates a new GroupedPermissionBits using the specified UNIX-style permission int. The int can be created\n     * by combining (binary OR and shift) values defined in {@link com.mucommander.commons.file.PermissionTypes} and\n     * {@link com.mucommander.commons.file.PermissionAccesses}.\n     *\n     * @param permissions a UNIX-style permission int.\n     */\n    public GroupedPermissionBits(int permissions) {\n        this.permissions = permissions;\n    }\n\n    @Override\n    public int getIntValue() {\n        return permissions;\n    }\n\n    @Override\n    public boolean getBitValue(int access, int type) {\n        return (permissions & (type << (access*3))) != 0;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/IndividualPermissionBits.java",
    "content": "package com.mucommander.commons.file;\n\n/**\n * IndividualPermissionBits is a partial implementation of {@link com.mucommander.commons.file.PermissionBits} that relies\n * on {@link #getBitValue(int, int)}: the implementation of {@link #getIntValue()} calls <code>getBitValue()</code>\n * sequentially for each permission bit, 9 times in total.\n *\n * @see com.mucommander.commons.file.GroupedPermissionBits\n * @author Maxence Bernard\n */\npublic abstract class IndividualPermissionBits implements PermissionBits {\n\n    public IndividualPermissionBits() {\n    }\n\n\n    @Override\n    public int getIntValue() {\n        int bitShift = 0;\n        int perms = 0;\n\n        for(int a=PermissionAccesses.OTHER_ACCESS; a<=PermissionAccesses.USER_ACCESS; a++) {\n            for(int p=PermissionTypes.EXECUTE_PERMISSION; p<=PermissionTypes.READ_PERMISSION; p=p<<1) {\n                if(getBitValue(a, p))\n                    perms |= (1<<bitShift);\n\n                bitShift++;\n            }\n        }\n\n        return perms;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/MacOsSystemFolder.java",
    "content": "package com.mucommander.commons.file;\n\n\nimport com.mucommander.commons.file.util.PathUtils;\nimport com.mucommander.commons.runtime.OsFamily;\n\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/**\n * Top-level Mac OS X system folders hidden by Finder. For more info about those files:\n * http://www.westwind.com/reference/OS-X/invisibles.html\n * \n * @author Arik Hadas. Maxence Bernard \n */\npublic enum MacOsSystemFolder {\n    TRASHES(\"/.Trashes\"),\n    VOL(\"/.vol\"),\n    DEV(\"/dev\"),\n    AUTOMOUNT(\"/automount\"),\n    BIN(\"/bin\"),\n    CORES(\"/cores\"),\n    ETC(\"/etc\"),\n    LOST_FOUND(\"/lost+found\"),    \n    NETWORK(\"/Network\"),\n    PRIVATE(\"/private\"),\n    SBIN(\"/sbin\"),\n    TMP(\"/tmp\"),\n    USR(\"/usr\"),\n    VAR(\"/var\"),\n//    \"/Volumes\",\n    MACH_SYM(\"/mach.sym\"),\n    MACH_KERNEL(\"/mach_kernel\"),\n    MACH(\"/mach\"),\n    DESKTOP_DB(\"/Desktop DB\"),\n    DESKTOP_DF(\"/Desktop DF\"),\n    FILE_TRANSFER_FOLDER(\"/File Transfer Folder\"),\n    HOTFILES_BTREE(\"/.hotfiles.btree\"),\n    SPOTLIGHT_V100(\"/.Spotlight-V100\"),\n    HIDDEN(\"/.hidden\"),     // Used by Mac OS X up to 10.3, not in 10.4\n    USER_TRASH(System.getProperty(\"user.home\")+\"/.Trash\"),  // User trash folder\n    // Mac OS 9 system folders \n    APPLESHARE_PDS(\"/AppleShare PDS\"),\n    CLEANUP_AT_STARTUP(\"/Cleanup At Startup\"),\n    DESKTOP_FOLDER(\"/Desktop Folder\"),\n    NETWORK_TRASH_FOLDER(\"/Network Trash Folder\"),\n    SHUTDOWN_CHECK(\"/Shutdown Check\"),\n    TEMPORARY_ITEMS(\"/Temporary Items\"),\n    USER_TEMPORARY_ITEMS(System.getProperty(\"user.home\")+\"/Temporary Items\"),  // User trash folder\n    THEFINDBYCONTENTFOLDER(\"/TheFindByContentFolder\"),\n    THEVOLUMESETTINGSFOLDER(\"/TheVolumeSettingsFolder\"),\n    TRASH(\"/Trash\"),\n    VM_STORAGE(\"/VM Storage\");\n\n    /** file path */\n    final String path;\n\n    static Set<String> paths;\n\n    static {\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            paths = Stream.of(values()).map(f -> f.path).collect(Collectors.toSet());\n        }\n    }\n\n\tMacOsSystemFolder(String path) {\n\t\tthis.path = path;\n\t}\n\n\tpublic static boolean isSystemFile(AbstractFile file) {\n        String path = file.getAbsolutePath();\n        path = PathUtils.removeTrailingSeparator(path);\n        return paths.contains(path);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/MimeTypes.java",
    "content": "package com.mucommander.commons.file;\n\nimport com.mucommander.commons.file.util.ResourceLoader;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.util.Hashtable;\nimport java.util.StringTokenizer;\n\n/**\n * This Hashtable maps file extensions to their mime type.\n *\n * @author Maxence Bernard\n */\npublic class MimeTypes extends Hashtable<String, String> {\n\n\tprivate final static MimeTypes MIME_TYPES = new MimeTypes();\n\n\t/** Name of the 'mime.types' resource file located in the same package as this class */\n\tprivate static final String MIME_TYPES_RESOURCE_NAME = \"mime.types\";\n\n\tprivate MimeTypes() {\n\t\ttry (BufferedReader br = new BufferedReader(new InputStreamReader(ResourceLoader.getPackageResourceAsStream(MimeTypes.class.getPackage(), MIME_TYPES_RESOURCE_NAME)))) {\n\t\t\tString line;\n\t\t\twhile ((line = br.readLine()) != null) {\n\t\t\t\ttry {\n\t\t\t\t\tStringTokenizer st = new StringTokenizer(line);\n\t\t\t\t\tString description = st.nextToken();\n\n\t\t\t\t\twhile (st.hasMoreTokens()) {\n\t\t\t\t\t\tput(st.nextToken(), description);\n\t\t\t\t\t}\n\t\t\t\t} catch (Exception e) {\n\t\t\t\t\t// If a line contains an error, catch the exception and go to the next line\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (IOException ignore) {\n\t\t}\n\t}\n\n\n\t/**\n\t * Returns the MIME type of the given file (determined by the file extension), <code>null</code>\n\t * if the type is unknown (unknown or no extension) or if the file is a folder.\n\t *\n\t * @param file the given file\n\t *\n\t * @return the MIME type\n\t */\n\tpublic static String getMimeType(AbstractFile file) {\n\t\tif (file.isDirectory()) {\n\t\t\treturn null;\n\t\t}\n\n\t\tString name = file.getName();\n\t\tint pos = name.lastIndexOf('.');\n\t\tif (pos < 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn MIME_TYPES.get(name.substring(pos+1).toLowerCase());\n\t}\n\n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/MutableFileAttributes.java",
    "content": "package com.mucommander.commons.file;\n\n/**\n * This interface extends <code>FileAttributes</code> to add attribute getters. Refer to {@link FileAttributes}'s\n * documentation for more information about attributes.\n *\n * <p>See the {@link SimpleFileAttributes} class for an implementation of this interface.\n *\n * @author Maxence Bernard\n * @see SimpleFileAttributes\n */\npublic interface MutableFileAttributes extends FileAttributes {\n\n    /**\n     * Sets the file's path.\n     *\n     * <p>The format and separator character of the path are filesystem-dependent.\n     *\n     * @param path the file's path\n     */\n    void setPath(String path);\n\n    /**\n     * Sets whether the file exists physically on the underlying filesystem.\n     *\n     * @param exists <code>true</code> if the file exists physically on the underlying filesystem\n     */\n    void setExists(boolean exists);\n\n    /**\n     * Sets the file's date in milliseconds since the epoch (00:00:00 GMT, January 1, 1970).\n     *\n     * @param date the file's date in milliseconds since the epoch (00:00:00 GMT, January 1, 1970)\n     */\n    void setDate(long date);\n\n    /**\n     * Sets the file's size in bytes.\n     *\n     * @param size the file's size in bytes\n     */\n    void setSize(long size);\n\n    /**\n     * Specifies whether the file is a directory or a regular file.\n     *\n     * @param directory <code>true</code> for directory, <code>false</code> for regular file\n     */\n    void setDirectory(boolean directory);\n\n    /**\n     * Sets the file's permissions.\n     *\n     * @param permissions the file's permissions\n     */\n    void setPermissions(FilePermissions permissions);\n\n    /**\n     * Sets the file's owner.\n     *\n     * @param owner the file's owner\n     */\n    void setOwner(String owner);\n\n    /**\n     * Sets the file's group.\n     *\n     * @param group the file's owner\n     */\n    void setGroup(String group);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/PathCanonizer.java",
    "content": "package com.mucommander.commons.file;\n\n/**\n * PathCanonizer is an interface that defines a single {@link #canonize(String)} method that returns the canonical\n * representation of a given path. This interface is used by {@link SchemeParser} implementations to perform\n * scheme-specific path canonization, independently of the actual URL parsing.\n *\n * @see DefaultSchemeParser\n * @author Maxence Bernard\n */\npublic interface PathCanonizer {\n\n    /**\n     * Returns a canonical representation of the given path.\n     *\n     * @param path path to canonize\n     * @return a canonical representation of the given path.\n     */\n    String canonize(String path);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/PermissionAccesses.java",
    "content": "package com.mucommander.commons.file;\n\n/**\n * This interface defines constants fields used for designating the three different permission accesses:\n * {@link #USER_ACCESS}, {@link #GROUP_ACCESS} and {@link #OTHER_ACCESS}. Their actual int values represent the number\n * of 3-bit left shifts (&lt;&lt; operator) needed to represent a particular\n * {@link com.mucommander.commons.file.PermissionTypes permission type} in a UNIX-style permission int. To illustrate,\n * the 'read' permission (value = 4) for the 'user' access (value = 2) is represented in a UNIX-style permission int as:\n * <code>4 &lt;&lt; 3*2 = 256 (400 octal)</code>.\n *\n * @see com.mucommander.commons.file.PermissionTypes\n * @author Maxence Bernard\n */\npublic interface PermissionAccesses {\n\n    /** Designates the 'other' permission access. */\n    int OTHER_ACCESS = 0;\n\n    /** Designates the 'group' permission access. */\n    int GROUP_ACCESS = 1;\n\n    /** Designates the 'user' permission access. */\n    int USER_ACCESS = 2;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/PermissionBits.java",
    "content": "package com.mucommander.commons.file;\n\n/**\n * This interface provides methods to access file permissions, for every combination of types and accesses defined\n * in {@link com.mucommander.commons.file.PermissionTypes} and {@link com.mucommander.commons.file.PermissionAccesses} respectively.\n * This interface also defines constants for commonly used permission values.\n *\n * <p>Permission bits can be queried individually using {@link #getBitValue(int, int)} or be represented altogether\n * as a UNIX-style permission int, using {@link #getIntValue()}.\n *\n * <p>Two implementations of this interface are provided:\n *   <ul>\n *     <li>{@link com.mucommander.commons.file.GroupedPermissionBits} (full implementation): implements this interface using a\n * given permission int.</li>\n *     <li>{@link com.mucommander.commons.file.IndividualPermissionBits} (partial implementation): implements the\n * <code>getIntValue()</code> method by relying on <code>getBitValue()</code> and querying it sequentially for every\n * permission bit.</li>\n *   </ul>\n *\n * @see com.mucommander.commons.file.GroupedPermissionBits\n * @see com.mucommander.commons.file.IndividualPermissionBits\n * @author Maxence Bernard\n */\npublic interface PermissionBits {\n\n    /** read/write/execute permissions set for user/group/other (777 octal) */\n    int FULL_PERMISSION_INT = 511;\n\n    /** read/write/execute permissions set for user/group/other (777 octal) */\n    PermissionBits FULL_PERMISSION_BITS = new GroupedPermissionBits(FULL_PERMISSION_INT);\n\n    /** read/write/execute permissions cleared for user/group/other (0) */\n    int EMPTY_PERMISSION_INT = 0;\n\n    /** read/write/execute permissions cleared for user/group/other (0) */\n    PermissionBits EMPTY_PERMISSION_BITS = new GroupedPermissionBits(EMPTY_PERMISSION_INT);\n\n\n    /**\n     * Returns the value of all the permission bits (9 in total) in a UNIX-style permission int. Each of the permission\n     * bits can be isolated by comparing them against the values defined in {@link com.mucommander.commons.file.PermissionTypes}\n     * and {@link com.mucommander.commons.file.PermissionAccesses}.\n     *\n     * @return the value of all the permission bits (9 in total) in a UNIX-style permission int\n     */\n    int getIntValue();\n\n    /**\n     * Returns the value of a specific permission bit: <code>true</code> if the permission is set, <code>false</code>\n     * if it isn't.\n     *\n     * @param access one of the values defined in {@link com.mucommander.commons.file.PermissionAccesses}\n     * @param type one of the values defined in {@link com.mucommander.commons.file.PermissionTypes}\n     * @return <code>true</code> if the permission is set, <code>false</code> if it isn't\n     */\n    boolean getBitValue(int access, int type);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/PermissionTypes.java",
    "content": "package com.mucommander.commons.file;\n\n/**\n * This interface defines constants fields used for designating the three different permission types:\n * {@link #READ_PERMISSION}, {@link #WRITE_PERMISSION} and {@link #EXECUTE_PERMISSION}. Their actual value represent\n * the bit to be set and left-shifted with the desired {@link com.mucommander.commons.file.PermissionAccesses permission access}\n * in a UNIX-style permission int.\n *\n * @see PermissionAccesses\n * @author Maxence Bernard\n */\npublic interface PermissionTypes {\n\n    /** Designates the 'execute' permission. */\n    int EXECUTE_PERMISSION = 1;\n\n    /** Designates the 'write' permission. */\n    int WRITE_PERMISSION = 2;\n\n    /** Designates the 'read' permission. */\n    int READ_PERMISSION = 4;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/ProtocolFile.java",
    "content": "package com.mucommander.commons.file;\n\n/**\n * Super class of all file protocol implementations (by opposition to {@link AbstractArchiveFile archive file} \n * implementations).\n *\n * @see ProtocolProvider\n * @author Maxence Bernard\n */\npublic abstract class ProtocolFile extends AbstractFile {\n\n    protected ProtocolFile(FileURL url) {\n        super(url);\n    }\n    \n\n    /**\n     * This implementation always returns <code>false</code>.\n     *\n     * @return <code>false</code>, always\n     */\n    @Override\n    public boolean isArchive() {\n        return false;\n    }\n\n    /*@Override\n    public boolean canGetReplication() {\n        return false;\n    }\n\n    @Override\n    public boolean canGetBlocksize() {\n        return false;\n    }\n\n    @Override\n    public short getReplication() {\n        return 0;\n    }\n\n    @Override\n    public long getBlocksize() {\n        return 0;\n    }\n\n    @Override\n    public void changeReplication(short replication) throws IOException {\n\n    }*/\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/ProtocolProvider.java",
    "content": "package com.mucommander.commons.file;\n\nimport java.io.IOException;\n\n/**\n * This interface allows {@link FileFactory} to instantiate {@link AbstractFile} implementations.\n * <p>\n * For {@link AbstractFile} implementations to be automatically instantiated by {@link FileFactory}, this interface\n * needs to be implemented and an instance registered with {@link FileFactory} and bound to a protocol identifier.\n *\n * @author Nicolas Rinaudo, Maxence Bernard\n * @see FileFactory\n * @see AbstractFile\n */\npublic interface ProtocolProvider {\n    /**\n     * Creates a new instance of <code>AbstractFile</code> that matches the specified URL.\n     * @param url URL to map as an <code>AbstractFile</code>.\n     * @param instantiationParams file implementation-specific parameters used for instantiating the\n     * {@link AbstractFile} implementation. Those parameters are used when creating file instances within\n     * the AbstractFile implementation.\n     * @return a new instance of <code>AbstractFile</code> that matches the specified URL.\n     * @throws IOException if an error occurs.\n     */\n    AbstractFile getFile(FileURL url, Object... instantiationParams) throws IOException;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/ROArchiveEntryFile.java",
    "content": "package com.mucommander.commons.file;\n\nimport java.io.OutputStream;\n\n/**\n * Represents a file entry inside a read-only archive. Read-only archives are characterized by\n * {@link AbstractArchiveFile#isWritable()} returning <code>false</code>.\n *\n * @see AbstractArchiveFile\n * @see RWArchiveEntryFile\n * @author Maxence Bernard\n */\npublic class ROArchiveEntryFile extends AbstractArchiveEntryFile {\n\n    protected ROArchiveEntryFile(FileURL url, AbstractArchiveFile archiveFile, ArchiveEntry entry) {\n        super(url, archiveFile, entry);\n    }\n\n    /**\n     * Always throws {@link UnsupportedFileOperationException} when called.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void setLastModifiedDate(long lastModified) throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_DATE);\n    }\n\n    /**\n     * Always return {@link PermissionBits#EMPTY_PERMISSION_BITS}.\n     */\n    @Override\n    public PermissionBits getChangeablePermissions() {\n        return PermissionBits.EMPTY_PERMISSION_BITS;\n    }\n\n    /**\n     * Always throws {@link UnsupportedFileOperationException} when called.\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void delete() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.DELETE);\n    }\n\n    /**\n     * Always throws {@link UnsupportedFileOperationException} when called.\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void mkdir() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.CREATE_DIRECTORY);\n    }\n\n    /**\n     * Always throws {@link UnsupportedFileOperationException} when called.\n     */\n    @Override\n    @UnsupportedFileOperation\n    public OutputStream getOutputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.WRITE_FILE);\n    }\n\n    /**\n     * Always throws {@link UnsupportedFileOperationException} when called.\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void changePermissions(int permissions) throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_PERMISSION);\n    }\n\n    /**\n     * Always throws {@link UnsupportedFileOperationException} when called.\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void changePermission(int access, int permission, boolean enabled) throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_PERMISSION);\n    }\n\n    /**\n     * Always returns <code>0</code>.\n     */\n    @Override\n    public long getFreeSpace() {\n        return 0;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/RWArchiveEntryFile.java",
    "content": "package com.mucommander.commons.file;\n\nimport com.mucommander.commons.io.ByteCounter;\nimport com.mucommander.commons.io.CounterOutputStream;\n\nimport javax.swing.tree.DefaultMutableTreeNode;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * Represents a file entry inside a read-write archive. Read-write archives are characterized by\n * {@link AbstractArchiveFile#isWritable()} returning <code>false</code>.\n *\n * @see AbstractArchiveFile\n * @see ROArchiveEntryFile\n * @author Maxence Bernard\n */\npublic class RWArchiveEntryFile extends AbstractArchiveEntryFile {\n\n    RWArchiveEntryFile(FileURL url, AbstractArchiveFile archiveFile, ArchiveEntry entry) {\n        super(url, archiveFile, entry);\n    }\n\n    /**\n     * Updates this entry's attributes in the archive and returns <code>true</code> if the update went OK.\n     *\n     * @return <code>true</code> if the attributes were successfully updated in the archive.\n     */\n    private boolean updateEntryAttributes() {\n        try {\n            ((AbstractRWArchiveFile)archiveFile).updateEntry(entry);\n            return true;\n        }\n        catch(IOException e) {\n            return false;\n        }\n    }\n\n\n\n    /**\n     * @throws IOException if the entry does not exist within the archive\n     */\n    @Override\n    public void setLastModifiedDate(long lastModified) throws IOException {\n        if (!entry.exists()) {\n            throw new IOException();\n        }\n\n        long oldDate = entry.getLastModifiedDate();\n        entry.setDate(lastModified);\n\n        boolean success = updateEntryAttributes();\n        if (!success) {\n            // restore old date if attributes could not be updated\n            entry.setDate(oldDate);\n            throw new IOException();\n        }\n    }\n    \n    /**\n     * Always returns {@link PermissionBits#FULL_PERMISSION_BITS}.\n     */\n    @Override\n    public PermissionBits getChangeablePermissions() {\n        // Todo: some writable archive implementations may not have full 'set' permissions support, or even no notion of permissions\n        return PermissionBits.FULL_PERMISSION_BITS;\n    }\n\n    /**\n     * Deletes this entry from the associated <code>AbstractArchiveFile</code>.\n     * <p>\n     * Throws a {@link UnsupportedFileOperationException} if the underlying file does not support the required\n     * read and write {@link FileOperation file operations}. Throws an <code>IOException</code> in any of the following\n     * cases:\n     * <ul>\n     *  <li>if this entry does not exist in the archive</li>\n     *  <li>if this entry is a non-empty directory</li>\n     *  <li>if an I/O error occurred</li>\n     * </ul>\n     *\n     * @throws IOException in any of the cases listed above.\n     */\n    @Override\n    public void delete() throws IOException {\n        if (!entry.exists()) {\n            throw new IOException();\n        }\n\n        AbstractRWArchiveFile rwArchiveFile = (AbstractRWArchiveFile)archiveFile;\n\n        // Throw an IOException if this entry is a non-empty directory\n        if (isDirectory()) {\n            ArchiveEntryTree tree = rwArchiveFile.getArchiveEntryTree();\n            if (tree != null) {\n                DefaultMutableTreeNode node = tree.findEntryNode(entry.getPath());\n                if (node != null && node.getChildCount() > 0) {\n                    throw new IOException();\n                }\n            }\n        }\n\n        // Delete the entry in the archive file\n        rwArchiveFile.deleteEntry(entry);\n\n        // Non-existing entries are considered as zero-length regular files\n        entry.setDirectory(false);\n        entry.setSize(0);\n        entry.setExists(false);\n    }\n\n    /**\n     * Creates this entry as a directory in the associated <code>AbstractArchiveFile</code>.\n     * <p>\n     * Throws a {@link UnsupportedFileOperationException} if the underlying file does not support the required\n     * read and write {@link FileOperation file operations}. Throws an <code>IOException</code> if this entry\n     * already exists in the archive or if an I/O error occurred.\n     *\n     * @throws IOException if this entry already exists in the archive or if an I/O error occurred.\n     */\n    @Override\n    public void mkdir() throws IOException {\n        if (entry.exists()) {\n            throw new IOException();\n        }\n\n        AbstractRWArchiveFile rwArchivefile = (AbstractRWArchiveFile)archiveFile;\n        // Update the ArchiveEntry\n        entry.setDirectory(true);\n        entry.setDate(System.currentTimeMillis());\n        entry.setSize(0);\n\n        // Add the entry to the archive file\n        rwArchivefile.addEntry(entry);\n\n        // The entry now exists\n        entry.setExists(true);\n    }\n\n    /**\n     * Returns an <code>OutputStream</code> that allows to write this entry's contents.\n     * <p>\n     * This method will create this entry as a regular file in the archive if it doesn't already exist, or replace\n     * it if it already does.\n     *\n     * @throws IOException if an I/O error occurred\n     */\n    @Override\n    public OutputStream getOutputStream() throws IOException {\n        if (entry.exists()) {\n            try {\n                delete();\n            } catch(IOException e) {\n                // Go ahead and try to add the file anyway\n            }\n        }\n\n        // Update the ArchiveEntry's size as data gets written to the OutputStream\n        OutputStream out = new CounterOutputStream(((AbstractRWArchiveFile)archiveFile).addEntry(entry),\n            new ByteCounter() {\n                @Override\n                public synchronized void add(long nbBytes) {\n                    entry.setSize(entry.getSize()+nbBytes);\n                    entry.setDate(System.currentTimeMillis());\n                }\n            });\n        entry.setExists(true);\n\n        return out;\n    }\n\n    @Override\n    public void changePermissions(int permissions) throws IOException {\n        if (!entry.exists()) {\n            throw new IOException();\n        }\n\n        FilePermissions oldPermissions = entry.getPermissions();\n        FilePermissions newPermissions = new SimpleFilePermissions(permissions, oldPermissions.getMask());\n        entry.setPermissions(newPermissions);\n\n        boolean success = updateEntryAttributes();\n        if (!success) {       // restore old permissions if attributes could not be updated\n            entry.setPermissions(oldPermissions);\n        }\n\n        if (!success) {\n            throw new IOException();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/SchemeHandler.java",
    "content": "package com.mucommander.commons.file;\n\n/**\n * <code>SchemeHandler</code> is an interface that allows {@link FileURL} to be specialized for a particular scheme.\n * It provides a number of scheme-specific features:\n * <dl>\n *  <dt>{@link #getStandardPort() standard port}</dt><dd>the standard port implied when no port is defined in the URL,\n * e.g. 21 for FTP</dd>\n *  <dt>{@link #getPathSeparator() path separator}</dt><dd>the character(s) that separates path fragments, e.g. '/' for\n * most schemes, '\\' for local paths under certain OSes like Windows.</dd>\n *  <dt>{@link #getGuestCredentials() guest credentials}</dt><dd>credentials to authenticate as a guest, e.g. 'GUEST'\n * for SMB, 'anonymous' for FTP.</dd>\n *  <dt>{@link #getRealm(FileURL) authentication realm}</dt><dd>the base URL throughout which a set of credentials can\n * be used.</dd>\n * </dl>\n * <p>\n * In addition to providing those attributes, a SchemeHandler provides a {@link SchemeParser} instance which takes care\n * of the actual parsing of URLs of a particular scheme when {@link FileURL#getFileURL(String)} is invoked. This allows\n * for scheme-specific parsing, like for example for the query part which should only be parsed and considered as a\n * separate part for certain schemes such as HTTP.\n *\n * <h3>Handler registration</h3>\n * <p>\n * <code>FileURL</code> registers a number of handlers for the schemes/protocols supported by the muCommander file API.\n * Additional handlers can be registered dynamically using {@link FileURL#registerHandler(String, SchemeHandler)}.\n * Likewise, existing handlers can be unregistered or replaced at runtime using\n * {@link FileURL#registerHandler(String, SchemeHandler)} and {@link FileURL#unregisterHandler(String)}.<br>\n * <br>\n * <code>FileURL</code> uses a default handler for schemes that do not have a specific handler registered.\n *\n * @see DefaultSchemeHandler\n * @see com.mucommander.commons.file.FileURL#registerHandler(String, SchemeHandler)\n * @see SchemeParser\n * @author Maxence Bernard\n */\npublic interface SchemeHandler {\n\n    /**\n     * Returns the <code>SchemeParser</code> that turns URL strings of a particular scheme into {@link FileURL} objects.\n     *\n     * @return the <code>SchemeParser</code> that turns URL strings of a particular scheme into {@link FileURL} objects\n     */\n    SchemeParser getParser();\n\n    /**\n     * Returns the authentication realm of the given location, i.e. the base location throughout which a set of\n     * credentials can be used. Any property contained by the specified FileURL will be carried over in the returned\n     * FileURL. On the contrary, credentials will not be copied, the returned URL always has no credentials. \n     *\n     * <p>This method returns a new FileURL instance every time it is called. Therefore, the returned URL can\n     * safely be modified without any risk of side effects.\n     *\n     * @param location the location for which to return the authentication realm\n     * @return the authentication realm of the specified url\n     */\n    FileURL getRealm(FileURL location);\n\n    /**\n     * Returns the scheme's guest credentials, <code>null</code> if the scheme doesn't have any.\n     * <p>\n     * Guest credentials offer a way to authenticate a URL as a 'guest' on file protocols that require a set of\n     * credentials to establish a connection. The returned credentials are provided with no guarantee that the filesystem\n     * will actually accept them and allow the request/connection. The notion of 'guest' credentials may or may not\n     * have a meaning depending on the underlying file protocol.\n     *\n     * @return the scheme's guest credentials, <code>null</code> if the scheme doesn't have any\n     */\n    Credentials getGuestCredentials();\n\n    /**\n     * Returns the type of authentication used by the scheme's file protocol. The returned value is one of the constants\n     * defined in {@link AuthenticationType}.\n     *\n     * @return the type of authentication used by the scheme's file protocol\n     */\n    AuthenticationType getAuthenticationType();\n\n    /**\n     * Returns the scheme's path separator, which serves as a delimiter for path fragments. For most schemes, this is\n     * the forward slash character.\n     *\n     * @return this scheme's path separator\n     */\n    String getPathSeparator();\n\n    /**\n     * Returns the scheme's standard port, <code>-1</code> if the scheme doesn't have any. Some file protocols may not\n     * have a notion of standard port or even no use for the port part at all, for example those that are not TCP\n     * or UDP based such as the local 'file' scheme. \n     *\n     * @return the scheme's standard port\n     */\n    int getStandardPort();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/SchemeParser.java",
    "content": "package com.mucommander.commons.file;\n\nimport java.net.MalformedURLException;\n\n/**\n * SchemeParser is an interface that provides a single {@link #parse(String, FileURL)} method used by\n * {@link FileURL#getFileURL(String)} to turn a URL string into a corresponding <code>FileURL</code> instance.\n *\n * @see FileURL#getFileURL(String)\n * @see com.mucommander.commons.file.SchemeHandler\n * @author Maxence Bernard\n */\npublic interface SchemeParser {\n\n    /**\n     * Extracts the different parts from the given URL string and sets them in the specified FileURL instance.\n     * The FileURL is empty when it is passed, with just the handler set. The scheme, host, port, login, password, path,\n     * ... parts must all be set, using the corresponding setter methods.\n     *\n     * <p>Some parts such as the query and fragment have a meaning only for certain schemes such as HTTP, other schemes\n     * may simply ignore the corresponding query/fragment delimiters ('?' and '#' resp.) and include them in the\n     * path part.\n     *\n     * @param url the URL to parse\n     * @param fileURL the FileURL instance in which to set the different parsed parts\n     * @throws MalformedURLException if the specified string is not a valid URL and cannot be parsed\n     */\n    void parse(String url, FileURL fileURL) throws MalformedURLException;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/SevenZipArchiveFormatDetector.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2026 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file;\n\nimport com.mucommander.commons.io.BufferPool;\nimport com.mucommander.commons.io.StreamUtils;\nimport lombok.extern.slf4j.Slf4j;\nimport net.sf.sevenzipjbinding.ArchiveFormat;\n\nimport java.io.IOException;\nimport java.io.PushbackInputStream;\n\n@Slf4j\npublic abstract class SevenZipArchiveFormatDetector {\n    private final int maxLen;\n\n    public SevenZipArchiveFormatDetector(int maxLen) {\n        this.maxLen = maxLen;\n    }\n\n    protected abstract ArchiveFormat detect(byte[] bytes);\n\n    public ArchiveFormat detect(AbstractFile file) {\n        byte[] bytes = readFirst(file);\n        if (bytes == null) {\n            return null;\n        }\n        ArchiveFormat format = detect(bytes);\n        BufferPool.releaseByteArray(bytes);\n        return format;\n    }\n\n    private byte[] readFirst(AbstractFile file) {\n        byte[] bytes = BufferPool.getByteArray(maxLen);\n        try {\n            PushbackInputStream is = file.getPushBackInputStream(maxLen);\n            int readBytes = StreamUtils.readUpTo(is, bytes);\n            is.unread(bytes, 0, readBytes);\n        } catch (IOException e) {\n            log.error(\"Exception on file read\", e);\n            BufferPool.releaseByteArray(bytes);\n            try {\n                file.closePushbackInputStream();\n            } catch (IOException e1) {\n                log.error(\"Exception on stream close\", e1);\n            }\n            return null;\n        }\n        return bytes;\n    }\n\n\n    protected static boolean checkSignature(byte[] data, byte[] signature) {\n        if (data.length < signature.length) {\n            return false;\n        }\n        for (int i = 0; i < signature.length; i++) {\n            if (data[i] != signature[i]) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    protected static boolean checkSignature(byte[] data, int[] signature) {\n        if (data.length < signature.length) {\n            return false;\n        }\n        for (int i = 0; i < signature.length; i++) {\n            if ((data[i] & 0xff) != signature[i] && signature[i] != -1) {\n                return false;\n            }\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/SimpleFileAttributes.java",
    "content": "package com.mucommander.commons.file;\n\n/**\n * This class is a simple implementation of the {@link com.mucommander.commons.file.FileAttributes} interface, where all\n * the attributes are stored as protected members of the class.\n *\n * @author Maxence Bernard\n */\npublic class SimpleFileAttributes implements MutableFileAttributes {\n\n    /** Path attribute */\n    private String path;\n\n    /** Exists attribute */\n    private boolean exists;\n\n    /** Date attribute */\n    private long date;\n\n    /** Size attribute */\n    private long size;\n\n    /** Directory attribute */\n    private boolean directory;\n\n    /** Permissions attribute */\n    private FilePermissions permissions;\n\n    /** Owner attribute */\n    private String owner;\n\n    /** Group attribute */\n    private String group;\n\n    /** Replication attribute */\n    private short replication = 1;\n\n    /** BlockSize attribute */\n    private long blockSize = 0;\n\n    /**\n     * Creates a new SimpleFileAttributes instance with unspecified/null attribute values.\n     */\n    public SimpleFileAttributes() {\n    }\n\n\n    /**\n     * Creates a new SimpleFileAttributes instance whose attributes are set to those of the given AbstractFile.\n     * Note that the path attribute is set to the file's {@link com.mucommander.commons.file.AbstractFile#getAbsolutePath() absolute path}.\n     *\n     * @param file the file from which to fetch the attribute values\n     */\n    public SimpleFileAttributes(AbstractFile file) {\n        setPath(file.getAbsolutePath());\n        setExists(file.exists());\n        setDate(file.getLastModifiedDate());\n        setSize(file.getSize());\n        setDirectory(file.isDirectory());\n        setPermissions(file.getPermissions());\n        setOwner(file.getOwner());\n        setGroup(file.getGroup());\n    }\n\n\n    @Override\n    public String getPath() {\n        return path;\n    }\n\n    @Override\n    public void setPath(String path) {\n        this.path = path;\n    }\n\n    @Override\n    public boolean exists() {\n        return exists;\n    }\n\n    @Override\n    public void setExists(boolean exists) {\n        this.exists = exists;\n    }\n\n    @Override\n    public long getLastModifiedDate() {\n        return date;\n    }\n\n    @Override\n    public void setDate(long date) {\n        this.date = date;\n    }\n\n    @Override\n    public long getSize() {\n        return size;\n    }\n\n    @Override\n    public void setSize(long size) {\n        this.size = size;\n    }\n\n    @Override\n    public boolean isDirectory() {\n        return directory;\n    }\n\n    @Override\n    public void setDirectory(boolean directory) {\n        this.directory = directory;\n    }\n\n    @Override\n    public FilePermissions getPermissions() {\n        return permissions;\n    }\n\n    @Override\n    public void setPermissions(FilePermissions permissions) {\n        this.permissions = permissions;\n    }\n\n    @Override\n    public String getOwner() {\n        return owner;\n    }\n\n    @Override\n    public void setOwner(String owner) {\n        this.owner = owner;\n    }\n\n    @Override\n    public String getGroup() {\n        return group;\n    }\n\n    @Override\n    public void setGroup(String group) {\n        this.group = group;\n    }\n\n    public short getReplication() {\n        return replication;\n    }\n\n    public void setReplication(short replication) {\n        this.replication = replication;\n    }\n\n    public long getBlockSize() {\n        return blockSize;\n    }\n\n    public void setBlockSize(long blockSize) {\n        this.blockSize = blockSize;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/SimpleFilePermissions.java",
    "content": "package com.mucommander.commons.file;\n\n/**\n * SimpleFilePermissions is a FilePermissions implementation that takes int values for the permission values and mask.\n * Additionally, this class defines <code>padPermission</code> static methods that allows to pad unsupported permission\n * bits with default values.\n *\n * @author Maxence Bernard\n */\npublic class SimpleFilePermissions extends GroupedPermissionBits implements FilePermissions {\n\n    /** The permissions mask */\n    protected PermissionBits mask;\n\n    /**\n     * Creates a new SimpleFilePermissions using the specified UNIX-style permission int for permission values and\n     * {@link #FULL_PERMISSION_BITS full permissions mask}.\n     *\n     * @param permissions a UNIX-style permission int that holds permission values.\n     */\n    public SimpleFilePermissions(int permissions) {\n        this(permissions, FULL_PERMISSION_BITS);\n    }\n\n    /**\n     * Creates a new SimpleFilePermissions using the specified UNIX-style permission int values for permission values\n     * and mask.\n     *\n     * @param permissions a UNIX-style permission int that holds permission values.\n     * @param mask a UNIX-style permission int which defines which permission bits are supported.\n     */\n    public SimpleFilePermissions(int permissions, int mask) {\n        this(permissions, new GroupedPermissionBits(mask));\n    }\n\n    /**\n     * Creates a new SimpleFilePermissions using the specified UNIX-style permission int and permission mask.\n     *\n     * @param permissions a UNIX-style permission int that holds permission values.\n     * @param mask a permission mask which defines which permission bits are supported.\n     */\n    public SimpleFilePermissions(int permissions, PermissionBits mask) {\n        super(permissions);\n\n        this.mask = mask;\n    }\n\n\n    /**\n     * Pads the given permissions with the specified ones: the permission bits that are not supported\n     * (as reported by the supplied permissions mask) are replaced by those of the default permissions.\n     * That means:<br>\n     *  - if the mask indicates that all permission bits are supported (mask = 777 octal), the supplied permissions will\n     * simply be returned, without using any of the default permissions<br>\n     *  - if the mask indicates that none of the permission bits are supported (mask = 0), the default permissions will\n     * be returned, without using any of the supplied permissions<br>\n     *\n     * @param permissions the permissions to pad with default permissions for the bits that are not supported\n     * @param defaultPermissions permissions to use for the bits that are not supported\n     * @return the permissions padded with the default permissions\n     */\n    public static FilePermissions padPermissions(FilePermissions permissions, FilePermissions defaultPermissions) {\n        int permissionMask = permissions.getMask().getIntValue();\n\n        return new SimpleFilePermissions((\n                permissions.getIntValue() & permissionMask) | (~permissionMask & defaultPermissions.getIntValue()),\n                defaultPermissions.getMask());\n    }\n\n    /**\n     * Pads the given permissions with the specified ones: the permission bits that are not supported\n     * (as reported by the supplied permissions mask) are replaced by those of the default permissions.\n     * That means:<br>\n     *  - if the mask indicates that all permission bits are supported (mask = 777 octal), the supplied permissions will\n     * simply be returned, without using any of the default permissions<br>\n     *  - if the mask indicates that none of the permission bits are supported (mask = 0), the default permissions will\n     * be returned, without using any of the supplied permissions<br>\n     *\n     * @param permissions the permissions to pad with default permissions for the bits that are not supported\n     * @param supportedPermissionsMask the bit mask that indicates which bits of the given permissions are supported\n     * @param defaultPermissions permissions to use for the bits that are not supported\n     * @return the given permissions padded with the default permissions\n     */\n    public static int padPermissions(int permissions, int supportedPermissionsMask, int defaultPermissions) {\n        return (permissions & supportedPermissionsMask) | (~supportedPermissionsMask & defaultPermissions);\n    }\n\n\n    @Override\n    public PermissionBits getMask() {\n        return mask;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/SingleArchiveEntryIterator.java",
    "content": "package com.mucommander.commons.file;\n\n/**\n * This class is an implementation of {@link ArchiveEntryIterator} that iterates through a single archive entry\n * specified at creation time. The entry passed to the constructor may be <code>null</code> -- the iterator will\n * act as an empty one. {@link #close()} is implemented as a no-op.\n *\n * @author Maxence Bernard\n */\npublic class SingleArchiveEntryIterator implements ArchiveEntryIterator {\n\n    /** The single entry to iterate through */\n    protected ArchiveEntry entry;\n\n    public SingleArchiveEntryIterator(ArchiveEntry entry) {\n        this.entry = entry;\n    }\n\n    @Override\n    public ArchiveEntry nextEntry() {\n        if (entry == null) {\n            return null;\n        }\n\n        ArchiveEntry nextEntry = entry;\n        entry = null;\n\n        return nextEntry;\n    }\n\n    /**\n     * Implemented as a no-op (nothing to close).\n     */\n    @Override\n    public void close() {\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/SyncedFileAttributes.java",
    "content": "package com.mucommander.commons.file;\n\n/**\n * SyncedFileAttributes is a FileAttributes implementation which allows attribute values to be automatically\n * updated when accessed after a certain amount of time (the 'time to live') since their last update. The update\n * is performed by the abstract {@link #updateAttributes()} method and is triggered by any of the attribute getters.\n * A typical usage for this class is for remote file systems that need to keep file attributes in sync with a server,\n * {@link #updateAttributes()} can retrieve a fresh copy of the attributes on the server.\n *\n * <p>Attributes can also be manually updated using attribute setters. The {@link #updateExpirationDate()} method allows\n * to reset the expiration date and consider the attributes as 'fresh'.\n *\n * <p>An initial value for the attributes 'time to live' is specified in the constructor and can later be changed using\n * {@link #setTtl(long)}. If the 'time to live' is set to -1, attributes are no longer automatically updated, this class\n * then simply acts as {@link com.mucommander.commons.file.SimpleFileAttributes}.\n *\n * @author Maxence Bernard\n */\npublic abstract class SyncedFileAttributes extends SimpleFileAttributes {\n\n    /** The attributes' 'time to live', negative values disable automatic attributes updates */\n    private long ttl;\n\n    /** The attributes' expiration timestamp/date */\n    private long expirationDate;\n\n    /** True when attributes are being updated */\n    private boolean isUpdating;\n\n\n    /**\n     * Creates a new SyncedFileAttributes using the specifies 'time to live' value.\n     *\n     * @param ttl the attributes' 'time to live', in milliseconds\n     * @param updateAttributesNow if <code>true</code>, attributes are automatically updated\n     */\n    public SyncedFileAttributes(long ttl, boolean updateAttributesNow) {\n        setTtl(ttl);    // also sets the expiration date\n\n       if (updateAttributesNow) {\n           checkForExpiration(true);   // force attributes update\n       }\n    }\n\n    /**\n     * Returns the attributes' 'time to live', i.e. the amount of time since the last update after which attributes will\n     * be automatically updated when any of the getter method is called.\n     *\n     * @return the attributes' 'time to live', in milliseconds\n     */\n    public long getTtl() {\n        return ttl;\n    }\n\n    /**\n     * Sets the attributes' 'time to live', i.e. the amount of time since the last update after which attributes will\n     * be automatically updated when any of the getter method is called.\n     * Note that setting the 'time to live' causes the expiration date to be updated with {@link #updateExpirationDate()}.\n     *\n     * @param ttl the attributes' 'time to live', in milliseconds\n     */\n    public void setTtl(long ttl) {\n        this.ttl = ttl;\n\n        // update the expiration date\n        updateExpirationDate();\n    }\n\n    /**\n     * Returns the attributes' expiration timestamp/date, the date after which attributes values will be automatically\n     * updated when any of the getter method is called.\n     *\n     * @return the attributes' expiration timestamp/date\n     */\n    public long getExpirationDate() {\n        return expirationDate;\n    }\n\n    /**\n     * Sets the attributes' expiration timestamp/date, the date after which attributes values will be automatically\n     * updated when they are accessed using any of the getter methods.\n     *\n     * @param expirationDate the attributes expiration timestamp/date\n     */\n    public void setExpirationDate(long expirationDate) {\n        this.expirationDate = expirationDate;\n    }\n\n    /**\n     * Updates the attributes' expiration date to 'now' + 'ttl' (as returned by {@link #getTtl()}).\n     * This method is called after attributes have been automatically updated. It can also be called after attribute\n     * values have been manually updated using the setter methods.\n     */\n    public void updateExpirationDate() {\n        setExpirationDate(ttl < 0 ? Long.MAX_VALUE : System.currentTimeMillis() + getTtl());\n    }\n\n    /**\n     * Returns <code>true</code> if attributes have expired, i.e. the {@link #getExpirationDate()} expiration date has\n     * passed, <code>false</code> if attributes are still 'fresh'. This method also returns <code>false</code> if\n     * automatic attributes' update has been disabled ('time to live' set to a negative value), or if attributes are\n     * currently being updated.\n     *  \n     * @return <code>true</code> if attributes have expired\n     */\n    public boolean hasExpired() {\n        return ttl>=0           // prevents automatic updates if ttl is set to a negative value\n            && !isUpdating()    // causes getters to return the current value while attributes are being updated\n            && System.currentTimeMillis()>expirationDate;\n    }\n\n    /**\n     * Returns <code>true</code> if attributes are currently being updated.\n     *\n     * @return <code>true</code> if attributes are currently being updated\n     */\n    private synchronized boolean isUpdating() {\n        return isUpdating;\n    }\n\n    /**\n     * Sets whether attributes are currently being updated.\n     *\n     * @param isUpdating <code>true</code> if attributes are currently being updated\n     */\n    private synchronized void setUpdating(boolean isUpdating) {\n        this.isUpdating = isUpdating;\n    }\n\n    /**\n     * Checks if the attributes have expired and if they have, calls {@link #updateAttributes()} to refresh their\n     * values.\n     *\n     * @param forceUpdate if true, attributes will systematically be updated, without checking the expiration date\n     */\n    protected void checkForExpiration(boolean forceUpdate) {\n        if (forceUpdate || hasExpired()) {\n            // After this method is called, hasExpired() returns false so that implementations of updateAttributes()\n            // can query attribute getters without entering a loop of death.\n            setUpdating(true);\n\n            // Updates attribute values\n            updateAttributes();\n\n            // Update expiration date after the attribute have actually been updated, note that it may take a while\n            // for remote file protocols to retrieve attributes.\n            updateExpirationDate();\n\n            // OK we're done\n            setUpdating(false);\n        }\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    /**\n     * Overridden to trigger attributes update if the expiration date has been reached.\n     */\n    @Override\n    public String getPath() {\n        checkForExpiration(false);\n\n        return super.getPath();\n    }\n\n    /**\n     * Overridden to trigger attributes update if the expiration date has been reached.\n     */\n    @Override\n    public boolean exists() {\n        checkForExpiration(false);\n\n        return super.exists();\n    }\n\n    /**\n     * Overridden to trigger attributes update if the expiration date has been reached.\n     */\n    @Override\n    public long getLastModifiedDate() {\n        checkForExpiration(false);\n\n        return super.getLastModifiedDate();\n    }\n\n    /**\n     * Overridden to trigger attributes update if the expiration date has been reached.\n     */\n    @Override\n    public long getSize() {\n        checkForExpiration(false);\n\n        return super.getSize();\n    }\n\n    /**\n     * Overridden to trigger attributes update if the expiration date has been reached.\n     */\n    @Override\n    public boolean isDirectory() {\n        checkForExpiration(false);\n\n        return super.isDirectory();\n    }\n\n    /**\n     * Overridden to trigger attributes update if the expiration date has been reached.\n     */\n    @Override\n    public FilePermissions getPermissions() {\n        checkForExpiration(false);\n\n        return super.getPermissions();\n    }\n\n    /**\n     * Overridden to trigger attributes update if the expiration date has been reached.\n     */\n    @Override\n    public String getOwner() {\n        checkForExpiration(false);\n\n        return super.getOwner();\n    }\n\n    /**\n     * Overridden to trigger attributes update if the expiration date has been reached.\n     */\n    @Override\n    public String getGroup() {\n        checkForExpiration(false);\n\n        return super.getGroup();\n    }\n\n    /**\n     * Overridden to trigger attributes update if the expiration date has been reached.\n     */\n    @Override\n    public short getReplication() {\n        checkForExpiration(false);\n\n        return super.getReplication();\n    }\n\n    /**\n     * Overridden to trigger attributes update if the expiration date has been reached.\n     */\n    @Override\n    public long getBlockSize() {\n        checkForExpiration(false);\n\n        return super.getBlockSize();\n    }\n\n\n    //////////////////////\n    // Abstract methods //\n    //////////////////////\n\n    /**\n     * Updates the attribute values. This method is automatically called when attributes are expired and one of the\n     * attribute getters is called. The implementation may choose to update only certain attributes, or skip updates\n     * under certain conditions.\n     */\n    public abstract void updateAttributes();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/UnsupportedFileOperation.java",
    "content": "package com.mucommander.commons.file;\n\nimport java.lang.annotation.*;\n\n/**\n * @author Maxence Bernard\n */\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface UnsupportedFileOperation {\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/UnsupportedFileOperationException.java",
    "content": "package com.mucommander.commons.file;\n\nimport java.io.IOException;\n\n/**\n * This exception can be thrown by certain {@link AbstractFile} methods, when the corresponding operation\n * is not available, either because the underlying file protocol does not support it, or because it is not\n * implemented. This exception may also be thrown by file operations that depend on another file operation that is\n * not supported.\n * <br>\n * Unlike <code>java.lang.UnsupportedOperationException</code>, this exception is <b>not</b> a\n * <code>RuntimeException</code> and must therefore be caught explicitly.\n *\n * <p>\n * This exception is to be thrown in a way that is independent of the actual file instance, and of I/O or\n * network conditions: an <code>AbstractFile</code> method that throws this exception once must throw it\n * always, for any file instance.\n *\n * @author Maxence Bernard\n * @see UnsupportedFileOperation\n * @see AbstractFile\n */\npublic class UnsupportedFileOperationException extends IOException {\n\n    /** The {@link FileOperation} this exception refers to */\n    private final FileOperation op;\n\n    /**\n     * Creates a new <code>UnsupportedFileOperationException</code> corresponding to the specified {@link FileOperation}.\n     *\n     * @param op the {@link FileOperation} this exception refers to.\n     */\n    public UnsupportedFileOperationException(FileOperation op) {\n        super();\n\n        this.op = op;\n    }\n\n    /**\n     * Creates a new <code>UnsupportedFileOperationException</code> corresponding to the specified {@link FileOperation}\n     * with a custom message.\n     *\n     * @param op the {@link FileOperation} this exception refers to.\n     * @param message a message describing the exception cause.\n     */\n    public UnsupportedFileOperationException(FileOperation op, String message) {\n        super(message);\n\n        this.op = op;\n    }\n\n    /**\n     * Returns the {@link FileOperation} this exception refers to.\n     *\n     * @return the {@link FileOperation} this exception refers to.\n     */\n    public FileOperation getFileOperation() {\n        return op;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/WrapperArchiveEntryIterator.java",
    "content": "package com.mucommander.commons.file;\n\nimport java.io.IOException;\nimport java.util.Iterator;\n\n/**\n * This class wraps a <code>java.util.Iterator</code> and implements <code>ArchiveEntryIterator</code> by\n * delegating methods to their <code>java.util.Iterator</code> equivalent. {@link #close()} is implemented as a no-op.\n *\n * @author Maxence Bernard\n */\npublic class WrapperArchiveEntryIterator implements ArchiveEntryIterator {\n\n    /** Wrapped iterator */\n    protected Iterator<? extends ArchiveEntry> iterator;\n\n    /**\n     * Creates a new <code>WrapperArchiveEntryIterator</code> that iterates through the given\n     * <code>java.util.Iterator</code>'s elements.\n     *\n     * @param iterator the wrapped iterator\n     */\n    public WrapperArchiveEntryIterator(Iterator<? extends ArchiveEntry> iterator) {\n        this.iterator = iterator;\n    }\n\n\n    @Override\n    public ArchiveEntry nextEntry() {\n        return iterator.hasNext() ? iterator.next() : null;\n    }\n\n    /**\n     * Implemented as a no-op (nothing to close).\n     */\n    @Override\n    public void close() throws IOException {\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/archiver/ArchiveFormat.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2017 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.archiver;\n\n/**\n * @author Oleg Trifonov\n * Created on 15/03/17.\n */\npublic enum ArchiveFormat {\n    ZIP(\"Zip\", \"zip\", true),\n    GZ(\"Gzip\", \"gz\", false),\n    BZ2(\"Bzip2\", \"bz2\", false),\n    TAR(\"Tar\", \"tar\", true),\n    TAR_GZ(\"Tar/Gzip\", \"tar.gz\", true),\n    TAR_BZ2(\"Tar/Bzip2\", \"tar.bz2\", true);\n//    ISO(\"ISO\", \"iso\", true);\n\n    /**\n     * The name of the given archive format, can be used for display in a GUI.\n     */\n    public final String name;\n    /**\n     * The default archive format extension. Note: some formats such as Tar/Gzip have several common\n     * extensions (e.g. tar.gz or tgz), the most common one will be returned\n     */\n    public final String ext;\n    /**\n     * true if the format used by this Archiver can store more than one entry\n     */\n    public final boolean supportManyEntries;\n\n    ArchiveFormat(String name, String ext, boolean supportManyEntries) {\n        this.name = name;\n        this.ext = ext;\n        this.supportManyEntries = supportManyEntries;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/archiver/Archiver.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.file.archiver;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileAttributes;\nimport com.mucommander.commons.file.FileOperation;\nimport com.mucommander.commons.file.UnsupportedFileOperationException;\nimport com.mucommander.commons.io.BufferedRandomOutputStream;\nimport com.mucommander.commons.io.RandomAccessOutputStream;\nimport org.apache.hadoop.io.compress.bzip2.CBZip2OutputStream;\n\nimport java.io.BufferedOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.zip.GZIPOutputStream;\n\n\n/**\n * Archiver is an abstract class that represents a generic file archiver and abstracts the underlying\n * compression method and specifics of the format.\n *\n * <p>Subclasses implement specific archive formats (Zip, Tar...) but cannot be instantiated directly.\n * Instead, the <code>getArchiver</code> methods can be used to retrieve an Archiver\n * instance for a specified archive format. A list of available archive formats can be dynamically retrieved\n * using {@link #getFormats(boolean) getFormats}.\n *\n * <p>Archive formats fall into 2 categories:\n * <ul>\n * <li><i>Single entry formats:</i> Formats that can only store one entry without any directory structure, e.g. Gzip or Bzip2.\n * <li><i>Many entries formats:</i> Formats that can store multiple entries along with a directory structure, e.g. Zip or Tar.\n * </ul>\n *\n * @author Maxence Bernard\n */\npublic abstract class Archiver {\n\n\n    /** The underlying stream this archiver is writing to */\n    protected OutputStream out;\n    /** Archive format of this Archiver */\n    protected ArchiveFormat format;\n    /** Support output stream for archiving files */\n    boolean supportStream;\n\t\n    /**\n     * Creates a new Archiver.\n     *\n     * @param out the OutputStream this Archiver will write to\n     */\n    Archiver(OutputStream out) {\n        this.out = out;\n        this.supportStream = true;\n    }\n\n    /**\n     * Returns the <code>OutputStream</code> this Archiver is writing to.\n     *\n     * @return the OutputStream this Archiver is writing to\n     */\n    public OutputStream getOutputStream() {\n        return out;\n    }\n\n    \n    /**\n     * Returns the archiver format used by this Archiver. See format constants.\n     * @return archiver format code\n     */\n    public ArchiveFormat getFormat() {\n        return this.format;\n    }\n\t\n    /**\n     * Sets the archiver format used by this Archiver, for internal use only.\n     */\n    private void setFormat(ArchiveFormat format) {\n        this.format = format;\n    }\n\t\n\n    /**\n     * Checks if the format used by this Archiver can store an optional comment.\n     * @return true if the format used by this Archiver can store an optional comment.\n     */\n    public boolean supportsComment() {\n        return formatSupportsComment(this.format);\n    }\n\n    /**\n     * @return true if the archiver supports writing with streams\n     */\n    public boolean supportsStream() {\n        return supportStream;\n    }\n\n    /**\n     * Sets an optional comment in the archive, the {@link #supportsComment()} or\n     * {@link #formatSupportsComment(ArchiveFormat)} must first be called to make sure\n     * the archive format supports comment, otherwise calling this method will have no effect.\n     *\n     * <p>Implementation note: Archiver implementations must override this method to handle comments\n     *\n     * @param comment the comment to be stored in the archive\n     */\n    public void setComment(String comment) {\n        // No-op\n    }\n\n\n    /**\n     * Normalizes the entry path, that is :\n     * <ul>\n     *   <li>replace any \\ character occurrence by / as this usually is the default separator for archive files\n     *   <li>if the entry is a directory, add a trailing slash to the path if it doesn't have one already\n     * </ul>\n     *\n     * @param entryPath\n\t  * @param isDirectory\n\t  *\n\t  * @return normalized path\n     */\n    String normalizePath(String entryPath, boolean isDirectory) {\n        // Replace any \\ character by /\n        entryPath = entryPath.replace('\\\\', '/');\n\t\t\n        // If entry is a directory, make sure the path contains a trailing / \n        if (isDirectory && !entryPath.endsWith(\"/\"))\n            entryPath += \"/\";\n\t\t\n        return entryPath;\n    }\n\n\n    ////////////////////\n    // Static methods //\n    ////////////////////\n\n    /**\n     * Returns an Archiver for the specified format and that uses the given {@link AbstractFile} to write entries to.\n     * <code>null</code> is returned if the specified format is not valid.\n     *\n     * <p>This method will first attempt to get a {@link RandomAccessOutputStream} if the given file is able to supply\n     * one, and if not, fall back to a regular <code>OutputStream</code>. Note that if the file exists, its contents\n     * will be overwritten. Write bufferring is used under the hood to improve performance.\n     *\n     * @param file the AbstractFile which the returned Archiver will write entries to\n     * @param format an archive format\n     * @return an Archiver for the specified format and that uses the given {@link AbstractFile} to write entries to ;\n     * null if the specified format is not valid.\n     * @throws IOException if the file cannot be opened for write, or if an error occurred while intializing the archiver\n     * @throws UnsupportedFileOperationException if the underlying filesystem does not support write operations\n     */\n    public static Archiver getArchiver(AbstractFile file, ArchiveFormat format) throws IOException, UnsupportedFileOperationException {\n//        switch(format) {\n//            case ISO:\n//                return new ISOArchiver(file);\n//        }\n        OutputStream out = null;\n\n        if (file.isFileOperationSupported(FileOperation.RANDOM_WRITE_FILE)) {\n            try {\n                // Important: if the file exists, it has to be overwritten as AbstractFile#getRandomAccessOutputStream()\n                // does NOT overwrite the file. This fixes bug #30.\n                if (file.exists()) {\n                    file.delete();\n                }\n                out = new BufferedRandomOutputStream(file.getRandomAccessOutputStream());\n            } catch (IOException e) {\n                // Fall back to a regular OutputStream\n            }\n        }\n\n        if (out == null) {\n            out = new BufferedOutputStream(file.getOutputStream());\n        }\n\n        return getArchiver(out, format);\n    }\n\n\n    /**\n     * Returns an Archiver for the specified format and that uses the given <code>OutputStream</code> to write entries to.\n     * <code>null</code> is returned if the specified format is not valid. Whenever possible, a\n     * {@link RandomAccessOutputStream} should be supplied as some formats take advantage of having a random write access.\n     *\n     * @param out the OutputStream which the returned Archiver will write entries to\n     * @param format an archive format\n     * @return an Archiver for the specified format and that uses the given {@link AbstractFile} to write entries to ;\n     * null if the specified format is not valid.\n     * @throws IOException if an error occurred while initializing the archiver\n     */\n    private static Archiver getArchiver(OutputStream out, ArchiveFormat format) throws IOException {\n        Archiver archiver;\n\n        switch (format) {\n            case ZIP:\n                archiver = new ZipArchiver(out);\n                break;\n            case GZ:\n                archiver = new SingleFileArchiver(new GZIPOutputStream(out));\n                break;\n            case BZ2:\n                archiver = new SingleFileArchiver(createBzip2OutputStream(out));\n                break;\n            case TAR:\n                archiver = new TarArchiver(out);\n                break;\n            case TAR_GZ:\n                archiver = new TarArchiver(new GZIPOutputStream(out));\n                break;\n            case TAR_BZ2:\n                archiver = new TarArchiver(createBzip2OutputStream(out));\n                break;\n//            case ISO:\n//                throw new IllegalStateException(\"ISO archiving not supported by stream\");\n\n            default:\n                return null;\n        }\n\t\t\n        archiver.setFormat(format);\n\n        return archiver;\n    }\n\n    /**\n     * Creates and returns a Bzip2 <code>OutputStream</code> using the given <code>OutputStream</code> as the underlying\n     * stream.\n     *\n     * @param out the underlying stream\n     * @return a Bzip2 OutputStream\n     * @throws IOException if an error occurred while initializing the Bzip2 OutputStream\n     */\n    private static OutputStream createBzip2OutputStream(OutputStream out) throws IOException {\n        // Writes the 2 magic bytes 'BZ', as required by CBZip2OutputStream. A quote from CBZip2OutputStream's Javadoc:\n        // \"Attention: The caller is responsible to write the two BZip2 magic bytes \"BZ\" to the specified stream\n        // prior to calling this constructor.\"\n\n        out.write('B');\n        out.write('Z');\n\n        return new CBZip2OutputStream(out);\n    }\n\n\n    /**\n     * Returns an array of available archive formats, single entry formats or many entries formats\n     * depending on the value of the specified boolean parameter. \n     *\n     * @param manyEntries if true, a list of many entries formats (a subset of single entry formats) will be returned\n     * @return an array of available archive formats\n     */\n    public static ArchiveFormat[] getFormats(boolean manyEntries) {\n        if (!manyEntries) {\n            return ArchiveFormat.values();\n        }\n        int cnt = 0;\n        for (ArchiveFormat af : ArchiveFormat.values()) {\n            if (af.supportManyEntries) {\n                cnt++;\n            }\n        }\n        ArchiveFormat[] result = new ArchiveFormat[cnt];\n        int i = 0;\n        for (ArchiveFormat af : ArchiveFormat.values()) {\n            if (af.supportManyEntries) {\n                result[i++] = af;\n            }\n        }\n        return result;\n    }\n\n\n    /**\n     * Returns true if the specified archive format can store an optional comment.\n     *\n     * @param format an archive format\n     * @return true if the specified archive format can store an optional comment\n     */\n    public static boolean formatSupportsComment(ArchiveFormat format) {\n        return format == ArchiveFormat.ZIP;\n    }\n\t\n\t\n    //////////////////////\n    // Abstract methods //\n    //////////////////////\n\n    /**\n     * Creates a new entry in the archive using the given relative path and file attributes, and returns an\n     * <code>OutputStream</code> to write the entry's contents. The specified file attributes are used to determine\n     * whether the entry is a directory or a regular file, and to set the entry's size, permissions and date.\n     * \n     * <p>If the entry is a regular file (not a directory), an OutputStream which can be used to write the contents\n     * of the entry will be returned, <code>null</code> otherwise. The OutputStream <b>must not</b> be closed once\n     * it has been used (Archiver takes care of this), only the {@link #close() close} method has to be called when\n     * all entries have been created.\n     *\n     * <p>If this Archiver uses a single entry format, the specified path and file won't be used at all.\n     * Also in this case, this method must be invoked only once (single entry), it will throw an IOException\n     * if invoked more than once.\n     *\n     * @param entryPath the path to be used to create the entry in the archive. This parameter is simply ignored if the\n     * archive is a single entry format.\n     * @param attributes used to determine whether the entry is a directory or regular file, and to retrieve its\n     * date and size\n     * @return <code>OutputStream</code> to write the entry's contents.\n     * @throws IOException if this Archiver failed to write the entry, or in the case of a single entry archiver, if\n     * this method was called more than once.\n     */\n    public abstract OutputStream createEntry(String entryPath, FileAttributes attributes) throws IOException;\n\n\n    /**\n     * @return Name of current file being processed\n     */\n    public String getProcessingFile() {\n        return null;\n    }\n    \n    /**\n     * Written bytes in total without the current file progress\n     * @return number of bytes written as a long\n     */\n    public long totalWrittenBytes() {\n        return -1;\n    }\n    \n    /**\n     * Written bytes to the current file being processed, will be the same size as the\n     * file if complete.\n     * @return number of bytes written as a long\n     */\n    public long writtenBytesCurrentFile() {\n        return -1;\n    }\n    \n    /**\n     * @return Size of the current file being processed in bytes\n     */\n    public long currentFileLength() {\n        return -1;\n    }\n    \n    /**\n     * Finish the archiving process when all files have been added.\n     */\n    public abstract void postProcess() throws IOException;\n    \n    /**\n     * Closes the underlying OuputStream and ressources used by this Archiver to write the archive. This method\n     * must be called when all entries have been added to the archive.\n     */\n    public abstract void close() throws IOException;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/archiver/ISOArchiver.java",
    "content": "package com.mucommander.commons.file.archiver;\n\nimport com.github.stephenc.javaisotools.eltorito.impl.ElToritoConfig;\nimport com.github.stephenc.javaisotools.iso9660.ConfigException;\nimport com.github.stephenc.javaisotools.iso9660.ISO9660Directory;\nimport com.github.stephenc.javaisotools.iso9660.ISO9660File;\nimport com.github.stephenc.javaisotools.iso9660.ISO9660RootDirectory;\nimport com.github.stephenc.javaisotools.iso9660.impl.ISO9660Config;\nimport com.github.stephenc.javaisotools.iso9660.impl.ISOImageFileHandler;\nimport com.github.stephenc.javaisotools.joliet.impl.JolietConfig;\nimport com.github.stephenc.javaisotools.rockridge.impl.RockRidgeConfig;\nimport com.github.stephenc.javaisotools.sabre.HandlerException;\nimport com.github.stephenc.javaisotools.sabre.StreamHandler;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileAttributes;\nimport com.mucommander.commons.file.impl.iso.MuCreateISO;\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\n/**\n * Archiver implementation using the ISO9660 archive format.\n *\n * @author Jeppe Vennekilde\n */\npublic class ISOArchiver extends Archiver {\n    private StreamHandler streamHandler;\n    private final ISO9660Config config;\n    private final ISO9660RootDirectory root;\n    //Adds support for longer file names & wider range of characters\n    private final boolean enableJoliet = true;\n    //Adds support for deeper directory hierarchies and even bigger file names (up to 255 bytes)\n    private final boolean enableRockRidge = true;\n    //Adds support for creation of bootable iso files (not implemented)\n    private final boolean enableElTorito = false;\n    private MuCreateISO createISOProcess = null;\n\n    ISOArchiver(AbstractFile file) {\n        super(null);\n        supportStream = false;\n        \n        config = new ISO9660Config();\n        try {\n            config.allowASCII(false);\n            config.setInterchangeLevel(1);\n            //The rock ridge extension of ISO9660 allow directory depth to exceed 8\n            config.restrictDirDepthTo8(!enableRockRidge);\n            config.setPublisher(System.getProperty(\"user.name\"));\n            //Max length of volume is 32 chars\n            config.setVolumeID(file.getName().substring(0, Math.min(file.getName().length(), 31)));\n            config.setDataPreparer(System.getProperty(\"user.name\"));\n            config.forceDotDelimiter(true);\n        } catch (ConfigException ex) {\n            Logger.getLogger(ISOArchiver.class.getName()).log(Level.SEVERE, null, ex);\n        }\n        \n        root = new ISO9660RootDirectory();\n        \n        try {\n            streamHandler = new ISOImageFileHandler(new File(file.getPath()));\n        } catch (FileNotFoundException ex) {\n            Logger.getLogger(ISOArchiver.class.getName()).log(Level.SEVERE, null, ex);\n        }\n    }\n\n\n    @Override\n    public OutputStream createEntry(String entryPath, FileAttributes attributes) {\n        try {\n            if (attributes.isDirectory()) {\n                String[] split = entryPath.split(\"\\\\\\\\\");\n                ISO9660Directory dir = new ISO9660Directory(split[split.length-1]);\n                ISO9660Directory parent = getParentDirectory(entryPath);\n                if (parent != null) {\n                    parent.addDirectory(dir);\n                }\n            } else {\n                try {\n                    ISO9660File file = new ISO9660File(new File(attributes.getPath()));\n                    ISO9660Directory parent = getParentDirectory(entryPath);\n                    if (parent != null) {\n                        parent.addFile(file);\n                    }\n                } catch (HandlerException ex) {\n                    Logger.getLogger(ISOArchiver.class.getName()).log(Level.SEVERE, null, ex);\n                }\n            }\n        } catch (RuntimeException e) {\n            e.printStackTrace();\n            throw e;\n        }\n        return null;    // TODO !!!! NPE here !!!\n    }\n    \n    /**\n     * Get the ISO9660Directory parent object the path belongs to\n     *\n     * @param isoPath the sub directory/file of the parent directory it will return\n     * @return an ISO9660Directory that is the parent of the provided path\n     */\n    private ISO9660Directory getParentDirectory(String isoPath){\n        String[] directories = isoPath.split(\"\\\\\\\\\");\n        //Initial directory (root)\n        ISO9660Directory parent = root;\n        for (int i = 0; i < directories.length - 1; i++){\n            ISO9660Directory dir = containsDirectory(parent,directories[i]);\n            if (dir == null) {\n                return null;\n            }\n            parent = dir;\n        }\n        return parent;\n    }\n    \n    /**\n     * Check if an ISO9660Directory contain a provided sub directory\n     *\n     * @param parentDirectory the directory that will be searched\n     * @param isoSubDirPath the ISO path that will be used for reference to see \n     * if the parent directory contains the sub directory\n     * @return an ISO9660Directory that is sub directory of the parent directory\n     * null if it does not contain the sub directory\n     */\n    private ISO9660Directory containsDirectory(ISO9660Directory parentDirectory, String isoSubDirPath){\n        for (ISO9660Directory directory : parentDirectory.getDirectories()) {\n            if (directory.getName().equals(isoSubDirPath)){\n                return directory;\n            }\n        }\n        return null;\n    }\n\n    @Override\n    public String getProcessingFile() {\n        return createISOProcess != null ? createISOProcess.getProcessingFile() : null;\n    }\n    \n    @Override\n    public long totalWrittenBytes(){\n        return createISOProcess != null ? createISOProcess.totalWrittenBytes(): 0;\n    }\n    \n    @Override\n    public long writtenBytesCurrentFile(){\n        return createISOProcess != null ? createISOProcess.writtenBytesCurrentFile(): 0;\n    }\n    \n    @Override\n    public long currentFileLength(){\n        return createISOProcess != null ? createISOProcess.currentFileLength(): 0;\n    }\n    \n    @Override\n    public void postProcess() throws IOException {\n        if (root.hasSubDirs() || !root.getFiles().isEmpty()) {\n            createISOProcess = new MuCreateISO(streamHandler, root);\n\n            RockRidgeConfig rrConfig = null;\n            if (enableRockRidge) {\n                // Rock Ridge support\n                rrConfig = new RockRidgeConfig();\n                rrConfig.setMkisofsCompatibility(false);\n                rrConfig.hideMovedDirectoriesStore(true);\n                rrConfig.forcePortableFilenameCharacterSet(true);\n            }\n\n            JolietConfig jolietConfig = null;\n            if (enableJoliet) {\n                // Joliet support\n                jolietConfig = new JolietConfig();\n                try {\n                    if (config.getPublisher() instanceof String){\n                        jolietConfig.setPublisher((String) config.getPublisher());\n                    } else {\n                        try {\n                            jolietConfig.setPublisher((File) config.getPublisher());\n                        } catch (HandlerException ex) {\n                            Logger.getLogger(ISOArchiver.class.getName()).log(Level.SEVERE, null, ex);\n                        }\n                    } \n                    //Max volume id is 16 in the joliet config\n                    jolietConfig.setVolumeID(config.getVolumeID().substring(0,Math.min(config.getVolumeID().length(), 15)));\n                    if (config.getDataPreparer() != null){\n                        if(config.getDataPreparer() instanceof String){\n                            jolietConfig.setDataPreparer((String) config.getDataPreparer());\n                        } else {\n                            try {\n                                jolietConfig.setDataPreparer((File) config.getDataPreparer());\n                            } catch (Exception ex) {\n                                Logger.getLogger(ISOArchiver.class.getName()).log(Level.SEVERE, null, ex);\n                            }\n                        } \n                    }\n                    jolietConfig.forceDotDelimiter(true);\n                } catch (ConfigException ex) {\n                    Logger.getLogger(ISOArchiver.class.getName()).log(Level.SEVERE, null, ex);\n                }\n            }\n\n            //ELTorito adds support for bootable ISO files, which is not supported at this time\n            //As this is for archiving, not creation of bootable ISO files (yet)\n            ElToritoConfig elToritoConfig = null;\n\n            try {\n                createISOProcess.process(config, rrConfig, jolietConfig, elToritoConfig);\n            } catch (HandlerException ex) {\n                Logger.getLogger(ISOArchiver.class.getName()).log(Level.SEVERE, null, ex);\n            }\n        }\n    }\n\n    @Override\n    public void close() throws IOException {\n    }\n    \n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/archiver/SingleFileArchiver.java",
    "content": "package com.mucommander.commons.file.archiver;\n\nimport com.mucommander.commons.file.FileAttributes;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n\n/**\n * Generic single file Archiver.\n *\n * @author Maxence Bernard\n */\nclass SingleFileArchiver extends Archiver {\n\n    private boolean firstEntry = true;\n\n\n    SingleFileArchiver(OutputStream outputStream) {\n        super(outputStream);\n    }\n\n\n    /**\n     * This method is a no-op, and does nothing but throw an IOException if it is called more than once,\n     * which should never be the case as this Archiver is only meant to store one file. \n     */\n    @Override\n    public OutputStream createEntry(String entryPath, FileAttributes attributes) throws IOException {\n        if (firstEntry) {\n            firstEntry = false;\n        } else {\n            throw new IOException();\n        }\n\n        return out;\n    }\n\t\n\t\n    @Override\n    public void close() throws IOException {\n        out.close();\n    }\n\n    @Override\n    public void postProcess() {}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/archiver/TarArchiver.java",
    "content": "package com.mucommander.commons.file.archiver;\n\nimport com.mucommander.commons.file.FileAttributes;\nimport com.mucommander.commons.file.FilePermissions;\nimport com.mucommander.commons.file.SimpleFilePermissions;\nimport com.mucommander.commons.file.impl.tar.provider.TarEntry;\nimport com.mucommander.commons.file.impl.tar.provider.TarOutputStream;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n\n/**\n * Archiver implementation using the Tar archive format.\n *\n * @author Maxence Bernard\n */\nclass TarArchiver extends Archiver {\n\n    private final TarOutputStream tos;\n    private boolean firstEntry = true;\n\n    TarArchiver(OutputStream outputStream) {\n        super(outputStream);\n\n        this.tos = new TarOutputStream(outputStream);\n        // Specifies how to handle files which filename is > 100 chars (default is to fail!)\n        this.tos.setLongFileMode(TarOutputStream.LONGFILE_GNU);\n    }\n\n\n\n    @Override\n    public OutputStream createEntry(String entryPath, FileAttributes attributes) throws IOException {\n        // Start by closing current entry\n        if (!firstEntry) {\n            tos.closeEntry();\n        }\n\n        boolean isDirectory = attributes.isDirectory();\n\t\t\n        // create the entry\n        TarEntry entry = new TarEntry(normalizePath(entryPath, isDirectory));\n        // Use provided file's size (required by TarOutputStream) and date\n        long size = attributes.getSize();\n        if (!isDirectory && size >= 0)\t\t// Do not set size if file is directory or file size is unknown!\n            entry.setSize(size);\n\n        // Set the entry's date and permissions\n        entry.setModTime(attributes.getLastModifiedDate());\n        entry.setMode(SimpleFilePermissions.padPermissions(attributes.getPermissions(), isDirectory\n                    ? FilePermissions.DEFAULT_DIRECTORY_PERMISSIONS\n                    : FilePermissions.DEFAULT_FILE_PERMISSIONS).getIntValue());\n\n        // Add the entry\n        tos.putNextEntry(entry);\n\n        if (firstEntry) {\n            firstEntry = false;\n        }\n\t\n        // Return the OutputStream that allows to write to the entry, only if it isn't a directory \n        return isDirectory ? null : tos;\n    }\n\n\n    @Override\n    public void close() throws IOException {\n        // Close current entry\n        if (!firstEntry) {\n            tos.closeEntry();\n        }\n\t\t\n        tos.close();\n    }\n    \n    @Override\n    public void postProcess() {}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/archiver/ZipArchiver.java",
    "content": "package com.mucommander.commons.file.archiver;\n\nimport com.mucommander.commons.file.FileAttributes;\nimport com.mucommander.commons.file.FilePermissions;\nimport com.mucommander.commons.file.SimpleFilePermissions;\nimport com.mucommander.commons.file.impl.zip.provider.ZipEntry;\nimport com.mucommander.commons.file.impl.zip.provider.ZipOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n\n/**\n * Archiver implementation using the Zip archive format.\n *\n * @author Maxence Bernard\n */\nclass ZipArchiver extends Archiver {\n\n    private final ZipOutputStream zos;\n    private boolean firstEntry = true;\n\n\n\n    ZipArchiver(OutputStream outputStream) {\n        super(outputStream);\n\n        this.zos = new ZipOutputStream(outputStream);\n    }\n\n\n    /**\n     * Overrides Archiver's no-op setComment method as Zip supports archive comment.\n     */\n    @Override\n    public void setComment(String comment) {\n        zos.setComment(comment);\n    } \n\t\n\n\n    @Override\n    public OutputStream createEntry(String entryPath, FileAttributes attributes) throws IOException {\n        // Start by closing current entry\n        if (!firstEntry) {\n            zos.closeEntry();\n        }\n\n        boolean isDirectory = attributes.isDirectory();\n\t\t\n        // Create the entry and use the provided file's date\n        ZipEntry entry = new ZipEntry(normalizePath(entryPath, isDirectory));\n        // Use provided file's size and date\n        long size = attributes.getSize();\n        if (!isDirectory && size >= 0) {    // Do not set size if file is directory or file size is unknown!\n            entry.setSize(size);\n        }\n\n        entry.setTime(attributes.getLastModifiedDate());\n        entry.setUnixMode(SimpleFilePermissions.padPermissions(attributes.getPermissions(), isDirectory\n                    ? FilePermissions.DEFAULT_DIRECTORY_PERMISSIONS\n                    : FilePermissions.DEFAULT_FILE_PERMISSIONS).getIntValue());\n\n        // Add the entry\n        zos.putNextEntry(entry);\n\n        if (firstEntry) {\n            firstEntry = false;\n        }\n\t\t\n        // Return the OutputStream that allows to write to the entry, only if it isn't a directory \n        return isDirectory ? null : zos;\n    }\n\n\n    @Override\n    public void close() throws IOException {\n        zos.close();\n    }\n    \n    @Override\n    public void postProcess() {}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/compat/CompatURLConnection.java",
    "content": "package com.mucommander.commons.file.compat;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.URL;\nimport java.net.URLConnection;\n\n/**\n * @author Maxence Bernard\n*/\nclass CompatURLConnection extends URLConnection {\n\n    protected AbstractFile file;\n\n    public CompatURLConnection(URL url) {\n        super(url);\n\n        // Not connected yet\n    }\n\n    public CompatURLConnection(URL url, AbstractFile file) {\n        super(url);\n\n        if(file!=null) {\n            this.file = file;\n            connected = true;\n        }\n    }\n\n    /**\n     * Checks if this <code>URLConnection</code> is connected and if it isn't, calls {@link #connect()} to connect it.\n     *\n     * @throws IOException if an error occurred while connecting this URLConnection\n     */\n    private void checkConnected() throws IOException {\n        if(!connected)\n            connect();\n    }\n\n\n    /**\n     * Creates the {@link AbstractFile} instance corresponding to the URL location, only if no <code>AbstractFile</code>\n     * has been specified when this <code>CompatURLConnection</code> was created.\n     *\n     * @throws IOException if an error occurred while instanciating the AbstractFile\n     */\n    @Override\n    public void connect() throws IOException {\n        if (!connected) {\n            file = FileFactory.getFile(url.toString(), true);\n            connected = true;\n        }\n    }\n\n\n    @Override\n    public InputStream getInputStream() throws IOException {\n        checkConnected();\n        return file.getInputStream();\n    }\n\n    @Override\n    public OutputStream getOutputStream() throws IOException {\n        checkConnected();\n        return file.getOutputStream();\n    }\n\n    @Override\n    public long getLastModified() {\n        try {\n            checkConnected();\n            return file.getLastModifiedDate();\n        } catch(IOException e) {\n            return 0;\n        }\n    }\n\n    @Override\n    public long getDate() {\n        return getLastModified();\n    }\n\n    @Override\n    public int getContentLength() {\n        try {\n            checkConnected();\n            return (int)file.getSize();\n        } catch(IOException e) {\n            return -1;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/compat/CompatURLStreamHandler.java",
    "content": "package com.mucommander.commons.file.compat;\n\nimport com.mucommander.commons.file.AbstractFile;\n\nimport java.io.IOException;\nimport java.net.URL;\nimport java.net.URLConnection;\nimport java.net.URLStreamHandler;\n\n\n/**\n * @author Maxence Bernard\n */\npublic class CompatURLStreamHandler extends URLStreamHandler {\n\n    protected AbstractFile file;\n\n    public CompatURLStreamHandler() {\n    }\n\n    public CompatURLStreamHandler(AbstractFile file) {\n        this.file = file;\n    }\n\n\n    @Override\n    protected URLConnection openConnection(URL url) throws IOException {\n        return new CompatURLConnection(url, file);      // Note: file may be null\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/connection/ConnectionHandler.java",
    "content": "package com.mucommander.commons.file.connection;\n\nimport com.mucommander.commons.file.AuthException;\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileURL;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\n\n/**\n * ConnectionHandler is a an abstract class that provides the basic operations for to interact with a server: establish\n * the connection, keep it alive and close it.\n *\n * @see com.mucommander.commons.file.connection.ConnectionPool\n * @author Maxence Bernard\n */\npublic abstract class ConnectionHandler {\n    private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionHandler.class);\n\n    /** URL of the server this ConnectionHandler connects to */\n    protected FileURL realm;\n\n    /** Credentials that are used to connect to the server */\n    protected Credentials credentials;\n\n    /** True if this ConnectionHandler is currently locked */\n    protected boolean isLocked;\n\n    /** Time at which the connection managed by this ConnectionHandler was last used */\n    private long lastActivityTimestamp;\n\n    /** Time at which the connection managed by this ConnectionHandler was last kept alive */\n    private long lastKeepAliveTimestamp;\n\n    /** Number of seconds of inactivity after which this ConnectionHandler's connection will be closed by ConnectionPool */\n    private long closeOnInactivityPeriod = DEFAULT_CLOSE_ON_INACTIVITY_PERIOD;\n\n    /** Number of seconds of inactivity after which this ConnectionHandler's connection will be kept alive by ConnectionPool */\n    private long keepAlivePeriod = DEFAULT_KEEP_ALIVE_PERIOD;\n\n    /** Default 'close on inactivity' period */\n    private final static long DEFAULT_CLOSE_ON_INACTIVITY_PERIOD = 300;\n\n    /** Default keep alive period (-1, keep alive disabled) */\n    private final static long DEFAULT_KEEP_ALIVE_PERIOD = -1;\n\n\n    /**\n     * Creates a new ConnectionHandler for the given server URL using the Credentials included in the URL (potentially\n     * <code>null</code>).\n     *\n     * @param serverURL URL of the server to connect to\n     */\n    public ConnectionHandler(FileURL serverURL) {\n        realm = serverURL.getRealm();\n        this.credentials = serverURL.getCredentials();\n    }\n\n\n    /**\n     * Returns the URL of the server this ConnectionHandler connects to.\n     *\n     * @return the URL of the server this ConnectionHandler connects to\n     */\n    public FileURL getRealm() {\n        return realm;\n    }\n\n\n    /**\n     * Returns the Credentials that are used to connect to the server, <code>null</code> if no credentials are used.\n     *\n     * @return the Credentials that are used to connect to the server, <code>null</code> if no credentials are used\n     */\n    public Credentials getCredentials() {\n        return credentials;\n    }\n\n\n    /**\n     * Checks if the connection is currently active (as returned by {@link #isConnected()} and if it isn't, starts it\n     * by calling {@link #startConnection()}. Returns true if the connection was properly started, false if the\n     * connection was already active, or throws an IOException if the connection could not be started.\n     *\n     * @return Returns true if the connection was properly started, false if the connection was already active\n     * @throws IOException if the connection could not be started\n     */\n    public boolean checkConnection() throws IOException {\n        if (!isConnected()) {\n            LOGGER.info(\"not connected, starting connection, this=\"+this);\n            startConnection();\n            return true;\n        }\n\n        return false;\n    }\n\n\n    /**\n     * Tries to lock this ConnectionHandler and returns true if it could be locked, false if it is already locked.\n     *\n     * @return true if it could be locked, false if it is already locked.\n     */\n    synchronized boolean acquireLock() {\n        if (isLocked) {\n            LOGGER.info(\"!!!!! acquireLock() returning false, should not happen !!!!!\", new Throwable());\n            return false;\n        }\n\n        isLocked = true;\n        return true;\n    }\n\n    /**\n     * Tries to release the lock on this ConnectionHandler and returns true if it could be locked, false if it\n     * is not locked.\n     *\n     * @return true if it could be locked, false if it is not locked\n     */\n    public boolean releaseLock() {\n        synchronized(this) {\n            if (!isLocked) {\n                LOGGER.info(\"!!!!! releaseLock() returning false, should not happen !!!!!\", new Throwable());\n                return false;\n            }\n\n            isLocked = false;\n        }\n\n        ConnectionPool.notifyConnectionHandlerLockReleased();\n\n        return true;\n    }\n\n    /**\n     * Returns <code>true</code> if this ConnectionHandler is currently locked.\n     *\n     * @return <code>true</code> if this ConnectionHandler is currently locked\n     */\n    public synchronized boolean isLocked() {\n        return isLocked;\n    }\n\n\n    /**\n     * Updates the time at which the connection managed by this ConnectionHandler was last used to now (current time).\n     */\n    void updateLastActivityTimestamp() {\n        lastActivityTimestamp = System.currentTimeMillis();\n    }\n\n    /**\n     * Returns the time at which the connection managed by this ConnectionHandler was last used.\n     *\n     * @return the time at which the connection managed by this ConnectionHandler was last used\n     */\n    long getLastActivityTimestamp() {\n        return lastActivityTimestamp;\n    }\n\n\n    /**\n     * Updates the time at which the connection managed by this ConnectionHandler was last kept alive to now (current time).\n     */\n    void updateLastKeepAliveTimestamp() {\n        lastKeepAliveTimestamp = System.currentTimeMillis();\n    }\n\n    /**\n     * Returns the time at which the connection managed by this ConnectionHandler was last kept alive.\n     *\n     * @return the time at which the connection managed by this ConnectionHandler was last kept alive\n     */\n    long getLastKeepAliveTimestamp() {\n        return lastKeepAliveTimestamp;\n    }\n\n\n    /**\n     * Returns the number of seconds of inactivity after which {@link ConnectionPool} will close the connection by\n     * calling {@link #closeConnection()}, <code>-1</code> to indicate that the connection should not be automatically\n     * closed.\n     *\n     * <p>By default, this value is 300 seconds (5 minutes).\n     *\n     * @return the number of seconds of inactivity after which {@link ConnectionPool} will close the connection,\n     * <code>-1</code> to indicate that the connection should not be automatically closed\n     */\n    long getCloseOnInactivityPeriod() {\n        return closeOnInactivityPeriod;\n    }\n\n    /**\n     * Sets the number of seconds of inactivity after which {@link ConnectionPool} will close the connection by calling\n     * {@link #closeConnection()}, <code>-1</code> to prevent the connection from being automatically closed.\n     *\n     * <p>By default, this value is 300 seconds (5 minutes).\n     *\n     * @param nbSeconds the number of seconds of inactivity after which {@link ConnectionPool} will close the connection,\n     * <code>-1</code> to indicate that the connection should not be automatically closed\n     */\n    public void setCloseOnInactivityPeriod(long nbSeconds) {\n        closeOnInactivityPeriod = nbSeconds;\n    }\n\n\n    /**\n     * Returns the number of seconds of inactivity after which {@link ConnectionPool} will keep the connection alive\n     * by calling {@link #keepAlive()}, <code>-1</code> to indicate that this connection should not be kept alive.\n     *\n     * <p>By default, this value is -1 (keep alive disabled).\n     *\n     * @return the number of seconds of inactivity after which {@link ConnectionPool} will keep the connection alive\n     * by calling {@link #keepAlive()}, <code>-1</code> to indicate that this connection should not be kept alive\n     */\n    long getKeepAlivePeriod() {\n        return keepAlivePeriod;\n    }\n\n    /**\n     * Returns the number of seconds of inactivity after which {@link ConnectionPool} will keep the connection alive\n     * by calling {@link #keepAlive()}, <code>-1</code> to indicate that this connection should not be kept alive.\n     *\n     * <p>By default, this value is -1 (keep alive disabled).\n     *\n     * @param nbSeconds the number of seconds of inactivity after which {@link ConnectionPool} will keep the connection\n     * alive by calling {@link #keepAlive()}, <code>-1</code> to indicate that this connection should not be kept alive\n     */\n    protected void setKeepAlivePeriod(long nbSeconds) {\n        keepAlivePeriod = nbSeconds;\n    }\n\n\n    /**\n     * Returns <code>true</code> if the given Object is a ConnectionHandler whose realm and credentials are equal to\n     * those of this ConnectionHandler. The credentials comparison is password-sensitive.\n     *\n     * @param o the Object to compare for equality\n     * @see Credentials#equals(Object, boolean)\n     */\n    public boolean equals(Object o) {\n        if (!(o instanceof ConnectionHandler)) {\n            return false;\n        }\n        ConnectionHandler connHandler = (ConnectionHandler)o;\n        return equals(connHandler.realm, connHandler.credentials);\n    }\n\n\n    /**\n     * Returns <code>true</code> if both the given realm and credentials are equal to those of this ConnectionHandler.\n     * The credentials comparison is password-sensitive.\n     *\n     * @param realm the FileURL to compare against this ConnectionHandler's\n     * @param credentials the Credentials to compare against this ConnectionHandler's\n     * @return true if both the given realm and credentials are equal to those of this ConnectionHandler\n     * @see Credentials#equals(Object, boolean)\n     */\n    public boolean equals(FileURL realm, Credentials credentials) {\n        if (!this.realm.equals(realm, false, true)) {\n            return false;\n        }\n\n        // Compare credentials. One or both Credentials instances may be null.\n\n        // Note: Credentials.equals() considers null as equal to empty Credentials (see Credentials#isEmpty())\n        return (this.credentials == null && credentials == null)\n            || (this.credentials != null && this.credentials.equals(credentials, true))\n            || (credentials != null && credentials.equals(this.credentials, true));\n    }\n\n\n    /**\n     * Throws an {@link AuthException} using this connection handler's realm, credentials and the message passed as\n     * an argument (can be <code>null</code>). The FileURL instance representing the realm that is used to create\n     * the <code>AuthException</code> is a clone of this realm, making it safe for modification.\n     *\n     * @param message the message to pass to AuthException's constructor, can be <code>null</code>\n     * @throws AuthException always throws the created AuthException\n     */\n    protected void throwAuthException(String message) throws AuthException {\n        FileURL clonedRealm = (FileURL)realm.clone();\n        clonedRealm.setCredentials(credentials);\n\n        throw new AuthException(clonedRealm, message);\n    }\n\n\n    //////////////////////\n    // Abstract methods //\n    //////////////////////\n\n    /**\n     * Starts the connection managed by this ConnectionHandler, and throws an IOException if the connection could not\n     * be established. This method may be called several times during the life of this ConnectionHandler, if the\n     * connection dropped and must be re-established.\n     *\n     * @throws IOException if an error occurred while trying to establish the connection\n     * @throws AuthException if an authentication error occurred (incorrect login or password, insufficient privileges...)\n     */\n    public abstract void startConnection() throws IOException, AuthException;\n\n    /**\n     * Returns <code>true</code> if the connection managed by this ConnectionHandler is currently active/established,\n     * in a state that makes it possible to serve client requests.\n     *\n     * <p>Implementation note: This method must not perform any I/O which could block the calling thread.\n     *\n     * @return <code>true</code> if the connection managed by this ConnectionHandler is currently active/established\n     */\n    public abstract boolean isConnected();\n\n    /**\n     * Closes the connection managed by this ConnectionHandler.\n     *\n     * <p>Implementation note: the implementation must guarantee that any calls to {@link #isConnected()} after this\n     * method has been called return false.\n     */\n    public abstract void closeConnection();\n\n    /**\n     * Keeps this connection alive.\n     *\n     * <p>Implementation note: if keep alive is not available in the underlying protocol or\n     * simply unnecessary, this method should be implemented as a no-op (do nothing).\n     */\n    public abstract void keepAlive();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/connection/ConnectionHandlerFactory.java",
    "content": "package com.mucommander.commons.file.connection;\n\nimport com.mucommander.commons.file.FileURL;\n\n/**\n * This interface should be implemented by classes that are able to create ConnectionHandler instances for a given\n * server location, typically {@link com.mucommander.commons.file.AbstractFile} implementations.\n *\n * <p>This interface allows to take advantage of {@link ConnectionPool} to share connections across\n * {@link com.mucommander.commons.file.AbstractFile} instances.\n *\n * @author Maxence Bernard\n */\npublic interface ConnectionHandlerFactory {\n\n    /**\n     * Creates and returns a {@link ConnectionHandler} instance for the given location.\n     */\n    ConnectionHandler createConnectionHandler(FileURL location);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/connection/ConnectionPool.java",
    "content": "package com.mucommander.commons.file.connection;\n\nimport java.io.InterruptedIOException;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileURL;\n\n\n/**\n * @see com.mucommander.commons.file.connection.ConnectionHandler\n * @author Maxence Bernard\n */\npublic class ConnectionPool implements Runnable {\n    private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionPool.class);\n\n    /** Singleton instance */\n    private static final ConnectionPool instance = new ConnectionPool();\n\n    /** List of registered ConnectionHandler */\n    private final static List<ConnectionHandler> connectionHandlers = new ArrayList<>();\n\n    /** The thread that monitors connections, null if there currently is no registered ConnectionHandler */\n    private static Thread monitorThread;\n\n    /** Controls how of often the thread monitor checks connections */\n    private final static int MONITOR_SLEEP_PERIOD = 1000;\n\n    /** Maximum number of simultaneous connections per realm/credentials combo */\n    private final static int MAX_CONNECTIONS_PER_REALM = 4;\n\n\n    public static ConnectionHandler getConnectionHandler(ConnectionHandlerFactory connectionHandlerFactory, FileURL url, boolean acquireLock) throws InterruptedIOException {\n        FileURL realm = url.getRealm();\n\n        while(true) {\n            synchronized(connectionHandlers) {      // Ensures that monitor thread is not currently changing the list while we access it\n                Credentials urlCredentials = url.getCredentials();\n                int matchingConnHandlers = 0;\n\n                // Try and find an appropriate existing ConnectionHandler\n                //for (ConnectionHandler connHandler : connectionHandlers) {\n                for (Iterator<ConnectionHandler> it = connectionHandlers.iterator(); it.hasNext(); ) {\n                    ConnectionHandler connHandler = it.next();\n                \t// ConnectionHandler must match the realm and credentials and must not be locked\n                \tif (connHandler.equals(realm, urlCredentials)) {\n                \t\tmatchingConnHandlers++;\n                \t\tsynchronized(connHandler) {     // Ensures that lock remains unchanged while we access/update it\n                \t\t\tif (!connHandler.isLocked()) {\n                \t\t\t\t// Try to acquire lock if a lock was requested\n                \t\t\t\tif (!acquireLock || connHandler.acquireLock()) {\n                \t\t\t\t\tLOGGER.info(\"returning ConnectionHandler {}, realm = {}\", connHandler, realm);\n\n                \t\t\t\t\t// Update last activity timestamp to now\n                \t\t\t\t\tconnHandler.updateLastActivityTimestamp();\n\n                \t\t\t\t\treturn connHandler;\n                \t\t\t\t}\n                \t\t\t}\n                \t\t}\n                \t}\n                    \n                    if (matchingConnHandlers == MAX_CONNECTIONS_PER_REALM) {\n                        LOGGER.info(\"Maximum number of connection per realm reached, waiting for one to be removed or released...\");\n                        try {\n                            // Wait for a ConnectionHandler to be released or removed from the pool\n                            final int timeout = 5000;\n                            long t0 = System.currentTimeMillis();\n                            connectionHandlers.wait(timeout);      // relinquishes the lock on connectionHandlers\n                            if (System.currentTimeMillis() - t0 > timeout) {\n                                connHandler.closeConnection();\n                                it.remove();\n                                throw new InterruptedIOException();\n                            }\n                            break;\n                        } catch(InterruptedException e) {\n                            LOGGER.info(\"Interrupted while waiting on a connection for {}\", url, e);\n                            connHandler.closeConnection();\n                            it.remove();\n\n                            throw new InterruptedIOException();\n                        }\n                    }\n                }\n\n                if (matchingConnHandlers == MAX_CONNECTIONS_PER_REALM) {\n                    continue;\n                }\n\n                // No suitable ConnectionHandler found, create a new one\n                ConnectionHandler connHandler = connectionHandlerFactory.createConnectionHandler(url);\n\n                // Acquire lock if a lock was requested\n                if (acquireLock) {\n                    connHandler.acquireLock();\n                }\n\n                LOGGER.info(\"adding new ConnectionHandler {}, realm = {}\", connHandler, connHandler.getRealm());\n\n                // Insert new ConnectionHandler at first position as if it has more chances to be accessed again soon\n                connectionHandlers.add(0, connHandler);// insertElementAt(connHandler, 0);\n\n                // Start monitor thread if it is not currently running (if there previously was no registered ConnectionHandler)\n                if (monitorThread == null) {\n                    LOGGER.info(\"starting monitor thread\");\n                    monitorThread = new Thread(instance);\n                    monitorThread.start();\n                }\n\n                // Update last activity timestamp to now\n                connHandler.updateLastActivityTimestamp();\n\n                return connHandler;\n            }\n        }\n    }\n\n\n    /**\n     * Returns a list of registered ConnectionHandler instances. As the name of this method implies, the returned\n     * list is only a snapshot and will not reflect the modifications that are made after this method has been called.\n     * The List is a cloned one and thus can be safely modified.\n     *\n     * @return a list of registered ConnectionHandler instances\n     */\n    public static List<ConnectionHandler> getConnectionHandlersSnapshot() {\n        synchronized (connectionHandlers) {\n            connectionHandlers.removeIf(connectionHandler -> !connectionHandler.isConnected());\n            return new ArrayList<>(connectionHandlers);\n        }\n    }\n    \n//    /**\n//     * Returns the ConnectionHandler instance located at the given position in the list.\n//     */\n//    private static ConnectionHandler getConnectionHandlerAt(int i) {\n//        return connectionHandlers.get(i);\n//    }\n\n    /**\n     * Called by {@link ConnectionHandler#releaseLock()} to notify the <code>ConnectionHandler</code> that a\n     * <code>ConnectionHandler</code> has been released.\n     */\n    static void notifyConnectionHandlerLockReleased() {\n        synchronized (connectionHandlers) {\n            // Notify any thread waiting for a ConnectionHandler to be released\n            connectionHandlers.notify();\n        }\n    }\n\n    /**\n     * Monitors connections and periodically:\n     * <ul>\n     *   <li>keeps connections alive\n     *   <li>closes and removes connections that have expired\n     * </ul>\n     */\n    public void run() {\n\n        while (monitorThread != null) {        // Thread will be interrupted by CloseConnectionThread if there are no more ConnectionHandler\n            long now = System.currentTimeMillis();\n\n            synchronized(connectionHandlers) {      // Ensures that getConnectionHandler is not currently changing the list while we access it\n                for (Iterator<ConnectionHandler> it = connectionHandlers.iterator(); it.hasNext();) {\n                    final ConnectionHandler connHandler = it.next();\n\n                    synchronized(connHandler) {     // Ensures that no one is trying to acquire a lock on the connection while we access it\n                        // Do not touch ConnectionHandler if it is currently locked\n                        if (connHandler.isLocked()) {\n                            continue;\n                        }\n\n                        // Remove ConnectionHandler instance from the list of registered ConnectionHandler\n                        // if it is not connected\n                        if (!connHandler.isConnected()) {\n                            LOGGER.info(\"Removing unconnected ConnectionHandler {}\", connHandler);\n\n                            it.remove();\n                            // Notify any thread waiting for a ConnectionHandler to be released\n                            connectionHandlers.notify();\n\n                            continue;       // Skips close on inactivity and keep alive checks\n                        }\n\n                        long lastUsed = connHandler.getLastActivityTimestamp();\n\n                        // If time-to-live has been reached without any connection activity, remove ConnectionHandler\n                        // from the list of registered ConnectionHandler and close the connection in a separate thread\n                        long closePeriod = connHandler.getCloseOnInactivityPeriod();\n                        if (closePeriod != -1 && now - lastUsed > closePeriod*1000) {\n                            LOGGER.info(\"Removing timed-out ConnectionHandler {}\",connHandler);\n\n                            it.remove();\n                            // Notify any thread waiting for a ConnectionHandler to be released\n                            connectionHandlers.notify();\n\n                            // Close connection in a separate thread as it could lock this thread\n                            new CloseConnectionThread(connHandler).start();\n\n                            continue;       // Skips keep alive check\n                        }\n\n                        // If keep-alive period has been reached without any connection activity or a keep alive,\n                        // keep connection alive in a separate thread\n                        long keepAlivePeriod = connHandler.getKeepAlivePeriod();\n                        if (keepAlivePeriod != -1 && now-Math.max(lastUsed, connHandler.getLastKeepAliveTimestamp()) > keepAlivePeriod*1000) {\n                            // Update last keep alive timestamp to now\n                            connHandler.updateLastKeepAliveTimestamp();\n\n                            // Keep connection alive in a separate thread as it could lock this thread\n                            new KeepAliveConnectionThread(connHandler).start();\n                        }\n                    }\n                }\n\n                // Stop monitor thread if there are no more ConnectionHandler\n                if (connectionHandlers.isEmpty()) {\n                    LOGGER.info(\"No more ConnectionHandler, stopping monitor thread\");\n                    monitorThread = null;\n                }\n            }\n\n            // Sleep for MONITOR_SLEEP_PERIOD milliseconds, minus the processing time of this loop\n            try {\n                Thread.sleep(Math.max(0, MONITOR_SLEEP_PERIOD-(System.currentTimeMillis()-now)));\n            } catch (InterruptedException e) {\n                // Will loop again\n            }\n        }\n    }\n\n\n    /**\n     * Closes a specified ConnectionHandler's connection in a separate thread and removes the ConnectionHandler from\n     * the list of registered ConnectionHandler instances.\n     */\n    private static class CloseConnectionThread extends Thread {\n\n        private final ConnectionHandler connHandler;\n\n        private CloseConnectionThread(ConnectionHandler connHandler) {\n            this.connHandler = connHandler;\n        }\n\n        @Override\n        public void run() {\n            // Try to close connection, only if it is connected\n            if (connHandler.isConnected()) {\n                LOGGER.info(\"Closing connection held by {}\", connHandler);\n                connHandler.closeConnection();\n            }\n        }\n    }\n\n\n    /**\n     * Keeps alive a specified ConnectionHandler's connection in a separate thread. If the connection is not currently\n     * active, {@link com.mucommander.commons.file.connection.ConnectionHandler#keepAlive()} will not be called.\n     */\n    private static class KeepAliveConnectionThread extends Thread {\n\n        private final ConnectionHandler connHandler;\n\n        private KeepAliveConnectionThread(ConnectionHandler connHandler) {\n            this.connHandler = connHandler;\n        }\n\n        @Override\n        public void run() {\n            LOGGER.info(\"keeping connection alive: {}\", connHandler);\n\n            synchronized(connHandler) {\n                // Ensures that lock was not grabbed in the meantime\n                if (connHandler.isLocked()) {\n                    return;\n                }\n\n                // Keep alive connection, only if it is connected\n                if (connHandler.isConnected()) {\n                    connHandler.keepAlive();\n                }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/AbstractContainsFilter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.commons.file.filter;\n\n/**\n * This filter matches files whose string criterion values contain a specified string.\n *\n * @author Maxence Bernard\n */\npublic class AbstractContainsFilter extends AbstractStringCriterionFilter {\n\n    /** The string to look for in criterion values */\n    private final String s;\n\n    /**\n     * Creates a new <code>AbstractContainsFilter</code> using the specified generator and string, and operating in the\n     * specified mode.\n     *\n     * @param generator generates criterion values for files as requested\n     * @param s the string to compare criterion values against\n     * @param caseSensitive if true, this filter will be case-sensitive\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    public AbstractContainsFilter(CriterionValueGenerator<String> generator, String s, boolean caseSensitive, boolean inverted) {\n        super(generator, caseSensitive, inverted);\n        this.s = s;\n    }\n\n\n    @Override\n    public boolean accept(String value) {\n        if (isCaseSensitive()) {\n            return value.contains(s);\n        }\n        return value.toLowerCase().contains(s.toLowerCase());\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/AbstractCriterionFilter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.file.filter;\n\nimport com.mucommander.commons.file.AbstractFile;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * <code>AbstractCriterionFilter</code> implements the bulk of the {@link CriterionFilter} interface, matching\n * files based on the criteria values generated by a given {@link CriterionValueGenerator}. The only method left for\n * subclasses to implement is {@link #accept(Object)}.\n *\n * @author Maxence Bernard\n */\npublic abstract class AbstractCriterionFilter<C> extends AbstractFileFilter implements CriterionFilter<C> {\n\n    private final CriterionValueGenerator<C> generator;\n\n    /**\n     * Creates a new <code>AbstractCriterionFilter</code> using the specified {@link CriterionValueGenerator} and operating\n     * in non-inverted mode.\n     *\n     * @param generator generates criterion values for files as requested\n     */\n    public AbstractCriterionFilter(CriterionValueGenerator<C> generator) {\n        this(generator, false);\n    }\n\n    /**\n     * Creates a new <code>AbstractCriterionFilter</code> using the specified {@link CriterionValueGenerator} and operating\n     * in the specified mode.\n     *\n     * @param generator generates criterion values for files as requested\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    AbstractCriterionFilter(CriterionValueGenerator<C> generator, boolean inverted) {\n        super(inverted);\n\n        this.generator = generator;\n    }\n\n    /**\n     * Returns <code>true</code> if this filter matched the given value, according to the current {@link #isInverted()}\n     * mode:\n     * <ul>\n     *  <li>if this filter currently operates in normal (non-inverted) mode, this method will return the value of\n     * {@link #accept(Object)}</li>\n     *  <li>if this filter currently operates in inverted mode, this method will return the value of\n     * {@link #reject(Object)}</li>\n     * </ul>\n     *\n     * @param value the value to test\n     * @return true if this filter matched the given value, according to the current inverted mode\n     */\n    public boolean match(C value) {\n        return inverted ? reject(value) : accept(value);\n    }\n\n    /**\n     * Returns <code>true</code> if the given value was rejected by this filter, <code>false</code> if it was accepted.\n     *\n     * <p>The {@link #isInverted() inverted} mode has no effect on the values returned by this method.\n     *\n     * @param value the value to be tested\n     * @return true if the given value was rejected by this filter\n     */\n    public boolean reject(C value) {\n        return !accept(value);\n    }\n\n    /**\n     * Convenience method that filters out files that do not {@link #match(AbstractFile) match} this filter and\n     * returns a file array of matched <code>AbstractFile</code> instances.\n     *\n     * @param values values to be tested\n     * @return an array of accepted AbstractFile instances\n     */\n    public C[] filter(C[] values) {\n        List<C> filteredValuesList = new ArrayList<>();\n\n        for (C value : values) {\n            if (accept(value)) {\n                filteredValuesList.add(value);\n            }\n        }\n        @SuppressWarnings({\"unchecked\"})\n        C[] filteredValues = (C[]) new Object[filteredValuesList.size()];\n        return filteredValuesList.toArray(filteredValues);\n    }\n\n    /**\n     * Convenience method that returns <code>true</code> if all the values in the specified array were matched by\n     * {@link #match(Object)}, <code>false</code> if one of the values wasn't.\n     *\n     * @param values the values to be tested\n     * @return true if all the values in the specified array were accepted\n     */\n    public boolean match(C[] values) {\n        for (C value : values) {\n            if (!match(value)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Convenience method that returns <code>true</code> if all the values in the specified array were accepted by\n     * {@link #accept(Object)}, <code>false</code> if one of the values wasn't.\n     *\n     * @param values the values to be tested\n     * @return true if all the values in the specified array were accepted\n     */\n    public boolean accept(C[] values) {\n        for (C value : values) {\n            if (!accept(value)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Convenience method that returns <code>true</code> if all the values in the specified array were rejected by\n     * {@link #reject(Object)}, <code>false</code> if one of the values wasn't.\n     *\n     * @param values the values to be tested\n     * @return true if all the values in the specified array were rejected\n     */\n    public boolean reject(C[] values) {\n        for (C value : values) {\n            if (!reject(value)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n\n    @Override\n    public boolean accept(AbstractFile file) {\n        return accept(generator.getCriterionValue(file));\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/AbstractEndsWithFilter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.file.filter;\n\nimport com.mucommander.commons.util.StringUtils;\n\n/**\n * This filter matches files whose string criterion values end with a specified string.\n *\n * @author Maxence Bernard\n */\npublic class AbstractEndsWithFilter extends AbstractStringCriterionFilter {\n\n    /** The string to compare criterion values against */\n    private final String s;\n\n    /**\n     * Creates a new <code>AbstractEndsWithFilter</code> using the specified generator and string, and operating in the \n     * specified mode.\n     *\n     * @param generator generates criterion values for files as requested\n     * @param s the string to compare criterion values against\n     * @param caseSensitive if true, this filter will be case-sensitive\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    public AbstractEndsWithFilter(CriterionValueGenerator<String> generator, String s, boolean caseSensitive, boolean inverted) {\n        super(generator, caseSensitive, inverted);\n        this.s = s;\n    }\n\n\n    @Override\n    public boolean accept(String value) {\n        if (isCaseSensitive()) {\n            return value.endsWith(s);\n        }\n        return StringUtils.endsWithIgnoreCase(value, s);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/AbstractEqualsFilter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.file.filter;\n\n/**\n * This filter matches files whose string criterion values that are equal to a specified string.\n *\n * @author Maxence Bernard\n */\npublic class AbstractEqualsFilter extends AbstractStringCriterionFilter {\n\n    /** The string to compare criterion values against */\n    private String s;\n\n    /**\n     * Creates a new <code>AbstractEndsWithFilter</code> using the specified generator and string, and operating in the\n     * specified mode.\n     *\n     * @param generator generates criterion values for files as requested\n     * @param s the string to compare criterion values against\n     * @param caseSensitive if true, this filter will be case-sensitive\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    public AbstractEqualsFilter(CriterionValueGenerator<String> generator, String s, boolean caseSensitive, boolean inverted) {\n        super(generator, caseSensitive, inverted);\n        this.s = s;\n    }\n\n\n\n    @Override\n    public boolean accept(String value) {\n        if (isCaseSensitive()) {\n            return value.equals(s);\n        }\n        return value.equalsIgnoreCase(s);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/AbstractExtensionFilter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.file.filter;\n\nimport com.mucommander.commons.util.StringUtils;\n\n/**\n * This filter matches files whose criterion values are equal to one of several specified extensions.\n *\n * <p>The extension(s) may be any string, but when used in the traditional sense of a file extension (e.g. zip extension)\n * the '.' character must be included in the specified extension (e.g. \".zip\" must be used, not just \"zip\").\n *\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic class AbstractExtensionFilter extends AbstractStringCriterionFilter {\n\n    /** File extensions to match against criterion values */\n    private final char[][] extensions;\n\n    /**\n     * Creates a new <code>AbstractExtensionFilter</code> using the specified generator and string, and operating in the\n     * specified mode.\n     *\n     * @param generator generates criterion values for files as requested\n     * @param extensions the extensions to compare criterion values against\n     * @param caseSensitive if true, this filter will be case-sensitive\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    AbstractExtensionFilter(CriterionValueGenerator<String> generator, String[] extensions, boolean caseSensitive, boolean inverted) {\n        super(generator, caseSensitive, inverted);\n\n        this.extensions = new char[extensions.length][];\n        for (int i = 0; i < extensions.length; i++) {\n            this.extensions[i] = extensions[i].toCharArray();\n        }\n    }\n\n\n    @Override\n    public boolean accept(String value) {\n        return isCaseSensitive() ? containsCaseSensitive(value) : containsIgnoreCase(value);\n    }\n\n    private boolean containsIgnoreCase(String value) {\n        int len = value.length();\n        for (char[] extension : extensions) {\n            if (StringUtils.matchesIgnoreCase(value, extension, len)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    private boolean containsCaseSensitive(String value) {\n        int len = value.length();\n        // If case isn't important, a simple String.endsWith is enough.\n        for (char[] extension : extensions) {\n            if (StringUtils.matches(value, extension, len)) {\n                return true;\n            }\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/AbstractFileFilter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.file.filter;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.util.FileSet;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n\n/**\n * <code>AbstractFileFilter</code> implements the bulk of the {@link FileFilter} interface. The only method left for\n * subclasses to implement is {@link #accept(AbstractFile)}.\n *\n * @see AbstractFilenameFilter\n * @author Maxence Bernard\n */\npublic abstract class AbstractFileFilter implements FileFilter {\n\n    /** True if this filter should operate in inverted mode and invert matches */\n    protected boolean inverted;\n\n    /**\n     * Creates a new <code>AbstractFileFilter</code> operating in non-inverted mode.\n     */\n    public AbstractFileFilter() {\n        this(false);\n    }\n\n    /**\n     * Creates a new <code>AbstractFileFilter</code> operating in the specified mode.\n     *\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    public AbstractFileFilter(boolean inverted) {\n        setInverted(inverted);\n    }\n\n\n    ///////////////////////////////\n    // FileFilter implementation //\n    ///////////////////////////////\n\n    public boolean isInverted() {\n        return inverted;\n    }\n\n    public void setInverted(boolean inverted) {\n        this.inverted = inverted;\n    }\n\n    public boolean match(AbstractFile file) {\n        return inverted ? reject(file) : accept(file);\n    }\n\n    public boolean reject(AbstractFile file) {\n        return !accept(file);\n    }\n\n    public AbstractFile[] filter(AbstractFile[] files) {\n        List<AbstractFile> filteredFilesV = new ArrayList<>();\n        for (AbstractFile file : files) {\n            if (match(file)) {\n                filteredFilesV.add(file);\n            }\n        }\n\n        AbstractFile[] filteredFiles = new AbstractFile[filteredFilesV.size()];\n        filteredFilesV.toArray(filteredFiles);\n        return filteredFiles;\n    }\n\n    public void filter(FileSet files) {\n        for (int i = 0; i < files.size();) {\n            if (reject(files.elementAt(i))) {\n                files.removeElementAt(i);\n            } else {\n                i++;\n            }\n        }\n    }\n\n    public boolean match(AbstractFile[] files) {\n        for (AbstractFile file : files) {\n            if (!match(file)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    public boolean match(FileSet files) {\n        int nbFiles = files.size();\n        for (int i=0; i<nbFiles; i++) {\n            if (!match(files.elementAt(i))) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    public boolean accept(AbstractFile[] files) {\n        for (AbstractFile file : files) {\n            if (!accept(file)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    public boolean accept(FileSet files) {\n        int nbFiles = files.size();\n        for (int i=0; i<nbFiles; i++) {\n            if (!accept(files.elementAt(i))) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    public boolean reject(AbstractFile[] files) {\n        for (AbstractFile file : files) {\n            if (!reject(file)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    public boolean reject(FileSet files) {\n        int nbFiles = files.size();\n        for (int i = 0; i < nbFiles; i++)\n            if (!reject(files.elementAt(i))) {\n                return false;\n            }\n\n        return true;\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/AbstractFilenameFilter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.file.filter;\n\n\n/**\n * <code>AbstractFilenameFilter</code> implements the bulk of the {@link FilenameFilter} interface. The only method left\n * for subclasses to implement is {@link #accept(Object)}.\n *\n * @author Maxence Bernard\n */\npublic abstract class AbstractFilenameFilter extends AbstractStringCriterionFilter implements FilenameFilter {\n\n    /**\n     * Creates a new case-insensitive <code>AbstractFilenameFilter</code> operating in non-inverted mode.\n     */\n    public AbstractFilenameFilter() {\n        this(false, false);\n    }\n\n    /**\n     * Creates a new <code>AbstractFilenameFilter</code> operating in non-inverted mode.\n     *\n     * @param caseSensitive if true, this FilenameFilter will be case-sensitive\n     */\n    public AbstractFilenameFilter(boolean caseSensitive) {\n        this(caseSensitive, false);\n    }\n\n    /**\n     * Creates a new <code>AbstractFilenameFilter</code> operating in the specified mode.\n     *\n     * @param caseSensitive if true, this FilenameFilter will be case-sensitive\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    public AbstractFilenameFilter(boolean caseSensitive, boolean inverted) {\n        super(new FilenameGenerator(), caseSensitive, inverted);\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/AbstractPathFilter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.file.filter;\n\n\n/**\n * <code>AbstractPathFilter</code> implements the bulk of the {@link PathFilter} interface. The only method left\n * for subclasses to implement is {@link #accept(Object)}.\n *\n * @author Maxence Bernard\n */\npublic abstract class AbstractPathFilter extends AbstractStringCriterionFilter implements PathFilter {\n\n    /**\n     * Creates a new case-insensitive <code>AbstractPathFilter</code> operating in non-inverted mode.\n     */\n    public AbstractPathFilter() {\n        this(false, false);\n    }\n\n    /**\n     * Creates a new <code>AbstractPathFilter</code> operating in non-inverted mode.\n     *\n     * @param caseSensitive if true, this FilePathFilter will be case-sensitive\n     */\n    public AbstractPathFilter(boolean caseSensitive) {\n        this(caseSensitive, false);\n    }\n\n    /**\n     * Creates a new <code>AbstractPathFilter</code> operating in the specified mode.\n     *\n     * @param caseSensitive if true, this FilePathFilter will be case-sensitive\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    public AbstractPathFilter(boolean caseSensitive, boolean inverted) {\n        super(new PathGenerator(), caseSensitive, inverted);\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/AbstractRegexpFilter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.file.filter;\n\nimport java.util.regex.Pattern;\nimport java.util.regex.PatternSyntaxException;\n\n/**\n * This {@link AbstractStringCriterionFilter} accept or reject files whose string criterion values match a specific\n * regular expression.\n *\n * @author Nicolas Rinaudo, Maxence Bernard\n */\npublic abstract class AbstractRegexpFilter extends AbstractStringCriterionFilter {\n\n    /** Pattern against which criteria values will be compared. */\n    private final Pattern pattern;\n\n    /**\n     * Creates a new <code>AbstractRegexpFilter</code> matching the specified regexp and operating in the specified\n     * modes.\n     *\n     * @param generator generates criterion values for files as requested\n     * @param regexp regular expression that matches string values.\n     * @param caseSensitive whether the regular expression is case sensitive or not.\n     * @param inverted if true, this filter will operate in inverted mode.\n     * @throws PatternSyntaxException if the syntax of the regular expression is not correct.\n     */\n    public AbstractRegexpFilter(CriterionValueGenerator<String> generator, String regexp, boolean caseSensitive, boolean inverted) throws PatternSyntaxException {\n        super(generator, caseSensitive, inverted);\n\n        pattern = Pattern.compile(regexp, caseSensitive ? 0 : Pattern.CASE_INSENSITIVE);\n    }\n\n    /**\n     * Returns the regular expression used by this filter.\n     *\n     * @return the regular expression used by this filter.\n     */\n    public String getRegularExpression() {\n        return pattern.pattern();\n    }\n\n\n    /**\n     * Returns <code>true</code> if the specified value matches the filter's regular expression.\n     *\n     * @param value value to match against the filter's regular expression.\n     * @return <code>true</code> if the specified value matches the filter's regular expression,\n     * <code>false</code> otherwise.\n     */\n    @Override\n    public boolean accept(String value) {\n        return pattern.matcher(value).matches();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/AbstractStartsWithFilter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.file.filter;\n\nimport com.mucommander.commons.util.StringUtils;\n\n/**\n * This filter matches files whose criterion values start with a specified string.\n *\n * @author Maxence Bernard\n */\npublic class AbstractStartsWithFilter extends AbstractStringCriterionFilter {\n\n    /** The string to match against criterion values */\n    private final String s;\n\n    /**\n     * Creates a new <code>AbstractStartsWithFilter</code> using the specified generator and string, and operating in the\n     * specified mode.\n     *\n     * @param generator generates criterion values for files as requested\n     * @param s the string to compare criterion values against\n     * @param caseSensitive if true, this filter will be case-sensitive\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    public AbstractStartsWithFilter(CriterionValueGenerator<String> generator, String s, boolean caseSensitive, boolean inverted) {\n        super(generator, caseSensitive, inverted);\n        this.s = s;\n    }\n\n\n\n    @Override\n    public boolean accept(String value) {\n        if (isCaseSensitive()) {\n            return value.startsWith(s);\n        }\n        return StringUtils.startsWithIgnoreCase(value, s);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/AbstractStringCriterionFilter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.file.filter;\n\n\n/**\n * <code>AbstractCriterionFilter</code> implements the bulk of the {@link StringCriterionFilter} interface, matching\n * files based on the criteria values generated by a given {@link CriterionValueGenerator}. The only method left for\n * subclasses to implement is {@link #accept(Object)}.\n *\n * @see AbstractPathFilter\n * @see AbstractFilenameFilter\n * @author Maxence Bernard\n */\npublic abstract class AbstractStringCriterionFilter extends AbstractCriterionFilter<String> implements StringCriterionFilter {\n\n    /** True if this StringCriterionFilter is case-sensitive. */\n    private boolean caseSensitive;\n\n    /**\n     * Creates a new case-insensitive <code>AbstractStringCriterionFilter</code> operating in non-inverted mode.\n     *\n     * @param generator generates criterion values for files as requested\n     */\n    public AbstractStringCriterionFilter(CriterionValueGenerator<String> generator) {\n        this(generator, false, false);\n    }\n\n    /**\n     * Creates a new <code>AbstractStringCriterionFilter</code> operating in non-inverted mode.\n     *\n     * @param generator generates criterion values for files as requested\n     * @param caseSensitive if true, this FilePathFilter will be case-sensitive\n     */\n    public AbstractStringCriterionFilter(CriterionValueGenerator<String> generator, boolean caseSensitive) {\n        this(generator, caseSensitive, false);\n    }\n\n    /**\n     * Creates a new <code>AbstractStringCriterionFilter</code> that operates in the specified mode.\n     *\n     * @param generator generates criterion values for files as requested\n     * @param caseSensitive if true, this FilePathFilter will be case-sensitive\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    public AbstractStringCriterionFilter(CriterionValueGenerator<String> generator, boolean caseSensitive, boolean inverted) {\n        super(generator, inverted);\n        setCaseSensitive(caseSensitive);\n    }\n\n\n    @Override\n    public boolean isCaseSensitive() {\n        return caseSensitive;\n    }\n\n    @Override\n    public void setCaseSensitive(boolean caseSensitive) {\n        this.caseSensitive = caseSensitive;\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/AndFileFilter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.file.filter;\n\nimport com.mucommander.commons.file.AbstractFile;\n\n/**\n * AndFileFilter is a {@link ChainedFileFilter} that matches a file if all of its registered filters match it.\n *\n * @author Maxence Bernard\n */\npublic class AndFileFilter extends ChainedFileFilter {\n\n    /**\n     * Creates a new <code>AndFileFilter</code> operating in non-inverted mode and containing the specified filters,\n     * if any.\n     *\n     * @param filters filters to add to this chained filter.\n     */\n    public AndFileFilter(FileFilter... filters) {\n        this(false, filters);\n    }\n\n    /**\n     * Creates a new <code>AndFileFilter</code> operating in the specified mode and containing the specified filters,\n     * if any.\n     *\n     * @param inverted if true, this filter will operate in inverted mode.\n     * @param filters filters to add to this chained filter.\n     */\n    public AndFileFilter(boolean inverted, FileFilter... filters) {\n        super(inverted, filters);\n    }\n\n\n\n    /**\n     * Calls {@link #match(com.mucommander.commons.file.AbstractFile)} on each of the registered filters, and returns\n     * <code>true</code> if all of them matched the given file, <code>false</code> if one of them didn't.\n     *\n     * <p>If this {@link ChainedFileFilter} contains no filter, this method will always return <code>true</code>.\n     *\n     * @param file the file to test against the registered filters\n     * @return if the file was matched by all filters, false if one of them didn't \n     */\n    @Override\n    public boolean accept(AbstractFile file) {\n        for (FileFilter filter : filters) {\n            if (!filter.match(file)) {\n                return false;\n            }\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/AttributeFileFilter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.file.filter;\n\nimport com.mucommander.commons.file.AbstractFile;\n\n/**\n * <code>AttributeFileFilter</code> matches files which have a specific attribute set.\n * Here's a list of supported file attributes:\n * <ul>\n *   <li>{@link FileAttribute#DIRECTORY}</li>\n *   <li>{@link FileAttribute#FILE}</li>\n *   <li>{@link FileAttribute#BROWSABLE}</li>\n *   <li>{@link FileAttribute#ARCHIVE}</li>\n *   <li>{@link FileAttribute#SYMLINK}</li>\n *   <li>{@link FileAttribute#HIDDEN}</li>\n *   <li>{@link FileAttribute#ROOT}</li>\n * </ul>\n *\n * <p>Only one attribute can be matched at a time. To match several attributes, combine them using a\n * {@link com.mucommander.commons.file.filter.ChainedFileFilter}.\n *\n * @author Maxence Bernard\n */\npublic class AttributeFileFilter extends AbstractFileFilter {\n\n\tpublic enum FileAttribute {\n\t\t/** Tests if the file is a {@link com.mucommander.commons.file.AbstractFile#isDirectory() directory}. */\n\t\tDIRECTORY,\n\t\t/** Tests if the file is a regular file, i.e. not a directory. This is equivalent to negating {@link #DIRECTORY}. */\n\t\tFILE,\n\t\t/** Tests if the file is {@link com.mucommander.commons.file.AbstractFile#isBrowsable() browsable}. */\n\t\tBROWSABLE,\n\t\t/** Tests if the file is an {@link com.mucommander.commons.file.AbstractFile#isArchive() archive}. */\n\t\tARCHIVE,\n\t\t/** Tests if the file is a {@link com.mucommander.commons.file.AbstractFile#isSymlink() symlink}. */\n\t\tSYMLINK,\n\t\t/** Tests if the file is {@link com.mucommander.commons.file.AbstractFile#isHidden() hidden}. */\n\t\tHIDDEN,\n\t\t/** Tests if the file is a {@link com.mucommander.commons.file.AbstractFile#isRoot() root folder}. */\n\t\tROOT,\n\t\t/** Tests if the file is a {@link com.mucommander.commons.file.AbstractFile#isSystem() system file}. */\n\t\tSYSTEM\n\t}\n\n    /** The attribute to test files against */\n    private FileAttribute attribute;\n\n\n    /**\n     * Creates a new <code>AttributeFileFilter</code> matching files that have the specified attribute set and operating\n     * in non-inverted mode.\n     *\n     * @param attribute the attribute to test files against\n     */\n    public AttributeFileFilter(FileAttribute attribute) {\n        this(attribute, false);\n    }\n\n    /**\n     * Creates a new <code>AttributeFileFilter</code> matching files that have the specified attribute set and operating\n     * in the specified mode.\n     *\n     * @param attribute the attribute to test files against\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    public AttributeFileFilter(FileAttribute attribute, boolean inverted) {\n        super(inverted);\n        this.attribute = attribute;\n    }\n\n\n    /**\n     * Returns the attribute which files are tested against.\n     *\n     * @return the attribute which files are tested against.\n     */\n    public FileAttribute getAttribute() {\n        return attribute;\n    }\n\n    /**\n     * Sets the attribute which files are tested against.\n     *\n     * @param attribute the attribute which files are tested against.\n     */\n    public void setAttribute(FileAttribute attribute) {\n        this.attribute = attribute;\n    }\n\n\n    @Override\n    public boolean accept(AbstractFile file) {\n        switch(attribute) {\n            case DIRECTORY:\n                return file.isDirectory();\n            case FILE:\n                return !file.isDirectory();\n            case BROWSABLE:\n                return file.isBrowsable();\n            case ARCHIVE:\n                return file.isArchive();\n            case SYMLINK:\n                return file.isSymlink();\n            case HIDDEN:\n                return file.isHidden();\n            case ROOT:\n                return file.isRoot();\n\t\t    case SYSTEM:\n\t\t\t    return file.isSystem();\n\t\t    default:\n\t\t    \treturn true;\n        }\n    }\n}\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/ChainedFileFilter.java",
    "content": "package com.mucommander.commons.file.filter;\n\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\n\n\n/**\n * ChainedFileFilter combines one or several {@link FileFilter} to act as just one.\n *{@link #addFileFilter(FileFilter)} and {@link #removeFileFilter(FileFilter)} allow to add or remove a\n * <code>FileFilter</code>, {@link #getFileFilterIterator()} to iterate through all the registered filters.\n *\n * <p>The {@link AndFileFilter} and {@link OrFileFilter} implementations match files that respectively match all of\n * the registered filters, or any of them.\n *\n * @see AndFileFilter\n * @see OrFileFilter\n * @author Maxence Bernard\n */\npublic abstract class ChainedFileFilter extends AbstractFileFilter {\n\n    /** List of registered FileFilter */\n    protected List<FileFilter> filters = new ArrayList<>();\n\n    /**\n     * Creates a new <code>ChainedFileFilter</code> operating in non-inverted mode and containing the specified filters,\n     * if any.\n     *\n     * @param filters filters to add to this chained filter.\n     */\n    public ChainedFileFilter(FileFilter... filters) {\n        this(false, filters);\n    }\n\n    /**\n     * Creates a new <code>ChainedFileFilter</code> operating in the specified mode and containing the specified filters,\n     * if any.\n     *\n     * @param inverted if true, this filter will operate in inverted mode.\n     * @param filters filters to add to this chained filter.\n     */\n    public ChainedFileFilter(boolean inverted, FileFilter... filters) {\n        super(inverted);\n\n        for (FileFilter filter : filters)\n            addFileFilter(filter);\n    }\n\n    /**\n     * Adds a new {@link FileFilter} to the list of chained filters.\n     *\n     * @param filter the FileFilter to add\n     */\n    public void addFileFilter(FileFilter filter) {\n        filters.add(filter);\n    }\n\n    /**\n     * Removes a {@link FileFilter} from the list of chained filters. Does nothing if the given <code>FileFilter</code>\n     * is not contained by this <code>ChainedFileFilter</code>.\n     *\n     * @param filter the FileFilter to remove\n     */\n    public void removeFileFilter(FileFilter filter) {\n        filters.remove(filter);\n    }\n\n    /**\n     * Returns an <code>Iterator</code> that traverses all the registered filters.\n     *\n     * @return an <code>Iterator</code> that traverses all the registered filters. \n     */\n    public Iterator<FileFilter> getFileFilterIterator() {\n        return filters.iterator();\n    }\n\n    /**\n     * Returns <code>true</code> if this chained filter doesn't contain any file filter.\n     *\n     * @return <code>true</code> if this chained filter doesn't contain any file filter.\n     */\n    public boolean isEmpty() {\n        return filters.isEmpty();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/ContainsFilenameFilter.java",
    "content": "package com.mucommander.commons.file.filter;\n\n/**\n * This {@link FilenameFilter} matches filenames that contain a specified string that can be located anywhere in the\n * filename.\n *\n * @author Maxence Bernard\n */\npublic class ContainsFilenameFilter extends AbstractContainsFilter implements FilenameFilter {\n\n    /**\n     * Creates a new case-insensitive <code>ContainsFilenameFilter</code> operating in non-inverted mode.\n     *\n     * @param s the string to compare filenames against\n     */\n    public ContainsFilenameFilter(String s) {\n        this(s, false, false);\n    }\n\n    /**\n     * Creates a new <code>ContainsFilenameFilter</code> operating in non-inverted mode.\n     *\n     * @param s the string to compare filenames against\n     * @param caseSensitive if true, this FilenameFilter will be case-sensitive\n     */\n    public ContainsFilenameFilter(String s, boolean caseSensitive) {\n        this(s, caseSensitive, false);\n    }\n\n    /**\n     * Creates a new <code>ContainsFilenameFilter</code> operating in the specified mode.\n     *\n     * @param s the string to compare filenames against\n     * @param caseSensitive if true, this FilenameFilter will be case-sensitive\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    public ContainsFilenameFilter(String s, boolean caseSensitive, boolean inverted) {\n        super(new FilenameGenerator(), s, caseSensitive, inverted);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/ContainsPathFilter.java",
    "content": "package com.mucommander.commons.file.filter;\n\n/**\n * This {@link PathFilter} matches paths that contain a specified string that can be located anywhere in the\n * path.\n *\n * @author Maxence Bernard\n */\npublic class ContainsPathFilter extends AbstractContainsFilter implements PathFilter {\n\n    /**\n     * Creates a new case-insensitive <code>ContainsPathFilter</code> operating in non-inverted mode.\n     *\n     * @param s the string to compare paths against\n     */\n    public ContainsPathFilter(String s) {\n        this(s, false, false);\n    }\n\n    /**\n     * Creates a new <code>ContainsPathFilter</code> operating in non-inverted mode.\n     *\n     * @param s the string to compare paths against\n     * @param caseSensitive if true, this PathFilter will be case-sensitive\n     */\n    public ContainsPathFilter(String s, boolean caseSensitive) {\n        this(s, caseSensitive, false);\n    }\n\n    /**\n     * Creates a new <code>ContainsPathFilter</code> operating in the specified mode.\n     *\n     * @param s the string to compare paths against\n     * @param caseSensitive if true, this PathFilter will be case-sensitive\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    public ContainsPathFilter(String s, boolean caseSensitive, boolean inverted) {\n        super(new PathGenerator(), s, caseSensitive, inverted);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/CriterionFilter.java",
    "content": "package com.mucommander.commons.file.filter;\n\nimport com.mucommander.commons.file.AbstractFile;\n\n/**\n * <code>CriterionFilter</code> is a {@link FileFilter} that operates on a file criterion. It can be used to match\n * paths without having to deal with {@link AbstractFile} instances. By extending {@link FileFilter}, this class can be\n * used everywhere a <code>FileFilter</code> is accepted.\n *\n * <p>Several convenience methods are provided to operate this filter on a set of criteria values, and filter out\n * values that are rejected by this filter.\n *\n * @see AbstractPathFilter\n * @author Maxence Bernard\n */\npublic interface CriterionFilter<C> extends FileFilter {\n\n    /**\n     * Returns <code>true</code> if this filter matched the given value, according to the current {@link #isInverted()}\n     * mode:\n     * <ul>\n     *  <li>if this filter currently operates in normal (non-inverted) mode, this method will return the value of {@link #accept(Object)}</li>\n     *  <li>if this filter currently operates in inverted mode, this method will return the value of {@link #reject(Object)}</li>\n     * </ul>\n     *\n     * @param value the value to test\n     * @return true if this filter matched the given value, according to the current inverted mode\n     */\n    boolean match(C value);\n\n    /**\n     * Returns <code>true</code> if the given value was rejected by this filter, <code>false</code> if it was accepted.\n     *\n     * <p>The {@link #isInverted() inverted} mode has no effect on the values returned by this method.\n     *\n     * @param value the value to be tested\n     * @return true if the given value was rejected by this filter\n     */\n    boolean reject(C value);\n\n    /**\n     * Convenience method that filters out files that do not {@link #match(AbstractFile) match} this filter and\n     * returns a file array of matched <code>AbstractFile</code> instances.\n     *\n     * @param value values to be tested\n     * @return an array of accepted AbstractFile instances\n     */\n    C[] filter(C[] value);\n\n    /**\n     * Convenience method that returns <code>true</code> if all the values in the specified array were matched by\n     * {@link #match(Object)}, <code>false</code> if one of the values wasn't.\n     *\n     * @param value the values to be tested\n     * @return true if all the values in the specified array were accepted\n     */\n    boolean match(C[] value);\n\n    /**\n     * Convenience method that returns <code>true</code> if all the values in the specified array were accepted by\n     * {@link #accept(Object)}, <code>false</code> if one of the values wasn't.\n     *\n     * @param value the values to be tested\n     * @return true if all the values in the specified array were accepted\n     */\n    boolean accept(C[] value);\n\n    /**\n     * Convenience method that returns <code>true</code> if all the values in the specified array were rejected by\n     * {@link #reject(Object)}, <code>false</code> if one of the values wasn't.\n     *\n     * @param value the values to be tested\n     * @return true if all the values in the specified array were rejected\n     */\n    boolean reject(C[] value);\n\n    /**\n     * Returns <code>true</code> if the given value was accepted by this filter, <code>false</code> if it was rejected.\n     *\n     * @param value the value to test\n     * @return true if the given value was accepted by this filter, false if it was rejected\n     */\n    boolean accept(C value);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/CriterionValueGenerator.java",
    "content": "package com.mucommander.commons.file.filter;\n\nimport com.mucommander.commons.file.AbstractFile;\n\n/**\n * This interface defines a {@link #getCriterionValue(AbstractFile)} method that generates a criterion value for\n * a specified {@link AbstractFile}. It is used by {@link CriterionFilter} to match files based on their criteria\n * values.\n *\n * @see CriterionFilter\n * @author Maxence Bernard\n */\npublic interface CriterionValueGenerator<C> {\n\n    C getCriterionValue(AbstractFile file);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/EmptyFileFilter.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/trolcommander\n * Copyright (C) 2014-2018 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.filter;\n\nimport com.mucommander.commons.file.AbstractFile;\n\npublic class EmptyFileFilter extends AbstractFileFilter {\n    @Override\n    public boolean accept(AbstractFile file) {\n        return file.getSize() == 0;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/EndsWithFilenameFilter.java",
    "content": "package com.mucommander.commons.file.filter;\n\n/**\n * This {@link FilenameFilter} matches filenames that end with a specified string.\n *\n * @author Maxence Bernard\n */\npublic class EndsWithFilenameFilter extends AbstractEndsWithFilter implements FilenameFilter {\n\n    /**\n     * Creates a new case-insensitive <code>EndsWithFilenameFilter</code> operating in non-inverted mode.\n     *\n     * @param s the string to compare filenames against\n     */\n    public EndsWithFilenameFilter(String s) {\n        this(s, false, false);\n    }\n\n    /**\n     * Creates a new <code>EndsWithFilenameFilter</code> operating in non-inverted mode.\n     *\n     * @param s the string to compare filenames against\n     * @param caseSensitive if true, this FilenameFilter will be case-sensitive\n     */\n    public EndsWithFilenameFilter(String s, boolean caseSensitive) {\n        this(s, caseSensitive, false);\n    }\n\n    /**\n     * Creates a new <code>EndsWithFilenameFilter</code> operating in the specified mode.\n     *\n     * @param s the string to compare filenames against\n     * @param caseSensitive if true, this FilenameFilter will be case-sensitive\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    public EndsWithFilenameFilter(String s, boolean caseSensitive, boolean inverted) {\n        super(new FilenameGenerator(), s, caseSensitive, inverted);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/EndsWithPathFilter.java",
    "content": "package com.mucommander.commons.file.filter;\n\n/**\n * This {@link PathFilter} matches paths that end with a specified string.\n *\n * @author Maxence Bernard\n */\npublic class EndsWithPathFilter extends AbstractEndsWithFilter implements PathFilter {\n\n    /**\n     * Creates a new case-insensitive <code>EndsWithPathFilter</code> operating in non-inverted mode.\n     *\n     * @param s the string to compare paths against\n     */\n    public EndsWithPathFilter(String s) {\n        this(s, false, false);\n    }\n\n    /**\n     * Creates a new <code>EndsWithPathFilter</code> operating in non-inverted mode.\n     *\n     * @param s the string to compare paths against\n     * @param caseSensitive if true, this PathFilter will be case-sensitive\n     */\n    public EndsWithPathFilter(String s, boolean caseSensitive) {\n        this(s, caseSensitive, false);\n    }\n\n    /**\n     * Creates a new <code>EndsWithPathFilter</code> operating in the specified mode.\n     *\n     * @param s the string to compare paths against\n     * @param caseSensitive if true, this PathFilter will be case-sensitive\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    public EndsWithPathFilter(String s, boolean caseSensitive, boolean inverted) {\n        super(new PathGenerator(), s, caseSensitive, inverted);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/EqualsFilenameFilter.java",
    "content": "package com.mucommander.commons.file.filter;\n\n/**\n * This {@link FilenameFilter} matches filenames that are equal to a specified string.\n *\n * @author Maxence Bernard\n */\npublic class EqualsFilenameFilter extends AbstractEqualsFilter implements FilenameFilter {\n\n    /**\n     * Creates a new case-insensitive <code>EqualsFilenameFilter</code> operating in non-inverted mode.\n     *\n     * @param s the string to compare filenames against\n     */\n    public EqualsFilenameFilter(String s) {\n        this(s, false, false);\n    }\n\n    /**\n     * Creates a new <code>EqualsFilenameFilter</code> operating in non-inverted mode.\n     *\n     * @param s the string to compare filenames against\n     * @param caseSensitive if true, this FilenameFilter will be case-sensitive\n     */\n    public EqualsFilenameFilter(String s, boolean caseSensitive) {\n        this(s, caseSensitive, false);\n    }\n\n    /**\n     * Creates a new <code>EqualsFilenameFilter</code> operating in the specified mode.\n     *\n     * @param s the string to compare filenames against\n     * @param caseSensitive if true, this FilenameFilter will be case-sensitive\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    public EqualsFilenameFilter(String s, boolean caseSensitive, boolean inverted) {\n        super(new FilenameGenerator(), s, caseSensitive, inverted);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/EqualsPathFilter.java",
    "content": "package com.mucommander.commons.file.filter;\n\n/**\n * This {@link PathFilter} matches paths that are equal to a specified string.\n *\n * @author Maxence Bernard\n */\npublic class EqualsPathFilter extends AbstractEqualsFilter implements PathFilter {\n\n    /**\n     * Creates a new case-insensitive <code>EqualsPathFilter</code> operating in non-inverted mode.\n     *\n     * @param s the string to compare paths against\n     */\n    public EqualsPathFilter(String s) {\n        this(s, false, false);\n    }\n\n    /**\n     * Creates a new <code>EqualsPathFilter</code> operating in non-inverted mode.\n     *\n     * @param s the string to compare paths against\n     * @param caseSensitive if true, this PathFilter will be case-sensitive\n     */\n    public EqualsPathFilter(String s, boolean caseSensitive) {\n        this(s, caseSensitive, false);\n    }\n\n    /**\n     * Creates a new <code>EqualsPathFilter</code> operating in the specified mode.\n     *\n     * @param s the string to compare paths against\n     * @param caseSensitive if true, this PathFilter will be case-sensitive\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    public EqualsPathFilter(String s, boolean caseSensitive, boolean inverted) {\n        super(new PathGenerator(), s, caseSensitive, inverted);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/ExtensionFilenameFilter.java",
    "content": "package com.mucommander.commons.file.filter;\n\n/**\n * This {@link FilenameFilter} matches files whose path end with one of several specified extensions.\n *\n * <p>The extension(s) may be any string, but when used in the traditional sense of a file extension (e.g. zip extension)\n * the '.' character must be included in the specified extension (e.g. \".zip\" must be used, not just \"zip\").\n * \n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic class ExtensionFilenameFilter extends AbstractExtensionFilter implements FilenameFilter {\n\n    /**\n     * Creates a case-insensitive <code>ExtensionFilenameFilter</code> matching filenames ending with the specified\n     * extension and operating in non-inverted mode.\n     *\n     * @param extension the extension to match\n     */\n    public ExtensionFilenameFilter(String extension) {\n        this(extension, false, false);\n    }\n\n    /**\n     * Creates a <code>ExtensionFilenameFilter</code> matching filenames ending with the specified extension\n     * and operating in the specified modes.\n     *\n     * @param extension the extension to match\n     * @param caseSensitive if true, this FilenameFilter will be case-sensitive\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    public ExtensionFilenameFilter(String extension, boolean caseSensitive, boolean inverted) {\n        this(new String[]{extension}, caseSensitive, inverted);\n    }\n\n    /**\n     * Creates a case-insensitive <code>ExtensionFilenameFilter</code> matching filenames ending with one of the\n     * specified extensions and operating in the specified mode.\n     *\n     * @param ext the extensions to match\n     */\n    public ExtensionFilenameFilter(String[] ext) {\n        this(ext, false, false);\n    }\n\n    /**\n     * Creates a new <code>ExtensionFilenameFilter</code> matching filenames ending with one of the specified\n     * extensions and operating in the specified modes.\n     *\n     * @param ext the extensions to match\n     * @param caseSensitive if true, this FilenameFilter will be case-sensitive\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    public ExtensionFilenameFilter(String[] ext, boolean caseSensitive, boolean inverted) {\n        super(new FilenameGenerator(), ext, caseSensitive, inverted);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/ExtensionPathFilter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.file.filter;\n\n/**\n * This {@link FilenameFilter} matches files whose path end with one of several specified extensions.\n *\n * <p>The extension(s) may be any string, but when used in the traditional sense of a file extension (e.g. zip extension)\n * the '.' character must be included in the specified extension (e.g. \".zip\" must be used, not just \"zip\").\n * \n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic class ExtensionPathFilter extends AbstractExtensionFilter implements PathFilter {\n\n    /**\n     * Creates a case-insensitive <code>ExtensionPathFilter</code> matching paths ending with the specified\n     * extension and operating in non-inverted mode.\n     *\n     * @param extension the extension to match\n     */\n    public ExtensionPathFilter(String extension) {\n        this(extension, false, false);\n    }\n\n    /**\n     * Creates a <code>ExtensionPathFilter</code> matching paths ending with the specified extension\n     * and operating in the specified modes.\n     *\n     * @param extension the extension to match\n     * @param caseSensitive if true, this PathFilter will be case-sensitive\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    private ExtensionPathFilter(String extension, boolean caseSensitive, boolean inverted) {\n        this(new String[]{extension}, caseSensitive, inverted);\n    }\n\n    /**\n     * Creates a case-insensitive <code>ExtensionPathFilter</code> matching paths ending with one of the\n     * specified extensions and operating in the specified mode.\n     *\n     * @param ext the extensions to match\n     */\n    public ExtensionPathFilter(String[] ext) {\n        this(ext, false, false);\n    }\n\n    /**\n     * Creates a new <code>ExtensionPathFilter</code> matching paths ending with one of the specified\n     * extensions and operating in the specified modes.\n     *\n     * @param ext the extensions to match\n     * @param caseSensitive if true, this PathFilter will be case-sensitive\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    public ExtensionPathFilter(String[] ext, boolean caseSensitive, boolean inverted) {\n        super(new PathGenerator(), ext, caseSensitive, inverted);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/FileFilter.java",
    "content": "package com.mucommander.commons.file.filter;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.util.FileSet;\n\n/**\n * A <code>FileFilter</code> matches files that meet certain criteria. It can operate in two opposite modes: inverted\n * and non-inverted. By default, a <code>FileFilter</code> operates in non-inverted mode where\n * {@link #match(AbstractFile)} returns the value of {@link #accept(AbstractFile)}. On the contrary, when operating in\n * inverted mode, {@link #match(AbstractFile)} returns the value of {@link #reject(AbstractFile)}. It is important to\n * understand that {@link #accept(AbstractFile)} and {@link #reject(AbstractFile)} are not affected by the inverted\n * mode in which a filter operates.\n *\n * <p>Several convenience methods are provided to operate this filter on a set of files, and filter out files that\n * do not match this filter.\n *\n * <p>A <code>FileFilter</code> instance can be passed to {@link AbstractFile#ls(FileFilter)} to filter out some of the\n * the files contained by a folder.\n *\n * @see AbstractFileFilter\n * @see FilenameFilter\n * @see com.mucommander.commons.file.AbstractFile#ls(FileFilter)\n * @author Maxence Bernard\n */\npublic interface FileFilter {\n\n    /**\n     * Return <code>true</code> if this filter operates in normal mode, <code>false</code> if in inverted mode.\n     *\n     * @return true if this filter operates in normal mode, false if in inverted mode\n     */\n    boolean isInverted();\n\n    /**\n     * Sets the mode in which {@link #match(com.mucommander.commons.file.AbstractFile)} operates. If <code>true</code>, this\n     * filter will operate in inverted mode: files that would be accepted by {@link #match(com.mucommander.commons.file.AbstractFile)}\n     * in normal (non-inverted) mode will be rejected, and vice-versa.<br>\n     * The inverted mode has no effect on the values returned by {@link #accept(com.mucommander.commons.file.AbstractFile)} and\n     * {@link #reject(com.mucommander.commons.file.AbstractFile)}.\n     *\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    void setInverted(boolean inverted);\n\n    /**\n     * Returns <code>true</code> if this filter matched the given file, according to the current {@link #isInverted()}\n     * mode:\n     * <ul>\n     *  <li>if this filter currently operates in normal (non-inverted) mode, this method will return the value of {@link #accept(com.mucommander.commons.file.AbstractFile)}</li>\n     *  <li>if this filter currently operates in inverted mode, this method will return the value of {@link #reject(com.mucommander.commons.file.AbstractFile)}</li>\n     * </ul>\n     *\n     * @param file the file to test\n     * @return true if this filter matched the given file, according to the current inverted mode\n     */\n    boolean match(AbstractFile file);\n\n    /**\n     * Returns <code>true</code> if the given file was rejected by this filter, <code>false</code> if it was accepted.\n     *\n     * <p>The {@link #isInverted() inverted} mode has no effect on the values returned by this method.\n     *\n     * @param file the file to test\n     * @return true if the given file was rejected by this FileFilter\n     */\n    boolean reject(AbstractFile file);\n\n    /**\n     * Convenience method that filters out files that do not {@link #match(AbstractFile) match} this filter and\n     * returns a file array of matched <code>AbstractFile</code> instances.\n     *\n     * @param files files to be tested against {@link #match(com.mucommander.commons.file.AbstractFile)}\n     * @return a file array of files that were matched by this filter\n     */\n    AbstractFile[] filter(AbstractFile files[]);\n\n    /**\n     * Convenience method that filters out files that do not {@link #match(AbstractFile) match} this filter\n     * and removes them from the given {@link FileSet}.\n     *\n     * @param files files to be tested against {@link #match(com.mucommander.commons.file.AbstractFile)}\n     */\n    void filter(FileSet files);\n\n    /**\n     * Convenience method that returns <code>true</code> if all the files contained in the specified file array\n     * were matched by {@link #match(AbstractFile)}, <code>false</code> if one of the files wasn't.\n     *\n     * @param files the files to test against this FileFilter\n     * @return true if all the files contained in the specified file array were matched by this filter\n     */\n    boolean match(AbstractFile files[]);\n\n    /**\n     * Convenience method that returns <code>true</code> if all the files contained in the specified {@link FileSet}\n     * were matched by {@link #match(AbstractFile)}, <code>false</code> if one of the files wasn't.\n     *\n     * @param files the files to test against this FileFilter\n     * @return true if all the files contained in the specified {@link FileSet} were matched by this filter\n     */\n    boolean match(FileSet files);\n\n    /**\n     * Convenience method that returns <code>true</code> if all the files contained in the specified file array\n     * were accepted by {@link #accept(AbstractFile)}, <code>false</code> if one of the files wasn't.\n     *\n     * @param files the files to test against this FileFilter\n     * @return true if all the files contained in the specified file array were accepted by this filter\n     */\n    boolean accept(AbstractFile files[]);\n\n    /**\n     * Convenience method that returns <code>true</code> if all the files contained in the specified {@link FileSet}\n     * were accepted by {@link #accept(AbstractFile)}, <code>false</code> if one of the files wasn't.\n     *\n     * @param files the files to test against this FileFilter\n     * @return true if all the files contained in the specified {@link FileSet} were accepted by this filter\n     */\n    boolean accept(FileSet files);\n\n    /**\n     * Convenience method that returns <code>true</code> if all the files contained in the specified file array\n     * were rejected by {@link #reject(AbstractFile)}, <code>false</code> if one of the files wasn't.\n     *\n     * @param files the files to test against this FileFilter\n     * @return true if all the files contained in the specified file array were rejected by this filter\n     */\n    boolean reject(AbstractFile files[]);\n\n    /**\n     * Convenience method that returns <code>true</code> if all the files contained in the specified {@link FileSet}\n     * were rejected by {@link #reject(AbstractFile)}, <code>false</code> if one of the files wasn't.\n     *\n     * @param files the files to test against this FileFilter\n     * @return true if all the files contained in the specified {@link FileSet} were rejected by this filter\n     */\n    boolean reject(FileSet files);\n\n    /**\n     * Returns <code>true</code> if the given file was accepted by this filter, <code>false</code> if it was rejected.\n     *\n     * <p>The {@link #isInverted() inverted} mode has no effect on the values returned by this method.\n     *\n     * @param file the file to test\n     * @return true if the given file was accepted by this FileFilter\n     */\n    boolean accept(AbstractFile file);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/FileOperationFilter.java",
    "content": "package com.mucommander.commons.file.filter;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileOperation;\n\n/**\n * <code>OperationFileFilter</code> matches files which support a specified {@link FileOperation file operation}.\n *\n * <p>Only one file operation can be matched at a time. To match several file operations, combine them using a\n * {@link com.mucommander.commons.file.filter.ChainedFileFilter}.\n *\n * @see FileOperation\n * @author Maxence Bernard\n */\npublic class FileOperationFilter extends AbstractFileFilter {\n\n    /** The file operation to match */\n    private FileOperation op;\n\n\n    public FileOperationFilter(FileOperation op) {\n        this(op, false);\n    }\n\n    public FileOperationFilter(FileOperation op, boolean inverted) {\n        super(inverted);\n        setFileOperation(op);\n    }\n\n    /**\n     * Returns the file operation this filter matches.\n     *\n     * @return the file operation this filter matches.\n     */\n    public FileOperation getFileOperation() {\n        return op;\n    }\n\n    /**\n     * Sets the file operation this filter matches, replacing the previous operation.\n     *\n     * @param op the file operation this filter matches.\n     */\n    public void setFileOperation(FileOperation op) {\n        this.op = op;\n    }\n\n\n    @Override\n    public boolean accept(AbstractFile file) {\n        return file.isFileOperationSupported(op);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/FilenameFilter.java",
    "content": "package com.mucommander.commons.file.filter;\n\nimport com.mucommander.commons.file.AbstractFile;\n\n/**\n * <code>FilenameFilter</code> is a {@link FileFilter} that operates on filenames.\n *\n * <p>A <code>FilenameFilter</code> can be passed to {@link AbstractFile#ls(FilenameFilter)} to filter out some of the\n * files contained by a folder without creating the associated <code>AbstractFile</code> instances.\n *\n * @see AbstractFilenameFilter\n * @author Maxence Bernard\n */\npublic interface FilenameFilter extends StringCriterionFilter {\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/FilenameGenerator.java",
    "content": "package com.mucommander.commons.file.filter;\n\nimport com.mucommander.commons.file.AbstractFile;\n\n/**\n * This interface specializes {@link CriterionValueGenerator} to have {@link #getCriterionValue(AbstractFile)} return\n * the filename of the specified file.\n *\n * @author Maxence Bernard\n */\npublic class FilenameGenerator implements CriterionValueGenerator<String> {\n\n\n    public String getCriterionValue(AbstractFile file) {\n        return file.getName();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/MountedDriveFilter.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.filter;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport com.mucommander.commons.runtime.OsFamily;\n\n/**\n * @author Oleg Trifonov\n * Created on 26/01/16.\n */\npublic class MountedDriveFilter extends AbstractFileFilter {\n    @Override\n    public boolean accept(AbstractFile file) {\n        if (file == null || !OsFamily.MAC_OS_X.isCurrent()) {\n            return false;\n        }\n        for (AbstractFile f : LocalFile.getVolumes() ) {\n            if (file.equals(f)) {\n                return !file.isSymlink();\n            }\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/OrFileFilter.java",
    "content": "package com.mucommander.commons.file.filter;\n\nimport com.mucommander.commons.file.AbstractFile;\n\n/**\n * <code>OrFileFilter</code> is a {@link ChainedFileFilter} that matches a file if one of its registered filters \n * matches it.\n *\n * @author Maxence Bernard\n */\npublic class OrFileFilter extends ChainedFileFilter {\n\n    /**\n     * Creates a new <code>OrFileFilter</code> operating in non-inverted mode and containing the specified filters,\n     * if any.\n     *\n     * @param filters filters to add to this chained filter.\n     */\n    public OrFileFilter(FileFilter... filters) {\n        this(false, filters);\n    }\n\n    /**\n     * Creates a new <code>OrFileFilter</code> operating in the specified mode and containing the specified filters,\n     * if any.\n     *\n     * @param inverted if true, this filter will operate in inverted mode.\n     * @param filters filters to add to this chained filter.\n     */\n    public OrFileFilter(boolean inverted, FileFilter... filters) {\n        super(inverted, filters);\n    }\n\n\n\n    /**\n     * Calls {@link #match(com.mucommander.commons.file.AbstractFile)} on each of the registered filters, and returns\n     * <code>true</code> if one of them matched the given file, <code>false</code> if none of them did.\n     *\n     * <p>If this {@link ChainedFileFilter} contains no filter, this method will always return <code>true</code>.\n     *\n     * @param file the file to test against the registered filters\n     * @return if the file was matched by one filter, false if none of them did\n     */\n    @Override\n    public boolean accept(AbstractFile file) {\n        for (FileFilter filter : filters) {\n            if (filter.match(file)) {\n                return true;\n            }\n        }\n        return filters.isEmpty();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/PassThroughFileFilter.java",
    "content": "package com.mucommander.commons.file.filter;\n\nimport com.mucommander.commons.file.AbstractFile;\n\n/**\n * <code>PassThroughFileFilter</code> is a filter that {@link #accept(com.mucommander.commons.file.AbstractFile) accepts} all\n * files. Depending on the {@link #isInverted() inverted} mode, this filter will match all files or no file at all.\n *\n * @author Maxence Bernard\n */\npublic class PassThroughFileFilter extends AbstractFileFilter {\n\n    /**\n     * Creates a new <code>PassThroughFileFilter</code> operating in non-inverted mode.\n     */\n    public PassThroughFileFilter() {\n        this(false);\n    }\n\n    /**\n     * Creates a new <code>PassThroughFileFilter</code> operating in the specified mode.\n     *\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    public PassThroughFileFilter(boolean inverted) {\n        super(inverted);\n    }\n\n\n    @Override\n    public boolean accept(AbstractFile file) {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/PathFilter.java",
    "content": "package com.mucommander.commons.file.filter;\n\n/**\n * <code>PathFilter</code> is a {@link FileFilter} that operates on absolute file paths.\n *\n * @see AbstractPathFilter\n * @author Maxence Bernard\n */\npublic interface PathFilter extends StringCriterionFilter {\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/PathGenerator.java",
    "content": "package com.mucommander.commons.file.filter;\n\nimport com.mucommander.commons.file.AbstractFile;\n\n/**\n * This interface specializes {@link CriterionValueGenerator} to have {@link #getCriterionValue(AbstractFile)} return\n * the absolute path of the specified file.\n *\n * @author Maxence Bernard\n */\npublic class PathGenerator implements CriterionValueGenerator<String> {\n\n    @Override\n    public String getCriterionValue(AbstractFile file) {\n        return file.getPath();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/RegexpFilenameFilter.java",
    "content": "package com.mucommander.commons.file.filter;\n\nimport java.util.regex.PatternSyntaxException;\n\n/**\n * This {@link FilenameFilter} that accepts or rejects files whose filename match a specific regular expression.\n *\n * @author Maxence Bernard\n */\npublic class RegexpFilenameFilter extends AbstractRegexpFilter implements FilenameFilter {\n\n    /**\n     * Creates a new <code>RegexpFilenameFilter</code> matching the specified regexp and operating in non-inverted\n     * mode.\n     *\n     * @param regexp regular expression that matches string values.\n     * @param caseSensitive whether the regular expression is case-sensitive or not.\n     * @throws PatternSyntaxException if the syntax of the regular expression is not correct.\n     */\n    public RegexpFilenameFilter(String regexp, boolean caseSensitive) throws PatternSyntaxException {\n        super(new FilenameGenerator(), regexp, caseSensitive, false);\n    }\n\n    /**\n     * Creates a new <code>RegexpFilenameFilter</code> matching the specified regexp and operating in the specified\n     * modes.\n     *\n     * @param regexp regular expression that matches string values.\n     * @param caseSensitive whether the regular expression is case-sensitive or not.\n     * @param inverted if true, this filter will operate in inverted mode.\n     * @throws PatternSyntaxException if the syntax of the regular expression is not correct.\n     */\n    public RegexpFilenameFilter(String regexp, boolean caseSensitive, boolean inverted) throws PatternSyntaxException {\n        super(new FilenameGenerator(), regexp, caseSensitive, inverted);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/RegexpPathFilter.java",
    "content": "package com.mucommander.commons.file.filter;\n\nimport java.util.regex.PatternSyntaxException;\n\n/**\n * This {@link PathFilter} that accepts or rejects files whose path match a specific regular expression.\n *\n * @author Maxence Bernard\n */\npublic class RegexpPathFilter extends AbstractRegexpFilter implements PathFilter {\n\n    /**\n     * Creates a new <code>RegexpPathFilter</code> matching the specified regexp and operating in non-inverted\n     * mode.\n     *\n     * @param regexp regular expression that matches string values.\n     * @param caseSensitive whether the regular expression is case-sensitive or not.\n     * @throws PatternSyntaxException if the syntax of the regular expression is not correct.\n     */\n    public RegexpPathFilter(String regexp, boolean caseSensitive) throws PatternSyntaxException {\n        super(new PathGenerator(), regexp, caseSensitive, false);\n    }\n\n    /**\n     * Creates a new <code>RegexpPathFilter</code> matching the specified regexp and operating in the specified\n     * modes.\n     *\n     * @param regexp regular expression that matches string values.\n     * @param caseSensitive whether the regular expression is case-sensitive or not.\n     * @param inverted if true, this filter will operate in inverted mode.\n     * @throws PatternSyntaxException if the syntax of the regular expression is not correct.\n     */\n    public RegexpPathFilter(String regexp, boolean caseSensitive, boolean inverted) throws PatternSyntaxException {\n        super(new PathGenerator(), regexp, caseSensitive, inverted);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/StartsWithFilenameFilter.java",
    "content": "package com.mucommander.commons.file.filter;\n\n/**\n * This {@link FilenameFilter} matches filenames that start with a specified string.\n *\n * @author Maxence Bernard\n */\npublic class StartsWithFilenameFilter extends AbstractStartsWithFilter implements FilenameFilter {\n\n    /**\n     * Creates a new case-insensitive <code>StartsFilenameFilter</code> matching filenames starting with the specified\n     * string and operating in the specified mode.\n     *\n     * @param s the string to compare filenames against\n     */\n    public StartsWithFilenameFilter(String s) {\n        this(s, false, false);\n    }\n\n    /**\n     * Creates a new <code>StartsFilenameFilter</code> matching filenames starting with the specified string and\n     * operating in non-inverted mode.\n     *\n     * @param s the string to compare filenames against\n     * @param caseSensitive if true, this FilenameFilter will be case-sensitive\n     */\n    public StartsWithFilenameFilter(String s, boolean caseSensitive) {\n        this(s, caseSensitive, false);\n    }\n\n    /**\n     * Creates a new <code>StartsFilenameFilter</code> matching filenames starting with the specified string and\n     * operating in the specified modes.\n     *\n     * @param s the string to compare filenames against\n     * @param caseSensitive if true, this FilenameFilter will be case-sensitive\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    public StartsWithFilenameFilter(String s, boolean caseSensitive, boolean inverted) {\n        super(new FilenameGenerator(), s, caseSensitive, inverted);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/StartsWithPathFilter.java",
    "content": "package com.mucommander.commons.file.filter;\n\n/**\n * This {@link PathFilter} matches paths that start with a specified string.\n *\n * @author Maxence Bernard\n */\npublic class StartsWithPathFilter extends AbstractStartsWithFilter implements PathFilter {\n\n    /**\n     * Creates a new case-insensitive <code>StartsPathFilter</code> matching paths starting with the specified\n     * string and operating in the specified mode.\n     *\n     * @param s the string to compare paths against\n     */\n    public StartsWithPathFilter(String s) {\n        this(s, false, false);\n    }\n\n    /**\n     * Creates a new <code>StartsPathFilter</code> matching paths starting with the specified string and\n     * operating in non-inverted mode.\n     *\n     * @param s the string to compare paths against\n     * @param caseSensitive if true, this PathFilter will be case-sensitive\n     */\n    public StartsWithPathFilter(String s, boolean caseSensitive) {\n        this(s, caseSensitive, false);\n    }\n\n    /**\n     * Creates a new <code>StartsPathFilter</code> matching paths starting with the specified string and\n     * operating in the specified modes.\n     *\n     * @param s the string to compare paths against\n     * @param caseSensitive if true, this PathFilter will be case-sensitive\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    public StartsWithPathFilter(String s, boolean caseSensitive, boolean inverted) {\n        super(new PathGenerator(), s, caseSensitive, inverted);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/StringCriterionFilter.java",
    "content": "package com.mucommander.commons.file.filter;\n\n/**\n * @author Maxence Bernard\n */\npublic interface StringCriterionFilter extends CriterionFilter<String> {\n\n    /**\n     * Returns <code>true</code> if this <code>CriterionFilter</code> is case-sensitive.\n     *\n     * @return true if this <code>CriterionFilter</code> is case-sensitive.\n     */\n    boolean isCaseSensitive();\n\n    /**\n     * Specifies whether this <code>CriterionFilter</code> should be case-sensitive or not when comparing paths.\n     *\n     * @param caseSensitive if true, this CriterionFilter will be case-sensitive\n     */\n    void setCaseSensitive(boolean caseSensitive);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/filter/WildcardFileFilter.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.filter;\n\nimport java.io.File;\nimport org.apache.commons.io.IOCase;\n\n\n/**\n * This filter matches files whose string criterion values correspond a specified wildcard mask (with '*' and/or '?' characters).\n *\n * @author Oleg Trifonov\n */\npublic class WildcardFileFilter extends AbstractStringCriterionFilter {\n    private org.apache.commons.io.filefilter.AbstractFileFilter fileFilter;\n\n    /**\n     * Creates a new case-insensitive <code>WildcardFileFilter</code> operating in non-inverted mode.\n     *\n     * @param s the wildcard to match\n     */\n    public WildcardFileFilter(String s) {\n        this(s, false, false);\n    }\n\n    /**\n     * Creates a new <code>WildcardFileFilter</code> operating in non-inverted mode.\n     *\n     * @param s the wildcard to match\n     * @param caseSensitive if true, this FilenameFilter will be case-sensitive\n     */\n    public WildcardFileFilter(String s, boolean caseSensitive) {\n        this(s, caseSensitive, false);\n    }\n\n    /**\n     * Creates a new <code>WildcardFileFilter</code> operating in the specified mode.\n     *\n     * @param s the wildcard to match\n     * @param caseSensitive if true, this FilenameFilter will be case-sensitive\n     * @param inverted if true, this filter will operate in inverted mode.\n     */\n    public WildcardFileFilter(String s, boolean caseSensitive, boolean inverted) {\n        super(new FilenameGenerator(), caseSensitive, inverted);\n        this.fileFilter = org.apache.commons.io.filefilter.WildcardFileFilter.builder().setWildcards(s).setIoCase(isCaseSensitive() ? IOCase.SENSITIVE : IOCase.INSENSITIVE).get();\n    }\n\n\n    @Override\n    public boolean accept(String value) {\n        return fileFilter.accept(new File(value));\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/icon/CacheableFileIconProvider.java",
    "content": "package com.mucommander.commons.file.icon;\n\nimport com.mucommander.commons.file.AbstractFile;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n/**\n * <code>CacheableFileIconProvider</code> is an interface to be implemented by file icon providers that wish to use\n * some icon caching to improve performance. This interface is to be used in conjunction with {@link CachedFileIconProvider}\n * to form a functional cached provider.\n *\n * @author Maxence Bernard\n */\npublic interface CacheableFileIconProvider extends FileIconProvider {\n\n    /**\n     * Returns <code>true</code> if the icon cache can be used for the specified file and preferred resolution. This\n     * method allows the icon cache to be used only for certain types of files and/or preferred resolutions.\n     *\n     * <p>This method is called by {@link CachedFileIconProvider#getFileIcon(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}\n     * each time an icon is requested. If <code>true</code> is returned, the icon cache will be looked up with\n     * {@link #lookupCache(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)} and if the cache did not return\n     * an icon, the icon will be added to the cache with {@link #addToCache(com.mucommander.commons.file.AbstractFile, javax.swing.Icon, java.awt.Dimension)}.\n     * <br>\n     * On the other hand, if <code>false</code> is returned, {@link #getFileIcon(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}\n     * will simply be called, without querying or adding to the cache.\n     *\n     * @param file the file for which to retrieve an icon\n     * @param preferredResolution the preferred resolution for the icon\n     * @return true if the icon cache can be used with the specified file\n     */\n    boolean isCacheable(AbstractFile file, Dimension preferredResolution);\n\n    /**\n     * This method is called by {@link CachedFileIconProvider#getFileIcon(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}\n     * to perform a cache lookup and give implementations a chance to re-use a cached icon. If a non-null value is\n     * returned, the returned icon will be used.\n     * <br>\n     * On the other hand, if <code>null</code> is returned, the icon will be fetched using {@link #getFileIcon(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}\n     * followed by a call to {@link #addToCache(com.mucommander.commons.file.AbstractFile, javax.swing.Icon,java.awt.Dimension)}\n     * to add the freshly-retrieved icon to the cache.\n     *\n     * <p>This method is called only if the prior call to {@link #isCacheable(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}\n     * returned <code>true</code>.\n     *\n     * @param file the file for which to look for a previously cached icon\n     * @param preferredResolution the preferred resolution for the icon\n     * @return a cached icon to re-use, null if there is none\n     */\n    Icon lookupCache(AbstractFile file, Dimension preferredResolution);\n\n    /**\n     * This method is called by {@link CachedFileIconProvider#getFileIcon(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}\n     * to give implementations a chance to cache an icon fetched with {@link #getFileIcon(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}\n     * and have it returned later by {@link #lookupCache(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}.\n     * <br>\n     * There is no obligation for this method to cache the given icon, implementations may freely choose whether to\n     * cache certain icons only.\n     *\n     * <p>This method is called only if the prior call to {@link #isCacheable(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}\n     * returned <code>true</code>.\n     *\n     * @param file the file that corresponds to the given icon\n     * @param icon the icon to add to the cache\n     * @param preferredResolution the preferred icon resolution that was originally requested\n     */\n    void addToCache(AbstractFile file, Icon icon, Dimension preferredResolution);\n\n    void cleanCache();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/icon/CachedFileIconProvider.java",
    "content": "package com.mucommander.commons.file.icon;\n\nimport com.mucommander.bookmark.Bookmark;\nimport com.mucommander.bookmark.BookmarkManager;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.ui.icon.FileIcons;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n/**\n * <code>CachedFileIconProvider</code> is a <code>FileIconProvider</code> with caching capabilities.\n *\n * <p>This class does not actually provide icons nor does it manage the contents of the cache ; it delegates these tasks\n * to a {@link CacheableFileIconProvider} instance. All this class does is use the cache implementation to harness its\n * benefits and take all the credit for it.<br>\n * When an icon is requested, a cache lookup is performed. If a cached value is found, it is returned. If not, the icon\n * is fetched from the underlying provider and added to the cache.\n *\n * @author Maxence Bernard\n */\npublic class CachedFileIconProvider implements FileIconProvider {\n\n    /** The underlying icon provider and cache manager */\n    protected CacheableFileIconProvider cacheableFip;\n\n\n    /**\n     * Creates a new CachedFileIconProvider that uses the given {@link CacheableFileIconProvider} to access the cache\n     * and retrieve the icons.\n     *\n     * @param cacheableFip the underlying icon provider and cache manager\n     */\n    public CachedFileIconProvider(CacheableFileIconProvider cacheableFip) {\n        this.cacheableFip = cacheableFip;\n    }\n\n    /**\n     * Creates and returns a {@link IconCache} instance.\n     *\n     * @return a new {@link IconCache} instance\n     */\n    public static IconCache createCache() {\n        return new IconCache();\n    }\n\n\n    /**\n     * <i>Implementation notes</i>: this method first calls {@link CacheableFileIconProvider#isCacheable(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}\n     * to determine if the icon cache is used.\n     *\n     * <p><b>If the file icon is cacheable</b>, {@link CacheableFileIconProvider#lookupCache(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}\n     * is called to look for a previously cached icon. If a value is found, it is returned. If not,\n     * {#getFileIcon(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)} is called on the <code>CacheableFileIconProvider</code>\n     * to retrieve the icon. This icon is then added to the cache by calling\n     * {@link CacheableFileIconProvider#addToCache(com.mucommander.commons.file.AbstractFile, javax.swing.Icon, java.awt.Dimension)}.\n     *\n     * <p><b>If the file icon is not cacheable</b>, {#getFileIcon(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}\n     * is simply called on the <code>CacheableFileIconProvider</code> and its value returned.\n     */\n    @Override\n    public Icon getFileIcon(AbstractFile file, Dimension preferredResolution) {\n        boolean isCacheable = cacheableFip.isCacheable(file, preferredResolution);\n\n        if (BookmarkManager.isBookmark(file)) {\n            for (Bookmark bookmark : BookmarkManager.getBookmarks()) {\n                if (file.getName().equals(bookmark.getName())) {\n                    // Note: if several bookmarks match current folder, the first one will be used\n                    file = FileFactory.getFile(bookmark.getLocation());\n                    //return getFileIcon(file, preferredResolution);\n                    return FileIcons.getFileIcon(file, preferredResolution);\n                }\n            }\n        }\n\n        // Look for the file icon in the provider's cache\n        Icon icon = isCacheable ? cacheableFip.lookupCache(file, preferredResolution) : null;\n\n        // Icon is not cacheable or isn't present in the cache, retrieve it from the provider\n        if (icon == null) {\n            icon = cacheableFip.getFileIcon(file, preferredResolution);\n\n            // Cache the icon\n            if (isCacheable && icon != null) {\n                cacheableFip.addToCache(file, icon, preferredResolution);\n            }\n        }\n\n        return icon;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/icon/FileIconProvider.java",
    "content": "package com.mucommander.commons.file.icon;\n\nimport com.mucommander.commons.file.AbstractFile;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n/**\n * FileIconProvider provides a standard interface for retrieving file icons. Icon providers can fetch icons from any\n * type of source, whether they be some API or a set of icon images. There is no requirement on the resolution in which\n * the icons are provided, nor on the set of file criteria used to select the icon (file kind, name, size, ...).\n * Implementations should however be able to return icons for any type of {@link AbstractFile}.\n * The specialized {@link com.mucommander.commons.file.icon.LocalFileIconProvider} class can come in handy for icon sources\n * that can only cope with local files.\n *\n * @see com.mucommander.commons.file.FileFactory#getDefaultFileIconProvider()\n * @author Maxence Bernard\n */\npublic interface FileIconProvider {\n\n    /**\n     * Returns an icon for the given file, or <code>null</code> if it couldn't be retrieved, either because the\n     * given file doesn't exist or for any other reason.\n     *\n     * <p>The specified <code>Dimension</code> is used as a hint at the preferred icon's resolution; there is\n     * absolutely no guarantee that the returned <code>Icon</code> will indeed have this resolution. This dimension is\n     * only used to choose between different resolutions should more than one resolution be available, and return the\n     * one that most closely matches the specified one.<br>\n     * The implementation is not expected to perform any rescaling (either up or down), this method should only return\n     * icons in their 'native' resolutions, using the preferred resolution to choose between different native dimensions.\n     * For example, if this provider is able to create icons both in 16x16 and 32x32 resolutions, and a 48x48 resolution\n     * is preferred, the 32x32 resolution should be favored as it is closer to 32x32.\n     *\n     * @param file the AbstractFile instance for which an icon is requested\n     * @param preferredResolution the preferred icon resolution\n     * @return an icon for the requested file\n     */\n    Icon getFileIcon(AbstractFile file, Dimension preferredResolution);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/icon/IconCache.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.file.icon;\n\n\nimport org.apache.commons.collections4.map.ReferenceMap;\n\nimport javax.swing.*;\n\n/**\n * This class provides an icon cache, mapping <code>Object</code> keys onto {@link Icon} instances.\n * Any kind of Object may be used as the key: a file, a URL, an extension, ... allowing different of icon caching\n * strategies to be implemented.\n *\n * <p>Icons are stored as {@link java.lang.ref.SoftReference soft references} so they can be garbage collected \n * when the VM runs low on memory.\n *\n * <p>The implementation uses the {@link ReferenceMap} class part of the <code>Apache Commons Collection</code> library.\n * All accesses to the underlying map is synchronized, making this cache thread-safe.\n *\n * @author Maxence Bernard\n */\npublic class IconCache {\n\n   /** The actual hash map */\n    private final ReferenceMap<Object, Icon> hashMap = new ReferenceMap<>(ReferenceMap.ReferenceStrength.HARD, ReferenceMap.ReferenceStrength.SOFT);\n\n    /**\n     * Creates a new icon cache.\n     */\n    IconCache() {\n    }\n\n    /**\n     * Adds a new key/icon mapping to the cache. If a mapping with the same key exists, it is replaced and the previous\n     * value returned.\n     *\n     * @param key the key that will later allow to retrieve the cached icon\n     * @param value the icon instance to cache\n     * @return returns the icon instance previously mapped onto the given key, <code>null</code> if no\n     * such mapping existed\n     */\n    public synchronized Icon put(Object key, Icon value) {\n        return hashMap.put(key, value);\n    }\n\n    /**\n     * Returns the {@link Icon} instance mapped onto the given key if there is one,\n     * <code>null</code> otherwise\n     *\n     * @param key key of the icon instance to retrieve\n     * @return the {@link Icon} instance mapped onto the given key if there is one,\n     * <code>null</code> otherwise\n     */\n    public synchronized Icon get(Object key) {\n        return hashMap.get(key);\n    }\n\n    /**\n     * Returns <code>true</code> if this cache currently contains a key/icon mapping where the given key is used as\n     * the mapping's key.\n     *\n     * @param key key to lookup\n     * @return <code>true</code> if this cache currently contains a key/icon mapping where the given key is used as\n     * the mapping's key.\n     */\n    public synchronized boolean containsKey(Object key) {\n        return hashMap.containsKey(key);\n    }\n\n    /**\n     * Returns <code>true</code> if this cache currently contains a key/icon mapping where the given icon is used as\n     * the mapping's value.\n     *\n     * @param icon icon to lookup\n     * @return <code>true</code> if this cache currently contains a key/icon mapping where the given icon is used as\n     * the mapping's key.\n     */\n    public synchronized boolean containsValue(Icon icon) {\n        return hashMap.containsValue(icon);\n    }\n\n    /**\n     * Removes all existing key/icon mapping from this cache, leaving the cache in the same state as it was right after\n     * its creation.\n     */\n    public synchronized void clear() {\n        hashMap.clear();\n    }\n\n    /**\n     * Returns the number of key/icon mapping this cache currently contains.\n     *\n     * @return the number of key/icon mapping this cache currently contains.\n     */\n    public synchronized int size() {\n        return hashMap.size();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/icon/LocalFileIconProvider.java",
    "content": "package com.mucommander.commons.file.icon;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.impl.local.LocalFile;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.io.IOException;\n\n/**\n * <code>LocalFileIconProvider</code> is an abstract {@link FileIconProvider} which makes things easier for\n * implementations that are only able to provide icons for local files.\n *\n * <p>This class implements {@link #getFileIcon(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)} and passes on\n * requests for local file icons to {@link #getLocalFileIcon(com.mucommander.commons.file.impl.local.LocalFile, com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}.\n * On the other hand, requests for non-local file icons are transformed to local ones, by creating a local temporary\n * file with the same name (the best effort) and extension (guaranteed) as the non-local file, and passes on the file\n * to {@link #getLocalFileIcon(com.mucommander.commons.file.impl.local.LocalFile, com.mucommander.commons.file.AbstractFile, java.awt.Dimension)}.\n *\n * @author Maxence Bernard\n */\npublic abstract class LocalFileIconProvider implements FileIconProvider {\n\n    /**\n     * Creates a returns a temporary local file/directory with the same extension as the specified file/directory\n     * (guaranteed), and the same filename as much as possible (the best effort).\n     * This method returns <code>null</code> if the temporary file/directory could not be created.\n     *\n     * @param nonLocalFile the non-local file for which to create a temporary file.\n     * @return a temporary local file/directory with the same extension as the specified file/directory\n     */\n    private LocalFile createTempLocalFile(AbstractFile nonLocalFile) {\n        try {\n            // Note: the returned temporary file may be an AbstractArchiveFile if the filename's extension corresponds\n            // to a registered archive format\n            LocalFile tempFile = FileFactory.getTemporaryFile(nonLocalFile.getName(), false).getAncestor(LocalFile.class);\n            if (tempFile == null) {\n                return null;\n            }\n            // create a directory\n            if (nonLocalFile.isDirectory()) {\n                tempFile.mkdir();\n            } else {    // create a regular file\n                tempFile.getOutputStream().close();\n            }\n            return tempFile;\n        } catch (IOException e) {\n            return null;\n        }\n    }\n\n\n    /////////////////////////////////////\n    // FileIconProvider implementation //\n    /////////////////////////////////////\n\n    public Icon getFileIcon(AbstractFile originalFile, Dimension preferredResolution) {\n        if (originalFile == null) {\n            return null;\n        }\n        // Specified file is a LocalFile or a ProxyFile proxying a LocalFile (e.g. an archive file): let's simply get\n        // the icon using #getLocalFileIcon(LocalFile)\n        AbstractFile topFile = originalFile.getTopAncestor();\n\n        if (topFile instanceof LocalFile) {\n            return getLocalFileIcon((LocalFile)topFile, originalFile, preferredResolution);\n        }\n        // File is a remote file: create a temporary local file (or directory) with the same extension to grab the icon\n        // and then delete the file. This operation is I/O bound and thus expensive, so an LRU is used to cache\n        // frequently-accessed file extensions.\n\n        LocalFile tempFile = createTempLocalFile(topFile);\n        if (tempFile == null) {\n            // No temp file, no icon!\n            return null;\n        }\n\n        // Get the file icon\n        Icon icon = getLocalFileIcon(tempFile, originalFile, preferredResolution);\n\n        // Delete the temporary file\n        try {\n            tempFile.delete();\n        } catch (IOException e) {\n            // Not much to do\n        }\n\n        return icon;\n    }\n\n\n    //////////////////////\n    // Abstract methods //\n    //////////////////////\n\n    /**\n     * Returns an icon for the given local file, <code>null</code> if the icon couldn't be retrieved. This method is\n     * called by {@link #getFileIcon(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)} with a {@link LocalFile}\n     * equivalent to the {@link AbstractFile} originally requested.\n     *\n     * <p>The specified <code>Dimension</code> is used as a hint at the preferred icon's resolution; there is\n     * absolutely no guarantee that the returned <code>Icon</code> will indeed have this resolution. This dimension is\n     * only used to choose between different resolutions should more than one resolution be available, and return the\n     * one that most closely matches the specified one.<br>\n     * This method is not expected to perform any rescaling (either up or down), returned resolutions should only be\n     * 'native' icon resolutions. For example, if this provider is able to create icons both in 16x16 and 32x32\n     * resolutions, and a 48x48 resolution is preferred, the 32x32 resolution should be favored for the returned icon.\n     *\n     * @param localFile the LocalFile instance for which an icon is requested\n     * @param originalFile the AbstractFile for which an icon was originally requested\n     * @param preferredResolution the preferred icon resolution\n     * @return an icon for the requested file\n     */\n    public abstract Icon getLocalFileIcon(LocalFile localFile, AbstractFile originalFile, Dimension preferredResolution);\n\n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/icon/impl/SwingFileIconProvider.java",
    "content": "package com.mucommander.commons.file.icon.impl;\n\nimport com.mucommander.commons.file.icon.CachedFileIconProvider;\nimport com.mucommander.commons.file.icon.LocalFileIconProvider;\n\n/**\n * This {@link LocalFileIconProvider} returns system file icons fetched from the Swing API. Those icons are only\n * available under one resolution, usually 16x16 but this may vary across platforms.\n *\n * <p>Icons are provided by one of the two following Swing classes; the one that provides the best results on the\n * target platform is used:\n * <ul>\n *   <li><code>javax.swing.filechooser.FileSystemView</code>: used on all platforms but Mac OS X</li>\n *   <li><code>javax.swing.JFileChooser: used on Mac OS X only</code></li>\n * </ul>\n * Those classes are only capable of returning icons for <code>java.io.File</code> instances, thus only work with local\n * files. {@link com.mucommander.commons.file.icon.LocalFileIconProvider} provides transparent creation of local temporary file\n * to handle non-local files.<br>\n * It is also noteworthy that those Swing classes maintain an icon cache. Therefore, local file icons are not cached,\n * only non-local files (remote protocol or archive entries) have their icons cached to avoid excessive temporary file\n * creation, using their extension as the cache's key.\n *\n * @author Maxence Bernard\n */\npublic class SwingFileIconProvider extends CachedFileIconProvider {\n\n    public SwingFileIconProvider() {\n        super(new SwingFileIconProviderImpl());\n    }\n\n    /**\n     * This method forces the initialization of the Swing object that is used to retrieve file icons the first time it\n     * is called, does nothing on subsequent calls.\n     * <p>\n     * The initialization must be triggered by a thread other than the EventDispatchThread, or a deadlock may happen\n     * on platforms where JFileChooser is used. This method provides to control when and where the initialization occurs.\n     */\n    public static void forceInit() {\n        SwingFileIconProviderImpl.checkInit();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/icon/impl/SwingFileIconProviderImpl.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.commons.file.icon.impl;\n\nimport ch.randelshofer.quaqua.osx.OSXFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileProtocols;\nimport com.mucommander.commons.file.icon.CacheableFileIconProvider;\nimport com.mucommander.commons.file.icon.CachedFileIconProvider;\nimport com.mucommander.commons.file.icon.IconCache;\nimport com.mucommander.commons.file.icon.LocalFileIconProvider;\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport com.mucommander.commons.file.util.ResourceLoader;\nimport com.mucommander.commons.io.SilenceableOutputStream;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.commons.runtime.OsVersion;\nimport lombok.extern.slf4j.Slf4j;\nimport ru.trolsoft.macosx.RetinaImageIcon;\nimport ru.trolsoft.utils.FileUtils;\n\nimport javax.swing.*;\nimport javax.swing.filechooser.FileSystemView;\nimport java.awt.*;\nimport java.awt.image.BufferedImage;\nimport java.io.*;\nimport java.net.URL;\n\n/**\n * Package-protected class which provides the {@link com.mucommander.commons.file.icon.LocalFileIconProvider} and\n * {@link com.mucommander.commons.file.icon.CacheableFileIconProvider} implementations to {@link SwingFileIconProvider}.\n *\n * @see SwingFileIconProvider\n * @author Maxence Bernard\n */\n@Slf4j\nclass SwingFileIconProviderImpl extends LocalFileIconProvider implements CacheableFileIconProvider {\n    /** Swing object used to retrieve file icons, used on all platforms but Mac OS X */\n    private static FileSystemView fileSystemView;\n\n    /** Swing object used to retrieve file icons, used under Mac OS X only */\n    private static JFileChooser fileChooser;\n\n    /** Caches icons for directories, used only for non-local files */\n    private static final IconCache directoryIconCache = CachedFileIconProvider.createCache();\n\n    /** Caches icons for regular files, used only for non-local files */\n    private static final IconCache fileIconCache = CachedFileIconProvider.createCache();\n\n    /** True if init has been called */\n    protected static boolean initialized;\n\n    /** Name of the 'symlink' icon resource located in the same package as this class */\n    private final static String SYMLINK_ICON_NAME = \"link.png\";\n\n    /** Icon that is painted over a symlink's target file icon to symbolize a symlink to the target file. */\n    private static ImageIcon SYMLINK_OVERLAY_ICON;\n\n    /** Allows stderr to be 'silenced' when needed */\n    private static SilenceableOutputStream errOut;\n\n\n\n    private static void prepareQuaquaLibraries() {\n        String jarPath = FileUtils.getJarPath();\n\n        try {\n            FileUtils.copyFromJarFile(\"libquaqua.jnilib\", jarPath);\n            FileUtils.copyFromJarFile(\"libquaqua64.dylib\", jarPath);\n            FileUtils.copyFromJarFile(\"libquaqua64.jnilib\", jarPath);\n        } catch (IOException e) {\n            log.error(\"Libraries prepare error\", e);\n        }\n        OSXFile.setNativePath(FileUtils.getJarPath() + File.separator);\n    }\n\n\n\n    /**\n     * Initializes the Swing object used to retrieve icons the first time this method is called, does nothing\n     * subsequent calls.\n     * Note: instanciating this object is expensive (I/O bound) so we want to do that only if needed, and only once.\n     */\n    synchronized static void checkInit() {\n        // This method is synchronized to ensure that the initialization happens only once\n        if (initialized) {\n            return;\n        }\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            // try to use quaqua OSXFile\n            prepareQuaquaLibraries();\n            // use ugly JFileChooser if error\n            if (!OSXFile.canWorkWithAliases()) {\n                fileChooser = new JFileChooser();\n            }\n        } else {\n            fileSystemView = FileSystemView.getFileSystemView();\n        }\n\n        // Loads the symlink overlay icon\n        URL iconURL = ResourceLoader.getPackageResourceAsURL(SwingFileIconProviderImpl.class.getPackage(), SYMLINK_ICON_NAME);\n        if (iconURL == null) {\n            throw new RuntimeException(\"Could not locate required symlink icon: \"+SwingFileIconProviderImpl.class.getPackage()+\"/\" + SYMLINK_ICON_NAME);\n        }\n\n        SYMLINK_OVERLAY_ICON = new ImageIcon(iconURL);\n\n        // Replace stderr with a SilenceablePrintStream that can be 'silenced' when needed\n        errOut = new SilenceableOutputStream(System.err, false);\n        System.setErr(new PrintStream(errOut, true));\n\n        initialized = true;\n    }\n\n\n    /**\n     * Returns an icon for the given <code>java.io.File</code> using the underlying Swing provider component,\n     * <code>null</code> in case of an error.\n     *\n     * @param javaIoFile the file for which to return an icon\n     * @return an icon for the specified file, null in case of an unexpected error\n     */\n    private static Icon getSwingIcon(File javaIoFile, int preferredSize) {\n        try {\n            if (fileSystemView != null) {\n                // FileSystemView.getSystemIcon() will behave in the following way if the specified file doesn't exist\n                // when the icon is requested:\n                //  - throw a NullPointerException (caused by a java.io.FileNotFoundException) => OK why not\n                //  - dump the stack trace to System.err => bad! bad! bad!\n                //\n                // A way to workaround this odd behavior would be to test if the file exists when it is requested,\n                // but a/ this is an expensive operation (especially under Windows) and b/ it wouldn't guarantee that\n                // the file effectively exists when the icon is requested.\n                // So the workaround here is to catch exceptions and 'silence' System.err output during the call.\n\n                errOut.setSilenced(true);\n                return fileSystemView.getSystemIcon(javaIoFile);\n            } else {\n                if (fileChooser == null) {\n                    if (RetinaImageIcon.IS_RETINA) {\n                        Icon icon = OSXFile.getIcon(javaIoFile, preferredSize*2);\n                        if (icon instanceof ImageIcon imageIcon) {\n                            return new RetinaImageIcon(imageIcon.getImage());\n                        }\n                    }\n                    return OSXFile.getIcon(javaIoFile, preferredSize);\n                }\n                return fileChooser.getIcon(javaIoFile);\n            }\n        } catch (Exception e) {\n            log.info(\"Caught exception while retrieving system icon for file {}\", javaIoFile.getAbsolutePath(), e);\n            return null;\n        } finally {\n            if (fileSystemView != null) {\n                errOut.setSilenced(false);\n            }\n        }\n    }\n\n\n    /**\n     * Returns an icon symbolizing a symlink to the given target icon. The returned icon uses the specified icon as\n     * its background and overlays a 'link' icon on top of it.\n     *\n     * @param targetFileIcon the icon representing the symlink's target\n     * @return an icon symbolizing a symlink to the given target\n     */\n    private static ImageIcon getSymlinkIcon(Icon targetFileIcon) {\n        BufferedImage bi = new BufferedImage(targetFileIcon.getIconWidth(), targetFileIcon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);\n\n        Graphics g = bi.getGraphics();\n        targetFileIcon.paintIcon(null, g, 0, 0);\n        SYMLINK_OVERLAY_ICON.paintIcon(null, g, 0, 0);\n\n        return new ImageIcon(bi);\n    }\n\n    /**\n     * Returns the extension of the given file using {@link AbstractFile#getExtension()}. If the extension is\n     * <code>null</code>, the empty string <code>\"\"</code> is returned, making the returned extension safe for use\n     * in a hash map where null keys are forbidden.\n     *\n     * @param file file on which to call {@link AbstractFile#getExtension}\n     * @return the file's extension, may be the empty string but never <code>null</code>\n     */\n    private static String getCheckedExtension(AbstractFile file) {\n        String extension = file.getExtension();\n        return extension == null ? \"\" : extension;\n    }\n\n\n    /**\n     * <b>Implementation notes:</b> returns <code>false</code> (no caching) for:\n     * <ul>\n     *  <li>local files: their icons are cached by the Swing component that provides icons.</li>\n     *  <li>symlinks: their icon cannot be cached using the file's extension as a key.</li>\n     * </ul>\n     * <code>true</code> is returned for non-local files that are not symlinks to avoid excessive temporary file\n     * creation.\n     */\n    @Override\n    public boolean isCacheable(AbstractFile file, Dimension preferredResolution) {\n        return file != null && !((file.getTopAncestor() instanceof LocalFile) || file.isSymlink());\n    }\n\n    @Override\n    public Icon lookupCache(AbstractFile file, Dimension preferredResolution) {\n        // Under Mac OS X, return the icon of /Network for the root of remote (non-local) locations. \n        if (OsFamily.MAC_OS_X.isCurrent() && !FileProtocols.FILE.equals(file.getURL().getScheme()) && file.isRoot()) {\n            return getSwingIcon(new File(\"/Network\"), preferredResolution.width);\n        }\n        // Look for an existing icon instance for the file's extension\n        return (file.isDirectory()? directoryIconCache : fileIconCache).get(getCheckedExtension(file));\n    }\n\n    @Override\n    public void addToCache(AbstractFile file, Icon icon, Dimension preferredResolution) {\n        // Map the extension onto the given icon\n        (file.isDirectory() ? directoryIconCache : fileIconCache).put(getCheckedExtension(file), icon);\n    }\n\n    /**\n     * <i>Implementation note:</i> only one resolution is available (usually 16x16) and blindly returned, the\n     * <code>preferredResolution</code> argument is simply ignored.\n     */\n    @Override\n    public Icon getLocalFileIcon(LocalFile localFile, AbstractFile originalFile, Dimension preferredResolution) {\n        // Initialize the Swing object the first time this method is called\n        checkInit();\n\n        // Retrieve the icon using the Swing provider component\n        Icon icon = getSwingIcon((File)localFile.getUnderlyingFileObject(), preferredResolution.width);\n\n        // Add a symlink indication to the icon if:\n        // - the original file is a symlink AND\n        //   - the original file is not a local file OR\n        //   - the original file is a local file but the Swing component generates icons which do not have a symlink\n        // indication. That is the case on Mac OS X 10.5 (regression, 10.4 did this just fine).\n        //\n        // Note that the symlink test is performed last because it is the most expensive.\n        //\n        if ((!(originalFile.getTopAncestor() instanceof LocalFile) || (OsFamily.MAC_OS_X.isCurrent() && OsVersion.MAC_OS_X_10_5.isCurrent()))\n                && originalFile.isSymlink()) {\n            icon = icon == null ? null : getSymlinkIcon(icon);\n        }\n\n        return icon;\n    }\n\n    public void cleanCache() {\n        directoryIconCache.clear();\n        fileIconCache.clear();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/CachedFile.java",
    "content": "package com.mucommander.commons.file.impl;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileOperation;\r\nimport com.mucommander.commons.file.FilePermissions;\r\nimport com.mucommander.commons.file.FileProtocols;\r\nimport com.mucommander.commons.file.filter.FileFilter;\r\nimport com.mucommander.commons.file.filter.FilenameFilter;\r\nimport com.mucommander.commons.file.impl.local.LocalFile;\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport lombok.extern.slf4j.Slf4j;\r\nimport ru.trolsoft.jni.NativeFileUtils;\r\n\r\nimport java.io.File;\r\nimport java.io.IOException;\r\nimport java.lang.reflect.Field;\r\nimport java.lang.reflect.Method;\r\n\r\n/**\r\n * CachedFile is a ProxyFile that caches the return values of most {@link AbstractFile} getter methods. This allows\r\n * to limit the number of calls to the underlying file methods which can have a cost since they are often I/O bound.\r\n * The methods that are cached are those overridden by this class, except for the <code>ls</code> methods, which are\r\n * overridden only to allow recursion (see {@link #CachedFile(com.mucommander.commons.file.AbstractFile, boolean)}).\r\n *\r\n * <p>The values are retrieved and cached only when the 'cached methods' are called for the first time; they are\r\n * not preemptively retrieved in the constructor, so using this class has no negative impact on performance,\r\n * except for the small extra CPU cost added by proxying the methods and the extra RAM used to store cached values.\r\n *\r\n * <p>Once the values are retrieved and cached, they never change: the same value will always be returned once a method\r\n * has been called for the first time. That means if the underlying file changes (e.g. its size or date has changed),\r\n * the changes will not be reflected by this CachedFile. Thus, this class should only be used when a 'real-time' view\r\n * of the file is not required, or when the file instance is used only for a small amount of time.\r\n *\r\n * @author Maxence Bernard\r\n */\r\n@Slf4j\r\npublic class CachedFile extends ProxyFile {\r\n    // Used to access the java.io.FileSystem#getBooleanAttributes method\r\n    private static final boolean GET_FILE_ATTRIBUTES_AVAILABLE;\r\n    private static final Method M_GET_BOOLEAN_ATTRIBUTES;\r\n    private static final int BA_DIRECTORY, BA_EXISTS, BA_HIDDEN;\r\n    private static final Object FS;\r\n    private static final boolean NATIVE_FILE_UTILS_AVAILABLE = OsFamily.MAC_OS_X.isCurrent() && NativeFileUtils.init();\r\n\r\n    // set-flags\r\n    private static final int SIZE_SET_MASK = 1;\r\n    private static final int LAST_MODIFICATION_SET_MASK = 1 << 1;\r\n    private static final int SYMLINK_SET_MASK = 1 << 2;\r\n    private static final int DIRECTORY_SET_MASK = 1 << 3;\r\n    private static final int ARCHIVE_SET_MASK = 1 << 4;\r\n    private static final int EXECUTABLE_SET_MASK = 1 << 5;\r\n    private static final int HIDDEN_SET_MASK = 1 << 6;\r\n    private static final int ABSOLUTE_PATH_SET_MASK = 1 << 7;\r\n    private static final int CANONICAL_PATH_SET_MASK = 1 << 8;\r\n    private static final int EXTENSION_SET_MASK = 1 << 9;\r\n    private static final int NAME_SET_MASK = 1 << 10;\r\n    private static final int FREE_SPACE_SET_MASK = 1 << 11;\r\n    private static final int TOTAL_SPACE_SET_MASK = 1 << 12;\r\n    private static final int EXISTS_SET_MASK = 1 << 13;\r\n    private static final int PERMISSIONS_SET_MASK = 1 << 14;\r\n    private static final int PERMISSIONS_STRING_SET_MASK = 1 << 15;\r\n    private static final int OWNER_SET_MASK = 1 << 16;\r\n    private static final int GROUP_SET_MASK = 1 << 17;\r\n    private static final int IS_ROOT_SET_MASK = 1 << 18;\r\n    private static final int PARENT_SET_MASK = 1 << 19;\r\n    private static final int GET_ROOT_SET_MASK = 1 << 20;\r\n    private static final int CANONICAL_FILE_SET_MASK = 1 << 21;\r\n    private static final int CREATION_DATE_SET_MASK = 1 << 22;\r\n    private static final int LAST_ACCESS_SET_MASK = 1 << 23;\r\n\r\n    // boolean values\r\n    private static final int SYMLINK_VALUE_MASK = 1 << 24;\r\n    private static final int DIRECTORY_VALUE_MASK = 1 << 25;\r\n    private static final int ARCHIVE_VALUE_MASK = 1 << 26;\r\n    private static final int EXECUTABLE_VALUE_MASK = 1 << 27;\r\n    private static final int HIDDEN_VALUE_MASK = 1 << 28;\r\n    private static final int EXISTS_VALUE_MASK = 1 << 29;\r\n    private static final int IS_ROOT_VALUE_MASK = 1 << 30;\r\n\r\n    /** If true, AbstractFile instances returned by this class will be wrapped into CachedFile instances */\r\n    private static final int RECURSE_INSTANCES_MASK = 1 << 29;\r\n\r\n    /**\r\n     * All boolean values stored here as bits\r\n     */\r\n    private int bitmask;\r\n\r\n    ///////////////////\r\n    // Cached values //\r\n    ///////////////////\r\n    \r\n    private long getSize;\r\n    private long getLastModified;\r\n    private long getCreationDate;\r\n    private long getLastAccessDate;\r\n    private String getAbsolutePath;\r\n    private String getCanonicalPath;\r\n    private String getExtension;\r\n    private String getName;\r\n    private long getFreeSpace;\r\n    private long getTotalSpace;\r\n    private FilePermissions getPermissions;\r\n    private String getPermissionsString;\r\n    private String getOwner;\r\n    private String getGroup;\r\n    private AbstractFile getParent;\r\n    private AbstractFile getRoot;\r\n    private AbstractFile getCanonicalFile;\r\n\r\n    /**\r\n     * Cached values for <code>isFileOperationSupported</code> method\r\n     */\r\n    private int supportedOperationsValuesMask;\r\n    private int supportedOperationsCachedMask;\r\n\r\n\r\n\r\n    static {\r\n        // Exposes the java.io.FileSystem class which by default has package access, in order to use its\r\n        // 'getBooleanAttributes' method to speed up access to file attributes under Windows.\r\n        // This method allows to retrieve the values of the 'exists', 'isDirectory' and 'isHidden' attributes in one\r\n        // pass, resolving the underlying file only once instead of 3 times. Since resolving a file is a particularly\r\n        // expensive operation under Windows due to improper use of the Win32 API, this helps speed things up a little.\r\n        // References:\r\n        //  - http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5036988\r\n        //  - http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6240028\r\n        //\r\n        // This hack was made for Windows, but is now used for other platforms as well as it is necessarily faster than\r\n        // retrieving file attributes individually.\r\n        boolean getFileAttributesAvailable;\r\n        Method mGetBooleanAttributes;\r\n        int baExists, baDirectory, baHidden;\r\n        Object fs;\r\n\r\n        if (NATIVE_FILE_UTILS_AVAILABLE) {\r\n            getFileAttributesAvailable = true;\r\n            mGetBooleanAttributes = null;\r\n            baExists = 0;\r\n            baDirectory = 0;\r\n            baHidden = 0;\r\n            fs = null;\r\n        } else {\r\n            try {\r\n                // Resolve FileSystem class, 'getBooleanAttributes' method and fields\r\n                Class<?> cFile = File.class;\r\n                Class<?> cFileSystem = Class.forName(\"java.io.FileSystem\");\r\n                mGetBooleanAttributes = cFileSystem.getDeclaredMethod(\"getBooleanAttributes\", cFile);\r\n                Field fBA_EXISTS = cFileSystem.getDeclaredField(\"BA_EXISTS\");\r\n                Field fBA_DIRECTORY = cFileSystem.getDeclaredField(\"BA_DIRECTORY\");\r\n                Field fBA_HIDDEN = cFileSystem.getDeclaredField(\"BA_HIDDEN\");\r\n                Field fFs;\r\n                try {\r\n                    fFs = cFile.getDeclaredField(\"FS\");\r\n                } catch (NoSuchFieldException e) {\r\n                    fFs = cFile.getDeclaredField(\"fs\");\r\n                }\r\n\r\n                // Allow access to the 'getBooleanAttributes' method and to the fields we're interested in\r\n                mGetBooleanAttributes.setAccessible(true);\r\n                fFs.setAccessible(true);\r\n\r\n                fBA_EXISTS.setAccessible(true);\r\n                fBA_DIRECTORY.setAccessible(true);\r\n                fBA_HIDDEN.setAccessible(true);\r\n\r\n                // Retrieve constant field values once for all\r\n                baExists = (Integer) fBA_EXISTS.get(null);\r\n                baDirectory = (Integer) fBA_DIRECTORY.get(null);\r\n                baHidden = (Integer) fBA_HIDDEN.get(null);\r\n                fs = fFs.get(null);\r\n                getFileAttributesAvailable = true;\r\n                log.trace(\"Access to java.io.FileSystem granted\");\r\n            } catch (Exception e) {\r\n                getFileAttributesAvailable = false;\r\n                mGetBooleanAttributes = null;\r\n                baExists = 0;\r\n                baDirectory = 0;\r\n                baHidden = 0;\r\n                fs = null;\r\n                log.info(\"Error while allowing access to java.io.FileSystem\", e);\r\n            }\r\n        }\r\n        GET_FILE_ATTRIBUTES_AVAILABLE = getFileAttributesAvailable;\r\n        M_GET_BOOLEAN_ATTRIBUTES = mGetBooleanAttributes;\r\n        BA_EXISTS = baExists;\r\n        BA_DIRECTORY = baDirectory;\r\n        BA_HIDDEN = baHidden;\r\n        FS = fs;\r\n    }\r\n\r\n\r\n    /**\r\n     * Creates a new CachedFile instance around the specified AbstractFile, caching returned values of cached methods\r\n     * as they are called. If recursion is enabled, the methods returning AbstractFile will return CachedFile instances,\r\n     * allowing the cache files recursively.\r\n     *\r\n     * @param file the AbstractFile instance for which returned values of getter methods should be cached\r\n     * @param recursiveInstances if true, AbstractFile instances returned by this class will be wrapped into CachedFile instances\r\n     */\r\n    public CachedFile(AbstractFile file, boolean recursiveInstances) {\r\n        super(file);\r\n        if (recursiveInstances) {\r\n            bitmask |= RECURSE_INSTANCES_MASK;\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Creates a CachedFile instance for each of the AbstractFile instances in the given array.\r\n     */\r\n    private AbstractFile[] createCachedFiles(AbstractFile[] files) {\r\n        int nbFiles = files.length;\r\n        for (int i = 0; i < nbFiles; i++) {\r\n            files[i] = new CachedFile(files[i], true);\r\n        }\r\n\r\n        return files;\r\n    }\r\n\r\n\r\n    /**\r\n     * Pre-fetches values of {@link #isDirectory}, {@link #exists} and {@link #isHidden} for the given local file,\r\n     * using the <code>java.io.FileSystem#getBooleanAttributes(java.io.File)</code> method.\r\n     * The given {@link AbstractFile} must be a local file or a proxy to a local file ('file' protocol). This method\r\n     * must only be called if the {@link #GET_FILE_ATTRIBUTES_AVAILABLE} field is <code>true</code>.\r\n     */\r\n    private void getFileAttributes(AbstractFile file) {\r\n        file = file.getTopAncestor();\r\n\r\n        if (file instanceof LocalFile) {\r\n            if (NATIVE_FILE_UTILS_AVAILABLE) {\r\n                initAttributesNativeCall(file);\r\n            } else {\r\n                initAttributesReflectionCall(file);\r\n            }\r\n        }\r\n    }\r\n\r\n    private void initAttributesReflectionCall(AbstractFile file) {\r\n        try {\r\n            int ba = (Integer) M_GET_BOOLEAN_ATTRIBUTES.invoke(FS, file.getUnderlyingFileObject());\r\n\r\n            if ((ba & BA_DIRECTORY) != 0) {\r\n                bitmask |= DIRECTORY_VALUE_MASK;\r\n            } else {\r\n                bitmask &= ~DIRECTORY_VALUE_MASK;\r\n            }\r\n            if ((ba & BA_EXISTS) != 0) {\r\n                bitmask |= EXISTS_VALUE_MASK;\r\n            } else {\r\n                bitmask &= ~EXISTS_VALUE_MASK;\r\n            }\r\n            if ((ba & BA_HIDDEN) != 0) {\r\n                bitmask |= HIDDEN_VALUE_MASK;\r\n            } else {\r\n                bitmask &= ~HIDDEN_VALUE_MASK;\r\n            }\r\n            bitmask |= DIRECTORY_SET_MASK | HIDDEN_SET_MASK | EXISTS_SET_MASK;\r\n        } catch (Exception e) {\r\n            log.info(\"Could not retrieve file attributes for {}\", file, e);\r\n        }\r\n    }\r\n\r\n    private void initAttributesNativeCall(AbstractFile file) {\r\n        int attr = NativeFileUtils.getLocalFileAttributes(file.getAbsolutePath());\r\n        if ((attr & NativeFileUtils.FA_MASK_DIRECTORY) != 0) {\r\n            bitmask |= DIRECTORY_VALUE_MASK;\r\n        } else {\r\n            bitmask &= ~DIRECTORY_VALUE_MASK;\r\n        }\r\n        if ((attr & NativeFileUtils.FA_MASK_EXISTS) != 0) {\r\n            bitmask |= EXISTS_VALUE_MASK;\r\n        } else {\r\n            bitmask &= ~EXISTS_VALUE_MASK;\r\n        }\r\n        if ((attr & NativeFileUtils.FA_MASK_HIDDEN) != 0) {\r\n            bitmask |= HIDDEN_VALUE_MASK;\r\n        } else {\r\n            bitmask &= ~HIDDEN_VALUE_MASK;\r\n        }\r\n        bitmask |= DIRECTORY_SET_MASK | HIDDEN_SET_MASK | EXISTS_SET_MASK;\r\n    }\r\n\r\n\r\n    ////////////////////////////////////////////////////\r\n    // Overridden methods to cache their return value //\r\n    ////////////////////////////////////////////////////\r\n\r\n    @Override\r\n    public long getSize() {\r\n        if ((bitmask & SIZE_SET_MASK) == 0) {\r\n            getSize = file.getSize();\r\n            bitmask |= SIZE_SET_MASK;\r\n        }\r\n        return getSize;\r\n    }\r\n\r\n    @Override\r\n    public long getLastModifiedDate() {\r\n        if ((bitmask & LAST_MODIFICATION_SET_MASK) == 0) {\r\n            getLastModified = file.getLastModifiedDate();\r\n            bitmask |= LAST_MODIFICATION_SET_MASK;\r\n        }\r\n        return getLastModified;\r\n    }\r\n\r\n    @Override\r\n    public long getCreationDate() throws IOException {\r\n        if ((bitmask & CREATION_DATE_SET_MASK) == 0) {\r\n            getCreationDate = file.getCreationDate();\r\n            bitmask |= CREATION_DATE_SET_MASK;\r\n        }\r\n        return getCreationDate;\r\n    }\r\n\r\n    @Override\r\n    public long getLastAccessDate() throws IOException {\r\n        if ((bitmask & LAST_ACCESS_SET_MASK) == 0) {\r\n            getLastAccessDate = file.getLastAccessDate();\r\n            bitmask |= LAST_ACCESS_SET_MASK;\r\n        }\r\n        return getLastAccessDate;\r\n    }\r\n\r\n    @Override\r\n    public boolean isSymlink() {\r\n        if ((bitmask & SYMLINK_SET_MASK) == 0) {\r\n            if (file.isSymlink()) {\r\n                bitmask |= SYMLINK_VALUE_MASK;\r\n            } else {\r\n                bitmask &= ~SYMLINK_VALUE_MASK;\r\n            }\r\n            bitmask |= SYMLINK_SET_MASK;\r\n        }\r\n        return (bitmask & SYMLINK_VALUE_MASK) != 0;\r\n    }\r\n\r\n    @Override\r\n    public boolean isDirectory() {\r\n        if ((bitmask & DIRECTORY_SET_MASK) == 0) {\r\n            if (isFileAttributesSupported()) {\r\n                getFileAttributes(file);\r\n            }\r\n        // Note: getFileAttributes() might fail to retrieve file attributes, so we need to test isDirectorySet again\r\n            if ((bitmask & DIRECTORY_SET_MASK) == 0) {\r\n                if (file.isDirectory()) {\r\n                    bitmask |= DIRECTORY_VALUE_MASK;\r\n                } else {\r\n                    bitmask &= ~DIRECTORY_VALUE_MASK;\r\n                }\r\n                bitmask |= DIRECTORY_SET_MASK;\r\n            }\r\n        }\r\n        return (bitmask & DIRECTORY_VALUE_MASK) != 0;\r\n    }\r\n\r\n    private boolean isFileAttributesSupported() {\r\n        return GET_FILE_ATTRIBUTES_AVAILABLE && FileProtocols.FILE.equals(file.getURL().getScheme());\r\n    }\r\n\r\n    @Override\r\n    public boolean isArchive() {\r\n        if ((bitmask & ARCHIVE_SET_MASK) == 0) {\r\n            if (file.isArchive()) {\r\n                bitmask |= ARCHIVE_VALUE_MASK;\r\n            } else {\r\n                bitmask &= ~ARCHIVE_VALUE_MASK;\r\n            }\r\n            bitmask |= ARCHIVE_SET_MASK;\r\n        }\r\n        return (bitmask & ARCHIVE_VALUE_MASK) != 0;\r\n    }\r\n\r\n    @Override\r\n    public boolean isHidden() {\r\n        if ((bitmask & HIDDEN_SET_MASK) == 0) {\r\n            if (isFileAttributesSupported()) {\r\n                getFileAttributes(file);\r\n            }\r\n        // Note: getFileAttributes() might fail to retrieve file attributes, so we need to test isDirectorySet again\r\n            if ((bitmask & HIDDEN_SET_MASK) == 0) {\r\n                if (file.isHidden()) {\r\n                    bitmask |= HIDDEN_VALUE_MASK;\r\n                } else {\r\n                    bitmask &= ~HIDDEN_VALUE_MASK;\r\n                }\r\n                bitmask |= HIDDEN_SET_MASK;\r\n            }\r\n        }\r\n        return (bitmask & HIDDEN_VALUE_MASK) != 0;\r\n    }\r\n\r\n    @Override\r\n    public boolean isExecutable() {\r\n        if ((bitmask & EXECUTABLE_SET_MASK) == 0) {\r\n            if (file.isExecutable()) {\r\n                bitmask |= EXECUTABLE_VALUE_MASK;\r\n            } else {\r\n                bitmask &= ~EXECUTABLE_VALUE_MASK;\r\n            }\r\n            bitmask |= EXECUTABLE_SET_MASK;\r\n        }\r\n        return (bitmask & EXECUTABLE_VALUE_MASK) != 0;\r\n    }\r\n\r\n    @Override\r\n    public String getAbsolutePath() {\r\n        if ((bitmask & ABSOLUTE_PATH_SET_MASK) == 0) {\r\n            getAbsolutePath = file.getAbsolutePath();\r\n            bitmask |= ABSOLUTE_PATH_SET_MASK;\r\n        }\r\n        return getAbsolutePath;\r\n    }\r\n\r\n    @Override\r\n    public String getCanonicalPath() {\r\n        if ((bitmask & CANONICAL_PATH_SET_MASK) == 0) {\r\n            getCanonicalPath = file.getCanonicalPath();\r\n            bitmask |= CANONICAL_PATH_SET_MASK;\r\n        }\r\n        return getCanonicalPath;\r\n    }\r\n\r\n    @Override\r\n    public String getExtension() {\r\n        if ((bitmask & EXTENSION_SET_MASK) == 0) {\r\n            getExtension = file.getExtension();\r\n            bitmask |= EXTENSION_SET_MASK;\r\n        }\r\n        return getExtension;\r\n    }\r\n\r\n    @Override\r\n    public String getName() {\r\n        if ((bitmask & NAME_SET_MASK) == 0) {\r\n            getName = file.getName();\r\n            bitmask |= NAME_SET_MASK;\r\n        }\r\n        return getName;\r\n    }\r\n\r\n    @Override\r\n    public long getFreeSpace() throws IOException {\r\n        if ((bitmask & FREE_SPACE_SET_MASK) == 0) {\r\n            getFreeSpace = file.getFreeSpace();\r\n            bitmask |= FREE_SPACE_SET_MASK;\r\n        }\r\n        return getFreeSpace;\r\n    }\r\n\r\n    @Override\r\n    public long getTotalSpace() throws IOException {\r\n        if ((bitmask & TOTAL_SPACE_SET_MASK) == 0) {\r\n            getTotalSpace = file.getTotalSpace();\r\n            bitmask |= TOTAL_SPACE_SET_MASK;\r\n        }\r\n        return getTotalSpace;\r\n    }\r\n\r\n    @Override\r\n    public boolean exists() {\r\n        if ((bitmask & EXISTS_SET_MASK) == 0) {\r\n            if (isFileAttributesSupported()) {\r\n                getFileAttributes(file);\r\n            }\r\n        // Note: getFileAttributes() might fail to retrieve file attributes, so we need to test isDirectorySet again\r\n            if ((bitmask & EXISTS_SET_MASK) == 0) {\r\n                if (file.exists()) {\r\n                    bitmask |= EXISTS_VALUE_MASK;\r\n                } else {\r\n                    bitmask &= ~EXISTS_VALUE_MASK;\r\n                }\r\n                bitmask |= EXISTS_SET_MASK;\r\n            }\r\n        }\r\n        return (bitmask & EXISTS_VALUE_MASK) != 0;\r\n    }\r\n\r\n    @Override\r\n    public FilePermissions getPermissions() {\r\n        if ((bitmask & PERMISSIONS_SET_MASK) == 0) {\r\n            getPermissions = file.getPermissions();\r\n            bitmask |= PERMISSIONS_SET_MASK;\r\n        }\r\n        return getPermissions;\r\n    }\r\n\r\n    @Override\r\n    public String getPermissionsString() {\r\n        if ((bitmask & PERMISSIONS_STRING_SET_MASK) == 0) {\r\n            getPermissionsString = file.getPermissionsString();\r\n            bitmask |= PERMISSIONS_STRING_SET_MASK;\r\n        }\r\n        return getPermissionsString;\r\n    }\r\n\r\n    @Override\r\n    public String getOwner() {\r\n        if ((bitmask & OWNER_SET_MASK) == 0) {\r\n            getOwner = file.getOwner();\r\n            bitmask |= OWNER_SET_MASK;\r\n        }\r\n        return getOwner;\r\n    }\r\n\r\n    @Override\r\n    public String getGroup() {\r\n        if ((bitmask & GROUP_SET_MASK) == 0) {\r\n            getGroup = file.getGroup();\r\n            bitmask |= GROUP_SET_MASK;\r\n        }\r\n        return getGroup;\r\n    }\r\n\r\n    @Override\r\n    public boolean isRoot() {\r\n        if ((bitmask & IS_ROOT_SET_MASK) == 0) {\r\n            if (file.isRoot()) {\r\n                bitmask |= IS_ROOT_VALUE_MASK;\r\n            } else {\r\n                bitmask &= ~IS_ROOT_VALUE_MASK;\r\n            }\r\n            bitmask |= IS_ROOT_SET_MASK;\r\n        }\r\n        return (bitmask & IS_ROOT_VALUE_MASK) != 0;\r\n    }\r\n\r\n\r\n    @Override\r\n    public AbstractFile getParent() {\r\n        if ((bitmask & PARENT_SET_MASK) == 0) {\r\n            getParent = file.getParent();\r\n            // create a CachedFile instance around the file if recursion is enabled\r\n            if ((bitmask & RECURSE_INSTANCES_MASK) != 0 && getParent != null) {\r\n                getParent = new CachedFile(getParent, true);\r\n            }\r\n            bitmask |= PARENT_SET_MASK;\r\n        }\r\n        return getParent;\r\n    }\r\n\r\n    @Override\r\n    public AbstractFile getRoot() {\r\n        if ((bitmask & GET_ROOT_SET_MASK) == 0) {\r\n            getRoot = file.getRoot();\r\n            // create a CachedFile instance around the file if recursion is enabled\r\n            if ((bitmask & RECURSE_INSTANCES_MASK) != 0) {\r\n                getRoot = new CachedFile(getRoot, true);\r\n            }\r\n            bitmask |= GET_ROOT_SET_MASK;\r\n        }\r\n        return getRoot;\r\n    }\r\n\r\n    @Override\r\n    public AbstractFile getCanonicalFile() {\r\n        if ((bitmask & CANONICAL_FILE_SET_MASK) == 0) {\r\n            getCanonicalFile = file.getCanonicalFile();\r\n            // create a CachedFile instance around the file if recursion is enabled\r\n            if ((bitmask & RECURSE_INSTANCES_MASK) != 0) {\r\n                // AbstractFile#getCanonicalFile() may return 'this' if the file is not a symlink. In that case,\r\n                // no need to create a new CachedFile, simply use this one. \r\n                if (getCanonicalFile == file) {\r\n                    getCanonicalFile = this;\r\n                } else {\r\n                    getCanonicalFile = new CachedFile(getCanonicalFile, true);\r\n            }\r\n            }\r\n            bitmask |= CANONICAL_FILE_SET_MASK;\r\n        }\r\n        return getCanonicalFile;\r\n    }\r\n\r\n\r\n    @Override\r\n    public AbstractFile[] ls() throws IOException {\r\n        // Don't cache ls() result but create a CachedFile instance around each of the files if recursion is enabled\r\n        AbstractFile[] files = file.ls();\r\n\r\n        if ((bitmask & RECURSE_INSTANCES_MASK) != 0) {\r\n            return createCachedFiles(files);\r\n        }\r\n\r\n        return files;\r\n    }\r\n\r\n    @Override\r\n    public AbstractFile[] ls(FileFilter filter) throws IOException {\r\n        // Don't cache ls() result but create a CachedFile instance around each of the files if recursion is enabled\r\n        AbstractFile[] files = file.ls(filter);\r\n\r\n        if ((bitmask & RECURSE_INSTANCES_MASK) != 0) {\r\n            return createCachedFiles(files);\r\n        }\r\n\r\n        return files;\r\n    }\r\n\r\n    @Override\r\n    public AbstractFile[] ls(FilenameFilter filter) throws IOException {\r\n        // Don't cache ls() result but create a CachedFile instance around each of the files if recursion is enabled\r\n        AbstractFile[] files = file.ls(filter);\r\n\r\n        if ((bitmask & RECURSE_INSTANCES_MASK) != 0) {\r\n            return createCachedFiles(files);\r\n        }\r\n\r\n        return files;\r\n    }\r\n\r\n\r\n    @Override\r\n    public boolean isFileOperationSupported(FileOperation op) {\r\n        int bitMask = 1 << op.ordinal();\r\n        if ((supportedOperationsCachedMask & bitMask) != 0) {\r\n            return (supportedOperationsValuesMask & bitMask) != 0;\r\n        }\r\n        boolean result = super.isFileOperationSupported(op);\r\n        if (result) {\r\n            supportedOperationsValuesMask |= bitMask;\r\n        }\r\n        supportedOperationsCachedMask |= bitMask;\r\n        return result;\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/DebugFile.java",
    "content": "package com.mucommander.commons.file.impl;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FilePermissions;\nimport java.io.IOException;\nimport java.util.Random;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * DebugFile is a {@link ProxyFile} to be used for debugging purposes. It allows to track the calls made to\n * {@link com.mucommander.commons.file.AbstractFile} methods that are commonly I/O-bound, by logging calls to each of those\n * methods. It also allows to slow those methods down to simulate a slow filesystem.\n *\n * @author Maxence Bernard\n */\npublic class DebugFile extends ProxyFile {\n    private static final Logger LOGGER = LoggerFactory.getLogger(DebugFile.class);\n\n    /** Maximum latency in milliseconds */\n    private int maxLatency;\n\n    /** Used to randomize the latency for each calls to slowed-down methods */\n    private static final Random random = new Random();\n\n    \n    /**\n     * Creates a DebugFile that proxies the calls made to the given AbstractFile's methods, with no latency.\n     *\n     * @param file the AbstractFile to proxy and debug\n     */\n    public DebugFile(AbstractFile file) {\n        this(file, 0);\n    }\n\n    /**\n     * Creates a DebugFile that proxies the calls made to the given AbstractFile and slows those methods down by\n     * simulating latency by making I/O bound methods wait.\n     *\n     * @param file the AbstractFile to proxy and debug\n     * @param maxLatency the maximum amount of latency in milliseconds\n     */\n    public DebugFile(AbstractFile file, int maxLatency) {\n        super(file);\n        \n        this.maxLatency = maxLatency;\n    }\n\n\n    /**\n     * Sets the the maximum amount of latency in milliseconds to add to calls made to IO-bound AbstractFile methods\n     * (i.e. those that are overridden by this class). The latency is randomized for each method call and uniformly \n     * distributed, the specified value serving as the maximum.\n     *\n     * @param maxLatency the maximum amount of latency in milliseconds to add to IO-bound AbstractFile method calls\n     * (those overridden by this class).\n     */\n    public void setMaxLatency(int maxLatency) {\n        this.maxLatency = maxLatency;\n    }\n\n\n    /**\n     * Sleeps a random number of milliseconds, up to {@link #maxLatency}.\n     */\n    private void lag() {\n        if (maxLatency > 0) {\n            try {\n                Thread.sleep(random.nextInt(maxLatency));\n            } catch(InterruptedException ignored) {}\n        }\n    }\n\n    /**\n     * Returns the debug string printed for all calls made to the AbstractFile methods overridden by this class.\n     */\n    private String getDebugString() {\n        return \"called on \"+super.getAbsolutePath()+\" (\"+file.getClass().getName()+\")\";\n    }\n\n\n    @Override\n    public long getLastModifiedDate() {\n        LOGGER.trace(getDebugString());\n        lag();\n\n        return super.getLastModifiedDate();\n    }\n\n    @Override\n    public long getSize() {\n        LOGGER.trace(getDebugString());\n        lag();\n\n        return super.getSize();\n    }\n\n    @Override\n    public boolean exists() {\n        LOGGER.trace(getDebugString());\n        lag();\n\n        return super.exists();\n    }\n\n    @Override\n    public boolean isDirectory() {\n        LOGGER.trace(getDebugString());\n        lag();\n\n        return super.isDirectory();\n    }\n\n    @Override\n    public boolean isSymlink() {\n        LOGGER.trace(getDebugString());\n        lag();\n\n        return super.isSymlink();\n    }\n\n    @Override\n    public long getFreeSpace() throws IOException {\n        LOGGER.trace(getDebugString());\n        lag();\n\n        return super.getFreeSpace();\n    }\n\n    @Override\n    public long getTotalSpace() throws IOException {\n        LOGGER.trace(getDebugString());\n        lag();\n\n        return super.getTotalSpace();\n    }\n\n    @Override\n    public String getName() {\n        LOGGER.trace(getDebugString());\n        lag();\n\n        return super.getName();\n    }\n\n    @Override\n    public String getExtension() {\n        LOGGER.trace(getDebugString());\n        lag();\n\n        return super.getExtension();\n    }\n\n    @Override\n    public String getAbsolutePath() {\n        LOGGER.trace(getDebugString());\n        lag();\n\n        return super.getAbsolutePath();\n    }\n\n    @Override\n    public String getCanonicalPath() {\n        LOGGER.trace(getDebugString());\n        lag();\n\n        return super.getCanonicalPath();\n    }\n\n    @Override\n    public AbstractFile getCanonicalFile() {\n        LOGGER.trace(getDebugString());\n        lag();\n\n        return super.getCanonicalFile();\n    }\n\n    @Override\n    public boolean isArchive() {\n        LOGGER.trace(getDebugString());\n        lag();\n\n        return super.isArchive();\n    }\n\n    @Override\n    public boolean isHidden() {\n        LOGGER.trace(getDebugString());\n        lag();\n\n        return super.isHidden();\n    }\n\n    @Override\n    public FilePermissions getPermissions() {\n        LOGGER.trace(getDebugString());\n        lag();\n\n        return super.getPermissions();\n    }\n\n    @Override\n    public String getOwner() {\n        LOGGER.trace(getDebugString());\n        lag();\n\n        return super.getOwner();\n    }\n\n    @Override\n    public String getGroup() {\n        LOGGER.trace(getDebugString());\n        lag();\n\n        return super.getGroup();\n    }\n\n    @Override\n    public AbstractFile getRoot() {\n        LOGGER.trace(getDebugString());\n        lag();\n\n        return super.getRoot();\n    }\n\n    @Override\n    public boolean isRoot() {\n        LOGGER.trace(getDebugString());\n        lag();\n\n        return super.isRoot();\n    }\n\n    @Override\n    public boolean equalsCanonical(Object f) {\n        LOGGER.trace(getDebugString());\n        lag();\n\n        return super.equals(f);\n    }\n\n    public String toString() {\n        LOGGER.trace(getDebugString());\n        lag();\n\n        return super.toString();\n    }\n\n    @Override\n    public AbstractFile getParent() {\n        LOGGER.trace(getDebugString());\n        lag();\n\n        return super.getParent();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/ProxyFile.java",
    "content": "package com.mucommander.commons.file.impl;\n\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.file.filter.FileFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\nimport com.mucommander.commons.io.FileTransferException;\nimport com.mucommander.commons.io.RandomAccessInputStream;\nimport com.mucommander.commons.io.RandomAccessOutputStream;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.PushbackInputStream;\nimport java.lang.reflect.Method;\nimport java.net.MalformedURLException;\nimport java.net.URL;\n\n/**\n * ProxyFile is an {@link AbstractFile} that acts as a proxy between the class that extends it\n * and the proxied <code>AbstractFile</code> instance specified to the constructor.\n * All <code>AbstractFile</code> public methods (abstract or not) are delegated to the proxied file.\n * The {@link #getProxiedFile()} method allows to retrieve the proxied file instance.\n *\n * <p>This class is useful for wrapper files, such as {@link com.mucommander.commons.file.AbstractArchiveFile archive files},\n * that provide additional functionality over an existing <code>AbstractFile</code> instance (the proxied file).\n * By implementing/overriding every <code>AbstractFile</code> methods, <code>ProxyFile</code> ensures that\n * all <code>AbstractFile</code> methods can safely be used, even if they are overridden by the proxied\n * file instance's class.\n *\n * <p><b>Implementation note:</b> the <code>java.lang.reflect.Proxy</code> class can unfortunately not be\n * used as it only works with interfaces (not abstract class). There doesn't seem to be any dynamic way to\n * proxy method invocations, so any modifications made to {@link com.mucommander.commons.file.AbstractFile} must be also\n * reflected in <code>ProxyFile</code>.\n *\n * @see com.mucommander.commons.file.AbstractArchiveFile\n * @author Maxence Bernard\n */\npublic abstract class ProxyFile extends AbstractFile {\n    private static Logger logger;\n\n    /** The proxied file instance */\n    protected AbstractFile file;\n\n\n    /**\n     * Creates a new ProxyFile using the given file to delegate AbstractFile method calls to.\n     *\n     * @param file the file to be proxied\n     */\n    public ProxyFile(AbstractFile file) {\n        super(file.getURL());\n        this.file = file;\n    }\n\n    /**\n     * Returns the <code>AbstractFile</code> instance proxied by this <code>ProxyFile</code>.\n     *\n     * @return the <code>AbstractFile</code> instance proxied by this <code>ProxyFile</code>\n     */\n    public AbstractFile getProxiedFile() {\n        return file;\n    }\n\n\n    @Override\n    public long getLastModifiedDate() {\n        return file.getLastModifiedDate();\n    }\n\n    @Override\n    public void setLastModifiedDate(long lastModified) throws IOException {\n        file.setLastModifiedDate(lastModified);\n    }\n\n    @Override\n    public long getSize() {\n        return file.getSize();\n    }\n\n    @Override\n    public AbstractFile getParent() {\n        return file.getParent();\n    }\n\n    @Override\n    public void setParent(AbstractFile parent) {\n        file.setParent(parent);\n    }\n\n    @Override\n    public boolean exists() {\n        return file.exists();\n    }\n\n    @Override\n    public void changePermission(int access, int permission, boolean enabled) throws IOException {\n        file.changePermission(access, permission, enabled);\n    }\n\n    @Override\n    public String getOwner() {\n        return file.getOwner();\n    }\n\n    @Override\n    public boolean canGetOwner() {\n        return file.canGetOwner();\n    }\n\n    @Override\n    public String getGroup() {\n        return file.getGroup();\n    }\n\n    @Override\n    public boolean canGetGroup() {\n        return file.canGetGroup();\n    }\n\n    @Override\n    public boolean isDirectory() {\n        return file.isDirectory();\n    }\n\n    @Override\n    public boolean isSymlink() {\n        return file.isSymlink();\n    }\n\n    @Override\n    public boolean isSystem() {\n        return file.isSystem();\n    }\n\n    @Override\n    public AbstractFile[] ls() throws IOException {\n        return file.ls();\n    }\n\n    @Override\n    public void mkdir() throws IOException {\n        file.mkdir();\n    }\n\n    @Override\n    public InputStream getInputStream() throws IOException {\n        return file.getInputStream();\n    }\n\n    @Override\n    public OutputStream getOutputStream() throws IOException {\n        return file.getOutputStream();\n    }\n\n    @Override\n    public OutputStream getAppendOutputStream() throws IOException {\n        return file.getAppendOutputStream();\n    }\n\n    @Override\n    public RandomAccessInputStream getRandomAccessInputStream() throws IOException {\n        return file.getRandomAccessInputStream();\n    }\n\n    @Override\n    public RandomAccessOutputStream getRandomAccessOutputStream() throws IOException {\n        return file.getRandomAccessOutputStream();\n    }\n\n    @Override\n    public void delete() throws IOException {\n        file.delete();\n    }\n\n    @Override\n    public void copyRemotelyTo(AbstractFile destFile) throws IOException {\n        file.copyRemotelyTo(destFile);\n    }\n\n    @Override\n    public void renameTo(AbstractFile destFile) throws IOException {\n        file.renameTo(destFile);\n    }\n\n    @Override\n    public long getFreeSpace() throws IOException {\n        return file.getFreeSpace();\n    }\n\n    @Override\n    public long getTotalSpace() throws IOException {\n        return file.getTotalSpace();\n    }\n\n    @Override\n    public Object getUnderlyingFileObject() {\n        return file.getUnderlyingFileObject();\n    }\n\n    \n    /////////////////////////////////////\n    // Overridden AbstractFile methods //\n    /////////////////////////////////////\n\n    @Override\n    public boolean isFileOperationSupported(FileOperation op) {\n        Class<? extends AbstractFile> thisClass = getClass();\n        Method opMethod = op.getCorrespondingMethod(thisClass);\n        // If the method corresponding to the file operation has been overridden by this class (a ProxyFile subclass),\n        // check the presence of the UnsupportedFileOperation annotation in this class.\n        try {\n            if (!thisClass.getMethod(opMethod.getName(), opMethod.getParameterTypes()).getDeclaringClass().equals(ProxyFile.class)) {\n                return AbstractFile.isFileOperationSupported(op, thisClass);\n            }\n        } catch(Exception e) {\n            // Should never happen, unless AbstractFile method signatures have changed and FileOperation has not been updated\n            getLogger().warn(\"Exception caught, this should not have happened\", e);\n        }\n\n        // Otherwise, check for the presence of the UnsupportedFileOperation annotation in the wrapped AbstractFile.\n        return file.isFileOperationSupported(op);\n    }\n\n    @Override\n    public FileURL getURL() {\n        return file.getURL();\n    }\n\n    @Override\n    public URL getJavaNetURL() throws MalformedURLException {\n        return file.getJavaNetURL();\n    }\n\n    @Override\n    public String getName() {\n        return file.getName();\n    }\n\n    @Override\n    public String getExtension() {\n        return file.getExtension();\n    }\n\n    @Override\n    public String getBaseName() {\n    \treturn file.getBaseName();\n    }\n    \n    @Override\n    public String getAbsolutePath() {\n        return file.getAbsolutePath();\n    }\n\n    @Override\n    public String getCanonicalPath() {\n        return file.getCanonicalPath();\n    }\n\n    @Override\n    public AbstractFile getCanonicalFile() {\n        return file.getCanonicalFile();\n    }\n\n    @Override\n    public String getSeparator() {\n        return file.getSeparator();\n    }\n\n    @Override\n    public boolean isArchive() {\n        return file.isArchive();\n    }\n\n    @Override\n    public boolean isHidden() {\n        return file.isHidden();\n    }\n\n    @Override\n    public FilePermissions getPermissions() {\n        return file.getPermissions();\n    }\n\n    @Override\n    public void changePermissions(int permissions) throws IOException {\n        file.changePermissions(permissions);\n    }\n\n    @Override\n    public PermissionBits getChangeablePermissions() {\n        return file.getChangeablePermissions();\n    }\n\n    @Override\n    public String getPermissionsString() {\n        return file.getPermissionsString();\n    }\n\n    @Override\n    public AbstractFile getRoot() {\n        return file.getRoot();\n    }\n\n    @Override\n    public boolean isRoot() {\n        return file.isRoot();\n    }\n\n    @Override\n    public AbstractFile getVolume() {\n        return file.getVolume();\n    }\n\n    @Override\n    public InputStream getInputStream(long offset) throws IOException {\n        return file.getInputStream(offset);\n    }\n\n    @Override\n    public void copyStream(InputStream in, boolean append, long length) throws FileTransferException {\n        file.copyStream(in, append, length);\n    }\n\n    @Override\n    public AbstractFile[] ls(FileFilter filter) throws IOException {\n        return file.ls(filter);\n    }\n\n    @Override\n    public AbstractFile[] ls(FilenameFilter filter) throws IOException {\n        return file.ls(filter);\n    }\n\n    @Override\n    public void mkfile() throws IOException {\n        file.mkfile();\n    }\n\n    @Override\n    public void deleteRecursively() throws IOException {\n        file.deleteRecursively();\n    }\n\n    @Override\n    public boolean equals(Object f) {\n        return file.equals(f);\n    }\n\n    @Override\n    public boolean equalsCanonical(Object f) {\n        return file.equalsCanonical(f);\n    }\n\n    public int hashCode() {\n        return file.hashCode();\n    }\n\n    public String toString() {\n        return file.toString();\n    }\n\n    @Override\n    public short getReplication() throws UnsupportedFileOperationException {\n        return file.getReplication();\n    }\n\n    @Override\n    public long getBlocksize() throws UnsupportedFileOperationException {\n        return file.getBlocksize();\n    }\n\n    @Override\n    public void changeReplication(short replication) throws IOException {\n        file.changeReplication(replication);\n    }\n\n    @Override\n    public boolean isExecutable() {\n    \treturn file.isExecutable();\n    }\n\n    @Override\n    public PushbackInputStream getPushBackInputStream(int bufferSize) throws IOException {\n    \treturn file.getPushBackInputStream(bufferSize);\n    }\n\n    @Override\n    public void closePushbackInputStream() throws IOException {\n    \tfile.closePushbackInputStream();\n    }\n\n    @Override\n    public boolean isLocalFile() {\n        return super.isLocalFile();\n    }\n\n    @Override\n    public long getCreationDate() throws IOException {\n        return super.getCreationDate();\n    }\n\n    @Override\n    public long getLastAccessDate() throws IOException {\n        return super.getLastAccessDate();\n    }\n\n    @Override\n    public boolean canRead() {\n        return file.canRead();\n    }\n\n    private static Logger getLogger() {\n        if (logger == null) {\n            logger = LoggerFactory.getLogger(ProxyFile.class);\n        }\n        return logger;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/SevenZipJBindingROArchiveFile.java",
    "content": "package com.mucommander.commons.file.impl;\r\n\r\nimport com.mucommander.commons.file.*;\r\nimport com.mucommander.commons.file.impl.sevenzip.SevenZipArchiveFile.ExtractCallback;\r\nimport com.mucommander.commons.file.impl.sevenzip.SignatureCheckedRandomAccessFile;\r\nimport com.mucommander.commons.file.impl.sevenzip.multivolume.InArchiveWrapper;\r\nimport com.mucommander.commons.file.impl.sevenzip.multivolume.SevenZipMultiVolumeCallbackHandler;\r\nimport com.mucommander.commons.file.impl.sevenzip.multivolume.SevenZipRarMultiVolumeCallbackHandler;\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.commons.util.CircularByteBuffer;\r\nimport net.sf.sevenzipjbinding.*;\r\nimport net.sf.sevenzipjbinding.impl.VolumedArchiveInStream;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport java.io.File;\r\nimport java.io.IOException;\r\nimport java.io.InputStream;\r\nimport java.util.ArrayList;\r\nimport java.util.Date;\r\nimport java.util.List;\r\nimport java.util.regex.Pattern;\r\n\r\npublic class SevenZipJBindingROArchiveFile extends AbstractROArchiveFile {\r\n    private static final Logger LOGGER = LoggerFactory.getLogger(SevenZipJBindingROArchiveFile.class);\r\n\r\n    private static final Pattern MULTI_PART_RAR_PATTERN = Pattern.compile(\"[.]part\\\\d+[.]rar\");\r\n\r\n    private static final String MULTI_PART_7Z_EXT = \".7z.001\";\r\n\r\n   private static volatile boolean libraryInit = false;\r\n    protected IInArchive inArchive;\r\n    private ArchiveFormat sevenZipJBindingFormat;\r\n    private final SevenZipArchiveFormatDetector formatDetector;\r\n\r\n    private final byte[] formatSignature;\r\n\r\n    /**\r\n     * Creates an AbstractROArchiveFile on top of the given file.\r\n     *\r\n     * @param file the file on top of which to create the archive\r\n     *\r\n     * @see <a href=\"http://sevenzipjbind.sourceforge.net/javadoc/net/sf/sevenzipjbinding/ArchiveFormat.html\">\r\n     *      ArchiveFormat</a>\r\n     */\r\n    public SevenZipJBindingROArchiveFile(AbstractFile file, ArchiveFormat sevenZipJBindingFormat, byte[] formatSignature) {\r\n        super(file);\r\n        this.sevenZipJBindingFormat = sevenZipJBindingFormat;\r\n        this.formatSignature = formatSignature;\r\n        this.formatDetector = null;\r\n        initSevenZipBindings();\r\n    }\r\n\r\n    public SevenZipJBindingROArchiveFile(AbstractFile file, SevenZipArchiveFormatDetector formatDetector) {\r\n        super(file);\r\n        this.sevenZipJBindingFormat = null;\r\n        this.formatSignature = new byte[] {};\r\n        this.formatDetector = formatDetector;\r\n        initSevenZipBindings();\r\n    }\r\n\r\n\r\n    private static void initSevenZipBindings() {\r\n        if (!libraryInit) {\r\n            synchronized (SevenZipJBindingROArchiveFile.class) {\r\n                try {\r\n                    if (OsFamily.getCurrent() == OsFamily.MAC_OS_X && OsFamily.isAarch64()) {\r\n                        SevenZip.initSevenZipFromPlatformJAR(\"Mac-arm64\");\r\n                    } else if (OsFamily.getCurrent() == OsFamily.MAC_OS_X) {\r\n                        SevenZip.initSevenZipFromPlatformJAR(\"Mac-x86_64\");\r\n                    } else if (OsFamily.getCurrent() == OsFamily.LINUX && OsFamily.isAmd64()) {\r\n                        SevenZip.initSevenZipFromPlatformJAR(\"Linux-amd64\");\r\n                    } else if (OsFamily.getCurrent() == OsFamily.LINUX) {\r\n                        SevenZip.initSevenZipFromPlatformJAR(\"Linux-i386\");\r\n                    } else {\r\n                        System.out.println(\"Arch: \" + System.getProperty(\"os.arch\"));\r\n                        SevenZip.initSevenZipFromPlatformJAR();\r\n                    }\r\n                    libraryInit = true;\r\n                } catch (SevenZipNativeInitializationException ex) {\r\n                    throw new RuntimeException(\"Unable to init 7-Zip-JBinding library bindings\", ex);\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n//    private IInArchive openInArchive() throws IOException {\r\n//        if (inArchive == null) {\r\n//            if (formatDetector != null) {\r\n//                sevenZipJBindingFormat = formatDetector.detect(file);\r\n//            }\r\n//            SignatureCheckedRandomAccessFile in = new SignatureCheckedRandomAccessFile(file, formatSignature);\r\n//            inArchive = SevenZip.openInArchive(sevenZipJBindingFormat, in);\r\n//        }\r\n//        return inArchive;\r\n//    }\r\n\r\n    /**\r\n     * Open the file and check its signature compared to the one provided in {@link #SevenZipJBindingROArchiveFile(AbstractFile, ArchiveFormat, byte[])}\r\n     * @return this {@code SevenZipJBindingROArchiveFile} instance when file signature matches the specified signature\r\n     * @throws IOException in case the file cannot be opened or its signature differs from the specified signature\r\n     */\r\n    public SevenZipJBindingROArchiveFile check() throws IOException {\r\n        openInArchive();\r\n        return this;\r\n    }\r\n\r\n    private IInArchive openInArchive() throws IOException {\r\n        if (inArchive == null) {\r\n            boolean multiPartRar = MULTI_PART_RAR_PATTERN.matcher(file.getName()).find();\r\n            boolean multiPartSevenZip = file.getName().toLowerCase().endsWith(MULTI_PART_7Z_EXT);\r\n\r\n\r\n//            if (formatDetector != null) {\r\n//                sevenZipJBindingFormat = formatDetector.detect(file);\r\n//            }\r\n//            SignatureCheckedRandomAccessFile in = new SignatureCheckedRandomAccessFile(file, formatSignature);\r\n//            inArchive = SevenZip.openInArchive(sevenZipJBindingFormat, in);\r\n\r\n            if (multiPartRar) {\r\n                SevenZipRarMultiVolumeCallbackHandler handler = new SevenZipRarMultiVolumeCallbackHandler(formatSignature, password);\r\n                IInStream firstStream = handler.getStream(file.getAbsolutePath());\r\n                IInArchive tmpInArchive = SevenZip.openInArchive(sevenZipJBindingFormat, firstStream, handler);\r\n                inArchive = new InArchiveWrapper(tmpInArchive, handler);\r\n            } else if (multiPartSevenZip) {\r\n                SevenZipMultiVolumeCallbackHandler handler = new SevenZipMultiVolumeCallbackHandler(formatSignature, file, password);\r\n                IInArchive tmpInArchive = SevenZip.openInArchive(sevenZipJBindingFormat, new VolumedArchiveInStream(handler));\r\n                if (isEnc(tmpInArchive) && password == null) {\r\n                    // Throwing this exception to trigger password dialog\r\n                    throw new IOException(String.format(\"Password protected file but password is null [file = %s]\", file.getName()));\r\n                }\r\n                inArchive = new InArchiveWrapper(tmpInArchive, handler);\r\n            } else {\r\n                SignatureCheckedRandomAccessFile in = new SignatureCheckedRandomAccessFile(file, formatSignature);\r\n                IInArchive tmpInArchive = SevenZip.openInArchive(sevenZipJBindingFormat, in, password);\r\n                inArchive = new InArchiveWrapper(tmpInArchive, in);\r\n            }\r\n        }\r\n        return inArchive;\r\n    }\r\n\r\n    private boolean isEnc(IInArchive archive) {\r\n        try {\r\n            if (Boolean.TRUE.equals(archive.getArchiveProperty(PropID.ENCRYPTED))) {\r\n                return true;\r\n            }\r\n\r\n            for (int i = 0; i < archive.getNumberOfItems(); i++) {\r\n                if (Boolean.TRUE.equals(archive.getProperty(i, PropID.ENCRYPTED))) {\r\n                    return true;\r\n                }\r\n            }\r\n        } catch (SevenZipException e) {\r\n            LOGGER.error(\"Error checking if file is encrypted\", e);\r\n        }\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public ArchiveEntryIterator getEntryIterator() throws IOException {\r\n        try {\r\n            final IInArchive sevenZipFile = openInArchive();\r\n            int nbEntries = sevenZipFile.getNumberOfItems();\r\n            List<ArchiveEntry> entries = new ArrayList<>();\r\n            for (int i = 0; i < nbEntries; i++) {\r\n                entries.add(createArchiveEntry(i));\r\n            }\r\n            return new WrapperArchiveEntryIterator(entries.iterator());\r\n        } catch (SevenZipException e) {\r\n            throw new IOException(e);\r\n        } finally {\r\n            try {\r\n                if (inArchive != null) {\r\n                    inArchive.close();\r\n                }\r\n            } catch (SevenZipException e) {\r\n                LOGGER.error(\"Error closing archive\", e);\r\n            }\r\n            inArchive = null;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public InputStream getEntryInputStream(ArchiveEntry entry, ArchiveEntryIterator entryIterator) {\r\n        final int[] in = new int[1];\r\n        in[0] = (Integer)entry.getEntryObject();\r\n        final CircularByteBuffer cbb = new CircularByteBuffer(CircularByteBuffer.INFINITE_SIZE);\r\n        new Thread(() -> {\r\n            synchronized (SevenZipJBindingROArchiveFile.this) {\r\n                try {\r\n                    final IInArchive sevenZipFile = openInArchive();\r\n                    sevenZipFile.extract(in, false, new ExtractCallback(inArchive, cbb.getOutputStream()));\r\n                } catch (IOException e) {\r\n                    LOGGER.error(\"Can't open archive\", e);\r\n                } finally {\r\n                    if (inArchive != null) {\r\n                        try {\r\n                            inArchive.close();\r\n                        } catch (SevenZipException e) {\r\n                            LOGGER.error(\"Can't close archive\", e);\r\n                        }\r\n                    }\r\n                    try {\r\n                        cbb.getOutputStream().close();\r\n                    } catch (IOException e) {\r\n                        LOGGER.error(\"Can't close outputstream\", e);\r\n                    }\r\n                    inArchive = null;\r\n                }\r\n            }\r\n        }).start();\r\n\r\n        return cbb.getInputStream();\r\n    }\r\n\r\n    /**\r\n     * Creates and return an {@link ArchiveEntry()} whose attributes are fetched from the given {@link com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZipEntry}\r\n     *\r\n     * @param i the index of entry\r\n     * @return an ArchiveEntry whose attributes are fetched from the given SevenZipEntry\r\n     */\r\n    private ArchiveEntry createArchiveEntry(int i) throws IOException {\r\n        final IInArchive sevenZipFile = openInArchive();\r\n        String path = sevenZipFile.getStringProperty(i, PropID.PATH);\r\n        boolean isDirectory = (Boolean)sevenZipFile.getProperty(i, PropID.IS_FOLDER);\r\n        Date time = (Date) sevenZipFile.getProperty(i, PropID.LAST_MODIFICATION_TIME);\r\n        Long size = (Long) sevenZipFile.getProperty(i, PropID.SIZE);\r\n        if (org.apache.commons.lang.StringUtils.isEmpty(path)) {\r\n            path = file.getNameWithoutExtension();\r\n        }\r\n        path = path.replace(File.separatorChar, ArchiveEntry.SEPARATOR_CHAR);\r\n        ArchiveEntry result = new ArchiveEntry(path, isDirectory,\r\n                time == null ? -1 : time.getTime(),\r\n                size == null ? -1 : size, true);\r\n        result.setEntryObject(i);\r\n        return result;\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/adb/AdbFile.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.impl.adb;\n\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.io.RandomAccessInputStream;\nimport com.mucommander.commons.io.RandomAccessOutputStream;\nimport se.vidstige.jadb.JadbConnection;\nimport se.vidstige.jadb.JadbDevice;\nimport se.vidstige.jadb.JadbException;\nimport se.vidstige.jadb.RemoteFile;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @author Oleg Trifonov\n * Created on 09/09/15.\n */\npublic class AdbFile extends ProtocolFile {\n\n\tprivate final RemoteFile remoteFile;\n\tprivate List<RemoteFile> childs;\n\tprivate AbstractFile parent;\n\tprivate JadbConnection jadbConnection;\n\tprivate String rootFolder;\n\n\tprivate static FileURL lastModifiedPath;        // FIXME that's a bad way to detect directory changes\n\n\t/**\n\t * Creates a new file instance with the given URL.\n\t *\n\t * @param url the FileURL instance that represents this file's location\n\t */\n\tprivate AdbFile(FileURL url, RemoteFile remoteFile) throws IOException {\n\t\tsuper(url);\n\n\t\tif (remoteFile == null) {\n\t\t\tJadbDevice device = getDevice(url);\n\t\t\tif (device == null) {\n\t\t\t\tthrow new IOException(\"ADB file error\");\n\t\t\t}\n\n\t\t\tString path = url.getPath();\n\t\t\tif (path.isEmpty() || \"\\\\\".equals(path)) {\n\t\t\t\tpath = \"/\";\n\t\t\t}\n\t\t\tremoteFile = tryLs(device, path);\n\t\t\tif (remoteFile == null && \"/\".equals(path)) {\n\t\t\t\tremoteFile = tryLs(device, \"/sdcard/\");\n\t\t\t\tif (remoteFile != null) {\n\t\t\t\t\trootFolder = \"/sdcard/\";\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (remoteFile == null && \"/\".equals(path)) {\n\t\t\t\tremoteFile = tryLs(device, \"/mnt/sdcard/\");\n\t\t\t\tif (remoteFile != null) {\n\t\t\t\t\trootFolder = \"/mnt/sdcard/\";\n\t\t\t\t}\n\t\t\t}\n\t\t\tcloseConnection();\n\t\t} else {\n\t\t\tif (remoteFile.isDirectory()) {\n\t\t\t\trebuildChildrenList(url);\n\t\t\t}\n\t\t}\n\t\tif (rootFolder == null) {\n\t\t\trootFolder = \"/\";\n\t\t}\n\t\tthis.remoteFile = remoteFile;\n\t}\n\n\tprivate RemoteFile tryLs(JadbDevice device, String path) throws IOException {\n\t\tRemoteFile result = null;\n\t\ttry {\n\t\t\tList<RemoteFile> files = device.list(path);\n\t\t\tchilds = new ArrayList<>();\n\t\t\tfor (RemoteFile rf : files) {\n\t\t\t\tif (\".\".equals(rf.getPath())) {\n\t\t\t\t\tresult = rf;\n\t\t\t\t} else {\n\t\t\t\t\tchilds.add(rf);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (JadbException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate void rebuildChildrenList(FileURL url) throws IOException {\n\t\ttry {\n\t\t\tJadbDevice device = getDevice(url);\n\t\t\tList<RemoteFile> files = device.list(\"/\" + url.getPath());\n\t\t\tchilds = new ArrayList<>();\n\t\t\tfor (RemoteFile rf : files) {\n\t\t\t\tif (!\".\".equals(rf.getPath())) {\n\t\t\t\t\tchilds.add(rf);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (JadbException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t\tcloseConnection();\n\t}\n\n\tJadbDevice getDevice(FileURL url) throws IOException {\n\t\tcloseConnection();\n\t\tjadbConnection = new JadbConnection();\n\t\tJadbDevice device = null;\n\t\ttry {\n\t\t\tList<JadbDevice> devices = jadbConnection.getDevices();\n\t\t\tfinal String host = url.getHost();\n\t\t\tfor (JadbDevice dev : devices) {\n\t\t\t\tif (dev.getSerial().equalsIgnoreCase(host)) {\n\t\t\t\t\tdevice = dev;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (JadbException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t\treturn device;\n\t}\n\n\tprivate void closeConnection() {\n\t\tif (jadbConnection != null) {\n\t\t\tjadbConnection = null;\n\t\t}\n\t}\n\n\n\tAdbFile(FileURL url) throws IOException {\n\t\tthis(url, null);\n\t}\n\n\n\t@Override\n\tpublic long getLastModifiedDate() {\n\t\tif (remoteFile == null) {\n\t\t\treturn 0;\n\t\t}\n\t\treturn remoteFile.getLastModified();\n\t}\n\n\t@Override\n\tpublic void setLastModifiedDate(long lastModified) {\n\t}\n\n\t@Override\n\tpublic void changeReplication(short replication) throws IOException {\n\n\t}\n\n\t@Override\n\tpublic long getSize() {\n\t\tif (remoteFile == null) {\n\t\t\treturn 0;\n\t\t}\n\t\treturn remoteFile.getSize();\n\t}\n\n\t@Override\n\tpublic AbstractFile getParent() {\n\t\tif (parent == null && !\"/\".equals(getURL().getPath())) {\n\t\t\ttry {\n\t\t\t\tparent = new AdbFile(getURL().getParent(), null);\n\t\t\t} catch (IOException e) {\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\t\t}\n\t\treturn parent;\n\t}\n\n\t@Override\n\tpublic void setParent(AbstractFile parent) {\n\n\t}\n\n\t@Override\n\tpublic boolean exists() {\n\t\tAdbFile adbParent = (AdbFile) getParent();\n\t\tif (adbParent == null || adbParent.childs == null) {\n\t\t\tString path = getURL().getPath();\n\t\t\treturn \"/\".equals(path);\n\t\t}\n\t\tfor (RemoteFile rf : adbParent.childs) {\n\t\t\tif (getName().equals(rf.getPath())) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic FilePermissions getPermissions() {\n\t\treturn childs == null ? FilePermissions.DEFAULT_FILE_PERMISSIONS : FilePermissions.DEFAULT_DIRECTORY_PERMISSIONS;\n\t\t// TODO !!!\n\t}\n\n\t@Override\n\tpublic PermissionBits getChangeablePermissions() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic void changePermission(int access, int permission, boolean enabled) {\n\n\t}\n\n\t@Override\n\tpublic String getOwner() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic short getReplication() {\n\t\treturn 0;\n\t}\n\n\t@Override\n\tpublic long getBlocksize() {\n\t\treturn 0;\n\t}\n\n\t@Override\n\tpublic boolean canGetOwner() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic String getGroup() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic boolean canGetGroup() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic boolean isDirectory() {\n\t\treturn remoteFile == null || remoteFile.isDirectory();\n\t}\n\n\t@Override\n\tpublic boolean isSymlink() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic boolean isSystem() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic AbstractFile[] ls() throws IOException {\n\t\tif (getURL().equals(lastModifiedPath)) {\n\t\t\trebuildChildrenList(lastModifiedPath);\n\t\t\tlastModifiedPath = null;\n\t\t}\n\t\tif (childs == null) {\n\t\t\treturn null;\n\t\t}\n\t\tAbstractFile[] result = new AbstractFile[childs.size() - 1];  // skip \"..\"\n\t\tint index = 0;\n\t\tfor (RemoteFile rf : childs) {\n\t\t\tif (\"..\".equals(rf.getPath())) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tFileURL url = FileURL.getFileURL(getURL() + rootFolder + rf.getPath());\n\t\t\tAdbFile adbFile = new AdbFile(url, rf);\n\t\t\tadbFile.parent = this;\n\t\t\tresult[index++] = adbFile;\n\t\t}\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic void mkdir() throws IOException {\n\t\tJadbDevice device = getDevice(getURL());\n\t\tif (device == null) {\n\t\t\tcloseConnection();\n\t\t\tthrow new IOException(\"file not found: \" + getURL());\n\t\t}\n\t\ttry {\n\t\t\tdevice.executeShell(\"mkdir\", getURL().getPath());\n\t\t} catch (JadbException e) {\n\t\t\tthrow new IOException(e);\n\t\t}\n\t\t// TODO    doesn't work without this delay    FIXME\n\t\ttry {\n\t\t\tThread.sleep(10);\n\t\t} catch (InterruptedException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t\tcloseConnection();\n\t\tif (getParent() instanceof AdbFile) {\n\t\t\tAdbFile parent = (AdbFile) getParent();\n\t\t\tlastModifiedPath = parent.getURL();\n\t\t\tparent.rebuildChildrenList(parent.getURL());\n\t\t}\n\t}\n\n\t@Override\n\tpublic InputStream getInputStream() throws IOException {\n\t\treturn new AdbInputStream(this);\n\t}\n\n\t@Override\n\tpublic OutputStream getOutputStream() throws IOException {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic OutputStream getAppendOutputStream() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic RandomAccessInputStream getRandomAccessInputStream() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic RandomAccessOutputStream getRandomAccessOutputStream() {\n\t\treturn null;\n\t}\n\n\tprivate void finishFileOperation() throws IOException {\n\t\t// TODO    doesn't work without this delay    FIXME\n\t\ttry {\n\t\t\tThread.sleep(10);\n\t\t} catch (InterruptedException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t\tcloseConnection();\n\t\tif (getParent() instanceof AdbFile) {\n\t\t\tAdbFile parent = (AdbFile) getParent();\n\t\t\tlastModifiedPath = parent.getURL();\n\t\t\tparent.rebuildChildrenList(parent.getURL());\n\t\t}\n\t}\n\n\n\t@Override\n\tpublic void delete() throws IOException {\n\t\tJadbDevice device = getDevice(getURL());\n\t\tif (device == null) {\n\t\t\tcloseConnection();\n\t\t\tthrow new IOException(\"file not found: \" + getURL());\n\t\t}\n\t\ttry {\n\t\t\tif (isDirectory()) {\n\t\t\t\tdevice.executeShell(\"rmdir\", getURL().getPath());\n\t\t\t} else {\n\t\t\t\tdevice.executeShell(\"rm\", getURL().getPath());\n\t\t\t}\n\t\t} catch (JadbException e) {\n\t\t\tcloseConnection();\n\t\t\te.printStackTrace();\n\t\t\tthrow new IOException(e);\n\t\t}\n\t\tfinishFileOperation();\n\t}\n\n\t@Override\n\tpublic void renameTo(AbstractFile destFile) throws IOException {\n\t\tJadbDevice device = getDevice(getURL());\n\t\tif (device == null) {\n\t\t\tcloseConnection();\n\t\t\tthrow new IOException(\"file not found: \" + getURL());\n\t\t}\n\t\ttry {\n\t\t\tdevice.executeShell(\"mv\", getURL().getPath(), destFile.getURL().getPath());\n\t\t} catch (JadbException e) {\n\t\t\tthrow new IOException(e);\n\t\t}\n\t\tfinishFileOperation();\n\t}\n\n\t@Override\n\tpublic void copyRemotelyTo(AbstractFile destFile) {\n\n\t}\n\n\t@Override\n\tpublic long getFreeSpace() {\n\t\treturn 0;\n\t}\n\n\t@Override\n\tpublic long getTotalSpace() {\n\t\treturn 0;\n\t}\n\n\t@Override\n\tpublic Object getUnderlyingFileObject() {\n\t\treturn null;\n\t}\n\n\n\t@Override\n\tpublic boolean isFileOperationSupported(FileOperation op) {\n\t\treturn op != FileOperation.WRITE_FILE && super.isFileOperationSupported(op);\n\t}\n\n\tpublic void pushTo(AbstractFile destFile) throws IOException {\n\t\tJadbDevice device = getDevice(getURL());\n\t\tif (device == null) {\n\t\t\tcloseConnection();\n\t\t\tthrow new IOException(\"file not found: \" + getURL());\n\t\t}\n\t\ttry {\n\t\t\tdevice.pull(new RemoteFile(getURL().getPath()), destFile.getOutputStream());\n\t\t} catch (JadbException e) {\n\t\t\tthrow new IOException(e);\n\t\t}\n\t\tcloseConnection();\n\t}\n\n\tpublic void pullFrom(AbstractFile sourceFile) throws IOException {\n\t\tJadbDevice device = getDevice(getURL());\n\t\tif (device == null) {\n\t\t\tcloseConnection();\n\t\t\tthrow new IOException(\"file not found: \" + getURL());\n\t\t}\n\t\tlong lastModified = sourceFile.getLastModifiedDate();\n\t\tint mode = 0664;\n\t\ttry {\n\t\t\tdevice.push(sourceFile.getInputStream(), lastModified, mode, new RemoteFile(getURL().getPath()));\n\t\t} catch (JadbException e) {\n\t\t\tcloseConnection();\n\t\t\te.printStackTrace();\n\t\t\tthrow new IOException(e);\n\t\t}\n\t\tcloseConnection();\n\t\tfinishFileOperation();\n//        try {\n//            Thread.sleep(100);\n//        } catch (InterruptedException e) {\n//            e.printStackTrace();\n//        }\n//        if (getParent() instanceof AdbFile) {\n//            AdbFile parent = (AdbFile)getParent();\n//            lastModifiedPath = parent.getURL();\n//            parent.rebuildChildrenList(parent.getURL());\n//        }\n\t}\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/adb/AdbInputStream.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.impl.adb;\n\nimport se.vidstige.jadb.JadbDevice;\nimport se.vidstige.jadb.JadbException;\nimport se.vidstige.jadb.RemoteFile;\n\nimport java.io.*;\nimport java.util.Random;\n\n/**\n * @author Oleg Trifonov\n * Created on 29/12/15.\n */\npublic class AdbInputStream extends InputStream {\n\n    private static final long MAX_CACHED_SIZE = 10*1024*1024;\n\n    private final ByteArrayOutputStream bos;\n    private InputStream is;\n    private final File tempFile;\n\n    AdbInputStream(AdbFile file) throws IOException {\n        this.bos = file.getSize() <= MAX_CACHED_SIZE ? new ByteArrayOutputStream() : null;\n        this.tempFile = bos == null ? File.createTempFile(file.getName(), \"\"+System.currentTimeMillis() + \"-\" + new Random().nextInt(0xffff)) : null;\n\n        JadbDevice device = file.getDevice(file.getURL());\n        if (device == null) {\n            throw new IOException(\"file not found: \" + file.getURL());\n        }\n        if (bos != null) {\n            try {\n                device.pull(new RemoteFile(file.getURL().getPath()), bos);\n            } catch (JadbException e) {\n                close();\n                throw new IOException(e);\n            }\n        } else {\n            try {\n                device.pull(new RemoteFile(file.getURL().getPath()), new FileOutputStream(tempFile));\n            } catch (JadbException e) {\n                close();\n                throw new IOException(e);\n            }\n        }\n    }\n\n    @Override\n    public int read() throws IOException {\n        if (is == null) {\n            is = bos != null ? new ByteArrayInputStream(bos.toByteArray()) : new FileInputStream(tempFile);\n        }\n        return is.read();\n    }\n\n    @Override\n    public synchronized void reset() throws IOException {\n        super.reset();\n        if (is != null) {\n            is.reset();\n        }\n    }\n\n    @Override\n    public void close() throws IOException {\n        super.close();\n        if (is != null) {\n            is.close();\n        }\n        if (bos != null) {\n            bos.close();\n        }\n        if (tempFile != null) {\n            tempFile.delete();\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/adb/AdbProtocolProvider.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.impl.adb;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.ProtocolProvider;\n\nimport java.io.IOException;\n\n/**\n * @author Oleg Trifonov\n * Created on 09/09/15.\n */\npublic class AdbProtocolProvider implements ProtocolProvider {\n    @Override\n    public AbstractFile getFile(FileURL url, Object... instantiationParams) throws IOException {\n        return new AdbFile(url);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/ar/ArArchiveEntryIterator.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.file.impl.ar;\n\nimport com.mucommander.commons.file.ArchiveEntry;\nimport com.mucommander.commons.file.ArchiveEntryIterator;\nimport com.mucommander.commons.io.StreamUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * An <code>ArchiveEntryIterator</code> that iterates through an AR archive. \n *\n * @author Maxence Bernard\n */\nclass ArArchiveEntryIterator implements ArchiveEntryIterator {\n    private static final Logger LOGGER = LoggerFactory.getLogger(ArArchiveEntryIterator.class);\n\n    /** InputStream to the the archive file */\n    private InputStream in;\n\n    /** The current entry, where the stream is currently positionned */\n    private ArchiveEntry currentEntry;\n\n    /** GNU variant: extended filenames contained in the special // entry's data */\n    private byte gnuExtendedNames[];\n\n\n    /**\n     * Creates a new <code>ArArchiveEntryIterator</code> that parses the given AR <code>InputStream</code>.\n     * The <code>InputStream</code> will be closed by {@link #close()}.\n     *\n     * @param in an AR archive <code>InputStream</code>\n     * @throws IOException if an I/O error occurred while initializing this iterator\n     */\n    ArArchiveEntryIterator(InputStream in) throws IOException {\n        this.in = in;\n\n        // Skip the global header: \"!<arch>\" string followed by LF char (8 characters in total).\n        StreamUtils.skipFully(in, 8);\n    }\n\n\n    /**\n     * Reads the next file header and returns an {@link ArchiveEntry} representing the entry.\n     *\n     * @return an ArchiveEntry representing the entry\n     * @throws IOException if an error occurred\n     */\n    ArchiveEntry getNextEntry() throws IOException {\n        byte fileHeader[] = new byte[60];\n\n        try {\n            // Fully read the 60 file header bytes. If it cannot be read, it most likely means we've reached\n            // the end of the archive.\n            StreamUtils.readFully(in, fileHeader);\n        }catch(IOException e) {\n            return null;\n        }\n\n        try {\n            // Read the 16 filename characters and trim string to remove any trailing white space\n            String name = new String(fileHeader, 0, 16).trim();\n\n            // Read the 12 file date characters, trim string to remove any trailing white space\n            // and parse date as a long.\n            // If the entry is the special // GNU one (see below), date is null and thus should not be parsed\n            // (would throw a NumberFormatException)\n            long date = name.equals(\"//\")?0:Long.parseLong(new String(fileHeader, 16, 12).trim()) * 1000;\n\n            // No use for file's Owner ID, Group ID and mode at the moment, skip them\n\n            // Read the 10 file size characters, trim string to remove any trailing white space\n            // and parse size as a long\n            long size = Long.parseLong(new String(fileHeader, 48, 10).trim());\n\n            // BSD variant : BSD ar store extended filenames by placing the string \"#1/\" followed by the file name length\n            // in the file name field, and appending the real filename to the file header.\n            if (name.startsWith(\"#1/\")) {\n                // Read extended name\n                int extendedNameLength = Integer.parseInt(name.substring(3));\n                name = new String(StreamUtils.readFully(in, new byte[extendedNameLength])).trim();\n                // Decrease remaining file size\n                size -= extendedNameLength;\n            }\n            // GNU variant: GNU ar stores multiple extended filenames in the data section of a file with the name \"//\",\n            // this record is referred to by future headers. A header references an extended filename by storing a \"/\"\n            // followed by a decimal offset to the start of the filename in the extended filename data section.\n            // This entry appears first in the archive, i.e. before any other entries.\n            else if(name.equals(\"//\")) {\n                this.gnuExtendedNames = StreamUtils.readFully(in, new byte[(int)size]);\n\n                // Skip one padding byte if size is odd\n                if (size % 2 != 0) {\n                    StreamUtils.skipFully(in, 1);\n                }\n\n                // Don't return this entry which should not be visible, but recurse to return next entry instead\n                return getNextEntry();\n            }\n            // GNU variant: entry with an extended name, look up extended name in // entry\n            else if(this.gnuExtendedNames!=null && name.startsWith(\"/\")) {\n                int off = Integer.parseInt(name.substring(1));\n                name = \"\";\n                byte b;\n                while((b=this.gnuExtendedNames[off++])!='/') {\n                    name += (char)b;\n                }\n            }\n\n            return new ArchiveEntry(name, false, date, size, true);\n        } catch(IOException e) {\n            // Re-throw IOException\n            LOGGER.info(\"Caught IOException\", e);\n            throw e;\n        } catch(Exception e2) {\n            // Catch any other exceptions (NumberFormatException for instance) and throw an IOException instead\n            LOGGER.info(\"Caught Exception\", e2);\n\n            throw new IOException();\n        }\n    }\n\n\n    /////////////////////////////////////////\n    // ArchiveEntryIterator implementation //\n    /////////////////////////////////////////\n\n    public ArchiveEntry nextEntry() throws IOException {\n        if(currentEntry!=null) {\n            // Skip the current entry's data, plus 1 padding byte if size is odd\n            long size = currentEntry.getSize();\n            StreamUtils.skipFully(in, size + (size%2));\n        }\n\n        // Get the next entry, if any\n        currentEntry = getNextEntry();\n\n        return currentEntry;\n    }\n\n    public void close() throws IOException {\n        in.close();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/ar/ArArchiveFile.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.commons.file.impl.ar;\n\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.io.BoundedInputStream;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * ArArchiveFile provides read-only access to archives in the unix AR format. Both the BSD and GNU variants (which adds\n * support for extended filenames) are supported.\n *\n * @see com.mucommander.commons.file.impl.ar.ArFormatProvider\n * @author Maxence Bernard\n */\npublic class ArArchiveFile extends AbstractROArchiveFile {\n    private static final Logger LOGGER = LoggerFactory.getLogger(ArArchiveFile.class);\n\n    /**\n     * Creates a ArArchiveFile around the given file.\n     *\n     * @param file the underlying file to wrap this archive file around\n     */\n    public ArArchiveFile(AbstractFile file) {\n        super(file);\n    }\n\n\n    ////////////////////////////////////////\n    // AbstractArchiveFile implementation //\n    ////////////////////////////////////////\n\n    @Override\n    public ArchiveEntryIterator getEntryIterator() throws IOException {\n        return new ArArchiveEntryIterator(getInputStream());\n    }\n\n    @Override\n    public InputStream getEntryInputStream(ArchiveEntry entry, ArchiveEntryIterator entryIterator) throws IOException {\n        InputStream in = getInputStream();\n        ArchiveEntryIterator iterator = new ArArchiveEntryIterator(in);\n\n        ArchiveEntry currentEntry;\n        while((currentEntry = iterator.nextEntry())!=null) {\n            if(currentEntry.getName().equals(entry.getName())) {\n                LOGGER.trace(\"found entry {}\", entry.getName());\n                return new BoundedInputStream(in, entry.getSize(), false);\n            }\n        }\n\n        // Entry not found, should not normally happen\n        LOGGER.info(\"Warning: entry not found, throwing IOException\");\n        throw new IOException();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/ar/ArFormatProvider.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.commons.file.impl.ar;\n\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.ArchiveFormatProvider;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\n\nimport java.io.IOException;\n\n/**\n * This class is the provider for the 'Ar' archive format implemented by {@link ArArchiveFile}.\n *\n * @see com.mucommander.commons.file.impl.ar.ArArchiveFile\n * @author Nicolas Rinaudo, Maxence Bernard\n */\npublic class ArFormatProvider implements ArchiveFormatProvider {\n\n    private static final String[] EXTENSIONS = {\".ar\", \".a\", \".deb\", \".udeb\"};\n\n    /**\n     * Static instance of the filename filter that matches archive filenames\n     */\n    private static final ExtensionFilenameFilter FILENAME_FILTER = new ExtensionFilenameFilter(EXTENSIONS);\n\n    @Override\n    public AbstractArchiveFile getFile(AbstractFile file) throws IOException {\n        return new ArArchiveFile(file);\n    }\n\n    @Override\n    public FilenameFilter getFilenameFilter() {\n        return FILENAME_FILTER;\n    }\n\n    @Override\n    public String[] getFileExtensions() {\n        return EXTENSIONS;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/ar/package.html",
    "content": "<body>\n  Provides an implementation of the AR archive format.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/arj/ArjFormatProvider.java",
    "content": "package com.mucommander.commons.file.impl.arj;\n\nimport java.io.IOException;\n\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.ArchiveFormatProvider;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\nimport com.mucommander.commons.file.impl.SevenZipJBindingROArchiveFile;\n\nimport net.sf.sevenzipjbinding.ArchiveFormat;\n\npublic class ArjFormatProvider implements ArchiveFormatProvider {\n\n    private static final String[] EXTENSIONS =  { \".arj\" };\n\n    /**\n     * Static instance of the filename filter that matches archive filenames\n     */\n    private final static ExtensionFilenameFilter FILENAME_FILTER = new ExtensionFilenameFilter(EXTENSIONS);\n\n    private final static byte[] SIGNATURE = { 0x60, (byte) 0xEA };\n\n    @Override\n    public AbstractArchiveFile getFile(AbstractFile file) throws IOException {\n        return new SevenZipJBindingROArchiveFile(file, ArchiveFormat.ARJ, SIGNATURE);\n    }\n\n    @Override\n    public FilenameFilter getFilenameFilter() {\n        return FILENAME_FILTER;\n    }\n\n    @Override\n    public String[] getFileExtensions() {\n        return EXTENSIONS;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/avrdude/AvrConfigFileUtils.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.impl.avrdude;\n\n\nimport java.io.*;\nimport java.util.Properties;\n\n/**\n * @author Oleg Trifonov\n * Created on 23/03/16.\n */\npublic class AvrConfigFileUtils {\n\n    private static final String KEY_DEVICE_NAME = \"avr_device_name\";\n    private static final String KEY_PROGRAMMER = \"programmer\";\n    private static final String KEY_VERIFY = \"verify\";\n    private static final String KEY_PORT = \"port\";\n    private static final String KEY_BAUDRATE = \"baudrate\";\n    private static final String KEY_BITCLOCK = \"bitclock\";\n    private static final String KEY_CONFIG_FILE = \"config_file\";\n    private static final String KEY_AUTOERASE = \"autoerase\";\n    private static final String KEY_ISP_CLOCK_DELAY = \"isp_clock_delay\";\n    private static final String KEY_OVERRIDE_INVALID_SIGNATURE_CHECK = \"override_invalid_signature_check\";\n    private static final String KEY_EXTENDED_PARAM = \"extended_param\";\n    private static final String KEY_AVRDUDE_PATH = \"avrdude_path\";\n\n\n    public static AvrdudeConfiguration load(String filePath) throws IOException {\n        Properties properties = new Properties();\n        Reader reader = new BufferedReader(new FileReader(filePath));\n        properties.load(reader);\n        AvrdudeConfiguration result = load(properties);\n        reader.close();\n        return result;\n    }\n\n\n    public static AvrdudeConfiguration load(InputStream is) throws IOException {\n        Properties properties = new Properties();\n        properties.load(is);\n        AvrdudeConfiguration result = load(properties);\n        is.close();\n        return result;\n    }\n\n\n    private static AvrdudeConfiguration load(Properties properties) {\n        String deviceName = properties.getProperty(KEY_DEVICE_NAME);\n        Integer baudrate = getPropertyInt(properties, KEY_BAUDRATE);\n        Integer bitclock = getPropertyInt(properties, KEY_BITCLOCK);\n        String configFile = properties.getProperty(KEY_CONFIG_FILE);\n        String programmer = properties.getProperty(KEY_PROGRAMMER);\n        boolean flashAutoerase = getPropertyBool(properties, KEY_AUTOERASE, true);\n        Integer ispCockDelay = getPropertyInt(properties, KEY_ISP_CLOCK_DELAY);\n        String port = properties.getProperty(KEY_PORT);\n        boolean overrideInvalidSignatureCheck = getPropertyBool(properties, KEY_OVERRIDE_INVALID_SIGNATURE_CHECK, false);\n\n        boolean verify = getPropertyBool(properties, KEY_VERIFY, true);\n        String extendedParam = properties.getProperty(KEY_EXTENDED_PARAM);\n        String avrdudeLocation = properties.getProperty(KEY_AVRDUDE_PATH);\n\n        return new AvrdudeConfiguration(deviceName, baudrate, bitclock, configFile, programmer,\n                flashAutoerase, ispCockDelay, port, overrideInvalidSignatureCheck, verify, extendedParam, avrdudeLocation);\n    }\n\n\n\n\n    private static Integer getPropertyInt(Properties properties, String key) {\n        try {\n            return Integer.parseInt(properties.getProperty(key));\n        } catch (Throwable t) {\n            return null;\n        }\n    }\n\n\n    private static boolean getPropertyBool(Properties properties, String key, boolean defaultValue) {\n        String val = properties.getProperty(key);\n        if (val == null) {\n            return defaultValue;\n        }\n        if (\"true\".equalsIgnoreCase(val) || \"yes\".equalsIgnoreCase(val)) {\n            return true;\n        } else if (\"false\".equalsIgnoreCase(val) || \"no\".equalsIgnoreCase(val)) {\n            return false;\n        }\n        return defaultValue;\n    }\n\n\n    public static void save(AvrdudeConfiguration config, String filePath) throws IOException {\n        BufferedWriter writer = new BufferedWriter(new FileWriter(filePath));\n        writer.write(build(config));\n        writer.close();\n    }\n\n\n    private static String build(AvrdudeConfiguration config) {\n        StringBuilder sb = new StringBuilder();\n\n        sb.append(\"### trolCommander avrdude configuration\\n\\n\");\n\n        sb.append(\"# Required. Specify AVR device. (etc. \\\"m8\\\", \\\"m32\\\", \\\"t13\\\")\\n\");\n        addRequiredParam(sb, KEY_DEVICE_NAME, config.deviceName);\n\n        sb.append(\"# Specify programmer type. (etc \\\"usbasp\\\")\\n\");\n        addRequiredParam(sb, KEY_PROGRAMMER, config.programmer);\n\n        sb.append(\"# Verify after write\\n\");\n        addRequiredParam(sb, KEY_VERIFY, config.verify);\n\n        sb.append(\"# Specify connection port\\n\");\n        addOptionalParam(sb, KEY_PORT, config.port);\n\n        sb.append(\"# RS-232 baud rate\\n\");\n        addOptionalParam(sb, KEY_BAUDRATE, config.baudrate);\n\n        sb.append(\"# JTAG/STK500v2 bit clock period (us)\\n\");\n        addOptionalParam(sb, KEY_BITCLOCK, config.bitclock);\n\n        sb.append(\"# Specify location of configuration file\\n\");\n        addOptionalParam(sb, KEY_CONFIG_FILE, config.configFile);\n\n        sb.append(\"# Enable auto erase for flash memory\\n\");\n        addRequiredParam(sb, KEY_AUTOERASE, config.flashAutoerase);\n\n        sb.append(\"# ISP Clock Delay [in microseconds]\\n\");\n        addOptionalParam(sb, KEY_ISP_CLOCK_DELAY, config.ispCockDelay);\n\n        sb.append(\"# Override invalid signature check.\\n\");\n        addRequiredParam(sb, KEY_OVERRIDE_INVALID_SIGNATURE_CHECK, config.overrideInvalidSignatureCheck);\n\n        sb.append(\"# Pass extended_param to programmer.\\n\");\n        addOptionalParam(sb, KEY_EXTENDED_PARAM, config.extendedParam);\n\n        sb.append(\"# Path to avrdude\\n\");\n        addOptionalParam(sb, KEY_AVRDUDE_PATH, config.avrdudeLocation);\n\n        return sb.toString();\n    }\n\n\n    private static void addRequiredParam(StringBuilder sb, String name, Object value) {\n        sb.append(name);\n        sb.append(\" = \");\n        sb.append(value);\n        sb.append(\"\\n\\n\");\n    }\n\n    private static void addOptionalParam(StringBuilder sb, String name, Object value) {\n        if (value != null) {\n            sb.append(name);\n            sb.append(\" = \");\n            sb.append(value);\n        } else {\n            sb.append(\"# \");\n            sb.append(name);\n            sb.append(\" = \");\n        }\n        sb.append(\"\\n\\n\");\n    }\n\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/avrdude/AvrDudeInputStream.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.impl.avrdude;\n\nimport com.mucommander.commons.HasProgress;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * @author Oleg Trifonov\n * Created on 31/03/16.\n */\npublic class AvrDudeInputStream extends InputStream implements HasProgress {\n\n    private StreamType type;\n    private Avrdude avrdude;\n    private AvrdudeConfiguration config;\n    private Avrdude.Operation operation;\n    private ByteArrayInputStream data;\n\n\n    public AvrDudeInputStream(StreamType type, AvrdudeConfiguration config, Avrdude.Operation operation) {\n        this.type = type;\n        this.config = config;\n        this.operation = operation;\n        this.avrdude = new Avrdude();\n    }\n\n\n    @Override\n    public int read() throws IOException {\n        if (avrdude.getStatus() == Avrdude.Status.NONE) {\n            readAll();\n        }\n        return data.read();\n    }\n\n    @Override\n    public int available() {\n        return data.available();\n    }\n\n    @Override\n    public synchronized void reset() {\n        data.reset();\n    }\n\n    @Override\n    public boolean markSupported() {\n        return data.markSupported();\n    }\n\n    @Override\n    public synchronized void mark(int readlimit) {\n        data.mark(readlimit);\n    }\n\n    @Override\n    public int getProgress() {\n        return avrdude.getProgress();\n    }\n\n    @Override\n    public boolean hasProgress() {\n        return true;\n    }\n\n    void readAll() throws IOException {\n        try {\n            avrdude.execute(config, operation, type);\n            avrdude.waitFor();\n            if (type == StreamType.HEX) {\n                data = new ByteArrayInputStream(avrdude.getHexOutput().getBytes());\n            }\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n            throw new IOException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/avrdude/Avrdude.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.impl.avrdude;\n\nimport com.mucommander.command.Command;\nimport com.mucommander.commons.io.StreamUtils;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.process.*;\nimport org.apache.commons.lang.StringUtils;\n\nimport java.io.*;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @author Oleg Trifonov\n * Created on 21/04/16.\n */\npublic class Avrdude {\n\n    enum Status {\n        NONE,\n        IN_PROGRESS,\n        FINISHED\n    }\n\n    public enum Operation {\n        READ_FLASH(false),\n        READ_EEPROM(false),\n        READ_SIGNATURE(false),\n        READ_FUSES(false),\n        READ_CALIBRATION(false),\n        WRITE_FLASH(true),\n        WRITE_EEPROM(true),\n        WRITE_FUSES(true),\n        WRITE_CALIBRATION(true);\n\n        final boolean isWriteOperation;\n\n        Operation(boolean isWriteOperation) {\n            this.isWriteOperation = isWriteOperation;\n        }\n    }\n\n    private volatile int progress;\n    private int exitCode;\n    private volatile Status status;\n    private AbstractProcess process;\n    private final InputStream inputStream;\n    private final ByteArrayOutputStream fullOutputStream = new ByteArrayOutputStream();\n\n    public Avrdude() {\n        this.inputStream = null;\n        status = Status.NONE;\n    }\n\n    public Avrdude(InputStream inputStream) {\n        this.inputStream = inputStream;\n        status = Status.NONE;\n    }\n\n    private static String buildCommandLine(AvrdudeConfiguration config, Operation operation, StreamType streamType) {\n        String cmd;\n\n        if (config.avrdudeLocation != null) {\n            cmd = config.avrdudeLocation;\n        } else {\n            cmd = OsFamily.WINDOWS.isCurrent() ? \"avrdude.exe\" : \"avrdude\";\n        }\n        cmd += \" -p \" + config.deviceName;\n        if (config.baudrate != null) {\n            cmd += \" -b \" + config.baudrate;\n        }\n        if (config.bitclock != null) {\n            cmd += \" -B \" + config.bitclock;\n        }\n        if (config.configFile != null) {\n            cmd += \" -C \" + config.configFile;\n        }\n        if (config.programmer != null) {\n            cmd += \" -c \" + config.programmer;\n        }\n        if (!config.flashAutoerase) {\n            cmd += \" -D\";\n        }\n        if (config.ispCockDelay != null) {\n            cmd += \" -i\" + config.ispCockDelay;\n        }\n        if (config.port != null) {\n            cmd += \" -P \" + config.port;\n        }\n        if (config.overrideInvalidSignatureCheck) {\n            cmd += \" -F\";\n        }\n        if (!config.verify) {\n            cmd += \" -V\";\n        }\n        if (config.extendedParam != null) {\n            cmd += \" -x \" + config.extendedParam;\n        }\n        AvrdudeDevice device = AvrdudeDevice.getDevice(config.deviceName);\n\n        switch (operation) {\n            case READ_FLASH:\n                return cmd + \" -u -U flash:r:-:\" + streamType.getAvrdudeName();\n            case READ_EEPROM:\n                return cmd + \" -u -U eeprom:r:-:\" + streamType.getAvrdudeName();\n            case READ_SIGNATURE:\n                return cmd + \" -u -U signature:r:-:\" + streamType.getAvrdudeName();\n            case READ_FUSES:\n                cmd += \" -u \";\n                if (device.blockSizes.containsKey(\"efuse\")) {\n                    cmd += \"-U efuse:-:\" + streamType.getAvrdudeName();\n                }\n                if (device.blockSizes.containsKey(\"hfuse\")) {\n                    cmd += \"-U hfuse:-:\" + streamType.getAvrdudeName();\n                }\n                if (device.blockSizes.containsKey(\"lfuse\")) {\n                    cmd += \"-U lfuse:-:\" + streamType.getAvrdudeName();\n                }\n                return cmd;\n            case READ_CALIBRATION:\n                return cmd + \" -u -U calibration:r:-:\" + streamType.getAvrdudeName();\n            case WRITE_FLASH:\n                return cmd + \" -u -U flash:w:-:\" + streamType.getAvrdudeName();\n            case WRITE_EEPROM:\n                return cmd + \" -u -U eeprom:w:-:\" + streamType.getAvrdudeName();\n            case WRITE_FUSES:\n                // TODO\n            case WRITE_CALIBRATION:\n                return cmd + \" -u -U calibration:w:-:\" + streamType.getAvrdudeName();\n\n        }\n        throw new RuntimeException(\"unknown operation\");\n    }\n\n\n    public void execute(AvrdudeConfiguration config, Operation operation, StreamType streamType) throws IOException, InterruptedException {\n        String cmd = buildCommandLine(config, operation, streamType);\n\n        ProcessListener processListener = new ProcessListener() {\n            int operationCount;\n            List<String> lines = new ArrayList<>();\n            int nextLineIndex = 0;\n            int progressCount = 0;\n\n            @Override\n            public void processDied(int returnValue) {\nSystem.out.println(\"--------- \");\n                for (String s : lines) {\n                    System.out.println(\"@\"+s);\n                }\n            }\n\n            @Override\n            public void processOutput(String output) {\n                String[] outLines = StringUtils.splitByWholeSeparatorPreserveAllTokens(output, \"\\n\");//output.split(\"\\n\");\n                for (int i = 0; i < outLines.length; i++) {\n                    String s = outLines[i];\n                    if (i == 0 && lines.size() > 0) {\n                        String old = lines.get(lines.size()-1);\n                        lines.set(lines.size()-1, old + s);\n                    } else {\n                        lines.add(s);\n                    }\n                }\n//if (output.equals(\"#\")) System.out.println(\">\"+output + \"  (\" + progress + \")\"); else\n//System.out.println(\">\"+output);\n                for (int i = nextLineIndex; i < lines.size(); i++) {\n                    if (i >= lines.size()) {\n                        break;\n                    }\n                    String s = lines.get(i);\n                    if (s.contains(\"Reading |\") || s.contains(\"Writing |\")) {\n                        operationCount++;\n                        nextLineIndex = i + 1;\n                    }\n//System.out.println(\"?\"+s + \"    (\" + operationCount + \")\");\n\n                }\n                if (nextLineIndex < lines.size()-1) {\n                    nextLineIndex = lines.size()-1;\n                }\n//                if (output.contains(\"Reading |\") || output.contains(\"Writing |\")) {\n//                    operationCount++;\n//                }\n                if (operationCount > 0 && status == Status.NONE) {\n                    status = Status.IN_PROGRESS;\n                    progressCount = 0;\n                    progress = 0;\n                }\n                if (status == Status.IN_PROGRESS && output.contains(\"#\")) {\n//System.out.println(\"\\n?+ \" + StringUtils.countMatches(output, \"#\"));\n                    progressCount += StringUtils.countMatches(output, \"#\")*2;\n                    if (progressCount > 100) {\n                        progress = progressCount - 100;\n                    }\n                }\n            }\n\n            @Override\n            public void processOutput(byte[] buffer, int offset, int length) {\n                fullOutputStream.write(buffer, offset, length);\n            }\n        };\n\n        String[] tokens = Command.getTokens(cmd);\n        process = ProcessRunner.execute(tokens, null, processListener, null);\n        if (inputStream != null) {\n//Thread.sleep(1000);\n            StreamUtils.copyStream(inputStream, process.getOutputStream());\n            process.getOutputStream().close();\n            inputStream.close();\n        }\n\n//        process.getOutputStream().write(\"!!!!!!!\\n\".getBytes());\n        process.getOutputStream().close();\n    }\n\n\n    public void waitFor() throws IOException, InterruptedException {\n        exitCode = process.waitFor();\n        process.waitMonitoring();\n        process.destroy();\n        status = Status.FINISHED;\n    }\n\n\n    public String getHexOutput() {\n        final String startTemplate = \"writing output file \\\"<stdout>\\\"\";\n        String fullOutput = fullOutputStream.toString();\n        int start = fullOutput.indexOf(startTemplate);\n        if (start < 0) {\n            return null;\n        }\n        start = fullOutput.indexOf(':', start + startTemplate.length());\n        int finish = fullOutput.indexOf(\"avrdude done\", start);\n        if (finish > 0) {\n            return fullOutput.substring(start-1, finish);\n        }\n        return null;//fullOutput.substring(start);\n    }\n\n\n    public int getProgress() {\n        return progress;\n    }\n\n    public Status getStatus() {\n        return status;\n    }\n\n\n\n    public static void main(String args[]) throws IOException {\n        AvrdudeConfiguration config = new AvrdudeConfiguration(\"m8\", null, null, null, \"usbasp\", true, null, null, true, false, null,\n                \"/Users/trol/-avrdude/avrdude-6.3/avrdude\");\n        //OutputStream os = new FileOutputStream(\"/Users/trol/--------.bin\");//System.out;\n\n/*\n        AvrDudeInputStream is = new AvrDudeInputStream(StreamType.HEX, config, Operation.READ_FLASH);\n        new Thread() {\n            int lastProgress = -1;\n            @Override\n            public void run() {\n                while (true) {\n                    int progress = is.getProgress();\n                    if (progress != lastProgress) {\n                        System.out.println(\"progress: \" + progress);\n                    }\n                    lastProgress = progress;\n                    if (progress == 100) {\n                        break;\n                    }\n                    try {\n                        Thread.sleep(50);\n                    } catch (InterruptedException e) {\n                    }\n                }\n            }\n        }.start();\n        StreamUtils.copyStream(is, new FileOutputStream(new File(\"/Users/trol/--------.hex\")));\n*/\n\n\n        AvrdudeOutputStream os = new AvrdudeOutputStream(StreamType.HEX, config, Operation.WRITE_FLASH);\n        new Thread() {\n            int lastProgress;\n            @Override\n            public void run() {\n                while (true) {\n                    int progress = os.getProgress();\n                    if (progress != lastProgress) {\n                        System.out.println(\"progress: \" + progress);\n                    }\n                    lastProgress = progress;\n                    if (progress == 100) {\n                        break;\n                    }\n                    try {\n                        Thread.sleep(50);\n                    } catch (InterruptedException e) {\n                    }\n                }\n            }\n        }.start();\n        StreamUtils.copyStream(new FileInputStream(new File(\"/Users/trol/--------.hex\")), os);\n        os.close();\n\n\n\n/*\n        Avrdude avrdude = new Avrdude();\n        avrdude.execute(config, Operation.READ_FLASH, StreamType.HEX);\n        new Thread() {\n            @Override\n            public void run() {\n                int progress = avrdude.progress;\n                while (progress < 100) {\n                    progress = avrdude.progress;\n                    System.out.println(\"Progress: \" + progress);\n                    try {\n                        Thread.sleep(50);\n                    } catch (InterruptedException e) {\n                        e.printStackTrace();\n                    }\n                }\n                try {\n                    avrdude.waitFor();\n                } catch (Exception e) {\n                    e.printStackTrace();\n                }\n                System.out.println(\"------------\");\n                System.out.println(avrdude.getHexOutput());\n            }\n        }.start();\n*/\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/avrdude/AvrdudeConfiguration.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.impl.avrdude;\n\n/**\n * @author Oleg Trifonov\n * Created on 23/03/16.\n */\npublic class AvrdudeConfiguration {\n    /**\n     * Required. Specify AVR device.\n     */\n    public final String deviceName;\n\n    /**\n     * Override RS-232 baud rate.\n     */\n    public final Integer baudrate;\n\n    /**\n     * Specify JTAG/STK500v2 bit clock period (us).\n     */\n    public final Integer bitclock;\n\n    /**\n     * Specify location of configuration file.\n     */\n    public final String configFile;\n\n    /**\n     * Specify programmer type.\n     */\n    public final String programmer;\n\n    /**\n     * Enable auto erase for flash memory\n     */\n    public final boolean flashAutoerase;\n\n    /**\n     * ISP Clock Delay [in microseconds]\n     */\n    public final Integer ispCockDelay;\n\n    /**\n     * Specify connection port.\n     */\n    public final String port;\n\n    /**\n     * Override invalid signature check.\n     */\n    public final boolean overrideInvalidSignatureCheck;\n\n    public final boolean verify;\n\n    /**\n     * Pass extended_param to programmer.\n     */\n    public final String extendedParam;\n\n    /**\n     * Path to avrdude\n     */\n    public final String avrdudeLocation;\n\n\n    public AvrdudeConfiguration(String deviceName, Integer baudrate, Integer bitclock, String configFile, String programmer,\n                         boolean flashAutoerase, Integer ispCockDelay, String port, boolean overrideInvalidSignatureCheck,\n                         boolean verify, String extendedParam, String avrdudeLocation) {\n        this.deviceName = deviceName;\n        this.baudrate = baudrate;\n        this.bitclock = bitclock;\n        this.configFile = configFile;\n        this.programmer = programmer;\n        this.flashAutoerase = flashAutoerase;\n        this.ispCockDelay = ispCockDelay;\n        this.port = port;\n        this.overrideInvalidSignatureCheck = overrideInvalidSignatureCheck;\n        this.verify = verify;\n        this.extendedParam = extendedParam;\n        this.avrdudeLocation = avrdudeLocation;\n    }\n\n    public AvrdudeConfiguration() {\n        this.deviceName = \"m8\";\n        this.baudrate = null;\n        this.bitclock = null;\n        this.configFile = null;\n        this.programmer = \"usbasp\";\n        this.flashAutoerase = true;\n        this.ispCockDelay = null;\n        this.port = null;\n        this.overrideInvalidSignatureCheck = false;\n        this.verify = true;\n        this.extendedParam = null;\n        this.avrdudeLocation = null;\n    }\n\n    public boolean isValid() {\n        return deviceName != null && programmer != null && !deviceName.trim().isEmpty() && !programmer.trim().isEmpty();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/avrdude/AvrdudeDevice.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.impl.avrdude;\n\nimport com.mucommander.commons.file.util.ResourceLoader;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.lang.ref.WeakReference;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * @author Oleg Trifonov\n * Created on 13/04/16.\n */\npublic class AvrdudeDevice {\n    private static final String AVRDUDE_RESOURCE_NAME = \"/avr/avrdude.csv\";\n\n    public final String id;\n    public final String name;\n    public final int signature;\n    public final Map<String, Integer> blockSizes;\n\n    private static WeakReference<Map<String, AvrdudeDevice>> devices;\n\n    private AvrdudeDevice(String id, String name, int signature, Map<String, Integer> blockSizes) {\n        this.id = id;\n        this.name = name;\n        this.signature = signature;\n        this.blockSizes = blockSizes;\n    }\n\n    public static AvrdudeDevice getDevice(String id) {\n        return getDevices().get(id);\n    }\n\n\n    private static Map<String, AvrdudeDevice> getDevices() {\n        Map<String, AvrdudeDevice> map = devices != null ? devices.get() : null;\n        if (map == null) {\n            map = load();\n            devices = new WeakReference<>(map);\n        }\n        return map;\n    }\n\n\n    private static Map<String, AvrdudeDevice> load() {\n        Map<String, AvrdudeDevice> result = new HashMap<>();\n        try (BufferedReader br = new BufferedReader(new InputStreamReader(ResourceLoader.getResourceAsStream(AVRDUDE_RESOURCE_NAME)))) {\n            String line;\n            while ((line = br.readLine()) != null) {\n                String[] parts = line.split(\":\");\n                String id = parts[0];\n                String name = parts[1];\n                int signature = Integer.parseInt(parts[2].substring(2), 16);\n                Map<String, Integer> blocks = new HashMap<>();\n                for (int blockIndex = 3; blockIndex < parts.length; blockIndex++) {\n                    String block = parts[blockIndex];\n                    int nameEndIndex = block.indexOf('[');\n                    String blockName = block.substring(0, nameEndIndex);\n                    int length = Integer.parseInt(block.substring(nameEndIndex+1, block.length()-1));\n                    blocks.put(blockName, length);\n                }\n                result.put(id, new AvrdudeDevice(id, name, signature, blocks));\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/avrdude/AvrdudeOutputStream.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.impl.avrdude;\n\nimport com.mucommander.commons.HasProgress;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * @author Oleg Trifonov\n * Created on 31/03/16.\n */\npublic class AvrdudeOutputStream extends OutputStream implements HasProgress {\n    private StreamType type;\n    private Avrdude avrdude;\n    private AvrdudeConfiguration config;\n    private Avrdude.Operation operation;\n    private ByteArrayOutputStream data;\n\n\n    public AvrdudeOutputStream(StreamType type, AvrdudeConfiguration config, Avrdude.Operation operation) {\n        this.type = type;\n        this.config = config;\n        this.operation = operation;\n        this.data = new ByteArrayOutputStream();\n    }\n\n    @Override\n    public void write(int b) throws IOException {\n        if (data == null) {\n            throw new IOException(\"Stream is closed\");\n        }\n        data.write(b);\n    }\n\n    @Override\n    public void close() throws IOException {\n        if (data == null) {\n            throw new IOException(\"Stream is closed\");\n        }\n        writeToDevice();\n        data.close();\n        data = null;\n    }\n\n    private void writeToDevice() throws IOException {\n        this.avrdude = new Avrdude(new ByteArrayInputStream(data.toByteArray()));\n        try {\n            avrdude.execute(config, operation, type);\n            avrdude.waitFor();\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n            throw new IOException(e);\n        }\n    }\n\n    @Override\n    public int getProgress() {\n        return avrdude == null ? 0 : avrdude.getProgress();\n    }\n\n    @Override\n    public boolean hasProgress() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/avrdude/AvrdudeProtocolProvider.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.impl.avrdude;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.ProtocolProvider;\nimport com.mucommander.commons.file.impl.avrdude.files.*;\n\nimport java.io.IOException;\n\n/**\n * @author Oleg Trifonov\n * Created on 09/02/16.\n */\npublic class AvrdudeProtocolProvider implements ProtocolProvider {\n\n    private static final String STORAGE_DIR = \"avr\";\n\n    @Override\n    public AbstractFile getFile(FileURL url, Object... instantiationParams) throws IOException {\n        if (isRootUrl(url)) {\n            return new AvrRootDir(url, getUrlPath(url));\n        } else if (isRootUrl(url.getParent())) {\n            return new AvrDeviceDir(url);\n        } else if (isRootUrl(url.getParent().getParent())) {\n            if (url.getFilename().equalsIgnoreCase(AvrConfigFile.FILENAME)) {\n                return new AvrConfigFile(url);\n            } else {\n                return new AvrMemoryDir(url);\n            }\n        }\n        return new AvrMemoryFile(url);\n    }\n\n    private static String getUrlPath(FileURL url) {\n        if (url == null) {\n            return null;\n        }\n        String location = url.toString();\n        int schemeDelimPos = location.indexOf(\"://\");\n        if (schemeDelimPos > 0) {\n            return location.substring(schemeDelimPos + 3);\n        }\n        return \"\";\n    }\n\n    private static boolean isRootUrl(FileURL url) {\n        if (url == null) {\n            return true;\n        }\n        final String path = getUrlPath(url);\n        return path == null || path.isEmpty() || path.equals(\"/\") || path.equals(\"\\\\\");\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/avrdude/StreamType.java",
    "content": "package com.mucommander.commons.file.impl.avrdude;\n\n/**\n * @author Oleg Trifonov\n * Created on 24/06/16.\n */\npublic enum StreamType {\n    BIN('r'),\n    HEX('i');\n\n    private final char avrdudeName;\n\n    StreamType(char avrdudeName) {\n        this.avrdudeName = avrdudeName;\n    }\n\n    public char getAvrdudeName() {\n        return avrdudeName;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/avrdude/files/AvrConfigFile.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.impl.avrdude.files;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FilePermissions;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.impl.avrdude.AvrConfigFileUtils;\nimport com.mucommander.commons.file.impl.avrdude.AvrdudeConfiguration;\nimport com.mucommander.commons.file.impl.avrdude.AvrdudeDevice;\n\nimport java.io.*;\n\n/**\n * @author Oleg Trifonov\n * Created on 24/03/16.\n */\npublic class AvrConfigFile extends AvrdudeFile {\n\n    public static final String FILENAME = \"config.properties\";\n\n    private static class ConfigInputStream extends ByteArrayInputStream {\n\n        public ConfigInputStream(String s) {\n            super(s.getBytes());\n        }\n    }\n\n    private class ConfigOutputStream extends ByteArrayOutputStream {\n        @Override\n        public void close() throws IOException {\n            AvrdudeConfiguration configuration = AvrConfigFileUtils.load(new ByteArrayInputStream(buf));\n            if (!configuration.isValid()) {\n                throw new IOException(\"wrong configuration\");\n            }\n            if (AvrdudeDevice.getDevice(configuration.deviceName) == null) {\n                throw new IOException(\"unknown device\");\n            }\n            AvrConfigFileUtils.save(configuration, getLocalConfigFile().getAbsolutePath());\n            AvrConfigFile.this.device = null;\n            AvrConfigFile.this.configuration = configuration;\n            super.close();\n        }\n    }\n\n    public AvrConfigFile(FileURL url) throws IOException {\n        super(url);\n    }\n\n    private static String extractPathFromUrl(FileURL url) {\n        return url.getParent().toString().substring(6) + FILENAME;  // 6 - length of \"avr://\"\n    }\n\n    @Override\n    public FilePermissions getPermissions() {\n        return FilePermissions.DEFAULT_FILE_PERMISSIONS;\n    }\n\n    @Override\n    public boolean isDirectory() {\n        return false;\n    }\n\n    @Override\n    public AbstractFile[] ls() throws IOException {\n        return new AbstractFile[0];\n    }\n\n    @Override\n    public void mkdir() throws IOException {\n\n    }\n\n    @Override\n    public boolean exists() {\n        return true;\n    }\n\n    @Override\n    public InputStream getInputStream() throws IOException {\n        AbstractFile configFile = getLocalConfigFile();\n        return configFile.getInputStream();\n    }\n\n    @Override\n    public long getSize() {\n        try {\n            return getLocalConfigFile().getSize();\n        } catch (IOException e) {\n            e.printStackTrace();\n            return -1;\n        }\n    }\n\n    @Override\n    public OutputStream getOutputStream() throws IOException {\n        return new ConfigOutputStream();\n    }\n\n\n    @Override\n    public void copyRemotelyTo(AbstractFile destFile) throws IOException {\n        getLocalConfigFile().copyRemotelyTo(destFile);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/avrdude/files/AvrDeviceDir.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.impl.avrdude.files;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FilePermissions;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.impl.avrdude.*;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * @author Oleg Trifonov\n * Created on 24/03/16.\n */\npublic class AvrDeviceDir extends AvrdudeFile {\n\n\n    private AvrdudeFile parent;\n\n    public AvrDeviceDir(FileURL url) throws IOException {\n        super(url);\n    }\n\n    @Override\n    public FilePermissions getPermissions() {\n        return FilePermissions.DEFAULT_DIRECTORY_PERMISSIONS;\n    }\n\n    @Override\n    public boolean isDirectory() {\n        return true;\n    }\n\n    @Override\n    public AbstractFile[] ls() throws IOException {\n        List<AvrdudeFile> childs = new ArrayList<>();\n        AvrdudeDevice device = getDevice();\n        Set<String> blocks = device.blockSizes.keySet();\n\n        childs.add(new AvrConfigFile(FileURL.getFileURL(getURL() + AvrConfigFile.FILENAME)));\n\n        if (blocks.contains(\"flash\")) {\n            childs.add(new AvrMemoryDir(FileURL.getFileURL(getURL() + AvrMemoryFile.Type.FLASH.name)));\n        }\n        if (blocks.contains(\"eeprom\")) {\n            childs.add(new AvrMemoryDir(FileURL.getFileURL(getURL() + AvrMemoryFile.Type.EEPROM.name)));\n        }\n        if (blocks.contains(\"fuse\") || blocks.contains(\"lfuse\")  || blocks.contains(\"hfuse\") || blocks.contains(\"efuse\")) {\n            childs.add(new AvrMemoryFile(FileURL.getFileURL(getURL() + AvrMemoryFile.Type.FUSES.name)));\n        }\n        if (blocks.contains(\"signature\")) {\n            //childs.add(new AvrMemoryFile(FileURL.getFileURL(getURL() + getDevice().name + SIGNATURE_FILE_EXT)));\n            childs.add(new AvrMemoryFile(FileURL.getFileURL(getURL() + AvrMemoryFile.Type.SIGNATURE.name)));\n        }\n        if (blocks.contains(\"calibration\")) {\n            childs.add(new AvrMemoryFile(FileURL.getFileURL(getURL() + AvrMemoryFile.Type.CALIBRATION.name)));\n        }\n        if (blocks.contains(\"lock\")) {\n            childs.add(new AvrMemoryFile(FileURL.getFileURL(getURL() + AvrMemoryFile.Type.LOCK.name)));\n        }\n\n        AbstractFile[] result = new AbstractFile[childs.size()];\n        return childs.toArray(result);\n    }\n\n    @Override\n    public void mkdir() throws IOException {\n        AbstractFile file = getBaseFolder().getChild(getURL().getFilename() + CONFIG_FILE_EXT);\n        if (file.exists()) {\n            throw new IOException(\"already exist\");\n        }\n        file.mkfile();\n        AvrConfigFileUtils.save(new AvrdudeConfiguration(), file.getAbsolutePath());\n    }\n\n    @Override\n    public boolean exists() {\n        try {\n            AbstractFile[] devices = getConfigFiles();\n            final String name = getName() + CONFIG_FILE_EXT;\n            for (AbstractFile configFile : devices) {\n                if (configFile.getName().equalsIgnoreCase(name)) {\n                    return true;\n                }\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        return false;\n    }\n\n    @Override\n    public AbstractFile getParent() {\n        if (parent == null) {\n            try {\n                parent = new AvrRootDir(FileURL.getFileURL(getURL().getScheme() + \"://\"), null);\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n        return parent;\n    }\n\n\n    @Override\n    public void delete() throws IOException {\n        getLocalConfigFile().delete();\n    }\n\n    @Override\n    public void renameTo(AbstractFile destFile) throws IOException {\n        if (destFile.getParent().getURL().getHost() == null) {\n            AbstractFile newConfig = getLocalConfigFile().getParent().getChild(destFile.getName() + CONFIG_FILE_EXT);\n            getLocalConfigFile().renameTo(newConfig);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/avrdude/files/AvrMemoryDir.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.impl.avrdude.files;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FilePermissions;\nimport com.mucommander.commons.file.FileURL;\n\nimport java.io.IOException;\n\n/**\n * @author Oleg Trifonov\n * Created on 25/03/16.\n */\npublic class AvrMemoryDir extends AvrdudeFile {\n\n    private AvrdudeFile parent;\n\n    public AvrMemoryDir(FileURL url) throws IOException {\n        super(url);\n    }\n\n\n    @Override\n    public FilePermissions getPermissions() {\n        return FilePermissions.DEFAULT_DIRECTORY_PERMISSIONS;\n    }\n\n    @Override\n    public boolean isDirectory() {\n        return true;\n    }\n\n    @Override\n    public AbstractFile[] ls() throws IOException {\n        AbstractFile[] files = new AbstractFile[2];\n        files[0] = new AvrMemoryFile(FileURL.getFileURL(getURL() + \"/\" + getURL().getFilename()+ \".hex\"));\n        files[1] = new AvrMemoryFile(FileURL.getFileURL(getURL() + \"/\" + getURL().getFilename() + \".bin\"));\n        return files;\n    }\n\n    @Override\n    public void mkdir() throws IOException {\n\n    }\n\n    @Override\n    public boolean exists() {\n        return true;\n    }\n\n    @Override\n    public AbstractFile getParent() {\n        if (parent == null) {\n            try {\n                parent = new AvrDeviceDir(getURL().getParent());\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n        return parent;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/avrdude/files/AvrMemoryFile.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.impl.avrdude.files;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FilePermissions;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.impl.avrdude.*;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n/**\n * @author Oleg Trifonov\n * Created on 24/03/16.\n */\npublic class AvrMemoryFile extends AvrdudeFile {\n\n    public enum Type {\n        FLASH(\"flash\"),\n        EEPROM(\"eeprom\"),\n        FUSES(\"fuses\"),\n        SIGNATURE(\"signature\"),\n        CALIBRATION(\"calibration\"),\n        LOCK(\"lock\");\n\n        Type(String name) {\n            this.name = name;\n        }\n\n        static Type fromFileName(String fileName) {\n            for (Type type : values()) {\n                if (fileName.startsWith(type.name)) {\n                    return type;\n                }\n            }\n            return null;\n        }\n\n        public final String name;\n    }\n\n    private final Type type;\n\n    public AvrMemoryFile(FileURL url) throws IOException {\n        super(url);\n        this.type = Type.fromFileName(getURL().getFilename());\n    }\n\n    @Override\n    public FilePermissions getPermissions() {\n        return FilePermissions.DEFAULT_FILE_PERMISSIONS;\n    }\n\n    @Override\n    public boolean isDirectory() {\n        return false;\n    }\n\n    @Override\n    public AbstractFile[] ls() throws IOException {\n        return new AbstractFile[0];\n    }\n\n    @Override\n    public void mkdir() throws IOException {\n\n    }\n\n    @Override\n    public boolean exists() {\n        return true;\n    }\n\n\n    @Override\n    public long getSize() {\n        String fullName = getURL().getFilename();\n        int dotPos = fullName.indexOf('.');\n        String fileName = dotPos > 0 ? fullName.substring(0, dotPos) : fullName;\n        if (fileName.contains(\"fuse\")) {\n            int size = 0;\n            for (String blockName : getDevice().blockSizes.keySet()) {\n                if (blockName.toLowerCase().contains(\"fuse\")) {\n                    size += getDevice().blockSizes.get(blockName);\n                }\n            }\n            return size;\n        } else if (fullName.endsWith(SIGNATURE_FILE_EXT)) {\n            return getDevice().blockSizes.get(\"signature\");\n        } else {\n            return getDevice().blockSizes.get(fileName);\n        }\n    }\n\n\n    @Override\n    public InputStream getInputStream() throws IOException {\nSystem.out.println(\"?-> \" + type);\n        Avrdude.Operation operation;\n        switch (type) {\n            case FLASH:\n                operation = Avrdude.Operation.READ_FLASH;\n                break;\n            case EEPROM:\n                operation = Avrdude.Operation.READ_EEPROM;\n                break;\n            case SIGNATURE:\n                operation = Avrdude.Operation.READ_SIGNATURE;\n                break;\n            case CALIBRATION:\n                operation = Avrdude.Operation.READ_CALIBRATION;\n                break;\n            default:\n                throw new RuntimeException(\"unsupported operation for \" + type);\n        }\n        return new AvrDudeInputStream(StreamType.HEX, configuration, operation);\n    }\n\n    @Override\n    public OutputStream getOutputStream() throws IOException {\n        Avrdude.Operation operation;\n        switch (type) {\n            case FLASH:\n                operation = Avrdude.Operation.WRITE_FLASH;\n                break;\n            case EEPROM:\n                operation = Avrdude.Operation.WRITE_EEPROM;\n                break;\n            case CALIBRATION:\n                operation = Avrdude.Operation.WRITE_CALIBRATION;\n                break;\n            default:\n                throw new RuntimeException(\"unsupported operation for \" + type);\n        }\n        return new AvrdudeOutputStream(StreamType.HEX, configuration, operation);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/avrdude/files/AvrRootDir.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.impl.avrdude.files;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FilePermissions;\nimport com.mucommander.commons.file.FileURL;\n\nimport java.io.IOException;\n\n/**\n * @author Oleg Trifonov\n * Created on 24/03/16.\n */\npublic class AvrRootDir extends AvrdudeFile {\n\n    public AvrRootDir(FileURL url, String path) throws IOException {\n        super(url);\n    }\n\n    @Override\n    public boolean isDirectory() {\n        return true;\n    }\n\n    @Override\n    public AbstractFile[] ls() throws IOException {\n        AbstractFile[] devices = getConfigFiles();\n        AvrDeviceDir[] result = new AvrDeviceDir[devices.length];\n        for (int i = 0; i < devices.length; i++) {\n            result[i] = new AvrDeviceDir(FileURL.getFileURL(getURL() + devices[i].getBaseName()));\n        }\n        return result;\n    }\n\n    @Override\n    public void mkdir() throws IOException {\n\n    }\n\n    @Override\n    public boolean exists() {\n        return true;\n    }\n\n    @Override\n    public FilePermissions getPermissions() {\n        return FilePermissions.DEFAULT_DIRECTORY_PERMISSIONS;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/avrdude/files/AvrdudeFile.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.impl.avrdude.files;\n\nimport com.mucommander.PlatformManager;\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.impl.avrdude.AvrConfigFileUtils;\nimport com.mucommander.commons.file.impl.avrdude.AvrdudeConfiguration;\nimport com.mucommander.commons.file.impl.avrdude.AvrdudeDevice;\nimport com.mucommander.commons.io.RandomAccessInputStream;\nimport com.mucommander.commons.io.RandomAccessOutputStream;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n/**\n * @author Oleg Trifonov\n * Created on 09/02/16.\n *\n * File hierarhy:\n *   DEVICE-NAME\n *    |- config.properties\n *    |- flash\n *    |   |- flash.bin\n *    |   |- flash.hex\n *    |- eeprom\n *    |   |- eeprom.bin\n *    |   |- eeprom.hex\n *    |- fuses\n *        |- fuses.bin\n *        |- fuses.hex\n *\n */\npublic abstract class AvrdudeFile extends ProtocolFile {\n\n    private static final String STORAGE_DIR = \"avr\";\n    protected static final String CONFIG_FILE_EXT = \".conf\";\n    protected static final String SIGNATURE_FILE_EXT = \".sign\";\n\n    protected AvrdudeConfiguration configuration;\n    protected AvrdudeDevice device;\n\n    AvrdudeFile(FileURL url) throws IOException {\n        super(url);\n        AbstractFile baseFolder = PlatformManager.getPreferencesFolder().getChild(STORAGE_DIR);\n        if (!baseFolder.exists()) {\n            baseFolder.mkdir();\n        }\n//        if (path.isEmpty() || path.equals(\"/\") || path.equals(\"\\\\\")) {\n//\n//        }\n//        System.out.println(\"path \" + path + \" [\" + getClass().getName());\n//        System.out.println(\"baseFolder \" + baseFolder + getClass().getName());\n    }\n\n    static AbstractFile getBaseFolder() throws IOException {\n        AbstractFile baseFolder = PlatformManager.getPreferencesFolder().getChild(STORAGE_DIR);\n        if (!baseFolder.exists()) {\n            baseFolder.mkdir();\n        }\n        return baseFolder;\n    }\n\n    static AbstractFile[] getConfigFiles() throws IOException {\n        return getBaseFolder().ls(new ExtensionFilenameFilter(CONFIG_FILE_EXT));\n    }\n\n\n    AbstractFile getLocalConfigFile() throws IOException {\n//        new Exception().printStackTrace();\n//System.out.println(\"::>\"+getURL());\n//System.out.println(\"::>\"+getBaseFolder());\n//System.out.println(getURL().getHost() + CONFIG_FILE_EXT);\n//System.out.println(\"::>\"+getBaseFolder().getChild(getURL().getHost() + CONFIG_FILE_EXT));\n        return getBaseFolder().getChild(getURL().getHost() + CONFIG_FILE_EXT);\n    }\n\n    public AvrdudeDevice getDevice() {\n        try {\n            if (device == null) {\n                device = AvrdudeDevice.getDevice(getConfiguration().deviceName);\n            }\n            return device;\n        } catch (IOException e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    public AvrdudeConfiguration getConfiguration() throws IOException {\n        if (configuration == null) {\n            configuration = AvrConfigFileUtils.load(getLocalConfigFile().getAbsolutePath());\n        }\n        return configuration;\n    }\n\n    @Override\n    public boolean isFileOperationSupported(FileOperation op) {\n        if (op == FileOperation.CHANGE_DATE || op == FileOperation.CHANGE_PERMISSION) {\n            return false;\n        }\n        return super.isFileOperationSupported(op);\n    }\n\n    @Override\n    public long getLastModifiedDate() {\n        // TODO store last modification data\n        try {\n            return getLocalConfigFile().getLastModifiedDate();\n        } catch (IOException e) {\n            e.printStackTrace();\n            return System.currentTimeMillis();\n        }\n    }\n\n    @Override\n    public void setLastModifiedDate(long lastModified) {\n\n    }\n\n    @Override\n    public void changeReplication(short replication) throws IOException {\n\n    }\n\n    @Override\n    public long getSize() {\n        return 0;\n    }\n\n    @Override\n    public AbstractFile getParent() {\n        return null;\n    }\n\n    @Override\n    public void setParent(AbstractFile parent) {\n\n    }\n\n    @Override\n    public boolean exists() {\n        return false;\n    }\n\n\n    @Override\n    public PermissionBits getChangeablePermissions() {\n        return null;\n    }\n\n    @Override\n    public void changePermission(int access, int permission, boolean enabled) {\n\n    }\n\n    @Override\n    public String getOwner() {\n        return null;\n    }\n\n    @Override\n    public short getReplication() {\n        return 0;\n    }\n\n    @Override\n    public long getBlocksize() {\n        return 0;\n    }\n\n    @Override\n    public boolean canGetOwner() {\n        return false;\n    }\n\n    @Override\n    public String getGroup() {\n        return null;\n    }\n\n    @Override\n    public boolean canGetGroup() {\n        return false;\n    }\n\n//    @Override\n//    public boolean isDirectory() {\n//        return false;\n//    }\n\n    @Override\n    public boolean isSymlink() {\n        return false;\n    }\n\n    @Override\n    public boolean isSystem() {\n        return false;\n    }\n\n\n    @Override\n    public InputStream getInputStream() throws IOException {\n        return null;\n    }\n\n    @Override\n    public OutputStream getOutputStream() throws IOException {\n        return null;\n    }\n\n    @Override\n    public OutputStream getAppendOutputStream() {\n        return null;\n    }\n\n    @Override\n    public RandomAccessInputStream getRandomAccessInputStream() {\n        return null;\n    }\n\n    @Override\n    public RandomAccessOutputStream getRandomAccessOutputStream() {\n        return null;\n    }\n\n    @Override\n    public void delete() throws IOException {\n\n    }\n\n    @Override\n    public void renameTo(AbstractFile destFile) throws IOException {\n\n    }\n\n    @Override\n    public void copyRemotelyTo(AbstractFile destFile) throws IOException {\n\n    }\n\n    @Override\n    public long getFreeSpace() {\n        return 0;\n    }\n\n    @Override\n    public long getTotalSpace() {\n        return 0;\n    }\n\n    @Override\n    public Object getUnderlyingFileObject() {\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/bzip2/Bzip2FormatProvider.java",
    "content": "package com.mucommander.commons.file.impl.bzip2;\n\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.ArchiveFormatProvider;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\nimport com.mucommander.commons.file.impl.SevenZipJBindingROArchiveFile;\nimport net.sf.sevenzipjbinding.ArchiveFormat;\n\nimport java.io.IOException;\n\n/**\n * This class is the provider for the 'Bzip2' archive format.\n *\n * @author Nicolas Rinaudo, Maxence Bernard\n */\npublic class Bzip2FormatProvider implements ArchiveFormatProvider {\n\n    private static final String[] EXTENSIONS = {\".bz2\"};\n\n    private static final byte[] SIGNATURE = {};\n\n    /**\n     * Static instance of the filename filter that matches archive filenames\n     * */\n    private static final ExtensionFilenameFilter FILENAME_FILTER = new ExtensionFilenameFilter(EXTENSIONS);\n\n    @Override\n    public AbstractArchiveFile getFile(AbstractFile file) throws IOException {\n        return new SevenZipJBindingROArchiveFile(file, ArchiveFormat.BZIP2, SIGNATURE);\n        //return new Bzip2ArchiveFile(file);\n    }\n\n    @Override\n    public FilenameFilter getFilenameFilter() {\n        return FILENAME_FILTER;\n    }\n\n    @Override\n    public String[] getFileExtensions() {\n        return EXTENSIONS;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/bzip2/package.html",
    "content": "<body>\n  Provides an implementation of the bzip2 archive format.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/cab/CabFormatProvider.java",
    "content": "package com.mucommander.commons.file.impl.cab;\n\nimport java.io.IOException;\n\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.ArchiveFormatProvider;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\nimport com.mucommander.commons.file.impl.SevenZipJBindingROArchiveFile;\n\nimport net.sf.sevenzipjbinding.ArchiveFormat;\n\npublic class CabFormatProvider implements ArchiveFormatProvider {\n    private static final String[] EXTENSIONS = { \".cab\" };\n\n    private final static ExtensionFilenameFilter FILENAME_FILTER = new ExtensionFilenameFilter(EXTENSIONS);\n\n    private final static byte[] SIGNATURE = { 0x4D, 0x53, 0x43, 0x46 };\n\n    @Override\n    public AbstractArchiveFile getFile(AbstractFile file) throws IOException {\n        return new SevenZipJBindingROArchiveFile(file, ArchiveFormat.CAB, SIGNATURE);\n    }\n\n    @Override\n    public FilenameFilter getFilenameFilter() {\n        return FILENAME_FILTER;\n    }\n\n    @Override\n    public String[] getFileExtensions() {\n        return EXTENSIONS;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/cpio/CpioFormatProvider.java",
    "content": "package com.mucommander.commons.file.impl.cpio;\n\nimport java.io.IOException;\n\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.ArchiveFormatProvider;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\nimport com.mucommander.commons.file.impl.SevenZipJBindingROArchiveFile;\n\nimport net.sf.sevenzipjbinding.ArchiveFormat;\n\npublic class CpioFormatProvider implements ArchiveFormatProvider {\n\n    private static final String[] EXTENSIONS = { \".cpio\" };\n    private final static ExtensionFilenameFilter FILENAME_FILTER = new ExtensionFilenameFilter(EXTENSIONS);\n\n//    private final static byte[] SIGNATURE = { 0x30, 0x37, 0x30, 0x37, 0x30 }; //=google but sevenzipjbinding:C771050823\n    private final static byte[] SIGNATURE = { }; \n    \n\n    @Override\n    public AbstractArchiveFile getFile(AbstractFile file) throws IOException {\n        return new SevenZipJBindingROArchiveFile(file, ArchiveFormat.CPIO, SIGNATURE);\n    }\n\n    @Override\n    public FilenameFilter getFilenameFilter() {\n        return FILENAME_FILTER;\n    }\n\n    @Override\n    public String[] getFileExtensions() {\n        return EXTENSIONS;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/deb/DebFormatProvider.java",
    "content": "package com.mucommander.commons.file.impl.deb;\n\nimport java.io.IOException;\n\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.ArchiveFormatProvider;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\nimport com.mucommander.commons.file.impl.SevenZipJBindingROArchiveFile;\n\nimport net.sf.sevenzipjbinding.ArchiveFormat;\n\npublic class DebFormatProvider implements ArchiveFormatProvider {\n    private static final String[] EXTENSIONS = { \".deb\" };\n\n    private final static ExtensionFilenameFilter FILENAME_FILTER = new ExtensionFilenameFilter(EXTENSIONS);\n\n    private final static byte[] SIGNATURE = {}; // TODO check in libmagic source\n\n    @Override\n    public AbstractArchiveFile getFile(AbstractFile file) throws IOException {\n        return new SevenZipJBindingROArchiveFile(file, ArchiveFormat.AR, SIGNATURE);\n    }\n\n    @Override\n    public FilenameFilter getFilenameFilter() {\n        return FILENAME_FILTER;\n    }\n\n    @Override\n    public String[] getFileExtensions() {\n        return EXTENSIONS;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/ftp/FTPFile.java",
    "content": "package com.mucommander.commons.file.impl.ftp;\n\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.file.connection.ConnectionHandler;\nimport com.mucommander.commons.file.connection.ConnectionHandlerFactory;\nimport com.mucommander.commons.file.connection.ConnectionPool;\nimport com.mucommander.commons.io.ByteUtils;\nimport com.mucommander.commons.io.FilteredOutputStream;\nimport com.mucommander.commons.io.RandomAccessInputStream;\nimport com.mucommander.commons.io.RandomAccessOutputStream;\nimport com.mucommander.commons.util.StringUtils;\nimport com.mucommander.core.FolderChangeMonitor;\nimport org.apache.commons.net.ftp.FTPClient;\nimport org.apache.commons.net.ftp.FTPConnectionClosedException;\nimport org.apache.commons.net.ftp.FTPReply;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.*;\nimport java.net.SocketException;\nimport java.net.SocketTimeoutException;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\n\n\n/**\n * FTPFile provides access to files located on an FTP server.\n *\n * <p>The associated {@link FileURL} scheme is {@link FileProtocols#FTP}. The host part of the URL designates the\n * FTP server. Credentials must be specified in the login and password parts as FTP servers require a login and\n * password. The path separator is '/'.\n *\n * <p>Here are a few examples of valid FTP URLs:\n * <code>\n * ftp://garfield/stuff/somefile<br>\n * ftp://john:p4sswd@garfield/stuff/somefile<br>\n * ftp://anonymous:john@somewhere.net@garfield/stuff/somefile<br>\n * </code>\n *\n * <p>Internally, FTPFile uses {@link ConnectionPool} to create FTP connections as needed and allows them to be reused\n * by FTPFile instances located on the same server, dealing with concurrency issues. Connections are thus managed\n * transparently and need not be manually managed.\n *\n * <p>Some FileURL properties control certain FTP connection settings:\n * <ul>\n *  <li>{@link #PASSIVE_MODE_PROPERTY_NAME}: controls whether passive or active transfer mode, <code>\"true\"</code> for\n *  passive mode, <code>\"false\"</code> for activemode. If the property is not specified when the connection is created,\n *  passive mode is assumed.\n *  <li>{@link #ENCODING_PROPERTY_NAME}: specifies the character encoding used by the server. If the property is not \n *  specified when the connection is created, {@link #DEFAULT_ENCODING} is assumed.\n * </ul>\n * These properties are only used when the FTP connection is created. Setting them after the connection is created\n * will not have any immediate effect, their values will only be used if the connection needs to be re-established.\n *\n * <p>Access to FTP files is provided by the <code>Commons-net</code> library distributed under the Apache Software License.\n * The {@link #getUnderlyingFileObject()} method allows to retrieve a <code>org.apache.commons.net.ftp.FTPFile</code>\n * instance corresponding to this FTPFile.\n *\n * @see ConnectionPool\n * @author Maxence Bernard\n */\npublic class FTPFile extends ProtocolFile implements ConnectionHandlerFactory {\n    private static final Logger LOGGER = LoggerFactory.getLogger(FTPFile.class);\n\n    private org.apache.commons.net.ftp.FTPFile file;\n\n    private final String absPath;\n\n    private AbstractFile parent;\n    private boolean parentValSet;\n    private final FilePermissions permissions;\n\n    private boolean fileExists;\n\n    private AbstractFile canonicalFile;\n\n    private final static String SEPARATOR = \"/\";\n\n    /** Name of the FTP passive mode property */\n    public final static String PASSIVE_MODE_PROPERTY_NAME = \"passiveMode\";\n\n    /** Name of the FTP encoding property */\n    public final static String ENCODING_PROPERTY_NAME = \"encoding\";\n\n    /** Default FTP encoding if {@link #ENCODING_PROPERTY_NAME} is not set */\n    public final static String DEFAULT_ENCODING = \"UTF-8\";\n\n    /** Name of the property that holds the number of retries after a recoverable connection failure (connection error\n     * or temporary server error in the 4xx range) */\n    public final static String NB_CONNECTION_RETRIES_PROPERTY_NAME = \"nbConnectionRetries\";\n\n    /** Default value if {@link #NB_CONNECTION_RETRIES_PROPERTY_NAME} is not set */\n    public final static int DEFAULT_NB_CONNECTION_RETRIES = 0;\n\n    /** Name of the property that holds the amount of time (in seconds) to wait before retrying to connect after a\n     *  temporary connection failure. */\n    public final static String CONNECTION_RETRY_DELAY_PROPERTY_NAME = \"connectionRetryDelay\";\n\n    /** Default value if {@link #CONNECTION_RETRY_DELAY_PROPERTY_NAME} is not set */\n    public final static int DEFAULT_CONNECTION_RETRY_DELAY = 15;\n\n    /** Date format used by the SITE UTIME command */\n    private final static SimpleDateFormat SITE_UTIME_DATE_FORMAT = new SimpleDateFormat(\"yyyyMMddHHmm\");\n\n\n    FTPFile(FileURL fileURL) throws IOException {\n        this(fileURL, null);\n    }\n\n    FTPFile(FileURL fileURL, org.apache.commons.net.ftp.FTPFile file) throws IOException {\n        super(fileURL);\n\n        this.absPath = fileURL.getPath();\n\n        if (file == null) {\n            this.file = getFTPFile(fileURL);\n            // If file doesn't exist (could not be resolved), create it\n            if (this.file == null) {\n                String name = fileURL.getFilename();    // Filename could potentially be null\n                this.file = createFTPFile(name == null ? \"\" : name, false);\n                this.fileExists = false;\n            } else {\n                this.fileExists = true;\n            }\n        } else {\n            this.file = file;\n            this.fileExists = true;\n        }\n        this.permissions = new FTPFilePermissions(this.file);\n    }\n\n\n    private org.apache.commons.net.ftp.FTPFile getFTPFile(FileURL fileURL) throws IOException {\n        // Todo: this method is very ineffective as it lists the parent directory to retrieve the information about the\n        // requested file to workaround the fact that FTPClient#listFiles follows directories.\n        // => Use the MLST command if supported by the server (use FEAT command to find out if it is supported).\n        // See http://tools.ietf.org/html/draft-ietf-ftpext-mlst-16\n        FileURL parentURL = fileURL.getParent();\n        LOGGER.trace(\"fileURL={} parent={}\", fileURL, parentURL);\n\n        // Parent is null, create '/' file\n        if (parentURL == null) {\n            return createFTPFile(\"/\", true);\n        } else {\n            FTPConnectionHandler connHandler = (FTPConnectionHandler)ConnectionPool.getConnectionHandler(this, fileURL, true);\n            org.apache.commons.net.ftp.FTPFile[] files;\n            try {\n                // Makes sure the connection is started, if not starts it\n                connHandler.checkConnection();\n\n                // List files contained by this file's parent in order to retrieve the FTPFile instance corresponding\n                // to this file\n                files = listFiles(connHandler, parentURL.getPath());\n            } finally {\n                // Release the lock on the ConnectionHandler\n                connHandler.releaseLock();\n            }\n\n            // File doesn't exist\n            if (files == null || files.length == 0) {\n                return null;\n            }\n\n            // Find the file in the parent folder's contents\n            String wantedName = fileURL.getFilename();\n            for (org.apache.commons.net.ftp.FTPFile f : files) {\n                if (f.getName().equalsIgnoreCase(wantedName)) {\n                    return f;\n                }\n            }\n\n            // File doesn't exists\n            return null;\n        }\n    }\n\n\n    private org.apache.commons.net.ftp.FTPFile createFTPFile(String name, boolean isDirectory) {\n        org.apache.commons.net.ftp.FTPFile file = new org.apache.commons.net.ftp.FTPFile();\n        file.setName(name);\n        file.setSize(0);\n        file.setTimestamp(java.util.Calendar.getInstance());\n        file.setType(isDirectory?org.apache.commons.net.ftp.FTPFile.DIRECTORY_TYPE:org.apache.commons.net.ftp.FTPFile.FILE_TYPE);\n        return file;\n    }\n\n\n    /**\n     * Lists and returns the contents of the given path on the server using the given connection handler.\n     * The directory contents is listed by issuing a CWD followed by a LIST so after this method is called, the current\n     * working directory is left to the specified path.\n     *\n     * @param connHandler the connection handler to use for communicating with the server\n     * @param absPath absolute path to the directory to list\n     * @return the directory's contents. The returned array may be empty but never null. The array may contain null\n     * individual entries as FTPClient#listFiles's Javadoc mentions.\n     * @throws IOException if an error occurred while communicating with the server\n     * @throws AuthException if the user is not allowed to access this directory\n     */\n    private static org.apache.commons.net.ftp.FTPFile[] listFiles(FTPConnectionHandler connHandler, String absPath) throws IOException {\n        org.apache.commons.net.ftp.FTPFile[] files;\n        try {\n            // Important: the folder is listed by changing the current working directory using the CWD command and then\n            // issuing a LIST to list the current directory, instead of issuing a LIST with the path as an argument.\n            // So we're sending:\n            //\n            //   CWD path\n            //   LIST\n            //\n            // Instead of:\n            //\n            //   LIST path\n            //\n            // The reason for that is that on some servers 'LIST path with spaces' fails whereas 'CWD path with spaces'\n            // succeeds. Most FTP clients seem to be doing this (CWD/LIST instead of LIST), there must be a reason.\n            //\n            // See:\n            // http://www.mucommander.com/forums/viewtopic.php?f=4&t=714\n            // http://issues.apache.org/jira/browse/NET-10\n\n            connHandler.ftpClient.changeWorkingDirectory(absPath);\n            files = connHandler.ftpClient.listFiles();\n\n            // Throw an IOException if server replied with an error\n            connHandler.checkServerReply();\n\n            if (files==null)     // In some rare conditions (bug) this method can return null\n                return new org.apache.commons.net.ftp.FTPFile[0];\n\n            return files;\n        }\n        // This exception is not an IOException and needs to be caught and thrown back as an IOException\n        catch(org.apache.commons.net.ftp.parser.ParserInitializationException e) {\n            LOGGER.info(\"ParserInitializationException caught\", e);\n            throw new IOException();\n        } catch(IOException e) {\n            // Checks if the IOException corresponds to a socket error and in that case, closes the connection\n            connHandler.checkSocketException(e);\n\n            // Throw back the IOException\n            throw e;\n        }\n    }\n\n\n    /////////////////////////////////////////////\n    // ConnectionHandlerFactory implementation //\n    /////////////////////////////////////////////\n\n    public ConnectionHandler createConnectionHandler(FileURL location) {\n        return new FTPConnectionHandler(location);\n    }\n\n\n    /////////////////////////////////////////\n    // AbstractFile methods implementation //\n    /////////////////////////////////////////\n\n    @Override\n    public boolean isSymlink() {\n        return file.isSymbolicLink();\n    }\n\n    @Override\n    public boolean isSystem() {\n        return false;\n    }\n\n    @Override\n    public long getLastModifiedDate() {\n        if (isSymlink()) {\n            return ((org.apache.commons.net.ftp.FTPFile) getCanonicalFile().getUnderlyingFileObject()).getTimestamp().getTimeInMillis();\n        }\n\n        return file.getTimestamp().getTimeInMillis();\n    }\n\n    /**\n     * Attempts to change this file's date using the <i>'SITE UTIME'</i> FTP command.\n     * This command seems to be implemented by modern FTP servers such as ProFTPd or PureFTP Server but since it is not\n     * part of the basic FTP command set, it may as well not be supported by the remote server.\n     */\n    @Override\n    public void setLastModifiedDate(long lastModified) throws IOException {\n        // Note: FTPFile.setTimeStamp only changes the instance's date, but doesn't change it on the server-side.\n        FTPConnectionHandler connHandler = null;\n        try {\n            // Retrieve a ConnectionHandler and lock it\n            connHandler = (FTPConnectionHandler)ConnectionPool.getConnectionHandler(this, fileURL, true);\n\n            // Throw UnsupportedFileOperationException if we know the 'SITE UTIME' command is not supported by the server\n            if (!connHandler.utimeCommandSupported) {\n                throw new UnsupportedFileOperationException(FileOperation.CHANGE_DATE);\n            }\n\n            // Makes sure the connection is started, if not starts it\n            connHandler.checkConnection();\n\n            String sdate;\n            // SimpleDateFormat instance must be synchronized externally if it is accessed concurrently\n            synchronized(SITE_UTIME_DATE_FORMAT) {\n                sdate = SITE_UTIME_DATE_FORMAT.format(new Date(lastModified));\n            }\n\n            LOGGER.info(\"sending SITE UTIME {} {}\", sdate, absPath);\n            boolean success = connHandler.ftpClient.sendSiteCommand(\"UTIME \"+sdate+\" \"+absPath);\n            LOGGER.info(\"server reply: {}\", connHandler.ftpClient.getReplyString());\n\n            if (!success) {\n                int replyCode = connHandler.ftpClient.getReplyCode();\n\n                // If server reported that the command is not supported, mark it in the ConnectionHandler so that\n                // we don't try it anymore\n                if(replyCode==FTPReply.UNRECOGNIZED_COMMAND\n                        || replyCode==FTPReply.COMMAND_NOT_IMPLEMENTED \n                        || replyCode==FTPReply.COMMAND_NOT_IMPLEMENTED_FOR_PARAMETER) {\n\n                    LOGGER.info(\"marking UTIME command as unsupported\");\n                    connHandler.utimeCommandSupported = false;\n                }\n\n                throw new IOException();\n            }\n        } catch (IOException e) {\n            // Checks if the IOException corresponds to a socket error and in that case, closes the connection\n            if(connHandler!=null)\n                connHandler.checkSocketException(e);\n\n            throw e;\n        } finally {\n            // Release the lock on the ConnectionHandler\n            if (connHandler != null) {\n                connHandler.releaseLock();\n            }\n        }\n    }\n\n    @Override\n    public long getSize() {\n        if (isSymlink()) {\n            return ((org.apache.commons.net.ftp.FTPFile) getCanonicalFile().getUnderlyingFileObject()).getSize();\n        }\n        return file.getSize();\n    }\n\n\n    @Override\n    public AbstractFile getParent() {\n        if (!parentValSet) {\n            FileURL parentFileURL = this.fileURL.getParent();\n            if (parentFileURL != null) {\n                try {\n                    parent = FileFactory.getFile(parentFileURL, null, createFTPFile(parentFileURL.getFilename(), true));\n                } catch(IOException e) {\n                    // No parent, that's all\n                }\n            }\n\n            parentValSet = true;\n        }\n\n        return parent;\n    }\n\n\n    @Override\n    public void setParent(AbstractFile parent) {\n        this.parent = parent;\n        this.parentValSet = true;\n    }\n\n\n    @Override\n    public boolean exists() {\n        return this.fileExists;\n    }\n\n    @Override\n    public FilePermissions getPermissions() {\n        if (isSymlink()) {\n            FTPFile ancestor = getCanonicalFile().getAncestor(FTPFile.class);\n            return ancestor != null ? ancestor.permissions : null;\n        }\n\n        return permissions;\n    }\n\n    @Override\n    public void changePermission(int access, int permission, boolean enabled) throws IOException {\n        changePermissions(ByteUtils.setBit(permissions.getIntValue(), (permission << (access*3)), enabled));\n    }\n\n    /**\n     * Returns {@link PermissionBits#FULL_PERMISSION_BITS} if the server supports the 'site chmod' command (not all\n     * servers do), {@link PermissionBits#EMPTY_PERMISSION_BITS} otherwise.\n     *\n     * @return {@link PermissionBits#FULL_PERMISSION_BITS} if the server supports the 'site chmod' command (not all\n     * servers do), {@link PermissionBits#EMPTY_PERMISSION_BITS} otherwise\n     */\n    @Override\n    public PermissionBits getChangeablePermissions() {\n        try {\n            // Do not lock the connection handler, not needed.\n            return ((FTPConnectionHandler)ConnectionPool.getConnectionHandler(this, fileURL, false)).chmodCommandSupported\n                    ?PermissionBits.FULL_PERMISSION_BITS    // Full permission support (777 octal)\n                    :PermissionBits.EMPTY_PERMISSION_BITS;  // Permissions can't be changed\n        } catch (InterruptedIOException e) {\n            // Should not happen in practice\n            return PermissionBits.EMPTY_PERMISSION_BITS;  // Permissions can't be changed\n        }\n    }\n\n    @Override\n    public String getOwner() {\n        return file.getUser();\n    }\n\n    @Override\n    public boolean canGetOwner() {\n        return true;\n    }\n\n    @Override\n    public String getGroup() {\n        return file.getGroup();\n    }\n\n    @Override\n    public boolean canGetGroup() {\n        return true;\n    }\n\n    @Override\n    public boolean isDirectory() {\n        // org.apache.commons.net.ftp.FTPFile#isDirectory() returns false if the file is a symlink pointing to a\n        // directory, this is a limitation of the Commons-net library.\n        // Todo: fix this by either:\n        // a) find a combination of 'LIST' switches which allows the output to contain both the 'is symlink' and the\n        // 'is the symlink target a directory' information. At a first glance, there doesn't seem to be one: either\n        // symlinks are followed or there aren't.\n        // b) Patch #ls() to issue an extra 'LIST -ldH *' to retrieve all symlinks' information when the directory has\n        // at least one symlink.\n        // c) if this file is a symlink, retrieve the symlink's target using #getFTPFile(FileURL) with '-ldH' switches\n        // and return the value of isDirectory(). This clearly is the least effective solution at it requires issuing\n        // one 'ls' command per symlink.\n\n        if (isSymlink()) {\n            return ((org.apache.commons.net.ftp.FTPFile) getCanonicalFile().getUnderlyingFileObject()).isDirectory();\n        }\n\n        return file.isDirectory();\n    }\n\n    @Override\n    public InputStream getInputStream() throws IOException {\n        return getInputStream(0);\n    }\n\n    @Override\n    public OutputStream getOutputStream() throws IOException {\n        return new FTPOutputStream(false);\n    }\n\n    @Override\n    public OutputStream getAppendOutputStream() throws IOException {\n        return new FTPOutputStream(true);\n    }\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException}: random read access is not available.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public RandomAccessInputStream getRandomAccessInputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.RANDOM_READ_FILE);\n    }\n\n//    public RandomAccessInputStream getRandomAccessInputStream() throws IOException {\n//        return new FTPRandomAccessInputStream();\n//    }\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException}: random write access is not available.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public RandomAccessOutputStream getRandomAccessOutputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.RANDOM_WRITE_FILE);\n    }\n\n    @Override\n    public void delete() throws IOException {\n        // Retrieve a ConnectionHandler and lock it\n        FTPConnectionHandler connHandler = (FTPConnectionHandler)ConnectionPool.getConnectionHandler(this, fileURL, true);\n        try {\n            // Makes sure the connection is started, if not starts it\n            connHandler.checkConnection();\n\n            if (isDirectory()) {\n                connHandler.ftpClient.removeDirectory(absPath);\n            } else {\n                connHandler.ftpClient.deleteFile(absPath);\n            }\n            fileExists = false; // need be false because the file can be get from cache pool\n\n            // Throw an IOException if server replied with an error\n            connHandler.checkServerReply();\n        } catch(IOException e) {\n            // Checks if the IOException corresponds to a socket error and in that case, closes the connection\n            connHandler.checkSocketException(e);\n\n            // Re-throw IOException\n            throw e;\n        } finally {\n            // Release the lock on the ConnectionHandler\n            connHandler.releaseLock();\n        }\n    }\n\n\n    @Override\n    public AbstractFile[] ls() throws IOException {\n        // Retrieve a ConnectionHandler and lock it\n        FTPConnectionHandler connHandler = (FTPConnectionHandler)ConnectionPool.getConnectionHandler(this, fileURL, true);\n        org.apache.commons.net.ftp.FTPFile[] files;\n        try {\n            // Makes sure the connection is started, if not starts it\n            connHandler.checkConnection();\n\n            files = listFiles(connHandler, absPath);\n        } finally {\n            // Release the lock on the ConnectionHandler\n            connHandler.releaseLock();\n        }\n\n        if (files == null || files.length == 0) {\n            return new AbstractFile[]{};\n        }\n\n        AbstractFile[] children = new AbstractFile[files.length];\n        int nbFiles = files.length;\n        int fileCount = 0;\n        String parentPath = fileURL.getPath();\n        if (!parentPath.endsWith(SEPARATOR)) {\n            parentPath += SEPARATOR;\n        }\n\n        for (org.apache.commons.net.ftp.FTPFile file1 : files) {\n            if (file1 == null) {\n                continue;\n            }\n\n            String childName = file1.getName();\n            if (childName.equals(\".\") || childName.equals(\"..\")) {\n                continue;\n            }\n\n            // Note: properties and credentials are cloned for every children's url\n            FileURL childURL = (FileURL) fileURL.clone();\n            childURL.setPath(parentPath + childName);\n\n            // Discard '.' and '..' files\n            if (childName.equals(\".\") || childName.equals(\"..\")) {\n                continue;\n            }\n\n            AbstractFile child = FileFactory.getFile(childURL, this, file1);\n            children[fileCount++] = child;\n        }\n\n        // create new array of the exact file count\n        if (fileCount < nbFiles) {\n            AbstractFile[] newChildren = new AbstractFile[fileCount];\n            System.arraycopy(children, 0, newChildren, 0, fileCount);\n            return newChildren;\n        }\n\n        return children;\n    }\n\n\n    @Override\n    public void mkdir() throws IOException {\n        // Retrieve a ConnectionHandler and lock it\n        FTPConnectionHandler connHandler = (FTPConnectionHandler)ConnectionPool.getConnectionHandler(this, fileURL, true);\n        try {\n            // Makes sure the connection is started, if not starts it\n            connHandler.checkConnection();\n\n            connHandler.ftpClient.makeDirectory(absPath);\n            // Throw an IOException if server replied with an error\n            connHandler.checkServerReply();\n\n            file.setType(org.apache.commons.net.ftp.FTPFile.DIRECTORY_TYPE);\n            fileExists = true;\n        } catch(IOException e) {\n            // Checks if the IOException corresponds to a socket error and in that case, closes the connection\n            connHandler.checkSocketException(e);\n            // Re-throw IOException\n            throw e;\n        } finally {\n            // Release the lock on the ConnectionHandler\n            connHandler.releaseLock();\n        }\n    }\n\n    /**\n     * Always throws {@link UnsupportedFileOperationException} when called.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public long getFreeSpace() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_FREE_SPACE);\n    }\n\n    /**\n     * Always throws {@link UnsupportedFileOperationException} when called.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public long getTotalSpace() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_TOTAL_SPACE);\n    }\n\n    /**\n     * Returns an <code>org.apache.commons.net.FTPFile</code> instance corresponding to this file.\n     */\n    @Override\n    public Object getUnderlyingFileObject() {\n        return file;\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public short getReplication() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_REPLICATION);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public long getBlocksize() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_BLOCKSIZE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public void changeReplication(short replication) throws IOException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_REPLICATION);\n    }\n\n    /**\n     * Changes permissions using the SITE CHMOD FTP command.\n     *\n     * This command is optional but seems to be supported by modern FTP servers such as ProFTPd or PureFTP Server.\n     * But it may as well not be supported by the remote FTP server as it is not part of the basic FTP command set.\n     *\n     * Implementation note: FTPFile.setPermission only changes the instance's permissions, but doesn't change it on the\n     * server-side.\n     */\n    @Override\n    public void changePermissions(int permissions) throws IOException {\n        FTPConnectionHandler connHandler = null;\n        try {\n            // Retrieve a ConnectionHandler and lock it\n            connHandler = (FTPConnectionHandler)ConnectionPool.getConnectionHandler(this, fileURL, true);\n\n            // Return if we know the CHMOD command is not supported by the server\n            if(!connHandler.chmodCommandSupported)\n                throw new UnsupportedFileOperationException(FileOperation.CHANGE_PERMISSION);\n\n            // Makes sure the connection is started, if not starts it\n            connHandler.checkConnection();\n\n            LOGGER.info(\"sending SITE CHMOD {} {}\", Integer.toOctalString(permissions), absPath);\n            boolean success = connHandler.ftpClient.sendSiteCommand(\"CHMOD \"+Integer.toOctalString(permissions)+\" \"+absPath);\n            LOGGER.info(\"server reply: {}\", connHandler.ftpClient.getReplyString());\n\n            if (!success) {\n                int replyCode = connHandler.ftpClient.getReplyCode();\n\n                // If server reported that the command is not supported, mark it in the ConnectionHandler so that\n                // we don't try it anymore\n                if(replyCode==FTPReply.UNRECOGNIZED_COMMAND\n                        || replyCode==FTPReply.COMMAND_NOT_IMPLEMENTED\n                        || replyCode==FTPReply.COMMAND_NOT_IMPLEMENTED_FOR_PARAMETER) {\n\n                    LOGGER.info(\"marking CHMOD command as unsupported\");\n                    connHandler.chmodCommandSupported = false;\n                }\n\n                throw new IOException();\n            }\n        } catch(IOException e) {\n            // Checks if the IOException corresponds to a socket error and in that case, closes the connection\n            if (connHandler != null) {\n                connHandler.checkSocketException(e);\n            }\n\n            throw e;\n        } finally {\n            // Release the lock on the ConnectionHandler\n            if (connHandler != null) {\n                connHandler.releaseLock();\n            }\n        }\n    }\n\n    /**\n     * Implementation notes: always throws an {@link UnsupportedFileOperationException}.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void copyRemotelyTo(AbstractFile destFile) throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.COPY_REMOTELY);\n    }\n    \n    /**\n     * Implementation notes: server-to-server renaming will work if the destination file also uses the 'FTP' scheme\n     * and is located on the same host.\n     */\n    @Override\n    public void renameTo(AbstractFile destFile) throws IOException {\n        // Throw an exception if the file cannot be renamed to the specified destination\n        checkRenamePrerequisites(destFile, false, false);\n\n        FTPConnectionHandler connHandler = null;\n        try {\n            // Retrieve a ConnectionHandler and lock it\n            connHandler = (FTPConnectionHandler)ConnectionPool.getConnectionHandler(this, fileURL, true);\n            // Makes sure the connection is started, if not starts it\n            connHandler.checkConnection();\n\n            if(!connHandler.ftpClient.rename(absPath, destFile.getURL().getPath()))\n                throw new IOException();\n        } catch(IOException e) {\n            // Checks if the IOException corresponds to a socket error and in that case, closes the connection\n            if(connHandler!=null)\n                connHandler.checkSocketException(e);\n\n            throw e;\n        } finally {\n            // Release the lock on the ConnectionHandler\n            if (connHandler!=null) {\n                connHandler.releaseLock();\n            }\n        }\n    }\n\n\n    @Override\n    public InputStream getInputStream(long offset) throws IOException {\n        return new FTPInputStream(offset);\n    }\n\n    @Override\n    public AbstractFile getCanonicalFile() {\n        if (!isSymlink()) {\n            return this;\n        }\n\n        // create the canonical file instance and cache it\n        if (canonicalFile == null) {\n            // getLink() returns the raw symlink target which can either be an absolute or a relative path. If the path is\n            // relative, prepared the absolute path of the symlink's parent folder.\n            String symlinkTargetPath = file.getLink();\n            if (!symlinkTargetPath.startsWith(\"/\")) {\n                String parentPath = fileURL.getParent().getPath();\n                if (!parentPath.endsWith(\"/\")) {\n                    parentPath += \"/\";\n                }\n                symlinkTargetPath = parentPath + symlinkTargetPath;\n            }\n\n            FileURL canonicalURL = (FileURL)fileURL.clone();\n            canonicalURL.setPath(symlinkTargetPath);\n\n            canonicalFile = FileFactory.getFile(canonicalURL);\n        }\n\n        return canonicalFile;\n    }\n\n    /**\n     * If the FTPFile is a symbolic link, this method returns the name of the file being pointed to by the symbolic link.\n     * @return The file pointed to by the symbolic link (null if the FTPFile is not a symbolic link).\n     */\n    public String getLink() {\n        return file.getLink();\n    }\n\n\n    ///////////////////\n    // Inner classes //\n    ///////////////////\n\n//    private class FTPProcess extends AbstractProcess {\n//\n//        /** True if the command returned a positive FTP reply code */\n//        private boolean success;\n//\n//        /** Allows to read the command's output */\n//        private ByteArrayInputStream bais;\n//\n//\n//        public FTPProcess(String tokens[]) throws IOException {\n//\n//            // Concatenates all tokens to create the command string\n//            String command = \"\";\n//            int nbTokens = tokens.length;\n//            for(int i=0; i<nbTokens; i++) {\n//                command += tokens[i];\n//                if(i!=nbTokens-1)\n//                    command += \" \";\n//            }\n//\n//            FTPConnectionHandler connHandler = null;\n//            try {\n//                // Retrieve a ConnectionHandler and lock it\n//                connHandler = (FTPConnectionHandler)ConnectionPool.getConnectionHandler(FTPFile.this, fileURL, true);\n//                // Makes sure the connection is started, if not starts it\n//                connHandler.checkConnection();\n//\n//                // Change the current directory on the remote server to :\n//                //  - this file's path if this file is a directory\n//                //  - to the parent folder's path otherwise\n//                if(!connHandler.ftpClient.changeWorkingDirectory(isDirectory()?fileURL.getPath():fileURL.getParent().getPath()))\n//                    throw new IOException();\n//\n//                // Has the command been successfully completed by the server ?\n//                success = FTPReply.isPositiveCompletion(connHandler.ftpClient.sendCommand(command));\n//\n//                // Retrieves the command's output and create an InputStream for getInputStream()\n//                ByteArrayOutputStream baos = new ByteArrayOutputStream();\n//                PrintWriter pw = new PrintWriter(baos, true);\n//                String replyStrings[] = connHandler.ftpClient.getReplyStrings();\n//                for(int i=0; i<replyStrings.length; i++)\n//                    pw.println(replyStrings[i]);\n//                pw.close();\n//\n//                bais = new ByteArrayInputStream(baos.toByteArray());\n//                // No need to close the ByteArrayOutputStream\n//            }\n//            catch(IOException e) {\n//               // Checks if the IOException corresponds to a socket error and in that case, closes the connection\n//                connHandler.checkSocketException(e);\n//\n//                // Re-throw IOException\n//                throw e;\n//            }\n//            finally {\n//                // Release the lock on the ConnectionHandler\n//                if(connHandler!=null)\n//                    connHandler.releaseLock();\n//            }\n//        }\n//\n//        public boolean usesMergedStreams() {\n//            // No specific stream for errors\n//            return true;\n//        }\n//\n//        public int waitFor() throws InterruptedException, IOException {\n//            return success?0:1;\n//        }\n//\n//        protected void destroyProcess() throws IOException {\n//            // No-op, command has already been executed\n//        }\n//\n//        public int exitValue() {\n//            return success?0:1;\n//        }\n//\n//        public OutputStream getOutputStream() throws IOException {\n//            // FTP commands are not interactive, the returned OutputStream simply ignores data that's fed to it\n//            return new SinkOutputStream();\n//        }\n//\n//        public InputStream getInputStream() throws IOException {\n//            if(bais==null)\n//                throw new IOException();\n//\n//            return bais;\n//        }\n//\n//        public InputStream getErrorStream() throws IOException {\n//            return getInputStream();\n//        }\n//    }\n\n    private class FTPInputStream extends FilterInputStream {\n\n        private FTPConnectionHandler connHandler;\n        private boolean isClosed;\n\n        private FTPInputStream(long skipBytes) throws IOException {\n            super(null);\n            try {\n                // Retrieve a ConnectionHandler and lock it\n                connHandler = (FTPConnectionHandler)ConnectionPool.getConnectionHandler(FTPFile.this, FTPFile.this.fileURL, true);\n                // Makes sure the connection is started, if not starts it\n                connHandler.checkConnection();\n                if (skipBytes > 0) {\n                    // Resume transfer at the given offset\n                    connHandler.ftpClient.setRestartOffset(skipBytes);\n                }\n                in = connHandler.ftpClient.retrieveFileStream(absPath);\n                if (in == null) {\n                    if (skipBytes > 0) {\n                        // Reset offset\n                        connHandler.ftpClient.setRestartOffset(0);\n                    }\n                    throw new IOException();\n                }\n            } catch(IOException e) {\n                if (connHandler != null) {\n                    // Checks if the IOException corresponds to a socket error and in that case, closes the connection\n                    connHandler.checkSocketException(e);\n\n                    // Release the lock on the ConnectionHandler if the InputStream could not be created\n                    connHandler.releaseLock();\n                }\n\n                // Re-throw IOException\n                throw e;\n            }\n        }\n\n        @Override\n        public void close() throws IOException {\n            // Make sure this method is only executed once, otherwise FTPClient#completePendingCommand() would lock\n            if (isClosed) {\n                return;\n            }\n\n            // we need to refresh the file after an update\n            // otherwise the displayed size of archive files is incorrect\n            file = getFTPFile(getURL());\n            isClosed = true;\n\n            try {\n                super.close();\n\n                LOGGER.info(\"complete pending commands\");\n                connHandler.ftpClient.completePendingCommand();\n                LOGGER.info(\"commands completed\");\n\n                // Todo: An IOException will be thrown by completePendingCommand if the transfer has not finished before calling close.\n                // An 'abort' command should be issued to the server before closing if the transfer is not finished yet.\n                // Currently in that case (transfer not finished) the whole connection has to be re-established (bad!).\n                // FTPClient#abort() is difficult to use to say the least. This post gives some insight: http://mail-archives.apache.org/mod_mbox/commons-user/200604.mbox/%3c78A73ABD8DB470439179DB682EA990B3025B87DF@mtlex02.NEXXLINK.INT%3e\n            } catch (IOException e) {\n                LOGGER.info(\"exception in completePendingCommands()\", e);\n\n                // Checks if the IOException corresponds to a socket error and in that case, closes the connection\n                connHandler.checkSocketException(e);\n\n                // Do not re-throw the exception because an IOException will be thrown if close is called before\n                // the transfer is finished (see above) which is pseudo-normal behavior (though sub-optimal).\n//                // Re-throw IOException\n//                throw e;\n            } finally {\n                // Release the lock on the ConnectionHandler\n                connHandler.releaseLock();\n            }\n        }\n    }\n\n\n    // This class works but because of the bug in FTPInputStream#close() which fails to interrupt an ongoing transfer\n    // gracefully, seek() will re-establish the FTP connection each time it is called, which is definitely not acceptable.\n    // Therefore, this class cannot be used at the moment.\n    private class FTPRandomAccessInputStream extends RandomAccessInputStream {\n\n        private FTPInputStream in;\n        private long offset;\n\n        private FTPRandomAccessInputStream() throws IOException {\n            this.in = new FTPInputStream(0);\n        }\n\n        @Override\n        public int read() throws IOException {\n            int read = in.read();\n\n            if (read != -1) {\n                offset += 1;\n            }\n\n            return read;\n        }\n\n        @Override\n        public int read(byte[] b, int off, int len) throws IOException {\n            int nbRead = in.read(b, off, len);\n\n            if (nbRead != -1) {\n                offset += nbRead;\n            }\n\n            return nbRead;\n        }\n\n        public long getOffset() {\n            return offset;\n        }\n\n        public long getLength() {\n            return FTPFile.this.getSize();\n        }\n\n        public void seek(final long offset) throws IOException {\n            try {\n                in.close();\n            } catch(IOException ignore) {}\n\n            in = new FTPInputStream(offset);\n            this.offset = offset;\n        }\n\n        @Override\n        public void close() throws IOException {\n            in.close();\n        }\n    }\n\n\n    private class FTPOutputStream extends FilteredOutputStream {\n\n        private FTPConnectionHandler connHandler;\n        private boolean isClosed;\n\n        private FTPOutputStream(boolean append) throws IOException {\n            super(null);\n\n            try {\n                // Retrieve a ConnectionHandler and lock it\n                connHandler = (FTPConnectionHandler)ConnectionPool.getConnectionHandler(FTPFile.this, fileURL, true);\n                // Makes sure the connection is started, if not starts it\n                connHandler.checkConnection();\n\n                if (append) {\n                    out = connHandler.ftpClient.appendFileStream(absPath);\n                } else {\n                    out = connHandler.ftpClient.storeFileStream(absPath);   // Note: do NOT use storeUniqueFileStream which appends .1 if the file already exists and fails with proftpd\n                }\n\n                if (out == null) {\n                    throw new IOException();\n                }\n            } catch(IOException e) {\n                if (connHandler != null) {\n                    // Checks if the IOException corresponds to a socket error and in that case, closes the connection\n                    connHandler.checkSocketException(e);\n\n                    // Release the lock on the ConnectionHandler if the OutputStream could not be created\n                    connHandler.releaseLock();\n                }\n\n                // Re-throw IOException\n                throw e;\n            }\n        }\n\n        @Override\n        public void close() throws IOException {\n            // Make sure this method is only executed once, otherwise FTPClient#completePendingCommand() would lock\n            if (isClosed) {\n                return;\n            }\n            // we need to refresh the file after update\n            // otherwise the file size for archives will be show incorrect etc.\n            try {\n                FTPFile.this.file = getFTPFile(getURL());\n            } catch (IOException e) {\n                // Checks if the IOException corresponds to a socket error and in that case, closes the connection\n                connHandler.checkSocketException(e);\n            }\n            // force to refresh folder pane with this file\n            FolderChangeMonitor.addFileToRefresh(getAbsolutePath());\n            isClosed = true;\n\n            try {\n                super.close();\n\n                LOGGER.trace(\"complete pending commands\");\n                if (connHandler != null && connHandler.ftpClient != null) {\n                    connHandler.ftpClient.completePendingCommand();\n                }\n                LOGGER.trace(\"commands completed\");\n            } catch(IOException e) {\n                LOGGER.info(\"exception in completePendingCommands()\", e);\n\n                // Checks if the IOException corresponds to a socket error and in that case, closes the connection\n                connHandler.checkSocketException(e);\n\n                // Re-throw IOException\n                throw e;\n            } finally {\n                // Release the lock on the ConnectionHandler\n                connHandler.releaseLock();\n            }\n        }\n    }\n\n\n    /**\n     * Handles connection to an FTP server.\n     */\n    private static class FTPConnectionHandler extends ConnectionHandler {\n\n        private FTPClient ftpClient;\n//        private CustomFTPClient ftpClient;\n\n        /** Controls whether passive mode should be used for data transfers (default is true) */\n        private final boolean passiveMode;\n\n        /** Encoding used by the FTP control connection */\n        private String encoding;\n\n        /** Number of connection retry attempts after a recoverable connection failure */\n        private int nbConnectionRetries;\n\n        /** Amount of time (in seconds) to wait before retrying to connect after a recoverable connection failure */\n        private int connectionRetryDelay;\n\n        /** False if SITE UTIME command is not supported by the remote server (once tried and failed) */\n        private boolean utimeCommandSupported = true;\n\n        /** False if SITE CHMOD command is not supported by the remote server (once tried and failed) */\n        private boolean chmodCommandSupported = true;\n\n        /** Controls how ofter should keepAlive() be called by ConnectionPool */\n        private final static long KEEP_ALIVE_PERIOD = 60;\n\n//        /** Connection timeout to the FTP server in seconds */\n//        private final static int CONNECTION_TIMEOUT = 30;\n\n//        private class CustomFTPClient extends FTPClient {\n//\n//            private Socket getSocket() {\n//                return _socket_;\n//            }\n//        }\n\n\n        private FTPConnectionHandler(FileURL location) {\n            super(location);\n\n            // Use the passive mode property if it is set\n            String passiveModeProperty = location.getProperty(PASSIVE_MODE_PROPERTY_NAME);\n            // Passive mode is enabled by default if property isn't specified\n            this.passiveMode = passiveModeProperty==null || !passiveModeProperty.equals(\"false\");\n\n            // Use the encoding property if it is set\n            this.encoding = location.getProperty(ENCODING_PROPERTY_NAME);\n            if (StringUtils.isNullOrBlank(encoding)) {\n                encoding = DEFAULT_ENCODING;\n            }\n\n            // Use the property that controls the number of connection retries after a recoverable connection failure,\n            // if the property is set\n            String prop = location.getProperty(NB_CONNECTION_RETRIES_PROPERTY_NAME);\n            if (prop==null) {\n                nbConnectionRetries = DEFAULT_NB_CONNECTION_RETRIES;\n            } else {\n                try {\n                    nbConnectionRetries = Integer.parseInt(prop);\n                } catch(NumberFormatException e) {\n                    nbConnectionRetries = DEFAULT_NB_CONNECTION_RETRIES;\n                }\n            }\n\n            // Use the property that controls the connection retry delay after a recoverable connection failure,\n            // if the property is set\n            prop = location.getProperty(CONNECTION_RETRY_DELAY_PROPERTY_NAME);\n            if (prop == null) {\n                connectionRetryDelay = DEFAULT_CONNECTION_RETRY_DELAY;\n            } else {\n                try {\n                    connectionRetryDelay = Integer.parseInt(prop);\n                } catch(NumberFormatException e) {\n                    connectionRetryDelay = DEFAULT_CONNECTION_RETRY_DELAY;\n                }\n            }\n\n            setKeepAlivePeriod(KEEP_ALIVE_PERIOD);\n        }\n\n\n        /**\n         * Checks the last server reply code and throws an IOException if the code doesn't correspond to a positive\n         * FTP reply:\n         *\n         * <ul>\n         * <li>If the reply is a credentials error (lack of permissions or not logged in), an {@link AuthException}\n         * is thrown. For all other error codes, an IOException is thrown with the server reply message.\n         * <li>If the reply code is FTPReply.SERVICE_NOT_AVAILABLE (connection dropped prematurely), the connection\n         * will be closed before an IOException with the server reply message is thrown.\n         * </ul>\n         *\n         * <p>If the reply is a positive one (not an error error), this method does nothing.\n         */\n        private void checkServerReply() throws IOException {\n            // Check that connection went ok\n            int replyCode = ftpClient.getReplyCode();\n            LOGGER.trace(\"server reply=\"+ftpClient.getReplyString());\n\n            // Close connection if the connection dropped prematurely so that isConnected() returns false\n            if (replyCode == FTPReply.SERVICE_NOT_AVAILABLE) {\n                closeConnection();\n            }\n\n            // If not, throw an exception using the reply string\n            if (!FTPReply.isPositiveCompletion(replyCode)) {\n                if (replyCode == FTPReply.BAD_COMMAND_SEQUENCE || replyCode == FTPReply.NEED_PASSWORD || replyCode == FTPReply.NOT_LOGGED_IN) {\n                    throwAuthException(ftpClient.getReplyString());\n                } else {\n                    throw new IOException(ftpClient.getReplyString());\n                }\n            }\n        }\n\n\n        /**\n         * Checks if the given IOException corresponds to a low-level socket exception, and if that is the case,\n         * closes the connection so that {@link #isConnected()} returns false.\n         * All IOException raised by FTPClient should be checked by this method so that socket errors are properly detected.\n         */\n        private void checkSocketException(IOException e) {\n            if (((e instanceof FTPConnectionClosedException) || (e instanceof SocketException) || (e instanceof SocketTimeoutException)) && isConnected()) {\n                LOGGER.info(\"socket exception detected, closing connection\", e);\n                closeConnection();\n            }\n        }\n\n\n        //////////////////////////////////////\n        // ConnectionHandler implementation //\n        //////////////////////////////////////\n\n        @Override\n        public void startConnection() throws IOException {\n            LOGGER.info(\"connecting to {}\", getRealm().getHost());\n\n//            this.ftpClient = new CustomFTPClient();\n            this.ftpClient = new FTPClient();\n\n            int retriesLeft = nbConnectionRetries;\n            int retryDelay = connectionRetryDelay * 1000;\n            do {\n\t            try {\n\t                FileURL realm = getRealm();\n\t\n\t                // Override default port (21) if a custom port was specified in the URL\n\t                int port = realm.getPort();\n\t                LOGGER.info(\"custom port={}\", port);\n\t                if (port >= 0) {\n                        ftpClient.setDefaultPort(port);\n                    }\n\t\n\t                // Sets the control encoding\n\t                // - most modern FTP servers seem to default to UTF-8, but not all of them do.\n\t                // - commons-ftp defaults to ISO-8859-1 which is not good\n\t                // Note: this has to be done before the connection is established otherwise it won't be taken into account\n\t                LOGGER.info(\"encoding={}\", encoding);\n\t                ftpClient.setControlEncoding(encoding);\n\t\n\t                // Connect to the FTP server\n\t                ftpClient.connect(realm.getHost());\n\t\n\t//                // Set a socket timeout: default value is 0 (no timeout)\n\t//                ftpClient.setSoTimeout(CONNECTION_TIMEOUT*1000);\n\t//                FileLogger.finer(\"soTimeout=\"+ftpClient.getSoTimeout());\n\t\n\t                // Throw an IOException if server replied with an error\n\t                checkServerReply();\n\n\t                Credentials credentials = getCredentials();\n\t\n\t                // Throw an AuthException if there are no credentials\n\t                LOGGER.info(\"fileURL={} credentials={}\", realm.toString(true), credentials);\n\t                if (credentials == null) {\n                        throwAuthException(null);\n                    }\n\t\n\t                // Login\n                    if (credentials != null) {\n                        ftpClient.login(credentials.getLogin(), credentials.getPassword());\n                    }\n\t                // Throw an IOException (potentially an AuthException) if the server replied with an error\n\t                checkServerReply();\n\t\n\t                // Enables/disables passive mode\n\t                LOGGER.info(\"passiveMode={}\", passiveMode);\n\t                if (passiveMode) {\n                        this.ftpClient.enterLocalPassiveMode();\n                    } else {\n                        this.ftpClient.enterLocalActiveMode();\n                    }\n\t\n\t                // Set file type to 'binary'\n\t                ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);\n\t\n\t                // Issue 'LIST -al' command to list hidden files (instead of LIST -l), only if the corresponding\n\t                // configuration option has been manually enabled in the preferences.\n\t                // The reason for not doing so by default is that the commons-net library will fail to properly parse\n\t                // directory listings on some servers when 'LIST -al' is used (bug).\n\t                // Note that by default, if 'LIST -l' is used, the decision to list hidden files is left to the\n\t                // FTP server: some servers will choose to show them, some will not. This behavior is usually a\n\t                // configuration setting of the FTP server.\n\t                ftpClient.setListHiddenFiles(FTPProtocolProvider.getForceHiddenFilesListing());\n\n\t                if (encoding.equalsIgnoreCase(\"UTF-8\")) {\n\t                    // This command enables UTF8 on the remote server... but only a few FTP servers currently support this command\n\t                    ftpClient.sendCommand(\"OPTS UTF8 ON\");\n\t                }\n\n\t                break;\n\t            } catch(IOException e) {\n                    // Attempt to retry if the connection failed, or if the server reply corresponds to a temporary error.\n                    // Unlike 5xx errors which are permanent, 4xx errors are temporary and may be retried, quote from\n                    // RFC 959: \"The command was not accepted and the requested action did not take place, but the error\n                    // condition is temporary and the action may be requested again.\"\n\t                int replyCode = ftpClient.getReplyCode();\n                    if (!ftpClient.isConnected() || FTPReply.isNegativeTransient(replyCode)) {\n                        LOGGER.info((!ftpClient.isConnected()?\"Connection error\":\"Temporary server error (\"+replyCode+\")\")+\", retries left=\"+retriesLeft, e);\n\n                        // Retry to connect, if we have at least an attempt left\n                        if (retriesLeft > 0) {\n                            retriesLeft--;\n\n                            // Wait before retrying\n                            if (retryDelay > 0) {\n                                LOGGER.info(\"waiting {} ms before retrying to connect\", retryDelay);\n\n                                try { Thread.sleep(retryDelay); }\n                                catch(InterruptedException ignore) {}\n                            }\n\n                            continue;\n                        }\n                    }\n\n                    // Disconnect if the connection could not be established\n                    if (ftpClient.isConnected())\n                        try {\n                            ftpClient.disconnect();\n                        } catch(IOException ignore) {}\n\n                    // Re-throw the exception\n                    throw e;\n\t            }\n            } while(true);\n        }\n\n\n        @Override\n        public boolean isConnected() {\n            // FTPClient#isConnected() will always return true once it is connected and does not detect socket\n            // disconnections. Furthermore, retrieving the underlying Socket instance does not help any more:\n            // Socket#isConnected() and Socket#isClosed() do not reflect socket errors that happen after the socket is\n            // connected.\n            // Thus, the only way (AFAIK) to know if the socket is still connected is to intercept all IOException\n            // thrown by FTPClient and check if they correspond to a socket exception.\n\n            return ftpClient != null && ftpClient.isConnected();\n\n//            if(ftpClient==null || !ftpClient.isConnected())\n//                return false;\n//\n//            Socket socket = ftpClient.getSocket();\n//            FileLogger.finest(\"socket=\"+socket+\" socket.isConnected()\"+socket.isConnected()+\" socket.isClosed()=\"+socket.isClosed());\n//\n//            return socket!=null && socket.isConnected() && !socket.isClosed();\n        }\n\n\n        @Override\n        public void closeConnection() {\n            if (ftpClient != null) {\n                // Try to logout, this may fail if the connection is broken\n                try {\n                    ftpClient.logout();\n                } catch(IOException e) {\n                    e.printStackTrace();\n                }\n\n                // Close the socket connection\n                try {\n                    ftpClient.disconnect();\n                } catch(IOException e) {\n                    e.printStackTrace();\n                }\n\n                ftpClient = null;\n            }\n        }\n\n\n        @Override\n        public void keepAlive() {\n            // Send a NOOP command to the server to keep the connection alive.\n            // Note: not all FTP servers support the NOOP command.\n            if (ftpClient != null) {\n                try {\n                    ftpClient.sendNoOp();\n                } catch (IOException e) {\n                    // Checks if the IOException corresponds to a socket error and in that case, closes the connection\n                    checkSocketException(e);\n                }\n            }\n        }\n    }\n\n    /**\n     * A Permissions implementation for FTPFile.\n     */\n    private static class FTPFilePermissions extends IndividualPermissionBits implements FilePermissions {\n\n        private final org.apache.commons.net.ftp.FTPFile file;\n\n        FTPFilePermissions(org.apache.commons.net.ftp.FTPFile file) {\n            this.file = file;\n        }\n\n        public boolean getBitValue(int access, int type) {\n            int fAccess;\n            if (access == USER_ACCESS) {\n                fAccess = org.apache.commons.net.ftp.FTPFile.USER_ACCESS;\n            } else if (access == GROUP_ACCESS) {\n                fAccess = org.apache.commons.net.ftp.FTPFile.GROUP_ACCESS;\n            } else if (access == OTHER_ACCESS) {\n                fAccess = org.apache.commons.net.ftp.FTPFile.WORLD_ACCESS;\n            } else {\n                return false;\n            }\n\n            int fPermission;\n            if (type == READ_PERMISSION) {\n                fPermission = org.apache.commons.net.ftp.FTPFile.READ_PERMISSION;\n            } else if (type == WRITE_PERMISSION) {\n                fPermission = org.apache.commons.net.ftp.FTPFile.WRITE_PERMISSION;\n            } else if (type == EXECUTE_PERMISSION) {\n                fPermission = org.apache.commons.net.ftp.FTPFile.EXECUTE_PERMISSION;\n            } else {\n                return false;\n            }\n\n            return file.hasPermission(fAccess, fPermission);\n        }\n\n        public PermissionBits getMask() {\n            return FULL_PERMISSION_BITS;        \n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/ftp/FTPProtocolProvider.java",
    "content": "package com.mucommander.commons.file.impl.ftp;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.ProtocolProvider;\n\nimport java.io.IOException;\n\n/**\n * This class is the provider for the FTP filesystem implemented by {@link com.mucommander.commons.file.impl.ftp.FTPFile}.\n *\n * @author Nicolas Rinaudo\n * @see com.mucommander.commons.file.impl.ftp.FTPFile\n */\npublic class FTPProtocolProvider implements ProtocolProvider {\n\n    /** Controls whether to force the listing of hidden files */\n    private static boolean forceHiddenFilesListing = false;\n\n    /**\n     * Controls whether to force the listing of hidden files. Enabling this option will cause 'LIST -al' commands\n     * to be issued when listing files, instead of 'LIST -l'.\n     * When this option is disabled, the decision to list hidden files is left to the FTP server: some servers will\n     * choose to show them, some will not. This behavior is usually a configuration setting of the FTP server.\n     * <p>\n     * This option is disabled by default. The reason for this is that the commons-net library will fail to properly\n     * parse directory listings on some servers when 'LIST -al' is used (bug).\n     *\n     * @param value <code>true</code> to force the listing of hidden files, <code>false</code> to leave it for the\n     * server to decide whether to show hidden files or not.\n     */\n    // Todo: check if this is still needed after upgrading to commons-net 2.0\n    // Todo: this should not be a configuration variable but rather a FileURL property\n    public static void setForceHiddenFilesListing(boolean value) {\n        forceHiddenFilesListing = value;\n    }\n\n    /**\n     * Returns <code>true</code> if the listing of hidden files is forced, <code>false</code> if the decision to show\n     * them is left to the server.\n     *\n     * @return <code>true</code> if the listing of hidden files is forced, <code>false</code> if the decision to show\n     * them is left to the server.\n     * @see #setForceHiddenFilesListing(boolean)\n     */\n    public static boolean getForceHiddenFilesListing() {\n        return forceHiddenFilesListing;\n    }\n\n    @Override\n    public AbstractFile getFile(FileURL url, Object... instantiationParams) throws IOException {\n        return instantiationParams.length==0\n            ?new FTPFile(url)\n            :new FTPFile(url, (org.apache.commons.net.ftp.FTPFile)instantiationParams[0]);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/ftp/package.html",
    "content": "<body>\n  Provides an implementation of the FTP protocol.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/gzip/GzipArchiveFile.java",
    "content": "package com.mucommander.commons.file.impl.gzip;\n\nimport com.mucommander.commons.file.*;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.zip.GZIPInputStream;\n\n/**\n * GzipArchiveFile provides read-only access to archives in the Gzip format.\n *\n * <p>The actual decompression work is performed by the {@link java.util.zip.GZIPInputStream} class.\n *\n * @see com.mucommander.commons.file.impl.gzip.GzipFormatProvider\n * @author Maxence Bernard\n */\npublic class GzipArchiveFile extends AbstractROArchiveFile {\n\n    /**\n     * Creates a GzipArchiveFile on top of the given file.\n     *\n     * @param file the underlying file to wrap this archive file around\n     */\n    public GzipArchiveFile(AbstractFile file) {\n        super(file);\n    }\n\n\n    ////////////////////////////////////////\n    // AbstractArchiveFile implementation //\n    ////////////////////////////////////////\n\n    @Override\n    public ArchiveEntryIterator getEntryIterator() {\n        String extension = getExtension();\n        String name = getName();\n\t\t\n        if (extension != null) {\n            extension = extension.toLowerCase();\n\t\t\t\n            // Remove the 'gz' or 'tgz' extension from the entry's name\n            if (extension.equals(\"tgz\")) {\n                name = name.substring(0, name.length()-3) + \"tar\";\n            } else if (extension.equals(\"gz\")) {\n                name = name.substring(0, name.length()-3);\n            }\n        }\n\n        return new SingleArchiveEntryIterator(new ArchiveEntry(\"/\"+name, false, getLastModifiedDate(), -1, true));\n    }\n\n\n    @Override\n    public InputStream getEntryInputStream(ArchiveEntry entry, ArchiveEntryIterator entryIterator) throws IOException {\n        return new GZIPInputStream(getInputStream());\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/gzip/GzipFormatProvider.java",
    "content": "package com.mucommander.commons.file.impl.gzip;\n\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.ArchiveFormatProvider;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\nimport com.mucommander.commons.file.impl.SevenZipJBindingROArchiveFile;\nimport net.sf.sevenzipjbinding.ArchiveFormat;\n\nimport java.io.IOException;\n\n/**\n * This class is the provider for the 'Gzip' archive format.\n *\n * @see com.mucommander.commons.file.impl.gzip.GzipArchiveFile\n * @author Nicolas Rinaudo, Maxence Bernard\n */\npublic class GzipFormatProvider implements ArchiveFormatProvider {\n    private static final String[] EXTENSIONS = {\".gz\"};\n    private final static byte[] SIGNATURE = {};\n\n    /**\n     * Static instance of the filename filter that matches archive filenames\n     */\n    private static final ExtensionFilenameFilter FILENAME_FILTER = new ExtensionFilenameFilter(EXTENSIONS);\n\n    @Override\n    public AbstractArchiveFile getFile(AbstractFile file) throws IOException {\n        return new SevenZipJBindingROArchiveFile(file, ArchiveFormat.GZIP, SIGNATURE);\n    }\n\n    @Override\n    public FilenameFilter getFilenameFilter() {\n        return FILENAME_FILTER;\n    }\n\n    @Override\n    public String[] getFileExtensions() {\n        return EXTENSIONS;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/gzip/package.html",
    "content": "<body>\n  Provides an implementation of the gzip archive format.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/hadoop/HDFSFile.java",
    "content": "package com.mucommander.commons.file.impl.hadoop;\r\n\r\nimport com.mucommander.commons.file.*;\r\nimport org.apache.hadoop.conf.Configuration;\r\nimport org.apache.hadoop.fs.FileStatus;\r\nimport org.apache.hadoop.fs.FileSystem;\r\nimport org.apache.hadoop.fs.permission.FsPermission;\r\n//import org.apache.hadoop.security.UnixUserGroupInformation;\r\n\r\nimport java.io.IOException;\r\nimport java.net.URI;\r\n\r\n/**\r\n * {@link HadoopFile} implementation for the HDFS protocol.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class HDFSFile extends HadoopFile {\r\n\r\n    // TODO: allow a custom group to be set (see TODO below)\r\n//    /** Name of the property holding the file's group */\r\n//    public static final String GROUP_PROPERTY_NAME = \"group\";\r\n\r\n    /** Default username */\r\n    private static final String DEFAULT_USERNAME;\r\n\r\n    /** Default group */\r\n    private static final String DEFAULT_GROUP;\r\n\r\n    /** Default file permissions */\r\n    private final static FilePermissions DEFAULT_PERMISSIONS = new SimpleFilePermissions(\r\n       FsPermission.getDefault().applyUMask(FsPermission.getUMask(DEFAULT_CONFIGURATION)).toShort() & PermissionBits.FULL_PERMISSION_INT\r\n    );\r\n\r\n\r\n    static {\r\n//        try {\r\n//            UnixUserGroupInformation ugi = UnixUserGroupInformation.login(DEFAULT_CONFIGURATION);\r\n//            DEFAULT_USERNAME = ugi.getUserName();\r\n//            // Do not use default groups, as these are pretty much useless\r\n//        }\r\n//        catch(Exception e) {\r\n//            // Should never happen but default to a reasonable value if it does\r\n//            DEFAULT_USERNAME = System.getProperty(\"user.name\");\r\n//        }\r\n            DEFAULT_USERNAME = System.getProperty(\"user.name\");\r\n        DEFAULT_GROUP = DEFAULT_CONFIGURATION.get(\"dfs.permissions.supergroup\", \"supergroup\");\r\n    }\r\n\r\n\r\n    protected HDFSFile(FileURL url) throws IOException {\r\n        super(url);\r\n    }\r\n\r\n    protected HDFSFile(FileURL url, FileSystem fs, FileStatus fileStatus) throws IOException {\r\n        super(url, fs, fileStatus);\r\n    }\r\n\r\n    public static String getDefaultUsername() {\r\n        return DEFAULT_USERNAME;\r\n    }\r\n\r\n    public static String getDefaultGroup() {\r\n        return DEFAULT_GROUP;\r\n    }\r\n\r\n    private static String getUsername(FileURL url) {\r\n        Credentials credentials = url.getCredentials();\r\n        String username;\r\n        if (credentials == null || (username = credentials.getLogin()).isEmpty())\r\n            username = getDefaultUsername();\r\n\r\n        return username;\r\n    }\r\n\r\n    private static String getGroup(FileURL url) {\r\n//        // Import the group from the URL's 'group' property, if set\r\n//        String group = url.getProperty(GROUP_PROPERTY_NAME);\r\n//        if(group==null || group.isEmpty()\r\n//            group = getDefaultGroup();\r\n//\r\n//        return group;\r\n\r\n        return getDefaultGroup();\r\n    }\r\n\r\n\r\n\r\n    @Override\r\n    protected FileSystem getHadoopFileSystem(FileURL url) throws IOException {\r\n        // Note: getRealm returns a fresh instance every time\r\n        FileURL realm = url.getRealm();\r\n\r\n        Configuration conf = new Configuration();\r\n\r\n        // Import the user from the URL's authority, if set\r\n        // TODO: for some reason, setting the group has no effect: files are still created with the default supergroup\r\n        //conf.setStrings(UnixUserGroupInformation.UGI_PROPERTY_NAME, getUsername(url), getGroup(url));\r\n\r\n        if (url.containsCredentials()) {\r\n            try {\r\n                return FileSystem.get(URI.create(realm.toString(false)), conf, url.getCredentials().getLogin());\r\n            } catch (InterruptedException e) {\r\n                throw new IOException(e);\r\n            }\r\n        } else {\r\n        return FileSystem.get(URI.create(realm.toString(false)), conf);\r\n    }\r\n    }\r\n\r\n    @Override\r\n    protected void setDefaultFileAttributes(FileURL url, HadoopFile.HadoopFileAttributes atts) {\r\n        atts.setOwner(getUsername(url));\r\n        atts.setGroup(getGroup(url));\r\n        atts.setPermissions(DEFAULT_PERMISSIONS);\r\n    }\r\n\r\n    @Override\r\n    @UnsupportedFileOperation\r\n    public long getFreeSpace() throws UnsupportedFileOperationException {\r\n        throw new UnsupportedFileOperationException(FileOperation.GET_FREE_SPACE);\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/hadoop/HDFSProtocolProvider.java",
    "content": "package com.mucommander.commons.file.impl.hadoop;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.ProtocolProvider;\nimport org.apache.hadoop.fs.FileStatus;\nimport org.apache.hadoop.fs.FileSystem;\n\nimport java.io.IOException;\n\n/**\n * A file protocol provider for the Hadoop HDFS filesystem.\n *\n * @author Maxence Bernard\n */\npublic class HDFSProtocolProvider implements ProtocolProvider {\n\n    public AbstractFile getFile(FileURL url, Object... instantiationParams) throws IOException {\n        return instantiationParams.length==0\n            ?new HDFSFile(url)\n            :new HDFSFile(url, (FileSystem)instantiationParams[0], (FileStatus)instantiationParams[1]);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/hadoop/HadoopFile.java",
    "content": "package com.mucommander.commons.file.impl.hadoop;\n\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.file.filter.FilenameFilter;\nimport com.mucommander.commons.io.*;\nimport org.apache.hadoop.conf.Configuration;\nimport org.apache.hadoop.fs.*;\nimport org.apache.hadoop.fs.permission.FsPermission;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n/**\n * This abstact class provides access to the Hadoop virtual filesystem, which, like the muCommander file API, provides a\n * unified access to a number of file protocols.\n *\n * <p>{@link ProtocolFile} is fully implemented by <code>HadoopFile</code>. All is left for subclasses is to implement\n * the abstract methods defined in this class.\n *\n * @see HDFSFile\n * @author Maxence Bernard\n */\npublic abstract class HadoopFile extends ProtocolFile {\n    private static final Logger LOGGER = LoggerFactory.getLogger(HadoopFile.class);\n\n    /** The Hadoop FileSystem object */\n    private final FileSystem fs;\n    /** The Hadoop */\n    private final Path path;\n\n    /** Holds file attributes */\n    private final HadoopFileAttributes fileAttributes;\n\n    /** Cached parent file instance, null if not created yet or if this file has no parent */\n    private AbstractFile parent;\n    /** Has the parent file been determined yet? */\n    private boolean parentValSet;\n\n    /** True if this file is currently being written */\n    private boolean isWriting;\n\n    /** Default Hadoop Configuration, whose values are fetched from XML configuration files. */\n    final static Configuration DEFAULT_CONFIGURATION = new Configuration();\n    \n\n    HadoopFile(FileURL url) throws IOException {\n        this(url, null, null);\n    }\n\n    HadoopFile(FileURL url, FileSystem fs, FileStatus fileStatus) throws IOException {\n        super(url);\n\n        if(fs==null) {\n            try {\n                this.fs = getHadoopFileSystem(url);\n            }\n            catch(IOException e) {\n                throw e;\n            }\n            catch(Exception e) {\n                // FileSystem implementations throw IllegalArgumentException under various circumstances\n                throw new IOException(e.getMessage());\n            }\n        } else {\n            this.fs = fs;\n        }\n\n        if (fileStatus == null) {\n            this.path = new Path(fileURL.getPath());\n            this.fileAttributes = new HadoopFileAttributes();\n        } else {\n            this.fileAttributes = new HadoopFileAttributes(fileStatus);\n            this.path = fileStatus.getPath();\n        }\n    }\n\n    private OutputStream getOutputStream(boolean append) throws IOException {\n        OutputStream out = new CounterOutputStream(\n            append?fs.append(path):fs.create(path, true),\n            new ByteCounter() {\n                @Override\n                public synchronized void add(long nbBytes) {\n                    fileAttributes.addToSize(nbBytes);\n                    fileAttributes.setDate(System.currentTimeMillis());\n                }\n            }\n        ) {\n            @Override\n            public void close() throws IOException {\n                super.close();\n                isWriting = false;\n            }\n        };\n\n        // Update local attributes\n        fileAttributes.setExists(true);\n        fileAttributes.setDate(System.currentTimeMillis());\n        fileAttributes.setSize(0);\n\n        isWriting = true;\n\n        return out;\n    }\n\n\n    /////////////////////////////////\n    // AbstractFile implementation //\n    /////////////////////////////////\n\n    @Override\n    public AbstractFile getParent() {\n        if(!parentValSet) {\n            FileURL parentFileURL = this.fileURL.getParent();\n            if(parentFileURL!=null)\n                parent = FileFactory.getFile(fileURL.getParent());\n\n            parentValSet = true;\n        }\n\n        return parent;\n    }\n\n    @Override\n    public void setParent(AbstractFile parent) {\n        this.parent = parent;\n        this.parentValSet = true;\n    }\n\n    @Override\n    public Object getUnderlyingFileObject() {\n        return fileAttributes;\n    }\n\n    // File attributes manipulation\n\n    @Override\n    public boolean exists() {\n        return fileAttributes.exists();\n    }\n\n    @Override\n    public boolean isDirectory() {\n        return fileAttributes.isDirectory();\n    }\n\n    /**\n     * Always returns <code>false</code>, Hadoop filesystems have no symlink support.\n     *\n     * @return returns <code>false</code>, Hadoop filesystems have no symlink support.\n     */\n    @Override\n    public boolean isSymlink() {\n        // No support for symlinks\n        return false;\n    }\n\n    @Override\n    public boolean isSystem() {\n        return false;\n    }\n\n    @Override\n    public long getLastModifiedDate() {\n        return fileAttributes.getLastModifiedDate();\n    }\n\n    @Override\n    public long getSize() {\n        return fileAttributes.getSize();\n    }\n\n    @Override\n    public PermissionBits getChangeablePermissions() {\n        return FilePermissions.FULL_PERMISSION_BITS;\n    }\n\n    @Override\n    public FilePermissions getPermissions() {\n        return fileAttributes.getPermissions();\n    }\n\n    @Override\n    public String getOwner() {\n        return fileAttributes.getOwner();\n    }\n\n    @Override\n    public boolean canGetOwner() {\n        return true;\n    }\n\n    @Override\n    public String getGroup() {\n        return fileAttributes.getGroup();\n    }\n\n    @Override\n    public boolean canGetGroup() {\n        return true;\n    }\n\n    @Override\n    public short getReplication() {\n        return fileAttributes.getReplication();\n    }\n\n    @Override\n    public long getBlocksize() {\n        return fileAttributes.getBlockSize();\n    }\n\n    // Supported file operations\n\n    @Override\n    public void mkdir() throws IOException {\n        if(exists() || !fs.mkdirs(path))\n            throw new IOException();\n\n        // Update local attributes\n        fileAttributes.setExists(true);\n        fileAttributes.setDirectory(true);\n        fileAttributes.setDate(System.currentTimeMillis());\n        fileAttributes.setSize(0);\n    }\n\n    @Override\n    public void delete() throws IOException {\n        if(!fs.delete(path, false))\n            throw new IOException();\n\n        // Update local attributes\n        fileAttributes.setExists(false);\n        fileAttributes.setDirectory(false);\n        fileAttributes.setSize(0);\n    }\n\n    @Override\n    public void renameTo(AbstractFile destFile) throws IOException {\n        // Throw an exception if the file cannot be renamed to the specified destination\n        checkRenamePrerequisites(destFile, false, false);\n\n        // Delete the destination if it already exists as FileSystem#rename would otherwise fail.\n        // Note: HadoopFile#delete() does not delete directories recursively (good).\n        if(destFile.exists())\n            destFile.delete();\n\n        if(!fs.rename(path, ((HadoopFile)destFile).path))\n            throw new IOException();\n\n        // Update destination file attributes by fetching them from the server\n        ((HadoopFileAttributes)destFile.getUnderlyingFileObject()).fetchAttributes();\n\n        // Update this file's attributes locally\n        fileAttributes.setExists(false);\n        fileAttributes.setDirectory(false);\n        fileAttributes.setSize(0);\n    }\n\n    @Override\n    public void setLastModifiedDate(long lastModified) throws IOException {\n        // Note: setTimes seems to fail on HDFS directories.\n        fs.setTimes(path, lastModified, lastModified);\n\n        // Update local attributes\n        fileAttributes.setDate(lastModified);\n    }\n\n    @Override\n    public void changeReplication(short replication) throws IOException {\n        // Note: setTimes seems to fail on HDFS directories.\n        fs.setReplication(path, replication);\n\n        // Update local attributes\n        fileAttributes.setReplication(replication);\n    }\n\n    @Override\n    public void changePermission(int access, int permission, boolean enabled) throws IOException {\n        changePermissions(ByteUtils.setBit(getPermissions().getIntValue(), (permission << (access*3)), enabled));\n    }\n\n    @Override\n    public InputStream getInputStream() throws IOException {\n        return fs.open(path);\n    }\n\n    @Override\n    public OutputStream getOutputStream() throws IOException {\n        return getOutputStream(false);\n    }\n\n    @Override\n    public RandomAccessInputStream getRandomAccessInputStream() throws IOException {\n        return new HadoopRandomAccessInputStream(fs.open(path), getSize());\n    }\n\n    @Override\n    public AbstractFile[] ls() throws IOException {\n        return ls(null);\n    }\n\n\n    // Unsupported file operations\n\n    @Override\n    @UnsupportedFileOperation\n    public OutputStream getAppendOutputStream() throws IOException {\n        // Currently not supported by any of the filesystems (S3, HDFS)\n        throw new UnsupportedFileOperationException(FileOperation.APPEND_FILE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public RandomAccessOutputStream getRandomAccessOutputStream() throws IOException {\n        throw new UnsupportedFileOperationException(FileOperation.RANDOM_WRITE_FILE);\n    }\n\n    /**\n     * Always throws {@link UnsupportedFileOperationException} when called.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void copyRemotelyTo(AbstractFile destFile) throws UnsupportedFileOperationException {\n        // TODO: implement for S3\n        throw new UnsupportedFileOperationException(FileOperation.COPY_REMOTELY);\n    }\n\n    /**\n     * Always throws {@link UnsupportedFileOperationException} when called.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    public long getFreeSpace() throws IOException {\n        return fs.getStatus().getRemaining();\n    }\n\n    /**\n     * Always throws {@link UnsupportedFileOperationException} when called.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    public long getTotalSpace() throws IOException {\n        return fs.getStatus().getCapacity();\n    }\n\n\n\n    @Override\n    public AbstractFile[] ls(FilenameFilter filter) throws IOException {\n        // We need to ensure that the file is a directory: if it isn't listStatus returns an empty array but doesn't\n        // throw an exception\n        if(!exists() || !isDirectory())\n            throw new IOException();\n\n        FileStatus[] statuses = filter==null\n                ?fs.listStatus(path)\n                :fs.listStatus(path, new HadoopFilenameFilter(filter));\n\n        int nbChildren = statuses==null?0:statuses.length;\n        AbstractFile[] children = new AbstractFile[nbChildren];\n        String parentPath = fileURL.getPath();\n        if(!parentPath.endsWith(\"/\"))\n            parentPath += \"/\";\n        FileURL childURL;\n        FileStatus childStatus;\n\n        for(int i=0; i<nbChildren; i++) {\n            childStatus = statuses[i];\n\n            childURL = (FileURL)fileURL.clone();\n            childURL.setPath(parentPath + childStatus.getPath().getName());\n\n            children[i] = FileFactory.getFile(childURL, this, fs, childStatus);\n        }\n\n        return children;\n    }\n\n    @Override\n    public void changePermissions(int permissions) throws IOException {\n       fs.setPermission(path, new FsPermission((short)permissions));\n\n        // Update local attributes\n        fileAttributes.setPermissions(new SimpleFilePermissions(permissions));\n    }\n\n\n    //////////////////////\n    // Abstract methods //\n    //////////////////////\n\n    /**\n     * Returns a Hadoop {@link FileSystem} instance for the specified realm.\n     *\n     * @param realm authentication realm\n     * @return a Hadoop {@link FileSystem} instance for the specified realm.\n     * @throws IOException if the FileSystem failed to be instantiated\n     */\n    protected abstract FileSystem getHadoopFileSystem(FileURL realm) throws IOException;\n\n    /**\n     * Sets default file attributes values for the file represented by the given URL. The atributes that need to be\n     * set are those that are protocol-specific.\n     *\n     * @param url URL of the file for which to set attributes\n     * @param atts the file attributes to set\n     */\n    protected abstract void setDefaultFileAttributes(FileURL url, HadoopFileAttributes atts);\n\n\n    /**\n     * HadoopFileAttributes provides getters and setters for Hadoop file attributes. By extending\n     * <code>SyncedFileAttributes</code>, this class caches attributes for a certain amount of time\n     * after which fresh values are retrieved from the server.\n     */\n    class HadoopFileAttributes extends SyncedFileAttributes {\n\n        private final static int TTL = 60000;\n\n        // this constructor is called by the public constructor\n        private HadoopFileAttributes() throws AuthException {\n            super(TTL, false);       // no initial update\n\n            fetchAttributes();      // throws AuthException if no or bad credentials\n            updateExpirationDate(); // declare the attributes as 'fresh'\n        }\n\n        // this constructor is called by #ls()\n        private HadoopFileAttributes(FileStatus fileStatus) {\n            super(TTL, false);   // no initial update\n\n            setAttributes(fileStatus);\n            setExists(true);\n\n            updateExpirationDate(); // declare the attributes as 'fresh'\n        }\n\n        private void fetchAttributes() throws AuthException {\n            // Do not update attributes while the file is being written, as they are not reflected immediately on the\n            // name node.\n            if(isWriting)\n                return;\n\n            try {\n                setAttributes(fs.getFileStatus(path));\n                setExists(true);\n            } catch (IOException e) {\n                // File doesn't exist on the server\n                setExists(false);\n                setDefaultFileAttributes(getURL(), this);\n\n                // Rethrow AuthException\n                if (e instanceof AuthException) {\n                    throw (AuthException) e;\n                }\n            }\n        }\n\n        /**\n         * Sets the file attributes using the values contained in the specified J2SSH FileAttributes instance.\n         *\n         * @param fileStatus FileStatus instance that contains the file attributes values to use\n         */\n        private void setAttributes(FileStatus fileStatus) {\n            setDirectory(fileStatus.isDirectory());\n            setDate(fileStatus.getModificationTime());\n            setSize(fileStatus.getLen());\n            setPermissions(new SimpleFilePermissions(\n               fileStatus.getPermission().toShort() & PermissionBits.FULL_PERMISSION_INT\n            ));\n            setOwner(fileStatus.getOwner());\n            setGroup(fileStatus.getGroup());\n            setReplication(fileStatus.getReplication());\n            setBlockSize(fileStatus.getBlockSize());\n        }\n\n        /**\n         * Increments the size attribute's value by the given number of bytes.\n         *\n         * @param increment number of bytes to add to the current size attribute's value\n         */\n        private void addToSize(long increment) {\n            setSize(getSize()+increment);\n        }\n\n\n        /////////////////////////////////////////\n        // SyncedFileAttributes implementation //\n        /////////////////////////////////////////\n\n        @Override\n        public void updateAttributes() {\n            try {\n                fetchAttributes();\n            } catch(Exception e) {        // AuthException\n                LOGGER.info(\"Failed to update attributes\", e);\n            }\n        }\n    }\n\n    /**\n     * Turns a Hadoop {@link FSDataInputStream} into a {@link RandomAccessInputStream}.\n     */\n    private static class HadoopRandomAccessInputStream extends RandomAccessInputStream {\n\n        private FSDataInputStream in;\n        private long length;\n\n        private HadoopRandomAccessInputStream(FSDataInputStream in, long length) {\n            this.in = in;\n            this.length = length;\n        }\n\n        public long getOffset() throws IOException {\n            return in.getPos();\n        }\n\n        public long getLength() {\n            return length;\n        }\n\n        public void seek(long offset) throws IOException {\n            in.seek(offset);\n        }\n\n        @Override\n        public int read() throws IOException {\n            return in.read();\n        }\n\n        @Override\n        public int read(byte[] b, int off, int len) throws IOException {\n            return in.read(b, off, len);\n        }\n\n        @Override\n        public void close() throws IOException {\n        }\n    }\n\n    /**\n     * Turns a {@link FilenameFilter} into a Hadoop {@link PathFilter}.\n     */\n    private static class HadoopFilenameFilter implements PathFilter {\n\n        private FilenameFilter filenameFilter;\n\n        private HadoopFilenameFilter(FilenameFilter filenameFilter) {\n            this.filenameFilter = filenameFilter;\n        }\n\n\n        ///////////////////////////////\n        // PathFilter implementation //\n        ///////////////////////////////\n                                       \n        public boolean accept(Path path) {\n            return filenameFilter.accept(path.getName());\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/hadoop/S3File.java",
    "content": "package com.mucommander.commons.file.impl.hadoop;\n\nimport com.mucommander.commons.file.AuthException;\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileURL;\nimport org.apache.hadoop.fs.FileStatus;\nimport org.apache.hadoop.fs.FileSystem;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.URLEncoder;\nimport java.nio.charset.StandardCharsets;\n\n/**\n * {@link HadoopFile} implementation for the Amazon S3 protocol.\n *\n * <p>Even though it is working for the most part, it is flawed in several ways and should not be used.\n * See the {@link com.mucommander.commons.file.impl.s3} package for a better implementation of the Amazon S3 protocol.\n *\n * @deprecated\n * @author Maxence Bernard\n */\npublic class S3File extends HadoopFile {\n\n    protected S3File(FileURL url) throws IOException {\n        super(url);\n    }\n\n    protected S3File(FileURL url, FileSystem fs, FileStatus fileStatus) throws IOException {\n        super(url, fs, fileStatus);\n    }\n\n\n    @Override\n    protected FileSystem getHadoopFileSystem(FileURL url) throws IOException {\n        if(!url.containsCredentials())\n            throw new AuthException(url);\n\n        // Note: getRealm returns a fresh instance every time\n        FileURL realm = url.getRealm();\n\n        // Import credentials\n        Credentials creds = url.getCredentials();\n        if(creds!=null) {\n            // URL-encode secret as it may contain non URL-safe characters ('+' and '/')\n            realm.setCredentials(new Credentials(creds.getLogin(), URLEncoder.encode(creds.getPassword(), StandardCharsets.UTF_8)));\n        }\n\n        // Change the scheme to the actual Hadoop fileystem (s3 -> s3n)\n        realm.setScheme(\"s3n\");\n\n        return FileSystem.get(URI.create(realm.toString(true, false)), DEFAULT_CONFIGURATION);\n    }\n\n    @Override\n    protected void setDefaultFileAttributes(FileURL url, HadoopFileAttributes atts) {\n        // Implemented as a no-op (S3 has no user info)\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/hadoop/S3ProtocolProvider.java",
    "content": "package com.mucommander.commons.file.impl.hadoop;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.ProtocolProvider;\nimport org.apache.hadoop.fs.FileStatus;\nimport org.apache.hadoop.fs.FileSystem;\n\nimport java.io.IOException;\n\n/**\n * A file protocol provider for the Amazon S3 protocol, provided by the Hadoop virtual filesystem.\n *\n * <p>Even though it is working for the most part, it is flawed in several ways and should not be used.\n * See the {@link com.mucommander.commons.file.impl.s3} package for a better implementation of the Amazon S3 protocol.\n *\n * @deprecated  \n * @author Maxence Bernard\n */\npublic class S3ProtocolProvider implements ProtocolProvider {\n\n    public AbstractFile getFile(FileURL url, Object... instantiationParams) throws IOException {\n        return instantiationParams.length==0\n            ?new S3File(url)\n            :new S3File(url, (FileSystem)instantiationParams[0], (FileStatus)instantiationParams[1]);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/http/HTTPFile.java",
    "content": "package com.mucommander.commons.file.impl.http;\n\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.io.BlockRandomInputStream;\nimport com.mucommander.commons.io.RandomAccessInputStream;\nimport com.mucommander.commons.io.RandomAccessOutputStream;\nimport com.mucommander.commons.io.base64.Base64Encoder;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.*;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n\n/**\n * HTTPFile provides access to files located on an HTTP/HTTPS server.\n *\n * <p>The associated {@link FileURL} schemes are {@link FileProtocols#HTTP} and {@link FileProtocols#HTTPS}.\n * The host part of the URL designates the HTTP server. Credentials can be specified in the login and password parts\n * and will be used for HTTP Basic Authentication.\n *\n * <p>Here are a few examples of valid HTTP URLs:\n * <code>\n * http://www.mucommander.com/index.html<br>\n * http://www.mucommander.com/index.php?<br>\n * http://john:p4sswd@www.mucommander.com/restricted_area/<br>\n * </code>\n *\n * <p>\n * A notable feature of HTTPFile is that it handles HTML/XHTML files as archives: when any of the {@link #ls()} methods\n * is called, the HTML file is parsed and any link found in the code is considered as a file:\n * <ul>\n *  <li>If the link looks like a link to an HTML file, the child HTTPFile will be 'browsable' ({@link #isBrowsable()}\n * will return <code>true</code>).\n *  <li>If not, the file will just be a regular file.\n * </ul>\n *\n * <p>In order to avoid the cost of having to perform a HEAD request for each file, some guessing based on the URL and\n * its filename is performed to determine if the file is an HTML/XHTML file or not.\n * In practice, this works quite well for most sites but the algorithm will be confused by some non-conventional\n * file naming, for instance if an HTML file ends with the '.gif' extension.\n * <p>\n * A HEAD request is then issued only for non-HTML files, to determine their size and last modified date.\n * HTML files will thus have a size returned by {@link #getSize()} of <code>-1</code> (undetermined), and a date\n * returned by {@link #getLastModifiedDate()} corresponding to 'now' (current time).\n *\n * <p>Access to HTTP files is provided by the <code>java.net</code> API. The {@link #getUnderlyingFileObject()} method\n * allows to retrieve a <code>java.net.URL</code> instance corresponding to this HTTPFile.\n *\n * @author Maxence Bernard\n */\npublic class HTTPFile extends ProtocolFile {\n    private static final Logger LOGGER = LoggerFactory.getLogger(HTTPFile.class);\n\n    /** java.net.URL corresponding to this */\n    private final URL url;\n\n    /** Contains the attributes of the remote HTTP resource. Contains default values until the file has been resolved */\n    private final SimpleFileAttributes attributes;\n\n    /** True if the file should be resolved on the remote HTTP server to fetch attribute values, false if these are\n     * guessed. */\n    private final boolean resolve;\n\n    /** True if file has been resolved on the remote HTTP server, either successfully or unsuccessfully */\n    private boolean fileResolved;\n\n    private boolean parentValSet;\n    protected AbstractFile parent;\n\t\n    /** Permissions for HTTP files: r-- (400 octal). Only the 'user' permissions bits are supported. */\n    private final static FilePermissions PERMISSIONS = new SimpleFilePermissions(256, 448);\n\n    /** User agent used for all HTTP connections made by HTTPFile */\n    // TODO: add file API version, like trolCommander-file-API/1.0\n    private static final String USER_AGENT = \"trolCommander-file-API (Java \"+System.getProperty(\"java.vm.version\")\n                                            + \"; \" + System.getProperty(\"os.name\") + \" \" +\n                                            System.getProperty(\"os.version\") + \" \" + System.getProperty(\"os.arch\") + \")\";\n\n    /** Matches HTML and XHTML attribute key/value pairs, where the value is surrounded by Single Quotes */\n    private final static Pattern linkAttributePatternSQ = Pattern.compile(\"(src|href|SRC|HREF)='.*?'\");\n\n    /** Matches HTML and XHTML attribute key/value pairs, where the value is surrounded by Double Quotes */\n    private final static Pattern linkAttributePatternDQ = Pattern.compile(\"(src|href|SRC|HREF)=\\\".*?\\\"\");\n\n\n    HTTPFile(FileURL fileURL) throws IOException {\n        // TODO: optimize this\n        this(fileURL, new URL(fileURL.toString(false)));\n    }\n\n\t\n    HTTPFile(FileURL fileURL, URL url) throws IOException {\n        super(fileURL);\n\n        String scheme = fileURL.getScheme().toLowerCase();\n        if ((!scheme.equals(FileProtocols.HTTP) && !scheme.equals(FileProtocols.HTTPS)) || fileURL.getHost() == null) {\n            throw new IOException();\n        }\n\n        this.url = url;\n\n        attributes = getDefaultAttributes();\n\n        String mimeType;\n        String filename = fileURL.getFilename();\n        // Simple/fuzzy heuristic to avoid file resolution (HEAD) in cases where we have good reasons to believe that\n        // the URL denotes a HTML/XTHML document:\n        //  - URL's path has no filename (e.g. http://www.mucommander.com/) or path ends with '/' (e.g. http://www.mucommander.com/download/)\n        //  - URL has a query part (works most of the time, must not always)\n        //  - URL has an extension that registered with an HTML/XHTML mime type\n        if ((filename == null || fileURL.getPath().endsWith(\"/\") || fileURL.getQuery()!=null || ((mimeType=MimeTypes.getMimeType(this))!=null && isParsableMimeType(mimeType)))) {\n            attributes.setDirectory(true);\n            resolve = false;\n        } else {\n            resolve = true;\n        }\n    }\n\n\n    private static SimpleFileAttributes getDefaultAttributes() {\n        SimpleFileAttributes attributes = new SimpleFileAttributes();\n        attributes.setDate(System.currentTimeMillis());\n        attributes.setSize(-1); // Unknown\n        attributes.setPermissions(PERMISSIONS);\n        // exist = false\n        // isDirectory = false\n        // path = null (unused)\n\n        return attributes;\n    }\n\n\n    /**\n     * Returns <code>true</code> if the given mime type corresponds to HTML or XHTML and can be parsed.\n     *\n     * @param mimeType a MIME type / content type\n     * @return <code>true</code> if the given mime type corresponds to HTML or XHTML and can be parsed\n     */\n    private boolean isParsableMimeType(String mimeType) {\n        return mimeType != null\n           && (mimeType.startsWith(\"text/html\") || mimeType.startsWith(\"application/xhtml+xml\") || mimeType.startsWith(\"application/xml\"));\n    }\n\n\n    /**\n     * Performs a HEAD request on the HTTP server to retrieve the file's attributes.\n     *\n     * @throws IOException if the HEAD request failed, either because the resource doesn't exist (404) or for any other\n     * reason\n     */\n    private void resolveFile() throws IOException {\n        try {\n            LOGGER.info(\"Resolving {}\", url);\n\n            // Get URLConnection instance\n            HttpURLConnection conn = getHttpURLConnection(url);\n\n            // Use HEAD instead of GET as we don't need the body\n            conn.setRequestMethod(\"HEAD\");\n\n            // Establish connection\n            conn.connect();\n\n            // Check HTTP response code and throw appropriate IOException if request failed\n            checkHTTPResponse(conn);\n\n            // Resolve date: use last-modified header, if not set use date header, and if still not set use System.currentTimeMillis\n            long date = conn.getLastModified();\n            if (date == 0) {\n                date = conn.getDate();\n                if (date == 0) {\n                    date = System.currentTimeMillis();\n                }\n            }\n            attributes.setDate(date);\n\n            // Resolve size with content-length header (-1 if not available)\n            attributes.setSize(conn.getContentLength());\n\n            // Test if content is HTML\n            String contentType = conn.getContentType();\n            if (isParsableMimeType(contentType)) {\n                attributes.setDirectory(true);\n            }\n\n            // File was successfully resolved on the remote HTTP server and thus exists\n            attributes.setExists(true);\n        } catch(IOException e) {\n            LOGGER.info(\"Failed to resolve file {}\", url, e);\n        } finally {\n            // Mark the file as resolved, even if the request failed\n            fileResolved = true;\n        }\n    }\n\n\n    /**\n     * Opens and returns a <code>HttpURLConnection</code> to the resource denoted by the specified URL.\n     * If the {@link FileURL} contained by this HTTPFile contains {@link Credentials}, these will be used as credentials\n     * for <i>HTTP Basic Authentication<i>.\n     *\n     * @param url the URL to open\n     * @return a HttpURLConnection to the resource denoted by the specified URL\n     * @throws IOException if the HttpURLConnection could not be opened\n     */\n    private HttpURLConnection getHttpURLConnection(URL url) throws IOException {\n        // Get URLConnection instance\n        HttpURLConnection conn = (HttpURLConnection)url.openConnection();\n\n        // If credentials are contained in this HTTPFile's FileURL, use them for Basic HTTP Authentication\n        Credentials credentials = fileURL.getCredentials();\n        if (credentials != null)\n            conn.setRequestProperty(\n                \"Authorization\",\n                \"Basic \"+ Base64Encoder.encode(credentials.getLogin()+\":\"+credentials.getPassword())\n            );\n\n        // Set user-agent header.\n        conn.setRequestProperty(\"User-Agent\", USER_AGENT);\n\n        return conn;\n    }\n\n\n    /**\n     * Checks the response code of the given HttpURLConnection and :\n     * <ul>\n     *  <li>throws an {@link AuthException} if the response code is 401 (Unauthorized)\n     *  <li>throws an IOException if the response code is not in the 2xx - 3xx range (not a positive response)\n     *  <li>does nothing otherwise\n     *\n     * @param conn the HttpURLConnection connection to examine\n     * @throws AuthException if the response code is 401 (Unauthorized)\n     * @throws IOException if the response code is not in the 2xx - 3xx range (not a positive response)\n     */\n    private void checkHTTPResponse(HttpURLConnection conn) throws IOException {\n        int responseCode = conn.getResponseCode();\n        LOGGER.info(\"response code = {}\", responseCode);\n\n        // If we got a 401 (Unauthorized) response, throw an AuthException to ask for credentials\n        if (responseCode == 401) {\n            throw new AuthException(fileURL, conn.getResponseMessage());\n        }\n\n        if (responseCode < 200 || responseCode >= 400) {\n            throw new IOException(conn.getResponseMessage());\n        }\n    }\n\n    private void checkResolveFile() {\n        if (resolve && !fileResolved) {\n            try {\n                resolveFile();\n            } catch(IOException e) {\n                LOGGER.info(\"Failed to resolve {}\", url, e);\n                // file will be considered as resolved\n            }\n        }\n    }\n\n\t\n    /////////////////////////////////////////\n    // AbstractFile methods implementation //\n    /////////////////////////////////////////\n\t\n    @Override\n    public long getLastModifiedDate() {\n        checkResolveFile();\n\n        return attributes.getLastModifiedDate();\n    }\n\n    /**\n     * Implementation notes: always throws {@link UnsupportedFileOperationException}.\n     *\n     * @throws UnsupportedFileOperationException always.\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void setLastModifiedDate(long date) throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_DATE);\n    }\n\t\n    @Override\n    public long getSize() {\n        checkResolveFile();\n\n        return attributes.getSize();\t// Size == -1 if not known\n    }\n\t\n    @Override\n    public AbstractFile getParent() {\n        if (!parentValSet) {\n            FileURL parentURL = fileURL.getParent();\n            this.parent = parentURL == null ? null : FileFactory.getFile(parentURL);\n            this.parentValSet = true;\n        }\n\t\t\n        return this.parent;\n    }\n\t\n\n    @Override\n    public void setParent(AbstractFile parent) {\n        this.parent = parent;\n        this.parentValSet = true;\n    }\n\n    @Override\n    public boolean exists() {\n        if (!fileResolved) {\n            // Note: file will only be resolved once, even if the request failed\n            try {\n                resolveFile();\n            } catch(IOException ignore) {}\n        }\n\n        return attributes.exists();\n    }\n\n    @Override\n    public FilePermissions getPermissions() {\n        return attributes.getPermissions();\n    }\n\n    @Override\n    public PermissionBits getChangeablePermissions() {\n        return PermissionBits.EMPTY_PERMISSION_BITS;\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public void changePermission(int access, int permission, boolean enabled) throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_PERMISSION);\n    }\n\n    @Override\n    public String getOwner() {\n        return null;\n    }\n\n    @Override\n    public boolean canGetOwner() {\n        return false;\n    }\n\n    @Override\n    public String getGroup() {\n        return null;\n    }\n\n    @Override\n    public boolean canGetGroup() {\n        return false;\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public short getReplication() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_REPLICATION);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public long getBlocksize() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_BLOCKSIZE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public void changeReplication(short replication) throws IOException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_REPLICATION);\n    }\n\n    @Override\n    public boolean isDirectory() {\n        checkResolveFile();\n\n        return attributes.isDirectory();\n    }\n\t\n    @Override\n    public boolean isSymlink() {\n        return false;\n    }\n\n    @Override\n    public boolean isSystem() {\n        return false;\n    }\n\n    @Override\n    public InputStream getInputStream() throws IOException {\n        HttpURLConnection conn = getHttpURLConnection(this.url);\n\n        // Establish connection\n        conn.connect();\n\n        // Check HTTP response code and throw appropriate IOException if request failed\n        checkHTTPResponse(conn);\n\n        return conn.getInputStream();\n    }\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException}: HTTP files are read-only.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public OutputStream getOutputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.WRITE_FILE);\n    }\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException}: HTTP files are read-only.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public OutputStream getAppendOutputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.APPEND_FILE);\n    }\n\n    @Override\n    public RandomAccessInputStream getRandomAccessInputStream() throws IOException {\n        return new HTTPRandomAccessInputStream();\n    }\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException}: HTTP files are read-only.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public RandomAccessOutputStream getRandomAccessOutputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.RANDOM_WRITE_FILE);\n    }\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException}: HTTP files are read-only.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void delete() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.DELETE);\n    }\n\n    /**\n     * Always throws {@link UnsupportedFileOperationException} when called.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void copyRemotelyTo(AbstractFile destFile) throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.COPY_REMOTELY);\n    }\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException}: HTTP files are read-only.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void renameTo(AbstractFile destFile) throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.RENAME);\n    }\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException}: HTTP files are read-only.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void mkdir() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.CREATE_DIRECTORY);\n    }\n\n    /**\n     * Always throws {@link UnsupportedFileOperationException} when called.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public long getFreeSpace() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_FREE_SPACE);\n    }\n\n    /**\n     * Always throws {@link UnsupportedFileOperationException} when called.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public long getTotalSpace() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_TOTAL_SPACE);\n    }\n\n    /**\n     * Returns a <code>java.net.URL</code> instance corresponding to this file.\n     */\n    @Override\n    public Object getUnderlyingFileObject() {\n        return url;\n    }\n\n    @Override\n    public AbstractFile[] ls() throws IOException {\n        // Implementation note: javax.swing.text.html.HTMLEditorKit isn't quite powerful enough to be used\n        try {\n            HttpURLConnection conn = resolveAndConnect(this.url);\n            URL contextURL = conn.getURL();\n\n            try (BufferedReader br = createReader(conn)) {\n                return findChildren(br, contextURL);\n            }\n        } catch (Exception e) {\n            LOGGER.info(\"Exception caught while parsing HTML, throwing IOException\", e);\n\n            if (e instanceof IOException) {\n                throw (IOException) e;\n            }\n\n            throw new IOException();\n        }\n    }\n\n    private HttpURLConnection resolveAndConnect(URL url) throws IOException {\n        final int maxRedirects = 10;\n        for (int i = 0; i < maxRedirects; i++) {\n            // Get a connection instance\n            HttpURLConnection conn = getHttpURLConnection(url);\n\n            // Disable automatic redirections to track URL change\n            conn.setInstanceFollowRedirects(false);\n\n            // Establish connection\n            conn.connect();\n\n            // Check HTTP response code and throw appropriate IOException if request failed\n            checkHTTPResponse(conn);\n\n            int responseCode = conn.getResponseCode();\n\n            // Test if response code is in the 3xx range (redirection) and if 'Location' field is set\n\n            if (responseCode >= 300 && responseCode < 400) {\n                String locationHeader = conn.getHeaderField(\"Location\");\n                if (locationHeader != null) {\n                    // Redirect to Location field and remember context url\n                    LOGGER.info(\"Location header = {}\", locationHeader);\n                    url = new URL(url, locationHeader);\n                    // One more time\n                    continue;\n                }\n            }\n            return conn;\n        }\n        throw new IOException(\"too many redirects\");\n    }\n\n    private AbstractFile[] findChildren(BufferedReader br, URL contextURL) throws IOException {\n        List<AbstractFile> children = new ArrayList<>();\n        // List that contains children URL, a TreeSet for fast (log(n)) search operations\n        Set<String> childrenURL = new TreeSet<>();\n        Credentials credentials = fileURL.getCredentials();\n\n//            String parentPath = fileURL.getPath();\n//            if (!parentPath.endsWith(\"/\")) {\n//                parentPath += \"/\";\n//            }\n\n        String parentHost = fileURL.getHost();\n\n        //FileURL tempChildURL = (FileURL)fileURL.clone();\n\n        Pattern pattern;\n        String line;\n        final String parentPath = contextURL.toString();\n        while ((line = br.readLine()) != null) {\n            for (pattern = linkAttributePatternSQ; ; pattern = linkAttributePatternDQ) {\n                Matcher matcher = pattern.matcher(line);\n                while (matcher.find()) {\n                    String match = matcher.group();\n                    String link = match.substring(match.indexOf(pattern==linkAttributePatternSQ ? '\\'' : '\\\"') + 1, match.length()-1);\n\n                    // These are not proper URLs, skip them\n                    // Don't add the same link more than once\n                    if (!linkCanBeDownloaded(link) || childrenURL.contains(link)) {\n                        continue;\n                    }\n\n                    try {\n                        LOGGER.trace(\"creating child {} context={}\", link, contextURL);\n                        URL childURL = new URL(contextURL, link);\n\n                        // create the child FileURL instance\n                        FileURL childFileURL = FileURL.getFileURL(childURL.toExternalForm());\n                        // Keep the parent's credentials (HTTP basic authentication), only if the host is the same.\n                        // It would otherwise be unsafe.\n                        if (parentHost.equals(childFileURL.getHost())) {\n                            childFileURL.setCredentials(credentials);\n                        }\n\n                        // TODO: resolve file here instead of in the constructor, and multiplex requests just like a browser\n\n                        // skip parent\n                        if (!childURL.toString().contains(parentPath)) {\n                            continue;\n                        }\n                        children.add(FileFactory.getFile(childFileURL, null, childURL, childURL.toString()));\n                        childrenURL.add(link);\n                    } catch (IOException e) {\n                        LOGGER.info(\"Cannot create child: {}\", e);\n                    }\n                }\n\n                if (pattern == linkAttributePatternDQ) {\n                    break;\n                }\n            }\n        }\n\n        AbstractFile[] childrenArray = new AbstractFile[children.size()];\n        children.toArray(childrenArray);\n        return childrenArray;\n    }\n\n\n    private boolean linkCanBeDownloaded(String link) {\n        link = link.toLowerCase();\n        return !(link.startsWith(\"mailto\") || link.startsWith(\"#\") || link.startsWith(\"javascript:\"));\n    }\n\n    private String getContentEncoding(HttpURLConnection conn) throws IOException {\n        // Retrieve content type and throw an IOException if doesn't correspond to a parsable type (HTML/XHTML)\n        String contentType = conn.getContentType();\n        if (!isParsableMimeType(contentType)) {\n            throw new IOException(\"Document cannot be parsed (not HTML or XHTML)\");  // Todo: localize this message\n        }\n        int pos;\n        String enc = null;\n        // Extract content type information (if any)\n        if ((pos = contentType.indexOf(\"charset\")) >= 0 || (pos = contentType.indexOf(\"Charset\")) >= 0) {\n            StringTokenizer st = new StringTokenizer(contentType.substring(pos));\n            enc = st.nextToken();\n        }\n        return enc;\n    }\n\n    private BufferedReader createReader(HttpURLConnection conn) throws IOException {\n        // Use the encoding reported in HTTP header if there was one, otherwise just use the default encoding\n        String enc = getContentEncoding(conn);\n\n        InputStream in = conn.getInputStream();\n        InputStreamReader ir;\n        if (enc == null) {\n            ir = new InputStreamReader(in);\n        } else {\n            try {\n                ir = new InputStreamReader(in, enc);\n            } catch (UnsupportedEncodingException e) {\n                ir = new InputStreamReader(in);\n            }\n        }\n\n        return new BufferedReader(ir);\n    }\n\n\n    @Override\n    public boolean isHidden() {\n        return false;\n    }\n\n    @Override\n    public String getName() {\n        try {\n            return java.net.URLDecoder.decode(super.getName(), StandardCharsets.UTF_8);\n        } catch(Exception e) {\n            return super.getName();\n        }\n    }\n\n    /**\n     * Overrides AbstractFile's getInputStream(long) method to provide a more efficient implementation:\n     * use the HTTP 1.1 header to start the transfer at the given offset.\n     */\n    @Override\n    public InputStream getInputStream(long offset) throws IOException {\n        HttpURLConnection conn = getHttpURLConnection(this.url);\n\n        // Set header that allows to resume transfer\n        conn.setRequestProperty(\"Range\", \"bytes=\"+offset+\"-\");\n\n        // Establish connection\n        conn.connect();\n\n        // Check HTTP response code and throw appropriate IOException if request failed\n        checkHTTPResponse(conn);\n\n        return conn.getInputStream();\n    }\n\n\n    ///////////////////\n    // Inner classes //\n    ///////////////////\n\n\n    /**\n     * HTTPRandomAccessInputStream extends BlockRandomInputStream to provide random read access to an HTTPFile.\n     * It uses the 'Range' request header to read the HTTP resource partially, chunk by chunk and reposition the offset\n     * when {@link #seek(long)} is called.\n     */\n    private class HTTPRandomAccessInputStream extends BlockRandomInputStream {\n\n        /** Amount of data returned  */\n        private final static int CHUNK_SIZE = 1024;\n\n        /** Length of the HTTP resource */\n        private final long length;\n\n\n        private HTTPRandomAccessInputStream() throws IOException {\n            super(CHUNK_SIZE);\n\n            // HEAD the HTTP resource to get its length\n            if (!fileResolved) {\n                resolveFile();\n            }\n\n            length = getSize();\n            if (length == -1) {        // Knowing the content length is required\n                throw new IOException();\n            }\n        }\n\n        ///////////////////////////////////////////\n        // BlockRandomInputStream implementation //\n        ///////////////////////////////////////////\n\n        @Override\n        protected int readBlock(long fileOffset, byte[] block, int blockLen) throws IOException {\n            HttpURLConnection conn = getHttpURLConnection(url);\n\n            // Note: 'Range' may not be supported by the HTTP server, in that case an IOException will be thrown\n            conn.setRequestProperty(\"Range\", \"bytes=\"+fileOffset +\"-\"+ Math.min(fileOffset+blockLen, length-1));\n\n            conn.connect();\n            checkHTTPResponse(conn);\n\n            // Read up to blockLen bytes\n            try (InputStream in = conn.getInputStream()) {\n                int totalRead = 0;\n                int read;\n                while (totalRead < blockLen) {\n                    read = in.read(block, totalRead, blockLen - totalRead);\n                    if (read == -1) {\n                        break;\n                    }\n                    totalRead += read;\n                }\n\n                return totalRead;\n            }\n        }\n\n        public long getLength() {\n            return length;\n        }\n\n        @Override\n        public void close() throws IOException {\n            // No-op, the underlying stream is already closed\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/http/HTTPProtocolProvider.java",
    "content": "package com.mucommander.commons.file.impl.http;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileURL;\r\nimport com.mucommander.commons.file.ProtocolProvider;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport javax.net.ssl.*;\r\nimport java.io.IOException;\r\nimport java.net.URL;\r\nimport java.security.SecureRandom;\r\nimport java.security.cert.X509Certificate;\r\n\r\n/**\r\n * This class is the provider for the HTTP/HTTPS filesystem implemented by {@link com.mucommander.commons.file.impl.http.HTTPFile}.\r\n *\r\n * @author Nicolas Rinaudo\r\n * @see com.mucommander.commons.file.impl.http.HTTPFile\r\n */\r\npublic class HTTPProtocolProvider implements ProtocolProvider {\r\n    private static final Logger LOGGER = LoggerFactory.getLogger(HTTPProtocolProvider.class);\r\n\r\n    static {\r\n        try {\r\n            disableCertificateVerifications();\r\n        }\r\n        catch(Exception e) {\r\n            LOGGER.info(\"Failed to install a custom TrustManager\", e);\r\n        }\r\n    }\r\n\r\n    /**\r\n\t * Installs a custom <code>javax.net.ssl.X509TrustManager</code> and <code>javax.net.ssl.HostnameVerifier</code>\r\n     * to bypass the default SSL certificate verifications and blindly trust all SSL certificates, even if they are\r\n     * self-signed, expired, or do not match the requested hostname.\r\n     * As a result in such cases, <code>HttpsURLConnection#openConnection()</code> will succeed instead of throwing a\r\n     * <code>javax.net.ssl.SSLException</code>.\r\n     *\r\n     * <p>This method needs to be called only once in the JVM lifetime and will impact all HTTPS connections made,\r\n     * i.e. not only the ones made by this class.\r\n     *\r\n     * <p>This clearly is unsecure for the user, but arguably better from a feature standpoint than systematically\r\n     * failing untrusted connections.\r\n     *\r\n     * @throws Exception if an error occurred while installing the custom X509TrustManager.\r\n\t */\r\n\tprivate static void disableCertificateVerifications() throws Exception {\r\n        // Todo: find a way to warn the user when the server cannot be trusted\r\n\r\n        // create a custom X509 trust manager that does not validate certificate chains\r\n        TrustManager permissiveTrustManager = new X509TrustManager() {\r\n            public X509Certificate[] getAcceptedIssuers() {\r\n                return null;\r\n            }\r\n            public void checkServerTrusted(X509Certificate[] certs, String authType) {\r\n            }\r\n\r\n            public void checkClientTrusted(X509Certificate[] certs, String authType) {\r\n            }\r\n        };\r\n\r\n        // Install the permissive trust manager\r\n        SSLContext sc = SSLContext.getInstance(\"SSL\");\r\n        sc.init(null, new TrustManager[]{permissiveTrustManager}, new SecureRandom());\r\n        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());\r\n\r\n        // create and install a custom hostname verifier that allows hostname mismatches\r\n        HostnameVerifier permissiveHostnameVerifier = (urlHostName, session) -> true;\r\n        HttpsURLConnection.setDefaultHostnameVerifier(permissiveHostnameVerifier);\r\n    }\r\n    \r\n    public AbstractFile getFile(FileURL url, Object... instantiationParams) throws IOException {\r\n        return instantiationParams.length == 0 ? new HTTPFile(url) :new HTTPFile(url, (URL)instantiationParams[0]);\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/http/package.html",
    "content": "<body>\n  Provides an implementation of the HTTP protocol.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/iso/IsoArchiveEntry.java",
    "content": "package com.mucommander.commons.file.impl.iso;\n\nimport com.mucommander.commons.file.ArchiveEntry;\n\n/**\n * This class represents an archive entry within an ISO archive.\n *\n * @author Maxence Bernard\n */\nclass IsoArchiveEntry extends ArchiveEntry {\n\n    private long index;\n    private int sectSize;\n    private long shiftOffset;\n    private boolean audio;\n\n    IsoArchiveEntry(String path, boolean directory, long date, long size, long index, int sectSize, long shiftOffset, boolean audio) {\n        super(path, directory, date, size, true);\n\n        this.index = index;\n        this.sectSize = sectSize;\n        this.shiftOffset = shiftOffset;\n        this.audio = audio;\n    }\n\n    long getIndex() {\n        return index;\n    }\n\n    public int getSectSize() {\n        return sectSize;\n    }\n\n    public long getShiftOffset() {\n        return shiftOffset;\n    }\n\n    public boolean getAudio() {\n        return audio;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/iso/IsoArchiveFile.java",
    "content": "package com.mucommander.commons.file.impl.iso;\n\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.io.FilterRandomAccessInputStream;\nimport com.mucommander.commons.io.RandomAccessInputStream;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * IsoArchiveFile provides read-only access to archives in the ISO and NRG formats.\n *\n * @author Maxence Bernard\n * @see com.mucommander.commons.file.impl.iso.IsoFormatProvider\n */\npublic class IsoArchiveFile extends AbstractROArchiveFile {\n\n    public IsoArchiveFile(AbstractFile file) {\n        super(file);\n    }\n\n\n    @Override\n    public ArchiveEntryIterator getEntryIterator() throws IOException {\n        RandomAccessInputStream rais = getRandomAccessInputStream();\n\n        return new IsoEntryIterator(IsoParser.getEntries(this, rais).iterator(), rais);\n    }\n\n    @Override\n    public InputStream getEntryInputStream(ArchiveEntry entry, ArchiveEntryIterator entryIterator) throws IOException {\n        // Cast the entry before creating the stream, in case it fails\n        IsoArchiveEntry isoEntry = (IsoArchiveEntry) entry;\n\n        RandomAccessInputStream rais;\n        // If a IsoEntryIterator is specified, reuse the iterator's stream\n        if (entryIterator != null && entryIterator instanceof IsoEntryIterator) {\n            // Override close() as a no-op so that the stream is re-used from one entry to another -- the stream will\n            // be closed when the iterator is closed.\n            rais = new FilterRandomAccessInputStream(((IsoEntryIterator) entryIterator).getRandomAccessInputStream()) {\n                @Override\n                public void close() {\n                    // No-op\n                }\n            };\n        } else {\n            rais = getRandomAccessInputStream();\n        }\n\n        return new IsoEntryInputStream(rais, isoEntry);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/iso/IsoEntryInputStream.java",
    "content": "package com.mucommander.commons.file.impl.iso;\n\nimport com.mucommander.commons.io.RandomAccessInputStream;\nimport com.mucommander.commons.io.StreamUtils;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * <code>IsoEntryInputStream</code> allows to reads an ISO entry.\n *\n * @author Xavier Martin\n */\nclass IsoEntryInputStream extends InputStream {\n\n    private RandomAccessInputStream rais;\n    private int pos;\n    private long size;\n    private int sectSize;\n    private boolean audio;\n\n    IsoEntryInputStream(RandomAccessInputStream rais, IsoArchiveEntry entry) throws IOException {\n        this.rais = rais;\n        this.size = entry.getSize();\n        this.pos = 0;\n        this.sectSize = entry.getSectSize();\n        this.audio = entry.getAudio();\n        rais.seek(IsoUtil.offsetInSector(entry.getIndex(), sectSize, audio) + entry.getShiftOffset());\n    }\n\n\n    ////////////////////////////////\n    // InputStream implementation //\n    ////////////////////////////////\n\n    @Override\n    public int read() throws IOException {\n        return rais.read();\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    @Override\n    public int read(byte b[]) throws IOException {\n        return read(b, 0, b.length);\n    }\n\n    @Override\n    public int read(byte b[], int off, int len) throws IOException {\n        int available = available();\n        int toRead = len;\n\n        if (toRead > available) {\n            toRead = available;\n        }\n\n        if (available == 0) {\n            return -1;\n        }\n\n        int ret;\n        int cur = off;\n        int before = pos;\n        int skip = 0;\n\n        // on the 1st run : generate a valid wav header\n        if (audio && pos == 0) {\n            IsoUtil.toArray(0x46464952, b, 0);                         // \"RIFF\"\n            IsoUtil.toArray((int) size - 8, b, 4);                     // size of file - 8\n            IsoUtil.toArray(0x45564157, b, 8);                         // \"WAVE\"\n            IsoUtil.toArray(0x20746D66, b, 12);                        // \"fmt \"\n            b[16] = 0x10;                                              // Chunk Data Size\n            IsoUtil.toArray(0x00020001, b, 20);                        // WAVE type format : PCM header 0100, stereo 0200\n            IsoUtil.toArray(0x0000AC44, b, 24);                        // sample rate : 44100hz\n            IsoUtil.toArray(0x0002B110, b, 28);                        // bytes/sec : 176400\n            IsoUtil.toArray(0x00100004, b, 32);                        // Block alignment 0400  + Bits/sample 1000\n            IsoUtil.toArray(0x61746164, b, 36);                        // \"data\"\n            IsoUtil.toArray((int) size - IsoUtil.WAV_header, b, 40);   // size of 'real' data\n            cur = IsoUtil.WAV_header;\n            toRead -= IsoUtil.WAV_header;\n            pos += IsoUtil.WAV_header;\n        }\n\n        switch (sectSize) {\n            case IsoUtil.MODE2_2336:\n                skip = IsoUtil.MODE2_2336_skip;\n                break;\n            case IsoUtil.MODE2_2352:\n                skip = IsoUtil.MODE2_2352_skip;\n                break;\n            case IsoUtil.MODE1_2048:\n                // shortcut\n                ret = rais.read(b, off, toRead);\n                if (ret != -1)\n                    pos += ret;\n                return ret;\n        }\n\n        // atm it work because it's called for 8192 (2048 * 4)\n        int full = toRead >> 11;\n        int half = toRead % IsoUtil.MODE1_2048;\n        if (full >= 1) {\n            for (int i = 0; i < full; i++) {\n                ret = rais.read(b, cur, IsoUtil.MODE1_2048);\n                if (ret != -1)\n                    pos += ret;\n                if (!audio)\n                    StreamUtils.skipFully(rais, IsoUtil.MODE2_EC_skip + skip);\n                cur += IsoUtil.MODE1_2048;\n            }\n            ret = rais.read(b, cur, half);\n            if (ret != -1)\n                pos += ret;\n        } else {\n            // in fact it doesn't work for internal viewer, because it's called by chunk of 1024\n            ret = rais.read(b, cur, half);\n            if (ret != -1)\n                pos += ret;\n            if (((pos % 2048) == 0) && !audio)\n                StreamUtils.skipFully(rais, IsoUtil.MODE2_EC_skip + skip);\n        }\n\n        return pos - before;\n    }\n\n    @Override\n    public int available() {\n        int available = (int) (size - pos);\n        return (available < 0) ? 0 : available;\n    }\n\n    @Override\n    public void close() throws IOException {\n        rais.close();\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/iso/IsoEntryIterator.java",
    "content": "package com.mucommander.commons.file.impl.iso;\n\nimport com.mucommander.commons.file.ArchiveEntry;\nimport com.mucommander.commons.file.WrapperArchiveEntryIterator;\nimport com.mucommander.commons.io.RandomAccessInputStream;\n\nimport java.io.IOException;\nimport java.util.Iterator;\n\n/**\n * This class iterates through the entries of an ISO file, and keeps the ISO file's\n * {@link #getRandomAccessInputStream RandomAccessInputStream} so that it doesn't have to be opened each time a\n * new entry is read. {@link #close} closes the stream.\n *\n * @author Maxence Bernard\n */\nclass IsoEntryIterator extends WrapperArchiveEntryIterator {\n\n    /**\n     * The ISO file's InputStream\n     */\n    private RandomAccessInputStream rais;\n\n    public IsoEntryIterator(Iterator<? extends ArchiveEntry> iterator, RandomAccessInputStream rais) {\n        super(iterator);\n\n        this.rais = rais;\n    }\n\n    /**\n     * Returns the ISO file's {@link RandomAccessInputStream} that was passed to the constructor.\n     *\n     * @return the ISO file's {@link RandomAccessInputStream} that was passed to the constructor.\n     */\n    RandomAccessInputStream getRandomAccessInputStream() {\n        return rais;\n    }\n\n    /**\n     * Closes the ISO file's {@link RandomAccessInputStream} that was passed to the constructor.\n     *\n     * @throws IOException if an I/O error occurs while closing the stream\n     */\n    @Override\n    public void close() throws IOException {\n        rais.close();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/iso/IsoFormatProvider.java",
    "content": "package com.mucommander.commons.file.impl.iso;\n\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.ArchiveFormatProvider;\nimport com.mucommander.commons.file.SevenZipArchiveFormatDetector;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\nimport com.mucommander.commons.file.impl.SevenZipJBindingROArchiveFile;\nimport net.sf.sevenzipjbinding.ArchiveFormat;\n\nimport java.io.IOException;\n\n/**\n * This class is the provider for the 'Iso' and 'Nrg' archive formats implemented by {@link IsoArchiveFile}.\n *\n * @author Nicolas Rinaudo, Maxence Bernard\n * @see com.mucommander.commons.file.impl.iso.IsoArchiveFile\n */\npublic class IsoFormatProvider implements ArchiveFormatProvider {\n\n    /**\n     * Array of format extensions\n     */\n    private final static String[] EXTENSIONS = {\".iso\", \".nrg\"};\n\n    private static final int[] SIGNATURE_FAT_MBR = {0xEB, -1, -1, 'M', 'S', 'D', 'O', 'S'};\n\n    /**\n     * Static instance of the filename filter that matches archive filenames\n     */\n    private final static ExtensionFilenameFilter FILENAME_FILTER = new ExtensionFilenameFilter(EXTENSIONS);\n\n    private static final SevenZipArchiveFormatDetector detector = new SevenZipArchiveFormatDetector(SIGNATURE_FAT_MBR.length) {\n        @Override\n        protected ArchiveFormat detect(byte[] bytes) {\n            if (checkSignature(bytes, SIGNATURE_FAT_MBR)) {\n                return ArchiveFormat.FAT;\n            }\n            return ArchiveFormat.ISO;\n        }\n    };\n\n    @Override\n    public AbstractArchiveFile getFile(AbstractFile file) throws IOException {\n        if (file.getExtension().equalsIgnoreCase(\"nrg\")) {\n            return new IsoArchiveFile(file);\n        }\n        return new SevenZipJBindingROArchiveFile(file, detector);\n    }\n\n    @Override\n    public FilenameFilter getFilenameFilter() {\n        return FILENAME_FILTER;\n    }\n\n    @Override\n    public String[] getFileExtensions() {\n        return EXTENSIONS;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/iso/IsoParser.java",
    "content": "package com.mucommander.commons.file.impl.iso;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.io.BufferPool;\nimport com.mucommander.commons.io.RandomAccessInputStream;\nimport com.mucommander.commons.io.StreamUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Calendar;\nimport java.util.List;\n\n/**\n * Parses entries contained in an ISO/NRG file.\n * <p>\n * <pre>\n * Reference: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-119.pdf\n * <p/>\n * Todo:\n *      * test with more images\n *      * rewrite/sanitize InputStream for cooked\n *      * add RockRidge, UDF and others extensions\n *      * add DiscJuggler & other weirdo file formats\n * </pre>\n *\n * @author Xavier Martin\n */\nclass IsoParser {\n    private static final Logger LOGGER = LoggerFactory.getLogger(IsoParser.class);\n    public static List<IsoArchiveEntry> getEntries(byte[] buffer, RandomAccessInputStream rais, int sectSize, long sector_offset, long shiftOffset) throws Exception {\n        List<IsoArchiveEntry> entries = new ArrayList<>();\n\n        Calendar calendar = Calendar.getInstance();\n        int start = 16;\n        isoPvd pvd = null;\n        todo todo_idr = null;\n\n        int level = 0;\n        for (int i = 1; i < 17; i++) {  // fuzzy search, can have type=0 (bootable el torito), type=2 (svd)\n            pvd = new isoPvd(buffer, rais, start + i, sectSize, shiftOffset);\n            if (pvd.type[0] == 2 && pvd.id[0] == 'C' && pvd.id[1] == 'D' && pvd.id[2] == '0' && pvd.id[3] == '0' && pvd.id[4] == '1') {\n                // gotta read docs a little more about those UCS-2 Escape Sequences\n                switch (pvd.unused3[2]) {\n                    case 0x40:\n                        level = 1;\n                        break;\n                    case 0x43:\n                        level = 2;\n                        break;\n                    case 0x45:\n                        level = 3;\n                }\n                break;\n            }\n        }\n\n        if (level == 0) // if no SVD with Joliet, fallback to plain-old ISO9660\n            pvd = new isoPvd(buffer, rais, start, sectSize, shiftOffset);\n\n        isoDr idr = new isoDr(pvd.root_directory_record, 0);\n        todo_idr = parse_dir(todo_idr, \"\", isonum_733(idr.extent), isonum_733(idr.size), rais, buffer, entries, sectSize, level, shiftOffset, sector_offset, calendar);\n        while (todo_idr != null) {\n            todo_idr = parse_dir(todo_idr, todo_idr.name, todo_idr.extent, todo_idr.length, rais, buffer, entries, sectSize, level, shiftOffset, sector_offset, calendar);\n            todo_idr = todo_idr.next;\n        }\n\n        return entries;\n    }\n\n    /**\n     * Parses the given ISO file and returns the list of entries it contains. The specified stream will *not* be closed\n     * by this method.\n     *\n     * @param file the ISO file to parse\n     * @param rais random access stream to read the ISO file. It will *not* be closed by this method.\n     * @return the list of entries contained by the ISO file\n     * @throws IOException if an I/O error occurs\n     */\n    static List<IsoArchiveEntry> getEntries(AbstractFile file, RandomAccessInputStream rais) throws IOException {\n        byte[] buffer = BufferPool.getByteArray(IsoUtil.MODE1_2048);\n\n        try {\n            if (\"nrg\".equals(file.getExtension())) {\n                return NrgParser.getEntries(buffer, file, rais);\n            }\n\n            int sectSize = IsoUtil.guessSectorSize(file);\n\n            // sector shift : 0 most of the time\n            long sector_offset = 0;\n\n            // bytes : depend if there's earlier track we discard\n            long shiftOffset = 0;\n\n            return getEntries(buffer, rais, sectSize, sector_offset, shiftOffset);\n\n            /*\n            if (\"cdi\".equals(file.getExtension())) {\n                // WIP\n                // http://cvs.berlios.de/cgi-bin/viewcvs.cgi/libdiscmage/libdiscmage/src/filter/cdi.c?revision=1.3&view=markup\n                int len = rais.available();\n                long offset = -1;\n\n                rais.seek(len - 7);\n                rais.read(buffer, 0, 8);\n                long version = IsoUtil.toDwordBE(buffer, 0);\n                offset = IsoUtil.toDwordBE(buffer, 4);\n                FileLogger.finest(\"cdi root \" + Long.toHexString(offset) + \" version \" + Long.toHexString(version));\n\n            }\n            */\n        }\n        catch (Exception e) {\n            LOGGER.info(\"Exception caught while parsing iso, throwing IOException\", e);\n\n            throw new IOException();\n        }\n        finally {\n            // Release the buffer\n            BufferPool.releaseByteArray(buffer);\n        }\n    }\n\n\n    private static void newString(byte[] b, int len, int level, StringBuffer name) throws Exception {\n        name.append((level == 0) ? new String(b, 0, len) : new String(b, 0, len, \"UnicodeBigUnmarked\"));\n    }\n\n    public static todo parse_dir(todo todo_idr, String rootname, int extent, int len, RandomAccessInputStream rais, byte[] buffer, List<IsoArchiveEntry> entries, int sectSize, int level, long shiftOffset, long sector_offset, Calendar calendar) throws Exception {\n        todo td;\n        int i;\n        isoDr idr;\n\n        while (len > 0) {\n            rais.seek(IsoUtil.offsetInSector(extent - sector_offset, sectSize, false) + shiftOffset);\n            StreamUtils.readFully(rais, buffer);\n            len -= buffer.length;\n            extent++;\n            i = 0;\n            while (true) {\n                idr = new isoDr(buffer, i);\n                if (idr.length[0] == 0) break;\n                stat fstat_buf = new stat();\n                StringBuffer name_buf = new StringBuffer();\n                fstat_buf.st_size = isonum_733(idr.size);\n                if ((idr.flags[0] & 2) > 0)\n                    fstat_buf.st_mode |= S_IFDIR;\n                else\n                    fstat_buf.st_mode |= S_IFREG;\n                if (idr.name_len[0] == 1 && idr.name[0] == 0)\n                    name_buf.append(\".\");\n                else if (idr.name_len[0] == 1 && idr.name[0] == 1)\n                    name_buf.append(\"..\");\n                else {\n                    newString(idr.name, idr.name_len[0] & 0xff, level, name_buf);\n                    //if (level == 0) { // strip ;VERSION\n                    int p = name_buf.lastIndexOf(\";\");\n                    if (p != -1)\n                        name_buf.setLength(p);\n                    p = name_buf.lastIndexOf(\".\"); // strip empty extension\n                    if (p != -1) {\n                        int s = name_buf.length() - 1;\n                        if (p == s)\n                            name_buf.setLength(s);\n                    }\n                    //}\n                }\n\n                if ((idr.flags[0] & 2) != 0\n                        && (idr.name_len[0] != 1\n                        || (idr.name[0] != 0 && idr.name[0] != 1))) {\n                    td = todo_idr;\n                    if (td != null) {\n                        while (td.next != null) td = td.next;\n                        td.next = new todo();\n                        td = td.next;\n                    } else {\n                        todo_idr = td = new todo();\n                    }\n                    td.next = null;\n                    td.extent = isonum_733(idr.extent);\n                    td.length = isonum_733(idr.size);\n                    td.name = rootname + name_buf + \"/\";\n                }\n                boolean dir = false;\n                String n = name_buf.toString();\n                if (!(\".\".equals(n) || \"..\".equals(n))) {\n                    StringBuilder name = new StringBuilder(rootname);\n                    name.append(n);\n\n                    if (S_ISDIR(fstat_buf.st_mode)) {\n                        dir = true;\n                        if (!n.endsWith(\"/\")) {\n                            name.append('/');\n                        }\n                    }\n                    calendar.set((idr.date[0] & 0xff) + 1900, idr.date[1] - 1, idr.date[2], idr.date[3], idr.date[4], idr.date[5]);\n                    // date_buf[6]\n                    // offset from Greenwich Mean Time, in 15-minute intervals, as a twos complement signed number,\n                    // positive for time zones east of Greenwich, and negative for time zones\n                    calendar.setTimeZone(new java.util.SimpleTimeZone(15 * 60 * 1000 * idr.date[6], \"\"));\n                    entries.add(\n                            new IsoArchiveEntry(\n                                    name.toString(),\n                                    dir,\n                                    calendar.getTimeInMillis(),\n                                    fstat_buf.st_size,\n                                    isonum_733(idr.extent) - sector_offset,\n                                    sectSize,\n                                    shiftOffset,\n                                    false)\n                    );\n                }\n\n                i += (buffer[i] & 0xff);\n                if (i > IsoUtil.MODE1_2048 - idr.s_length) break;\n            }\n        }\n\n        return todo_idr;\n    }\n\n    // ======================================\n    private static int ISODCL(int start, int end) {\n        return (end - start + 1);\n    }\n\n    private static int isonum_731(byte[] p) {\n        return IsoUtil.toDwordBE(p, 0);\n    }\n\n    public static int isonum_733(byte[] p) {\n        return (isonum_731(p));\n    }\n\n    private static boolean S_ISDIR(int m) {\n        return ((m & S_IFDIR) == S_IFDIR);\n    }\n\n    // ======================================\n    private static int S_IFREG = 0100000;\n    private static int S_IFDIR = 0040000;\n\n    private static class stat {\n        int st_size;\n        int st_mode;\n    }\n\n    public static class todo {\n        public todo next;\n        public String name;\n        public int extent;\n        public int length;\n    }\n\n    public static class isoDr {\n        public byte[] length = new byte[ISODCL(1, 1)];\n        public byte[] ext_attr_length = new byte[ISODCL(2, 2)];\n        public byte[] extent = new byte[ISODCL(3, 10)];\n        public byte[] size = new byte[ISODCL(11, 18)];\n        public byte[] date = new byte[ISODCL(19, 25)];\n        public byte[] flags = new byte[ISODCL(26, 26)];\n        public byte[] file_unit_size = new byte[ISODCL(27, 27)];\n        public byte[] interleave = new byte[ISODCL(28, 28)];\n        public byte[] volume_sequence_number = new byte[ISODCL(29, 32)];\n        public byte[] name_len = new byte[ISODCL(33, 33)];\n        public byte[] name = new byte[/*38*/128];   // quickly bumped to 128 for Joliet : doesn't lead to a crash yet :)\n\n        public int s_length = 34;\n\n        public byte[][] dataDr = {length, ext_attr_length, extent, size,\n                date, flags, file_unit_size, interleave,\n                volume_sequence_number, name_len, name};\n\n        public isoDr(byte[] src, int pos) {\n            for (int i = 0, max = dataDr.length; i < max; i++) {\n                int l = dataDr[i].length;\n                if ((src.length - pos) < dataDr[i].length && i == 10)\n                    l = src.length - pos;\n                System.arraycopy(src, pos, dataDr[i], 0, l);\n                pos += dataDr[i].length;\n            }\n        }\n    }\n\n    public static class isoPvd {\n        public byte[] type = new byte[ISODCL(1, 1)];\n        public byte[] id = new byte[ISODCL(2, 6)];\n        public byte[] version = new byte[ISODCL(7, 7)];\n        public byte[] unused1 = new byte[ISODCL(8, 8)];\n        public byte[] system_id = new byte[ISODCL(9, 40)];\n        public byte[] volume_id = new byte[ISODCL(41, 72)];\n        public byte[] unused2 = new byte[ISODCL(73, 80)];\n        public byte[] volume_space_size = new byte[ISODCL(81, 88)];\n        public byte[] unused3 = new byte[ISODCL(89, 120)];\n        public byte[] volume_set_size = new byte[ISODCL(121, 124)];\n        public byte[] volume_sequence_number = new byte[ISODCL(125, 128)];\n        public byte[] logical_block_size = new byte[ISODCL(129, 132)];\n        public byte[] path_table_size = new byte[ISODCL(133, 140)];\n        public byte[] type_l_path_table = new byte[ISODCL(141, 144)];\n        public byte[] opt_type_l_path_table = new byte[ISODCL(145, 148)];\n        public byte[] type_m_path_table = new byte[ISODCL(149, 152)];\n        public byte[] opt_type_m_path_table = new byte[ISODCL(153, 156)];\n        public byte[] root_directory_record = new byte[ISODCL(157, 190)];\n        public byte[] volume_set_id = new byte[ISODCL(191, 318)];\n        public byte[] publisher_id = new byte[ISODCL(319, 446)];\n        public byte[] preparer_id = new byte[ISODCL(447, 574)];\n        public byte[] application_id = new byte[ISODCL(575, 702)];\n        public byte[] copyright_file_id = new byte[ISODCL(703, 739)];\n        public byte[] abstract_file_id = new byte[ISODCL(740, 776)];\n        public byte[] bibliographic_file_id = new byte[ISODCL(777, 813)];\n        public byte[] creation_date = new byte[ISODCL(814, 830)];\n        public byte[] modification_date = new byte[ISODCL(831, 847)];\n        public byte[] expiration_date = new byte[ISODCL(848, 864)];\n        public byte[] effective_date = new byte[ISODCL(865, 881)];\n        public byte[] file_structure_version = new byte[ISODCL(882, 882)];\n        public byte[] unused4 = new byte[ISODCL(883, 883)];\n        public byte[] application_data = new byte[ISODCL(884, 1395)];\n        public byte[] unused5 = new byte[ISODCL(1396, IsoUtil.MODE1_2048)];\n\n        byte[][] dataPvr = {type, id, version, unused1,\n                system_id, volume_id, unused2, volume_space_size,\n                unused3, volume_set_size, volume_sequence_number, logical_block_size,\n                path_table_size, type_l_path_table, opt_type_l_path_table, type_m_path_table,\n                opt_type_m_path_table, root_directory_record, volume_set_id, publisher_id,\n                preparer_id, application_id, copyright_file_id, abstract_file_id,\n                bibliographic_file_id, creation_date, modification_date, expiration_date,\n                effective_date, file_structure_version, unused4, application_data,\n                unused5};\n\n        public isoPvd(byte[] pvd, RandomAccessInputStream rais, int start, int sectSize, long shiftOffset) throws IOException {\n            rais.seek(IsoUtil.offsetInSector(start, sectSize, false) + shiftOffset);\n            if (rais.read(pvd) == -1) {\n                throw new IOException(\"unable to read PVD\");\n            }\n            load(pvd);\n        }\n\n        void load(byte[] src) {\n            for (int i = 0, pos = 0, max = dataPvr.length; i < max; i++) {\n                System.arraycopy(src, pos, dataPvr[i], 0, dataPvr[i].length);\n                pos += dataPvr[i].length;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/iso/IsoUtil.java",
    "content": "package com.mucommander.commons.file.impl.iso;\n\nimport com.mucommander.commons.file.AbstractFile;\n\n/**\n * @author Xavier Martin\n */\nclass IsoUtil {\n    static final int MODE1_2048 = 2048;\n    static final int MODE2_2352 = 2352;\n    static final int MODE2_2336 = 2336;\n\n    static final int MODE2_2352_skip = 24;\n    static final int MODE2_2336_skip = 8;\n\n    static final int MODE2_EC_skip = 280;\n\n    static final int WAV_header = 44;\n\n    public static int guessSectorSize(AbstractFile file) {\n        int sectSize = MODE1_2048;\n\n        // cooked images are usually 2352 bytes/sector, so it's 1st check here\n        if (file.getSize() % MODE2_2352 == 0) {\n            sectSize = MODE2_2352;\n        } else if (file.getSize() % MODE2_2336 == 0) {\n            sectSize = MODE2_2336;\n        }\n\n        return sectSize;\n    }\n\n    public static int toDwordBE(byte[] p, int offset) {\n        return ((p[offset] & 0xff)\n                | ((p[1 + offset] & 0xff) << 8)\n                | ((p[2 + offset] & 0xff) << 16)\n                | ((p[3 + offset] & 0xff) << 24));\n    }\n\n    public static int toDword(byte[] p, int offset) {\n        return ((p[3 + offset] & 0xff)\n                | ((p[2 + offset] & 0xff) << 8)\n                | ((p[1 + offset] & 0xff) << 16)\n                | ((p[offset] & 0xff) << 24));\n    }\n\n    public static void toArray(int value, byte[] b, int offset) {\n        b[offset] = (byte) (value & 0xff);\n        b[offset + 1] = (byte) ((value & 0xff00) >> 8);\n        b[offset + 2] = (byte) ((value & 0xff0000) >> 16);\n        b[offset + 3] = (byte) ((value & 0xff000000) >> 24);\n    }\n\n    public static long offsetInSector(long index, int sectSize, boolean audio) {\n        switch (sectSize) {\n            case MODE2_2352:\n                return (index * 2352) + (audio ? 0 : 24);\n            case MODE2_2336:\n                return (index * 2336) + 8;\n        }\n        return index << 11;\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/iso/MuCreateISO.java",
    "content": "/*\n * Copyright (c) 2010. Stephen Connolly.\n * Copyright (C) 2007. Jens Hatlak <hatlak@rbg.informatik.tu-darmstadt.de>\n *\n * This library is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 2.1 of the License, or (at your option) any later version.\n *\n * This library is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public\n * License along with this library; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\n */\n\n/*\n * This file is copied from https://github.com/stephenc/java-iso-tools/blob/master/iso9660-writer/src/main/java/com/github/stephenc/javaisotools/iso9660/impl/CreateISO.java\n * and has been modified to be able to retrieve data from the arhiving process\n */\n\npackage com.mucommander.commons.file.impl.iso;\n\nimport com.github.stephenc.javaisotools.eltorito.impl.ElToritoConfig;\nimport com.github.stephenc.javaisotools.eltorito.impl.ElToritoHandler;\nimport com.github.stephenc.javaisotools.iso9660.ISO9660RootDirectory;\nimport com.github.stephenc.javaisotools.iso9660.impl.ISO9660Config;\nimport com.github.stephenc.javaisotools.iso9660.impl.ISO9660Element;\nimport com.github.stephenc.javaisotools.iso9660.impl.ISO9660Handler;\nimport com.github.stephenc.javaisotools.iso9660.impl.LogicalSectorPaddingHandler;\nimport com.github.stephenc.javaisotools.joliet.impl.JolietConfig;\nimport com.github.stephenc.javaisotools.joliet.impl.JolietHandler;\nimport com.github.stephenc.javaisotools.rockridge.impl.RockRidgeConfig;\nimport com.github.stephenc.javaisotools.sabre.HandlerException;\nimport com.github.stephenc.javaisotools.sabre.StreamHandler;\n\npublic class MuCreateISO {\n\n    private final ISO9660RootDirectory root;\n    private StreamHandler streamHandler;\n    private MuFileHandler fileHandler;\n\n    public MuCreateISO(StreamHandler streamHandler, ISO9660RootDirectory root) {\n        this.streamHandler = new LogicalSectorPaddingHandler(streamHandler, streamHandler);\n        this.root = root;\n    }\n\n    public void process(ISO9660Config iso9660Config, RockRidgeConfig rrConfig, JolietConfig jolietConfig,\n                        ElToritoConfig elToritoConfig) throws HandlerException {\n        if (iso9660Config == null) {\n            throw new NullPointerException(\"Cannot create ISO without ISO9660Config.\");\n        }\n        ((LogicalSectorPaddingHandler) streamHandler).setPadEnd(iso9660Config.getPadEnd());\n\n        // Last handler added processes data first\n        if (jolietConfig != null) {\n            streamHandler = new JolietHandler(streamHandler, root, jolietConfig);\n        }\n        if (elToritoConfig != null) {\n            streamHandler = new ElToritoHandler(streamHandler, elToritoConfig);\n        }\n        streamHandler = new ISO9660Handler(streamHandler, root, iso9660Config, rrConfig);\n        fileHandler = new MuFileHandler(streamHandler, root);\n        streamHandler = fileHandler;\n\n        streamHandler.startDocument();\n\n        // System Area\n        streamHandler.startElement(new ISO9660Element(\"SA\"));\n        streamHandler.endElement();\n\n        // Volume Descriptor Set\n        streamHandler.startElement(new ISO9660Element(\"VDS\"));\n        streamHandler.endElement();\n\n        // Boot Info Area\n        streamHandler.startElement(new ISO9660Element(\"BIA\"));\n        streamHandler.endElement();\n\n        // Path Table Area\n        streamHandler.startElement(new ISO9660Element(\"PTA\"));\n        streamHandler.endElement();\n\n        // Directory Records Area\n        streamHandler.startElement(new ISO9660Element(\"DRA\"));\n        streamHandler.endElement();\n\n        // Boot Data Area\n        streamHandler.startElement(new ISO9660Element(\"BDA\"));\n        streamHandler.endElement();\n\n        // File Contents Area\n        streamHandler.startElement(new ISO9660Element(\"FCA\"));\n        streamHandler.endElement();\n\n        streamHandler.endDocument();\n    }\n    \n    /**\n     * @return Name of current file being processed\n     */\n    public String getProcessingFile(){\n        return fileHandler != null ? fileHandler.getProcessingFile() : null;\n    }\n    \n    /**\n     * Written bytes in total without the current file progress\n     * @return number of bytes written as a long\n     */\n    public long totalWrittenBytes(){\n        return fileHandler != null ? fileHandler.totalWrittenBytes(): 0;\n    }\n\n    /**\n     * Written bytes to the current file being processed, will be the same size as the\n     * file if complete.\n     * @return number of bytes written as a long\n     */\n    public long writtenBytesCurrentFile() {\n        return fileHandler != null ? fileHandler.writtenBytesCurrentFile(): 0;\n    }\n    \n    /**\n     * @return Size of the current file being processed in bytes\n     */\n    public long currentFileLength(){\n        return fileHandler != null ? fileHandler.currentFileLength(): 0;\n    }\n    \n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/iso/MuFileHandler.java",
    "content": "/*\n * Copyright (c) 2010. Stephen Connolly.\n * Copyright (C) 2007. Jens Hatlak <hatlak@rbg.informatik.tu-darmstadt.de>\n *\n * This library is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 2.1 of the License, or (at your option) any later version.\n *\n * This library is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public\n * License along with this library; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\n */\n\n/*\n * This file is copied from https://github.com/stephenc/java-iso-tools/blob/master/iso9660-writer/src/main/java/com/github/stephenc/javaisotools/iso9660/impl/FileHandler.java\n * and has been modified to be able to retrieve data from the arhiving process\n */\n\npackage com.mucommander.commons.file.impl.iso;\n\nimport com.github.stephenc.javaisotools.iso9660.ISO9660Directory;\nimport com.github.stephenc.javaisotools.iso9660.ISO9660File;\nimport com.github.stephenc.javaisotools.iso9660.ISO9660RootDirectory;\nimport com.github.stephenc.javaisotools.iso9660.impl.FileElement;\nimport com.github.stephenc.javaisotools.iso9660.impl.ISO9660Element;\nimport com.github.stephenc.javaisotools.sabre.DataReference;\nimport com.github.stephenc.javaisotools.sabre.Element;\nimport com.github.stephenc.javaisotools.sabre.HandlerException;\nimport com.github.stephenc.javaisotools.sabre.StreamHandler;\nimport com.github.stephenc.javaisotools.sabre.impl.ChainingStreamHandler;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Iterator;\n\npublic class MuFileHandler extends ChainingStreamHandler {\n\n    private final ISO9660RootDirectory root;\n    private String processingFile = null;\n    private DataReferenceProgress drp = null;\n    private long totalWritenBytes = 0;\n\n    public MuFileHandler(StreamHandler streamHandler, ISO9660RootDirectory root) {\n        super(streamHandler, streamHandler);\n        this.root = root;\n    }\n\n    public void startElement(Element element) throws HandlerException {\n        if (element instanceof ISO9660Element) {\n            String id = (String) element.getId();\n            process(id);\n        }\n        super.startElement(element);\n    }\n\n    private void process(String id) throws HandlerException {\n        if (id.equals(\"FCA\")) {\n            doFCA();\n        }\n    }\n\n    private void doFCA() throws HandlerException {\n        doFCADirs(root);\n\n        Iterator<ISO9660Directory> it = root.sortedIterator();\n        while (it.hasNext()) {\n            ISO9660Directory dir = it.next();\n            doFCADirs(dir);\n        }\n    }\n\n    private void doFCADirs(ISO9660Directory dir) throws HandlerException {\n        for (ISO9660File file : dir.getFiles()) {\n            doFile(file);\n        }\n    }\n\n    private void doFile(ISO9660File file) throws HandlerException {\n        processingFile = file.getName();\n        DataReferenceProgress dataReferenceProgress = new DataReferenceProgress(file.getDataReference());\n        drp = dataReferenceProgress;\n        \n        super.startElement(new FileElement(file));\n\n        data(dataReferenceProgress);\n        super.endElement();\n        totalWritenBytes += currentFileLength();\n    }\n\n    /**\n     * @return Name of current file being processed\n     */\n    public String getProcessingFile() {\n        return processingFile;\n    }\n    \n    /**\n     * Written bytes in total without the current file progress\n     * @return number of bytes written as a long\n     */\n    public long totalWrittenBytes(){\n        return drp != null ? totalWritenBytes : 0;\n    }\n    \n    /**\n     * @return Size of the current file being processed in bytes\n     */\n    public long currentFileLength(){\n        return drp != null ? drp.getLength() : 0;\n    }\n\n    /**\n     * Written bytes to the current file being processed, will be the same size as the\n     * file if complete.\n     * @return number of bytes written as a long\n     */\n    public long writtenBytesCurrentFile() {\n        return drp != null ? drp.getWrittenBytes(): 0;\n    }\n    \n    /*\n     * DataReference wrapper that will allow the progress to be retrieved\n     */\n    private static class DataReferenceProgress implements DataReference{\n        private final DataReference dr;\n        private InputStream is = null;\n        \n        DataReferenceProgress(DataReference dr){\n            this.dr = dr;\n        }\n\n        @Override\n        public long getLength() {\n            return dr.getLength();\n        }\n        \n        public int available(){\n            try {\n                return is != null ? is.available() : 0;\n            } catch (IOException ex) {}\n            return 0;\n        }\n        \n        public long getWrittenBytes(){\n            return is != null ? getLength() - available(): 0;\n        }\n\n        @Override\n        public InputStream createInputStream() throws IOException {\n            is = dr.createInputStream();\n            return is; \n        }\n\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/iso/NrgParser.java",
    "content": "package com.mucommander.commons.file.impl.iso;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.io.RandomAccessInputStream;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Vector;\n\n/**\n * @see <a href=\"http://en.wikipedia.org/wiki/NRG_(file_format)\">NRG file format on Wikipedia</a>\n * @author Xavier Martin\n */\nclass NrgParser extends IsoParser {\n\n    static List<IsoArchiveEntry> getEntries(byte[] buffer, AbstractFile file, RandomAccessInputStream rais) throws Exception {\n        int sectSize = IsoUtil.MODE1_2048;\n\n        // sector shift : 0 most of the time\n        long sector_offset = 0;\n\n        // bytes : depend if there's earlier track we discard\n        long shiftOffset = 0;\n\n        int len = rais.available();\n\n        int[] tracksMode = new int[255];\n        long[] tracksOffset = new long[255];\n        long[] tracksStart = new long[255];\n        long[] tracksEnd = new long[255];\n\n        int tracks = 0;\n\n        for (int i = 7; i <= 11; i += 4) {\n            long offset = -1;\n\n            rais.seek(len - i);\n\n            if (rais.read(buffer, 0, (i == 7) ? 4 : 12) == -1)\n                throw new IOException(\"unable to read tail of nrg file\");\n\n            if (buffer[0] == 'N' && buffer[1] == 'E' && buffer[2] == 'R' && buffer[3] == '5') // v2 footer\n                offset = IsoUtil.toDword(buffer, 8);\n            else if (buffer[0] == 'N' && buffer[1] == 'E' && buffer[2] == 'R' && buffer[3] == 'O') // v1 footer\n                offset = IsoUtil.toDword(buffer, 4);\n\n            if (offset == -1)\n                continue;\n\n            // read chunks\n            boolean end = false;\n            for (int j = 0; j < 255 && !end; j++) {\n                long clen;\n\n                rais.seek(offset);\n\n                if (rais.read(buffer, 0, 8) == -1)\n                    throw new IOException(\"unable to read chunk in tail of nrg file\");\n\n                if (buffer[0] == 'E' && buffer[1] == 'N' && buffer[2] == 'D' && buffer[3] == '!')\n                    end = true;\n                else if (buffer[0] == 'E' && buffer[1] == 'T' && buffer[2] == 'N') {\n                    clen = IsoUtil.toDword(buffer, 4);\n                    boolean ETN2 = buffer[3] == '2';\n\n                    if (rais.read(buffer, 0, (int) clen) == -1)\n                        throw new IOException(\"unable to read chunk in tail of nrg file\");\n\n                    for (int z = 0; z < clen; z += ETN2 ? 32 : 20) {\n                        tracksOffset[tracks] = IsoUtil.toDword(buffer, ETN2 ? 4 : 0);\n                        tracksMode[tracks] = IsoUtil.toDword(buffer, ETN2 ? 16 : 8);\n                        tracks++;\n                    }\n                    end = true;\n                } else if (buffer[0] == 'D' && buffer[1] == 'A' && buffer[2] == 'O') {\n                    boolean DAOX = buffer[3] == 'X';\n                    clen = IsoUtil.toDword(buffer, 4);\n                    offset += 8 + clen;\n\n                    // skip endian\n                    if (rais.read(buffer, 0, 4) == -1)\n                        throw new IOException(\"unable to skip endian in DAO chunk\");\n\n                    if (rais.read(buffer, 0, (int) clen) == -1)\n                        throw new IOException(\"unable to read DAO chunk\");\n\n                    int first = buffer[16];\n                    int cur = first - 1;\n                    for (int z = 18; z < clen - 4; z += DAOX ? 42 : 30) {\n                        tracksMode[cur] = buffer[z + 14];\n                        tracksOffset[cur] = IsoUtil.toDword(buffer, DAOX ? z + 30 : 22);\n                        tracksEnd[cur] = IsoUtil.toDword(buffer, DAOX ? z + 38 : 26) - 1;\n                        cur++;\n                    }\n                    tracks = cur;\n                    rais.seek(offset);\n                    // TODO CUES\n                } else if (buffer[0] == 'C' && buffer[1] == 'U' && buffer[2] == 'E' && buffer[3] == 'X') {\n                    clen = IsoUtil.toDword(buffer, 4);\n                    offset += 8 + clen;\n\n                    if (rais.read(buffer, 0, (int) clen) == -1)\n                        throw new IOException(\"unable to read CUEX chunk\");\n\n                    for (int z = 0; z < clen; z += 8) {\n                        //long toc = IsoUtil.toDword(buffer, z);\n                        long tstart = IsoUtil.toDword(buffer, z + 4);\n                        if (buffer[z + 2] == 0 || (buffer[z + 1] & 0xff) == 0xAA) {\n                            // skip toc/pregap ?\n                        } else {\n                            tracksStart[tracks] = tstart;\n                            tracks++;\n                        }\n                    }\n                    rais.seek(offset);\n                } else {\n                    // skip irrelevant chunk\n                    clen = IsoUtil.toDword(buffer, 4);\n                    offset += 8 + clen;\n                    rais.seek(offset);\n                }\n            }\n\n            boolean audioOnly = true;\n            for (int k = 0; k < tracks; k++) {\n                shiftOffset = tracksOffset[k];\n                sector_offset = tracksStart[k];\n\n                switch (tracksMode[k]) {\n                    case 0:\n                        sectSize = IsoUtil.MODE1_2048;\n                        audioOnly = false;\n                        break;\n                    case 3:\n                        sectSize = IsoUtil.MODE2_2336;\n                        audioOnly = false;\n                        break;\n                    case 7:\n                        // audio 2352 - 2352\n                        sectSize = IsoUtil.MODE2_2352;\n                        break;\n                    case 16:\n                        // TODO find a sample image w/ subchannel data\n                        // audio 2448 - 2352\n                        break;\n                    default:\n                        throw new Exception(\"unhandled mode \" + tracksMode[k]);\n                }\n            }\n\n            // fun : handle audio disc :)\n            if (audioOnly) {\n                List<IsoArchiveEntry> entries = new Vector<>();\n                for (int k = 0; k < tracks; k++) {\n                    entries.add(\n                            new IsoArchiveEntry(\n                                    file.getName() + \".TRACK\" + (k + 1) + \".wav\",\n                                    false,\n                                    file.getLastModifiedDate(),\n                                    tracksEnd[k] - tracksOffset[k] + IsoUtil.WAV_header, // adding wav header\n                                    0,\n                                    sectSize,\n                                    tracksOffset[k],\n                                    true)\n                    );\n                }\n                return entries;\n            }\n\n        }\n\n        return getEntries(buffer, rais, sectSize, sector_offset, shiftOffset);\n\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/iso/package.html",
    "content": "<body>\n  Provides an implementation of the ISO archive format.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/local/LocalFile.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2010 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU Lesser General Public License as published by\r\n * the Free Software Foundation, either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU Lesser General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU Lesser General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\n\r\npackage com.mucommander.commons.file.impl.local;\r\n\r\nimport com.mucommander.commons.file.*;\r\nimport com.mucommander.commons.file.filter.FilenameFilter;\r\nimport com.mucommander.commons.file.util.Kernel32;\r\nimport com.mucommander.commons.file.util.Kernel32API;\r\nimport com.mucommander.commons.file.util.PathUtils;\r\nimport com.mucommander.commons.io.BufferPool;\r\nimport com.mucommander.commons.io.FilteredOutputStream;\r\nimport com.mucommander.commons.io.RandomAccessInputStream;\r\nimport com.mucommander.commons.io.RandomAccessOutputStream;\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.commons.runtime.OsVersion;\r\nimport java.nio.Buffer;\r\nimport ru.trolsoft.jni.NativeFileUtils;\r\n\r\nimport java.io.*;\r\nimport java.nio.ByteBuffer;\r\nimport java.nio.channels.FileChannel;\r\nimport java.nio.file.*;\r\nimport java.nio.file.attribute.BasicFileAttributes;\r\nimport java.nio.file.attribute.FileOwnerAttributeView;\r\nimport java.nio.file.attribute.PosixFileAttributes;\r\nimport java.nio.file.attribute.UserPrincipal;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.StringTokenizer;\r\nimport java.util.regex.Matcher;\r\nimport java.util.regex.Pattern;\r\n\r\n\r\n/**\r\n * LocalFile provides access to files located on a locally-mounted filesystem. \r\n * Note that despite the class' name, LocalFile instances may indifferently be residing on a local hard drive,\r\n * or on a remote server mounted locally by the operating system.\r\n *\r\n * <p>The associated {@link FileURL} scheme is {@link FileProtocols#FILE}. The host part should be {@link FileURL#LOCALHOST},\r\n * except for Windows UNC URLs (see below). Native path separators ('/' or '\\\\' depending on the OS) can be used\r\n * in the path part.\r\n *\r\n * <p>Here are a few examples of valid local file URLs:\r\n * <code>\r\n * file://localhost/C:\\winnt\\system32\\<br>\r\n * file://localhost/usr/bin/gcc<br>\r\n * file://localhost/~<br>\r\n * file://home/maxence/..<br>\r\n * </code>\r\n *\r\n * <p>Windows UNC paths can be represented as FileURL instances, using the host part of the URL. The URL format for\r\n * those is the following:<br>\r\n * <code>file:\\\\server\\share</code> .<br>\r\n *\r\n * <p>Under Windows, LocalFile will translate those URLs back into a UNC path. For example, a LocalFile created with the\r\n * <code>file://garfield/stuff</code> FileURL will have the <code>getAbsolutePath()</code> method return\r\n * <code>\\\\garfield\\stuff</code>. Note that this UNC path translation doesn't happen on OSes other than Windows, which\r\n * would not be able to handle the path.\r\n *\r\n * <p>Access to local files is provided by the <code>java.io</code> API, {@link #getUnderlyingFileObject()} allows\r\n * to retrieve an <code>java.io.File</code> instance corresponding to this LocalFile.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class LocalFile extends ProtocolFile {\r\n\tprivate final static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(LocalFile.class);\r\n\r\n    private static final boolean IS_UNIX_BASED = OsFamily.getCurrent().isUnixBased();\r\n    /** Are we running Windows ? */\r\n    private static final boolean IS_WINDOWS = OsFamily.WINDOWS.isCurrent();\r\n\r\n\r\n    protected File file;\r\n    private final FilePermissions permissions;\r\n\r\n    /** Absolute file path, free of trailing separator */\r\n    protected String absPath;\r\n\r\n    /** Caches the parent folder, initially null until getParent() gets called */\r\n    protected AbstractFile parent;\r\n    /** Indicates whether the parent folder instance has been retrieved and cached or not (parent can be null) */\r\n    private boolean parentValueSet;\r\n\t\r\n    /** Underlying local filesystem's path separator: \"/\" under UNIX systems, \"\\\" under Windows and OS/2 */\r\n    public final static String SEPARATOR = File.separator;\r\n\r\n\r\n    /** True if the underlying local filesystem uses drives assigned to letters (e.g. A:\\, C:\\, ...) instead\r\n     * of having single a root folder '/' */\r\n    public final static boolean USES_ROOT_DRIVES = IS_WINDOWS || OsFamily.OS_2.isCurrent();\r\n\r\n    /** Pattern matching Windows-like drives' root, e.g. C:\\ */\r\n    final static Pattern DRIVE_ROOT_PATTERN = Pattern.compile(\"^[a-zA-Z][:][\\\\\\\\]\");\r\n\r\n    // Permissions can only be changed under Java 1.6 and up and are limited to 'user' access.\r\n    // Note: 'read' and 'execute' permissions have no meaning under Windows (files are either read-only or\r\n    // read-write) and as such can't be changed.\r\n\r\n    /** Changeable permissions mask for Java 1.6 and up, on OSes other than Windows */\r\n    private static final PermissionBits CHANGEABLE_PERMISSIONS_JAVA_1_6_NON_WINDOWS = new GroupedPermissionBits(448);   // rwx------ (700 octal)\r\n\r\n    /** Changeable permissions mask for Java 1.6 and up, on Windows OS (any version) */\r\n    private static final PermissionBits CHANGEABLE_PERMISSIONS_JAVA_1_6_WINDOWS = new GroupedPermissionBits(128);   // -w------- (200 octal)\r\n\r\n//    /** Changeable permissions mask for Java 1.5 or below */\r\n//    private static PermissionBits CHANGEABLE_PERMISSIONS_JAVA_1_5 = PermissionBits.EMPTY_PERMISSION_BITS;   // --------- (0)\r\n\r\n    /** Bit mask that indicates which permissions can be changed */\r\n    private final static PermissionBits CHANGEABLE_PERMISSIONS =\r\n            IS_WINDOWS ? CHANGEABLE_PERMISSIONS_JAVA_1_6_WINDOWS : CHANGEABLE_PERMISSIONS_JAVA_1_6_NON_WINDOWS;\r\n//            JavaVersion.JAVA_1_6.isCurrentOrHigher()\r\n//            ? (IS_WINDOWS ? CHANGEABLE_PERMISSIONS_JAVA_1_6_WINDOWS : CHANGEABLE_PERMISSIONS_JAVA_1_6_NON_WINDOWS)\r\n//            : CHANGEABLE_PERMISSIONS_JAVA_1_5;\r\n\r\n    /**\r\n \t * List of known UNIX filesystems.\r\n \t */\r\n \tprivate static final String[] KNOWN_UNIX_FS = { \"adfs\", \"affs\", \"autofs\", \"cifs\", \"coda\", \"cramfs\",\r\n                                                   \"debugfs\", \"efs\", \"ext2\", \"ext3\", \"fuseblk\", \"hfs\", \"hfsplus\", \"hpfs\",\r\n                                                   \"iso9660\", \"jfs\", \"minix\", \"msdos\", \"ncpfs\", \"nfs\", \"nfs4\", \"ntfs\",\r\n                                                   \"qnx4\", \"reiserfs\", \"smbfs\", \"udf\", \"ufs\", \"usbfs\", \"vfat\", \"xfs\" };\r\n\r\n    private static final boolean NATIVE_FILE_UTILS_AVAILABLE = OsFamily.MAC_OS_X.isCurrent() && NativeFileUtils.init();\r\n\r\n    static {\r\n        // Prevents Windows from poping up a message box when it cannot find a file. Those message box are triggered by\r\n        // java.io.File methods when operating on removable drives such as floppy or CD-ROM drives which have no disk\r\n        // inserted.\r\n        // This has been fixed in Java 1.6 b55 but this fixes previous versions of Java.\r\n        // See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4089199\r\n        if (IS_WINDOWS && Kernel32.isAvailable()) {\r\n            Kernel32.getInstance().SetErrorMode(Kernel32API.SEM_NOOPENFILEERRORBOX|Kernel32API.SEM_FAILCRITICALERRORS);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * @param fileURL file URL\r\n     *\r\n     * Creates a new instance of LocalFile and a corresponding {@link File} instance.\r\n     */\r\n    protected LocalFile(FileURL fileURL) throws IOException {\r\n        this(fileURL, null);\r\n    }\r\n\r\n    /**\r\n     * Creates a new instance of LocalFile, using the given {@link File} if not <code>null</code>, creating a new\r\n     * {@link File} instance otherwise.\r\n     *\r\n     * @param fileURL\r\n     * @param file\r\n     */\r\n    protected LocalFile(FileURL fileURL, File file) throws IOException {\r\n        super(fileURL);\r\n\r\n        if (file == null) {\r\n            String path = fileURL.getPath();\r\n\r\n            // Remove the leading '/' for Windows-like paths\r\n            if (USES_ROOT_DRIVES) {\r\n                path = path.substring(1);\r\n            }\r\n            \r\n            // create the java.io.File instance and throw an exception if the path is not absolute.\r\n            file = new File(path);\r\n            if (!file.isAbsolute()) {\r\n                throw new IOException();\r\n            }\r\n\r\n            absPath = file.getAbsolutePath();\r\n\r\n            // Remove the trailing separator if present\r\n            if (absPath.endsWith(SEPARATOR)) {\r\n                absPath = absPath.substring(0, absPath.length() - 1);\r\n            }\r\n        }\r\n        // the java.io.File instance was created by ls(), no need to re-create it or call the costly File#getAbsolutePath()\r\n        else {\r\n            this.absPath = fileURL.getPath();\r\n\r\n            // Remove the leading '/' for Windows-like paths\r\n            if (USES_ROOT_DRIVES) {\r\n                absPath = absPath.substring(1);\r\n            }\r\n        }\r\n\r\n        this.file = file;\r\n        this.permissions = new LocalFilePermissions(file);\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the user home folder. Most if not all OSes have one, but in the unlikely event that the OS doesn't have\r\n     * one or that the folder cannot be resolved, <code>null</code> will be returned.\r\n     *\r\n     * @return the user home folder\r\n     */\r\n    public static AbstractFile getUserHome() {\r\n        String userHomePath = System.getProperty(\"user.home\");\r\n        return userHomePath == null ? null : FileFactory.getFile(userHomePath);\r\n    }\r\n\r\n    /**\r\n     * Returns the total and free space on the volume where this file resides.\r\n     *\r\n     * <p>Using this method to retrieve both free space and volume space is more efficient than calling\r\n     * {@link #getFreeSpace()} and {@link #getTotalSpace()} separately -- the underlying method retrieving both\r\n     * attributes at the same time.\r\n     *\r\n     * @return a {totalSpace, freeSpace} long array, both values can be null if the information could not be retrieved\r\n     * @throws IOException if an I/O error occurred\r\n     */\r\n    public long[] getVolumeInfo() throws IOException {\r\n        // Under Java 1.6 and up, use the (new) java.io.File methods\r\n//        if (JavaVersion.JAVA_1_6.isCurrentOrHigher()) {\r\n            return new long[] {\r\n                getTotalSpace(),\r\n                getFreeSpace()\r\n            };\r\n//        }\r\n\r\n        // Under Java 1.5 or lower, use native methods\r\n//        return getNativeVolumeInfo();\r\n    }\r\n\r\n//    /**\r\n//     * Uses platform dependent functions to retrieve the total and free space on the volume where this file resides.\r\n//     *\r\n//     * @return a {totalSpace, freeSpace} long array, both values can be <code>null</code> if the information could not\r\n//     * be retrieved.\r\n//     * @throws IOException if an I/O error occurred\r\n//     */\r\n//    protected long[] getNativeVolumeInfo() throws IOException {\r\n//        if (IS_WINDOWS) {\r\n//            return getWindowsVolumeInfo(getAbsolutePath());\r\n//        } else if (IS_UNIX_BASED) {\r\n//            return getUnixVolumeInfo(getAbsolutePath());\r\n//        }\r\n//\r\n//        return new long[]{-1, -1};\r\n//    }\r\n\r\n\r\n\r\n//    private long[] getWindowsVolumeInfo(String absPath) throws IOException {\r\n//        long dfInfo[] = new long[]{-1, -1};\r\n//        // Use the Kernel32 DLL if it is available\r\n//        if (Kernel32.isAvailable()) {\r\n//            // Retrieves the total and free space information using the GetDiskFreeSpaceEx function of the\r\n//            // Kernel32 API.\r\n//            LongByReference totalSpaceLBR = new LongByReference();\r\n//            LongByReference freeSpaceLBR = new LongByReference();\r\n//\r\n//            if (Kernel32.getInstance().GetDiskFreeSpaceEx(absPath, null, totalSpaceLBR, freeSpaceLBR)) {\r\n//                dfInfo[0] = totalSpaceLBR.getValue();\r\n//                dfInfo[1] = freeSpaceLBR.getValue();\r\n//            } else {\r\n//                logger.warn(\"Call to GetDiskFreeSpaceEx failed, absPath={}\", absPath);\r\n//            }\r\n//        } else if (OsVersion.WINDOWS_NT.isCurrentOrHigher()) {\r\n//            // Otherwise, parse the output of 'dir \"filePath\"' command to retrieve free space information, if\r\n//            // running Window NT or higher.\r\n//            // Note: no command invocation under Windows 95/98/Me, because it causes a shell window to\r\n//            // appear briefly every time this method is called (See ticket #63).\r\n//\r\n//            // 'dir' command returns free space on the last line\r\n//            Process process = Runtime.getRuntime().exec(\r\n//                    (OsVersion.getCurrent().compareTo(OsVersion.WINDOWS_NT)>=0 ? \"cmd /c\" : \"command.com /c\")\r\n//                            + \" dir \\\"\"+absPath+\"\\\"\");\r\n//\r\n//            // Check that the process was correctly started\r\n//            if (process != null) {\r\n//                String lastLine = readLastLine(process.getInputStream());\r\n//\r\n//                // Last dir line may look like something this (might vary depending on system's language, below in French):\r\n//                // 6 Rep(s)  14 767 521 792 octets libres\r\n//                if (lastLine != null) {\r\n//                    StringTokenizer st = new StringTokenizer(lastLine, \" \\t\\n\\r\\f,.\");\r\n//                    // Discard first token\r\n//                    st.nextToken();\r\n//\r\n//                    // Concatenates as many contiguous groups of numbers\r\n//                    String token;\r\n//                    String freeSpace = \"\";\r\n//                    while(st.hasMoreTokens()) {\r\n//                        token = st.nextToken();\r\n//                        char c = token.charAt(0);\r\n//                        if (c >= '0' && c <= '9') {\r\n//                            freeSpace += token;\r\n//                        } else if(!freeSpace.isEmpty()) {\r\n//                            break;\r\n//                        }\r\n//                    }\r\n//\r\n//                    dfInfo[1] = Long.parseLong(freeSpace);\r\n//                }\r\n//            }\r\n//        }\r\n//        return dfInfo;\r\n//    }\r\n//\r\n//    private long[] getUnixVolumeInfo(String absPath) throws IOException {\r\n//        long dfInfo[] = new long[]{-1, -1};\r\n//        // Parses the output of 'df -P -k \"filePath\"' command on UNIX-based systems to retrieve free and total space information\r\n//\r\n//        // 'df -P -k' returns totals in block of 1K = 1024 bytes, -P uses the POSIX output format, ensures that line won't break\r\n//        Process process = Runtime.getRuntime().exec(new String[]{\"df\", \"-P\", \"-k\", absPath}, null, file);\r\n//\r\n//        // Check that the process was correctly started\r\n//        if (process != null) {\r\n//            // Discard the first line (\"Filesystem   1K-blocks     Used    Avail Capacity  Mounted on\");\r\n//            String line = readSecondLine(process.getInputStream());\r\n//\r\n//            // Sample lines:\r\n//            // /dev/disk0s2            116538416 109846712  6179704    95%    /\r\n//            // automount -fstab [202]          0         0        0   100%    /automount/Servers\r\n//            // /dev/disk2s2                 2520      1548      972    61%    /Volumes/muCommander 0.8\r\n//\r\n//            // We're interested in the '1K-blocks' and 'Avail' fields (only).\r\n//            // The 'Filesystem' and 'Mounted On' fields can contain spaces (e.g. 'automount -fstab [202]' and\r\n//            // '/Volumes/muCommander 0.8' resp.) and therefore be made of several tokens. A stable way to\r\n//            // determine the position of the fields we're interested in is to look for the last token that\r\n//            // starts with a '/' character which should necessarily correspond to the first token of the\r\n//            // 'Mounted on' field. The '1K-blocks' and 'Avail' fields are 4 and 2 tokens away from it\r\n//            // respectively.\r\n//\r\n//            // Start by tokenizing the whole line\r\n//            List<String> tokenV = new ArrayList<>();\r\n//            if (line != null) {\r\n//                StringTokenizer st = new StringTokenizer(line);\r\n//                while(st.hasMoreTokens()) {\r\n//                    tokenV.add(st.nextToken());\r\n//                }\r\n//            }\r\n//\r\n//            int nbTokens = tokenV.size();\r\n//            if (nbTokens < 6) {\r\n//                // This shouldn't normally happen\r\n//                logger.warn(\"Failed to parse output of df -k {} line={}\", absPath, line);\r\n//                return dfInfo;\r\n//            }\r\n//\r\n//            // Find the last token starting with '/'\r\n//            int pos = nbTokens-1;\r\n//            while (!tokenV.get(pos).startsWith(\"/\")) {\r\n//                if (pos == 0) {\r\n//                    // This shouldn't normally happen\r\n//                    logger.warn(\"Failed to parse output of df -k {} line={}\", absPath, line);\r\n//                    return dfInfo;\r\n//                }\r\n//\r\n//                --pos;\r\n//            }\r\n//\r\n//            // '1-blocks' field (total space)\r\n//            dfInfo[0] = Long.parseLong(tokenV.get(pos - 4)) * 1024;\r\n//            // 'Avail' field (free space)\r\n//            dfInfo[1] = Long.parseLong(tokenV.get(pos - 2)) * 1024;\r\n//        }\r\n//\r\n////                // Retrieves the total and free space information using the POSIX statvfs function\r\n////                POSIX.STATVFSSTRUCT struct = new POSIX.STATVFSSTRUCT();\r\n////                if(POSIX.INSTANCE.statvfs(absPath, struct)==0) {\r\n////                    dfInfo[0] = struct.f_blocks * (long)struct.f_frsize;\r\n////                    dfInfo[1] = struct.f_bfree * (long)struct.f_frsize;\r\n////                }\r\n//\r\n//        return dfInfo;\r\n//    }\r\n//\r\n//\r\n//    @Nullable\r\n//    private String readLastLine(InputStream is) throws IOException {\r\n//        String lastLine = null;\r\n//        try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {\r\n//            String line;\r\n//            // Retrieves last line of dir\r\n//            while ((line = br.readLine()) != null) {\r\n//                if (!line.trim().isEmpty()) {\r\n//                    lastLine = line;\r\n//                }\r\n//            }\r\n//            return lastLine;\r\n//        }\r\n//    }\r\n//\r\n//    private static String readSecondLine(InputStream is) throws IOException {\r\n//        try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {\r\n//            br.readLine();\r\n//            return br.readLine();\r\n//        }\r\n//    }\r\n\r\n\r\n\r\n    /**\r\n     * Attemps to detect if this file is the root of a removable media drive (floppy, CD, DVD, USB drive...).\r\n     * This method produces accurate results only under Windows.\r\n     *\r\n     * @return <code>true</code> if this file is the root of a removable media drive (floppy, CD, DVD, USB drive...). \r\n     */\r\n    public boolean guessRemovableDrive() {\r\n        if (IS_WINDOWS && Kernel32.isAvailable()) {\r\n            int driveType = Kernel32.getInstance().GetDriveType(getAbsolutePath(true));\r\n            if (driveType != Kernel32API.DRIVE_UNKNOWN) {\r\n                return driveType == Kernel32API.DRIVE_REMOVABLE || driveType == Kernel32API.DRIVE_CDROM;\r\n            }\r\n        }\r\n\r\n\r\n        // For other OS that have root drives (OS/2), a weak way to characterize removable drives is by checking if the\r\n        // corresponding root folder is read-only.\r\n        return hasRootDrives() && isRoot() && !file.canWrite();\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns <code>true</code> if the underlying local filesystem uses drives assigned to letters (e.g. A:\\, C:\\, ...)\r\n     * instead of having a single root folder '/' under which mount points are attached.\r\n     * This is <code>true</code> for the following platforms:\r\n     * <ul>\r\n     *  <li>Windows</li>\r\n     *  <li>OS/2</li>\r\n     *  <li>Any other platform that has '\\' for a path separator</li>\r\n     * </ul>\r\n     *\r\n     * @return <code>true</code> if the underlying local filesystem uses drives assigned to letters\r\n     */\r\n    public static boolean hasRootDrives() {\r\n        return IS_WINDOWS || OsFamily.OS_2.isCurrent() || \"\\\\\".equals(SEPARATOR);\r\n    }\r\n\r\n\r\n    /**\r\n     * Resolves and returns all local volumes:\r\n     * <ul>\r\n     *   <li>On UNIX-based OSes, these are the mount points declared in <code>/etc/ftab</code>.</li>\r\n     *   <li>On the Windows platform, these are the drives displayed in Explorer. Some of the returned volumes may\r\n     * correspond to removable drives and thus may not always be available -- if they aren't, {@link #exists()} will\r\n     * return <code>false</code>.</li>\r\n     * </ul>\r\n     * <p>\r\n     * The return list of volumes is purposively not cached so that new volumes will be returned as soon as they are\r\n     * mounted.\r\n     *\r\n     * @return all local volumes\r\n     */\r\n    public static AbstractFile[] getVolumes() {\r\n        List<AbstractFile> volumesV = new ArrayList<>();\r\n\r\n        // Add Mac OS X's /Volumes subfolders and not file roots ('/') since Volumes already contains a named link\r\n        // (like 'Hard drive' or whatever silly name the user gave his primary hard disk) to /\r\n        if (OsFamily.MAC_OS_X.isCurrent()) {\r\n            addMacOSXVolumes(volumesV);\r\n        } else {\r\n            // Add java.io.File's root folders\r\n            addJavaIoFileRoots(volumesV);\r\n\r\n            // Add /proc/mounts folders under UNIX-based systems.\r\n            if (IS_UNIX_BASED) {\r\n                addMountEntries(volumesV);\r\n            }\r\n        }\r\n\r\n        // Add home folder, if it is not already present in the list\r\n        AbstractFile homeFolder = getUserHome();\r\n        if (!(homeFolder == null || volumesV.contains(homeFolder))) {\r\n            volumesV.add(homeFolder);\r\n        }\r\n\r\n        AbstractFile[] volumes = new AbstractFile[volumesV.size()];\r\n        volumesV.toArray(volumes);\r\n\r\n        return volumes;\r\n    }\r\n\r\n\r\n    /**\r\n     * Resolves the root folders returned by {@link File#listRoots()} and adds them to the given <code>Vector</code>.\r\n     *\r\n     * @param v the <code>Vector</code> to add root folders to\r\n     */\r\n    private static void addJavaIoFileRoots(List<AbstractFile> v) {\r\n        // Warning : No file operation should be performed on the resolved folders as under Win32, this would cause a\r\n        // dialog to appear for removable drives such as A:\\ if no disk is present.\r\n//        File[] fileRoots = File.listRoots();\r\n//        for (File fileRoot : fileRoots) {\r\n//            try {\r\n//                v.add(FileFactory.getFile(fileRoot.getAbsolutePath(), true));\r\n//            } catch (IOException ignore) {}\r\n//        }\r\n        for (Path p : FileSystems.getDefault().getRootDirectories()) {\r\n            try {\r\n                v.add(FileFactory.getFile(p.toString(), true));\r\n            } catch (IOException ignore) {}\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Parses the output of <code>/sbin/mount -p</code> on FreeBSD or the <code>/proc/mounts</code> kernel virtual file\r\n     * otherwise, resolves all the mount points that look like regular filesystems it contains and adds them to\r\n     * the given <code>List</code>.\r\n     *\r\n     * @param list the <code>List</code> to add mount points to\r\n     */\r\n    private static void addMountEntries(List<AbstractFile> list) {\r\n        try (BufferedReader br = new BufferedReader(new InputStreamReader(streamMountPoints()))) {\r\n            String line;\r\n\r\n            // read each line in file and parse it\r\n            while ((line = br.readLine()) != null) {\r\n                line = line.trim();\r\n                // split line into tokens separated by \" \\t\\n\\r\\f\"\r\n                // tokens are: device, mount_point, fs_type, attributes, fs_freq, fs_passno\r\n                StringTokenizer st = new StringTokenizer(line);\r\n                st.nextToken();\r\n                String mountPoint = st.nextToken().replace(\"\\\\040\", \" \");\r\n                String fsType = st.nextToken();\r\n                if (isKnownFileSystem(fsType)) {\r\n                    AbstractFile file = FileFactory.getFile(mountPoint);\r\n                    if (file != null && !list.contains(file)) {\r\n                        list.add(file);\r\n                    }\r\n                }\r\n            }\r\n        } catch (Exception e) {\r\n            String warning = \"Error parsing\" + (OsFamily.FREEBSD.isCurrent() ? \"/sbin/mount -p output\" : \"/proc/mounts entries\");\r\n            logger.warn(warning, e);\r\n        }\r\n    }\r\n\r\n    private static InputStream streamMountPoints() throws IOException {\r\n        return OsFamily.FREEBSD.isCurrent() ?\r\n                new ProcessBuilder(\"/sbin/mount\", \"-p\").start().getInputStream() : new FileInputStream(\"/proc/mounts\");\r\n    }\r\n\r\n\r\n    private static boolean isKnownFileSystem(String fsType) {\r\n        for (String fs : KNOWN_UNIX_FS) {\r\n            if (fs.equals(fsType)) {\r\n                // this is really known physical FS\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    /**\r\n     * Adds all <code>/Volumes</code> subfolders to the given <code>Vector</code>.\r\n     *\r\n     * @param v the <code>Vector</code> to add the volumes to\r\n     */\r\n    private static void addMacOSXVolumes(List<AbstractFile> v) {\r\n        // /Volumes not resolved for some reason, giving up\r\n        AbstractFile volumesFolder = FileFactory.getFile(\"/Volumes\");\r\n        if (volumesFolder == null) {\r\n            return;\r\n        }\r\n\r\n        // Adds subfolders\r\n        try {\r\n            AbstractFile[] volumesFiles = volumesFolder.ls();\r\n\r\n            for (AbstractFile folder : volumesFiles) {\r\n                if (folder.isDirectory()) {\r\n                    // The primary hard drive (the one corresponding to '/') is listed under Volumes and should be\r\n                    // returned as the first volume\r\n                    if (folder.getCanonicalPath().equals(\"/\")) {\r\n                        v.add(0, folder);\r\n                    } else {\r\n                        v.add(folder);\r\n                    }\r\n                }\r\n            }\r\n        } catch(IOException e) {\r\n        \tlogger.warn(\"Can't get /Volumes subfolders\", e);\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns a <code>java.io.File</code> instance corresponding to this file.\r\n     */\r\n    @Override\r\n    public Object getUnderlyingFileObject() {\r\n        return file;\r\n    }\r\n\r\n    @Override\r\n    public boolean isSymlink() {\r\n        // At the moment symlinks under Windows (aka NTFS junction points) are not supported because java.io.File\r\n        // knows nothing about them and there is no way to discriminate them. So there is no need to waste time\r\n        // comparing canonical paths, just return false.\r\n        // TODO: add support for .lnk files (~hard links)\r\n        if (IS_WINDOWS) {\r\n            return false;\r\n        }\r\n\r\n        // Check the case if we have a symbolic link with wrong target path\r\n        if (!file.isFile()) {\r\n            Path path = FileSystems.getDefault().getPath(getAbsolutePath(), \"\");\r\n            if (Files.isSymbolicLink(path)) {\r\n                return true;\r\n            }\r\n        }\r\n\r\n        // Note: this value must not be cached as its value can change over time (canonical path can change)\r\n        return Files.isSymbolicLink(file.toPath());\r\n//        AbstractFile parent = getParent();\r\n//        String canonPath = getCanonicalPath(false);\r\n//        if (parent == null || canonPath == null) {\r\n//            return false;\r\n//        }\r\n//            String parentCanonPath = parent.getCanonicalPath(true);\r\n//            return !canonPath.equalsIgnoreCase(parentCanonPath+getName());\r\n        }\r\n\r\n    @Override\r\n    public boolean isSystem() {\r\n        if (OsFamily.MAC_OS_X.isCurrent()) {\r\n        \treturn MacOsSystemFolder.isSystemFile(this);\r\n        }\r\n        if (OsFamily.WINDOWS.isCurrent()) {\r\n    \t\tif (!Kernel32.isAvailable()) {\r\n                return false;\r\n            }\r\n\r\n    \t\tString filePath = file.getAbsolutePath();\r\n    \t\tint attributes = Kernel32.getInstance().GetFileAttributes(filePath); \r\n\r\n    \t\t// if GetFileAttributes() fails we try FindFirstFile() as fallback\r\n    \t\t// such a case would be pagefile.sys\r\n    \t\tif (attributes == Kernel32API.INVALID_FILE_ATTRIBUTES) {\r\n    \t\t\tKernel32API.FindFileHandle findFileHandle = null;\r\n    \t\t\tKernel32API.WIN32_FIND_DATA findFileData = new Kernel32API.WIN32_FIND_DATA();        \t\t\r\n\r\n    \t\t\ttry {\r\n    \t\t\t\tfindFileHandle = Kernel32.getInstance().FindFirstFile(filePath, findFileData);\r\n\r\n    \t\t\t\tif (findFileHandle.isValid()) {\r\n    \t\t\t\t\tattributes = findFileData.dwFileAttributes;\r\n    \t\t\t\t}\r\n    \t\t\t} finally {\r\n    \t\t\t\tif (findFileHandle != null && findFileHandle.isValid()) {\r\n    \t\t\t\t\tKernel32.getInstance().FindClose(findFileHandle);\t\r\n    \t\t\t\t}\r\n    \t\t\t}\r\n    \t\t}\r\n\r\n    \t\treturn (attributes & Kernel32API.FILE_ATTRIBUTE_SYSTEM) != 0;\r\n    \t}\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    public long getLastModifiedDate() {\r\n        return file.lastModified();\r\n    }\r\n\r\n    @Override\r\n    public long getCreationDate() throws IOException {\r\n        return Files.readAttributes(file.toPath(), BasicFileAttributes.class).creationTime().toMillis();\r\n    }\r\n\r\n    @Override\r\n    public long getLastAccessDate() throws IOException {\r\n        return Files.readAttributes(file.toPath(), BasicFileAttributes.class).lastAccessTime().toMillis();\r\n    }\r\n\r\n    @Override\r\n    public void setLastModifiedDate(long lastModified) throws IOException {\r\n        // java.io.File#setLastModified(long) throws an IllegalArgumentException if time is negative.\r\n        // If specified time is negative, set it to 0 (01/01/1970).\r\n        if (lastModified < 0) {\r\n            lastModified = 0;\r\n        }\r\n\r\n        if (!file.setLastModified(lastModified)) {\r\n            throw new IOException();\r\n        }\r\n    }\r\n\t\t\r\n    @Override\r\n    public long getSize() {\r\n        return file.length();\r\n    }\r\n\t\r\n    @Override\r\n    public AbstractFile getParent() {\r\n        // Retrieve the parent AbstractFile instance and cache it\r\n        if (!parentValueSet) {\r\n            if (!isRoot()) {\r\n                FileURL parentURL = getURL().getParent();\r\n                if (parentURL != null) {\r\n                    parent = FileFactory.getFile(parentURL);\r\n                }\r\n            }\r\n            parentValueSet = true;\r\n        }\r\n        return parent;\r\n    }\r\n\t\r\n    @Override\r\n    public void setParent(AbstractFile parent) {\r\n        this.parent = parent;\r\n        this.parentValueSet = true;\r\n    }\r\n\t\t\r\n    @Override\r\n    public boolean exists() {\r\n        return file.exists();\r\n    }\r\n\t\r\n    @Override\r\n    public FilePermissions getPermissions() {\r\n        return permissions;\r\n    }\r\n\r\n    @Override\r\n    public PermissionBits getChangeablePermissions() {\r\n        return CHANGEABLE_PERMISSIONS;\r\n    }\r\n\r\n    @Override\r\n    public void changePermission(int access, int permission, boolean enabled) throws IOException {\r\n        // Only the 'user' permissions under Java 1.6 are supported\r\n        if (access != USER_ACCESS) {\r\n            throw new IOException();\r\n        }\r\n\r\n        boolean success = false;\r\n        if (permission == READ_PERMISSION) {\r\n            success = file.setReadable(enabled);\r\n        } else if (permission == WRITE_PERMISSION) {\r\n            success = file.setWritable(enabled);\r\n        } else if (permission == EXECUTE_PERMISSION) {\r\n            success = file.setExecutable(enabled);\r\n        }\r\n\r\n        if (!success) {\r\n            throw new IOException();\r\n        }\r\n    }\r\n//    private String getOwnerPosix() {\r\n//        Path path = Paths.get(file.toURI());\r\n//        try {\r\n//            PosixFileAttributes attr = Files.readAttributes(path, PosixFileAttributes.class);\r\n//            return attr.owner().getName();\r\n//        } catch (IOException | UnsupportedOperationException e) {\r\n//            e.printStackTrace();\r\n//        return null;\r\n//    }\r\n//    }\r\n\t@Override\r\n\tpublic String getOwner() {\r\n\t\ttry {\r\n\t\t\tPath path = Paths.get(file.toURI());\r\n\t\t\tif (Files.exists(path)) {\r\n\t\t\t\tFileOwnerAttributeView ownerAttributeView = Files.getFileAttributeView(path, FileOwnerAttributeView.class);\r\n\t\t\t\tUserPrincipal owner = ownerAttributeView.getOwner();\r\n\t\t\t\treturn owner.getName();\r\n\t\t\t\t// PosixFileAttributes attr = Files.readAttributes(path,\r\n\t\t\t\t// PosixFileAttributes.class);\r\n\t\t\t\t// return attr.owner().getName();\r\n\t\t\t} else {\r\n\t\t\t\treturn null;\r\n\t\t\t}\r\n\t\t} catch (IOException | UnsupportedOperationException e) {\r\n\t\t\te.printStackTrace();\r\n\t\t\treturn null;\r\n\t\t}\r\n\t}\r\n\r\n    /**\r\n     * Always returns <code>true</code> for unix-based systems\r\n     */\r\n    @Override\r\n    public boolean canGetOwner() {\r\n        return IS_UNIX_BASED;\r\n    }\r\n\r\n\r\n\t@Override\r\n\tpublic String getGroup() {\r\n        Path path = Paths.get(file.toURI());\r\n        if (!Files.exists(path)) {\r\n            return null;\r\n        }\r\n        try {\r\n            PosixFileAttributes attr = Files.readAttributes(path, PosixFileAttributes.class);\r\n            return attr.group().getName();\r\n        } catch (UnsupportedOperationException e) {\r\n            logger.debug(\"File {} doesn't have an owner: {}. Enable trace to see the exception.\", file.getAbsolutePath(), e.getMessage());\r\n            logger.trace(\"File {} doesn't have an owner: {}.\", file.getAbsolutePath(), e.getMessage(), e);\r\n            return null;\r\n        } catch (IOException e) {\r\n            logger.warn(\"Error for [{}]\", file.getAbsolutePath(), e);\r\n            // DosFileAttributeView dos = Files.getFileAttributeView(\r\n            // path, DosFileAttributeView.class);\r\n//\t\t\t\t\tAclFileAttributeView aclFileAttributes = Files.getFileAttributeView(path,\r\n//\t\t\t\t\t\t\tAclFileAttributeView.class);\r\n//\r\n//\t\t\t\t\tfor (AclEntry aclEntry : aclFileAttributes.getAcl()) {\r\n//\t\t\t\t\t\tSystem.out.println(aclEntry.principal() + \":\");\r\n//\t\t\t\t\t\tSystem.out.println(aclEntry.permissions() + \"\\n\");\r\n//\t\t\t\t\t}\r\n            return null;\r\n        }\r\n\t}\r\n\r\n    /**\r\n     * Always returns <code>true</code> for unit based systems\r\n     */\r\n    @Override\r\n    public boolean canGetGroup() {\r\n        return IS_UNIX_BASED;\r\n    }\r\n\r\n    @Override\r\n    public boolean isDirectory() {\r\n        if (NATIVE_FILE_UTILS_AVAILABLE) {\r\n            return NativeFileUtils.isLocalDirectory(file.getAbsolutePath());\r\n        }\r\n        // This test is not necessary anymore now that 'No disk' error dialogs are disabled entirely (using Kernel32\r\n        // DLL's SetErrorMode function). Leaving this code commented for a while in case the problem comes back.\r\n\r\n//        // To avoid drive seeks and potential 'floppy drive not available' dialog under Win32\r\n//        // triggered by java.io.File.isDirectory()\r\n//        if(IS_WINDOWS && guessFloppyDrive())\r\n//            return true;\r\n\r\n        return file.isDirectory();\r\n    }\r\n\r\n    /**\r\n     * Implementation notes: the returned <code>InputStream</code> uses a NIO {@link FileChannel} under the hood to\r\n     * benefit from <code>InterruptibleChannel</code> and allow a thread waiting for an I/O to be gracefully interrupted\r\n     * using <code>Thread#interrupt()</code>.\r\n     */\r\n    @Override\r\n    public InputStream getInputStream() throws IOException {\r\n        return new LocalInputStream(new FileInputStream(file).getChannel());\r\n    }\r\n\r\n    /**\r\n     * Implementation notes: the returned <code>InputStream</code> uses a NIO {@link FileChannel} under the hood to\r\n     * benefit from <code>InterruptibleChannel</code> and allow a thread waiting for an I/O to be gracefully interrupted\r\n     * using <code>Thread#interrupt()</code>.\r\n     */\r\n    @Override\r\n    public OutputStream getOutputStream() throws IOException {\r\n        return new LocalOutputStream(new FileOutputStream(absPath, false).getChannel());\r\n    }\r\n\r\n    /**\r\n     * Implementation notes: the returned <code>InputStream</code> uses a NIO {@link FileChannel} under the hood to\r\n     * benefit from <code>InterruptibleChannel</code> and allow a thread waiting for an I/O to be gracefully interrupted\r\n     * using <code>Thread#interrupt()</code>.\r\n     */\r\n    @Override\r\n    public OutputStream getAppendOutputStream() throws IOException {\r\n        return new LocalOutputStream(new FileOutputStream(absPath, true).getChannel());\r\n    }\r\n\r\n    /**\r\n     * Implementation notes: the returned <code>InputStream</code> uses a NIO {@link FileChannel} under the hood to\r\n     * benefit from <code>InterruptibleChannel</code> and allow a thread waiting for an I/O to be gracefully interrupted\r\n     * using <code>Thread#interrupt()</code>.\r\n     */\r\n    @Override\r\n    public RandomAccessInputStream getRandomAccessInputStream() throws IOException {\r\n        return new LocalRandomAccessInputStream(new RandomAccessFile(file, \"r\").getChannel());\r\n    }\r\n\r\n    /**\r\n     * Implementation notes: the returned <code>InputStream</code> uses a NIO {@link FileChannel} under the hood to\r\n     * benefit from <code>InterruptibleChannel</code> and allow a thread waiting for an I/O to be gracefully interrupted\r\n     * using <code>Thread#interrupt()</code>.\r\n     */\r\n    @Override\r\n    public RandomAccessOutputStream getRandomAccessOutputStream() throws IOException {\r\n        return new LocalRandomAccessOutputStream(new RandomAccessFile(file, \"rw\").getChannel());\r\n    }\r\n\r\n    @Override\r\n    public void delete() throws IOException {\r\n        if (!file.delete()) {\r\n            throw new IOException();\r\n        }\r\n    }\r\n\r\n\r\n    @Override\r\n    public AbstractFile[] ls() throws IOException {\r\n        return ls(null);\r\n    }\r\n\r\n    @Override\r\n    public void mkdir() throws IOException {\r\n        Path path = FileSystems.getDefault().getPath(getAbsolutePath(), \"\");\r\n        try {\r\n            Files.createDirectory(path);\r\n        } catch (AccessDeniedException e) {\r\n            throw new FileAccessDeniedException();\r\n        } catch (IOException e) {\r\n            throw e;\r\n        } catch (Throwable t) {\r\n            throw new IOException(t);\r\n        }\r\n    }\r\n\t\r\n    @Override\r\n    public void renameTo(AbstractFile destFile) throws IOException {\r\n        // Throw an exception if the file cannot be renamed to the specified destination.\r\n        // Fail in some situations where java.io.File#renameTo() doesn't.\r\n        // Note that java.io.File#renameTo()'s implementation is system-dependant, so it's always a good idea to\r\n        // perform all those checks even if some are not necessary on this or that platform.\r\n        checkRenamePrerequisites(destFile, true, false);\r\n\r\n        // The behavior of java.io.File#renameTo() when the destination file already exists is not consistent\r\n        // across platforms:\r\n        // - Under UNIX, it succeeds and return true\r\n        // - Under Windows, it fails and return false\r\n        // This ticket goes in great details about the issue: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4017593\r\n        //\r\n        // => Since this method is required to succeed when the destination file exists, the Windows platform needs\r\n        // special treatment.\r\n\r\n        destFile = destFile.getTopAncestor();\r\n        File destJavaIoFile = ((LocalFile)destFile).file;\r\n\r\n        if (IS_WINDOWS) {\r\n            // This check is necessary under Windows because java.io.File#renameTo(java.io.File) does not return false\r\n            // if the destination file is located on a different drive, contrary for example to Mac OS X where renameTo\r\n            // returns false in this case.\r\n            // Not doing this under Windows would mean files would get moved between drives with renameTo, which doesn't\r\n            // allow the transfer to be monitored.\r\n            // Note that Windows UNC paths are handled by checkRenamePrerequisites() when comparing hosts for equality.\r\n            if (!getRoot().equals(destFile.getRoot())) {\r\n                throw new IOException();\r\n            }\r\n\r\n            // Windows 9x or Windows Me: Kernel32's MoveFileEx function is NOT available\r\n            if (OsVersion.WINDOWS_ME.isCurrentOrLower()) {\r\n                // The destination file is deleted before calling java.io.File#renameTo().\r\n                // Note that in this case, the atomicity of this method is not guaranteed anymore -- if\r\n                // java.io.File#renameTo() fails (for whatever reason), the destination file is deleted anyway.\r\n                if (destFile.exists()) {\r\n                    if (!destJavaIoFile.delete()) {\r\n                        throw new IOException();\r\n                    }\r\n                }\r\n            } else if (Kernel32.isAvailable()) {\r\n                // Windows NT: Kernel32's MoveFileEx can be used, if the Kernel32 DLL is available.\r\n\r\n                // Note: MoveFileEx is always used, even if the destination file does not exist, to avoid having to\r\n                // call #exists() on the destination file which has a cost.\r\n                if (!Kernel32.getInstance().MoveFileEx(absPath, destFile.getAbsolutePath(),\r\n                        Kernel32API.MOVEFILE_REPLACE_EXISTING | Kernel32API.MOVEFILE_WRITE_THROUGH)) {\r\n                \tString errorMessage = Integer.toString(Kernel32.getInstance().GetLastError());\r\n                \t// TODO: use Kernel32.FormatMessage\r\n                    throw new IOException(\"Rename using Kernel32 API failed: \" + errorMessage);\r\n                } else {\r\n                \t// move successful\r\n                \treturn;\r\n                }\r\n            }\r\n            // else fall back to java.io.File#renameTo\r\n        }\r\n\r\n        if (!file.renameTo(destJavaIoFile)) {\r\n            throw new IOException();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public long getFreeSpace() throws IOException {\r\n        return file.getUsableSpace();\r\n    }\r\n\t\r\n    @Override\r\n    public long getTotalSpace() throws IOException {\r\n        return file.getTotalSpace();\r\n    }\r\n\r\n    // Unsupported file operations\r\n\r\n    /**\r\n     * Always throws {@link UnsupportedFileOperationException} when called.\r\n     *\r\n     * @throws UnsupportedFileOperationException always\r\n     */\r\n    @Override\r\n    @UnsupportedFileOperation\r\n    public void copyRemotelyTo(AbstractFile destFile) throws UnsupportedFileOperationException {\r\n        throw new UnsupportedFileOperationException(FileOperation.COPY_REMOTELY);\r\n    }\r\n\r\n    @Override\r\n    @UnsupportedFileOperation\r\n    public short getReplication() throws UnsupportedFileOperationException {\r\n        throw new UnsupportedFileOperationException(FileOperation.GET_REPLICATION);\r\n    }\r\n\r\n    @Override\r\n    @UnsupportedFileOperation\r\n    public long getBlocksize() throws UnsupportedFileOperationException {\r\n        throw new UnsupportedFileOperationException(FileOperation.GET_BLOCKSIZE);\r\n    }\r\n\r\n    @Override\r\n    @UnsupportedFileOperation\r\n    public void changeReplication(short replication) throws IOException {\r\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_REPLICATION);\r\n    }\r\n\r\n    @Override\r\n    public String getName() {\r\n        // If this file has no parent, return:\r\n        // - the drive's name under OSes with root drives such as Windows, e.g. \"C:\"\r\n        // - \"/\" under Unix-based systems\r\n        if (isRoot()) {\r\n            return hasRootDrives() ? absPath : \"/\";\r\n        }\r\n\r\n        return file.getName();\r\n    }\r\n\r\n    @Override\r\n    public String getAbsolutePath() {\r\n        // Append separator for root folders (C:\\ , /) and for directories\r\n        if (isRoot() || (isDirectory() && !absPath.endsWith(SEPARATOR))) {\r\n            return absPath + SEPARATOR;\r\n        }\r\n\r\n        return absPath;\r\n    }\r\n\r\n\r\n    @Override\r\n    public String getCanonicalPath() {\r\n        // This test is not necessary anymore now that 'No disk' error dialogs are disabled entirely (using Kernel32\r\n        // DLL's SetErrorMode function). Leaving this code commented for a while in case the problem comes back.\r\n         \r\n//        // To avoid drive seeks and potential 'floppy drive not available' dialog under Win32\r\n//        // triggered by java.io.File.getCanonicalPath()\r\n//        if(IS_WINDOWS && guessFloppyDrive())\r\n//            return absPath;\r\n\r\n\r\n        // Note: canonical path must not be cached as its resolution can change over time, for instance\r\n        // if a file 'Test' is renamed to 'test' in the same folder, its canonical path would still be 'Test'\r\n        // if it was resolved prior to the renaming and thus be recognized as a symbolic link\r\n        try {\r\n            String canonicalPath = file.getCanonicalPath();\r\n            // Append separator for directories\r\n            if (isDirectory() && !canonicalPath.endsWith(SEPARATOR)) {\r\n                return canonicalPath + SEPARATOR;\r\n            }\r\n            return canonicalPath;\r\n        } catch(IOException e) {\r\n            return absPath;\r\n        }\r\n    }\r\n\r\n\r\n    @Override\r\n    public String getSeparator() {\r\n        return SEPARATOR;\r\n    }\r\n\r\n\r\n    @Override\r\n    public AbstractFile[] ls(FilenameFilter filenameFilter) throws IOException {\r\n        File[] files = file.listFiles(filenameFilter == null ? null : new LocalFilenameFilter(filenameFilter));\r\n\r\n        if (files == null) {\r\n            throw new IOException();\r\n        }\r\n\r\n        int nbFiles = files.length;\r\n        AbstractFile[] children = new AbstractFile[nbFiles];\r\n\r\n        for(int i = 0; i < nbFiles; i++) {\r\n            // Clone the FileURL of this file and set the child's path, this is more efficient than creating a new\r\n            // FileURL instance from scratch.\r\n            FileURL childURL = (FileURL)fileURL.clone();\r\n\r\n\t\t\tchildURL.setPath(absPath + SEPARATOR + files[i].getName());\r\n\r\n            // Retrieves an AbstractFile (LocalFile or AbstractArchiveFile) instance that's potentially already in\r\n            // the cache, reuse this file as the file's parent, and the already-created java.io.File instance.\r\n            children[i] = FileFactory.getFile(childURL, this, files[i]);\r\n        }\r\n\r\n        return children;\r\n    }\r\n\r\n    @Override\r\n    public boolean isHidden() {\r\n        if (NATIVE_FILE_UTILS_AVAILABLE) {\r\n            return NativeFileUtils.isLocalFileHidden(file.getAbsolutePath());\r\n        }\r\n        return file.isHidden();\r\n    }\r\n\r\n    @Override\r\n    public boolean isExecutable() {\r\n        if (NATIVE_FILE_UTILS_AVAILABLE) {\r\n            return NativeFileUtils.isLocalFileExecutable(file.getAbsolutePath());\r\n        } else if (IS_UNIX_BASED) {\r\n            return !file.isDirectory() && file.canExecute();\r\n        }\r\n        return super.isExecutable();\r\n    }\r\n\r\n    @Override\r\n    public boolean canRead() {\r\n        return file.canRead();\r\n    }\r\n\r\n    /**\r\n     * Overridden to play nice with platforms that have root drives -- for those, the drive's root (e.g. <code>C:\\</code>)\r\n     * is returned instead of <code>/</code>.\r\n     */\r\n    @Override\r\n    public AbstractFile getRoot() {\r\n        if (USES_ROOT_DRIVES) {\r\n            Matcher matcher = DRIVE_ROOT_PATTERN.matcher(absPath + SEPARATOR);\r\n\r\n            // Test if this file already is the root folder\r\n            if (matcher.matches()) {\r\n                return this;\r\n            }\r\n\r\n            // Extract the drive from the path\r\n            matcher.reset();\r\n            if (matcher.find()) {\r\n                return FileFactory.getFile(matcher.group());\r\n            }\r\n        }\r\n\r\n        return super.getRoot();\r\n    }\r\n\r\n    /**\r\n     * Overridden to play nice with platforms that have root drives -- for those, <code>true</code> is returned if\r\n     * this file's path matches the drive root's (e.g. <code>C:\\</code>).\r\n     */\r\n    @Override\r\n    public boolean isRoot() {\r\n        if (USES_ROOT_DRIVES) {\r\n            return DRIVE_ROOT_PATTERN.matcher(absPath + SEPARATOR).matches();\r\n        }\r\n        return super.isRoot();\r\n    }\r\n\r\n    /**\r\n     * Overridden to return the local volume on which this file is located. The returned volume is one of the volumes\r\n     * returned by {@link #getVolumes()}.\r\n     */\r\n    @Override\r\n    public AbstractFile getVolume() {\r\n        AbstractFile[] volumes = LocalFile.getVolumes();\r\n\r\n        // Looks for the volume that best matches this file, i.e. the volume that is the deepest parent of this file.\r\n        // If this file is itself a volume, return it.\r\n        int bestDepth = -1;\r\n        int bestMatch = -1;\r\n\r\n        String thisPath = getAbsolutePath(true);\r\n\r\n        for (int i = 0; i < volumes.length; i++) {\r\n            AbstractFile volume = volumes[i];\r\n            String volumePath = volume.getAbsolutePath(true);\r\n\r\n            if (thisPath.equals(volumePath)) {\r\n                return this;\r\n            } else if (thisPath.startsWith(volumePath)) {\r\n                int depth = PathUtils.getDepth(volumePath, volume.getSeparator());\r\n                if (depth > bestDepth) {\r\n                    bestDepth = depth;\r\n                    bestMatch = i;\r\n                }\r\n            }\r\n        }\r\n\r\n        if (bestMatch != -1) {\r\n            return volumes[bestMatch];\r\n        }\r\n\r\n        // If no volume matched this file (shouldn't normally happen), return the root folder\r\n        return getRoot();\r\n    }\r\n\r\n\r\n    ///////////////////\r\n    // Inner classes //\r\n    ///////////////////\r\n\r\n    /**\r\n     * LocalRandomAccessInputStream extends RandomAccessInputStream to provide random read access to a LocalFile.\r\n     * This implementation uses a NIO <code>FileChannel</code> under the hood to benefit from\r\n     * <code>InterruptibleChannel</code> and allow a thread waiting for an I/O to be gracefully interrupted using\r\n     * <code>Thread#interrupt()</code>.\r\n     */\r\n    public static class LocalRandomAccessInputStream extends RandomAccessInputStream {\r\n\r\n        private final FileChannel channel;\r\n\r\n        /*\r\n          Here we use class Buffer but refer to ByteBuffer object.\r\n          This fixes a problem when running a project compiled for Java 9+ on older versions (Java 8).\r\n          In Java 8, while calling limit() or position() (and some other) methods of ByteBuffer class, since it has no implementation for this method,\r\n          so it is actually calling the method from extended class, Buffer.\r\n          However, in Java 9 and higher, ByteBuffer class has implemented its own methods, and the returning object is changed from Buffer to ByteBuffer\r\n         */\r\n        private final Buffer buffer;\r\n\r\n        LocalRandomAccessInputStream(FileChannel channel) {\r\n            this.channel = channel;\r\n            this.buffer = BufferPool.getByteBuffer();\r\n        }\r\n\r\n        @Override\r\n        public int read() throws IOException {\r\n            synchronized(buffer) {\r\n                buffer.position(0);\r\n                buffer.limit(1);\r\n\r\n                int nbRead = channel.read((ByteBuffer) buffer);\r\n                if (nbRead <= 0) {\r\n                    return nbRead;\r\n                }\r\n\r\n                return 0xFF & ((ByteBuffer)buffer).get(0);\r\n            }\r\n        }\r\n\r\n        @Override\r\n        public int read(byte[] b, int off, int len) throws IOException {\r\n            synchronized(buffer) {\r\n                buffer.position(0);\r\n                buffer.limit(Math.min(buffer.capacity(), len));\r\n\r\n                int nbRead = channel.read((ByteBuffer)buffer);\r\n                if (nbRead <= 0) {\r\n                    return nbRead;\r\n                }\r\n\r\n                buffer.position(0);\r\n                ((ByteBuffer)buffer).get(b, off, nbRead);\r\n\r\n                return nbRead;\r\n            }\r\n        }\r\n\r\n        @Override\r\n        public void close() throws IOException {\r\n            BufferPool.releaseByteBuffer((ByteBuffer)buffer);\r\n            channel.close();\r\n        }\r\n\r\n        public long getOffset() throws IOException {\r\n            return channel.position();\r\n        }\r\n\r\n        public long getLength() throws IOException {\r\n            return channel.size();\r\n        }\r\n\r\n        public void seek(long offset) throws IOException {\r\n            channel.position(offset);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * A replacement for <code>java.io.FileInputStream</code> that uses a NIO {@link FileChannel} under the hood to\r\n     * benefit from <code>InterruptibleChannel</code> and allow a thread waiting for an I/O to be gracefully interrupted\r\n     * using <code>Thread#interrupt()</code>.\r\n     *\r\n     * <p>This class simply delegates all its methods to a\r\n     * {@link com.mucommander.commons.file.impl.local.LocalFile.LocalRandomAccessInputStream} instance. Therefore, this class\r\n     * does not derive from {@link com.mucommander.commons.io.RandomAccessInputStream}, preventing random-access methods from\r\n     * being used.\r\n     *\r\n     */\r\n    static class LocalInputStream extends FilterInputStream {\r\n        LocalInputStream(FileChannel channel) {\r\n            super(new LocalRandomAccessInputStream(channel));\r\n        }\r\n    }\r\n\r\n    /**\r\n     * A replacement for <code>java.io.FileOutputStream</code> that uses a NIO {@link FileChannel} under the hood to\r\n     * benefit from <code>InterruptibleChannel</code> and allow a thread waiting for an I/O to be gracefully interrupted\r\n     * using <code>Thread#interrupt()</code>.\r\n     *\r\n     * <p>This class simply delegates all its methods to a\r\n     * {@link com.mucommander.commons.file.impl.local.LocalFile.LocalRandomAccessOutputStream} instance. Therefore, this class\r\n     * does not derive from {@link com.mucommander.commons.io.RandomAccessOutputStream}, preventing random-access methods from\r\n     * being used.\r\n     *\r\n     */\r\n    static class LocalOutputStream extends FilteredOutputStream {\r\n        LocalOutputStream(FileChannel channel) {\r\n            super(new LocalRandomAccessOutputStream(channel));\r\n        }\r\n    }\r\n\r\n    /**\r\n     * LocalRandomAccessOutputStream extends RandomAccessOutputStream to provide random write access to a LocalFile.\r\n     * This implementation uses a NIO <code>FileChannel</code> under the hood to benefit from\r\n     * <code>InterruptibleChannel</code> and allow a thread waiting for an I/O to be gracefully interrupted using\r\n     * <code>Thread#interrupt()</code>.\r\n     */\r\n    public static class LocalRandomAccessOutputStream extends RandomAccessOutputStream {\r\n\r\n        private final FileChannel channel;\r\n\r\n        /*\r\n          Here we use class Buffer but refer to ByteBuffer object. See comment above.\r\n         */\r\n        private final Buffer buffer;\r\n\r\n        LocalRandomAccessOutputStream(FileChannel channel) {\r\n            this.channel = channel;\r\n            this.buffer = BufferPool.getByteBuffer();\r\n        }\r\n\r\n        @Override\r\n        public void write(int i) throws IOException {\r\n            synchronized(buffer) {\r\n                buffer.position(0);\r\n                buffer.limit(1);\r\n\r\n                ((ByteBuffer)buffer).put((byte)i);\r\n                buffer.position(0);\r\n\r\n                channel.write((ByteBuffer)buffer);\r\n            }\r\n        }\r\n\r\n        @Override\r\n        public void write(byte[] b) throws IOException {\r\n            write(b, 0, b.length);\r\n        }\r\n\r\n        @Override\r\n        public void write(byte[] b, int off, int len) throws IOException {\r\n            synchronized(buffer) {\r\n                do {\r\n                    buffer.position(0);\r\n                    int nbToWrite = Math.min(buffer.capacity(), len);\r\n                    buffer.limit(nbToWrite);\r\n\r\n                    ((ByteBuffer)buffer).put(b, off, nbToWrite);\r\n                    buffer.position(0);\r\n\r\n                    nbToWrite = channel.write((ByteBuffer)buffer);\r\n\r\n                    len -= nbToWrite;\r\n                    off += nbToWrite;\r\n                } while(len > 0);\r\n            }\r\n        }\r\n\r\n        @Override\r\n        public void setLength(long newLength) throws IOException {\r\n            long currentLength = getLength();\r\n\r\n            if (newLength == currentLength) {\r\n                return;\r\n            }\r\n\r\n            long currentPos = channel.position();\r\n\r\n            if (newLength<currentLength) {\r\n                // Truncate the file and position the offset to the new EOF if it was beyond\r\n                channel.truncate(newLength);\r\n                if (currentPos > newLength) {\r\n                    channel.position(newLength);\r\n                }\r\n            } else {\r\n                // Expand the file by positioning the offset at the new EOF and writing a byte, and reposition the\r\n                // offset to where it was\r\n                channel.position(newLength-1);      // Note: newLength cannot be 0\r\n                write(0);\r\n                channel.position(currentPos);\r\n            }\r\n\r\n        }\r\n\r\n        @Override\r\n        public void close() throws IOException {\r\n            BufferPool.releaseByteBuffer((ByteBuffer)buffer);\r\n            channel.close();\r\n        }\r\n\r\n        public long getOffset() throws IOException {\r\n            return channel.position();\r\n        }\r\n\r\n        public long getLength() throws IOException {\r\n            return channel.size();\r\n        }\r\n\r\n        public void seek(long offset) throws IOException {\r\n            channel.position(offset);\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * A Permissions implementation for LocalFile.\r\n     */\r\n    private static class LocalFilePermissions extends IndividualPermissionBits implements FilePermissions {\r\n        \r\n        private final File file;\r\n\r\n        // Permissions are limited to the user access type. Executable permission flag is only available under Java 1.6\r\n        // and up.\r\n        // Note: 'read' and 'execute' permissions have no meaning under Windows (files are either read-only or\r\n        // read-write), but we return default values.\r\n\r\n        /** Mask for supported permissions under Java 1.6 */\r\n        private static final PermissionBits MASK = new GroupedPermissionBits(448);   // rwx------ (700 octal)\r\n\r\n        private LocalFilePermissions(java.io.File file) {\r\n            this.file = file;\r\n        }\r\n\r\n        public boolean getBitValue(int access, int type) {\r\n            // Only the 'user' permissions are supported\r\n            if (access != USER_ACCESS) {\r\n                return false;\r\n            }\r\n            switch (type) {\r\n                case READ_PERMISSION:\r\n                    return file.canRead();\r\n                case WRITE_PERMISSION:\r\n                    return file.canWrite();\r\n                case EXECUTE_PERMISSION:\r\n                    return file.canExecute();\r\n            }\r\n\r\n//            if (type==READ_PERMISSION) {\r\n//                return file.canRead();\r\n//            }else if(type==WRITE_PERMISSION)\r\n//                return file.canWrite();\r\n//            // Execute permission can only be retrieved under Java 1.6 and up\r\n//            else if(type==EXECUTE_PERMISSION && JavaVersion.JAVA_1_6.isCurrentOrHigher())\r\n//                return file.canExecute();\r\n\r\n            return false;\r\n        }\r\n\r\n        /**\r\n         * Overridden for performance reasons.\r\n         */\r\n        @Override\r\n        public int getIntValue() {\r\n            int userPerms = 0;\r\n\r\n            if (getBitValue(USER_ACCESS, READ_PERMISSION)) {\r\n                userPerms |= READ_PERMISSION;\r\n            }\r\n            if (getBitValue(USER_ACCESS, WRITE_PERMISSION)) {\r\n                userPerms |= WRITE_PERMISSION;\r\n            }\r\n            if (getBitValue(USER_ACCESS, EXECUTE_PERMISSION)) {\r\n                userPerms |= EXECUTE_PERMISSION;\r\n            }\r\n            return userPerms<<6;\r\n        }\r\n\r\n        public PermissionBits getMask() {\r\n            return MASK;\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Turns a {@link FilenameFilter} into a {@link java.io.FilenameFilter}.\r\n     */\r\n    private static class LocalFilenameFilter implements java.io.FilenameFilter {\r\n\r\n        private final FilenameFilter filter;\r\n\r\n        private LocalFilenameFilter(FilenameFilter filter) {\r\n            this.filter = filter;\r\n        }\r\n\r\n        @Override\r\n        public boolean accept(File dir, String name) {\r\n            return filter.accept(name);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/local/LocalProtocolProvider.java",
    "content": "package com.mucommander.commons.file.impl.local;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.ProtocolProvider;\nimport com.mucommander.commons.runtime.OsFamily;\n\nimport java.io.IOException;\n\n/**\n * This class is the provider for the local filesystem implemented by {@link com.mucommander.commons.file.impl.local.LocalFile}\n * and network path given in UNC format which is implemented by {@link com.mucommander.commons.file.impl.local.UNCFile}\n *\n * @author Maxence Bernard, Arik Hadas\n * @see com.mucommander.commons.file.impl.local.LocalFile\n * @see com.mucommander.commons.file.impl.local.UNCFile\n */\npublic class LocalProtocolProvider implements ProtocolProvider {\n\n\t/** Are we running Windows ? */\n    private final static boolean IS_WINDOWS =  OsFamily.WINDOWS.isCurrent();\n\t\n    public AbstractFile getFile(FileURL url, Object... instantiationParams) throws IOException {\n        return isUncFile(url)?\n        \t (instantiationParams.length==0?new UNCFile(url):new UNCFile(url ,(java.io.File)instantiationParams[0]))\n        \t:(instantiationParams.length==0?new LocalFile(url):new LocalFile(url, (java.io.File)instantiationParams[0]));\n    }\n\t\n\t/**\n     * Returns <code>true</code> if the specified {@link FileURL} denotes a Windows UNC file.\n     *\n     * @param fileURL the {@link FileURL} to test\n     * @return <code>true</code> if the specified {@link FileURL} denotes a Windows UNC file.\n     */\n    private static boolean isUncFile(FileURL fileURL) {\n        return IS_WINDOWS && !FileURL.LOCALHOST.equals(fileURL.getHost());\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/local/SpecialWindowsLocation.java",
    "content": "package com.mucommander.commons.file.impl.local;\n\nimport com.mucommander.commons.file.DummyFile;\nimport com.mucommander.commons.file.FileURL;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.net.MalformedURLException;\n\n/**\n * Represents special Windows locations such as 'My Computer', 'Network Neighborhood', 'Recycle Bin', ... as dummy\n * <code>AbstractFile</code> instances.\n *\n * <p>This class is totally useless on platforms other than Windows.\n *\n * @author Maxence Bernard\n */\npublic class SpecialWindowsLocation extends DummyFile {\n    private static final Logger LOGGER = LoggerFactory.getLogger(SpecialWindowsLocation.class);\n\n    /** Control Panel */\n    public final static SpecialWindowsLocation CONTROL_PANEL = createLocation(\"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\\::{21EC2020-3AEA-1069-A2DD-08002B30309D}\");\n\n    /** My Computer */\n    public final static SpecialWindowsLocation MY_COMPUTER = createLocation(\"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\");\n\n    /** My Documents */\n    public final static SpecialWindowsLocation MY_DOCUMENTS = createLocation(\"::{450D8FBA-AD25-11D0-98A8-0800361B1103}\");\n\n    /** Network Neighborhood */\n    public final static SpecialWindowsLocation NETWORK_NEIGHBORHOOD = createLocation(\"::{208D2C60-3AEA-1069-A2D7-08002B30309D}\");\n\n    /** Recycle Bin */\n    public final static SpecialWindowsLocation RECYCLE_BIN = createLocation(\"::{645FF040-5081-101B-9F08-00AA002F954E}\");\n\n    /**\n     * Creates a SpecialWindowsLocation using the specified class ID and returns it.\n     *\n     * @param clsid the class ID\n     * @return the new SpecialWindowsLocation\n     */\n    private static SpecialWindowsLocation createLocation(String clsid) {\n        try {\n            return new SpecialWindowsLocation(clsid);\n        }\n        catch(MalformedURLException e) {\n            LOGGER.warn(\"Unable to creation location {}\", clsid, e);\n        }\n\n        return null;\n    }\n\n    /** A class ID */\n    protected String clsid;\n\n    /**\n     * Creates a new special Windows location using the given CLSID (Class identifier).\n     *\n     * @param clsid a Windows class identifier\n     * @throws java.net.MalformedURLException should not happen\n     */\n    public SpecialWindowsLocation(String clsid) throws MalformedURLException {\n        super(FileURL.getFileURL(\"file:///\"));    // dummy URL, '/' corresponds to nothing under Windows\n\n        this.clsid = clsid;\n    }\n\n    /**\n     * Implementation notes: returns the CLSID (Class identifier) passed to the constructor.\n     */\n    @Override\n    public String getName() {\n        return clsid;\n    }\n\n    /**\n     * Implementation notes: returns the CLSID (Class identifier) passed to the constructor.\n     */\n    @Override\n    public String getAbsolutePath() {\n        return clsid;\n    }\n\n    /**\n     * Implementation notes: always returns <code>true</code>.\n     */\n    @Override\n    public boolean isDirectory() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/local/UNCFile.java",
    "content": "package com.mucommander.commons.file.impl.local;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.io.RandomAccessFile;\nimport java.nio.channels.FileChannel;\nimport java.util.StringTokenizer;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.FileOperation;\nimport com.mucommander.commons.file.FilePermissions;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.GroupedPermissionBits;\nimport com.mucommander.commons.file.IndividualPermissionBits;\nimport com.mucommander.commons.file.PermissionBits;\nimport com.mucommander.commons.file.ProtocolFile;\nimport com.mucommander.commons.file.UnsupportedFileOperation;\nimport com.mucommander.commons.file.UnsupportedFileOperationException;\nimport com.mucommander.commons.file.filter.FilenameFilter;\nimport com.mucommander.commons.file.impl.local.LocalFile.LocalInputStream;\nimport com.mucommander.commons.file.impl.local.LocalFile.LocalOutputStream;\nimport com.mucommander.commons.file.impl.local.LocalFile.LocalRandomAccessInputStream;\nimport com.mucommander.commons.file.impl.local.LocalFile.LocalRandomAccessOutputStream;\nimport com.mucommander.commons.file.util.Kernel32;\nimport com.mucommander.commons.file.util.Kernel32API;\nimport com.mucommander.commons.file.util.PathUtils;\nimport com.mucommander.commons.io.RandomAccessInputStream;\nimport com.mucommander.commons.io.RandomAccessOutputStream;\nimport com.mucommander.commons.runtime.OsVersion;\nimport com.sun.jna.ptr.LongByReference;\n\n/**\n * TODO: update this documentation and LocalFile documentation\n *\n * @author Arik Hadas\n */\npublic class UNCFile extends ProtocolFile {\n    private static final Logger LOGGER = LoggerFactory.getLogger(UNCFile.class);\n\n    protected File file;\n    private final FilePermissions permissions;\n\n    /**\n     * Absolute file path, free of trailing separator\n     */\n    protected String absPath;\n\n    /**\n     * Caches the parent folder, initially null until getParent() gets called\n     */\n    protected AbstractFile parent;\n    /**\n     * Indicates whether the parent folder instance has been retrieved and cached or not (parent can be null)\n     */\n    protected boolean parentValueSet;\n\n    /**\n     * Underlying Windows's path separator\n     */\n    public final static String SEPARATOR = \"\\\\\";\n\n    // Permissions can only be changed under Java 1.6 and up and are limited to 'user' access.\n    // Note: 'read' and 'execute' permissions have no meaning under Windows (files are either read-only or\n    // read-write) and as such can't be changed.\n\n    /**\n     * Changeable permissions mask for Java 1.6 and up, on Windows OS (any version)\n     */\n    private static final PermissionBits CHANGEABLE_PERMISSIONS = new GroupedPermissionBits(128);   // -w------- (200 octal)\n\n\n    /**\n     * Creates a new instance of UNCFile and a corresponding {@link File} instance.\n     */\n    protected UNCFile(FileURL fileURL) throws IOException {\n        this(fileURL, null);\n    }\n\n    /**\n     * Creates a new instance of UNCFile, using the given {@link File} if not <code>null</code>, creating a new\n     * {@link File} instance otherwise.\n     *\n     * @throws IOException if an I/O error occurs.\n     */\n    protected UNCFile(FileURL fileURL, File file) throws IOException {\n        super(fileURL);\n\n        if (file == null) {\n            absPath = SEPARATOR + SEPARATOR + fileURL.getHost() + fileURL.getPath().replace('/', '\\\\');    // Replace leading / char by \\\n\n            // create the java.io.File instance and throw an exception if the path is not absolute.\n            file = new File(absPath);\n            if (!file.isAbsolute()) {\n                throw new IOException();\n            }\n        }\n        // the java.io.File instance was created by ls(), no need to re-create it or call the costly File#getAbsolutePath()\n        else {\n            absPath = SEPARATOR + SEPARATOR + fileURL.getHost() + fileURL.getPath().replace('/', '\\\\');\n        }\n\n        // Remove the trailing separator if present\n        if (absPath.endsWith(SEPARATOR)) {\n            absPath = absPath.substring(0, absPath.length() - 1);\n        }\n\n        this.file = file;\n        this.permissions = new UNCFilePermissions(file);\n    }\n\n    /**\n     * Returns a <code>java.io.File</code> instance corresponding to this file.\n     */\n    @Override\n    public Object getUnderlyingFileObject() {\n        return file;\n    }\n\n    @Override\n    public boolean isSymlink() {\n        // At the moment symlinks under Windows (aka NTFS junction points) are not supported because java.io.File\n        // knows nothing about them and there is no way to discriminate them. So there is no need to waste time\n        // comparing canonical paths, just return false.\n        // Todo: add support for .lnk files (~hard links)\n        return false;\n    }\n\n    @Override\n    public boolean isSystem() {\n        return false;\n    }\n\n    @Override\n    public long getLastModifiedDate() {\n        return file.lastModified();\n    }\n\n    @Override\n    public void setLastModifiedDate(long lastModified) throws IOException {\n        // java.io.File#setLastModified(long) throws an IllegalArgumentException if time is negative.\n        // If specified time is negative, set it to 0 (01/01/1970).\n        if (lastModified < 0)\n            lastModified = 0;\n\n        if (!file.setLastModified(lastModified))\n            throw new IOException();\n    }\n\n    @Override\n    public long getSize() {\n        return file.length();\n    }\n\n    @Override\n    public AbstractFile getParent() {\n        // Retrieve the parent AbstractFile instance and cache it\n        if (!parentValueSet) {\n            if (!isRoot()) {\n                FileURL parentURL = getURL().getParent();\n\n                if (parentURL != null) {\n                    parent = FileFactory.getFile(parentURL);\n                }\n            }\n            parentValueSet = true;\n        }\n        return parent;\n    }\n\n    @Override\n    public void setParent(AbstractFile parent) {\n        this.parent = parent;\n        this.parentValueSet = true;\n    }\n\n    @Override\n    public boolean exists() {\n        return file.exists();\n    }\n\n    @Override\n    public FilePermissions getPermissions() {\n        return permissions;\n    }\n\n    @Override\n    public PermissionBits getChangeablePermissions() {\n        return CHANGEABLE_PERMISSIONS;\n    }\n\n    @Override\n    public void changePermission(int access, int permission, boolean enabled) throws IOException {\n        // Only the 'user' permissions under Java 1.6 are supported\n        if (access != USER_ACCESS)\n            throw new IOException();\n\n        boolean success = false;\n        if (permission == READ_PERMISSION)\n            success = file.setReadable(enabled);\n        else if (permission == WRITE_PERMISSION)\n            success = file.setWritable(enabled);\n        else if (permission == EXECUTE_PERMISSION)\n            success = file.setExecutable(enabled);\n\n        if (!success)\n            throw new IOException();\n    }\n\n    /**\n     * Always returns <code>null</code>, this information is not available unfortunately.\n     */\n    @Override\n    public String getOwner() {\n        return null;\n    }\n\n    /**\n     * Always returns <code>false</code>, this information is not available unfortunately.\n     */\n    @Override\n    public boolean canGetOwner() {\n        return false;\n    }\n\n    /**\n     * Always returns <code>null</code>, this information is not available unfortunately.\n     */\n    @Override\n    public String getGroup() {\n        return null;\n    }\n\n    /**\n     * Always returns <code>false</code>, this information is not available unfortunately.\n     */\n    @Override\n    public boolean canGetGroup() {\n        return false;\n    }\n\n    @Override\n    public boolean isDirectory() {\n        // This test is not necessary anymore now that 'No disk' error dialogs are disabled entirely (using Kernel32\n        // DLL's SetErrorMode function). Leaving this code commented for a while in case the problem comes back.\n\n//        // To avoid drive seeks and potential 'floppy drive not available' dialog under Win32\n//        // triggered by java.io.File.isDirectory()\n//        if(IS_WINDOWS && guessFloppyDrive())\n//            return true;\n\n        return file.isDirectory();\n    }\n\n    /**\n     * Implementation notes: the returned <code>InputStream</code> uses a NIO {@link FileChannel} under the hood to\n     * benefit from <code>InterruptibleChannel</code> and allow a thread waiting for an I/O to be gracefully interrupted\n     * using <code>Thread#interrupt()</code>.\n     *\n     * @throws IOException if an I/O error occurs.\n     */\n    @Override\n    public InputStream getInputStream() throws IOException {\n        return new LocalInputStream(new FileInputStream(file).getChannel());\n    }\n\n    /**\n     * Implementation notes: the returned <code>InputStream</code> uses a NIO {@link FileChannel} under the hood to\n     * benefit from <code>InterruptibleChannel</code> and allow a thread waiting for an I/O to be gracefully interrupted\n     * using <code>Thread#interrupt()</code>.\n     *\n     * @throws IOException if an I/O error occurs.\n     */\n    @Override\n    public OutputStream getOutputStream() throws IOException {\n        return new LocalOutputStream(new FileOutputStream(absPath, false).getChannel());\n    }\n\n    /**\n     * Implementation notes: the returned <code>InputStream</code> uses a NIO {@link FileChannel} under the hood to\n     * benefit from <code>InterruptibleChannel</code> and allow a thread waiting for an I/O to be gracefully interrupted\n     * using <code>Thread#interrupt()</code>.\n     *\n     * @throws IOException if an I/O error occurs.\n     */\n    @Override\n    public OutputStream getAppendOutputStream() throws IOException {\n        return new LocalOutputStream(new FileOutputStream(absPath, true).getChannel());\n    }\n\n    /**\n     * Implementation notes: the returned <code>InputStream</code> uses a NIO {@link FileChannel} under the hood to\n     * benefit from <code>InterruptibleChannel</code> and allow a thread waiting for an I/O to be gracefully interrupted\n     * using <code>Thread#interrupt()</code>.\n     *\n     * @throws IOException if an I/O error occurs.\n     */\n    @Override\n    public RandomAccessInputStream getRandomAccessInputStream() throws IOException {\n        return new LocalRandomAccessInputStream(new RandomAccessFile(file, \"r\").getChannel());\n    }\n\n    /**\n     * Implementation notes: the returned <code>InputStream</code> uses a NIO {@link FileChannel} under the hood to\n     * benefit from <code>InterruptibleChannel</code> and allow a thread waiting for an I/O to be gracefully interrupted\n     * using <code>Thread#interrupt()</code>.\n     *\n     * @throws IOException if an I/O error occurs.\n     */\n    @Override\n    public RandomAccessOutputStream getRandomAccessOutputStream() throws IOException {\n        return new LocalRandomAccessOutputStream(new RandomAccessFile(file, \"rw\").getChannel());\n    }\n\n    @Override\n    public void delete() throws IOException {\n        if (!file.delete()) {\n            throw new IOException();\n        }\n    }\n\n\n    @Override\n    public AbstractFile[] ls() throws IOException {\n        return ls(null);\n    }\n\n    @Override\n    public void mkdir() throws IOException {\n        if (!file.mkdir())\n            throw new IOException();\n    }\n\n    @Override\n    public void renameTo(AbstractFile destFile) throws IOException {\n        // Throw an exception if the file cannot be renamed to the specified destination.\n        // Fail in some situations where java.io.File#renameTo() doesn't.\n        // Note that java.io.File#renameTo()'s implementation is system-dependant, so it's always a good idea to\n        // perform all those checks even if some are not necessary on this or that platform.\n        checkRenamePrerequisites(destFile, true, false);\n\n        // The behavior of java.io.File#renameTo() when the destination file already exists is not consistent\n        // across platforms:\n        // - Under UNIX, it succeeds and return true\n        // - Under Windows, it fails and return false\n        // This ticket goes in great details about the issue: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4017593\n        //\n        // => Since this method is required to succeed when the destination file exists, the Windows platform needs\n        // special treatment.\n\n        destFile = destFile.getTopAncestor();\n        File destJavaIoFile = ((UNCFile) destFile).file;\n\n        // This check is necessary under Windows because java.io.File#renameTo(java.io.File) does not return false\n        // if the destination file is located on a different drive, contrary for example to Mac OS X where renameTo\n        // returns false in this case.\n        // Not doing this under Windows would mean files would get moved between drives with renameTo, which doesn't\n        // allow the transfer to be monitored.\n        // Note that Windows UNC paths are handled by checkRenamePrerequisites() when comparing hosts for equality.\n        if (!getRoot().equals(destFile.getRoot()))\n            throw new IOException();\n\n        // Windows 9x or Windows Me: Kernel32's MoveFileEx function is NOT available\n        if (OsVersion.WINDOWS_ME.isCurrentOrLower()) {\n            // The destination file is deleted before calling java.io.File#renameTo().\n            // Note that in this case, the atomicity of this method is not guaranteed anymore -- if\n            // java.io.File#renameTo() fails (for whatever reason), the destination file is deleted anyway.\n            if (destFile.exists())\n                if (!destJavaIoFile.delete())\n                    throw new IOException();\n        }\n        // Windows NT: Kernel32's MoveFileEx can be used, if the Kernel32 DLL is available.\n        else if (Kernel32.isAvailable()) {\n            // Note: MoveFileEx is always used, even if the destination file does not exist, to avoid having to\n            // call #exists() on the destination file which has a cost.\n            if (!Kernel32.getInstance().MoveFileEx(absPath, destFile.getAbsolutePath(),\n                    Kernel32API.MOVEFILE_REPLACE_EXISTING | Kernel32API.MOVEFILE_WRITE_THROUGH)) {\n                String errorMessage = Integer.toString(Kernel32.getInstance().GetLastError());\n                // TODO: use Kernel32.FormatMessage\n                throw new IOException(\"Rename using Kernel32 API failed: \" + errorMessage);\n            } else {\n                // move successful\n                return;\n            }\n        }\n        // else fall back to java.io.File#renameTo\n        if (!file.renameTo(destJavaIoFile)) {\n            throw new IOException();\n        }\n    }\n\n    @Override\n    public long getFreeSpace() throws IOException {\n        return file.getUsableSpace();\n    }\n\n    @Override\n    public long getTotalSpace() throws IOException {\n        return file.getTotalSpace();\n    }\n\n    // Unsupported file operations\n\n    /**\n     * Always throws {@link UnsupportedFileOperationException} when called.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void copyRemotelyTo(AbstractFile destFile) throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.COPY_REMOTELY);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public short getReplication() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_REPLICATION);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public long getBlocksize() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_BLOCKSIZE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public void changeReplication(short replication) throws IOException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_REPLICATION);\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n\n    /// /////////////////////\n\n    @Override\n    public String getName() {\n        // If this file has no parent, return:\n        // - the drive's name under OSes with root drives such as Windows, e.g. \"C:\"\n        // - \"/\" under Unix-based systems\n        if (isRoot())\n            return absPath;\n\n        return file.getName();\n    }\n\n    @Override\n    public String getAbsolutePath() {\n        // Append separator for directories\n        if (isDirectory() && !absPath.endsWith(SEPARATOR))\n            return absPath + SEPARATOR;\n\n        return absPath;\n    }\n\n\n    @Override\n    public String getCanonicalPath() {\n        // This test is not necessary anymore now that 'No disk' error dialogs are disabled entirely (using Kernel32\n        // DLL's SetErrorMode function). Leaving this code commented for a while in case the problem comes back.\n\n//        // To avoid drive seeks and potential 'floppy drive not available' dialog under Win32\n//        // triggered by java.io.File.getCanonicalPath()\n//        if(IS_WINDOWS && guessFloppyDrive())\n//            return absPath;\n\n        // Note: canonical path must not be cached as its resolution can change over time, for instance\n        // if a file 'Test' is renamed to 'test' in the same folder, its canonical path would still be 'Test'\n        // if it was resolved prior to the renaming and thus be recognized as a symbolic link\n        try {\n            String canonicalPath = file.getCanonicalPath();\n            // Append separator for directories\n            if (isDirectory() && !canonicalPath.endsWith(SEPARATOR))\n                canonicalPath = canonicalPath + SEPARATOR;\n\n            return canonicalPath;\n        } catch (IOException e) {\n            return absPath;\n        }\n    }\n\n    @Override\n    public String getSeparator() {\n        return SEPARATOR;\n    }\n\n    @Override\n    public AbstractFile[] ls(FilenameFilter filenameFilter) throws IOException {\n        File[] files = file.listFiles(filenameFilter == null ? null : new UNCFilenameFilter(filenameFilter));\n        if (files == null) {\n            throw new IOException();\n        }\n        int nbFiles = files.length;\n        AbstractFile[] children = new AbstractFile[nbFiles];\n        for (int i = 0; i < nbFiles; i++) {\n            File file = files[i];\n\n            // Clone the FileURL of this file and set the child's path, this is more efficient than creating a new\n            // FileURL instance from scratch.\n            FileURL childURL = (FileURL) fileURL.clone();\n\n            childURL.setPath(addTrailingSeparator(fileURL.getPath()) + file.getName());\n\n            // Retrieves an AbstractFile (LocalFile or AbstractArchiveFile) instance that's potentially already in\n            // the cache, reuse this file as the file's parent, and the already-created java.io.File instance.\n            children[i] = FileFactory.getFile(childURL, this, file);\n        }\n\n        return children;\n    }\n\n    @Override\n    public boolean isHidden() {\n        return file.isHidden();\n    }\n\n    /**\n     * TODO\n     */\n    @Override\n    public AbstractFile getRoot() {\n        String[] splittedBySeparator = absPath.split(\"\\\\\\\\\");\n        return FileFactory.getFile(SEPARATOR + SEPARATOR + splittedBySeparator[2] + SEPARATOR + splittedBySeparator[3]);\n    }\n\n    /**\n     * TODO\n     */\n    @Override\n    public boolean isRoot() {\n        return countIndexOf(absPath, \"\\\\\\\\\") <= 3;\n    }\n\n    private int countIndexOf(String text, String search) {\n        return text.split(search).length - 1;\n    }\n\n    /**\n     * Overridden to return the local volume on which this file is located. The returned volume is one of the volumes\n     * returned by {@link LocalFile#getVolumes()}.\n     */\n    @Override\n    public AbstractFile getVolume() {\n        AbstractFile[] volumes = LocalFile.getVolumes();\n\n        // Looks for the volume that best matches this file, i.e. the volume that is the deepest parent of this file.\n        // If this file is itself a volume, return it.\n        int bestDepth = -1;\n        int bestMatch = -1;\n        int depth;\n\n        String thisPath = getAbsolutePath(true);\n\n        for (int i = 0; i < volumes.length; i++) {\n            AbstractFile volume = volumes[i];\n            String volumePath = volume.getAbsolutePath(true);\n\n            if (thisPath.equals(volumePath)) {\n                return this;\n            } else if (thisPath.startsWith(volumePath)) {\n                depth = PathUtils.getDepth(volumePath, volume.getSeparator());\n                if (depth > bestDepth) {\n                    bestDepth = depth;\n                    bestMatch = i;\n                }\n            }\n        }\n\n        if (bestMatch >= 0) {\n            return volumes[bestMatch];\n        }\n\n        // If no volume matched this file (shouldn't normally happen), return the root folder\n        return getRoot();\n    }\n\n\n    /**\n     * Returns the total and free space on the volume where this file resides.\n     *\n     * <p>Using this method to retrieve both free space and volume space is more efficient than calling\n     * {@link #getFreeSpace()} and {@link #getTotalSpace()} separately -- the underlying method retrieving both\n     * attributes at the same time.\n     *\n     * @return a {totalSpace, freeSpace} long array, both values can be null if the information could not be retrieved\n     * @throws IOException if an I/O error occurred\n     */\n    public long[] getVolumeInfo() throws IOException {\n        return new long[] { getTotalSpace(), getFreeSpace() };\n    }\n\n    /**\n     * Uses platform dependent functions to retrieve the total and free space on the volume where this file resides.\n     *\n     * @return a {totalSpace, freeSpace} long array, both values can be <code>null</code> if the information could not\n     * be retrieved.\n     * @throws IOException if an I/O error occurred\n     */\n    protected long[] getNativeVolumeInfo() throws IOException {\n        BufferedReader br = null;\n        String absPath = getAbsolutePath();\n        long[] dfInfo = new long[]{-1, -1};\n\n        try {\n            // Use the Kernel32 DLL if it is available\n            if (Kernel32.isAvailable()) {\n                // Retrieves the total and free space information using the GetDiskFreeSpaceEx function of the\n                // Kernel32 API.\n                LongByReference totalSpaceLBR = new LongByReference();\n                LongByReference freeSpaceLBR = new LongByReference();\n\n                if (Kernel32.getInstance().GetDiskFreeSpaceEx(absPath, null, totalSpaceLBR, freeSpaceLBR)) {\n                    dfInfo[0] = totalSpaceLBR.getValue();\n                    dfInfo[1] = freeSpaceLBR.getValue();\n                } else {\n                    LOGGER.warn(\"Call to GetDiskFreeSpaceEx failed, absPath={}\", absPath);\n                }\n            }\n            // Otherwise, parse the output of 'dir \"filePath\"' command to retrieve free space information, if\n            // running Window NT or higher.\n            // Note: no command invocation under Windows 95/98/Me, because it causes a shell window to\n            // appear briefly every time this method is called (See ticket #63).\n            else if (OsVersion.WINDOWS_NT.isCurrentOrHigher()) {\n                // 'dir' command returns free space on the last line\n                Process process = Runtime.getRuntime().exec(\n                        (OsVersion.getCurrent().compareTo(OsVersion.WINDOWS_NT) >= 0 ? \"cmd /c\" : \"command.com /c\")\n                                + \" dir \\\"\" + absPath + \"\\\"\");\n\n                // Check that the process was correctly started\n                if (process != null) {\n                    br = new BufferedReader(new InputStreamReader(process.getInputStream()));\n                    String line;\n                    String lastLine = null;\n                    // Retrieves last line of dir\n                    while ((line = br.readLine()) != null) {\n                        if (!line.trim().isEmpty())\n                            lastLine = line;\n                    }\n\n                    // Last dir line may look like something this (might vary depending on system's language, below in French):\n                    // 6 Rep(s)  14 767 521 792 octets libres\n                    if (lastLine != null) {\n                        StringTokenizer st = new StringTokenizer(lastLine, \" \\t\\n\\r\\f,.\");\n                        // Discard first token\n                        st.nextToken();\n\n                        // Concatenates as many contiguous groups of numbers\n                        String token;\n                        String freeSpace = \"\";\n                        while (st.hasMoreTokens()) {\n                            token = st.nextToken();\n                            char c = token.charAt(0);\n                            if (c >= '0' && c <= '9') {\n                                freeSpace += token;\n                            } else if (!freeSpace.isEmpty()) {\n                                break;\n                            }\n                        }\n\n                        dfInfo[1] = Long.parseLong(freeSpace);\n                    }\n                }\n            }\n        } finally {\n            if (br != null) {\n                try {\n                    br.close();\n                } catch (IOException ignored) {\n                }\n            }\n        }\n\n        return dfInfo;\n    }\n\n    /**\n     * A Permissions implementation for LocalFile.\n     */\n    private static class UNCFilePermissions extends IndividualPermissionBits implements FilePermissions {\n\n        private final java.io.File file;\n\n        // Permissions are limited to the user access type. Executable permission flag is only available under Java 1.6\n        // and up.\n        // Note: 'read' and 'execute' permissions have no meaning under Windows (files are either read-only or\n        // read-write), but we return default values.\n\n        /**\n         * Mask for supported permissions under Java 1.6\n         */\n        private final static PermissionBits MASK = new GroupedPermissionBits(448);   // rwx------ (700 octal)\n\n        private UNCFilePermissions(java.io.File file) {\n            this.file = file;\n        }\n\n        public boolean getBitValue(int access, int type) {\n            // Only the 'user' permissions are supported\n            if (access != USER_ACCESS) {\n                return false;\n            }\n            if (type == READ_PERMISSION) {\n                return file.canRead();\n            } else if (type == WRITE_PERMISSION) {\n                return file.canWrite();\n            } else if (type == EXECUTE_PERMISSION) {\n                return file.canExecute();\n            }\n\n            return false;\n        }\n\n        /**\n         * Overridden for performance reasons.\n         */\n        @Override\n        public int getIntValue() {\n            int userPerms = 0;\n\n            if (getBitValue(USER_ACCESS, READ_PERMISSION)) {\n                userPerms |= READ_PERMISSION;\n            }\n            if (getBitValue(USER_ACCESS, WRITE_PERMISSION)) {\n                userPerms |= WRITE_PERMISSION;\n            }\n            if (getBitValue(USER_ACCESS, EXECUTE_PERMISSION)) {\n                userPerms |= EXECUTE_PERMISSION;\n            }\n            return userPerms << 6;\n        }\n\n        public PermissionBits getMask() {\n            return MASK;\n        }\n    }\n\n    /**\n     * Turns a {@link FilenameFilter} into a {@link java.io.FilenameFilter}.\n     */\n    private static class UNCFilenameFilter implements java.io.FilenameFilter {\n\n        private final FilenameFilter filter;\n\n        private UNCFilenameFilter(FilenameFilter filter) {\n            this.filter = filter;\n        }\n\n        public boolean accept(File dir, String name) {\n            return filter.accept(name);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/local/package.html",
    "content": "<body>\n  Provides an implementation of the local file system.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/lst/LstArchiveEntry.java",
    "content": "package com.mucommander.commons.file.impl.lst;\n\nimport com.mucommander.commons.file.ArchiveEntry;\n\n/**\n * An LST archive entry. In addition to the common attributes found in {@link ArchiveEntry}, it contains a base\n * folder which, when concatenated with this entry's path, gives the absolute path to the file referenced by the\n * LST entry. \n *\n * @author Maxence Bernard\n */\npublic class LstArchiveEntry extends ArchiveEntry {\n\n    /** The base folder that when concatenated to this entry's path gives the absolute path to the file referenced\n     * by this entry */\n    protected String baseFolder;\n\n    LstArchiveEntry(String path, boolean directory, long date, long size, String baseFolder) {\n        super(path, directory, date, size, true);\n\n        this.baseFolder = baseFolder;\n    }\n\n    /**\n     * Returns the base folder which, when concatenated with this entry's path, gives the absolute path to the file\n     * referenced by the LST entry. The returned path should always end with a trailing separator character.\n     *\n     * @return the base folder of this entry\n     */\n    protected String getBaseFolder() {\n        return baseFolder;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/lst/LstArchiveEntryIterator.java",
    "content": "package com.mucommander.commons.file.impl.lst;\n\nimport com.mucommander.commons.file.ArchiveEntry;\nimport com.mucommander.commons.file.ArchiveEntryIterator;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.text.SimpleDateFormat;\nimport java.util.StringTokenizer;\n\n/**\n * An <code>ArchiveEntryIterator</code> that iterates through an LST archive.\n *\n * @author Maxence Bernard\n */\nclass LstArchiveEntryIterator implements ArchiveEntryIterator {\n    private static final Logger LOGGER = LoggerFactory.getLogger(LstArchiveEntryIterator.class);\n\n    /** Allows to read the LST archive line by line */\n    private final BufferedReader br;\n\n    /** Parses LST-formatted dates */\n    private final SimpleDateFormat lstDateFormat = new SimpleDateFormat(\"yyyy.MM.dd HH:mm.ss\");\n\n    /** The next entry to be returned by #nextEntry(), null if there is no more entry */\n    private ArchiveEntry nextEntry;\n\n    /** Base folder of all entries */\n    private final String baseFolder;\n\n    /** Current directory, used for parsing the LST file */\n    private String currentDir = \"\";\n\n\n    /**\n     * Creates a new <code>LstArchiveEntryIterator</code> that parses the given LST <code>InputStream</code>.\n     * The <code>InputStream</code> will be closed by {@link #close()}.\n     *\n     * @param in an LST archive <code>InputStream</code>\n     * @throws IOException if an I/O error occurred while initializing this iterator\n     */\n    LstArchiveEntryIterator(InputStream in) throws IOException {\n        br = new BufferedReader(new InputStreamReader(in));\n\n        // Read the base folder\n        baseFolder = br.readLine();\n        if(baseFolder==null)\n            throw new IOException();\n    }\n\n    /**\n     * Reads the next entry and returns an {@link ArchiveEntry} representing it.\n     *\n     * @return an ArchiveEntry representing the entry\n     * @throws IOException if an error occurred\n     */\n    ArchiveEntry getNextEntry() throws IOException {\n        String line = br.readLine();\n        if(line==null)\n            return null;\n\n        try {\n            StringTokenizer st = new StringTokenizer(line, \"\\t\");\n\n            String name = st.nextToken().replace('\\\\', '/');\n            long size = Long.parseLong(st.nextToken());\n            long date = lstDateFormat.parse((st.nextToken()+\" \"+st.nextToken())).getTime();\n\n            String path;\n            boolean isDirectory;\n\n            if(name.endsWith(\"/\")) {\n                isDirectory = true;\n                currentDir = name;\n                path = currentDir;\n            }\n            else {\n                isDirectory = false;\n                path = currentDir+name;\n            }\n\n            return new LstArchiveEntry(path, isDirectory, date, size, baseFolder);\n        }\n        catch(Exception e) {    // Catches exceptions thrown by StringTokenizer and SimpleDateFormat\n            LOGGER.info(\"Exception caught while parsing LST file\", e);\n\n            throw new IOException();\n        }\n    }\n\n\n    /////////////////////////////////////////\n    // ArchiveEntryIterator implementation //\n    /////////////////////////////////////////\n\n    public ArchiveEntry nextEntry() throws IOException {\n        // Return the next entry, if any\n        return getNextEntry();\n    }\n\n    public void close() throws IOException {\n        br.close();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/lst/LstArchiveFile.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.commons.file.impl.lst;\n\nimport com.mucommander.commons.file.*;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * IsoArchiveFile provides read-only access to archives in the LST format made popular by Total Commander.\n *\n * <p>Entries are parsed from the .lst file and can be read (an InputStream to them can be opened) if the file exists\n * locally.\n *\n * <p>For reference, here's a short LST file:\n * <pre>\n * c:\\\n * cygwin\\\t0\t2006.10.2\t19:35.2\n * cygwin.bat\t57\t2006.10.2\t19:34.58\n * cygwin.ico\t7022\t2006.10.2\t19:40.52\n * cygwin\\bin\\\t0\t2006.10.2\t19:40.52\n * addftinfo.exe\t67072\t2002.12.16\t10:3.24\n * afmtodit\t8544\t2002.12.16\t10:3.22\n * apropos\t1786\t2005.5.4\t2:12.50\n * ascii.exe\t7168\t2006.3.20\t20:44.24\n * ash.exe\t74240\t2004.1.27\t2:14.20\n * awk.exe\t19\t2006.10.2\t19:34.4\n * </pre>\n *\n * @see com.mucommander.commons.file.impl.lst.LstFormatProvider\n * @author Maxence Bernard\n */\npublic class LstArchiveFile extends AbstractROArchiveFile {\n    \n    LstArchiveFile(AbstractFile file) {\n        super(file);\n    }\n\n\n    @Override\n    public ArchiveEntryIterator getEntryIterator() throws IOException {\n        return new LstArchiveEntryIterator(getInputStream());\n    }\n\n    @Override\n    public InputStream getEntryInputStream(ArchiveEntry entry, ArchiveEntryIterator entryIterator) throws IOException {\n        // Will throw an IOException if the file designated by the entry doesn't exist \n        return FileFactory.getFile(((LstArchiveEntry)entry).getBaseFolder()+entry.getPath(), true).getInputStream();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/lst/LstFormatProvider.java",
    "content": "package com.mucommander.commons.file.impl.lst;\n\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.ArchiveFormatProvider;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\n\nimport java.io.IOException;\n\n/**\n * This class is the provider for the 'Lst' archive format implemented by {@link LstArchiveFile}.\n *\n * @see com.mucommander.commons.file.impl.lst.LstArchiveFile\n * @author Nicolas Rinaudo, Maxence Bernard\n */\npublic class LstFormatProvider implements ArchiveFormatProvider {\n\n    private static final String[] EXTENSIONS = {\".lst\"};\n\n    /**\n     * Static instance of the filename filter that matches archive filenames\n     */\n    private final static ExtensionFilenameFilter FILENAME_FILTER = new ExtensionFilenameFilter(EXTENSIONS);\n\n\n    @Override\n    public AbstractArchiveFile getFile(AbstractFile file) throws IOException {\n        return new LstArchiveFile(file);\n    }\n\n    @Override\n    public FilenameFilter getFilenameFilter() {\n        return FILENAME_FILTER;\n    }\n\n    @Override\n    public String[] getFileExtensions() {\n        return EXTENSIONS;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/lst/package.html",
    "content": "<body>\n  Provides an implementation of the LST archive format.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/lzh/LzhFormatProvider.java",
    "content": "package com.mucommander.commons.file.impl.lzh;\n\nimport java.io.IOException;\n\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.ArchiveFormatProvider;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\nimport com.mucommander.commons.file.impl.SevenZipJBindingROArchiveFile;\n\nimport net.sf.sevenzipjbinding.ArchiveFormat;\n\npublic class LzhFormatProvider implements ArchiveFormatProvider {\n\n    private static final String[] EXTENSIONS = { \".lzh\", \".lha\" };\n\n    private final static ExtensionFilenameFilter FILENAME_FILTER = new ExtensionFilenameFilter(EXTENSIONS);\n\n//    private final static byte[] SIGNATURE = { 0x2D, 0x6C, 0x68 };//=google but sevenzipjbinding examples 24FB2D\n    private final static byte[] SIGNATURE = {  };\n\n    @Override\n    public AbstractArchiveFile getFile(AbstractFile file) throws IOException {\n        return new SevenZipJBindingROArchiveFile(file, ArchiveFormat.LZH, SIGNATURE);\n    }\n\n    @Override\n    public FilenameFilter getFilenameFilter() {\n        return FILENAME_FILTER;\n    }\n\n    @Override\n    public String[] getFileExtensions() {\n        return EXTENSIONS;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/lzma/LzmaFormatProvider.java",
    "content": "package com.mucommander.commons.file.impl.lzma;\n\nimport java.io.IOException;\n\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.ArchiveFormatProvider;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\nimport com.mucommander.commons.file.impl.SevenZipJBindingROArchiveFile;\n\nimport net.sf.sevenzipjbinding.ArchiveFormat;\n\npublic class LzmaFormatProvider implements ArchiveFormatProvider {\n\n    private static final String[] EXTENSIONS = { \".lzma\" };\n\n    private final static ExtensionFilenameFilter FILENAME_FILTER = new ExtensionFilenameFilter(EXTENSIONS);\n\n    private final static byte[] SIGNATURE = {};\n\n    @Override\n    public AbstractArchiveFile getFile(AbstractFile file) throws IOException {\n        return new SevenZipJBindingROArchiveFile(file, ArchiveFormat.LZMA, SIGNATURE);\n    }\n\n    @Override\n    public FilenameFilter getFilenameFilter() {\n        return FILENAME_FILTER;\n    }\n\n    @Override\n    public String[] getFileExtensions() {\n        return EXTENSIONS;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/nfs/NFSFile.java",
    "content": "package com.mucommander.commons.file.impl.nfs;\n\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.file.filter.FilenameFilter;\nimport com.mucommander.commons.io.RandomAccessInputStream;\nimport com.mucommander.commons.io.RandomAccessOutputStream;\nimport com.sun.xfile.XFile;\nimport com.sun.xfile.XFileInputStream;\nimport com.sun.xfile.XFileOutputStream;\nimport com.sun.xfile.XRandomAccessFile;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n\n/**\n * NFSFile provides access to files located on an NFS/WebNFS server.\n *\n * <p>The associated {@link FileURL} scheme is {@link FileProtocols#NFS}. The host part of the URL designates the\n * NFS server. The path separator is '/'.\n *\n * <p>Here are a few examples of valid NFS URLs:\n * <code>\n * nfs://garfield/stuff/<br>\n * nfs://192.168.1.1:2049/stuff/somefile<br>\n * </code>\n *\n * <p>Access to NFS files is provided by the <code>Yanfs</code> library (formerly WebNFS) distributed under the BSD\n * license. The {@link #getUnderlyingFileObject()} method allows to retrieve a <code>com.sun.xfile.XFile</code> instance\n * corresponding to this NFSFile.\n *\n * @author Maxence Bernard\n */\npublic class NFSFile extends ProtocolFile {\n\n    /** Underlying file instance */\n    private final XFile file;\n\n    private String absPath;\n\n    private final FilePermissions permissions;\n\n    /** Caches the parent folder, initially null until getParent() gets called */\n    private AbstractFile parent;\n    /** Indicates whether the parent folder instance has been retrieved and cached or not (parent can be null) */\n    private boolean parentValueSet;\n\n    public final static String SEPARATOR = \"/\";\n\n    /** Name of the NFS version property */\n    public final static String NFS_VERSION_PROPERTY_NAME = \"version\";\n\n    /** NFS version 2 */\n    public final static String NFS_VERSION_2 = \"v2\";\n\n    /** NFS version 3 */\n    public final static String NFS_VERSION_3 = \"v3\";\n\n    /** Default NFS version */\n    public final static String DEFAULT_NFS_VERSION = NFS_VERSION_2;\n\n    /** Name of the NFS transport protocol property */\n    public final static String NFS_PROTOCOL_PROPERTY_NAME = \"protocol\";\n\n    /** 'Auto' transport protocol: TCP is tried first and if the connection cannot be established, falls back to UDP */\n    public final static String NFS_PROTOCOL_AUTO = \"Auto\";\n\n    /** TCP transport protocol */\n    public final static String NFS_PROTOCOL_TCP = \"TCP\";\n\n    /** UDP transport protocol */\n    public final static String NFS_PROTOCOL_UDP = \"UDP\";\n\n    /** Default transport protocol */\n    public final static String DEFAULT_NFS_PROTOCOL = NFS_PROTOCOL_AUTO;\n\n\n    /**\n     * Creates a new instance of NFSFile.\n     *\n     * @param fileURL fiel url\n     */\n    NFSFile(FileURL fileURL) {\n        super(fileURL);\n\n        // create the NFS URL used by XFile.\n\n        // The general syntax for NFS URLs is : nfs://<host>:<port><url-path>, as specified by RFC 2054\n        // Additionally, XFile allows some special flags to be used in the port part of the URL to specify connection\n        // properties. Those flags must be placed after the port, and before the colon character delimiting the end of\n        // the port part.\n        // Here's the list of allowed flags (quoted from com.sun.nfs.NfsURL):\n        // vn\t- NFS version, e.g. \"v3\"\n        // u\t- Force UDP - normally TCP is preferred\n        // t\t- Force TDP - don't fall back to UDP\n        // m    - Force Mount protocol.  Normally public filehandle is preferred\n        //\n        // Example: nfs://server:123v2um/path : use port 123 with NFS v2 over UDP and Mount protocol\n        //\n        // The 'm' flag must be specified, otherwise regular NFS shares (i.e. non WebNFS-enabled ones) that don't\n        // specify a public filehandle will fail. However, using this flag has two unfortunate consequences:\n        // - the NFS version fails to be properly negotiated as it normally does (try v3 then fall back on v2): the\n        //  NFS version must be specified in the URL.\n        // - an extra slash character must be added before the path part, otherwise it is considered as relative to\n        //  the public filehandle and will thus fail to resolve.\n        //\n        // These issues might get fixed in Yanfs someday. When that happens, this code might be simplified.\n\n        // Determines the NFS version (v2 or v3) to be used, based on the version property\n        String nfsVersion = fileURL.getProperty(NFS_VERSION_PROPERTY_NAME);\n        if (nfsVersion == null) {\n            nfsVersion = DEFAULT_NFS_VERSION;\n        }\n\n        // Determines the NFS transport protocol (Auto, TCP or UDP) to be used, based on the protocol property\n        String nfsProtocol = fileURL.getProperty(NFS_PROTOCOL_PROPERTY_NAME);\n        nfsProtocol = NFS_PROTOCOL_TCP.equals(nfsProtocol)?\"t\":NFS_PROTOCOL_UDP.equals(nfsProtocol)?\"u\":\"\";\n\n        // Omit port part if none is contained in the FileURL or if it is 2049\n        int port = fileURL.getPort();\n        String portString = port < 0 || port == 2049 ? \"\" : \"\"+port;\n\n        // create the XFile instance with the weird NFS url\n        this.file = new XFile(\"nfs://\"+fileURL.getHost()+\":\"+portString+nfsVersion+nfsProtocol+\"m\"+\"/\"+fileURL.getPath());\n\n        // Retrieve the absolute path from the FileURL and NOT from the XFile instance which will return those weird flags\n        this.absPath = fileURL.toString();\n        // Remove trailing separator (if any)\n        this.absPath = absPath.endsWith(SEPARATOR)?absPath.substring(0,absPath.length()-1):absPath;\n\n        this.permissions = new NFSFilePermissions(file);\n    }\n\n\n    @Override\n    public long getLastModifiedDate() {\n        return file.lastModified();\n    }\n\n    /**\n     * Implementation notes: always throws {@link UnsupportedFileOperationException}.\n     *\n     * @throws UnsupportedFileOperationException always.\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void setLastModifiedDate(long lastModified) throws UnsupportedFileOperationException {\n        // XFile has no method for that purpose\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_DATE);\n    }\n\n    @Override\n    public long getSize() {\n        return file.length();\n    }\n\n    @Override\n    public AbstractFile getParent() {\n        // Retrieve parent AbstractFile and cache it\n        if (!parentValueSet) {\n            FileURL parentURL = getURL().getParent();\n            if (parentURL != null) {\n                parent = FileFactory.getFile(parentURL);\n                // Note: parent may be null if it can't be resolved\n            }\n            parentValueSet = true;\n        }\n        return parent;\n    }\n\n    @Override\n    public void setParent(AbstractFile parent) {\n        this.parent = parent;\n        this.parentValueSet = true;\n    }\n\n    @Override\n    public boolean exists() {\n        return file.exists();\n    }\n\n    @Override\n    public FilePermissions getPermissions() {\n        return permissions;\n    }\n\n    @Override\n    public PermissionBits getChangeablePermissions() {\n        // no permission can be changed\n        return PermissionBits.EMPTY_PERMISSION_BITS;\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public void changePermission(int access, int permission, boolean enabled) throws UnsupportedFileOperationException {\n        // XFile has no method for that unfortunately\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_PERMISSION);\n    }\n\n    /**\n     * Always returns <code>null</code>, this information is not available unfortunately.\n     */\n    @Override\n    public String getOwner() {\n        return null;\n    }\n\n    /**\n     * Always returns <code>false</code>, this information is not available unfortunately.\n     */\n    @Override\n    public boolean canGetOwner() {\n        return false;\n    }\n\n    /**\n     * Always returns <code>null</code>, this information is not available unfortunately.\n     */\n    @Override\n    public String getGroup() {\n        return null;\n    }\n\n    /**\n     * Always returns <code>false</code>, this information is not available unfortunately.\n     */\n    @Override\n    public boolean canGetGroup() {\n        return false;\n    }\n\n    @Override\n    public boolean isDirectory() {\n        return file.isDirectory();\n    }\n\n    /**\n     * Always returns <code>false</code> (symlinks are not detected).\n     */\n    @Override\n    public boolean isSymlink() {\n        // Yanfs is unable to detect symlinks at this time\n        return false;\n    }\n\n    @Override\n    public boolean isSystem() {\n        return false;\n    }\n\n    @Override\n    public AbstractFile[] ls() throws IOException {\n        return ls(null);\n    }\n\n    @Override\n    public void mkdir() throws IOException {\n        if(!new XFile(absPath).mkdir())\n            throw new IOException();\n    }\n\n    @Override\n    public InputStream getInputStream() throws IOException {\n        return new XFileInputStream(file);\n    }\n\n    @Override\n    public OutputStream getOutputStream() throws IOException {\n        return new XFileOutputStream(absPath, false);\n    }\n\n    @Override\n    public OutputStream getAppendOutputStream() throws IOException {\n        return new XFileOutputStream(absPath, true);\n    }\n\n    @Override\n    public RandomAccessInputStream getRandomAccessInputStream() throws IOException {\n        return new NFSRandomAccessInputStream(new XRandomAccessFile(file, \"r\"));\n    }\n\n    /**\n     * <b>Warning:</b> the returned {@link com.mucommander.commons.file.impl.nfs.NFSFile.NFSRandomAccessOutputStream} instance\n     * is not fully functional, its {@link com.mucommander.commons.file.impl.nfs.NFSFile.NFSRandomAccessOutputStream#setLength(long)}\n     * method has a limitation.\n     *\n     * @return a RandomAccessOutputStream that is not fully functional\n     * @throws IOException if the file could not be opened for random write access\n     */\n    @Override\n    @UnsupportedFileOperation\n    public RandomAccessOutputStream getRandomAccessOutputStream() throws IOException {\n        return new NFSRandomAccessOutputStream(new XRandomAccessFile(file, \"rw\"));\n    }\n\n    @Override\n    public void delete() throws IOException {\n        boolean ret = file.delete();\n\n        if (!ret) {\n            throw new IOException();\n        }\n    }\n\n    /**\n     * Implementation notes: server-to-server renaming will work if the destination file also uses the 'NFS' scheme\n     * and is located on the same host.\n     */\n    @Override\n    public void renameTo(AbstractFile destFile) throws IOException {\n        // Throw an exception if the file cannot be renamed to the specified destination\n        checkRenamePrerequisites(destFile, true, false);\n\n        // Rename file\n        if (!file.renameTo(((NFSFile)destFile).file)) {\n            throw new IOException();\n        }\n    }\n\n    /**\n     * Returns a <code>com.sun.xfile.XFile</code> instance corresponding to this file.\n     */\n    @Override\n    public Object getUnderlyingFileObject() {\n        return file;\n    }\n\n\n    // Unsupported file operations\n\n    /**\n     * Always throws {@link UnsupportedFileOperationException} when called.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void copyRemotelyTo(AbstractFile destFile) throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.COPY_REMOTELY);\n    }\n\n    /**\n     * Always throws {@link UnsupportedFileOperationException} when called.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public long getFreeSpace() throws UnsupportedFileOperationException {\n        // XFile has no method to provide that information\n        throw new UnsupportedFileOperationException(FileOperation.GET_FREE_SPACE);\n    }\n\n    /**\n     * Always throws {@link UnsupportedFileOperationException} when called.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public long getTotalSpace() throws UnsupportedFileOperationException {\n        // XFile has no method to provide that information\n        throw new UnsupportedFileOperationException(FileOperation.GET_TOTAL_SPACE);\n    }\n\n\n    @Override\n    public AbstractFile[] ls(FilenameFilter filenameFilter) throws IOException {\n        String[] names = file.list();\n\n        if (names == null) {\n            throw new IOException();\n        }\n\n        if (filenameFilter != null) {\n            names = filenameFilter.filter(names);\n        }\n\n        AbstractFile[] children = new AbstractFile[names.length];\n        FileURL childURL;\n        String baseURLPath = fileURL.getPath();\n        if (!baseURLPath.endsWith(\"/\")) {\n            baseURLPath += SEPARATOR;\n        }\n\n        for (int i = 0; i < names.length; i++) {\n            // Clone this file's URL with the connection properties and set the child file's path\n            childURL = (FileURL)fileURL.clone();\n            childURL.setPath(baseURLPath+names[i]);\n\n            // create the child NFSFile using this file as a parent\n            children[i] = FileFactory.getFile(childURL, this);\n        }\n\n        return children;\n    }\n\n\n    /**\n     * NFSRandomAccessInputStream extends RandomAccessInputStream to provide random read access to an NFSFile.\n     */\n    public static class NFSRandomAccessInputStream extends RandomAccessInputStream {\n\n        private final XRandomAccessFile raf;\n\n        NFSRandomAccessInputStream(XRandomAccessFile raf) {\n            this.raf = raf;\n        }\n\n        @Override\n        public int read() throws IOException {\n            return raf.read();\n        }\n\n        @Override\n        public int read(byte[] b, int off, int len) throws IOException {\n            return raf.read(b, off, len);\n        }\n\n        @Override\n        public void close() throws IOException {\n            raf.close();\n        }\n\n        public long getOffset() throws IOException {\n            return raf.getFilePointer();\n        }\n\n        public long getLength() throws IOException {\n            return raf.length();\n        }\n\n        public void seek(long offset) throws IOException {\n            raf.seek(offset);\n        }\n    }\n\n    /**\n     * NFSRandomAccessOutputStream extends RandomAccessOutputStream to provide random write access to an NFSFile.\n     *\n     * <p><b>Warning:</b> this RandomAccessOutputStream is not fully functional, the {@link #setLength(long)} has a\n     * limitation.\n     */\n    public static class NFSRandomAccessOutputStream extends RandomAccessOutputStream {\n\n        private final XRandomAccessFile raf;\n\n        NFSRandomAccessOutputStream(XRandomAccessFile raf) {\n            this.raf = raf;\n        }\n\n        @Override\n        public void write(int i) throws IOException {\n            raf.write(i);\n        }\n\n        @Override\n        public void write(byte[] b) throws IOException {\n            raf.write(b);\n        }\n\n        @Override\n        public void write(byte[] b, int off, int len) throws IOException {\n            raf.write(b, off, len);\n        }\n\n        @Override\n        public void close() throws IOException {\n            raf.close();\n        }\n\n        public long getOffset() throws IOException {\n            return raf.getFilePointer();\n        }\n\n        public long getLength() throws IOException {\n            return raf.length();\n        }\n\n        public void seek(long offset) throws IOException {\n            raf.seek(offset);\n        }\n\n        /**\n         * <b>Warning:</b> this method is only capable of expanding the file, not truncating it.\n         * It will throw an <code>IOException</code> whenever the <code>newLength</code> parameter is greater than\n         * the current length reported by {@link #getLength()}.\n         *\n         * @param newLength the new file's length\n         * @throws IOException If an I/O error occurred while trying to change the file's length\n         */\n        @Override\n        public void setLength(long newLength) throws IOException {\n            // This operation is supported only if the new length is greater (or equal) than the current length\n            long currentLength = getLength();\n            if (newLength < currentLength) {\n                throw new IOException();\n            }\n\n            if (newLength == currentLength) {\n                return;\n            }\n\n            // Extend the file's length by seeking to the end and writing a byte\n            seek(newLength-1);\n            write(0);\n        }\n    }\n\n\n    /**\n     * A Permissions implementation for NFSFile.\n     */\n    private static class NFSFilePermissions extends IndividualPermissionBits implements FilePermissions {\n\n        private final XFile file;\n\n        private final static PermissionBits MASK = new GroupedPermissionBits(384);  // rw------- (300 octal)\n\n        NFSFilePermissions(XFile file) {\n            this.file = file;\n        }\n\n        public boolean getBitValue(int access, int type) {\n            if (access != USER_ACCESS) {\n                return false;\n            }\n            if (type == READ_PERMISSION) {\n                return file.canRead();\n            } else if(type == WRITE_PERMISSION) {\n                return file.canWrite();\n            }\n\n            return false;\n        }\n\n        public PermissionBits getMask() {\n            return MASK;\n        }\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public short getReplication() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_REPLICATION);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public long getBlocksize() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_BLOCKSIZE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public void changeReplication(short replication) throws IOException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_REPLICATION);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/nfs/NFSProtocolProvider.java",
    "content": "package com.mucommander.commons.file.impl.nfs;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.ProtocolProvider;\n\nimport java.io.IOException;\n\n/**\n * This class is the provider for the NFS filesystem implemented by {@link com.mucommander.commons.file.impl.nfs.NFSFile}.\n *\n * @author Nicolas Rinaudo\n * @see com.mucommander.commons.file.impl.nfs.NFSFile\n */\npublic class NFSProtocolProvider implements ProtocolProvider {\n\n    public AbstractFile getFile(FileURL url, Object... instantiationParams) throws IOException {\n        return new NFSFile(url);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/nfs/package.html",
    "content": "<body>\n  Provides an implementation of the NFS protocol.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/rar/RarFormatProvider.java",
    "content": "package com.mucommander.commons.file.impl.rar;\r\n\r\nimport com.mucommander.commons.file.AbstractArchiveFile;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.ArchiveFormatProvider;\r\nimport com.mucommander.commons.file.SevenZipArchiveFormatDetector;\r\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\r\nimport com.mucommander.commons.file.filter.FilenameFilter;\r\nimport com.mucommander.commons.file.impl.SevenZipJBindingROArchiveFile;\r\nimport com.mucommander.commons.file.impl.sevenzip.SevenZipArchiveFile;\r\nimport net.sf.sevenzipjbinding.ArchiveFormat;\r\n\r\nimport java.io.IOException;\r\n\r\n/**\r\n * This class is the provider for the 'Rar' archive format implemented by {@link SevenZipArchiveFile}.\r\n *\r\n * @author Arik Hadas\r\n */\r\npublic class RarFormatProvider implements ArchiveFormatProvider {\r\n\r\n    private static final String[] EXTENSIONS = {\".rar\", \".cbr\"};\r\n\r\n    private final static byte[] RAR4_SIGNATURE = {0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00};\r\n    private final static byte[] RAR5_SIGNATURE = {0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x01, 0x00};\r\n\r\n\t/**\r\n     * Static instance of the filename filter that matches archive filenames\r\n     */\r\n    private final static ExtensionFilenameFilter FILENAME_FILTER = new ExtensionFilenameFilter(EXTENSIONS);\r\n\r\n    private static final SevenZipArchiveFormatDetector detector = new SevenZipArchiveFormatDetector(RAR5_SIGNATURE.length) {\r\n        @Override\r\n        protected ArchiveFormat detect(byte[] bytes) {\r\n            if (checkSignature(bytes, RAR4_SIGNATURE)) {\r\n                return ArchiveFormat.RAR;\r\n            } else if (checkSignature(bytes, RAR5_SIGNATURE)) {\r\n                return ArchiveFormat.RAR5;\r\n            }\r\n            return null;\r\n        }\r\n    };\r\n\r\n\r\n    @Override\r\n    public AbstractArchiveFile getFile(AbstractFile file) throws IOException {\r\n        return new SevenZipJBindingROArchiveFile(file, detector);\r\n    }\r\n\r\n    @Override\r\n    public FilenameFilter getFilenameFilter() {\r\n        return FILENAME_FILTER;\r\n    }\r\n\r\n    @Override\r\n    public String[] getFileExtensions() {\r\n        return EXTENSIONS;\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/rpm/RpmFormatProvider.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2020 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.impl.rpm;\n\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.ArchiveFormatProvider;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\nimport com.mucommander.commons.file.impl.SevenZipJBindingROArchiveFile;\nimport net.sf.sevenzipjbinding.ArchiveFormat;\n\nimport java.io.IOException;\n\npublic class RpmFormatProvider implements ArchiveFormatProvider {\n\n        private static final String[] EXTENSIONS = { \".rpm\" };\n\n        private final static ExtensionFilenameFilter FILENAME_FILTER = new ExtensionFilenameFilter(EXTENSIONS);\n\n        private final static byte[] SIGNATURE = {(byte)0xED, (byte)0xAB, (byte)0xEE, (byte)0xDB, 0x03};\n\n        @Override\n        public AbstractArchiveFile getFile(AbstractFile file) throws IOException {\n            return new SevenZipJBindingROArchiveFile(file, ArchiveFormat.RPM, SIGNATURE);\n        }\n\n        @Override\n        public FilenameFilter getFilenameFilter() {\n            return FILENAME_FILTER;\n        }\n\n        @Override\n        public String[] getFileExtensions() {\n            return EXTENSIONS;\n        }\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/s3/S3Bucket.java",
    "content": "package com.mucommander.commons.file.impl.s3;\n\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.io.RandomAccessInputStream;\nimport org.jets3t.service.S3Service;\nimport org.jets3t.service.S3ServiceException;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n/**\n * <code>S3Bucket</code> represents an Amazon S3 bucket.\n *\n * @author Maxence Bernard\n */\npublic class S3Bucket extends S3File {\n    private static final Logger LOGGER = LoggerFactory.getLogger(S3File.class);\n\n    private final String bucketName;\n    private final S3BucketFileAttributes atts;\n\n    // TODO: add support for ACL ? (would cost an extra request per bucket)\n    /** Default permissions for S3 buckets */\n    private final static FilePermissions DEFAULT_PERMISSIONS = new SimpleFilePermissions(448);   // rwx------\n\n\n    protected S3Bucket(FileURL url, S3Service service, String bucketName) throws AuthException {\n        super(url, service);\n\n        this.bucketName = bucketName;\n        atts = new S3BucketFileAttributes();\n    }\n\n    protected S3Bucket(FileURL url, S3Service service, org.jets3t.service.model.S3Bucket bucket) {\n        super(url, service);\n\n        this.bucketName = bucket.getName();\n        atts = new S3BucketFileAttributes(bucket);\n    }\n\n\n\n    @Override\n    public FileAttributes getFileAttributes() {\n        return atts;\n    }\n\n\n    @Override\n    public String getOwner() {\n        return atts.getOwner();\n    }\n\n    @Override\n    public boolean canGetOwner() {\n        return true;\n    }\n\n    @Override\n    public AbstractFile[] ls() throws IOException {\n        return listObjects(bucketName, \"\", this);\n    }\n\n    @Override\n    public void delete() throws IOException {\n        try {\n            service.deleteBucket(bucketName);\n        } catch(S3ServiceException e) {\n            throw getIOException(e);\n        }\n    }\n\n    @Override\n    public void mkdir() throws IOException {\n        try {\n            service.createBucket(bucketName);\n        } catch(S3ServiceException e) {\n            throw getIOException(e);\n        }\n    }\n\n\n    // Unsupported operations\n\n    @Override\n    @UnsupportedFileOperation\n    public void copyRemotelyTo(AbstractFile destFile) throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.COPY_REMOTELY);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public void renameTo(AbstractFile destFile) throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.RENAME);\n    }\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException}.\n     */\n    @Override\n    @UnsupportedFileOperation\n    public InputStream getInputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.READ_FILE);\n    }\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException}.\n     */\n    @Override\n    @UnsupportedFileOperation\n    public OutputStream getOutputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.WRITE_FILE);\n    }\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException}.\n     */\n    @Override\n    @UnsupportedFileOperation\n    public RandomAccessInputStream getRandomAccessInputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.RANDOM_READ_FILE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public short getReplication() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_REPLICATION);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public long getBlocksize() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_BLOCKSIZE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public void changeReplication(short replication) throws IOException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_REPLICATION);\n    }\n\n\n    /**\n     * S3BucketFileAttributes provides getters and setters for S3 bucket attributes. By extending\n     * <code>SyncedFileAttributes</code>, this class caches attributes for a certain amount of time\n     * after which fresh values are retrieved from the server.\n     *\n     * @author Maxence Bernard\n     */\n    private class S3BucketFileAttributes extends SyncedFileAttributes {\n\n        private final static int TTL = 60000;\n\n        private S3BucketFileAttributes() throws AuthException {\n            super(TTL, false);      // no initial update\n\n            fetchAttributes();      // throws AuthException if no or bad credentials\n            updateExpirationDate(); // declare the attributes as 'fresh'\n        }\n\n        private S3BucketFileAttributes(org.jets3t.service.model.S3Bucket bucket) {\n            super(TTL, false);      // no initial update\n\n            setAttributes(bucket);\n            setExists(true);\n\n            updateExpirationDate(); // declare the attributes as 'fresh'\n        }\n\n        private void setAttributes(org.jets3t.service.model.S3Bucket bucket) {\n            setDirectory(true);\n            setDate(bucket.getCreationDate().getTime());\n            setPermissions(DEFAULT_PERMISSIONS);\n            setOwner(bucket.getOwner().getDisplayName());\n        }\n\n        private void fetchAttributes() throws AuthException {\n            org.jets3t.service.model.S3Bucket bucket;\n            S3ServiceException e = null;\n            try {\n                // Note: unlike getObjectDetails, getBucket returns null when the bucket does not exist\n                // (that is because the corresponding request is a GET on the root resource, not a HEAD on the bucket).\n                bucket = service.getBucket(bucketName);\n            } catch(S3ServiceException ex) {\n                e = ex;\n                bucket = null;\n            }\n\n            if (bucket!=null) {\n                // Bucket exists\n                setExists(true);\n                setAttributes(bucket);\n            } else {\n                // Bucket doesn't exist on the server, or could not be retrieved\n                setExists(false);\n\n                setDirectory(false);\n                setDate(0);\n                setPermissions(FilePermissions.EMPTY_FILE_PERMISSIONS);\n                setOwner(null);\n\n                if (e != null) {\n                    handleAuthException(e, fileURL);\n                }\n            }\n        }\n\n\n        @Override\n        public void updateAttributes() {\n            try {\n                fetchAttributes();\n            } catch(Exception e) {        // AuthException\n                LOGGER.info(\"Failed to update attributes\", e);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/s3/S3File.java",
    "content": "package com.mucommander.commons.file.impl.s3;\n\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.io.RandomAccessOutputStream;\nimport org.jets3t.service.Constants;\nimport org.jets3t.service.S3ObjectsChunk;\nimport org.jets3t.service.S3Service;\nimport org.jets3t.service.S3ServiceException;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.Date;\n\n/**\n * Super class of {@link S3Root}, {@link S3Bucket} and {@link S3Object}.\n *\n * @author Maxence Bernard\n */\npublic abstract class S3File extends ProtocolFile {\n\n    protected org.jets3t.service.S3Service service;\n\n    protected AbstractFile parent;\n    protected boolean parentSet;\n\n    protected S3File(FileURL url, S3Service service) {\n        super(url);\n\n        this.service = service;\n    }\n\n    protected IOException getIOException(S3ServiceException e) throws IOException {\n        return getIOException(e, fileURL);\n    }\n\n    protected static IOException getIOException(S3ServiceException e, FileURL fileURL) throws IOException {\n        handleAuthException(e, fileURL);\n\n        Throwable cause = e.getCause();\n        if (cause instanceof IOException)\n            return (IOException) cause;\n\n        return new IOException(e);\n    }\n\n    protected static void handleAuthException(S3ServiceException e, FileURL fileURL) throws AuthException {\n        int code = e.getResponseCode();\n        if (code == 401 || code == 403)\n            throw new AuthException(fileURL);\n    }\n\n    protected AbstractFile[] listObjects(String bucketName, String prefix, S3File parent) throws IOException {\n        try {\n            S3ObjectsChunk chunk = service.listObjectsChunked(bucketName, prefix, \"/\", Constants.DEFAULT_OBJECT_LIST_CHUNK_SIZE, null, true);\n            org.jets3t.service.model.S3Object[] objects = chunk.getObjects();\n            String[] commonPrefixes = chunk.getCommonPrefixes();\n\n            if (objects.length == 0 && !prefix.isEmpty()) {\n                // This happens only when the directory does not exist\n                throw new IOException();\n            }\n\n            AbstractFile[] children = new AbstractFile[objects.length + commonPrefixes.length];\n            FileURL childURL;\n            int i = 0;\n            String objectKey;\n\n            for (org.jets3t.service.model.S3Object object : objects) {\n                // Discard the object corresponding to the prefix itself\n                objectKey = object.getKey();\n                if (objectKey.equals(prefix))\n                    continue;\n\n                childURL = (FileURL) fileURL.clone();\n                childURL.setPath(bucketName + \"/\" + objectKey);\n\n                children[i] = FileFactory.getFile(childURL, parent, service, object);\n                i++;\n            }\n\n            org.jets3t.service.model.S3Object directoryObject;\n            for (String commonPrefix : commonPrefixes) {\n                childURL = (FileURL) fileURL.clone();\n                childURL.setPath(bucketName + \"/\" + commonPrefix);\n\n                directoryObject = new org.jets3t.service.model.S3Object(commonPrefix);\n                // Common prefixes are not objects per se, and therefore do not have a date, content-length nor owner.\n                directoryObject.setLastModifiedDate(new Date(System.currentTimeMillis()));\n                directoryObject.setContentLength(0);\n                children[i] = FileFactory.getFile(childURL, parent, service, directoryObject);\n                i++;\n            }\n\n            // Trim the array if an object was discarded.\n            // Note: Having to recreate an array sucks (puts pressure on the GC), but I haven't found a reliable way\n            // to know in advance whether the prefix will appear in the results or not.\n            if (i < children.length) {\n                AbstractFile[] childrenTrimmed = new AbstractFile[i];\n                System.arraycopy(children, 0, childrenTrimmed, 0, i);\n\n                return childrenTrimmed;\n            }\n\n            return children;\n        } catch (S3ServiceException e) {\n            throw getIOException(e);\n        }\n    }\n\n\n    public abstract FileAttributes getFileAttributes();\n\n\n\n    @Override\n    public AbstractFile getParent() {\n        if (!parentSet) {\n            FileURL parentFileURL = this.fileURL.getParent();\n            if (parentFileURL != null) {\n                try {\n                    parent = FileFactory.getFile(parentFileURL, null, service);\n                } catch (IOException e) {\n                    // No parent\n                }\n            }\n\n            parentSet = true;\n        }\n\n        return parent;\n    }\n\n    @Override\n    public void setParent(AbstractFile parent) {\n        this.parent = parent;\n        this.parentSet = true;\n    }\n\n\n    // Delegates to FileAttributes\n\n    @Override\n    public long getLastModifiedDate() {\n        return getFileAttributes().getLastModifiedDate();\n    }\n\n    @Override\n    public long getSize() {\n        return getFileAttributes().getSize();\n    }\n\n    @Override\n    public boolean exists() {\n        return getFileAttributes().exists();\n    }\n\n    @Override\n    public boolean isDirectory() {\n        return getFileAttributes().isDirectory();\n    }\n\n    @Override\n    public FilePermissions getPermissions() {\n        return getFileAttributes().getPermissions();\n    }\n\n    @Override\n    public Object getUnderlyingFileObject() {\n        return getFileAttributes();\n    }\n\n\n    // Unsupported operations, no matter the kind of resource (object, bucket, service)\n\n    @Override\n    public boolean isSymlink() {\n        return false;\n    }\n\n    @Override\n    public boolean isSystem() {\n        return false;\n    }\n\n    @Override\n    public PermissionBits getChangeablePermissions() {\n        return PermissionBits.EMPTY_PERMISSION_BITS;\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public void changePermission(int access, int permission, boolean enabled) throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_PERMISSION);\n    }\n\n    @Override\n    public String getGroup() {\n        return null;\n    }\n\n    @Override\n    public boolean canGetGroup() {\n        return false;\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public OutputStream getAppendOutputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.APPEND_FILE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public RandomAccessOutputStream getRandomAccessOutputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.RANDOM_WRITE_FILE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public long getFreeSpace() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_FREE_SPACE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public long getTotalSpace() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_TOTAL_SPACE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public void setLastModifiedDate(long lastModified) throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_DATE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public OutputStream getOutputStream() throws IOException {\n        throw new UnsupportedFileOperationException(FileOperation.WRITE_FILE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public long getBlocksize() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_BLOCKSIZE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public short getReplication() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_REPLICATION);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public void changeReplication(short replication) throws IOException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_REPLICATION);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/s3/S3Object.java",
    "content": "package com.mucommander.commons.file.impl.s3;\n\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.io.BufferPool;\nimport com.mucommander.commons.io.FileTransferException;\nimport com.mucommander.commons.io.RandomAccessInputStream;\nimport com.mucommander.commons.io.StreamUtils;\nimport org.jets3t.service.S3Service;\nimport org.jets3t.service.S3ServiceException;\nimport org.jets3t.service.model.S3Owner;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n/**\n * <code>S3Object</code> represents an Amazon S3 object.\n *\n * @author Maxence Bernard\n */\npublic class S3Object extends S3File {\n    private static final Logger LOGGER = LoggerFactory.getLogger(S3Object.class);\n\n    private final String bucketName;\n    private final S3ObjectFileAttributes atts;\n\n    /** Maximum size of an S3 object (5GB) */\n    private final static long MAX_OBJECT_SIZE = 5368709120l;\n\n    // TODO: add support for ACL ? (would cost an extra request per object)\n    /** Default permissions for S3 objects */\n    private final static FilePermissions DEFAULT_PERMISSIONS = new SimpleFilePermissions(384);   // rw-------\n\n\n    protected S3Object(FileURL url, S3Service service, String bucketName) throws AuthException {\n        super(url, service);\n\n        this.bucketName = bucketName;\n        atts = new S3ObjectFileAttributes();\n    }\n\n    protected S3Object(FileURL url, S3Service service, String bucketName, org.jets3t.service.model.S3Object object) throws AuthException {\n        super(url, service);\n\n        this.bucketName = bucketName;\n        atts = new S3ObjectFileAttributes(object);\n    }\n\n    private String getObjectKey() {\n        String urlPath = fileURL.getPath();\n        // Strip out the bucket name from the path\n        return urlPath.substring(bucketName.length()+2);\n    }\n\n    private String getObjectKey(boolean wantTrailingSeparator) {\n        String objectKey = getObjectKey();\n        return wantTrailingSeparator?addTrailingSeparator(objectKey):removeTrailingSeparator(objectKey);\n    }\n\n    /**\n     * Uploads the object contained in the given input stream to S3 by performing a 'PUT Object' request.\n     * The input stream is always closed, whether the operation failed or succeeded.\n     *\n     * @param in the stream that contains the object to be uploaded\n     * @param objectLength length of the object\n     * @throws FileTransferException if an error occurred during the transfer\n     */\n    private void putObject(InputStream in, long objectLength) throws FileTransferException {\n        try {\n            // Init S3 object\n            org.jets3t.service.model.S3Object object = new org.jets3t.service.model.S3Object(getObjectKey(false));\n            object.setDataInputStream(in);\n            object.setContentLength(objectLength);\n\n            // Transfer to S3 and update local file attributes\n            atts.setAttributes(service.putObject(bucketName, object));\n            atts.setExists(true);\n            atts.updateExpirationDate();\n        }\n        catch(S3ServiceException e) {\n            throw new FileTransferException(FileTransferException.UNKNOWN_REASON);\n        }\n        finally {\n            // Close the InputStream, no matter what\n            try {\n                in.close();\n            } catch(IOException e) {\n                // Do not re-throw the exception to prevent exceptions caught in the catch block from being replaced\n            }\n        }\n    }\n\n\n    @Override\n    public FileAttributes getFileAttributes() {\n        return atts;\n    }\n\n\n    @Override\n    public String getOwner() {\n        return atts.getOwner();\n    }\n\n    @Override\n    public boolean canGetOwner() {\n        return true;\n    }\n\n    @Override\n    public AbstractFile[] ls() throws IOException {\n        return listObjects(bucketName, getObjectKey(true), this);\n    }\n\n    @Override\n    public void mkdir() throws IOException {\n        if(exists())\n            throw new IOException();\n\n        try {\n            atts.setAttributes(service.putObject(bucketName, new org.jets3t.service.model.S3Object(getObjectKey(true))));\n            atts.setExists(true);\n            atts.updateExpirationDate();\n        }\n        catch(S3ServiceException e) {\n            throw getIOException(e);\n        }\n    }\n\n    @Override\n    public void delete() throws IOException {\n        // Note: DELETE on a non-existing resource is a successful request, so we need this check\n        if (!exists()) {\n            throw new IOException();\n        }\n\n        try {\n            // Make sure that the directory is empty, abort if not.\n            // Note that we must not count the parent directory (this file).\n            boolean isDirectory = isDirectory();\n            if(isDirectory && service.listObjectsChunked(bucketName, getObjectKey(true), \"/\", 2, null, false).getObjects().length>1)\n                throw new IOException(\"Directory not empty\");\n\n            service.deleteObject(bucketName, getObjectKey(isDirectory));\n\n            // Update file attributes locally\n            atts.setExists(false);\n            atts.setDirectory(false);\n            atts.setSize(0);\n        } catch(S3ServiceException e) {\n            throw getIOException(e);\n        }\n    }\n\n    @Override\n    public void renameTo(AbstractFile destFile) throws IOException {\n        copyTo(destFile);\n        delete();\n    }\n\n    @Override\n    public void copyRemotelyTo(AbstractFile destFile) throws IOException {\n        checkCopyRemotelyPrerequisites(destFile, true, false);\n\n        S3Object destObjectFile = destFile.getAncestor(S3Object.class);\n\n        try {\n            // Let the COPY request fail if both objects are not located in the same region, saves 2 HEAD BUCKET requests.\n//            // Ensure that both objects' bucket are located in the same region (null means US standard)\n//            String sourceBucketLocation = service.getBucket(bucketName).getLocation();\n//            String destBucketLocation = destObjectFile.service.getBucket(destObjectFile.bucketName).getLocation();\n//            if((sourceBucketLocation==null && destBucketLocation!=null)\n//            || (sourceBucketLocation!=null && destBucketLocation==null)\n//            || !(sourceBucketLocation!=null && !sourceBucketLocation.equals(destBucketLocation))\n//            || !destBucketLocation.equals(destBucketLocation))\n//                throw new IOException();\n\n            boolean isDirectory = isDirectory();\n            org.jets3t.service.model.S3Object destObject = new org.jets3t.service.model.S3Object(destObjectFile.getObjectKey(isDirectory));\n\n            destObject.addAllMetadata(\n                    service.copyObject(bucketName, getObjectKey(isDirectory), destObjectFile.bucketName, destObject, false)\n            );\n\n            // Update destination file attributes\n            destObjectFile.atts.setAttributes(destObject);\n            destObjectFile.atts.setExists(true);\n        }\n        catch(S3ServiceException e) {\n            throw getIOException(e);\n        }\n    }\n\n    @Override\n    public InputStream getInputStream() throws IOException {\n        return getInputStream(0);\n    }\n\n    @Override\n    public InputStream getInputStream(long offset) throws IOException {\n        try {\n            // Note: do *not* use S3ObjectRandomAccessInputStream if the object is to be read sequentially, as it would\n            // add unnecessary billing overhead since it reads the object chunk by chunk, each in a separate GET request.\n            return service.getObject(bucketName, getObjectKey(false), null, null, null, null, offset==0?null:offset, null).getDataInputStream();\n        }\n        catch(S3ServiceException e) {\n            throw getIOException(e);\n        }\n    }\n\n    @Override\n    public RandomAccessInputStream getRandomAccessInputStream() throws IOException {\n        if(!exists())\n            throw new IOException();\n\n        return new S3ObjectRandomAccessInputStream();\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public OutputStream getOutputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.WRITE_FILE);\n\n        // This stream is broken: close has no way to know if the transfer went through entirely. If it didn't\n        // (close was called before the end of the input stream), the partially copied file will still be transferred\n        // to S3, when it shouldn't.\n\n//        final AbstractFile tempFile = FileFactory.getTemporaryFile(false);\n//        final OutputStream tempOut = tempFile.getOutputStream();\n//\n//        // Update local attributes temporarily\n//        atts.setExists(true);\n//        atts.setSize(0);\n//        atts.setDirectory(false);\n//\n//        // Return an OutputStream to a temporary file that will be copied to the S3 object when the stream is closed.\n//        // The object's length has to be declared in the PUT request's headers and this is the only way to do so.\n//        return new FilteredOutputStream(tempOut) {\n//            @Override\n//            public void close() throws IOException {\n//                tempOut.close();\n//\n//                InputStream tempIn = tempFile.getInputStream();\n//                try {\n//                    long tempFileSize = tempFile.getSize();\n//\n//                    org.jets3t.service.model.S3Object object = new org.jets3t.service.model.S3Object(getObjectKey(false));\n//                    object.setDataInputStream(tempIn);\n//                    object.setContentLength(tempFileSize);\n//\n//                    // Transfer to S3 and update local file attributes\n//                    atts.setAttributes(service.putObject(bucketName, object));\n//                    atts.setExists(true);\n//                    atts.updateExpirationDate();\n//                }\n//                catch(S3ServiceException e) {\n//                    throw getIOException(e);\n//                }\n//                finally {\n//                    try {\n//                        tempIn.close();\n//                    }\n//                    catch(IOException e) {\n//                        // Do not re-throw the exception to prevent exceptions caught in the catch block from being replaced\n//                    }\n//\n//                    try {\n//                        tempFile.delete();\n//                    }\n//                    catch(IOException e) {\n//                        // Do not re-throw the exception to prevent exceptions caught in the catch block from being replaced\n//                    }\n//                }\n//            }\n//        };\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public short getReplication() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_REPLICATION);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public long getBlocksize() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_BLOCKSIZE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public void changeReplication(short replication) throws IOException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_REPLICATION);\n    }\n\n    @Override\n    public void copyStream(InputStream in, boolean append, long length) throws FileTransferException {\n        if (append) {\n//            throw new UnsupportedFileOperationException(FileOperation.APPEND_FILE);\n            throw new FileTransferException(FileTransferException.READING_SOURCE);\n        }\n\n        // TODO: compute md5 ?\n\n        // If the length is known, we can upload the object directly without having to go through the tedious process\n        // of copying the stream to a temporary file.\n        if (length>=0) {\n            putObject(in, length);\n        } else {\n            // Copy the stream to a temporary file so that we can know the object's length, which has to be declared\n            // in the PUT request's headers (that is before the transfer is started).\n            final AbstractFile tempFile;\n            final OutputStream tempOut;\n            try {\n                tempFile = FileFactory.getTemporaryFile(false);\n                tempOut = tempFile.getOutputStream();\n            } catch (IOException e) {\n                throw new FileTransferException(FileTransferException.OPENING_DESTINATION);\n            }\n\n            try {\n                // Copy the stream to the temporary file\n                try {\n                    StreamUtils.copyStream(in, tempOut, IO_BUFFER_SIZE);\n                }\n                finally {\n                    // Close the stream even if copyStream() threw an IOException\n                    try {\n                        tempOut.close();\n                    } catch(IOException e) {\n                        // Do not re-throw the exception to prevent swallowing the exception thrown in the try block\n                    }\n                }\n\n                InputStream tempIn;\n                try {\n                    tempIn = tempFile.getInputStream();\n                } catch(IOException e) {\n                    throw new FileTransferException(FileTransferException.OPENING_SOURCE);\n                }\n\n                putObject(tempIn, tempFile.getSize());\n            } finally {\n                // Delete the temporary file, no matter what.\n                try {\n                    tempFile.delete();\n                } catch(IOException e) {\n                    // Do not re-throw the exception to prevent exceptions caught in the catch block from being replaced\n                }\n            }\n        }\n    }\n\n    ///////////////////\n    // Inner classes //\n    ///////////////////\n\n    /**\n     * Provides random read access to an S3 object by using GET Range requests with a start offset and no end.\n     * The connection is closed and a new one opened when seeking is required.\n     *\n     * <p>\n     * Note: At the time of this writing, a GET request on Amazon S3 costs the equivalent of 6666 bytes of data\n     * transferred ($0.01 per 10,000 GET requests, $0.15 per GB transferred). If the object is being read and a seek is\n     * requested to an offset that is less than 6666 bytes away from the current position going forward, the bytes\n     * separating the current position to the new one are skipped (read and discarded), instead of closing the current\n     * stream and opening a new one (which would cost 1 GET request). Doing so is cheaper (in $$$) and probably faster.\n     */\n    private class S3ObjectRandomAccessInputStream extends RandomAccessInputStream {\n\n        /** Length of the S3 object */\n        private final long length;\n\n        /** Current offset in the object stream */\n        private long offset;\n\n        /** Current object stream */\n        private InputStream in;\n\n        /** If the object is being read and a seek is requested to an offset that is less than this amount of bytes away\n         * from the current position going forward, the bytes separating the current position to the new one are\n         * skipped. */\n        private final static int SKIP_BYTE_SIZE = 6666;\n\n        protected S3ObjectRandomAccessInputStream() {\n            length = getSize();\n        }\n\n        /**\n         * Opens an input stream allowing to read the object and starting at the given offset. The current input stream\n         * (if any) is closed.\n         *\n         * @param offset position at which to start reading the object\n         * @throws IOException on error\n         */\n        private synchronized void openStream(long offset) throws IOException {\n            // Nothing to do if the requested offset is the current offset\n            if (in != null && this.offset == offset) {\n                return;\n            }\n\n            // If there is an open connection and the offset to reach is located between the current offset and\n            // SKIP_SIZE, move to the said offset by skipping the difference instead of closing the connection and\n            // opening a new one:\n            if (in != null && offset > this.offset && offset - this.offset < SKIP_BYTE_SIZE) {\n                byte[] skipBuf = BufferPool.getByteArray(SKIP_BYTE_SIZE);    // Use a constant buffer size to always reuse the same instance\n                try {\n                    StreamUtils.readFully(in, skipBuf, 0, (int)(offset-this.offset));\n                    this.offset = offset;\n                } finally {\n                    BufferPool.releaseByteArray(skipBuf);\n                }\n            }\n            // If not, close the current connection\n            else {\n                if (in != null) {\n                    try {\n                        in.close();\n                    }\n                    catch(IOException e) {\n                        // Report the error but don't throw the exception\n                        LOGGER.info(\"Failed to close connection\", e);\n                    }\n                }\n\n                try {\n                    this.in = service.getObject(bucketName, getObjectKey(false), null, null, null, null, offset, null)\n                        .getDataInputStream();\n                    this.offset = offset;\n                } catch(S3ServiceException e) {\n                    throw getIOException(e);\n                }\n            }\n        }\n\n\n        @Override\n        public synchronized int read(byte[] b, int off, int len) throws IOException {\n            if (in == null) {\n                openStream(0);\n            }\n\n            int nbRead = in.read(b, off, len);\n            if (nbRead > 0) {\n                offset += nbRead;\n            }\n\n            return nbRead;\n        }\n\n        @Override\n        public synchronized int read() throws IOException {\n            if (in == null) {\n                openStream(0);\n            }\n            int i = in.read();\n            if (i != -1) {\n                offset++;\n            }\n            return i;\n        }\n\n        public long getLength() {\n            return length;\n        }\n\n        public synchronized long getOffset() {\n            return offset;\n        }\n\n        public synchronized void seek(long offset) throws IOException {\n            openStream(offset);\n        }\n\n        @Override\n        public synchronized void close() throws IOException {\n            if (in != null) {\n                try {\n                    in.close();\n                    // Let the IOException be thrown\n                } finally {\n                    // Further attempts to close the stream will be no-ops\n                    in = null;\n                    offset = 0;\n                }\n            }\n        }\n    }\n\n//    /**\n//     * Reads an S3 object block by block. Each block is read by issuing a GET request with a specified Range.\n//     *\n//     * <p>Note: A GET request on Amazon S3 costs the equivalent of 6KB of data transferred. Setting the block size too\n//     * low will cause extra requests to be performed. Setting it too high will cause extra data to be transferred.\n//     */\n//    private class S3ObjectRandomAccessInputStream extends BlockRandomInputStream {\n//\n//        /** Amount of data returned by each 'GET Object' request */\n//        private final static int BLOCK_SIZE = 8192;\n//\n//        /** Length of the S3 object */\n//        private long length;\n//\n//        protected S3ObjectRandomAccessInputStream() {\n//            super(BLOCK_SIZE);\n//\n//            length = getSize();\n//        }\n//\n//\n//        ///////////////////////////////////////////\n//        // BlockRandomInputStream implementation //\n//        ///////////////////////////////////////////\n//\n//        @Override\n//        protected int readBlock(long fileOffset, byte[] block, int blockLen) throws IOException {\n//            try {\n//                InputStream in = service.getObject(bucketName, getObjectKey(false), null, null, null, null, fileOffset, fileOffset+BLOCK_SIZE)\n//                    .getDataInputStream();\n//\n//                // Read up to blockLen bytes\n//                try {\n//                    int totalRead = 0;\n//                    int read;\n//                    while(totalRead<blockLen) {\n//                        read = in.read(block, totalRead, blockLen-totalRead);\n//                        if(read==-1)\n//                            break;\n//\n//                        totalRead += read;\n//                    }\n//\n//                    return totalRead;\n//                }\n//                finally {\n//                    in.close();\n//                }\n//            }\n//            catch(S3ServiceException e) {\n//                throw getIOException(e);\n//            }\n//        }\n//\n//        public long getLength() throws IOException {\n//            return length;\n//        }\n//\n//        @Override\n//        public void close() throws IOException {\n//            // No-op, the underlying stream is already closed\n//        }\n//    }\n\n\n    /**\n     * S3ObjectFileAttributes provides getters and setters for S3 object attributes. By extending\n     * <code>SyncedFileAttributes</code>, this class caches attributes for a certain amount of time\n     * after which fresh values are retrieved from the server.\n     *\n     * @author Maxence Bernard\n     */\n    private class S3ObjectFileAttributes extends SyncedFileAttributes {\n\n        private final static int TTL = 60000;\n\n        private S3ObjectFileAttributes() throws AuthException {\n            super(TTL, false);      // no initial update\n\n            fetchAttributes();      // throws AuthException if no or bad credentials\n            updateExpirationDate(); // declare the attributes as 'fresh'\n        }\n\n        private S3ObjectFileAttributes(org.jets3t.service.model.S3Object object) {\n            super(TTL, false);      // no initial update\n\n            setAttributes(object);\n            setExists(true);\n\n            updateExpirationDate(); // declare the attributes as 'fresh'\n        }\n\n        private void setAttributes(org.jets3t.service.model.S3Object object) {\n            setDirectory(object.getKey().endsWith(\"/\"));\n            setSize(object.getContentLength());\n            setDate(object.getLastModifiedDate().getTime());\n            setPermissions(DEFAULT_PERMISSIONS);\n            // Note: owner is null for common prefix objects\n            S3Owner owner = object.getOwner();\n            setOwner(owner==null?null:owner.getDisplayName());\n        }\n\n        private void fetchAttributes() throws AuthException {\n            try {\n                setAttributes(service.getObjectDetails(bucketName, getObjectKey(), null, null, null, null));\n                // Object does not exist on the server\n                setExists(true);\n            } catch(S3ServiceException e) {\n                // Object does not exist on the server, or could not be retrieved\n                setExists(false);\n\n                setDirectory(false);\n                setSize(0);\n                setDate(0);\n                setPermissions(FilePermissions.EMPTY_FILE_PERMISSIONS);\n                setOwner(null);\n\n                handleAuthException(e, fileURL);\n            }\n        }\n\n\n\n        @Override\n        public void updateAttributes() {\n            try {\n                fetchAttributes();\n            } catch(Exception e) {        // AuthException\n                LOGGER.info(\"Failed to update attributes\", e);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/s3/S3ProtocolProvider.java",
    "content": "package com.mucommander.commons.file.impl.s3;\n\nimport com.mucommander.commons.file.*;\nimport org.jets3t.service.Jets3tProperties;\nimport org.jets3t.service.S3Service;\nimport org.jets3t.service.S3ServiceException;\nimport org.jets3t.service.impl.rest.httpclient.RestS3Service;\nimport org.jets3t.service.security.AWSCredentials;\n\nimport java.io.IOException;\nimport java.util.StringTokenizer;\n\n/**\n * A file protocol provider for the Amazon S3 protocol.\n *\n * @author Maxence Bernard\n */\npublic class S3ProtocolProvider implements ProtocolProvider {\n    public S3ProtocolProvider() {\n    }\n\n    public AbstractFile getFile(FileURL url, Object... instantiationParams) throws IOException {\n        Credentials credentials = url.getCredentials();\n        if (credentials == null || credentials.getLogin().isEmpty() || credentials.getPassword().isEmpty()) {\n            throw new AuthException(url);\n        }\n\n        S3Service service;\n        String bucketName;\n\n        if (instantiationParams.length == 0) {\n            try {\n                service = new RestS3Service(new AWSCredentials(credentials.getLogin(), credentials.getPassword()));\n                Jets3tProperties props = new Jets3tProperties();\n                props.setProperty(\"s3service.s3-endpoint\", url.getHost());\n            }\n            catch(S3ServiceException e) {\n                throw S3File.getIOException(e, url);\n            }\n        } else {\n            service = (S3Service)instantiationParams[0];\n        }\n\n        String path = url.getPath();\n\n        // Root resource\n        if ((\"/\").equals(path)) {\n            return new S3Root(url, service);\n        }\n\n        // Fetch the bucket name from the URL\n        StringTokenizer st = new StringTokenizer(path, \"/\");\n        bucketName = st.nextToken();\n\n        // Object resource\n        if (st.hasMoreTokens()) {\n            if (instantiationParams.length == 2) {\n                return new S3Object(url, service, bucketName, (org.jets3t.service.model.S3Object)instantiationParams[1]);\n            }\n\n            return new S3Object(url, service, bucketName);\n        }\n\n        // Bucket resource\n        if (instantiationParams.length == 2) {\n            return new S3Bucket(url, service, (org.jets3t.service.model.S3Bucket)instantiationParams[1]);\n        }\n\n        return new S3Bucket(url, service, bucketName);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/s3/S3Root.java",
    "content": "package com.mucommander.commons.file.impl.s3;\n\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.io.RandomAccessInputStream;\nimport org.jets3t.service.S3Service;\nimport org.jets3t.service.S3ServiceException;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n/**\n * <code>S3Root</code> represents the Amazon S3 root resource, also known as 'service'.\n *\n * @author Maxence Bernard\n */\npublic class S3Root extends S3File {\n\n    private final SimpleFileAttributes atts;\n\n    /** Default permissions for the S3 root */\n    private final static FilePermissions DEFAULT_PERMISSIONS = new SimpleFilePermissions(448);   // rwx------\n\n    protected S3Root(FileURL url, S3Service service) {\n        super(url, service);\n\n        atts = new SimpleFileAttributes();\n        atts.setPath(\"/\");\n        atts.setExists(true);\n        atts.setDate(0);\n        atts.setSize(0);\n        atts.setDirectory(true);\n        atts.setPermissions(DEFAULT_PERMISSIONS);\n        atts.setOwner(null);\n        atts.setGroup(null);\n    }\n\n\n    ///////////////////////////\n    // S3File implementation //\n    ///////////////////////////\n\n    @Override\n    public FileAttributes getFileAttributes() {\n        return atts;\n    }\n\n\n    /////////////////////////////////\n    // ProtocolFile implementation //\n    /////////////////////////////////\n\n    @Override\n    public String getOwner() {\n        return null;\n    }\n\n    @Override\n    public boolean canGetOwner() {\n        return false;\n    }\n\n    @Override\n    public AbstractFile[] ls() throws IOException {\n        try {\n            org.jets3t.service.model.S3Bucket[] buckets = service.listAllBuckets();\n            int nbBuckets = buckets.length;\n\n            AbstractFile[] bucketFiles = new AbstractFile[nbBuckets];\n            FileURL bucketURL;\n            for(int i=0; i<nbBuckets; i++) {\n                bucketURL = (FileURL)fileURL.clone();\n                bucketURL.setPath(\"/\"+buckets[i].getName());\n\n                bucketFiles[i] = FileFactory.getFile(bucketURL, null, service, buckets[i]);\n            }\n\n            return bucketFiles;\n        }\n        catch(S3ServiceException e) {\n            throw getIOException(e);\n        }\n    }\n\n    // Unsupported operations\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException}.\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void mkdir() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.CREATE_DIRECTORY);\n    }\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException}.\n     */\n    @Override\n    @UnsupportedFileOperation\n    public InputStream getInputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.READ_FILE);\n    }\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException}.\n     */\n    @Override\n    @UnsupportedFileOperation\n    public OutputStream getOutputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.WRITE_FILE);\n    }\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException}.\n     */\n    @Override\n    @UnsupportedFileOperation\n    public RandomAccessInputStream getRandomAccessInputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.RANDOM_READ_FILE);\n    }\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException}.\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void delete() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.DELETE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public void copyRemotelyTo(AbstractFile destFile) throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.COPY_REMOTELY);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public void renameTo(AbstractFile destFile) throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.RENAME);\n    }\n    @Override\n    @UnsupportedFileOperation\n    public short getReplication() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_REPLICATION);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public long getBlocksize() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_BLOCKSIZE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public void changeReplication(short replication) throws IOException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_REPLICATION);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/SevenZipArchiveFile.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.impl.sevenzip;\n\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.io.RandomAccessFile;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.impl.SevenZipJBindingROArchiveFile;\n\nimport net.sf.sevenzipjbinding.ArchiveFormat;\nimport net.sf.sevenzipjbinding.ExtractAskMode;\nimport net.sf.sevenzipjbinding.ExtractOperationResult;\nimport net.sf.sevenzipjbinding.IArchiveExtractCallback;\nimport net.sf.sevenzipjbinding.IArchiveOpenVolumeCallback;\nimport net.sf.sevenzipjbinding.IInArchive;\nimport net.sf.sevenzipjbinding.IInStream;\nimport net.sf.sevenzipjbinding.ISequentialOutStream;\nimport net.sf.sevenzipjbinding.PropID;\nimport net.sf.sevenzipjbinding.SevenZipException;\nimport net.sf.sevenzipjbinding.impl.RandomAccessFileInStream;\n\n/**\n * Created on 23/05/14.\n * @author Oleg Trifonov\n */\npublic class SevenZipArchiveFile extends SevenZipJBindingROArchiveFile {\n\n\n    public SevenZipArchiveFile(AbstractFile file, ArchiveFormat sevenZipJBindingFormat, byte[] formatSignature) {\n        super(file, sevenZipJBindingFormat, formatSignature);\n    }\n\n\n    private static class ArchiveOpenVolumeCallback implements IArchiveOpenVolumeCallback {\n\n        /**\n         * Cache for opened file streams\n         */\n        private final Map<String, RandomAccessFile> openedRandomAccessFileList = new HashMap<>();\n\n        /**\n         * This method doesn't needed, if using with VolumedArchiveInStream\n         * and pass the name of the first archive in constructor.\n         * (Use two argument constructor)\n         *\n         * @see IArchiveOpenVolumeCallback#getProperty(PropID)\n         */\n        public Object getProperty(PropID propID) {\n            return null;\n        }\n\n        /**\n         *\n         * The name of the required volume will be calculated out of the\n         * name of the first volume and volume index. If you need\n         * need volume index (integer) you will have to parse filename\n         * and extract index.\n         *\n         * <pre>\n         * int index = filename.substring(filename.length() - 3,\n         *         filename.length());\n         * </pre>\n         *\n         */\n        public IInStream getStream(String filename) {\n            try {\n                // We use caching of opened streams, so check cache first\n                RandomAccessFile randomAccessFile = openedRandomAccessFileList.get(filename);\n                if (randomAccessFile != null) { // Cache hit.\n                    // Move the file pointer back to the beginning\n                    // in order to emulating new stream\n                    randomAccessFile.seek(0);\n                    return new RandomAccessFileInStream(randomAccessFile);\n                }\n\n                // Nothing useful in cache. Open required volume.\n                randomAccessFile = new RandomAccessFile(filename, \"r\");\n\n                // Put new stream in the cache\n                openedRandomAccessFileList.put(filename, randomAccessFile);\n\n                return new RandomAccessFileInStream(randomAccessFile);\n            } catch (FileNotFoundException fileNotFoundException) {\n                // Required volume doesn't exist. This happens if the volume:\n                // 1. never exists. 7-Zip doesn't know how many volumes should\n                //    exist, so it have to try each volume.\n                // 2. should be there, but doesn't. This is an error case.\n\n                // Since normal and error cases are possible,\n                // we can't throw an error message\n                return null; // We return always null in this case\n            } catch (Exception e) {\n                throw new RuntimeException(e);\n            }\n        }\n\n        /**\n         * Close all opened streams\n         */\n        void close() throws IOException {\n            for (RandomAccessFile file : openedRandomAccessFileList.values()) {\n                file.close();\n            }\n        }\n    }\n\n\n\n    public static class ExtractCallback implements IArchiveExtractCallback {\n        private int hash = 0;\n        private long size = 0;\n        private int index;\n        private boolean skipExtraction;\n        private IInArchive inArchive;\n        private OutputStream os;\n\n        public ExtractCallback(IInArchive inArchive, OutputStream os) {\n            this.inArchive = inArchive;\n            this.os = os;\n        }\n\n        public ISequentialOutStream getStream(int index, ExtractAskMode extractAskMode) throws SevenZipException {\n            this.index = index;\n            skipExtraction = (Boolean) inArchive.getProperty(index, PropID.IS_FOLDER);\n            if (skipExtraction || extractAskMode != ExtractAskMode.EXTRACT) {\n                return null;\n            }\n            return data -> {\n                hash ^= Arrays.hashCode(data);\n                size += data.length;\n                try {\n                    os.write(data);\n                } catch (IOException e) {\n                    throw new SevenZipException(e);\n                }\n                return data.length; // Return amount of proceed data\n            };\n        }\n\n        public void prepareOperation(ExtractAskMode extractAskMode) {\n//System.out.println(\"prepare  \" + index);\n        }\n\n        public void setOperationResult(ExtractOperationResult extractOperationResult) {\n            if (skipExtraction) {\n                return;\n            }\n            if (extractOperationResult != ExtractOperationResult.OK) {\n                System.err.println(\"Extraction error  = \" + extractOperationResult);\n            } else {\n//System.out.println(String.format(\"%9X | %10s | %s\", hash, size, inArchive.getProperty(index, PropID.PATH)));\n                hash = 0;\n                size = 0;\n            }\n        }\n\n        public void setCompleted(long completeValue) {\n//System.out.println(\"completed  \" + completeValue);\n        }\n\n        public void setTotal(long total) {\n//System.out.println(\"total  \" + index + \"   \" + total);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/SevenZipFormatProvider.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip;\n\nimport java.io.IOException;\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.ArchiveFormatProvider;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\nimport net.sf.sevenzipjbinding.ArchiveFormat;\n\n/**\n * This class is the provider for the '7z' archive format implemented by {@link SevenZipArchiveFile}.\n *\n * @author Arik Hadas\n */\npublic class SevenZipFormatProvider implements ArchiveFormatProvider {\n\n    private static final String[] EXTENSIONS = {\".7z\", \".cb7\"};\n\t/**\n     * Static instance of the filename filter that matches archive filenames\n     * */\n    private final static ExtensionFilenameFilter filenameFilter = new ExtensionFilenameFilter(EXTENSIONS);\n\n    private static final byte[] SIGNATURE = { 0x37, 0x7A, (byte) 0xBC, (byte) 0xAF, 0x27, 0x1C };\n\n    @Override\n    public AbstractArchiveFile getFile(AbstractFile file) throws IOException {\n        return new SevenZipArchiveFile(file, ArchiveFormat.SEVEN_ZIP, SIGNATURE);\n    }\n\n    public FilenameFilter getFilenameFilter() {\n        return filenameFilter;\n    }\n\n    @Override\n    public String[] getFileExtensions() {\n        return EXTENSIONS;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/SignatureCheckedRandomAccessFile.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.impl.sevenzip;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileOperation;\nimport com.mucommander.commons.file.UnsupportedFileOperationException;\nimport com.mucommander.commons.io.RandomAccessInputStream;\nimport com.mucommander.commons.io.StreamUtils;\nimport net.sf.sevenzipjbinding.IInStream;\nimport net.sf.sevenzipjbinding.ISequentialInStream;\nimport net.sf.sevenzipjbinding.SevenZipException;\n\nimport org.apache.commons.io.IOUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport ru.trolsoft.utils.StrUtils;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.PushbackInputStream;\n\n/**\n * @author Oleg Trifonov\n */\npublic class SignatureCheckedRandomAccessFile implements IInStream, ISequentialInStream {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(SignatureCheckedRandomAccessFile.class);\n\n    private final AbstractFile file;\n\n    private InputStream stream;\n\n    private long position;\n\n    public SignatureCheckedRandomAccessFile(AbstractFile file, byte[] signature) throws UnsupportedFileOperationException {\n        super();\n        this.position = 0;\n        this.file = file;\n        try {\n            this.stream = openStreamAndCheckSignature(file, signature);\n        } catch (IOException e) {\n            e.printStackTrace();\n            LOGGER.trace(\"Error\", e);\n            throw new UnsupportedFileOperationException(FileOperation.READ_FILE);\n        }\n    }\n\n\n    @Override\n    public synchronized long seek(long offset, int seekOrigin) throws SevenZipException {\n        try {\n            if (file.isFileOperationSupported(FileOperation.RANDOM_READ_FILE)) {\n                seekOnRandomAccessFile(offset, seekOrigin);\n            } else {\n                seekOnSequentialFile(offset, seekOrigin);\n            }\n        } catch (IOException e) {\n            throw new SevenZipException(e);\n        }\n        return position;\n    }\n\n    private void seekOnRandomAccessFile(long offset, int seekOrigin) throws IOException {\n        RandomAccessInputStream randomAccessInputStream = (RandomAccessInputStream) stream;\n        switch (seekOrigin) {\n        case SEEK_SET:\n            position = offset;\n            break;\n        case SEEK_CUR:\n            position += offset;\n            break;\n        case SEEK_END:\n            position = randomAccessInputStream.getLength() + offset;\n            break;\n        }\n        randomAccessInputStream.seek(position);\n    }\n\n    /**\n     * @param offset\n     * @param seekOrigin\n     * @throws IOException\n     */\n    private void seekOnSequentialFile(long offset, int seekOrigin) throws IOException {\n        switch (seekOrigin) {\n        case SEEK_SET:\n            if (position != offset) {\n                stream.close();\n                stream = file.getInputStream();\n                skip(offset);\n                position = offset;\n            }\n            break;\n        case SEEK_CUR:\n            skip(offset);\n            position += offset;\n            break;\n        case SEEK_END:\n            long size = file.getSize();\n            if (size == -1) {\n                throw new IOException(\"can't seek from file end without knowing it's size\");\n            }\n            long newPosition = size + (offset > 0 ? offset : 0);\n            if (position != newPosition) {\n                position = newPosition;\n                stream.close();\n                stream = file.getInputStream();\n                stream.skip(position);\n            }\n            break;\n        }\n    }\n\n    /**\n     * @param skip\n     * @throws IOException\n     */\n    private void skip(long skip) throws IOException {\n        if (skip <= 0) {\n            return;\n        }\n        long skipped = stream.skip(skip);\n        if (skipped < 0) {\n            throw new IOException(\"non reasonable number of bytes skipped\");\n        }\n        position += skipped;\n        while (skipped < skip) {\n            int skipNow = (int) Long.min(skip - skipped, 1024);\n            byte[] skipBuffer = new byte[skipNow];\n            int read = stream.read(skipBuffer, 0, skipBuffer.length);\n            if (read == -1) {\n                break;\n            } else {\n                position += read;\n                skipped += read;\n            }\n        }\n    }\n\n    @Override\n    public synchronized int read(byte[] bytes) throws SevenZipException {\n        if (bytes.length == 0) {\n            return 0;\n        }\n        try {\n            int read = stream.read(bytes);\n            if (read != -1) {\n                position += read;\n                return read;\n            }\n            return 0;\n        } catch (IOException e) {\n            throw new SevenZipException(e);\n        }\n    }\n\n    private InputStream openStreamAndCheckSignature(AbstractFile file, byte[] signature) throws IOException {\n        byte[] buf = new byte[signature.length];\n\n        InputStream is = null;\n\n        int read = 0;\n        try {\n            if (file.isFileOperationSupported(FileOperation.RANDOM_READ_FILE)) {\n                RandomAccessInputStream raiStream = file.getRandomAccessInputStream();\n                is = raiStream;\n                if (buf.length > 0) {\n                    raiStream.seek(0);\n                    read = StreamUtils.readUpTo(raiStream, buf);\n                    raiStream.seek(0);\n                }\n            } else {\n                PushbackInputStream pushbackInputStream = null;\n                if (buf.length > 0) {\n                    pushbackInputStream = file.getPushBackInputStream(buf.length);\n                    is = pushbackInputStream;\n                    read = StreamUtils.readUpTo(pushbackInputStream, buf);\n                } else {\n                    is = file.getInputStream();\n                }\n                // TODO sometimes reading from pushbackInputStream returns 0\n                if (read <= 0 && file.getSize() > 0) {\n                    return file.getInputStream();\n                }\n                pushbackInputStream.unread(buf, 0, read);\n            }\n            if (signature != null && !checkSignature(buf, signature)) {\n                throw new IOException(\"Wrong file signature was \" + StrUtils.bytesToHexStr(buf, 0, read)\n                + \" but should be \" + StrUtils.bytesToHexStr(signature, 0, signature.length));\n            }\n        } catch (IOException e) {\n            IOUtils.closeQuietly(is);\n            throw e;\n        }\n\n        return is;\n    }\n\n    private static boolean checkSignature(byte[] data, byte[] signature) {\n        for (int i = 0; i < signature.length; i++) {\n            if (data[i] != signature[i]) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    @Override\n    public synchronized void close() throws IOException {\n        if (stream != null) {\n            stream.close();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/multivolume/InArchiveWrapper.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2025 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.impl.sevenzip.multivolume;\n\nimport java.io.Closeable;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.sf.sevenzipjbinding.ArchiveFormat;\nimport net.sf.sevenzipjbinding.ExtractOperationResult;\nimport net.sf.sevenzipjbinding.IArchiveExtractCallback;\nimport net.sf.sevenzipjbinding.IInArchive;\nimport net.sf.sevenzipjbinding.IOutItemAllFormats;\nimport net.sf.sevenzipjbinding.IOutUpdateArchive;\nimport net.sf.sevenzipjbinding.IOutUpdateArchive7z;\nimport net.sf.sevenzipjbinding.IOutUpdateArchiveBZip2;\nimport net.sf.sevenzipjbinding.IOutUpdateArchiveGZip;\nimport net.sf.sevenzipjbinding.IOutUpdateArchiveTar;\nimport net.sf.sevenzipjbinding.IOutUpdateArchiveZip;\nimport net.sf.sevenzipjbinding.ISequentialOutStream;\nimport net.sf.sevenzipjbinding.PropID;\nimport net.sf.sevenzipjbinding.PropertyInfo;\nimport net.sf.sevenzipjbinding.SevenZipException;\nimport net.sf.sevenzipjbinding.simple.ISimpleInArchive;\n\npublic class InArchiveWrapper implements IInArchive {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(InArchiveWrapper.class);\n\n    private final IInArchive archive;\n\n    private final Closeable[] closeables;\n\n    public InArchiveWrapper(IInArchive archive, Closeable... closeables) {\n        this.archive = archive;\n        this.closeables = closeables;\n    }\n\n    @Override\n    public int getNumberOfItems() throws SevenZipException {\n        return getMainArchive().getNumberOfItems();\n    }\n\n    @Override\n    public Object getProperty(int index, PropID propID) throws SevenZipException {\n        return getMainArchive().getProperty(index, propID);\n    }\n\n    @Override\n    public String getStringProperty(int index, PropID propID) throws SevenZipException {\n        return getMainArchive().getStringProperty(index, propID);\n    }\n\n    @Override\n    public void extract(int[] indices, boolean testMode, IArchiveExtractCallback extractCallback) throws SevenZipException {\n        getMainArchive().extract(indices, testMode, extractCallback);\n    }\n\n    @Override\n    public ExtractOperationResult extractSlow(int index, ISequentialOutStream outStream) throws SevenZipException {\n        return getMainArchive().extractSlow(index, outStream);\n    }\n\n    @Override\n    public ExtractOperationResult extractSlow(int index, ISequentialOutStream outStream, String password) throws SevenZipException {\n        return getMainArchive().extractSlow(index, outStream, password);\n    }\n\n    @Override\n    public Object getArchiveProperty(PropID propID) throws SevenZipException {\n        return getMainArchive().getArchiveProperty(propID);\n    }\n\n    @Override\n    public String getStringArchiveProperty(PropID propID) throws SevenZipException {\n        return getMainArchive().getStringArchiveProperty(propID);\n    }\n\n    @Override\n    public int getNumberOfProperties() throws SevenZipException {\n        return getMainArchive().getNumberOfProperties();\n    }\n\n    @Override\n    public PropertyInfo getPropertyInfo(int index) throws SevenZipException {\n        return getMainArchive().getPropertyInfo(index);\n    }\n\n    @Override\n    public int getNumberOfArchiveProperties() throws SevenZipException {\n        return getMainArchive().getNumberOfArchiveProperties();\n    }\n\n    @Override\n    public PropertyInfo getArchivePropertyInfo(int index) throws SevenZipException {\n        return getMainArchive().getArchivePropertyInfo(index);\n    }\n\n    @Override\n    public ISimpleInArchive getSimpleInterface() {\n        return getMainArchive().getSimpleInterface();\n    }\n\n    @Override\n    public ArchiveFormat getArchiveFormat() {\n        return getMainArchive().getArchiveFormat();\n    }\n\n    @Override\n    public IOutUpdateArchive<IOutItemAllFormats> getConnectedOutArchive() throws SevenZipException {\n        return getMainArchive().getConnectedOutArchive();\n    }\n\n    @Override\n    public IOutUpdateArchive7z getConnectedOutArchive7z() throws SevenZipException {\n        return getMainArchive().getConnectedOutArchive7z();\n    }\n\n    @Override\n    public IOutUpdateArchiveZip getConnectedOutArchiveZip() throws SevenZipException {\n        return getMainArchive().getConnectedOutArchiveZip();\n    }\n\n    @Override\n    public IOutUpdateArchiveTar getConnectedOutArchiveTar() throws SevenZipException {\n        return getMainArchive().getConnectedOutArchiveTar();\n    }\n\n    @Override\n    public IOutUpdateArchiveGZip getConnectedOutArchiveGZip() throws SevenZipException {\n        return getMainArchive().getConnectedOutArchiveGZip();\n    }\n\n    @Override\n    public IOutUpdateArchiveBZip2 getConnectedOutArchiveBZip2() throws SevenZipException {\n        return getMainArchive().getConnectedOutArchiveBZip2();\n    }\n\n    @Override\n    public void close() throws SevenZipException {\n        getMainArchive().close();\n        if (closeables != null) {\n            for (Closeable c : closeables) {\n                try {\n                    c.close();\n                } catch (Exception e) {\n                    LOGGER.warn(\"Error closing: {}\", c, e);\n                }\n            }\n        }\n\n    }\n\n    private IInArchive getMainArchive() {\n        return archive;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/multivolume/SevenZipMultiVolumeCallbackHandler.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2025 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.file.impl.sevenzip.multivolume;\n\nimport java.io.Closeable;\nimport java.io.FileNotFoundException;\nimport java.io.RandomAccessFile;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport com.mucommander.commons.file.impl.sevenzip.SignatureCheckedRandomAccessFile;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\n\nimport net.sf.sevenzipjbinding.IArchiveOpenVolumeCallback;\nimport net.sf.sevenzipjbinding.ICryptoGetTextPassword;\nimport net.sf.sevenzipjbinding.IInStream;\nimport net.sf.sevenzipjbinding.ISeekableStream;\nimport net.sf.sevenzipjbinding.PropID;\nimport net.sf.sevenzipjbinding.SevenZipException;\nimport net.sf.sevenzipjbinding.impl.RandomAccessFileInStream;\n\npublic class SevenZipMultiVolumeCallbackHandler implements IArchiveOpenVolumeCallback, ICryptoGetTextPassword, Closeable {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(SevenZipMultiVolumeCallbackHandler.class);\n\n    private final Map<String, IInStream> fileCache = new HashMap<>();\n\n    private final AbstractFile firstFile;\n\n    private final byte[] signature;\n\n    private final String password;\n\n    private boolean firstVolume = true;\n\n    public SevenZipMultiVolumeCallbackHandler(byte[] signature, AbstractFile firstFile, String password) {\n        this.signature = signature;\n        this.firstFile = firstFile;\n        this.password = password;\n    }\n\n    @Override\n    public Object getProperty(PropID propID) {\n        switch (propID) {\n            case NAME:\n                return firstFile.getAbsolutePath();\n        }\n        return null;\n    }\n\n    @Override\n    public IInStream getStream(String filename) {\n        try {\n            IInStream stream = fileCache.get(filename);\n            if (stream != null) {\n                stream.seek(0, ISeekableStream.SEEK_SET);\n            } else {\n                if (firstVolume) {\n                    // Only first file starts with magic number\n                    AbstractFile abstractFile = FileFactory.getFile(filename);\n                    stream = new SignatureCheckedRandomAccessFile(abstractFile, signature);\n                    firstVolume = false;\n                } else {\n                    stream = new RandomAccessFileInStream(new RandomAccessFile(filename, \"r\"));\n                }\n                fileCache.put(filename, stream);\n            }\n            return stream;\n        } catch (FileNotFoundException e) {\n            // There is no way to know ahead of time if we reached the last file,\n            // So it is safe to ignore this Exception\n            LOGGER.debug(\"Multi volume 7z file not found - This is expected after reading the final volume [filename = {}]\", filename, e);\n            return null;\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    @Override\n    public void close() {\n        for (IInStream f : fileCache.values()) {\n            try {\n                f.close();\n            } catch (Exception e) {\n                LOGGER.error(\"Error closing IInStream\", e);\n            }\n        }\n    }\n\n    @Override\n    public String cryptoGetTextPassword() throws SevenZipException {\n        if (password == null) {\n            throw new SevenZipException(\"No password was provided for opening protected archive.\");\n        }\n        return password;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/multivolume/SevenZipRarMultiVolumeCallbackHandler.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2025 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.impl.sevenzip.multivolume;\n\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport com.mucommander.commons.file.impl.sevenzip.SignatureCheckedRandomAccessFile;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\n\nimport net.sf.sevenzipjbinding.IArchiveOpenCallback;\nimport net.sf.sevenzipjbinding.IArchiveOpenVolumeCallback;\nimport net.sf.sevenzipjbinding.ICryptoGetTextPassword;\nimport net.sf.sevenzipjbinding.IInStream;\nimport net.sf.sevenzipjbinding.ISeekableStream;\nimport net.sf.sevenzipjbinding.PropID;\nimport net.sf.sevenzipjbinding.SevenZipException;\n\npublic class SevenZipRarMultiVolumeCallbackHandler implements\n        IArchiveOpenVolumeCallback, IArchiveOpenCallback, ICryptoGetTextPassword, Closeable {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(SevenZipRarMultiVolumeCallbackHandler.class);\n\n    private Map<String, SignatureCheckedRandomAccessFile> fileCache = new HashMap<>();\n\n    private String lastFileName;\n\n    private final byte[] signature;\n\n    private final String password;\n\n    public SevenZipRarMultiVolumeCallbackHandler(byte[] signature, String password) {\n        this.signature = signature;\n        this.password = password;\n    }\n\n    @Override\n    public void setTotal(Long files, Long bytes) throws SevenZipException {\n        // NO-OP\n    }\n\n    @Override\n    public void setCompleted(Long files, Long bytes) throws SevenZipException {\n        // NO-OP\n    }\n\n    @Override\n    public Object getProperty(PropID propID) throws SevenZipException {\n        switch (propID) {\n            case NAME:\n                return lastFileName;\n        }\n        return null;\n    }\n\n    @Override\n    public IInStream getStream(String filename) throws SevenZipException {\n        try {\n            SignatureCheckedRandomAccessFile stream = fileCache.get(filename);\n            if (stream != null) {\n                stream.seek(0, ISeekableStream.SEEK_SET);\n            } else {\n                AbstractFile abstractFile = FileFactory.getFile(filename);\n                stream = new SignatureCheckedRandomAccessFile(abstractFile, signature);\n                fileCache.put(filename, stream);\n            }\n\n            lastFileName = filename;\n            return stream;\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    @Override\n    public String cryptoGetTextPassword() throws SevenZipException {\n        if (password == null) {\n            throw new SevenZipException(\"No password was provided for opening protected archive.\");\n        }\n        return password;\n    }\n\n    @Override\n    public void close() throws IOException {\n        for (SignatureCheckedRandomAccessFile f : fileCache.values()) {\n            try {\n                f.close();\n            } catch (Exception e) {\n                LOGGER.error(\"Error closing SignatureCheckedRandomAccessFile\", e);\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/Common/BoolVector.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.Common;\n\npublic class BoolVector {\n    \n    protected boolean[] data = new boolean[10];\n    int capacityIncr = 10;\n    int elt = 0;\n    \n    public BoolVector() {\n    }\n    \n    public int size() {\n        return elt;\n    }\n    \n    private void ensureCapacity(int minCapacity) {\n        int oldCapacity = data.length;\n        if (minCapacity > oldCapacity) {\n            boolean [] oldData = data;\n            int newCapacity = oldCapacity + capacityIncr;\n            if (newCapacity < minCapacity) {\n                newCapacity = minCapacity;\n            }\n            data = new boolean[newCapacity];\n            System.arraycopy(oldData, 0, data, 0, elt);\n        }\n    }\n    \n    public boolean get(int index) {\n        if (index >= elt)\n            throw new ArrayIndexOutOfBoundsException(index);\n        \n        return data[index];\n    }\n    \n    public void Reserve(int s) {\n        ensureCapacity(s);\n    }\n    \n    public void add(boolean b) {\n        ensureCapacity(elt + 1);\n        data[elt++] = b;\n    }\n    \n    public void clear() {\n        elt = 0;\n    }\n    \n    public boolean isEmpty() {\n        return elt == 0;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/Common/ByteBuffer.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.Common;\n\npublic class ByteBuffer {\n    int _capacity;\n    byte [] _items;\n    \n    public ByteBuffer() {\n        _capacity = 0;\n        _items = null;\n    }\n    \n    public byte [] data() { return _items; }\n    \n    public int GetCapacity() { return  _capacity; }\n    \n    public void SetCapacity(int newCapacity) {\n        if (newCapacity == _capacity)\n            return;\n        \n        byte [] newBuffer;\n        if (newCapacity > 0) {\n            newBuffer = new byte[newCapacity];\n            if(_capacity > 0) {\n                int len = _capacity;\n                if (newCapacity < len) len = newCapacity;\n                \n                System.arraycopy(_items,0,newBuffer,0,len); // for (int i = 0 ; i < len ; i++) newBuffer[i] = _items[i];\n            }\n        } else\n            newBuffer = null;\n        \n        // delete []_items;\n        _items = newBuffer;\n        _capacity = newCapacity;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/Common/CRC.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.Common;\r\n\r\npublic class CRC {\r\n    static public int[] Table = new int[256];\r\n    \r\n    static {\r\n        for (int i = 0; i < 256; i++) {\r\n            int r = i;\r\n            for (int j = 0; j < 8; j++)\r\n                if ((r & 1) != 0)\r\n                    r = (r >>> 1) ^ 0xEDB88320;\r\n                else\r\n                    r >>>= 1;\r\n            Table[i] = r;\r\n        }\r\n    }\r\n    \r\n    int _value = -1;\r\n    \r\n    public void Init() {\r\n        _value = -1;\r\n    }\r\n    \r\n    public void updateByte(int b) {\r\n        _value = Table[(_value ^ b) & 0xFF] ^ (_value >>> 8);\r\n    }\r\n    \r\n    public void updateUInt32(int v) {\r\n        for (int i = 0; i < 4; i++)\r\n            updateByte((v >> (8 * i)) & 0xFF);\r\n    }\r\n    \r\n    public void updateUInt64(long v) {\r\n        for (int i = 0; i < 8; i++)\r\n            updateByte((int) ((v >> (8 * i))) & 0xFF);\r\n    }\r\n    \r\n    public int getDigest() {\r\n        return ~_value;\r\n    }\r\n    \r\n    public void Update(byte[] data, int size) {\r\n        for (int i = 0; i < size; i++)\r\n            _value = Table[(_value ^ data[i]) & 0xFF] ^ (_value >>> 8);\r\n    }\r\n    \r\n    public void Update(byte[] data) {\r\n        for (byte aData : data) _value = Table[(_value ^ aData) & 0xFF] ^ (_value >>> 8);\r\n    }\r\n    \r\n    public void Update(byte[] data, int offset, int size) {\r\n        for (int i = 0; i < size; i++)\r\n            _value = Table[(_value ^ data[offset + i]) & 0xFF] ^ (_value >>> 8);\r\n    }\r\n    \r\n    public static int calculateDigest(byte[] data, int size) {\r\n        CRC crc = new CRC();\r\n        crc.Update(data, size);\r\n        return crc.getDigest();\r\n    }\r\n    \r\n    static public boolean verifyDigest(int digest, byte[] data, int size) {\r\n        return (calculateDigest(data, size) == digest);\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/Common/IntVector.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.Common;\n\n// TODO remove it!\npublic class IntVector {\n    protected int[] data = new int[10];\n    int capacityIncr = 10;\n    int elt = 0;\n    \n    public IntVector() {\n    }\n    \n    public int size() {\n        return elt;\n    }\n    \n    private void ensureCapacity(int minCapacity) {\n        int oldCapacity = data.length;\n        if (minCapacity > oldCapacity) {\n            int [] oldData = data;\n            int newCapacity = oldCapacity + capacityIncr;\n            if (newCapacity < minCapacity) {\n                newCapacity = minCapacity;\n            }\n            data = new int[newCapacity];\n            System.arraycopy(oldData, 0, data, 0, elt);\n        }\n    }\n    \n    public int get(int index) {\n        if (index >= elt)\n            throw new ArrayIndexOutOfBoundsException(index);\n        \n        return data[index];\n    }\n    \n    public void Reserve(int s) {\n        ensureCapacity(s);\n    }\n    \n    public void add(int b) {\n        ensureCapacity(elt + 1);\n        data[elt++] = b;\n    }\n    \n    public void clear() {\n        elt = 0;\n    }\n    \n    public boolean isEmpty() {\n        return elt == 0;\n    }\n\n    public int remove(int index) {\n        if (index >= elt)\n            throw new ArrayIndexOutOfBoundsException(index);\n        int oldValue = data[index];\n        \n        int numMoved = elt - index - 1;\n        if (numMoved > 0)\n            System.arraycopy(elt, index+1, elt, index,numMoved);\n        // TODO WTF ?!\n        \n        // data[--elt] = null; // Let gc do its work\n        \n        return oldValue;\n    }\n    \n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/Common/LimitedSequentialInStream.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.Common;\n\npublic class LimitedSequentialInStream extends java.io.InputStream {\n    java.io.InputStream _stream; // ISequentialInStream\n    long _size;\n    long _pos;\n    boolean _wasFinished;\n    \n    public LimitedSequentialInStream() {\n    }\n    \n    public void SetStream(java.io.InputStream stream) { // ISequentialInStream\n        _stream = stream;\n    }\n    \n    public void Init(long streamSize) {\n        _size = streamSize;\n        _pos = 0;\n        _wasFinished = false;\n    }\n    \n    public int read() throws java.io.IOException {\n        int ret = _stream.read();\n        if (ret == -1) _wasFinished = true;\n        return ret;\n    }\n    \n    public int read(byte [] data,int off, int size) throws java.io.IOException {\n        long sizeToRead2 = (_size - _pos);\n        if (size < sizeToRead2) sizeToRead2 = size;\n        \n        int sizeToRead = (int)sizeToRead2;\n        \n        if (sizeToRead > 0) {\n            int realProcessedSize = _stream.read(data, off, sizeToRead);\n            if (realProcessedSize == -1) {\n                _wasFinished = true;\n                return -1;\n            }\n            _pos += realProcessedSize;\n            return realProcessedSize;\n        }\n        \n        return -1; // EOF\n    }\n}\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/Common/LockedInStream.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.Common;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.IInStream;\n\npublic class LockedInStream {\n    IInStream _stream;\n    \n    public LockedInStream() {\n    }\n    \n    public void Init(IInStream stream) {\n        _stream = stream;\n    }\n    \n    /* really too slow, don't use !\n    public synchronized int read(long startPos) throws java.io.IOException\n    {\n        // NWindows::NSynchronization::CCriticalSectionLock lock(_criticalSection);\n        _stream.Seek(startPos, IInStream.STREAM_SEEK_SET);\n        return _stream.read();\n    }\n     */\n    \n    public synchronized int read(long startPos, byte  [] data, int size) throws java.io.IOException {\n        // NWindows::NSynchronization::CCriticalSectionLock lock(_criticalSection);\n        _stream.Seek(startPos, IInStream.STREAM_SEEK_SET);\n        return _stream.read(data,0, size);\n    }\n    \n    public synchronized int read(long startPos, byte  [] data, int off, int size) throws java.io.IOException {\n        // NWindows::NSynchronization::CCriticalSectionLock lock(_criticalSection);\n        _stream.Seek(startPos, IInStream.STREAM_SEEK_SET);\n        return _stream.read(data,off, size);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/Common/LockedSequentialInStreamImp.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.Common;\n\npublic class LockedSequentialInStreamImp extends java.io.InputStream {\n    LockedInStream _lockedInStream;\n    long _pos;\n    \n    public LockedSequentialInStreamImp() {\n    }\n    \n    public void Init(LockedInStream lockedInStream, long startPos) {\n        _lockedInStream = lockedInStream;\n        _pos = startPos;\n    }\n    \n    public int read() throws java.io.IOException {\n        throw new java.io.IOException(\"LockedSequentialInStreamImp : read() not implemented\");\n        /*\n        int ret = _lockedInStream.read(_pos);\n        if (ret == -1) return -1; // EOF\n         \n        _pos += 1;\n         \n        return ret;\n         */\n    }\n    \n    public int read(byte [] data, int off, int size) throws java.io.IOException {\n        int realProcessedSize = _lockedInStream.read(_pos, data,off, size);\n        if (realProcessedSize == -1) return -1; // EOF\n        \n        _pos += realProcessedSize;\n        \n        return realProcessedSize;\n    }\n    \n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/Common/LongVector.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.Common;\n\npublic class LongVector {\n    protected long[] data = new long[10];\n    int capacityIncr = 10;\n    int elt = 0;\n    \n    public LongVector() {\n    }\n    \n    public int size() {\n        return elt;\n    }\n    \n    private void ensureCapacity(int minCapacity) {\n        int oldCapacity = data.length;\n        if (minCapacity > oldCapacity) {\n            long [] oldData = data;\n            int newCapacity = oldCapacity + capacityIncr;\n            if (newCapacity < minCapacity) {\n                newCapacity = minCapacity;\n            }\n            data = new long[newCapacity];\n            System.arraycopy(oldData, 0, data, 0, elt);\n        }\n    }\n    \n    public long get(int index) {\n        if (index >= elt)\n            throw new ArrayIndexOutOfBoundsException(index);\n        \n        return data[index];\n    }\n    \n    public void Reserve(int s) {\n        ensureCapacity(s);\n    }\n    \n    public void add(long b) {\n        ensureCapacity(elt + 1);\n        data[elt++] = b;\n    }\n    \n    public void clear() {\n        elt = 0;\n    }\n    \n    public boolean isEmpty() {\n        return elt == 0;\n    }\n    \n    public long Back() {\n        if (elt < 1)\n            throw new ArrayIndexOutOfBoundsException(0);\n        \n        return data[elt-1];\n    }\n    \n    public long Front() {\n        if (elt < 1)\n            throw new ArrayIndexOutOfBoundsException(0);\n        \n        return data[0];\n    }\n    \n    public void DeleteBack() {\n        // Delete(_size - 1);\n        remove(elt-1);\n    }\n    \n    public long remove(int index) {\n        if (index >= elt)\n            throw new ArrayIndexOutOfBoundsException(index);\n        long oldValue = data[index];\n        \n        int numMoved = elt - index - 1;\n        if (numMoved > 0)\n            System.arraycopy(elt, index+1, elt, index,numMoved);\n        // TODO WTF ?!\n        \n        // data[--elt] = null; // Let gc do its work\n        \n        return oldValue;\n    }\n    \n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/Common/ObjectVector.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.Common;\n\npublic class ObjectVector<E> extends java.util.Vector<E>\n{\n    public ObjectVector() {\n        super();\n    }\n    \n    public void Reserve(int s) {\n        ensureCapacity(s);\n    }\n    \n    public E Back() {\n        return get(elementCount-1);\n    }\n    \n    public E Front() {\n        return get(0);\n    }\n    \n    public void DeleteBack() {\n        remove(elementCount-1);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/Common/RecordVector.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.Common;\n\npublic class RecordVector<E> extends java.util.Vector<E>\n{\n    public RecordVector() {\n        super();\n    }\n    \n    public void Reserve(int s) {\n        ensureCapacity(s);\n    }\n    \n    public E Back() {\n        return get(elementCount-1);\n    }\n    \n    public E Front() {\n        return get(0);\n    }\n    \n    public void DeleteBack() {\n        remove(elementCount-1);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/Common/BindInfo.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.Common;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.IntVector;\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.RecordVector;\n\npublic class BindInfo {\n    public RecordVector<CoderStreamsInfo> Coders = new RecordVector<>();\n    public RecordVector<BindPair> BindPairs = new RecordVector<>();\n    public IntVector InStreams = new IntVector();\n    public IntVector OutStreams = new IntVector();\n    \n    public void Clear() {\n        Coders.clear();\n        BindPairs.clear();\n//        InStreams.clear();\n//        OutStreams.clear();\n    }\n      \n    public int FindBinderForInStream(int inStream) // const\n    {\n        for (int i = 0; i < BindPairs.size(); i++)\n            if (BindPairs.get(i).InIndex == inStream)\n                return i;\n        return -1;\n    }\n    \n    public int FindBinderForOutStream(int outStream) // const\n    {\n        for (int i = 0; i < BindPairs.size(); i++)\n            if (BindPairs.get(i).OutIndex == outStream)\n                return i;\n        return -1;\n    }\n    \n    public int GetCoderInStreamIndex(int coderIndex) // const\n    {\n        int streamIndex = 0;\n        for (int i = 0; i < coderIndex; i++)\n            streamIndex += Coders.get(i).NumInStreams;\n        return streamIndex;\n    }\n    \n    public int GetCoderOutStreamIndex(int coderIndex) // const\n    {\n        int streamIndex = 0;\n        for (int i = 0; i < coderIndex; i++)\n            streamIndex += Coders.get(i).NumOutStreams;\n        return streamIndex;\n    }\n    \n    public void FindInStream(int streamIndex,\n            int [] coderIndex, // UInt32 &coderIndex\n            int [] coderStreamIndex // UInt32 &coderStreamIndex\n            )\n            \n    {\n        for (coderIndex[0] = 0; coderIndex[0] < Coders.size(); coderIndex[0]++) {\n            int curSize = Coders.get(coderIndex[0]).NumInStreams;\n            if (streamIndex < curSize) {\n                coderStreamIndex[0] = streamIndex;\n                return;\n            }\n            streamIndex -= curSize;\n        }\n        throw new UnknownError(\"1\");\n    }\n    \n    public void FindOutStream(int streamIndex,\n            int [] coderIndex, // UInt32 &coderIndex,\n            int [] coderStreamIndex /* , UInt32 &coderStreamIndex */ ) {\n        for (coderIndex[0] = 0; coderIndex[0] < Coders.size(); coderIndex[0]++) {\n            int curSize = Coders.get(coderIndex[0]).NumOutStreams;\n            if (streamIndex < curSize) {\n                coderStreamIndex[0] = streamIndex;\n                return;\n            }\n            streamIndex -= curSize;\n        }\n        throw new UnknownError(\"1\");\n    }\n    \n}\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/Common/BindPair.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.Common;\n\npublic class BindPair {\n    public int InIndex;\n    public int OutIndex;\n    \n    public BindPair() {\n        InIndex = 0;\n        OutIndex = 0;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/Common/CoderInfo.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.Common;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.LongVector;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressCoder;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressCoder2;\n\n\n\npublic class CoderInfo {\n    ICompressCoder Coder;\n    ICompressCoder2 Coder2;\n    int NumInStreams;\n    int NumOutStreams;\n    \n    LongVector InSizes = new LongVector();\n    LongVector OutSizes = new LongVector();\n    LongVector InSizePointers = new LongVector();\n    LongVector OutSizePointers = new LongVector();\n    \n    public CoderInfo(int numInStreams, int numOutStreams) {\n        NumInStreams = numInStreams;\n        NumOutStreams = numOutStreams;\n        InSizes.Reserve(NumInStreams);\n        InSizePointers.Reserve(NumInStreams);\n        OutSizePointers.Reserve(NumOutStreams);\n        OutSizePointers.Reserve(NumOutStreams);\n    }\n    \n    static public void SetSizes(\n            LongVector srcSizes,\n            LongVector sizes,\n            LongVector sizePointers,\n            int numItems)\n    {\n        sizes.clear();\n        sizePointers.clear();\n        for(int i = 0; i < numItems; i++) {\n            if (srcSizes == null || srcSizes.get(i) == -1) { // TBD null => -1\n                sizes.add((long) 0);\n                sizePointers.add(-1);\n            } else {\n                sizes.add(srcSizes.get(i)); // sizes.Add(*srcSizes[i]);\n                sizePointers.add(sizes.Back()); // sizePointers.Add(&sizes.Back());\n            }\n        }\n    }\n    \n    public void SetCoderInfo(\n            LongVector inSizes, //  const UInt64 **inSizes,\n            LongVector outSizes) //const UInt64 **outSizes)\n    {\n        SetSizes(inSizes, InSizes, InSizePointers, NumInStreams);\n        SetSizes(outSizes, OutSizes, OutSizePointers, NumOutStreams);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/Common/CoderMixer2.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.Common;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.LongVector;\n\npublic interface CoderMixer2 {\n    \n    void ReInit();\n\n    void SetBindInfo(BindInfo bindInfo);\n\n    void SetCoderInfo(int coderIndex,LongVector inSizes, LongVector outSizes);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/Common/CoderMixer2ST.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.Common;\n\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.LongVector;\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.ObjectVector;\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.RecordVector;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.HRESULT;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressCoder;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressCoder2;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressProgressInfo;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressSetInStream;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressSetOutStream;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressSetOutStreamSize;\n\n\n\npublic class CoderMixer2ST implements ICompressCoder2 , CoderMixer2 {\n    \n    BindInfo _bindInfo = new BindInfo();\n    ObjectVector<STCoderInfo> _coders = new ObjectVector<>();\n\n    \n    public CoderMixer2ST() {\n    }\n    \n    \n    public void SetBindInfo(BindInfo bindInfo) {\n        _bindInfo = bindInfo;\n    }\n    \n    public void AddCoderCommon(boolean isMain) {\n        CoderStreamsInfo csi = _bindInfo.Coders.get(_coders.size());\n        _coders.add(new STCoderInfo(csi.NumInStreams, csi.NumOutStreams, isMain));\n    }\n    \n    public void AddCoder2(ICompressCoder2 coder, boolean isMain) {\n        AddCoderCommon(isMain);\n        _coders.Back().Coder2 = coder;\n    }\n    \n    public void AddCoder(ICompressCoder coder, boolean isMain) {\n        AddCoderCommon(isMain);\n        _coders.Back().Coder = coder;\n    }\n    \n    public void ReInit() {\n    }\n    \n    public void SetCoderInfo(int coderIndex,LongVector inSizes, LongVector outSizes) {\n        // _coders[coderIndex].SetCoderInfo(inSizes, outSizes);\n        _coders.get(coderIndex).SetCoderInfo(inSizes, outSizes);\n    }\n    \n    public int GetInStream(\n            RecordVector<InputStream> inStreams,\n            Object useless_inSizes, // const UInt64 **inSizes,\n            int streamIndex,\n            InputStream [] inStreamRes) {\n        InputStream seqInStream;\n\n        for(int i = 0; i < _bindInfo.InStreams.size(); i++) {\n            if (_bindInfo.InStreams.get(i) == streamIndex) {\n                seqInStream = inStreams.get(i);\n                inStreamRes[0] = seqInStream; // seqInStream.Detach();\n                return HRESULT.S_OK;\n            }\n        }\n        int binderIndex = _bindInfo.FindBinderForInStream(streamIndex);\n        if (binderIndex < 0)\n            return HRESULT.E_INVALIDARG;\n        \n        \n        int tmp1 [] = new int[1]; // TBD\n        int tmp2 [] = new int[1]; // TBD\n        _bindInfo.FindOutStream(_bindInfo.BindPairs.get(binderIndex).OutIndex,\n                tmp1 /* coderIndex */ , tmp2 /* coderStreamIndex */ );\n        int coderIndex = tmp1[0], coderStreamIndex = tmp2[0];\n        \n        CoderInfo coder = _coders.get(coderIndex);\n        if (coder.Coder == null)\n            return HRESULT.E_NOTIMPL;\n        \n        seqInStream = (InputStream)coder.Coder; // coder.Coder.QueryInterface(IID_ISequentialInStream, &seqInStream);\n\n        int startIndex = _bindInfo.GetCoderInStreamIndex(coderIndex);\n        \n        if (coder.Coder == null)\n            return HRESULT.E_NOTIMPL;\n        \n        ICompressSetInStream setInStream = (ICompressSetInStream)coder.Coder; //  coder.Coder.QueryInterface(IID_ICompressSetInStream, &setInStream);\n        if (setInStream == null)\n            return HRESULT.E_NOTIMPL;\n        \n        if (coder.NumInStreams > 1)\n            return HRESULT.E_NOTIMPL;\n        for (int i = 0; i < coder.NumInStreams; i++) {\n            InputStream [] tmp = new InputStream[1];\n            int res = GetInStream(inStreams, useless_inSizes, startIndex + i, tmp /* &seqInStream2 */ );\n            if (res != HRESULT.S_OK) return res;\n            InputStream seqInStream2 = tmp[0];\n            res = setInStream.SetInStream(seqInStream2);\n            if (res != HRESULT.S_OK) return res;\n        }\n        inStreamRes[0] = seqInStream; // seqInStream.Detach();\n        return HRESULT.S_OK;\n    }\n    \n    public int GetOutStream(\n            RecordVector<OutputStream> outStreams,\n            Object useless_outSizes, //  const UInt64 **outSizes,\n            int streamIndex,\n            OutputStream [] outStreamRes) {\n        OutputStream seqOutStream;\n        for(int i = 0; i < _bindInfo.OutStreams.size(); i++)\n            if (_bindInfo.OutStreams.get(i) == streamIndex) {\n            seqOutStream = outStreams.get(i);\n            outStreamRes[0] = seqOutStream; // seqOutStream.Detach();\n            return  HRESULT.S_OK;\n            }\n        int binderIndex = _bindInfo.FindBinderForOutStream(streamIndex);\n        if (binderIndex < 0)\n            return HRESULT.E_INVALIDARG;\n        \n        int tmp1[] = new int[1];\n        int tmp2[] = new int[1];\n        _bindInfo.FindInStream(_bindInfo.BindPairs.get(binderIndex).InIndex,\n                tmp1 /* coderIndex*/ , tmp2 /* coderStreamIndex */ );\n        int coderIndex = tmp1[0];\n        \n        CoderInfo coder = _coders.get(coderIndex);\n        if (coder.Coder == null)\n            return HRESULT.E_NOTIMPL;\n        \n        try\n        {\n            seqOutStream = (OutputStream)coder.Coder; // coder.Coder.QueryInterface(IID_ISequentialOutStream, &seqOutStream);\n        } catch (java.lang.ClassCastException e) {\n            return HRESULT.E_NOTIMPL;\n        }\n        \n        int startIndex = _bindInfo.GetCoderOutStreamIndex(coderIndex);\n        \n        if (coder.Coder == null)\n            return HRESULT.E_NOTIMPL;\n        \n        ICompressSetOutStream setOutStream;\n        try {\n            setOutStream = (ICompressSetOutStream)coder.Coder; // coder.Coder.QueryInterface(IID_ICompressSetOutStream, &setOutStream);\n        } catch (java.lang.ClassCastException e) {     \n            return HRESULT.E_NOTIMPL;\n        }\n        \n        if (coder.NumOutStreams > 1)\n            return HRESULT.E_NOTIMPL;\n        for (int i = 0; i < coder.NumOutStreams; i++) {\n            OutputStream [] tmp = new OutputStream[1];\n            int res = GetOutStream(outStreams, useless_outSizes, startIndex + i, tmp /* &seqOutStream2 */ );\n            if (res != HRESULT.S_OK) return res;\n            OutputStream seqOutStream2 = tmp[0];\n            res = setOutStream.SetOutStream(seqOutStream2);\n            if (res != HRESULT.S_OK) return res;\n        }\n        outStreamRes[0] = seqOutStream; // seqOutStream.Detach();\n        return HRESULT.S_OK;\n    }\n    \n    public int Code(\n            RecordVector<InputStream>  inStreams,\n            Object useless_inSizes, // const UInt64 ** inSizes ,\n            int numInStreams,\n            RecordVector<OutputStream> outStreams,\n            Object useless_outSizes, // const UInt64 ** /* outSizes */,\n            int numOutStreams,\n            ICompressProgressInfo progress) throws IOException {\n        if (numInStreams != _bindInfo.InStreams.size() ||\n                numOutStreams != _bindInfo.OutStreams.size())\n            return HRESULT.E_INVALIDARG;\n        \n        \n        // Find main coder\n        int _mainCoderIndex = -1;\n        int i;\n        for (i = 0; i < _coders.size(); i++)\n            if (_coders.get(i).IsMain) {\n            _mainCoderIndex = i;\n            break;\n            }\n        if (_mainCoderIndex < 0)\n            for (i = 0; i < _coders.size(); i++)\n                if (_coders.get(i).NumInStreams > 1) {\n            if (_mainCoderIndex >= 0)\n                return HRESULT.E_NOTIMPL;\n            _mainCoderIndex = i;\n                }\n        if (_mainCoderIndex < 0)\n            _mainCoderIndex = 0;\n        \n        // _mainCoderIndex = 0;\n        // _mainCoderIndex = _coders.Size() - 1;\n        CoderInfo mainCoder = _coders.get(_mainCoderIndex);\n        \n        ObjectVector<InputStream > seqInStreams = new ObjectVector<>(); // CObjectVector< CMyComPtr<ISequentialInStream> >\n        ObjectVector<OutputStream > seqOutStreams = new ObjectVector<>(); // CObjectVector< CMyComPtr<ISequentialOutStream> >\n        int startInIndex = _bindInfo.GetCoderInStreamIndex(_mainCoderIndex);\n        int startOutIndex = _bindInfo.GetCoderOutStreamIndex(_mainCoderIndex);\n        for (i = 0; i < mainCoder.NumInStreams; i++) {\n            InputStream tmp [] = new InputStream[1];\n            int res = GetInStream(inStreams, useless_inSizes, startInIndex + i, tmp /* &seqInStream */ );\n            if (res != HRESULT.S_OK) return res;\n            InputStream seqInStream = tmp[0];\n            seqInStreams.add(seqInStream);\n        }\n        for (i = 0; i < mainCoder.NumOutStreams; i++) {\n            OutputStream tmp [] = new OutputStream[1];\n            int res = GetOutStream(outStreams, useless_outSizes, startOutIndex + i, tmp);\n            if (res != HRESULT.S_OK) return res;\n            OutputStream seqOutStream = tmp[0];\n            seqOutStreams.add(seqOutStream);\n        }\n        RecordVector<InputStream > seqInStreamsSpec = new RecordVector<>();\n        RecordVector<OutputStream > seqOutStreamsSpec = new RecordVector<>();\n        for (i = 0; i < mainCoder.NumInStreams; i++)\n            seqInStreamsSpec.add(seqInStreams.get(i));\n        for (i = 0; i < mainCoder.NumOutStreams; i++)\n            seqOutStreamsSpec.add(seqOutStreams.get(i));\n        \n        for (i = 0; i < _coders.size(); i++) {\n            if (i == _mainCoderIndex)\n                continue;\n            CoderInfo coder = _coders.get(i);\n            \n            try {\n                ICompressSetOutStreamSize setOutStreamSize = (ICompressSetOutStreamSize)coder.Coder;\n                \n                int res = setOutStreamSize.SetOutStreamSize(coder.OutSizePointers.get(0));\n                if (res != HRESULT.S_OK) return res;\n            } catch (java.lang.ClassCastException e) {\n                // nothing to do\n            }\n        }\n        if (mainCoder.Coder != null) {\n            int res = mainCoder.Coder.Code(\n                    seqInStreamsSpec.get(0),\n                    seqOutStreamsSpec.get(0),\n                    // TBD mainCoder.InSizePointers.get(0),\n                    mainCoder.OutSizePointers.get(0),\n                    progress);\n            if (res != HRESULT.S_OK) return res;\n        } else {\n            int res = mainCoder.Coder2.Code(\n                    seqInStreamsSpec, // &seqInStreamsSpec.Front(\n                    mainCoder.InSizePointers.Front(), // &mainCoder.InSizePointers.Front()\n                    mainCoder.NumInStreams,\n                    seqOutStreamsSpec, // &seqOutStreamsSpec.Front()\n                    mainCoder.OutSizePointers.Front(), // &mainCoder.OutSizePointers.Front()\n                    mainCoder.NumOutStreams,\n                    progress);\n            if (res != HRESULT.S_OK) return res;\n        }\n\n        \n        OutputStream stream = seqOutStreams.Front();\n        stream.flush();\n            \n        return HRESULT.S_OK;\n    }\n    \n    public void close() {\n        \n    }\n}\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/Common/CoderStreamsInfo.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.Common;\n\npublic class CoderStreamsInfo {\n    public int NumInStreams;\n    public int NumOutStreams;\n    \n    public CoderStreamsInfo() {\n        NumInStreams = 0;\n        NumOutStreams = 0;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/Common/FilterCoder.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.Common;\n\n\nimport java.io.IOException;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.HRESULT;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressCoder;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressSetOutStream;\n\n/*\n  // #ifdef _ST_MODE\n  public ICompressSetInStream,\n  public ISequentialInStream,\n  public ICompressSetOutStream,\n  public ISequentialOutStream,\n  public IOutStreamFlush,\n  // #endif\n */\npublic class FilterCoder extends java.io.OutputStream implements ICompressCoder , ICompressSetOutStream {\n    \n    public com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressFilter Filter = null;\n    \n    java.io.OutputStream _outStream = null;\n    int _bufferPos;  //  UInt32\n    \n    boolean _outSizeIsDefined;\n    long _outSize;\n    long _nowPos64;\n    \n    int   Init() // HRESULT\n    {\n        _nowPos64 = 0;\n        _outSizeIsDefined = false;\n        return Filter.Init();\n    }\n    \n    // ICompressCoder\n    public int Code(\n            java.io.InputStream inStream, // , ISequentialInStream\n            java.io.OutputStream outStream, // ISequentialOutStream\n            long outSize, com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressProgressInfo progress) throws java.io.IOException {\n        throw new java.io.IOException(\"Not implemented\");\n    }\n    \n    // java.io.OutputStream\n    public  void write(int b) {\n        throw new UnknownError(\"FilterCoder write\");\n    }\n    \n    public void write(byte b[], int off, int size) throws IOException {\n        if (b == null) {\n            throw new NullPointerException();\n        } else if ((off < 0) || (off > b.length) || (size < 0) ||\n                ((off + size) > b.length) || ((off + size) < 0)) {\n            throw new IndexOutOfBoundsException();\n        } else if (size == 0) {\n            return;\n        }\n        \n        if (off != 0) throw new IOException(\"FilterCoder - off <> 0\");\n        \n        byte [] cur_data = b;\n        int     cur_off = 0;\n        while(size > 0) {\n            int sizeMax = kBufferSize - _bufferPos;\n            int sizeTemp = size;\n            if (sizeTemp > sizeMax)\n                sizeTemp = sizeMax;\n            System.arraycopy(cur_data, cur_off,    _buffer, _bufferPos , sizeTemp); // memmove(_buffer + _bufferPos, data, sizeTemp);\n            size -= sizeTemp;\n            cur_off = cur_off + sizeTemp;\n            int endPos = _bufferPos + sizeTemp;\n            _bufferPos = Filter.Filter(_buffer, endPos);\n            if (_bufferPos == 0) {\n                _bufferPos = endPos;\n                break;\n            }\n            if (_bufferPos > endPos) {\n                if (size != 0)\n                    throw new IOException(\"FilterCoder - write() : size  <> 0\"); // return HRESULT.E_FAIL;\n                break;\n            }\n            \n            WriteWithLimit(_outStream, _bufferPos);\n            \n            int i = 0;\n            while(_bufferPos < endPos)\n                _buffer[i++] = _buffer[_bufferPos++];\n            _bufferPos = i;\n        }\n        \n        // return HRESULT.S_OK;\n    }\n    \n    void WriteWithLimit(java.io.OutputStream outStream, int size) throws IOException {\n        if (_outSizeIsDefined) {\n            long remSize = _outSize - _nowPos64;\n            if (size > remSize)\n                size = (int)remSize;\n        }\n        \n        outStream.write(_buffer,0,size);\n        \n        _nowPos64 += size;\n    }\n    \n    byte [] _buffer;\n    \n    static final int kBufferSize = 1 << 17;\n    public FilterCoder() {\n        _buffer = new byte[kBufferSize];\n    }\n    \n    \n    // ICompressSetOutStream\n    public int SetOutStream(java.io.OutputStream outStream) {\n        _bufferPos = 0;\n        _outStream = outStream;\n        return Init();\n    }\n    \n    public int ReleaseOutStream() throws IOException {\n        if (_outStream != null) _outStream.close(); // Release()\n        _outStream = null;\n        return HRESULT.S_OK;\n    }\n    \n    public void flush() throws IOException {\n        if (_bufferPos != 0) {\n            int endPos = Filter.Filter(_buffer, _bufferPos);\n            if (endPos > _bufferPos) {\n                for (; _bufferPos < endPos; _bufferPos++)\n                    _buffer[_bufferPos] = 0;\n                if (Filter.Filter(_buffer, endPos) != endPos)\n                    throw new IOException(\"FilterCoder - flush() : E_FAIL\"); // return HRESULT.E_FAIL;\n            }\n            _outStream.write(_buffer,0,_bufferPos);\n            _bufferPos = 0;\n        }\n        _outStream.flush();\n    }\n    \n    public void close() throws IOException {\n        if (_outStream != null) _outStream.close(); // Release()\n        _outStream = null;\n    }\n    \n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/Common/OutStreamWithCRC.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.Common;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.CRC;\n\n\npublic class OutStreamWithCRC extends java.io.OutputStream {\n    \n    java.io.OutputStream _stream;\n    long _size;\n    CRC _crc = new CRC();\n    boolean _calculateCrc;\n    \n    public void write(int b) throws java.io.IOException {\n        throw new java.io.IOException(\"OutStreamWithCRC - write() not implemented\");\n    }\n    \n    public void write(byte [] data,int off, int  size) throws java.io.IOException {\n        if(_stream != null) {\n            if (size == 0) {\n                throw new java.io.IOException(\"size = 0\");\n            } else {\n                _stream.write(data, off,size);\n            }\n        }\n        if (_calculateCrc)\n            _crc.Update(data,off, size);\n        \n        _size += size;\n    }\n    \n    public void SetStream(java.io.OutputStream stream) { _stream = stream; }\n    \n    public void Init() {\n        Init(true);\n    }\n    public void Init(boolean calculateCrc) {\n        _size = 0;\n        _calculateCrc = calculateCrc;\n        _crc.Init();\n    }\n    public void ReleaseStream() throws java.io.IOException {\n        // _stream.Release();\n        if (_stream != null) _stream.close();\n        _stream = null;\n    }\n    public long GetSize()  {\n        return _size;\n    }\n    public int GetCRC()  {\n        return _crc.getDigest();\n    }\n    public void InitCRC() {\n        _crc.Init();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/Common/STCoderInfo.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.Common;\n\npublic class STCoderInfo extends CoderInfo {\n    boolean IsMain;\n\n    public STCoderInfo(int numInStreams, int  numOutStreams, boolean isMain) {\n        super(numInStreams, numOutStreams);\n        this.IsMain = isMain;\n    }   \n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/IArchiveExtractCallback.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive;\n\npublic interface IArchiveExtractCallback extends com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.IProgress {\n    \n    // GetStream OUT: S_OK - OK, S_FALSE - skip this file\n    int GetStream(int index, java.io.OutputStream [] outStream,  int askExtractMode) throws java.io.IOException;\n    \n    int PrepareOperation(int askExtractMode);\n    int SetOperationResult(int resultEOperationResult) throws java.io.IOException;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/IInArchive.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive;\n\nimport java.io.IOException;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.IInStream;\n\npublic interface IInArchive {\n    int NExtract_NAskMode_kExtract = 0;\n    int NExtract_NAskMode_kTest = 1;\n    int NExtract_NAskMode_kSkip = 2;\n    \n    int NExtract_NOperationResult_kOK = 0;\n    int NExtract_NOperationResult_kUnSupportedMethod = 1;\n    int NExtract_NOperationResult_kDataError = 2;\n    int NExtract_NOperationResult_kCRCError = 3;\n    \n    // Static-SFX (for Linux) can be big.\n    long kMaxCheckStartPosition = 1 << 22;\n    \n    SevenZipEntry getEntry(int index);\n    \n    int size();\n    \n    int close() throws IOException ;\n    \n    int Extract(int [] indices, int numItems,\n            int testModeSpec, IArchiveExtractCallback extractCallbackSpec) throws java.io.IOException;\n    \n    int Open(IInStream stream) throws IOException;\n    \n    int Open(\n            IInStream stream, // InStream *stream\n            long maxCheckStartPosition // const UInt64 *maxCheckStartPosition,\n            // IArchiveOpenCallback *openArchiveCallback */\n            ) throws java.io.IOException;\n\n    \n}\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/SevenZip/AltCoderInfo.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZip;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.ByteBuffer;\n\nclass AltCoderInfo {\n    public MethodID MethodID;\n    public ByteBuffer Properties;\n    \n    public AltCoderInfo() {\n        MethodID = new MethodID();\n        Properties = new ByteBuffer();\n    } \n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/SevenZip/ArchiveDatabase.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZip;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.BoolVector;\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.IntVector;\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.LongVector;\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.ObjectVector;\n\nclass ArchiveDatabase {\n    public LongVector PackSizes = new LongVector();\n    public BoolVector PackCRCsDefined = new BoolVector();\n    public IntVector PackCRCs = new IntVector();\n    public ObjectVector<Folder> Folders = new ObjectVector<>();\n    public IntVector NumUnPackStreamsVector = new IntVector();\n    public ObjectVector<FileItem> Files = new ObjectVector<>();\n    \n    void Clear() {\n        PackSizes.clear();\n        PackCRCsDefined.clear();\n//        PackCRCs.clear();\n        Folders.clear();\n//        NumUnPackStreamsVector.clear();\n        Files.clear();\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/SevenZip/ArchiveDatabaseEx.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZip;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.IntVector;\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.LongVector;\n\npublic class ArchiveDatabaseEx extends ArchiveDatabase {\n    InArchiveInfo ArchiveInfo = new InArchiveInfo();\n    LongVector packStreamStartPositions = new LongVector();\n    IntVector folderStartPackStreamIndex = new IntVector();\n   \n    IntVector folderStartFileIndex = new IntVector();\n    IntVector fileIndexToFolderIndexMap = new IntVector();\n    \n    void Clear() {\n        super.Clear();\n        ArchiveInfo.Clear();\n        packStreamStartPositions.clear();\n        folderStartPackStreamIndex.clear();\n        folderStartFileIndex.clear();\n        fileIndexToFolderIndexMap.clear();\n    }\n    \n    void FillFolderStartPackStream() {\n        folderStartPackStreamIndex.clear();\n        folderStartPackStreamIndex.Reserve(Folders.size());\n        int startPos = 0;\n        for (Folder Folder : Folders) {\n            folderStartPackStreamIndex.add(startPos);\n            startPos += Folder.PackStreams.size();\n        }\n    }\n    \n    void FillStartPos() {\n        packStreamStartPositions.clear();\n        packStreamStartPositions.Reserve(PackSizes.size());\n        long startPos = 0;\n        for(int i = 0; i < PackSizes.size(); i++) {\n            packStreamStartPositions.add(startPos);\n            startPos += PackSizes.get(i);\n        }\n    }\n    \n    public void Fill()  throws java.io.IOException {\n        FillFolderStartPackStream();\n        FillStartPos();\n        FillFolderStartFileIndex();\n    }\n    \n    public long GetFolderFullPackSize(int folderIndex) {\n        int packStreamIndex = folderStartPackStreamIndex.get(folderIndex);\n        Folder folder = Folders.get(folderIndex);\n        long size = 0;\n        for (int i = 0; i < folder.PackStreams.size(); i++)\n            size += PackSizes.get(packStreamIndex + i);\n        return size;\n    }\n    \n    \n    void FillFolderStartFileIndex() throws java.io.IOException {\n        folderStartFileIndex.clear();\n        folderStartFileIndex.Reserve(Folders.size());\n        fileIndexToFolderIndexMap.clear();\n        fileIndexToFolderIndexMap.Reserve(Files.size());\n        \n        int folderIndex = 0;\n        int indexInFolder = 0;\n        for (int i = 0; i < Files.size(); i++) {\n            FileItem file = Files.get(i);\n            boolean emptyStream = !file.HasStream;\n            if (emptyStream && indexInFolder == 0) {\n                fileIndexToFolderIndexMap.add(InArchive.kNumNoIndex);\n                continue;\n            }\n            if (indexInFolder == 0) {\n                // v3.13 incorrectly worked with empty folders\n                // v4.07: Loop for skipping empty folders\n                for (;;) {\n                    if (folderIndex >= Folders.size())\n                        throw new java.io.IOException(\"Incorrect Header\"); // CInArchiveException(CInArchiveException::kIncorrectHeader);\n                    folderStartFileIndex.add(i); // check it\n                    if (NumUnPackStreamsVector.get(folderIndex) != 0)\n                        break;\n                    folderIndex++;\n                }\n            }\n            fileIndexToFolderIndexMap.add(folderIndex);\n            if (emptyStream)\n                continue;\n            indexInFolder++;\n            if (indexInFolder >= NumUnPackStreamsVector.get(folderIndex)) {\n                folderIndex++;\n                indexInFolder = 0;\n            }\n        }\n    }\n    \n    public long GetFolderStreamPos(int folderIndex, int indexInFolder) {\n        return ArchiveInfo.DataStartPosition +\n                packStreamStartPositions.get(folderStartPackStreamIndex.get(folderIndex) +\n                indexInFolder);\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/SevenZip/BindInfoEx.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZip;\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.RecordVector;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.Common.BindInfo;\n\n\n\nclass BindInfoEx extends BindInfo {\n    \n    RecordVector<MethodID> CoderMethodIDs = new RecordVector<>();\n    \n    public void Clear() {\n        super.Clear(); // CBindInfo::Clear();\n        CoderMethodIDs.clear();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/SevenZip/CoderInfo.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZip;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.ObjectVector;\n\nclass CoderInfo {\n    \n    int NumInStreams;\n    int NumOutStreams;\n    public ObjectVector<AltCoderInfo> AltCoders = new com.mucommander.commons.file.impl.sevenzip.provider.Common.ObjectVector<>();\n    \n    boolean IsSimpleCoder() { return (NumInStreams == 1) && (NumOutStreams == 1); }\n    \n    public CoderInfo() {\n        NumInStreams = 0;\n        NumOutStreams = 0;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/SevenZip/Decoder.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZip;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.ByteBuffer;\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.LimitedSequentialInStream;\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.LockedInStream;\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.LockedSequentialInStreamImp;\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.LongVector;\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.ObjectVector;\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.RecordVector;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.HRESULT;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressCoder;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressCoder2;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressFilter;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressSetDecoderProperties2;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.Common.BindPair;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.Common.CoderMixer2;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.Common.CoderMixer2ST;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.Common.CoderStreamsInfo;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.Common.FilterCoder;\n\n\n\n// import SevenZip.Archive.Common.CoderMixer2MT;\n\n\nclass Decoder {\n    \n    boolean _bindInfoExPrevIsDefined;\n    BindInfoEx _bindInfoExPrev;\n    \n    boolean _multiThread;\n    \n    // CoderMixer2MT _mixerCoderMTSpec;\n    CoderMixer2ST _mixerCoderSTSpec;\n    CoderMixer2 _mixerCoderCommon;\n    \n    ICompressCoder2 _mixerCoder;\n    ObjectVector<Object> _decoders;\n    \n    public Decoder(boolean multiThread) {\n        _multiThread = multiThread;\n        _bindInfoExPrevIsDefined = false;\n        _bindInfoExPrev = new BindInfoEx();\n        \n        _mixerCoder = null;\n        _decoders = new ObjectVector<>();\n        \n        // #ifndef EXCLUDE_COM -- LoadMethodMap();\n    }\n    \n    static void ConvertFolderItemInfoToBindInfo(Folder folder,BindInfoEx bindInfo) {\n        bindInfo.Clear();\n        \n        for (int i = 0; i < folder.BindPairs.size(); i++) {\n            BindPair bindPair = new BindPair();\n            bindPair.InIndex = folder.BindPairs.get(i).InIndex;\n            bindPair.OutIndex = folder.BindPairs.get(i).OutIndex;\n            bindInfo.BindPairs.add(bindPair);\n        }\n        int outStreamIndex = 0;\n        for (int i = 0; i < folder.Coders.size(); i++) {\n            CoderStreamsInfo coderStreamsInfo = new CoderStreamsInfo();\n            CoderInfo coderInfo = folder.Coders.get(i);\n            coderStreamsInfo.NumInStreams = coderInfo.NumInStreams;\n            coderStreamsInfo.NumOutStreams = coderInfo.NumOutStreams;\n            bindInfo.Coders.add(coderStreamsInfo);\n            AltCoderInfo altCoderInfo = coderInfo.AltCoders.Front();\n            bindInfo.CoderMethodIDs.add(altCoderInfo.MethodID);\n            for (int j = 0; j < coderStreamsInfo.NumOutStreams; j++, outStreamIndex++)\n                if (folder.FindBindPairForOutStream(outStreamIndex) < 0)\n                    bindInfo.OutStreams.add(outStreamIndex);\n        }\n        for (int i = 0; i < folder.PackStreams.size(); i++)\n            bindInfo.InStreams.add(folder.PackStreams.get(i));\n    }\n    static boolean AreCodersEqual(CoderStreamsInfo a1, CoderStreamsInfo a2) {\n        return (a1.NumInStreams == a2.NumInStreams) &&\n                (a1.NumOutStreams == a2.NumOutStreams);\n    }\n    \n    static boolean AreBindPairsEqual(BindPair a1, BindPair a2) {\n        return (a1.InIndex == a2.InIndex) &&\n                (a1.OutIndex == a2.OutIndex);\n    }\n    \n    static boolean AreBindInfoExEqual(BindInfoEx a1, BindInfoEx a2) {\n        if (a1.Coders.size() != a2.Coders.size())\n            return false;\n        int i;\n        for (i = 0; i < a1.Coders.size(); i++)\n            if (!AreCodersEqual(a1.Coders.get(i), a2.Coders.get(i)))\n                return false;\n        if (a1.BindPairs.size() != a2.BindPairs.size())\n            return false;\n        for (i = 0; i < a1.BindPairs.size(); i++)\n            if (!AreBindPairsEqual(a1.BindPairs.get(i), a2.BindPairs.get(i)))\n                return false;\n        for (i = 0; i < a1.CoderMethodIDs.size(); i++)\n            if (a1.CoderMethodIDs.get(i) != a2.CoderMethodIDs.get(i))\n                return false;\n        return a1.InStreams.size() == a2.InStreams.size() && a1.OutStreams.size() == a2.OutStreams.size();\n    }\n    \n    int Decode(com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.IInStream inStream,\n            long startPos,\n            LongVector packSizes, int packSizesOffset, // const UInt64 *packSizes,\n            Folder folderInfo,\n            OutputStream outStream,\n            com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressProgressInfo compressProgress // , // ICompressProgressInfo *compressProgress,\n            // _ST_MODE boolean mtMode, int numThreads\n            ) throws IOException {\n        \n        \n        ObjectVector<InputStream> inStreams = new ObjectVector<>(); // CObjectVector< CMyComPtr<ISequentialInStream> >\n        \n        LockedInStream lockedInStream = new LockedInStream();\n        lockedInStream.Init(inStream);\n        \n        for (int j = 0; j < folderInfo.PackStreams.size(); j++) {\n            LockedSequentialInStreamImp lockedStreamImpSpec = new LockedSequentialInStreamImp();\n            lockedStreamImpSpec.Init(lockedInStream, startPos);\n            startPos += packSizes.get(j+packSizesOffset);\n            \n            LimitedSequentialInStream streamSpec = new LimitedSequentialInStream();\n            streamSpec.SetStream(lockedStreamImpSpec);\n            streamSpec.Init(packSizes.get(j+packSizesOffset));\n            inStreams.add(streamSpec);\n        }\n        \n        int numCoders = folderInfo.Coders.size();\n        \n        BindInfoEx bindInfo = new BindInfoEx();\n        ConvertFolderItemInfoToBindInfo(folderInfo, bindInfo);\n        boolean createNewCoders;\n        createNewCoders = !_bindInfoExPrevIsDefined || !AreBindInfoExEqual(bindInfo, _bindInfoExPrev);\n\n        if (createNewCoders) {\n            int i;\n            _decoders.clear();\n            \n            if (_mixerCoder != null) _mixerCoder.close(); // _mixerCoder.Release();\n            \n            if (_multiThread) {\n                /*\n                _mixerCoderMTSpec = new CoderMixer2MT();\n                _mixerCoder = _mixerCoderMTSpec;\n                _mixerCoderCommon = _mixerCoderMTSpec;\n                */\n                throw new IOException(\"multithreaded decoder not implemented\");\n            } else {\n                _mixerCoderSTSpec = new CoderMixer2ST();\n                _mixerCoder = _mixerCoderSTSpec;\n                _mixerCoderCommon = _mixerCoderSTSpec;\n            }\n            _mixerCoderCommon.SetBindInfo(bindInfo);\n            \n            for (i = 0; i < numCoders; i++) {\n                CoderInfo coderInfo = folderInfo.Coders.get(i);\n                AltCoderInfo altCoderInfo = coderInfo.AltCoders.Front();\n                /*\n                #ifndef EXCLUDE_COM\n                        CMethodInfo methodInfo;\n                if (!GetMethodInfo(altCoderInfo.MethodID, methodInfo))\n                    return E_NOTIMPL;\n                #endif\n                 */\n                \n                if (coderInfo.IsSimpleCoder()) {\n                    ICompressCoder decoder = null;\n                    ICompressFilter filter = null;\n                    \n                    // #ifdef COMPRESS_LZMA\n                    if (altCoderInfo.MethodID.equals(MethodID.k_LZMA))\n                        decoder = new com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.LZMA.Decoder(); // NCompress::NLZMA::CDecoder;\n                    \n                    if (altCoderInfo.MethodID.equals(MethodID.k_PPMD))\n                        System.out.println(\"PPMD not implemented\"); // decoder = new NCompress::NPPMD::CDecoder;\n                    \n                    if (altCoderInfo.MethodID.equals(MethodID.k_BCJ_X86))\n                        filter = new com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.Branch.BCJ_x86_Decoder();\n                    \n                    if (altCoderInfo.MethodID.equals(MethodID.k_Deflate))\n                        System.out.println(\"DEFLATE not implemented\"); // decoder = new NCompress::NDeflate::NDecoder::CCOMCoder;\n                    \n                    if (altCoderInfo.MethodID.equals(MethodID.k_BZip2))\n                        System.out.println(\"BZIP2 not implemented\"); // decoder = new NCompress::NBZip2::CDecoder;\n                    \n                    if (altCoderInfo.MethodID.equals(MethodID.k_Copy))\n                        decoder = new com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.Copy.Decoder(); // decoder = new NCompress::CCopyCoder;\n                    \n                    if (altCoderInfo.MethodID.equals(MethodID.k_7zAES))\n                        throw new IOException(\"k_7zAES not implemented\"); // filter = new NCrypto::NSevenZ::CDecoder;\n                    \n                    if (filter != null) {\n                        FilterCoder coderSpec = new FilterCoder();\n                        decoder = coderSpec;\n                        coderSpec.Filter = filter;\n                    }\n                    /*\n                        #ifndef EXCLUDE_COM\n                                if (decoder == 0) {\n                            RINOK(_libraries.CreateCoderSpec(methodInfo.FilePath,\n                                    methodInfo.Decoder, &decoder));\n                                }\n                        #endif\n                     */\n                    if (decoder == null)\n                        return HRESULT.E_NOTIMPL;\n                    \n                    _decoders.add(decoder);\n                    \n                    if (_multiThread) {\n                        // _mixerCoderMTSpec.AddCoder(decoder);\n                        throw new IOException(\"Multithreaded decoder is not implemented\");\n                    } else {\n                        _mixerCoderSTSpec.AddCoder(decoder, false);\n                    }\n                } else {\n\n                    ICompressCoder2 decoder = null;\n                     \n                    if (altCoderInfo.MethodID.equals(MethodID.k_BCJ2)) {\n                        decoder = new com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.Branch.BCJ2_x86_Decoder();\n                    }\n                            \n                     \n                            if (decoder == null)\n                                return HRESULT.E_NOTIMPL;\n                     \n                    _decoders.add(decoder);\n                    if (_multiThread) {\n                       // _mixerCoderMTSpec.AddCoder2(decoder);\n                       throw new IOException(\"Multithreaded decoder is not implemented\");\n                    } else {\n                        _mixerCoderSTSpec.AddCoder2(decoder, false);\n                    }\n                }\n            }\n            _bindInfoExPrev = bindInfo;\n            _bindInfoExPrevIsDefined = true;\n        }\n        \n        int i;\n        _mixerCoderCommon.ReInit();\n        \n        int packStreamIndex = 0, unPackStreamIndex = 0;\n        int coderIndex = 0;\n        \n        for (i = 0; i < numCoders; i++) {\n            CoderInfo coderInfo = folderInfo.Coders.get(i);\n            AltCoderInfo altCoderInfo = coderInfo.AltCoders.Front();\n            Object decoder = _decoders.get(coderIndex); // CMyComPtr<IUnknown> &decoder = _decoders[coderIndex];\n            \n            {\n                try {\n                    ICompressSetDecoderProperties2 setDecoderProperties = (ICompressSetDecoderProperties2)decoder;\n                \n                    ByteBuffer properties = altCoderInfo.Properties;\n                    int size = properties.GetCapacity();\n                    if (size == -1) // (size > 0xFFFFFFFF)\n                        return HRESULT.E_NOTIMPL;\n                    if (size > 0) {\n                        boolean ret = setDecoderProperties.SetDecoderProperties2(properties.data() /* , size */ );\n                        if (!ret) return HRESULT.E_FAIL;\n                    }\n                } catch (ClassCastException e) {\n                    // nothing to do\n                }\n            }\n            /*\n            #ifdef COMPRESS_MT\n                    if (mtMode) {\n                CMyComPtr<ICompressSetCoderMt> setCoderMt;\n                decoder.QueryInterface(IID_ICompressSetCoderMt, &setCoderMt);\n                if (setCoderMt) {\n                    RINOK(setCoderMt->SetNumberOfThreads(numThreads));\n                }\n                    }\n            #endif\n             \n            #ifndef _NO_CRYPTO\n             {\n                CMyComPtr<ICryptoSetPassword> cryptoSetPassword;\n                decoder.QueryInterface(IID_ICryptoSetPassword, &cryptoSetPassword);\n                if (cryptoSetPassword) {\n                    if (getTextPassword == 0)\n                        return E_FAIL;\n                    CMyComBSTR password;\n                    RINOK(getTextPassword->CryptoGetTextPassword(&password));\n                    CByteBuffer buffer;\n                    UString unicodePassword(password);\n                    const UInt32 sizeInBytes = unicodePassword.Length() * 2;\n                    buffer.SetCapacity(sizeInBytes);\n                    for (int i = 0; i < unicodePassword.Length(); i++) {\n                        wchar_t c = unicodePassword[i];\n                        ((Byte *)buffer)[i * 2] = (Byte)c;\n                        ((Byte *)buffer)[i * 2 + 1] = (Byte)(c >> 8);\n                    }\n                    RINOK(cryptoSetPassword->CryptoSetPassword(\n                            (const Byte *)buffer, sizeInBytes));\n                }\n            }\n            #endif\n             */\n            coderIndex++;\n            \n            int numInStreams = coderInfo.NumInStreams;\n            int numOutStreams = coderInfo.NumOutStreams;\n            LongVector packSizesPointers = new LongVector(); // CRecordVector<const UInt64 *>\n            LongVector unPackSizesPointers = new LongVector(); // CRecordVector<const UInt64 *>\n            packSizesPointers.Reserve(numInStreams);\n            unPackSizesPointers.Reserve(numOutStreams);\n            int j;\n            for (j = 0; j < numOutStreams; j++, unPackStreamIndex++)\n                unPackSizesPointers.add(folderInfo.UnPackSizes.get(unPackStreamIndex));\n            \n            for (j = 0; j < numInStreams; j++, packStreamIndex++) {\n                int bindPairIndex = folderInfo.FindBindPairForInStream(packStreamIndex);\n                if (bindPairIndex >= 0)\n                    packSizesPointers.add(\n                            folderInfo.UnPackSizes.get(folderInfo.BindPairs.get(bindPairIndex).OutIndex));\n                else {\n                    int index = folderInfo.FindPackStreamArrayIndex(packStreamIndex);\n                    if (index < 0)\n                        return HRESULT.E_FAIL;\n                    packSizesPointers.add(packSizes.get(index));\n                }\n            }\n            \n            _mixerCoderCommon.SetCoderInfo(i,\n                    packSizesPointers, // &packSizesPointers.Front(),\n                    unPackSizesPointers // &unPackSizesPointers.Front()\n                    );\n        }\n        \n        int [] temp_useless = new int [1]; // TBD\n        int [] tmp1 = new int[1];\n        bindInfo.FindOutStream(bindInfo.OutStreams.get(0), tmp1 /* mainCoder */ , temp_useless /* temp */);\n\n        if (_multiThread) {\n           // _mixerCoderMTSpec.SetProgressCoderIndex(mainCoder);\n           throw new IOException(\"Multithreaded decoder is not implemented\");\n        }\n        \n        if (numCoders == 0)\n            return 0;\n        RecordVector<InputStream> inStreamPointers = new RecordVector<>(); // CRecordVector<ISequentialInStream *>\n        inStreamPointers.Reserve(inStreams.size());\n        for (i = 0; i < inStreams.size(); i++)\n            inStreamPointers.add(inStreams.get(i));\n\n        RecordVector<OutputStream> outStreamPointer = new RecordVector<>();\n        outStreamPointer.add(outStream);\n        return _mixerCoder.Code(\n                inStreamPointers, //&inStreamPointers.Front(),\n                null,\n                inStreams.size(),\n                outStreamPointer, // &outStreamPointer,\n                null, 1, compressProgress);\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/SevenZip/ExtractFolderInfo.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZip;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.BoolVector;\n\n\nclass ExtractFolderInfo {\n  /* #ifdef _7Z_VOL\n     int VolumeIndex;\n  #endif */\n  public int FileIndex;\n  public int FolderIndex;\n  public BoolVector ExtractStatuses = new BoolVector();\n  public long UnPackSize;\n  public ExtractFolderInfo(\n    /* #ifdef _7Z_VOL\n    int volumeIndex, \n    #endif */\n    int fileIndex, int folderIndex)  // CNum fileIndex, CNum folderIndex\n  {\n    /* #ifdef _7Z_VOL\n    VolumeIndex(volumeIndex),\n    #endif */\n    FileIndex = fileIndex;\n    FolderIndex = folderIndex;\n    UnPackSize = 0;\n\n    if (fileIndex != InArchive.kNumNoIndex)\n    {\n      ExtractStatuses.Reserve(1);\n      ExtractStatuses.add(true);\n    }\n  }\n}\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/SevenZip/FileItem.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZip;\n\npublic class FileItem {\n    \n    public long CreationTime;\n    public long LastWriteTime;\n    public long LastAccessTime;\n    \n    public long UnPackSize;\n    public long StartPos;\n    public int Attributes;\n    public int FileCRC;\n    \n    public boolean IsDirectory;\n    public boolean IsAnti;\n    public boolean IsFileCRCDefined;\n    public boolean AreAttributesDefined;\n    public boolean HasStream;\n    // public boolean IsCreationTimeDefined; replace by (CreationTime != 0)\n    // public boolean IsLastWriteTimeDefined; replace by (LastWriteTime != 0)\n    // public boolean IsLastAccessTimeDefined; replace by (LastAccessTime != 0)\n    public boolean IsStartPosDefined;\n    public String name;\n    \n    public FileItem() {\n        HasStream = true;\n        IsDirectory = false;\n        IsAnti = false;\n        IsFileCRCDefined = false;\n        AreAttributesDefined = false;\n        CreationTime = 0; // IsCreationTimeDefined = false;\n        LastWriteTime = 0; // IsLastWriteTimeDefined = false;\n        LastAccessTime = 0; // IsLastAccessTimeDefined = false;\n        IsStartPosDefined = false;\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/SevenZip/Folder.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZip;\n\nimport java.io.IOException;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.IntVector;\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.LongVector;\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.RecordVector;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.Common.BindPair;\n\n\n\nclass Folder {\n    public RecordVector<CoderInfo> Coders = new RecordVector<>();\n    RecordVector<BindPair> BindPairs = new RecordVector<>();\n    IntVector PackStreams = new IntVector();\n    LongVector UnPackSizes = new LongVector();\n    int UnPackCRC;\n    boolean UnPackCRCDefined;\n    Folder() {\n        UnPackCRCDefined = false;\n    }\n\n    long GetUnPackSize() throws IOException {\n        if (UnPackSizes.isEmpty())\n            return 0;\n        for (int i = UnPackSizes.size() - 1; i >= 0; i--)\n            if (FindBindPairForOutStream(i) < 0)\n                return UnPackSizes.get(i);\n        throw new IOException(\"1\"); // throw 1  // TBD\n    }\n    \n    int FindBindPairForInStream(int inStreamIndex) {\n        for(int i = 0; i < BindPairs.size(); i++)\n            if (BindPairs.get(i).InIndex == inStreamIndex)\n                return i;\n        return -1;\n    }\n    \n    int FindBindPairForOutStream(int outStreamIndex) {\n        for(int i = 0; i < BindPairs.size(); i++)\n            if (BindPairs.get(i).OutIndex == outStreamIndex)\n                return i;\n        return -1;\n    }\n    \n     int FindPackStreamArrayIndex(int inStreamIndex) {\n        for(int i = 0; i < PackStreams.size(); i++)\n            if (PackStreams.get(i) == inStreamIndex)\n                return i;\n        return -1;\n    }\n      \n    int GetNumOutStreams() {\n        int result = 0;\n        for (CoderInfo Coder : Coders) result += Coder.NumOutStreams;\n        return result;\n    }\n    \n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/SevenZip/FolderOutStream.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZip;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.BoolVector;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.HRESULT;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.IArchiveExtractCallback;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.IInArchive;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.Common.OutStreamWithCRC;\n\n\n\nclass FolderOutStream extends java.io.OutputStream {\n    OutStreamWithCRC _outStreamWithHashSpec;\n    java.io.OutputStream _outStreamWithHash;\n    \n    ArchiveDatabaseEx _archiveDatabase;\n    BoolVector _extractStatuses;\n    int _startIndex;\n    int _ref2Offset;\n    IArchiveExtractCallback _extractCallback;\n    boolean _testMode;\n    int _currentIndex;\n    boolean _fileIsOpen;\n   \n    long _filePos;\n\n    public FolderOutStream() {\n        _outStreamWithHashSpec = new OutStreamWithCRC();\n        _outStreamWithHash = _outStreamWithHashSpec;\n    }\n    \n    public int Init(\n            ArchiveDatabaseEx archiveDatabase,\n            int ref2Offset,\n            int startIndex,\n            BoolVector extractStatuses,\n            IArchiveExtractCallback extractCallback,\n            boolean testMode) throws java.io.IOException {\n        _archiveDatabase = archiveDatabase;\n        _ref2Offset = ref2Offset;\n        _startIndex = startIndex;\n        \n        _extractStatuses = extractStatuses;\n        _extractCallback = extractCallback;\n        _testMode = testMode;\n        \n        _currentIndex = 0;\n        _fileIsOpen = false;\n        return WriteEmptyFiles();\n    }\n    \n    int OpenFile() throws java.io.IOException {\n        int askMode;\n        if(_extractStatuses.get(_currentIndex))\n            askMode = _testMode ?\n                IInArchive.NExtract_NAskMode_kTest :\n                IInArchive.NExtract_NAskMode_kExtract;\n        else\n            askMode = IInArchive.NExtract_NAskMode_kSkip;\n        \n        \n        int index = _startIndex + _currentIndex;\n        \n        // RINOK(_extractCallback->GetStream(_ref2Offset + index, &realOutStream, askMode));\n        java.io.OutputStream [] realOutStream2 = new java.io.OutputStream[1]; // TBD\n        int ret = _extractCallback.GetStream(_ref2Offset + index, realOutStream2, askMode);\n        if (ret != HRESULT.S_OK) return ret;\n         \n        java.io.OutputStream realOutStream = realOutStream2[0];\n        \n        _outStreamWithHashSpec.SetStream(realOutStream);\n        _outStreamWithHashSpec.Init();\n        if (askMode == IInArchive.NExtract_NAskMode_kExtract &&\n                (realOutStream == null)) {\n            FileItem fileInfo = _archiveDatabase.Files.get(index);\n            if (!fileInfo.IsAnti && !fileInfo.IsDirectory)\n                askMode = IInArchive.NExtract_NAskMode_kSkip;\n        }\n        return _extractCallback.PrepareOperation(askMode);\n    }\n    \n    int WriteEmptyFiles() throws java.io.IOException {\n        for(;_currentIndex < _extractStatuses.size(); _currentIndex++) {\n            int index = _startIndex + _currentIndex;\n            FileItem fileInfo = _archiveDatabase.Files.get(index);\n            if (!fileInfo.IsAnti && !fileInfo.IsDirectory && fileInfo.UnPackSize != 0)\n                return HRESULT.S_OK;\n            int res = OpenFile();\n            if (res != HRESULT.S_OK) return res;\n            \n            res = _extractCallback.SetOperationResult(\n                    IInArchive.NExtract_NOperationResult_kOK);\n            if (res != HRESULT.S_OK) return res;\n            _outStreamWithHashSpec.ReleaseStream();\n        }\n        return HRESULT.S_OK;\n    }\n    \n    public void write(int b) throws java.io.IOException {\n        throw new java.io.IOException(\"FolderOutStream - write() not implemented\");\n    }\n    \n    public void write(byte [] data,int off,  int size) throws java.io.IOException //  UInt32 *processedSize\n    {\n        int realProcessedSize = 0;\n        while(_currentIndex < _extractStatuses.size()) {\n            if (_fileIsOpen) {\n                int index = _startIndex + _currentIndex;\n                FileItem fileInfo = _archiveDatabase.Files.get(index);\n                long fileSize = fileInfo.UnPackSize;\n                \n                long numBytesToWrite2 = (int)(fileSize - _filePos);\n                int tmp = size - realProcessedSize;\n                if (tmp < numBytesToWrite2) numBytesToWrite2 = tmp;\n                \n                int numBytesToWrite = (int)numBytesToWrite2;\n                \n                int processedSizeLocal;\n                // int res = _outStreamWithHash.Write((const Byte *)data + realProcessedSize,numBytesToWrite, &processedSizeLocal));\n                // if (res != HRESULT.S_OK) throw new java.io.IOException(\"_outStreamWithHash.Write : \" + res); // return res;\n                processedSizeLocal = numBytesToWrite;\n                _outStreamWithHash.write(data,realProcessedSize + off,numBytesToWrite);\n                \n                _filePos += processedSizeLocal;\n                realProcessedSize += processedSizeLocal;\n\n                if (_filePos == fileSize) {\n                    boolean digestsAreEqual;\n                    digestsAreEqual = !fileInfo.IsFileCRCDefined || (fileInfo.FileCRC == _outStreamWithHashSpec.GetCRC());\n\n                    int res = _extractCallback.SetOperationResult(\n                            digestsAreEqual ?\n                                    IInArchive.NExtract_NOperationResult_kOK :\n                                    IInArchive.NExtract_NOperationResult_kCRCError);\n                    if (res != HRESULT.S_OK)\n                        throw new java.io.IOException(\"_extractCallback.SetOperationResult : \" + res); // return res;\n\n                    _outStreamWithHashSpec.ReleaseStream();\n                    _fileIsOpen = false;\n                    _currentIndex++;\n                }\n                if (realProcessedSize == size) {\n                    int res = WriteEmptyFiles();\n                    if (res != HRESULT.S_OK) throw new java.io.IOException(\"WriteEmptyFiles : \" + res); // return res;\n                    return ;// return realProcessedSize;\n                }\n            } else {\n                int res = OpenFile();\n                if (res != HRESULT.S_OK) throw new java.io.IOException(\"OpenFile : \" + res); // return res;\n                _fileIsOpen = true;\n                _filePos = 0;\n            }\n        }\n        \n        // return size;\n    }\n    \n    public int FlushCorrupted(int resultEOperationResult) throws java.io.IOException {\n        while(_currentIndex < _extractStatuses.size()) {\n            if (_fileIsOpen) {\n                int res = _extractCallback.SetOperationResult(resultEOperationResult);\n                if (res != HRESULT.S_OK) return res;\n                \n                _outStreamWithHashSpec.ReleaseStream();\n                _fileIsOpen = false;\n                _currentIndex++;\n            } else {\n                int res = OpenFile();\n                if (res != HRESULT.S_OK) return res;\n                _fileIsOpen = true;\n            }\n        }\n        return HRESULT.S_OK;\n    }\n    \n    public int WasWritingFinished() {\n        int val = _extractStatuses.size();\n        if (_currentIndex == val)\n            return HRESULT.S_OK;\n        return HRESULT.E_FAIL;\n    }\n    \n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/SevenZip/Handler.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZip;\n\nimport java.io.IOException;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.ObjectVector;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.HRESULT;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressProgressInfo;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.IInStream;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.IArchiveExtractCallback;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.IInArchive;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZipEntry;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Common.LocalCompressProgressInfo;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Common.LocalProgress;\n\n\npublic class Handler implements com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.IInArchive {\n    \n    // useless LongVector _fileInfoPopIDs = new LongVector();\n    \n    IInStream _inStream;\n    \n    ArchiveDatabaseEx _database;\n    \n    int _numThreads;\n    \n    public Handler() {\n        _numThreads = 1; // TBD\n        \n        _database = new ArchiveDatabaseEx();\n        \n    }\n    \n    public int Open(IInStream stream) throws IOException {\n        return Open(stream,kMaxCheckStartPosition);\n    }\n    \n    \n    public int Open(\n            IInStream stream, // InStream *stream\n            long maxCheckStartPosition // const UInt64 *maxCheckStartPosition,\n            // IArchiveOpenCallback *openArchiveCallback */\n            ) throws IOException {\n        close();\n        \n        // useless _fileInfoPopIDs.clear();\n        // TBD try\n        {\n            // TBD CMyComPtr<IArchiveOpenCallback> openArchiveCallbackTemp = openArchiveCallback;\n            \n            /* TBD\n            CMyComPtr<ICryptoGetTextPassword> getTextPassword;\n            if (openArchiveCallback)\n            {\n              openArchiveCallbackTemp.QueryInterface(\n                  IID_ICryptoGetTextPassword, &getTextPassword);\n            }\n             */\n            \n            InArchive archive = new InArchive();\n            int ret = archive.Open(stream, maxCheckStartPosition);\n            if (ret != HRESULT.S_OK) return ret;\n            ret = archive.ReadDatabase(_database); // getTextPassword\n            if (ret != HRESULT.S_OK) return ret;\n            _database.Fill();\n            _inStream = stream;\n        }\n        \n        // FillPopIDs(); // useless _fileInfoPopIDs\n        \n        return HRESULT.S_OK;\n    }\n    \n    public int Extract(int [] indices, int numItems,\n            int testModeSpec, IArchiveExtractCallback extractCallbackSpec) throws IOException {\n        \n        boolean testMode = (testModeSpec != 0);\n        long importantTotalUnPacked = 0;\n        \n        boolean allFilesMode = (numItems == -1);\n        if (allFilesMode)\n            numItems =\n                    // #ifdef _7Z_VOL\n                    // _refs.Size();\n                    // #else\n                    _database.Files.size();\n        // #endif\n        \n        if(numItems == 0)\n            return HRESULT.S_OK;\n        \n        ObjectVector<ExtractFolderInfo> extractFolderInfoVector = new ObjectVector<>();\n        for(int ii = 0; ii < numItems; ii++) {\n            int ref2Index = allFilesMode ? ii : indices[ii];\n            \n            {\n        /*\n      #ifdef _7Z_VOL\n      // const CRef &ref = ref2.Refs[ri];\n      const CRef &ref = _refs[ref2Index];\n         \n      int volumeIndex = ref.VolumeIndex;\n      const CVolume &volume = _volumes[volumeIndex];\n      const CArchiveDatabaseEx &database = volume.Database;\n      UInt32 fileIndex = ref.ItemIndex;\n      #else\n         */\n                ArchiveDatabaseEx database = _database;\n                //#endif\n                \n                int folderIndex = database.fileIndexToFolderIndexMap.get(ref2Index);\n                if (folderIndex == com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZip.InArchive.kNumNoIndex) {\n                    extractFolderInfoVector.add( new ExtractFolderInfo(\n                            // #ifdef _7Z_VOL\n                            // volumeIndex,\n                            // #endif\n                            ref2Index, com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZip.InArchive.kNumNoIndex));\n                    continue;\n                }\n                if (extractFolderInfoVector.isEmpty() ||\n                        folderIndex != extractFolderInfoVector.Back().FolderIndex\n        /*\n        #ifdef _7Z_VOL\n        || volumeIndex != extractFolderInfoVector.Back().VolumeIndex\n        #endif\n         */\n                        ) {\n                    extractFolderInfoVector.add( new ExtractFolderInfo(\n            /*\n            #ifdef _7Z_VOL\n            volumeIndex,\n            #endif\n             */\n                            com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZip.InArchive.kNumNoIndex, folderIndex));\n                    Folder folderInfo = database.Folders.get(folderIndex);\n                    long unPackSize = folderInfo.GetUnPackSize();\n                    importantTotalUnPacked += unPackSize;\n                    extractFolderInfoVector.Back().UnPackSize = unPackSize;\n                }\n                \n                ExtractFolderInfo efi = extractFolderInfoVector.Back();\n                \n                // const CFolderInfo &folderInfo = m_dam_Folders[folderIndex];\n                int startIndex = database.folderStartFileIndex.get(folderIndex); // CNum\n                for (int index = efi.ExtractStatuses.size();\n                index <= ref2Index - startIndex; index++) {\n                    // UInt64 unPackSize = _database.Files[startIndex + index].UnPackSize;\n                    // Count partial_folder_size\n                    // efi.UnPackSize += unPackSize;\n                    // importantTotalUnPacked += unPackSize;\n                    efi.ExtractStatuses.add(index == ref2Index - startIndex);\n                }\n            }\n        }\n        \n        extractCallbackSpec.SetTotal(importantTotalUnPacked);\n        \n        Decoder decoder = new Decoder(\n                // #ifdef _ST_MODE\n                false\n                // #else\n                // true\n                // #endif\n                );\n        \n        long currentImportantTotalUnPacked = 0;\n        long totalFolderUnPacked;\n        \n        for(int i = 0; i < extractFolderInfoVector.size(); i++,\n                currentImportantTotalUnPacked += totalFolderUnPacked) {\n            ExtractFolderInfo efi = extractFolderInfoVector.get(i);\n            totalFolderUnPacked = efi.UnPackSize;\n            \n            int res = extractCallbackSpec.SetCompleted(currentImportantTotalUnPacked);\n            if (res != HRESULT.S_OK) return res;\n\n            FolderOutStream folderOutStream = new FolderOutStream();\n\n            // #ifdef _7Z_VOL\n            // const CVolume &volume = _volumes[efi.VolumeIndex];\n            // const CArchiveDatabaseEx &database = volume.Database;\n            // #else\n            ArchiveDatabaseEx database = _database;\n            //#endif\n            \n            int startIndex; // CNum\n            if (efi.FileIndex != InArchive.kNumNoIndex)\n                startIndex = efi.FileIndex;\n            else\n                startIndex = database.folderStartFileIndex.get(efi.FolderIndex);\n            \n            \n            int result = folderOutStream.Init(database,\n                    // #ifdef _7Z_VOL\n                    // volume.StartRef2Index,\n                    // #else\n                    0,\n                    // #endif\n                    startIndex,\n                    efi.ExtractStatuses, extractCallbackSpec, testMode);\n            \n            if (result != HRESULT.S_OK) return result;\n            \n            if (efi.FileIndex != InArchive.kNumNoIndex)\n                continue;\n            \n            int folderIndex = efi.FolderIndex; // CNum\n            Folder folderInfo = database.Folders.get(folderIndex);\n            \n            LocalProgress localProgressSpec = new LocalProgress();\n            localProgressSpec.Init(extractCallbackSpec, false);\n            \n            LocalCompressProgressInfo localCompressProgressSpec =\n                    new LocalCompressProgressInfo();\n            localCompressProgressSpec.Init(localProgressSpec, ICompressProgressInfo.INVALID , currentImportantTotalUnPacked);\n            \n            int packStreamIndex = database.folderStartPackStreamIndex.get(folderIndex); // CNum\n            long folderStartPackPos = database.GetFolderStreamPos(folderIndex, 0);\n            \n    /*\n    #ifndef _NO_CRYPTO\n    CMyComPtr<ICryptoGetTextPassword> getTextPassword;\n    if (extractCallback)\n      extractCallback.QueryInterface(IID_ICryptoGetTextPassword, &getTextPassword);\n    #endif\n     */\n            \n            try {\n                result = decoder.Decode(\n                        // #ifdef _7Z_VOL\n                        // volume.Stream,\n                        // #else\n                        _inStream,\n                        // #endif\n                        folderStartPackPos,\n                        database.PackSizes,  // database.PackSizes.get(packStreamIndex),\n                        packStreamIndex,\n                        folderInfo,\n                        folderOutStream,\n                        localCompressProgressSpec\n                        // #ifndef _NO_CRYPTO\n                        // , getTextPassword\n                        // #endif\n                        // #ifdef COMPRESS_MT\n                        // , true, _numThreads\n                        // #endif\n                        );\n                \n                if (result == HRESULT.S_FALSE) {\n                    result = folderOutStream.FlushCorrupted(IInArchive.NExtract_NOperationResult_kDataError);\n                    if (result != HRESULT.S_OK) return result;\n                    continue;\n                }\n                if (result == HRESULT.E_NOTIMPL) {\n                    result = folderOutStream.FlushCorrupted(IInArchive.NExtract_NOperationResult_kUnSupportedMethod);\n                    if (result != HRESULT.S_OK) return result;\n                    continue;\n                }\n                if (result != HRESULT.S_OK)\n                    return result;\n                if (folderOutStream.WasWritingFinished() != HRESULT.S_OK) {\n                    result = folderOutStream.FlushCorrupted(IInArchive.NExtract_NOperationResult_kDataError);\n                    if (result != HRESULT.S_OK) return result;\n                }\n            } catch(Exception e) {\n                System.out.println(\"IOException : \" + e);\n                e.printStackTrace();\n                result = folderOutStream.FlushCorrupted(IInArchive.NExtract_NOperationResult_kDataError);\n                if (result != HRESULT.S_OK) return result;\n            }\n        }\n        return HRESULT.S_OK;\n    }\n    \n    \n    public int close() throws IOException {\n/*\n        #ifdef _7Z_VOL\n        _volumes.Clear();\n        _refs.Clear();\n        #else\n        _inStream.Release();\n        _database.Clear();\n        #endif\n        return S_OK;\n */\n        if (_inStream != null) _inStream.close();  // _inStream.Release();\n        _inStream = null;\n        _database.Clear();\n        return 0;\n    }\n    \n    public int size() {\n        return _database.Files.size();\n    }\n    \n    long getPackSize(int index2) {\n        long packSize = 0;\n        int folderIndex = _database.fileIndexToFolderIndexMap.get(index2);\n        if (folderIndex != InArchive.kNumNoIndex) {\n            if (_database.folderStartFileIndex.get(folderIndex) == index2)\n                packSize = _database.GetFolderFullPackSize(folderIndex);\n        }\n        return packSize;\n    }\n    \n    static int GetUInt32FromMemLE(byte [] p , int off) {\n        return p[off] | (((int)p[off + 1]) << 8) | (((int)p[off + 2]) << 16) | (((int)p[off +3]) << 24);\n    }\n    \n    static String GetStringForSizeValue(int value) {\n        for (int i = 31; i >= 0; i--) {\n            if ((1 << i) == value) {\n                return \"\" + i;\n            }\n        }\n        String result = \"\";\n        if (value % (1 << 20) == 0) {\n            result += \"\" + (value >> 20);\n            result += \"m\";\n        } else if (value % (1 << 10) == 0) {\n            result += \"\" + (value >> 10);\n            result += \"k\";\n        } else {\n            result += \"\" + (value);\n            result += \"b\";\n        }\n        return result;\n    }\n    \n    String getMethods(int index2) {\n        int folderIndex = _database.fileIndexToFolderIndexMap.get(index2);\n        if (folderIndex != InArchive.kNumNoIndex) {\n            Folder folderInfo = _database.Folders.get(folderIndex);\n            StringBuilder methodsString = new StringBuilder();\n            for (int i = folderInfo.Coders.size() - 1; i >= 0; i--) {\n                CoderInfo coderInfo = folderInfo.Coders.get(i);\n                if (methodsString.length() != 0) {\n                    methodsString.append(' ');\n                }\n                \n                // MethodInfo methodInfo;\n\n                for (int j = 0; j < coderInfo.AltCoders.size(); j++) {\n                    if (j > 0) {\n                        methodsString.append(\"|\");\n                    }\n                    AltCoderInfo altCoderInfo = coderInfo.AltCoders.get(j);\n                    \n                    String methodName = getMethodName(altCoderInfo);\n                    if (methodName != null) {\n                        methodsString.append(methodName);\n                        \n                        if (altCoderInfo.MethodID.equals(MethodID.k_LZMA)) {\n                            if (altCoderInfo.Properties.GetCapacity() >= 5) {\n                                methodsString.append(\":\");\n                                int dicSize = GetUInt32FromMemLE(altCoderInfo.Properties.data(),1);\n                                methodsString.append(GetStringForSizeValue(dicSize));\n                            }\n                        }\n                        /* else if (altCoderInfo.MethodID == k_PPMD) {\n                            if (altCoderInfo.Properties.GetCapacity() >= 5) {\n                                Byte order = *(const Byte *)altCoderInfo.Properties;\n                                methodsString += \":o\";\n                                methodsString += ConvertUInt32ToString(order);\n                                methodsString += \":mem\";\n                                UInt32 dicSize = GetUInt32FromMemLE(\n                                        ((const Byte *)altCoderInfo.Properties + 1));\n                                methodsString += GetStringForSizeValue(dicSize);\n                            }\n                        } else if (altCoderInfo.MethodID == k_AES) {\n                            if (altCoderInfo.Properties.GetCapacity() >= 1) {\n                                methodsString += \":\";\n                                const Byte *data = (const Byte *)altCoderInfo.Properties;\n                                Byte firstByte = *data++;\n                                UInt32 numCyclesPower = firstByte & 0x3F;\n                                methodsString += ConvertUInt32ToString(numCyclesPower);\n                            }\n                        } else {\n                            if (altCoderInfo.Properties.GetCapacity() > 0) {\n                                methodsString += \":[\";\n                                for (size_t bi = 0; bi < altCoderInfo.Properties.GetCapacity(); bi++) {\n                                    if (bi > 5 && bi + 1 < altCoderInfo.Properties.GetCapacity()) {\n                                        methodsString += \"..\";\n                                        break;\n                                    } else\n                                        methodsString += GetHex2(altCoderInfo.Properties[bi]);\n                                }\n                                methodsString += \"]\";\n                            }\n                        }\n                         */\n                    } else {\n                        // TBD methodsString += altCoderInfo.MethodID.ConvertToString();\n                    }\n                }\n            }\n            return methodsString.toString();\n        }\n        \n        return \"\";\n    }\n\n    private String getMethodName(AltCoderInfo altCoderInfo) {\n        if (altCoderInfo.MethodID.equals(MethodID.k_Copy)) {\n            return \"Copy\";\n        } else if (altCoderInfo.MethodID.equals(MethodID.k_LZMA)) {\n            return \"LZMA\";\n        } else if (altCoderInfo.MethodID.equals(MethodID.k_BCJ)) {\n            return \"BCJ\";\n        } else if (altCoderInfo.MethodID.equals(MethodID.k_BCJ2)) {\n            return \"BCJ2\";\n        } else if (altCoderInfo.MethodID.equals(MethodID.k_PPMD)) {\n            return \"PPMD\";\n        } else if (altCoderInfo.MethodID.equals(MethodID.k_Deflate)) {\n            return \"Deflate\";\n        } else if (altCoderInfo.MethodID.equals(MethodID.k_Deflate64)) {\n            return \"Deflate64\";\n        } else if (altCoderInfo.MethodID.equals(MethodID.k_BZip2)) {\n            return \"BZip2\";\n        } else if (altCoderInfo.MethodID.equals(MethodID.k_7zAES)) {\n            return \"7zAES\";\n        } else {\n            return null;\n        }\n    }\n\n    public SevenZipEntry getEntry(int index) {\n        com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZip.FileItem item = _database.Files.get(index);\n\n        long crc = -1;\n        if (item.IsFileCRCDefined) {\n            crc = item.FileCRC & 0xFFFFFFFFL;\n        }\n        \n        long position = -1;\n        if (item.IsStartPosDefined)\n            position = item.StartPos;\n\n        return new SevenZipEntry(\n                item.name,\n                getPackSize(index),\n                item.UnPackSize,\n                crc,\n                item.LastWriteTime,\n                position,\n                item.IsDirectory,\n                item.Attributes,\n                getMethods(index)\n                );\n    }\n    \n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/SevenZip/Header.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZip;\n\nclass Header\n{\n    public static final int kSignatureSize = 6;\n    public static byte [] kSignature= {'7', 'z', (byte)0xBC, (byte)0xAF, 0x27, 0x1C};\n    public static byte kMajorVersion = 0;\n    \n    public class NID\n    {\n        public static final int kEnd = 0;      \n        public static final int kHeader = 1;\n        public static final int kArchiveProperties = 2;\n        public static final int kAdditionalStreamsInfo = 3;\n        public static final int kMainStreamsInfo = 4;\n        public static final int kFilesInfo = 5;\n    \n        public static final int kPackInfo = 6;\n        public static final int kUnPackInfo = 7;\n        public static final int kSubStreamsInfo = 8;\n\n        public static final int kSize = 9;\n        public static final int kCRC = 10;\n        \n        public static final int kFolder = 11;\n        public static final int kCodersUnPackSize = 12;\n        public static final int kNumUnPackStream = 13;\n \n        public static final int kEmptyStream = 14;\n        public static final int kEmptyFile = 15;\n        public static final int kAnti = 16;\n\n        public static final int kName = 17;\n        public static final int kCreationTime = 18;\n        public static final int kLastAccessTime = 19;\n        public static final int kLastWriteTime = 20;\n        public static final int kWinAttributes = 21;\n        public static final int kComment = 22;\n\n        public static final int kEncodedHeader = 23;\n\n        public static final int kStartPos = 24;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/SevenZip/InArchive.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZip;\r\n\r\n\r\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.*;\r\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.Common.BindPair;\r\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Common.SequentialOutStreamImp2;\r\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Common.StreamUtils;\r\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.HRESULT;\r\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.IInStream;\r\n\r\nimport java.io.IOException;\r\nimport java.io.OutputStream;\r\n\r\n\r\nclass InArchive extends Header {\r\n    \r\n    // CNum\r\n    static public final int kNumMax = 0x7FFFFFFF;\r\n    static public final int kNumNoIndex = 0xFFFFFFFF;\r\n    \r\n    \r\n    IInStream _stream; // CMyComPtr<IInStream> _stream;\r\n    \r\n    ObjectVector<InByte2> _inByteVector;\r\n    InByte2 _inByteBack;\r\n    \r\n    long _arhiveBeginStreamPosition;\r\n    long _position;\r\n    \r\n    public InArchive() {\r\n        _inByteVector = new ObjectVector<>();\r\n        _inByteBack = new InByte2();\r\n    }\r\n    \r\n    public void AddByteStream(byte [] buffer, int size) {\r\n        _inByteVector.add(new InByte2());\r\n        _inByteBack = _inByteVector.Back();\r\n        _inByteBack.Init(buffer, size);\r\n    }\r\n    \r\n    void DeleteByteStream() {\r\n        _inByteVector.DeleteBack();\r\n        if (!_inByteVector.isEmpty())\r\n            _inByteBack = _inByteVector.Back();\r\n    }\r\n    \r\n    static boolean TestSignatureCandidate(byte [] testBytes, int off) {\r\n        for (int i = 0; i < kSignatureSize; i++) {\r\n            // System.out.println(\" \" + i + \":\" + testBytes[i] + \" \" + kSignature[i]);\r\n            if (testBytes[i + off] != kSignature[i])\r\n                return false;\r\n        }\r\n        return true;\r\n    }\r\n    int ReadDirect(IInStream stream, // IInStream *stream,\r\n            byte [] data, // void *data,\r\n            int off,\r\n            int size // UInt32 size,\r\n            ) throws IOException {\r\n        int realProcessedSize = StreamUtils.ReadStream(stream, data, off, size);\r\n        if (realProcessedSize != -1) _position += realProcessedSize;\r\n        return realProcessedSize;\r\n    }\r\n    int ReadDirect(byte [] data, int size) throws IOException {\r\n        return ReadDirect(_stream, data, 0, size);\r\n    }\r\n    \r\n    int SafeReadDirectUInt32() throws IOException {\r\n        int val = 0;\r\n        byte [] b = new byte[4];\r\n        \r\n        int realProcessedSize = ReadDirect(b, 4);\r\n        if (realProcessedSize != 4)\r\n            throw new IOException(\"Unexpected End Of Archive\"); // throw CInArchiveException(CInArchiveException::kUnexpectedEndOfArchive);\r\n        \r\n        for (int i = 0; i < 4; i++) {\r\n            val |= ((b[i] & 0xff) << (8 * i));\r\n        }\r\n        return val;\r\n    }\r\n    \r\n    int ReadUInt32() throws IOException {\r\n        int value = 0;\r\n        for (int i = 0; i < 4; i++) {\r\n            int b = ReadByte();\r\n            value |= ((b) << (8 * i));\r\n        }\r\n        return value;\r\n    }\r\n    \r\n    long ReadUInt64() throws IOException {\r\n        long value = 0;\r\n        for (int i = 0; i < 8; i++) {\r\n            int b = ReadByte();\r\n            value |= (((long)b) << (8 * i));\r\n        }\r\n        return value;\r\n    }\r\n    \r\n    int ReadBytes(byte data[], int size) {\r\n        if (!_inByteBack.ReadBytes(data, size))\r\n            return HRESULT.E_FAIL;\r\n        return HRESULT.S_OK;\r\n    }\r\n    \r\n    int ReadByte()  throws IOException {\r\n        return _inByteBack.ReadByte();\r\n    }\r\n    \r\n    long SafeReadDirectUInt64() throws IOException {\r\n        long val = 0;\r\n        byte [] b = new byte[8];\r\n        \r\n        int realProcessedSize = ReadDirect(b, 8);\r\n        if (realProcessedSize != 8)\r\n            throw new IOException(\"Unexpected End Of Archive\"); // throw CInArchiveException(CInArchiveException::kUnexpectedEndOfArchive);\r\n        \r\n        for (int i = 0; i < 8; i++) {\r\n            val |= ((long)(b[i] & 0xFF) << (8 * i));\r\n        }\r\n        return val;\r\n    }\r\n    \r\n    char ReadWideCharLE()   throws IOException {\r\n        int b1 = _inByteBack.ReadByte();\r\n        int b2 = _inByteBack.ReadByte();\r\n        char c = (char)(((char)(b2) << 8) + b1);\r\n        return c;\r\n    }\r\n    \r\n    long ReadNumber()  throws IOException {\r\n        int firstByte = ReadByte();\r\n        \r\n        int mask = 0x80;\r\n        long value = 0;\r\n        for (int i = 0; i < 8; i++) {\r\n            if ((firstByte & mask) == 0) {\r\n                long highPart = firstByte & (mask - 1);\r\n                value += (highPart << (i * 8));\r\n                return value;\r\n            }\r\n            int b = ReadByte();\r\n            if (b < 0)\r\n                throw new IOException(\"ReadNumber - Can't read stream\");\r\n            \r\n            value |= (((long)b) << (8 * i));\r\n            mask >>= 1;\r\n        }\r\n        return value;\r\n    }\r\n    \r\n    int ReadNum()  throws IOException { // CNum\r\n        long value64 = ReadNumber();\r\n        if (value64 > InArchive.kNumMax)\r\n            throw new IOException(\"ReadNum - value > CNum.kNumMax\"); // return E_FAIL;\r\n        \r\n        return (int)value64;\r\n    }\r\n    \r\n    long ReadID()   throws IOException {\r\n        return ReadNumber();\r\n    }\r\n    \r\n    int FindAndReadSignature(\r\n            IInStream stream, // IInStream *stream,\r\n            long searchHeaderSizeLimit // const UInt64 *searchHeaderSizeLimit\r\n            ) throws IOException {\r\n        _position = _arhiveBeginStreamPosition;\r\n        \r\n        stream.Seek(_arhiveBeginStreamPosition,IInStream.STREAM_SEEK_SET);\r\n        \r\n        byte [] signature = new byte[kSignatureSize];\r\n        \r\n        int processedSize = ReadDirect(stream, signature, 0, kSignatureSize);\r\n        if(processedSize != kSignatureSize)\r\n            return HRESULT.S_FALSE;\r\n        \r\n        if (TestSignatureCandidate(signature,0))\r\n            return HRESULT.S_OK;\r\n        \r\n        // SFX support\r\n        ByteBuffer byteBuffer = new ByteBuffer();\r\n        final int kBufferSize = (1 << 16);\r\n        byteBuffer.SetCapacity(kBufferSize);\r\n        byte [] buffer = byteBuffer.data();\r\n        int numPrevBytes = kSignatureSize - 1;\r\n        \r\n        System.arraycopy(signature,1,buffer,0,numPrevBytes);\r\n        \r\n        long curTestPos = _arhiveBeginStreamPosition + 1;\r\n        for (;;) {\r\n            if (searchHeaderSizeLimit != -1)\r\n                if (curTestPos - _arhiveBeginStreamPosition > searchHeaderSizeLimit)\r\n                    break;\r\n            int numReadBytes = kBufferSize - numPrevBytes;\r\n            \r\n            // RINOK(ReadDirect(stream, buffer + numPrevBytes, numReadBytes, &processedSize));\r\n            processedSize = ReadDirect(stream, buffer,numPrevBytes, numReadBytes);\r\n            if (processedSize == -1) return HRESULT.S_FALSE;\r\n            \r\n            int numBytesInBuffer = numPrevBytes + processedSize;\r\n            if (numBytesInBuffer < kSignatureSize)\r\n                break;\r\n            int numTests = numBytesInBuffer - kSignatureSize + 1;\r\n            for(int pos = 0; pos < numTests; pos++, curTestPos++) {\r\n                if (TestSignatureCandidate(buffer , pos)) {\r\n                    _arhiveBeginStreamPosition = curTestPos;\r\n                    _position = curTestPos + kSignatureSize;\r\n                    stream.Seek(_position, IInStream.STREAM_SEEK_SET);\r\n                    return HRESULT.S_OK;\r\n                }\r\n            }\r\n            numPrevBytes = numBytesInBuffer - numTests;\r\n            System.arraycopy(buffer,numTests,buffer,0,numPrevBytes);\r\n        }\r\n        \r\n        return HRESULT.S_FALSE;\r\n    }\r\n    \r\n    int SkeepData(long size)  throws IOException {\r\n        for (long i = 0; i < size; i++) {\r\n            int temp = ReadByte();\r\n        }\r\n        return HRESULT.S_OK;\r\n    }\r\n    \r\n    int SkeepData()  throws IOException {\r\n        long size = ReadNumber();\r\n        return SkeepData(size);\r\n    }\r\n    \r\n    \r\n    int ReadArchiveProperties(InArchiveInfo archiveInfo)  throws IOException {\r\n        for (;;) {\r\n            long type = ReadID();\r\n            if (type == NID.kEnd)\r\n                break;\r\n            SkeepData();\r\n        }\r\n        return HRESULT.S_OK;\r\n    }\r\n    \r\n    int GetNextFolderItem(Folder folder)  throws IOException {\r\n        int numCoders = ReadNum();\r\n        \r\n        folder.Coders.clear();\r\n        folder.Coders.Reserve(numCoders);\r\n        int numInStreams = 0;\r\n        int numOutStreams = 0;\r\n        int i;\r\n        for (i = 0; i < numCoders; i++) {\r\n            folder.Coders.add(new CoderInfo());\r\n            CoderInfo coder = folder.Coders.Back();\r\n            \r\n            for (;;) {\r\n                coder.AltCoders.add(new AltCoderInfo());\r\n                AltCoderInfo altCoder = coder.AltCoders.Back();\r\n                int mainByte = ReadByte();\r\n                altCoder.MethodID.IDSize = (byte)(mainByte & 0xF);\r\n                int ret = ReadBytes(altCoder.MethodID.ID, altCoder.MethodID.IDSize);\r\n                if (ret != HRESULT.S_OK) return ret;\r\n                if ((mainByte & 0x10) != 0) {\r\n                    coder.NumInStreams = ReadNum();\r\n                    coder.NumOutStreams = ReadNum();\r\n                } else {\r\n                    coder.NumInStreams = 1;\r\n                    coder.NumOutStreams = 1;\r\n                }\r\n                if ((mainByte & 0x20) != 0) {\r\n                    int propertiesSize = ReadNum();\r\n                    altCoder.Properties.SetCapacity(propertiesSize);\r\n                    // RINOK(ReadBytes((Byte *)altCoder.Properties, (size_t)propertiesSize));\r\n                    ret = ReadBytes(altCoder.Properties.data(), propertiesSize);\r\n                }\r\n                if ((mainByte & 0x80) == 0)\r\n                    break;\r\n            }\r\n            numInStreams += coder.NumInStreams;\r\n            numOutStreams += coder.NumOutStreams;\r\n        }\r\n        \r\n        // RINOK(ReadNumber(numBindPairs));\r\n        int numBindPairs = numOutStreams - 1;\r\n        folder.BindPairs.clear();\r\n        folder.BindPairs.Reserve(numBindPairs);\r\n        for (i = 0; i < numBindPairs; i++) {\r\n            BindPair bindPair = new BindPair();\r\n            bindPair.InIndex = ReadNum();\r\n            bindPair.OutIndex = ReadNum();\r\n            folder.BindPairs.add(bindPair);\r\n        }\r\n        \r\n        int numPackedStreams = numInStreams - numBindPairs;\r\n        folder.PackStreams.Reserve(numPackedStreams);\r\n        if (numPackedStreams == 1) {\r\n            for (int j = 0; j < numInStreams; j++)\r\n                if (folder.FindBindPairForInStream(j) < 0) {\r\n                folder.PackStreams.add(j);\r\n                break;\r\n                }\r\n        } else\r\n            for(i = 0; i < numPackedStreams; i++) {\r\n            int packStreamInfo = ReadNum();\r\n            folder.PackStreams.add(packStreamInfo);\r\n            }\r\n        \r\n        return HRESULT.S_OK;\r\n    }\r\n    \r\n    int WaitAttribute(long attribute)  throws IOException {\r\n        for (;;) {\r\n            long type = ReadID();\r\n            if (type == attribute)\r\n                return HRESULT.S_OK;\r\n            if (type == NID.kEnd)\r\n                return HRESULT.S_FALSE;\r\n            int ret = SkeepData();\r\n            if (ret != HRESULT.S_OK) return ret;\r\n        }\r\n    }\r\n    \r\n    int Open(\r\n            IInStream stream,  // IInStream *stream\r\n            long searchHeaderSizeLimit // const UInt64 *searchHeaderSizeLimit\r\n            ) throws IOException {\r\n        Close();\r\n        \r\n        _arhiveBeginStreamPosition = stream.Seek(0, IInStream.STREAM_SEEK_CUR);\r\n        _position = _arhiveBeginStreamPosition;\r\n        \r\n        int ret = FindAndReadSignature(stream, searchHeaderSizeLimit);\r\n        if (ret != HRESULT.S_OK) return ret;\r\n        \r\n        _stream = stream;\r\n        \r\n        return HRESULT.S_OK;\r\n    }\r\n    \r\n    void Close()  throws IOException {\r\n        if (_stream != null) _stream.close(); // _stream.Release();\r\n        _stream = null;\r\n    }\r\n    \r\n    int ReadStreamsInfo(\r\n            ObjectVector<ByteBuffer> dataVector,\r\n            long [] dataOffset,\r\n            LongVector packSizes,\r\n            BoolVector packCRCsDefined,\r\n            IntVector packCRCs,\r\n            ObjectVector<Folder> folders,\r\n            IntVector numUnPackStreamsInFolders,\r\n            LongVector unPackSizes,\r\n            BoolVector digestsDefined,\r\n            IntVector digests)  throws IOException {\r\n        \r\n        for (;;) {\r\n            long type = ReadID();\r\n            switch((int)type) {\r\n                case NID.kEnd:\r\n                    return HRESULT.S_OK;\r\n                case NID.kPackInfo:\r\n                {\r\n                    int result = ReadPackInfo(dataOffset, packSizes,\r\n                            packCRCsDefined, packCRCs);\r\n                    if (result != HRESULT.S_OK) return result;\r\n                    break;\r\n                }\r\n                case NID.kUnPackInfo:\r\n                {\r\n                    int result = ReadUnPackInfo(dataVector, folders);\r\n                    if (result != HRESULT.S_OK) return result;\r\n                    break;\r\n                }\r\n                case NID.kSubStreamsInfo:\r\n                {\r\n                    int result = ReadSubStreamsInfo(folders, numUnPackStreamsInFolders,\r\n                            unPackSizes, digestsDefined, digests);\r\n                    if (result != HRESULT.S_OK) return result;\r\n                    break;\r\n                }\r\n            }\r\n        }\r\n    }\r\n    \r\n    int ReadFileNames(ObjectVector<FileItem> files)  throws IOException {\r\n        for (FileItem file : files) {\r\n            String name = \"\";\r\n            for (; ; ) {\r\n                char c = ReadWideCharLE();\r\n                if (c == '\\0')\r\n                    break;\r\n                name += c;\r\n            }\r\n            file.name = name;\r\n        }\r\n        return HRESULT.S_OK;\r\n    }\r\n    \r\n    int ReadBoolVector(int numItems, BoolVector v)  throws IOException {\r\n        v.clear();\r\n        v.Reserve(numItems);\r\n        int b = 0;\r\n        int mask = 0;\r\n        for(int i = 0; i < numItems; i++) {\r\n            if (mask == 0) {\r\n                b = ReadByte();\r\n                mask = 0x80;\r\n            }\r\n            v.add((b & mask) != 0);\r\n            mask >>= 1;\r\n        }\r\n        return HRESULT.S_OK;\r\n    }\r\n    \r\n    int ReadBoolVector2(int numItems, BoolVector v)  throws IOException { // CBoolVector\r\n        int allAreDefined = ReadByte();\r\n        if (allAreDefined == 0)\r\n            return ReadBoolVector(numItems, v);\r\n        v.clear();\r\n        v.Reserve(numItems);\r\n        for (int i = 0; i < numItems; i++)\r\n            v.add(true);\r\n        return HRESULT.S_OK;\r\n    }\r\n    \r\n    int ReadHashDigests(int numItems,\r\n            BoolVector digestsDefined,\r\n            IntVector digests)  throws IOException {\r\n        int ret = ReadBoolVector2(numItems, digestsDefined);\r\n        if (ret != HRESULT.S_OK) return ret;\r\n        \r\n        digests.clear();\r\n        digests.Reserve(numItems);\r\n        for(int i = 0; i < numItems; i++) {\r\n            int crc = 0;\r\n            if (digestsDefined.get(i))\r\n                crc = ReadUInt32();\r\n            digests.add(crc);\r\n        }\r\n        return HRESULT.S_OK;\r\n    }\r\n    \r\n    int ReadPackInfo(\r\n            long []  dataOffset, // UInt64 &dataOffset,\r\n            LongVector packSizes, // CRecordVector<UInt64> &packSizes,\r\n            BoolVector packCRCsDefined, // CRecordVector<bool> &packCRCsDefined,\r\n            IntVector packCRCs) // CRecordVector<UInt32> &packCRCs)\r\n            throws IOException {\r\n        dataOffset[0] = ReadNumber();\r\n        int numPackStreams = ReadNum();\r\n        \r\n        int ret = WaitAttribute(NID.kSize);\r\n        if (ret != HRESULT.S_OK) return ret;\r\n        \r\n        packSizes.clear();\r\n        packSizes.Reserve(numPackStreams);\r\n        for(int i = 0; i < numPackStreams; i++) // CNum i\r\n        {\r\n            long size = ReadNumber();\r\n            packSizes.add(size);\r\n        }\r\n        \r\n        long type;\r\n        for (;;) {\r\n            type = ReadID();\r\n            if (type == NID.kEnd)\r\n                break;\r\n            if (type == NID.kCRC) {\r\n                ret = ReadHashDigests(numPackStreams, packCRCsDefined, packCRCs);\r\n                if (ret != HRESULT.S_OK) return ret;\r\n                continue;\r\n            }\r\n            ret = SkeepData();\r\n            if (ret != HRESULT.S_OK) return ret;\r\n        }\r\n        if (packCRCsDefined.isEmpty()) {\r\n            packCRCsDefined.Reserve(numPackStreams);\r\n            packCRCsDefined.clear();\r\n            packCRCs.Reserve(numPackStreams);\r\n            packCRCs.clear();\r\n            for(int i = 0; i < numPackStreams; i++) {\r\n                packCRCsDefined.add(false);\r\n                packCRCs.add(0);\r\n            }\r\n        }\r\n        return HRESULT.S_OK;\r\n    }\r\n    \r\n    int ReadUnPackInfo(\r\n            ObjectVector<ByteBuffer> dataVector,\r\n            ObjectVector<Folder> folders)  throws IOException {\r\n        int ret = WaitAttribute(NID.kFolder);\r\n        if (ret != HRESULT.S_OK) return ret;\r\n        \r\n        int numFolders = ReadNum();\r\n        \r\n        {\r\n            StreamSwitch streamSwitch = new StreamSwitch();\r\n            ret = streamSwitch.Set(this, dataVector);\r\n            if (ret != HRESULT.S_OK) return ret;\r\n            folders.clear();\r\n            folders.Reserve(numFolders);\r\n            for(int i = 0; i < numFolders; i++) {\r\n                folders.add(new Folder());\r\n                ret = GetNextFolderItem(folders.Back());\r\n                if (ret != HRESULT.S_OK) {\r\n                    streamSwitch.close();\r\n                    return ret;\r\n                }\r\n            }\r\n            streamSwitch.close();\r\n        }\r\n        \r\n        ret = WaitAttribute(NID.kCodersUnPackSize);\r\n        if (ret != HRESULT.S_OK) return ret;\r\n        \r\n        for(int i = 0; i < numFolders; i++) {\r\n            Folder folder = folders.get(i);\r\n            int numOutStreams = folder.GetNumOutStreams();\r\n            folder.UnPackSizes.Reserve(numOutStreams);\r\n            for(int j = 0; j < numOutStreams; j++) {\r\n                long unPackSize = ReadNumber();\r\n                folder.UnPackSizes.add(unPackSize);\r\n            }\r\n        }\r\n        \r\n        for (;;) {\r\n            long type = ReadID();\r\n            if (type == NID.kEnd)\r\n                return HRESULT.S_OK;\r\n            if (type == NID.kCRC) {\r\n                BoolVector crcsDefined = new BoolVector();\r\n                IntVector crcs = new IntVector();\r\n                ret = ReadHashDigests(numFolders, crcsDefined, crcs);\r\n                if (ret != HRESULT.S_OK) return ret;\r\n                for(int i = 0; i < numFolders; i++) {\r\n                    Folder folder = folders.get(i);\r\n                    folder.UnPackCRCDefined = crcsDefined.get(i);\r\n                    folder.UnPackCRC = crcs.get(i);\r\n                }\r\n                continue;\r\n            }\r\n            ret = SkeepData();\r\n            if (ret != HRESULT.S_OK) return ret;\r\n        }\r\n    }\r\n    \r\n    int ReadSubStreamsInfo(\r\n            ObjectVector<Folder> folders,\r\n            IntVector numUnPackStreamsInFolders,\r\n            LongVector unPackSizes,\r\n            BoolVector digestsDefined,\r\n            IntVector digests)  throws IOException {\r\n        numUnPackStreamsInFolders.clear();\r\n        numUnPackStreamsInFolders.Reserve(folders.size());\r\n        long type;\r\n        for (;;) {\r\n            type = ReadID();\r\n            if (type == NID.kNumUnPackStream) {\r\n                for(int i = 0; i < folders.size(); i++) {\r\n                    int value = ReadNum();\r\n                    numUnPackStreamsInFolders.add(value);\r\n                }\r\n                continue;\r\n            }\r\n            if (type == NID.kCRC || type == NID.kSize)\r\n                break;\r\n            if (type == NID.kEnd)\r\n                break;\r\n            int ret = SkeepData();\r\n            if (ret != HRESULT.S_OK) return ret;\r\n        }\r\n        \r\n        if (numUnPackStreamsInFolders.isEmpty())\r\n            for(int i = 0; i < folders.size(); i++)\r\n                numUnPackStreamsInFolders.add(1);\r\n        \r\n        for(int i = 0; i < numUnPackStreamsInFolders.size(); i++) {\r\n            // v3.13 incorrectly worked with empty folders\r\n            // v4.07: we check that folder is empty\r\n            int numSubstreams = numUnPackStreamsInFolders.get(i);\r\n            if (numSubstreams == 0)\r\n                continue;\r\n            long sum = 0;\r\n            for (int j = 1; j < numSubstreams; j++) {\r\n                long size;\r\n                if (type == NID.kSize) {\r\n                    size = ReadNumber();\r\n                    unPackSizes.add(size);\r\n                    sum += size;\r\n                }\r\n            }\r\n            unPackSizes.add(folders.get(i).GetUnPackSize() - sum);\r\n        }\r\n        if (type == NID.kSize) {\r\n            type = ReadID();\r\n        }\r\n        \r\n        int numDigests = 0;\r\n        int numDigestsTotal = 0;\r\n        for(int i = 0; i < folders.size(); i++) {\r\n            int numSubstreams = numUnPackStreamsInFolders.get(i);\r\n            if (numSubstreams != 1 || !folders.get(i).UnPackCRCDefined)\r\n                numDigests += numSubstreams;\r\n            numDigestsTotal += numSubstreams;\r\n        }\r\n        \r\n        for (;;) {\r\n            if (type == NID.kCRC) {\r\n                BoolVector digestsDefined2 = new BoolVector();\r\n                IntVector digests2 = new IntVector();\r\n                int ret = ReadHashDigests(numDigests, digestsDefined2, digests2);\r\n                if (ret != HRESULT.S_OK) return ret;\r\n                \r\n                int digestIndex = 0;\r\n                for (int i = 0; i < folders.size(); i++) {\r\n                    int numSubstreams = numUnPackStreamsInFolders.get(i);\r\n                    Folder folder = folders.get(i);\r\n                    if (numSubstreams == 1 && folder.UnPackCRCDefined) {\r\n                        digestsDefined.add(true);\r\n                        digests.add(folder.UnPackCRC);\r\n                    } else\r\n                        for (int j = 0; j < numSubstreams; j++, digestIndex++) {\r\n                        digestsDefined.add(digestsDefined2.get(digestIndex));\r\n                        digests.add(digests2.get(digestIndex));\r\n                        }\r\n                }\r\n            } else if (type == NID.kEnd) {\r\n                if (digestsDefined.isEmpty()) {\r\n                    digestsDefined.clear();\r\n                    digests.clear();\r\n                    for (int i = 0; i < numDigestsTotal; i++) {\r\n                        digestsDefined.add(false);\r\n                        digests.add(0);\r\n                    }\r\n                }\r\n                return HRESULT.S_OK;\r\n            } else {\r\n                int ret = SkeepData();\r\n                if (ret != HRESULT.S_OK) return ret;\r\n            }\r\n            type = ReadID();\r\n        }\r\n    }\r\n    \r\n    static final long SECS_BETWEEN_EPOCHS = 11644473600L;\r\n    static final long SECS_TO_100NS = 10000000L; /* 10^7 */\r\n    \r\n    static long FileTimeToLong(int dwHighDateTime, int dwLowDateTime) {\r\n        // The FILETIME structure is a 64-bit value representing the number of 100-nanosecond intervals since January 1\r\n        long tm = dwHighDateTime;\r\n        tm <<=32;\r\n        tm |= (dwLowDateTime & 0xFFFFFFFFL);\r\n        return (tm -  (SECS_BETWEEN_EPOCHS * SECS_TO_100NS)) / (10000L); /* now convert to milliseconds */\r\n    }\r\n    \r\n    int ReadTime(ObjectVector<ByteBuffer> dataVector,\r\n            ObjectVector<FileItem> files, long type)  throws IOException {\r\n        BoolVector boolVector = new BoolVector();\r\n        int ret = ReadBoolVector2(files.size(), boolVector);\r\n        if (ret != HRESULT.S_OK) return ret;\r\n        \r\n        StreamSwitch streamSwitch = new StreamSwitch();\r\n        ret = streamSwitch.Set(this, dataVector);\r\n        if (ret != HRESULT.S_OK) {\r\n            streamSwitch.close();\r\n            return ret;\r\n        }\r\n        \r\n        for(int i = 0; i < files.size(); i++) {\r\n            FileItem file = files.get(i);\r\n            int low = 0;\r\n            int high = 0;\r\n            boolean defined = boolVector.get(i);\r\n            if (defined) {\r\n                low = ReadUInt32();\r\n                high = ReadUInt32();\r\n            }\r\n            switch((int)type) {\r\n                case NID.kCreationTime:\r\n                    // file.IsCreationTimeDefined = defined;\r\n                    if (defined)\r\n                        file.CreationTime = FileTimeToLong(high,low);\r\n                    break;\r\n                case NID.kLastWriteTime:\r\n                    // file.IsLastWriteTimeDefined = defined;\r\n                    if (defined)\r\n                        file.LastWriteTime = FileTimeToLong(high,low);\r\n                    break;\r\n                case NID.kLastAccessTime:\r\n                    // file.IsLastAccessTimeDefined = defined;\r\n                    if (defined)\r\n                        file.LastAccessTime = FileTimeToLong(high,low);\r\n                    break;\r\n            }\r\n        }\r\n        streamSwitch.close();\r\n        return HRESULT.S_OK;\r\n    }\r\n    \r\n    int ReadAndDecodePackedStreams(long baseOffset,\r\n            long [] dataOffset,\r\n            ObjectVector<ByteBuffer> dataVector // CObjectVector<CByteBuffer> &dataVector\r\n            ) throws IOException {\r\n        LongVector packSizes = new LongVector(); // CRecordVector<UInt64> packSizes;\r\n        BoolVector packCRCsDefined = new BoolVector(); // CRecordVector<bool> packCRCsDefined;\r\n        IntVector packCRCs = new IntVector(); // CRecordVector<UInt32> packCRCs;\r\n        \r\n        ObjectVector<Folder> folders = new ObjectVector<>();\r\n        \r\n        IntVector numUnPackStreamsInFolders = new IntVector();\r\n        LongVector unPackSizes = new LongVector();\r\n        BoolVector digestsDefined = new BoolVector();\r\n        IntVector digests = new IntVector();\r\n        \r\n        int ret = ReadStreamsInfo(null,\r\n                dataOffset,\r\n                packSizes,\r\n                packCRCsDefined,\r\n                packCRCs,\r\n                folders,\r\n                numUnPackStreamsInFolders,\r\n                unPackSizes,\r\n                digestsDefined,\r\n                digests);\r\n        \r\n        // database.ArchiveInfo.DataStartPosition2 += database.ArchiveInfo.StartPositionAfterHeader;\r\n        \r\n        int packIndex = 0;\r\n        Decoder decoder = new Decoder(false); // _ST_MODE\r\n        \r\n        long dataStartPos = baseOffset + dataOffset[0];\r\n        for (Folder folder : folders) {\r\n            dataVector.add(new ByteBuffer());\r\n            ByteBuffer data = dataVector.Back();\r\n            long unPackSize = folder.GetUnPackSize();\r\n            if (unPackSize > InArchive.kNumMax)\r\n                return HRESULT.E_FAIL;\r\n            if (unPackSize > 0xFFFFFFFFL)\r\n                return HRESULT.E_FAIL;\r\n            data.SetCapacity((int) unPackSize);\r\n            \r\n            SequentialOutStreamImp2 outStreamSpec = new SequentialOutStreamImp2();\r\n            OutputStream outStream = outStreamSpec;\r\n            outStreamSpec.Init(data.data(), (int) unPackSize);\r\n            \r\n            int result = decoder.Decode(_stream, dataStartPos,\r\n                    packSizes, packIndex,  // &packSizes[packIndex]\r\n                    folder, outStream, null\r\n                    // _ST_MODE , false, 1\r\n                    );\r\n            if (result != HRESULT.S_OK) return result;\r\n            \r\n            if (folder.UnPackCRCDefined)\r\n                if (!CRC.verifyDigest(folder.UnPackCRC, data.data(), (int) unPackSize))\r\n                    throw new IOException(\"Incorrect Header\"); // CInArchiveException(CInArchiveException::kIncorrectHeader);\r\n            for (int j = 0; j < folder.PackStreams.size(); j++)\r\n                dataStartPos += packSizes.get(packIndex++);\r\n        }\r\n        return HRESULT.S_OK;\r\n    }\r\n    \r\n    int ReadDatabase(ArchiveDatabaseEx database) throws IOException {\r\n        database.Clear();\r\n        database.ArchiveInfo.StartPosition = _arhiveBeginStreamPosition;\r\n        \r\n        byte [] btmp = new byte[2];\r\n        int realProcessedSize = ReadDirect(btmp, 2);\r\n        if (realProcessedSize != 2)\r\n            throw new IOException(\"Unexpected End Of Archive\"); // throw CInArchiveException(CInArchiveException::kUnexpectedEndOfArchive);\r\n        \r\n        database.ArchiveInfo.ArchiveVersion_Major = btmp[0];\r\n        database.ArchiveInfo.ArchiveVersion_Minor = btmp[1];\r\n        \r\n        if (database.ArchiveInfo.ArchiveVersion_Major != kMajorVersion)\r\n            throw new IOException(\"Unsupported Version\");\r\n        \r\n        CRC crc = new CRC();\r\n        int crcFromArchive = SafeReadDirectUInt32();\r\n        long nextHeaderOffset = SafeReadDirectUInt64();\r\n        long nextHeaderSize = SafeReadDirectUInt64();\r\n        int nextHeaderCRC = SafeReadDirectUInt32();\r\n        \r\n/*\r\n  #ifdef FORMAT_7Z_RECOVERY\r\n  ...\r\n  #endif\r\n */\r\n        \r\n        crc.updateUInt64(nextHeaderOffset);\r\n        crc.updateUInt64(nextHeaderSize);\r\n        crc.updateUInt32(nextHeaderCRC);\r\n        \r\n        database.ArchiveInfo.StartPositionAfterHeader = _position;\r\n        \r\n        if (crc.getDigest() != crcFromArchive)\r\n            throw new IOException(\"Incorrect Header\"); // CInArchiveException(CInArchiveException::kIncorrectHeader);\r\n        \r\n        if (nextHeaderSize == 0)\r\n            return HRESULT.S_OK;\r\n        \r\n        if (nextHeaderSize >= 0xFFFFFFFFL)\r\n            return HRESULT.E_FAIL;\r\n        \r\n        _position = _stream.Seek(nextHeaderOffset,IInStream.STREAM_SEEK_CUR);\r\n        \r\n        ByteBuffer buffer2 = new ByteBuffer();\r\n        buffer2.SetCapacity((int)nextHeaderSize);\r\n        \r\n        // SafeReadDirect(buffer2.data(), (int)nextHeaderSize);\r\n        realProcessedSize = ReadDirect(buffer2.data(), (int)nextHeaderSize);\r\n        if (realProcessedSize != (int)nextHeaderSize)\r\n            throw new IOException(\"Unexpected End Of Archive\"); // throw CInArchiveException(CInArchiveException::kUnexpectedEndOfArchive);\r\n        \r\n        if (!CRC.verifyDigest(nextHeaderCRC, buffer2.data(), (int) nextHeaderSize))\r\n            throw new IOException(\"Incorrect Header\"); // CInArchiveException(CInArchiveException::kIncorrectHeader);\r\n        \r\n        StreamSwitch streamSwitch = new StreamSwitch();\r\n        streamSwitch.Set(this, buffer2);\r\n        \r\n        ObjectVector<ByteBuffer> dataVector = new ObjectVector<>(); // CObjectVector<CByteBuffer> dataVector;\r\n        \r\n        for (;;) {\r\n            long type = ReadID();\r\n            if (type == NID.kHeader)\r\n                break;\r\n            if (type != NID.kEncodedHeader)\r\n                throw new IOException(\"Incorrect Header\"); // CInArchiveException(CInArchiveException::kIncorrectHeader);\r\n            \r\n            long [] ltmp = new long[1];\r\n            ltmp[0] = database.ArchiveInfo.DataStartPosition2;\r\n            int result = ReadAndDecodePackedStreams(\r\n                    database.ArchiveInfo.StartPositionAfterHeader,\r\n                    ltmp, // database.ArchiveInfo.DataStartPosition2,\r\n                    dataVector);\r\n            if (result != HRESULT.S_OK) return result;\r\n            \r\n            database.ArchiveInfo.DataStartPosition2 = ltmp[0];\r\n            \r\n            if (dataVector.size() == 0)\r\n                return HRESULT.S_OK;\r\n            if (dataVector.size() > 1)\r\n                throw new IOException(\"Incorrect Header\"); // CInArchiveException(CInArchiveException::kIncorrectHeader);\r\n            streamSwitch.Remove();\r\n            streamSwitch.Set(this, dataVector.get(0)); // dataVector.Front()\r\n        }\r\n        \r\n        streamSwitch.close();\r\n        return ReadHeader(database);\r\n    }\r\n    \r\n    int ReadHeader(ArchiveDatabaseEx database)  throws IOException {\r\n        long type = ReadID();\r\n        \r\n        if (type == NID.kArchiveProperties) {\r\n            int ret = ReadArchiveProperties(database.ArchiveInfo);\r\n            if (ret != HRESULT.S_OK) return ret;\r\n            type = ReadID();\r\n        }\r\n        \r\n        ObjectVector<ByteBuffer> dataVector = new ObjectVector<>();\r\n        \r\n        if (type == NID.kAdditionalStreamsInfo) {\r\n            long [] ltmp = new long[1];\r\n            ltmp[0] = database.ArchiveInfo.DataStartPosition2;\r\n            int result = ReadAndDecodePackedStreams(\r\n                    database.ArchiveInfo.StartPositionAfterHeader,\r\n                    ltmp, // database.ArchiveInfo.DataStartPosition2,\r\n                    dataVector);\r\n            if (result != HRESULT.S_OK) return result;\r\n            \r\n            database.ArchiveInfo.DataStartPosition2 = ltmp[0];\r\n            \r\n            database.ArchiveInfo.DataStartPosition2 += database.ArchiveInfo.StartPositionAfterHeader;\r\n            type = ReadID();\r\n        }\r\n        \r\n        LongVector unPackSizes = new LongVector();\r\n        BoolVector digestsDefined = new BoolVector();\r\n        IntVector digests = new IntVector();\r\n        \r\n        if (type == NID.kMainStreamsInfo) {\r\n            long [] ltmp = new long[1];\r\n            ltmp[0] = database.ArchiveInfo.DataStartPosition;\r\n            int result = ReadStreamsInfo(dataVector,\r\n                    ltmp, // database.ArchiveInfo.DataStartPosition,\r\n                    database.PackSizes,\r\n                    database.PackCRCsDefined,\r\n                    database.PackCRCs,\r\n                    database.Folders,\r\n                    database.NumUnPackStreamsVector,\r\n                    unPackSizes,\r\n                    digestsDefined,\r\n                    digests);\r\n            if (result != HRESULT.S_OK) return result;\r\n            database.ArchiveInfo.DataStartPosition = ltmp[0];\r\n            database.ArchiveInfo.DataStartPosition += database.ArchiveInfo.StartPositionAfterHeader;\r\n            type = ReadID();\r\n        } else {\r\n            for(int i = 0; i < database.Folders.size(); i++) {\r\n                database.NumUnPackStreamsVector.add(1);\r\n                Folder folder = database.Folders.get(i);\r\n                unPackSizes.add(folder.GetUnPackSize());\r\n                digestsDefined.add(folder.UnPackCRCDefined);\r\n                digests.add(folder.UnPackCRC);\r\n            }\r\n        }\r\n        \r\n        database.Files.clear();\r\n        \r\n        if (type == NID.kEnd)\r\n            return HRESULT.S_OK;\r\n        if (type != NID.kFilesInfo)\r\n            throw new IOException(\"Incorrect Header\"); // throw CInArchiveException(CInArchiveException::kIncorrectHeader);\r\n        \r\n        int numFiles = ReadNum();\r\n        database.Files.Reserve(numFiles);\r\n        for(int i = 0; i < numFiles; i++)\r\n            database.Files.add(new FileItem());\r\n        \r\n        database.ArchiveInfo.FileInfoPopIDs.add(NID.kSize);\r\n        if (!database.PackSizes.isEmpty())\r\n            database.ArchiveInfo.FileInfoPopIDs.add(NID.kPackInfo);\r\n        if (numFiles > 0  && !digests.isEmpty())\r\n            database.ArchiveInfo.FileInfoPopIDs.add(NID.kCRC);\r\n        \r\n        BoolVector emptyStreamVector = new BoolVector();\r\n        emptyStreamVector.Reserve(numFiles);\r\n        for(int i = 0; i < numFiles; i++)\r\n            emptyStreamVector.add(false);\r\n        BoolVector emptyFileVector = new BoolVector();\r\n        BoolVector antiFileVector = new BoolVector();\r\n        int numEmptyStreams = 0;\r\n        \r\n        // int sizePrev = -1;\r\n        // int posPrev = 0;\r\n        \r\n        for (;;) {\r\n            type = ReadID();\r\n            if (type == NID.kEnd)\r\n                break;\r\n            long size = ReadNumber();\r\n            \r\n            // sizePrev = size;\r\n            // posPrev = _inByteBack->GetProcessedSize();\r\n            \r\n            database.ArchiveInfo.FileInfoPopIDs.add(type);\r\n            switch((int)type) {\r\n                case NID.kName:\r\n                {\r\n                    StreamSwitch streamSwitch = new StreamSwitch();\r\n                    int result = streamSwitch.Set(this, dataVector);\r\n                    if (result != HRESULT.S_OK) return result;\r\n                    result = ReadFileNames(database.Files);\r\n                    streamSwitch.close();\r\n                    if (result != HRESULT.S_OK) return result;\r\n                    break;\r\n                }\r\n                case NID.kWinAttributes:\r\n                {\r\n                    BoolVector boolVector = new BoolVector();\r\n                    int result = ReadBoolVector2(database.Files.size(), boolVector);\r\n                    if (result != HRESULT.S_OK) return result;\r\n                    \r\n                    StreamSwitch streamSwitch = new StreamSwitch();\r\n                    result = streamSwitch.Set(this, dataVector);\r\n                    if (result != HRESULT.S_OK) return result;\r\n                    for(int i = 0; i < numFiles; i++) {\r\n                        FileItem file = database.Files.get(i);\r\n                        file.AreAttributesDefined = boolVector.get(i);\r\n                        if (file.AreAttributesDefined) {\r\n                            file.Attributes = ReadUInt32();\r\n                        }\r\n                    }\r\n                    streamSwitch.close();\r\n                    break;\r\n                }\r\n                case NID.kStartPos:\r\n                {\r\n                    BoolVector boolVector = new BoolVector();\r\n                    int result = ReadBoolVector2(database.Files.size(), boolVector);\r\n                    if (result != HRESULT.S_OK) return result;\r\n                    \r\n                    StreamSwitch streamSwitch = new StreamSwitch();\r\n                    result = streamSwitch.Set(this, dataVector);\r\n                    if (result != HRESULT.S_OK) return result;\r\n                    for(int i = 0; i < numFiles; i++) {\r\n                        FileItem file = database.Files.get(i);\r\n                        file.IsStartPosDefined = boolVector.get(i);\r\n                        if (file.IsStartPosDefined) {\r\n                            file.StartPos = ReadUInt64();\r\n                        }\r\n                    }\r\n                    streamSwitch.close();\r\n                    break;\r\n                }\r\n                case NID.kEmptyStream:\r\n                {\r\n                    int result = ReadBoolVector(numFiles, emptyStreamVector);\r\n                    if (result != HRESULT.S_OK) return result;\r\n                    \r\n                    for (int i = 0; i < emptyStreamVector.size(); i++)\r\n                        if (emptyStreamVector.get(i))\r\n                            numEmptyStreams++;\r\n                    emptyFileVector.Reserve(numEmptyStreams);\r\n                    antiFileVector.Reserve(numEmptyStreams);\r\n                    for (int i = 0; i < numEmptyStreams; i++) {\r\n                        emptyFileVector.add(false);\r\n                        antiFileVector.add(false);\r\n                    }\r\n                    break;\r\n                }\r\n                case NID.kEmptyFile:\r\n                {\r\n                    int result = ReadBoolVector(numEmptyStreams, emptyFileVector);\r\n                    if (result != HRESULT.S_OK) return result;\r\n                    break;\r\n                }\r\n                case NID.kAnti:\r\n                {\r\n                    int result = ReadBoolVector(numEmptyStreams, antiFileVector);\r\n                    if (result != HRESULT.S_OK) return result;\r\n                    break;\r\n                }\r\n                case NID.kCreationTime:\r\n                case NID.kLastWriteTime:\r\n                case NID.kLastAccessTime:\r\n                {\r\n                    int result = ReadTime(dataVector, database.Files, type);\r\n                    if (result != HRESULT.S_OK) return result;\r\n                    break;\r\n                }\r\n                default:\r\n                {\r\n                    database.ArchiveInfo.FileInfoPopIDs.DeleteBack();\r\n                    int result = SkeepData(size);\r\n                    if (result != HRESULT.S_OK) return result;\r\n                }\r\n            }\r\n        }\r\n        \r\n        int emptyFileIndex = 0;\r\n        int sizeIndex = 0;\r\n        for(int i = 0; i < numFiles; i++) {\r\n            FileItem file = database.Files.get(i);\r\n            file.HasStream = !emptyStreamVector.get(i);\r\n            if(file.HasStream) {\r\n                file.IsDirectory = false;\r\n                file.IsAnti = false;\r\n                file.UnPackSize = unPackSizes.get(sizeIndex);\r\n                file.FileCRC = digests.get(sizeIndex);\r\n                file.IsFileCRCDefined = digestsDefined.get(sizeIndex);\r\n                sizeIndex++;\r\n            } else {\r\n                file.IsDirectory = !emptyFileVector.get(emptyFileIndex);\r\n                file.IsAnti = antiFileVector.get(emptyFileIndex);\r\n                emptyFileIndex++;\r\n                file.UnPackSize = 0;\r\n                file.IsFileCRCDefined = false;\r\n            }\r\n        }\r\n        \r\n        return HRESULT.S_OK;\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/SevenZip/InArchiveInfo.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZip;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.LongVector;\n\nclass InArchiveInfo\n{\n    public byte ArchiveVersion_Major;\n    public byte ArchiveVersion_Minor;\n    \n    public long StartPosition;\n    public long StartPositionAfterHeader;\n    public long DataStartPosition;\n    public long DataStartPosition2;    \n    LongVector FileInfoPopIDs = new LongVector();\n    \n    void Clear()\n    {\n        FileInfoPopIDs.clear();\n    }    \n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/SevenZip/InByte2.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZip;\nimport java.io.IOException;\n\nclass InByte2 {\n    byte [] _buffer;\n    int _size;\n    int _pos;\n    \n    public void Init(byte [] buffer, int size) {\n        _buffer = buffer;\n        _size = size;\n        _pos = 0;\n    }\n    public int ReadByte() throws IOException {\n        if(_pos >= _size)\n            throw new IOException(\"CInByte2 - Can't read stream\");\n        return (_buffer[_pos++] & 0xFF);\n    }\n    \n    int ReadBytes2(byte [] data, int size) {\n        int processedSize;\n        for(processedSize = 0; processedSize < size && _pos < _size; processedSize++)\n            data[processedSize] = _buffer[_pos++];\n        return processedSize;\n    }\n    \n    boolean ReadBytes(byte [] data, int size) {\n        int processedSize = ReadBytes2(data, size);\n        return (processedSize == size);\n    }\n    \n    int GetProcessedSize() { return _pos; }\n    \n    InByte2() {\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/SevenZip/MethodID.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZip;\n\nclass MethodID {\n    \n    static public final MethodID k_LZMA      = new MethodID(0x3, 0x1, 0x1);\n    static public final MethodID k_PPMD      = new MethodID(0x3, 0x4, 0x1);\n    static public final MethodID k_BCJ_X86   = new MethodID(0x3, 0x3, 0x1, 0x3);\n    static public final MethodID k_BCJ       = new MethodID(0x3, 0x3, 0x1, 0x3);\n    static public final MethodID k_BCJ2      = new MethodID(0x3, 0x3, 0x1, 0x1B);\n    static public final MethodID k_Deflate   = new MethodID(0x4, 0x1, 0x8);\n    static public final MethodID k_Deflate64 = new MethodID(0x4, 0x1, 0x9);\n    static public final MethodID k_BZip2     = new MethodID(0x4, 0x2, 0x2);\n    static public final MethodID k_Copy      = new MethodID(0x0);\n    static public final MethodID k_7zAES     = new MethodID(0x6, 0xF1, 0x07, 0x01);\n    \n    static final int kMethodIDSize = 15;\n    byte [] ID;\n    byte IDSize;\n    \n    public MethodID() {\n        ID = new byte[kMethodIDSize];\n        IDSize = 0;\n    }\n \n    public MethodID(int a) {\n        int size = 1;\n        ID = new byte[size];\n        IDSize = (byte)size;\n        ID[0] = (byte)a;\n    } \n        \n    public MethodID(int a, int b ,int c) {\n        int size = 3;\n        ID = new byte[size];\n        IDSize = (byte)size;\n        ID[0] = (byte)a;\n        ID[1] = (byte)b;\n        ID[2] = (byte)c;\n    }    \n \n    public MethodID(int a, int b ,int c, int d) {\n        int size = 4;\n        ID = new byte[size];\n        IDSize = (byte)size;\n        ID[0] = (byte)a;\n        ID[1] = (byte)b;\n        ID[2] = (byte)c;\n        ID[3] = (byte)d;\n    } \n        \n    public boolean equals(MethodID anObject) {\n        if (IDSize != anObject.IDSize) return false;\n        \n        for(int i = 0; i < IDSize ; i++) {\n            if (ID[i] != anObject.ID[i]) return false;\n        }\n        \n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/SevenZip/StreamSwitch.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZip;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.ByteBuffer;\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.ObjectVector;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.HRESULT;\n\n\nclass StreamSwitch {\n    InArchive _archive;\n    boolean _needRemove;\n\n    public StreamSwitch() {\n        _needRemove = false;\n    }\n    \n    public void close() {\n        Remove();\n    }\n    \n    void Remove() {\n        if (_needRemove) {\n            _archive.DeleteByteStream();\n            _needRemove = false;\n        }\n    }\n    \n    void Set(InArchive archive, ByteBuffer byteBuffer) {\n        Set(archive, byteBuffer.data(), byteBuffer.GetCapacity());\n    }\n    \n    void Set(InArchive archive, byte [] data, int size) {\n        Remove();\n        _archive = archive;\n        _archive.AddByteStream(data, size);\n        _needRemove = true;\n    }\n    \n    int Set(InArchive archive, ObjectVector<ByteBuffer> dataVector)   throws java.io.IOException {\n        Remove();\n        int external = archive.ReadByte();\n        if (external != 0) {\n            int dataIndex = archive.ReadNum();\n            Set(archive, dataVector.get(dataIndex));\n        }\n        return HRESULT.S_OK;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Archive/SevenZipEntry.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive;\n\npublic class SevenZipEntry {\n    \n    long LastWriteTime;\n    \n    long UnPackSize;\n    long PackSize;\n    \n    int Attributes;\n    long FileCRC;\n    \n    boolean IsDirectory;\n    \n    String Name;\n    String Methods;\n    \n    long Position;\n    \n    public SevenZipEntry(\n            String name,\n            long packSize,\n            long unPackSize,\n            long crc,\n            long lastWriteTime,\n            long position,\n            boolean isDir,\n            int att,\n            String methods) {\n        \n        this.Name = name;\n        this.PackSize = packSize;\n        this.UnPackSize = unPackSize;\n        this.FileCRC = crc;\n        this.LastWriteTime = lastWriteTime;\n        this.Position = position;\n        this.IsDirectory = isDir;\n        this.Attributes = att;\n        this.Methods = methods;\n    }\n    \n    public long getCompressedSize() {\n        return PackSize;\n    }\n    \n    public long getSize() {\n        return UnPackSize;\n    }\n    \n    public long getCrc() {\n        return FileCRC;\n    }\n    \n    public String getName() {\n        return Name;\n    }\n    \n    public long getTime() {\n        return LastWriteTime;\n    }\n    \n    public long getPosition() {\n        return Position;\n    }\n    \n    public boolean isDirectory() {\n        return IsDirectory;\n    }\n    \n    static final String kEmptyAttributeChar = \".\";\n    static final String kDirectoryAttributeChar = \"D\";\n    static final String kReadonlyAttributeChar  = \"R\";\n    static final String kHiddenAttributeChar    = \"H\";\n    static final String kSystemAttributeChar    = \"S\";\n    static final String kArchiveAttributeChar   = \"A\";\n    static public final int FILE_ATTRIBUTE_READONLY =            0x00000001  ;\n    static public final int FILE_ATTRIBUTE_HIDDEN    =           0x00000002  ;\n    static public final int FILE_ATTRIBUTE_SYSTEM    =           0x00000004  ;\n    static public final int FILE_ATTRIBUTE_DIRECTORY = 0x00000010;\n    static public final int FILE_ATTRIBUTE_ARCHIVE  =            0x00000020  ;\n    \n    public String getAttributesString() {\n        String ret = \"\";\n        ret += ((Attributes & FILE_ATTRIBUTE_DIRECTORY) != 0 || IsDirectory) ?\n            kDirectoryAttributeChar: kEmptyAttributeChar;\n        ret += ((Attributes & FILE_ATTRIBUTE_READONLY) != 0)?\n            kReadonlyAttributeChar: kEmptyAttributeChar;\n        ret += ((Attributes & FILE_ATTRIBUTE_HIDDEN) != 0) ?\n            kHiddenAttributeChar: kEmptyAttributeChar;\n        ret += ((Attributes & FILE_ATTRIBUTE_SYSTEM) != 0) ?\n            kSystemAttributeChar: kEmptyAttributeChar;\n        ret += ((Attributes & FILE_ATTRIBUTE_ARCHIVE) != 0) ?\n            kArchiveAttributeChar: kEmptyAttributeChar;\n        return ret;\n    }\n    \n    public String getMethods() {\n        return Methods;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/ArchiveExtractCallback.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip;\n\n\n\n\nimport java.io.File;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.IArchiveExtractCallback;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.IInArchive;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZipEntry;\n\npublic class ArchiveExtractCallback implements IArchiveExtractCallback // , ICryptoGetTextPassword,\n{\n    \n    static class OutputStream extends java.io.OutputStream {\n        java.io.RandomAccessFile file;\n        \n        public OutputStream(java.io.RandomAccessFile f) {\n            file = f;\n        }\n        \n        public void close()  throws java.io.IOException {\n            file.close();\n            file = null;\n        }\n        /*\n        public void flush()  throws java.io.IOException {\n            file.flush();\n        }\n         */\n        public void write(byte[] b)  throws java.io.IOException {\n            file.write(b);\n        }\n        \n        public void write(byte[] b, int off, int len)  throws java.io.IOException {\n            file.write(b,off,len);\n        }\n        \n        public void write(int b)  throws java.io.IOException {\n            file.write(b);\n        }\n    }\n    \n    public int SetTotal(long size) {\n        return HRESULT.S_OK;\n    }\n    \n    public int SetCompleted(long completeValue) {\n        return HRESULT.S_OK;\n    }\n    \n    public void PrintString(String str) {\n        System.out.print(str);\n    }\n    \n    public void PrintNewLine() {\n        System.out.println();\n    }\n    public int PrepareOperation(int askExtractMode) {\n    \tSystem.out.println(\"askExtractMode = \" + askExtractMode);\n        _extractMode = false;\n        switch (askExtractMode) {\n            case IInArchive.NExtract_NAskMode_kExtract:\n                _extractMode = true;\n        }\n        System.out.println(\"here1\");\n        switch (askExtractMode) {\n            case IInArchive.NExtract_NAskMode_kExtract:\n                PrintString(\"Extracting  \");\n                break;\n            case IInArchive.NExtract_NAskMode_kTest:\n                PrintString(\"Testing     \");\n                break;\n            case IInArchive.NExtract_NAskMode_kSkip:\n                PrintString(\"Skipping    \");\n                break;\n        }\n        System.out.println(\"here2\");\n        PrintString(_filePath);\n        return HRESULT.S_OK;\n    }\n    \n    public int SetOperationResult(int operationResult) throws java.io.IOException {\n        switch(operationResult) {\n            case IInArchive.NExtract_NOperationResult_kOK:\n                break;\n            default:\n            {\n                NumErrors++;\n                PrintString(\"     \");\n                switch(operationResult) {\n                    case IInArchive.NExtract_NOperationResult_kUnSupportedMethod:\n                        PrintString(\"Unsupported Method\");\n                        break;\n                    case IInArchive.NExtract_NOperationResult_kCRCError:\n                        PrintString(\"CRC Failed\");\n                        break;\n                    case IInArchive.NExtract_NOperationResult_kDataError:\n                        PrintString(\"Data Error\");\n                        break;\n                    default:\n                        PrintString(\"Unknown Error\");\n                }\n            }\n        }\n            /*\n            if(_outFileStream != null && _processedFileInfo.UTCLastWriteTimeIsDefined)\n                _outFileStreamSpec->File.SetLastWriteTime(&_processedFileInfo.UTCLastWriteTime);\n             */\n        if (_outFileStream != null) _outFileStream.close(); // _outFileStream.Release();\n            /*\n            if (_extractMode && _processedFileInfo.AttributesAreDefined)\n                NFile::NDirectory::MySetFileAttributes(_diskFilePath, _processedFileInfo.Attributes);\n             */\n        PrintNewLine();\n        return HRESULT.S_OK;\n    }\n    \n    java.io.OutputStream _outFileStream;\n    \n    public int GetStream(int index,\n            java.io.OutputStream [] outStream,\n            int askExtractMode) {\n        \n        outStream[0] = null;\n        \n        SevenZipEntry item = _archiveHandler.getEntry(index);\n        _filePath = item.getName();\n        \n        File file = new File(_filePath);\n        \n        switch (askExtractMode) {\n            case IInArchive.NExtract_NAskMode_kTest:\n                return HRESULT.S_OK;\n                \n            case IInArchive.NExtract_NAskMode_kExtract:\n                \n                try {\n                    isDirectory = item.isDirectory();\n                    \n                    if (isDirectory) {\n                        if (file.isDirectory()) {\n                            return HRESULT.S_OK;\n                        }\n                        if (file.mkdirs())\n                            return HRESULT.S_OK;\n                        else\n                            return HRESULT.S_FALSE;\n                    }\n                    \n                    \n                    File dirs = file.getParentFile();\n                    if (dirs != null) {\n                        if (!dirs.isDirectory())\n                            if (!dirs.mkdirs())\n                                return HRESULT.S_FALSE;\n                    }\n                    \n                    long pos = item.getPosition();\n                    if (pos == -1) {\n                        file.delete();\n                    }\n                    \n                    java.io.RandomAccessFile outStr = new java.io.RandomAccessFile(_filePath,\"rw\");\n                    \n                    if (pos != -1) {\n                        outStr.seek(pos);\n                    }\n                    \n                    outStream[0] = new OutputStream(outStr);\n                } catch (java.io.IOException e) {\n                    return HRESULT.S_FALSE;\n                }\n                \n                return HRESULT.S_OK;\n                \n        }\n        \n        // other case : skip ...\n        \n        return HRESULT.S_OK;\n        \n    }\n    \n    com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.IInArchive _archiveHandler;  // IInArchive\n    String _filePath;       // name inside arcvhive\n    String _diskFilePath;   // full path to file on disk\n    \n    public long NumErrors;\n    boolean PasswordIsDefined;\n    String Password;\n    boolean _extractMode;\n    \n    boolean isDirectory;\n    \n    public ArchiveExtractCallback() { PasswordIsDefined = false; }\n    \n    \n    public void Init(com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.IInArchive archiveHandler) {\n        NumErrors = 0;\n        _archiveHandler = archiveHandler;\n    }\n    \n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Common/InBuffer.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Common;\n\npublic class InBuffer {\n    int _bufferPos;\n    int _bufferLimit;\n    byte [] _bufferBase;\n    java.io.InputStream _stream = null; // CMyComPtr<ISequentialInStream>\n    long _processedSize;\n    int _bufferSize;\n    boolean _wasFinished;\n    \n    public InBuffer() {\n        \n    }\n    // ~CInBuffer() { Free(); }\n    \n    public void Create(int bufferSize) {\n        final int kMinBlockSize = 1;\n        if (bufferSize < kMinBlockSize)\n            bufferSize = kMinBlockSize;\n        if (_bufferBase != null && _bufferSize == bufferSize)\n            return ;\n        Free();\n        _bufferSize = bufferSize;\n        _bufferBase = new byte[bufferSize];\n    }\n    void Free() {\n        _bufferBase = null;\n    }\n    \n    public void SetStream(java.io.InputStream stream) { // ISequentialInStream\n        _stream = stream;\n    }\n    public void Init() {\n        _processedSize = 0;\n        _bufferPos = 0; //  = _bufferBase;\n        _bufferLimit = 0; // _buffer;\n        _wasFinished = false;\n    }\n    public void ReleaseStream() throws java.io.IOException {\n        if (_stream != null) _stream.close(); // _stream.Release();\n        _stream = null;\n    }\n    \n    public int read()  throws java.io.IOException {\n        if(_bufferPos >= _bufferLimit)\n            return ReadBlock2();\n        return _bufferBase[_bufferPos++] & 0xFF;\n    }\n    \n    public boolean ReadBlock() throws java.io.IOException {\n        if (_wasFinished)\n            return false;\n        _processedSize += _bufferPos; // (_buffer - _bufferBase);\n        \n        int  numProcessedBytes = _stream.read(_bufferBase, 0,_bufferSize);\n        if (numProcessedBytes == -1) numProcessedBytes = 0; // EOF\n        \n        _bufferPos = 0; // _bufferBase;\n        _bufferLimit = numProcessedBytes; // _buffer + numProcessedBytes;\n        _wasFinished = (numProcessedBytes == 0);\n        return (!_wasFinished);\n    }\n    \n    public int ReadBlock2() throws java.io.IOException {\n        if(!ReadBlock())\n            return -1; // 0xFF;\n        return _bufferBase[_bufferPos++] & 0xFF;\n    }\n    \n    public long GetProcessedSize() { return _processedSize + (_bufferPos); }\n    public boolean WasFinished() { return _wasFinished; }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Common/LocalCompressProgressInfo.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Common;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressProgressInfo;\n\n\npublic class LocalCompressProgressInfo implements ICompressProgressInfo {\n    ICompressProgressInfo _progress;\n\n    boolean _inStartValueIsAssigned;\n    boolean _outStartValueIsAssigned;\n    long _inStartValue;\n    long _outStartValue;\n\n    public void Init(ICompressProgressInfo progress, long inStartValue, long outStartValue) {\n\n        _progress = progress;\n        _inStartValueIsAssigned = (inStartValue != ICompressProgressInfo.INVALID);\n        if (_inStartValueIsAssigned)\n            _inStartValue = inStartValue;\n        _outStartValueIsAssigned = (outStartValue != ICompressProgressInfo.INVALID);\n        if (_outStartValueIsAssigned)\n            _outStartValue = outStartValue;\n\n    }\n    \n    public int SetRatioInfo(long inSize, long outSize) {\n        long inSizeNew, outSizeNew;\n        long inSizeNewPointer;\n        long outSizeNewPointer;\n        if (_inStartValueIsAssigned && inSize != ICompressProgressInfo.INVALID) {\n            inSizeNew = _inStartValue + (inSize); // *inSize\n            inSizeNewPointer = inSizeNew;\n        } else\n            inSizeNewPointer = ICompressProgressInfo.INVALID;\n        \n        if (_outStartValueIsAssigned && outSize != ICompressProgressInfo.INVALID) {\n            outSizeNew = _outStartValue + (outSize);\n            outSizeNewPointer = outSizeNew;\n        } else\n            outSizeNewPointer = ICompressProgressInfo.INVALID;\n        return _progress.SetRatioInfo(inSizeNewPointer, outSizeNewPointer);\n        \n    }\n    \n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Common/LocalProgress.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Common;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressProgressInfo;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.IProgress;\n\n\npublic class LocalProgress implements ICompressProgressInfo {\n    IProgress _progress;\n    boolean _inSizeIsMain;\n    \n    public void Init(IProgress progress, boolean inSizeIsMain) {\n        _progress = progress;\n        _inSizeIsMain = inSizeIsMain;\n    }\n    \n    public int SetRatioInfo(long inSize, long outSize) {\n        return _progress.SetCompleted(_inSizeIsMain ? inSize : outSize);\n        \n    }\n    \n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Common/SequentialOutStreamImp2.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Common;\n\npublic class SequentialOutStreamImp2 extends java.io.OutputStream {\n    byte []_buffer;\n    int _size;\n    int _pos;\n    public void Init(byte [] buffer, int size) {\n        _buffer = buffer;\n        _pos = 0;\n        _size = size;\n    }\n    \n    public void write(int b) throws java.io.IOException {\n        throw new java.io.IOException(\"SequentialOutStreamImp2 - write() not implemented\");\n    }\n    \n    public void write(byte [] data,int off, int size) throws java.io.IOException {\n        for(int i = 0 ; i < size ; i++) {\n            if (_pos < _size) {\n                _buffer[_pos++] = data[off + i];\n            } else {\n                throw new java.io.IOException(\"SequentialOutStreamImp2 - can't write\");\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Common/StreamUtils.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Common;\n\nimport java.io.IOException;\n\npublic class StreamUtils\n{    \n    static public int  ReadStream(java.io.InputStream stream, byte [] data,int off, int size) throws IOException\n    {\n        int processedSize = 0;\n\n        while(size != 0)\n        {\n             int processedSizeLoc = stream.read(data,off + processedSize,size);\n             if (processedSizeLoc > 0)\n             {\n                processedSize += processedSizeLoc;\n                size -= processedSizeLoc;\n             }\n             if (processedSizeLoc == -1) {\n                 if (processedSize > 0) return processedSize;\n                 return -1; // EOF\n             }\n        }\n        return processedSize;\n    }\n    \n    // HRESULT WriteStream(ISequentialOutStream *stream, const void *data, UInt32 size, UInt32 *processedSize);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Compression/Branch/BCJ2_x86_Decoder.java",
    "content": "\npackage com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.Branch;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.RecordVector;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.HRESULT;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressCoder2;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressProgressInfo;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Common.InBuffer;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.LZ.OutWindow;\n\n\npublic class BCJ2_x86_Decoder implements ICompressCoder2 {\n\n    public static final int kNumMoveBits = 5;\n    \n    InBuffer _mainInStream = new InBuffer();\n    InBuffer _callStream = new InBuffer();\n    InBuffer _jumpStream = new InBuffer();\n    \n    com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.BitDecoder _statusE8Decoder[] = new com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.BitDecoder[256];\n    com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.BitDecoder _statusE9Decoder = new com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.BitDecoder(kNumMoveBits);\n    com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.BitDecoder _statusJccDecoder = new com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.BitDecoder(kNumMoveBits);\n    \n    OutWindow _outStream = new OutWindow();\n    com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Decoder _rangeDecoder = new com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Decoder();\n    \n    \n    // static final boolean IsJcc(int b0, int b1) {\n    //     return ((b0 == 0x0F) && ((b1 & 0xF0) == 0x80));\n    // }\n    \n    int CodeReal(\n            RecordVector<java.io.InputStream>  inStreams,\n            Object useless1, // const UInt64 ** /* inSizes */,\n            int numInStreams,\n            RecordVector<java.io.OutputStream> outStreams,\n            Object useless2, // const UInt64 ** /* outSizes */,\n            int numOutStreams,\n            ICompressProgressInfo progress) throws java.io.IOException {\n        \n        if (numInStreams != 4 || numOutStreams != 1)\n            return HRESULT.E_INVALIDARG;\n        \n        _mainInStream.Create(1 << 16);\n        _callStream.Create(1 << 20);\n        _jumpStream.Create(1 << 16);\n        _rangeDecoder.Create(1 << 20);\n        _outStream.Create(1 << 16);\n        \n        _mainInStream.SetStream(inStreams.get(0));\n        _callStream.SetStream(inStreams.get(1));\n        _jumpStream.SetStream(inStreams.get(2));\n        _rangeDecoder.SetStream(inStreams.get(3));\n        _outStream.SetStream(outStreams.get(0));\n        \n        _mainInStream.Init();\n        _callStream.Init();\n        _jumpStream.Init();\n        _rangeDecoder.Init();\n        _outStream.Init();\n        \n        for (int i = 0; i < 256; i++) {\n            _statusE8Decoder[i] = new com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.BitDecoder(kNumMoveBits);\n            _statusE8Decoder[i].Init();\n        }\n        _statusE9Decoder.Init();\n        _statusJccDecoder.Init();\n        \n        int prevByte = 0;\n        int processedBytes = 0;\n        for (;;) {\n            \n            if (processedBytes > (1 << 20) && progress != null) {\n                long nowPos64 = _outStream.GetProcessedSize();\n                int res = progress.SetRatioInfo(ICompressProgressInfo.INVALID, nowPos64);\n                if (res != HRESULT.S_OK) return res;\n                \n                processedBytes = 0;\n            }\n            \n            processedBytes++;\n            int b = _mainInStream.read();\n            if (b == -1)\n                return Flush();\n            _outStream.WriteByte(b); // System.out.println(\"0:\"+b);\n            // if ((b != 0xE8) && (b != 0xE9) && (!IsJcc(prevByte, b))) {\n            if ((b != 0xE8) && (b != 0xE9) && (!((prevByte == 0x0F) && ((b & 0xF0) == 0x80)))) {\n                prevByte = b;\n                continue;\n            }\n            \n            boolean status;\n            if (b == 0xE8)\n                status = (_statusE8Decoder[prevByte].Decode(_rangeDecoder) == 1);\n            else if (b == 0xE9)\n                status = (_statusE9Decoder.Decode(_rangeDecoder) == 1);\n            else\n                status = (_statusJccDecoder.Decode(_rangeDecoder) == 1);\n            \n            if (status) {\n                int src;\n                if (b == 0xE8) {\n                    int b0 = _callStream.read();\n                    // if(b0 == -1) return HRESULT.S_FALSE;\n                    src = b0 << 24;\n                    \n                    b0 = _callStream.read();\n                    // if(b0 == -1) return HRESULT.S_FALSE;\n                    src |= b0 << 16;\n                    \n                    b0 = _callStream.read();\n                    // if(b0 == -1) return HRESULT.S_FALSE;\n                    src |= b0 << 8;\n                    \n                    b0 = _callStream.read();\n                    if(b0 == -1) return HRESULT.S_FALSE;\n                    src |= b0;\n                    \n                } else {\n                    int b0 = _jumpStream.read();\n                    // if(b0 == -1) return HRESULT.S_FALSE;\n                    src = b0 << 24;\n                    \n                    b0 = _jumpStream.read();\n                    // if(b0 == -1) return HRESULT.S_FALSE;\n                    src |= b0 << 16;\n                    \n                    b0 = _jumpStream.read();\n                    // if(b0 == -1) return HRESULT.S_FALSE;\n                    src |= b0 << 8;\n                    \n                    b0 = _jumpStream.read();\n                    if(b0 == -1) return HRESULT.S_FALSE;\n                    src |= b0;\n                    \n                }\n                int dest = src - ((int)_outStream.GetProcessedSize() + 4) ;\n                _outStream.WriteByte(dest);\n                _outStream.WriteByte((dest >> 8));\n                _outStream.WriteByte((dest >> 16));\n                _outStream.WriteByte((dest >> 24));\n                prevByte = (dest >> 24) & 0xFF;\n                processedBytes += 4;\n            } else\n                prevByte = b;\n        }\n    }\n    \n    public int Flush() throws java.io.IOException {\n        _outStream.Flush();\n        return HRESULT.S_OK;\n    }\n    \n    public int Code(\n            RecordVector<java.io.InputStream>  inStreams, // ISequentialInStream **inStreams,\n            Object useless_inSizes, // const UInt64 ** /* inSizes */,\n            int numInStreams,\n            RecordVector<java.io.OutputStream> outStreams, // ISequentialOutStream **outStreams\n            Object useless_outSizes, // const UInt64 ** /* outSizes */,\n            int numOutStreams,\n            ICompressProgressInfo progress) throws java.io.IOException {\n        \n        try {\n            return CodeReal(inStreams, useless_inSizes, numInStreams,\n                    outStreams, useless_outSizes,numOutStreams, progress);\n        } catch(java.io.IOException e) {\n            throw e;\n        } finally {\n            ReleaseStreams();\n        }\n    }\n    \n    void ReleaseStreams() throws java.io.IOException {\n        _mainInStream.ReleaseStream();\n        _callStream.ReleaseStream();\n        _jumpStream.ReleaseStream();\n        _rangeDecoder.ReleaseStream();\n        _outStream.ReleaseStream();\n    }\n    \n    public void close() throws java.io.IOException {\n        ReleaseStreams();       \n    }\n    \n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Compression/Branch/BCJ_x86_Decoder.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.Branch;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.HRESULT;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressFilter;\n\npublic class BCJ_x86_Decoder implements ICompressFilter {\n    \n    // struct CBranch86 - begin\n    int  [] _prevMask = new int[1];  // UInt32\n    int  [] _prevPos = new int[1]; // UInt32\n    void x86Init() {\n        _prevMask[0] = 0;\n        _prevPos[0] = -5;\n    }\n    // struct CBranch86 - end\n        \n    static final boolean [] kMaskToAllowedStatus = {true, true, true, false, true, false, false, false };\n    \n    static final int [] kMaskToBitNumber = {0, 1, 2, 2, 3, 3, 3, 3};\n    \n    static boolean Test86MSByte(int b) { return ((b) == 0 || (b) == 0xFF); }\n    \n    static int x86_Convert(byte [] buffer, int endPos, int nowPos,\n            int [] prevMask, int [] prevPos, boolean encoding) {\n        int bufferPos = 0;\n        int limit;\n        \n        if (endPos < 5)\n            return 0;\n        \n        if (nowPos - prevPos[0] > 5)\n            prevPos[0] = nowPos - 5;\n        \n        limit = endPos - 5;\n        while(bufferPos <= limit) {\n            int b = (buffer[bufferPos] & 0xFF);\n            int offset;\n            if (b != 0xE8 && b != 0xE9) {\n                bufferPos++;\n                continue;\n            }\n            offset = (nowPos + bufferPos - prevPos[0]);\n            prevPos[0] = (nowPos + bufferPos);\n            if (offset > 5)\n                prevMask[0] = 0;\n            else {\n                for (int i = 0; i < offset; i++) {\n                    prevMask[0] &= 0x77;\n                    prevMask[0] <<= 1;\n                }\n            }\n            b = (buffer[bufferPos + 4] & 0xFF);\n            if (Test86MSByte(b) && kMaskToAllowedStatus[(prevMask[0] >> 1) & 0x7] &&\n                    (prevMask[0] >>> 1) < 0x10) {\n                int src =\n                        (b << 24) |\n                        ((buffer[bufferPos + 3] & 0xFF) << 16) |\n                        ((buffer[bufferPos + 2] & 0xFF) << 8) |\n                        (buffer[bufferPos + 1] & 0xFF);\n                \n                int dest;\n                for (;;) {\n                    int index;\n                    if (encoding)\n                        dest = (nowPos + bufferPos + 5) + src;\n                    else\n                        dest = src - (nowPos + bufferPos + 5);\n                    if (prevMask[0] == 0)\n                        break;\n                    index = kMaskToBitNumber[prevMask[0] >>> 1];\n                    b = ((dest >> (24 - index * 8)) & 0xFF);\n                    if (!Test86MSByte(b))\n                        break;\n                    src = dest ^ ((1 << (32 - index * 8)) - 1);\n                }\n                buffer[bufferPos + 4] = (byte)(~(((dest >> 24) & 1) - 1));\n                buffer[bufferPos + 3] = (byte)(dest >> 16);\n                buffer[bufferPos + 2] = (byte)(dest >> 8);\n                buffer[bufferPos + 1] = (byte)dest;\n                bufferPos += 5;\n                prevMask[0] = 0;\n            } else {\n                bufferPos++;\n                prevMask[0] |= 1;\n                if (Test86MSByte(b))\n                    prevMask[0] |= 0x10;\n            }\n        }\n        return bufferPos;\n    }\n    \n    public int SubFilter(byte [] data, int size) {\n        return x86_Convert(data, size, _bufferPos, _prevMask, _prevPos, false);\n    }\n    \n    public void SubInit() {\n        x86Init();\n    }\n  \n    int   _bufferPos; // UInt32\n    \n    // ICompressFilter interface\n    public int Init() {\n        _bufferPos = 0;\n        SubInit();\n        return HRESULT.S_OK;\n    }\n    \n    public int Filter(byte [] data, int size) {\n        int processedSize = SubFilter(data, size);\n        _bufferPos += processedSize;\n        return processedSize;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Compression/Copy/Decoder.java",
    "content": "\npackage com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.Copy;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.HRESULT;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressCoder;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressProgressInfo;\n\n\npublic class Decoder implements ICompressCoder {\n    \n    static final int kBufferSize = 1 << 17;\n    \n    public int Code(\n            java.io.InputStream inStream, // , ISequentialInStream\n            java.io.OutputStream outStream, // ISequentialOutStream\n            long outSize, ICompressProgressInfo progress) throws java.io.IOException {\n        \n        byte [] _buffer = new byte[kBufferSize];\n        long TotalSize = 0;\n        \n        for (;;) {\n            int realProcessedSize;\n            int size = kBufferSize;\n            \n            if (outSize != -1) // NULL\n                if (size > (outSize - TotalSize))\n                    size = (int)(outSize - TotalSize);\n            \n            realProcessedSize = inStream.read(_buffer, 0,size);\n            if(realProcessedSize == -1) // EOF\n                break;\n            outStream.write(_buffer,0,realProcessedSize);\n            TotalSize += realProcessedSize;\n            if (progress != null) {\n                int res = progress.SetRatioInfo(TotalSize, TotalSize);\n                if (res != HRESULT.S_OK) return res;\n            }\n        }\n        return HRESULT.S_OK;  \n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Compression/LZ/BinTree.java",
    "content": "// LZ.BinTree\n\npackage com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.LZ;\nimport java.io.IOException;\n\n\npublic class BinTree extends InWindow {\n\tint _cyclicBufferPos;\n\tint _cyclicBufferSize = 0;\n\tint _matchMaxLen;\n\t\n\tint[] _son;\n\tint[] _hash;\n\t\n\tint _cutValue = 0xFF;\n\tint _hashMask;\n\tint _hashSizeSum = 0;\n\t\n\tboolean hashArray = true;\n\n\tstatic final int kHash2Size = 1 << 10;\n\tstatic final int kHash3Size = 1 << 16;\n\tstatic final int kBT2HashSize = 1 << 16;\n\tstatic final int kStartMaxLen = 1;\n\tstatic final int kHash3Offset = kHash2Size;\n\tstatic final int kEmptyHashValue = 0;\n\tstatic final int kMaxValForNormalize = (1 << 30) - 1;\n\t\n\tint kNumHashDirectBytes = 0;\n\tint kMinMatchCheck = 4;\n\tint kFixHashSize = kHash2Size + kHash3Size;\n\n\tpublic void setType(int numHashBytes) {\n\t\thashArray = (numHashBytes > 2);\n\t\tif (hashArray)\n\t\t{\n\t\t\tkNumHashDirectBytes = 0;\n\t\t\tkMinMatchCheck = 4;\n\t\t\tkFixHashSize = kHash2Size + kHash3Size;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tkNumHashDirectBytes = 2;\n\t\t\tkMinMatchCheck = 2 + 1;\n\t\t\tkFixHashSize = 0;\n\t\t}\n\t}\n\t\n\n\t\n\n\tpublic void Init() throws IOException\n\t{\n\t\tsuper.Init();\n\t\tfor (int i = 0; i < _hashSizeSum; i++)\n\t\t\t_hash[i] = kEmptyHashValue;\n\t\t_cyclicBufferPos = 0;\n\t\treduceOffsets(-1);\n\t}\n\t\n\tpublic void movePos() throws IOException\n\t{\n\t\tif (++_cyclicBufferPos >= _cyclicBufferSize)\n\t\t\t_cyclicBufferPos = 0;\n\t\tsuper.movePos();\n\t\tif (_pos == kMaxValForNormalize)\n\t\t\tNormalize();\n\t}\n\t\n\n\t\n\t\n\t\n\t\n\t\n\t\n\tpublic boolean Create(int historySize, int keepAddBufferBefore,\n\t\t\tint matchMaxLen, int keepAddBufferAfter)\n\t{\n\t\tif (historySize > kMaxValForNormalize - 256)\n\t\t\treturn false;\n\t\t_cutValue = 16 + (matchMaxLen >> 1);\n\n\t\tint windowReservSize = (historySize + keepAddBufferBefore +\n\t\t\t\tmatchMaxLen + keepAddBufferAfter) / 2 + 256;\n\t\t\n\t\tsuper.create(historySize + keepAddBufferBefore, matchMaxLen + keepAddBufferAfter, windowReservSize);\n\t\t\n\t\t_matchMaxLen = matchMaxLen;\n\n\t\tint cyclicBufferSize = historySize + 1;\n\t\tif (_cyclicBufferSize != cyclicBufferSize)\n\t\t\t_son = new int[(_cyclicBufferSize = cyclicBufferSize) * 2];\n\n\t\tint hs = kBT2HashSize;\n\n\t\tif (hashArray)\n\t\t{\n\t\t\ths = historySize - 1;\n\t\t\ths |= (hs >> 1);\n\t\t\ths |= (hs >> 2);\n\t\t\ths |= (hs >> 4);\n\t\t\ths |= (hs >> 8);\n\t\t\ths >>= 1;\n\t\t\ths |= 0xFFFF;\n\t\t\tif (hs > (1 << 24))\n\t\t\t\ths >>= 1;\n\t\t\t_hashMask = hs;\n\t\t\ths++;\n\t\t\ths += kFixHashSize;\n\t\t}\n\t\tif (hs != _hashSizeSum)\n\t\t\t_hash = new int [_hashSizeSum = hs];\n\t\treturn true;\n\t}\n\tpublic int GetMatches(int[] distances) throws IOException\n\t{\n\t\tint lenLimit;\n\t\tif (_pos + _matchMaxLen <= _streamPos)\n\t\t\tlenLimit = _matchMaxLen;\n\t\telse\n\t\t{\n\t\t\tlenLimit = _streamPos - _pos;\n\t\t\tif (lenLimit < kMinMatchCheck)\n\t\t\t{\n\t\t\t\tmovePos();\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\n\t\tint offset = 0;\n\t\tint matchMinPos = (_pos > _cyclicBufferSize) ? (_pos - _cyclicBufferSize) : 0;\n\t\tint cur = _bufferOffset + _pos;\n\t\tint maxLen = kStartMaxLen; // to avoid items for len < hashSize;\n\t\tint hashValue, hash2Value = 0, hash3Value = 0;\n\t\t\n\t\tif (hashArray)\n\t\t{\n\t\t\tint temp = CrcTable[_bufferBase[cur] & 0xFF] ^ (_bufferBase[cur + 1] & 0xFF);\n\t\t\thash2Value = temp & (kHash2Size - 1);\n\t\t\ttemp ^= (_bufferBase[cur + 2] & 0xFF) << 8;\n\t\t\thash3Value = temp & (kHash3Size - 1);\n\t\t\thashValue = (temp ^ (CrcTable[_bufferBase[cur + 3] & 0xFF] << 5)) & _hashMask;\n\t\t}\n\t\telse\n\t\t\thashValue = ((_bufferBase[cur] & 0xFF) ^ ((_bufferBase[cur + 1] & 0xFF) << 8));\n\n\t\tint curMatch = _hash[kFixHashSize + hashValue];\n\t\tif (hashArray)\n\t\t{\n\t\t\tint curMatch2 = _hash[hash2Value];\n\t\t\tint curMatch3 = _hash[kHash3Offset + hash3Value];\n\t\t\t_hash[hash2Value] = _pos;\n\t\t\t_hash[kHash3Offset + hash3Value] = _pos;\n\t\t\tif (curMatch2 > matchMinPos)\n\t\t\t\tif (_bufferBase[_bufferOffset + curMatch2] == _bufferBase[cur])\n\t\t\t\t{\n\t\t\t\t\tdistances[offset++] = maxLen = 2;\n\t\t\t\t\tdistances[offset++] = _pos - curMatch2 - 1;\n\t\t\t\t}\n\t\t\tif (curMatch3 > matchMinPos)\n\t\t\t\tif (_bufferBase[_bufferOffset + curMatch3] == _bufferBase[cur])\n\t\t\t\t{\n\t\t\t\t\tif (curMatch3 == curMatch2)\n\t\t\t\t\t\toffset -= 2;\n\t\t\t\t\tdistances[offset++] = maxLen = 3;\n\t\t\t\t\tdistances[offset++] = _pos - curMatch3 - 1;\n\t\t\t\t\tcurMatch2 = curMatch3;\n\t\t\t\t}\n\t\t\tif (offset != 0 && curMatch2 == curMatch)\n\t\t\t{\n\t\t\t\toffset -= 2;\n\t\t\t\tmaxLen = kStartMaxLen;\n\t\t\t}\n\t\t}\n\n\t\t_hash[kFixHashSize + hashValue] = _pos;\n\n\t\tint ptr0 = (_cyclicBufferPos << 1) + 1;\n\t\tint ptr1 = (_cyclicBufferPos << 1);\n\n\t\tint len0, len1;\n\t\tlen0 = len1 = kNumHashDirectBytes;\n\n\t\tif (kNumHashDirectBytes != 0)\n\t\t{\n\t\t\tif (curMatch > matchMinPos)\n\t\t\t{\n\t\t\t\tif (_bufferBase[_bufferOffset + curMatch + kNumHashDirectBytes] !=\n\t\t\t\t\t\t_bufferBase[cur + kNumHashDirectBytes])\n\t\t\t\t{\n\t\t\t\t\tdistances[offset++] = maxLen = kNumHashDirectBytes;\n\t\t\t\t\tdistances[offset++] = _pos - curMatch - 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tint count = _cutValue;\n\n\t\twhile (true)\n\t\t{\n\t\t\tif (curMatch <= matchMinPos || count-- == 0)\n\t\t\t{\n\t\t\t\t_son[ptr0] = _son[ptr1] = kEmptyHashValue;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tint delta = _pos - curMatch;\n\t\t\tint cyclicPos = ((delta <= _cyclicBufferPos) ?\n\t\t\t\t(_cyclicBufferPos - delta) :\n\t\t\t\t(_cyclicBufferPos - delta + _cyclicBufferSize)) << 1;\n\n\t\t\tint pby1 = _bufferOffset + curMatch;\n\t\t\tint len = Math.min(len0, len1);\n\t\t\tif (_bufferBase[pby1 + len] == _bufferBase[cur + len])\n\t\t\t{\n\t\t\t\twhile(++len != lenLimit)\n\t\t\t\t\tif (_bufferBase[pby1 + len] != _bufferBase[cur + len])\n\t\t\t\t\t\tbreak;\n\t\t\t\tif (maxLen < len)\n\t\t\t\t{\n\t\t\t\t\tdistances[offset++] = maxLen = len;\n\t\t\t\t\tdistances[offset++] = delta - 1;\n\t\t\t\t\tif (len == lenLimit)\n\t\t\t\t\t{\n\t\t\t\t\t\t_son[ptr1] = _son[cyclicPos];\n\t\t\t\t\t\t_son[ptr0] = _son[cyclicPos + 1];\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ((_bufferBase[pby1 + len] & 0xFF) < (_bufferBase[cur + len] & 0xFF))\n\t\t\t{\n\t\t\t\t_son[ptr1] = curMatch;\n\t\t\t\tptr1 = cyclicPos + 1;\n\t\t\t\tcurMatch = _son[ptr1];\n\t\t\t\tlen1 = len;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t_son[ptr0] = curMatch;\n\t\t\t\tptr0 = cyclicPos;\n\t\t\t\tcurMatch = _son[ptr0];\n\t\t\t\tlen0 = len;\n\t\t\t}\n\t\t}\n\t\tmovePos();\n\t\treturn offset;\n\t}\n\n\tpublic void Skip(int num) throws IOException\n\t{\n\t\tdo\n\t\t{\n\t\t\tint lenLimit;\n\t\t\tif (_pos + _matchMaxLen <= _streamPos)\n\t\t\tlenLimit = _matchMaxLen;\n\t\t\telse\n\t\t\t{\n\t\t\t\tlenLimit = _streamPos - _pos;\n\t\t\t\tif (lenLimit < kMinMatchCheck)\n\t\t\t\t{\n\t\t\t\t\tmovePos();\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tint matchMinPos = (_pos > _cyclicBufferSize) ? (_pos - _cyclicBufferSize) : 0;\n\t\t\tint cur = _bufferOffset + _pos;\n\t\t\t\n\t\t\tint hashValue;\n\n\t\t\tif (hashArray)\n\t\t\t{\n\t\t\t\tint temp = CrcTable[_bufferBase[cur] & 0xFF] ^ (_bufferBase[cur + 1] & 0xFF);\n\t\t\t\tint hash2Value = temp & (kHash2Size - 1);\n\t\t\t\t_hash[hash2Value] = _pos;\n\t\t\t\ttemp ^= ((_bufferBase[cur + 2] & 0xFF) << 8);\n\t\t\t\tint hash3Value = temp & (kHash3Size - 1);\n\t\t\t\t_hash[kHash3Offset + hash3Value] = _pos;\n\t\t\t\thashValue = (temp ^ (CrcTable[_bufferBase[cur + 3] & 0xFF] << 5)) & _hashMask;\n\t\t\t}\n\t\t\telse\n\t\t\t\thashValue = ((_bufferBase[cur] & 0xFF) ^ ((_bufferBase[cur + 1] & 0xFF) << 8));\n\n\t\t\tint curMatch = _hash[kFixHashSize + hashValue];\n\t\t\t_hash[kFixHashSize + hashValue] = _pos;\n\n\t\t\tint ptr0 = (_cyclicBufferPos << 1) + 1;\n\t\t\tint ptr1 = (_cyclicBufferPos << 1);\n\n\t\t\tint len0, len1;\n\t\t\tlen0 = len1 = kNumHashDirectBytes;\n\n\t\t\tint count = _cutValue;\n\t\t\twhile (true)\n\t\t\t{\n\t\t\t\tif (curMatch <= matchMinPos || count-- == 0)\n\t\t\t\t{\n\t\t\t\t\t_son[ptr0] = _son[ptr1] = kEmptyHashValue;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tint delta = _pos - curMatch;\n\t\t\t\tint cyclicPos = ((delta <= _cyclicBufferPos) ?\n\t\t\t\t\t(_cyclicBufferPos - delta) :\n\t\t\t\t\t(_cyclicBufferPos - delta + _cyclicBufferSize)) << 1;\n\n\t\t\t\tint pby1 = _bufferOffset + curMatch;\n\t\t\t\tint len = Math.min(len0, len1);\n\t\t\t\tif (_bufferBase[pby1 + len] == _bufferBase[cur + len])\n\t\t\t\t{\n\t\t\t\t\twhile (++len != lenLimit)\n\t\t\t\t\t\tif (_bufferBase[pby1 + len] != _bufferBase[cur + len])\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\tif (len == lenLimit)\n\t\t\t\t\t{\n\t\t\t\t\t\t_son[ptr1] = _son[cyclicPos];\n\t\t\t\t\t\t_son[ptr0] = _son[cyclicPos + 1];\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif ((_bufferBase[pby1 + len] & 0xFF) < (_bufferBase[cur + len] & 0xFF))\n\t\t\t\t{\n\t\t\t\t\t_son[ptr1] = curMatch;\n\t\t\t\t\tptr1 = cyclicPos + 1;\n\t\t\t\t\tcurMatch = _son[ptr1];\n\t\t\t\t\tlen1 = len;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t_son[ptr0] = curMatch;\n\t\t\t\t\tptr0 = cyclicPos;\n\t\t\t\t\tcurMatch = _son[ptr0];\n\t\t\t\t\tlen0 = len;\n\t\t\t\t}\n\t\t\t}\n\t\t\tmovePos();\n\t\t}\n\t\twhile (--num != 0);\n\t}\n\t\n\tvoid NormalizeLinks(int[] items, int numItems, int subValue)\n\t{\n\t\tfor (int i = 0; i < numItems; i++)\n\t\t{\n\t\t\tint value = items[i];\n\t\t\tif (value <= subValue)\n\t\t\t\tvalue = kEmptyHashValue;\n\t\t\telse\n\t\t\t\tvalue -= subValue;\n\t\t\titems[i] = value;\n\t\t}\n\t}\n\t\n\tvoid Normalize()\n\t{\n\t\tint subValue = _pos - _cyclicBufferSize;\n\t\tNormalizeLinks(_son, _cyclicBufferSize * 2, subValue);\n\t\tNormalizeLinks(_hash, _hashSizeSum, subValue);\n\t\treduceOffsets(subValue);\n\t}\n\t\n\tpublic void SetCutValue(int cutValue) { _cutValue = cutValue; }\n\n\tprivate static final int[] CrcTable = new int[256];\n\n\tstatic\n\t{\n\t\tfor (int i = 0; i < 256; i++)\n\t\t{\n\t\t\tint r = i;\n\t\t\tfor (int j = 0; j < 8; j++)\n\t\t\t\tif ((r & 1) != 0)\n\t\t\t\t\tr = (r >>> 1) ^ 0xEDB88320;\n\t\t\t\telse\n\t\t\t\t\tr >>>= 1;\n\t\t\tCrcTable[i] = r;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Compression/LZ/InWindow.java",
    "content": "// LZ.InWindow\n\npackage com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.LZ;\n\nimport java.io.IOException;\n\npublic class InWindow {\n\tpublic byte[] _bufferBase; // pointer to buffer with data\n\tjava.io.InputStream _stream;\n\tint _posLimit;  // offset (from _buffer) of first byte when new block reading must be done\n\tboolean _streamEndWasReached; // if (true) then _streamPos shows real end of stream\n\t\n\tint _pointerToLastSafePosition;\n\t\n\tpublic int _bufferOffset;\n\t\n\tpublic int _blockSize;  // Size of Allocated memory block\n\tpublic int _pos;             // offset (from _buffer) of curent byte\n\tint _keepSizeBefore;  // how many BYTEs must be kept in buffer before _pos\n\tint _keepSizeAfter;   // how many BYTEs must be kept buffer after _pos\n\tpublic int _streamPos;   // offset (from _buffer) of first not read byte from Stream\n\t\n\tpublic void moveBlock() {\n\t\tint offset = _bufferOffset + _pos - _keepSizeBefore;\n\t\t// we need one additional byte, since movePos moves on 1 byte.\n\t\tif (offset > 0)\n\t\t\toffset--;\n\n\t\tint numBytes = _bufferOffset + _streamPos - offset;\n\t\t\n\t\t// check negative offset ????\n\t\tfor (int i = 0; i < numBytes; i++)\n\t\t\t_bufferBase[i] = _bufferBase[offset + i];\n\t\t_bufferOffset -= offset;\n\t}\n\t\n\tpublic void readBlock() throws IOException\n\t{\n\t\tif (_streamEndWasReached)\n\t\t\treturn;\n\t\twhile (true)\n\t\t{\n\t\t\tint size = (0 - _bufferOffset) + _blockSize - _streamPos;\n\t\t\tif (size == 0)\n\t\t\t\treturn;\n\t\t\tint numReadBytes = _stream.read(_bufferBase, _bufferOffset + _streamPos, size);\n\t\t\tif (numReadBytes == -1)\n\t\t\t{\n\t\t\t\t_posLimit = _streamPos;\n\t\t\t\tint pointerToPostion = _bufferOffset + _posLimit;\n\t\t\t\tif (pointerToPostion > _pointerToLastSafePosition)\n\t\t\t\t\t_posLimit = _pointerToLastSafePosition - _bufferOffset;\n\t\t\t\t\n\t\t\t\t_streamEndWasReached = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t_streamPos += numReadBytes;\n\t\t\tif (_streamPos >= _pos + _keepSizeAfter)\n\t\t\t\t_posLimit = _streamPos - _keepSizeAfter;\n\t\t}\n\t}\n\t\n\tvoid free() { _bufferBase = null; }\n\t\n\tpublic void create(int keepSizeBefore, int keepSizeAfter, int keepSizeReserv)\n\t{\n\t\t_keepSizeBefore = keepSizeBefore;\n\t\t_keepSizeAfter = keepSizeAfter;\n\t\tint blockSize = keepSizeBefore + keepSizeAfter + keepSizeReserv;\n\t\tif (_bufferBase == null || _blockSize != blockSize)\n\t\t{\n\t\t\tfree();\n\t\t\t_blockSize = blockSize;\n\t\t\t_bufferBase = new byte[_blockSize];\n\t\t}\n\t\t_pointerToLastSafePosition = _blockSize - keepSizeAfter;\n\t}\n\t\n\tpublic void SetStream(java.io.InputStream stream) { _stream = stream; \t}\n\tpublic void ReleaseStream() { _stream = null; }\n\n\tpublic void Init() throws IOException\n\t{\n\t\t_bufferOffset = 0;\n\t\t_pos = 0;\n\t\t_streamPos = 0;\n\t\t_streamEndWasReached = false;\n\t\treadBlock();\n\t}\n\t\n\tpublic void movePos() throws IOException\n\t{\n\t\t_pos++;\n\t\tif (_pos > _posLimit)\n\t\t{\n\t\t\tint pointerToPostion = _bufferOffset + _pos;\n\t\t\tif (pointerToPostion > _pointerToLastSafePosition)\n\t\t\t\tmoveBlock();\n\t\t\treadBlock();\n\t\t}\n\t}\n\t\n\tpublic byte getIndexByte(int index)\t{ return _bufferBase[_bufferOffset + _pos + index]; }\n\t\n\t// index + limit have not to exceed _keepSizeAfter;\n\tpublic int getMatchLen(int index, int distance, int limit)\n\t{\n\t\tif (_streamEndWasReached)\n\t\t\tif ((_pos + index) + limit > _streamPos)\n\t\t\t\tlimit = _streamPos - (_pos + index);\n\t\tdistance++;\n\t\t// Byte *pby = _buffer + (size_t)_pos + index;\n\t\tint pby = _bufferOffset + _pos + index;\n\t\t\n\t\tint i;\n\t\tfor (i = 0; i < limit && _bufferBase[pby + i] == _bufferBase[pby + i - distance]; i++);\n\t\treturn i;\n\t}\n\t\n\tpublic int getNumAvailableBytes()\t{ return _streamPos - _pos; }\n\t\n\tpublic void reduceOffsets(int subValue)\n\t{\n\t\t_bufferOffset += subValue;\n\t\t_posLimit -= subValue;\n\t\t_pos -= subValue;\n\t\t_streamPos -= subValue;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Compression/LZ/OutWindow.java",
    "content": "// LZ.OutWindow\n\npackage com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.LZ;\n\nimport java.io.IOException;\n\npublic class OutWindow {\n    byte[] _buffer;\n    byte[] _buffer2 = null;\n    int    _bufferPos2 = 0;\n    int _pos;\n    int _windowSize = 0;\n    int _streamPos;\n    java.io.OutputStream _stream;\n    long _processedSize;\n    \n    public void Create(int windowSize) {\n        final int kMinBlockSize = 1;\n        if (windowSize < kMinBlockSize)\n            windowSize = kMinBlockSize;\n        \n        if (_buffer == null || _windowSize != windowSize)\n            _buffer = new byte[windowSize];\n        _windowSize = windowSize;\n        _pos = 0;\n        _streamPos = 0;\n    }\n    \n    public void SetStream(java.io.OutputStream stream) throws IOException {\n        ReleaseStream();\n        _stream = stream;\n    }\n    \n    public void ReleaseStream() throws IOException {\n        Flush();\n        _stream = null;\n    }\n    \n    public void SetMemStream(byte [] d) {\n        _buffer2 = d;\n        _bufferPos2 = 0;\n    }\n    \n    public void Init() {\n        Init(false);\n    }\n    public void Init(boolean solid) {\n        _processedSize = 0;\n        if (!solid) {\n            _streamPos = 0;\n            _pos = 0;\n        }\n    }\n    \n    public void Flush() throws IOException {\n        int size = _pos - _streamPos;\n        if (size == 0)\n            return;\n        if (_stream != null) _stream.write(_buffer, _streamPos, size);\n        if (_buffer2 != null) {\n            System.arraycopy(_buffer, _streamPos, _buffer2, _bufferPos2, size);\n            _bufferPos2 += size;\n        }\n        if (_pos >= _windowSize)\n            _pos = 0;\n        _streamPos = _pos;\n    }\n    \n    public void CopyBlock(int distance, int len) throws IOException {\n        int pos = _pos - distance - 1;\n        if (pos < 0)\n            pos += _windowSize;\n        for (; len != 0; len--) {\n            if (pos >= _windowSize)\n                pos = 0;\n            _buffer[_pos++] = _buffer[pos++];\n            _processedSize++;\n            if (_pos >= _windowSize)\n                Flush();\n        }\n    }\n    \n    public void PutByte(byte b) throws IOException {\n        _buffer[_pos++] = b;\n        _processedSize++;\n        if (_pos >= _windowSize)\n            Flush();\n    }\n    \n    public void WriteByte(int b)  throws IOException {\n        _buffer[_pos++] = (byte)b;\n        _processedSize++;\n        if (_pos >= _windowSize)\n            Flush();\n    }\n    \n    public byte GetByte(int distance) {\n        int pos = _pos - distance - 1;\n        if (pos < 0)\n            pos += _windowSize;\n        return _buffer[pos];\n    }\n    \n    public long GetProcessedSize() {\n        return _processedSize;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Compression/LZMA/Base.java",
    "content": "// Base.java\n\npackage com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.LZMA;\n\npublic class Base\n{\n\tpublic static final int kNumRepDistances = 4;\n\tpublic static final int kNumStates = 12;\n\t\n\tpublic static int StateInit()\n\t{\n\t\treturn 0;\n\t}\n\t\n\tpublic static int StateUpdateChar(int index)\n\t{\n\t\tif (index < 4) \n\t\t\treturn 0;\n\t\tif (index < 10) \n\t\t\treturn index - 3;\n\t\treturn index - 6;\n\t}\n\t\n\tpublic static int StateUpdateMatch(int index)\n\t{\n\t\treturn (index < 7 ? 7 : 10); \n\t}\n\n\tpublic static int StateUpdateRep(int index)\n\t{ \n\t\treturn (index < 7 ? 8 : 11); \n\t}\n\t\n\tpublic static int StateUpdateShortRep(int index)\n\t{ \n\t\treturn (index < 7 ? 9 : 11); \n\t}\n\n\tpublic static boolean StateIsCharState(int index)\n\t{ \n\t\treturn index < 7; \n\t}\n\t\n\tpublic static final int kNumPosSlotBits = 6;\n\tpublic static final int kDicLogSizeMin = 0;\n\t// public static final int kDicLogSizeMax = 28;\n\t// public static final int kDistTableSizeMax = kDicLogSizeMax * 2;\n\t\n\tpublic static final int kNumLenToPosStatesBits = 2; // it's for speed optimization\n\tpublic static final int kNumLenToPosStates = 1 << kNumLenToPosStatesBits;\n\t\n\tpublic static final int kMatchMinLen = 2;\n\t\n\tpublic static int GetLenToPosState(int len)\n\t{\n\t\tlen -= kMatchMinLen;\n\t\tif (len < kNumLenToPosStates)\n\t\t\treturn len;\n\t\treturn kNumLenToPosStates - 1;\n\t}\n\t\n\tpublic static final int kNumAlignBits = 4;\n\tpublic static final int kAlignTableSize = 1 << kNumAlignBits;\n\tpublic static final int kAlignMask = (kAlignTableSize - 1);\n\t\n\tpublic static final int kStartPosModelIndex = 4;\n\tpublic static final int kEndPosModelIndex = 14;\n\tpublic static final int kNumPosModels = kEndPosModelIndex - kStartPosModelIndex;\n\t\n\tpublic static final  int kNumFullDistances = 1 << (kEndPosModelIndex / 2);\n\t\n\tpublic static final  int kNumLitPosStatesBitsEncodingMax = 4;\n\tpublic static final  int kNumLitContextBitsMax = 8;\n\t\n\tpublic static final  int kNumPosStatesBitsMax = 4;\n\tpublic static final  int kNumPosStatesMax = (1 << kNumPosStatesBitsMax);\n\tpublic static final  int kNumPosStatesBitsEncodingMax = 4;\n\tpublic static final  int kNumPosStatesEncodingMax = (1 << kNumPosStatesBitsEncodingMax);\n\t\n\tpublic static final  int kNumLowLenBits = 3;\n\tpublic static final  int kNumMidLenBits = 3;\n\tpublic static final  int kNumHighLenBits = 8;\n\tpublic static final  int kNumLowLenSymbols = 1 << kNumLowLenBits;\n\tpublic static final  int kNumMidLenSymbols = 1 << kNumMidLenBits;\n\tpublic static final  int kNumLenSymbols = kNumLowLenSymbols + kNumMidLenSymbols + (1 << kNumHighLenBits);\n\tpublic static final  int kMatchMaxLen = kMatchMinLen + kNumLenSymbols - 1;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Compression/LZMA/Decoder.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.LZMA;\n\n\nimport java.io.IOException;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.HRESULT;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressProgressInfo;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.LZ.OutWindow;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.BitTreeDecoder;\n\n\n/*\n  public ICompressCoder,\n  public ICompressSetDecoderProperties2,\n  public ICompressGetInStreamProcessedSize,\n  #ifdef _ST_MODE\n  public ICompressSetInStream,\n  public ICompressSetOutStreamSize,\n  public ISequentialInStream,\n  #endif\n */\n// OLD CODE public class Decoder implements SevenZip.ICompressCoder , SevenZip.ICompressSetDecoderProperties2\npublic class Decoder\n        extends java.io.InputStream // _ST_MODE\n        implements com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressCoder , com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressSetDecoderProperties2 ,\n        com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressGetInStreamProcessedSize,\n        // #ifdef _ST_MODE\n        com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressSetInStream,\n        com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressSetOutStreamSize\n        // #endif\n        \n{\n    static class LenDecoder {\n        short[] m_Choice = new short[2];\n        BitTreeDecoder[] m_LowCoder = new BitTreeDecoder[Base.kNumPosStatesMax];\n        BitTreeDecoder[] m_MidCoder = new BitTreeDecoder[Base.kNumPosStatesMax];\n        BitTreeDecoder m_HighCoder = new BitTreeDecoder(Base.kNumHighLenBits);\n        int m_NumPosStates = 0;\n        \n        public void Create(int numPosStates) {\n            for (; m_NumPosStates < numPosStates; m_NumPosStates++) {\n                m_LowCoder[m_NumPosStates] = new BitTreeDecoder(Base.kNumLowLenBits);\n                m_MidCoder[m_NumPosStates] = new BitTreeDecoder(Base.kNumMidLenBits);\n            }\n        }\n        \n        public void Init() {\n            com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Decoder.InitBitModels(m_Choice);\n            for (int posState = 0; posState < m_NumPosStates; posState++) {\n                m_LowCoder[posState].Init();\n                m_MidCoder[posState].Init();\n            }\n            m_HighCoder.Init();\n        }\n        \n        public int Decode(com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Decoder rangeDecoder, int posState) throws IOException {\n            if (rangeDecoder.DecodeBit(m_Choice, 0) == 0)\n                return m_LowCoder[posState].Decode(rangeDecoder);\n            int symbol = Base.kNumLowLenSymbols;\n            if (rangeDecoder.DecodeBit(m_Choice, 1) == 0)\n                symbol += m_MidCoder[posState].Decode(rangeDecoder);\n            else\n                symbol += Base.kNumMidLenSymbols + m_HighCoder.Decode(rangeDecoder);\n            return symbol;\n        }\n    }\n    \n    static class LiteralDecoder {\n        static class Decoder2 {\n            short[] m_Decoders = new short[0x300];\n            \n            public void Init() {\n                com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Decoder.InitBitModels(m_Decoders);\n            }\n            \n            public byte DecodeNormal(com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Decoder rangeDecoder) throws IOException {\n                int symbol = 1;\n                do\n                    symbol = (symbol << 1) | rangeDecoder.DecodeBit(m_Decoders, symbol);\n                while (symbol < 0x100);\n                return (byte)symbol;\n            }\n            \n            public byte DecodeWithMatchByte(com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Decoder rangeDecoder, byte matchByte) throws IOException {\n                int symbol = 1;\n                do\n                {\n                    int matchBit = (matchByte >> 7) & 1;\n                    matchByte <<= 1;\n                    int bit = rangeDecoder.DecodeBit(m_Decoders, ((1 + matchBit) << 8) + symbol);\n                    symbol = (symbol << 1) | bit;\n                    if (matchBit != bit) {\n                        while (symbol < 0x100)\n                            symbol = (symbol << 1) | rangeDecoder.DecodeBit(m_Decoders, symbol);\n                        break;\n                    }\n                }\n                while (symbol < 0x100);\n                return (byte)symbol;\n            }\n        }\n        \n        Decoder2[] m_Coders;\n        int m_NumPrevBits;\n        int m_NumPosBits;\n        int m_PosMask;\n        \n        public void Create(int numPosBits, int numPrevBits) {\n            if (m_Coders != null && m_NumPrevBits == numPrevBits && m_NumPosBits == numPosBits)\n                return;\n            m_NumPosBits = numPosBits;\n            m_PosMask = (1 << numPosBits) - 1;\n            m_NumPrevBits = numPrevBits;\n            int numStates = 1 << (m_NumPrevBits + m_NumPosBits);\n            m_Coders = new Decoder2[numStates];\n            for (int i = 0; i < numStates; i++)\n                m_Coders[i] = new Decoder2();\n        }\n        \n        public void Init() {\n            int numStates = 1 << (m_NumPrevBits + m_NumPosBits);\n            for (int i = 0; i < numStates; i++)\n                m_Coders[i].Init();\n        }\n        \n        Decoder2 GetDecoder(int pos, byte prevByte) {\n            return m_Coders[((pos & m_PosMask) << m_NumPrevBits) + ((prevByte & 0xFF) >>> (8 - m_NumPrevBits))];\n        }\n    }\n    \n    OutWindow m_OutWindow = new OutWindow();\n    com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Decoder m_RangeDecoder = new com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Decoder();\n    \n    short[] m_IsMatchDecoders = new short[Base.kNumStates << Base.kNumPosStatesBitsMax];\n    short[] m_IsRepDecoders = new short[Base.kNumStates];\n    short[] m_IsRepG0Decoders = new short[Base.kNumStates];\n    short[] m_IsRepG1Decoders = new short[Base.kNumStates];\n    short[] m_IsRepG2Decoders = new short[Base.kNumStates];\n    short[] m_IsRep0LongDecoders = new short[Base.kNumStates << Base.kNumPosStatesBitsMax];\n    \n    BitTreeDecoder[] m_PosSlotDecoder = new BitTreeDecoder[Base.kNumLenToPosStates];\n    short[] m_PosDecoders = new short[Base.kNumFullDistances - Base.kEndPosModelIndex];\n    \n    BitTreeDecoder m_PosAlignDecoder = new BitTreeDecoder(Base.kNumAlignBits);\n    \n    LenDecoder m_LenDecoder = new LenDecoder();\n    LenDecoder m_RepLenDecoder = new LenDecoder();\n    \n    LiteralDecoder m_LiteralDecoder = new LiteralDecoder();\n    \n    int m_DictionarySize = -1;\n    int m_DictionarySizeCheck =  -1;\n    \n    int m_posStateMask;\n    \n    public Decoder() {\n        for (int i = 0; i < Base.kNumLenToPosStates; i++)\n            m_PosSlotDecoder[i] = new BitTreeDecoder(Base.kNumPosSlotBits);\n    }\n    \n    boolean SetDictionarySize(int dictionarySize) {\n        if (dictionarySize < 0)\n            return false;\n        if (m_DictionarySize != dictionarySize) {\n            m_DictionarySize = dictionarySize;\n            m_DictionarySizeCheck = Math.max(m_DictionarySize, 1);\n            m_OutWindow.Create(Math.max(m_DictionarySizeCheck, (1 << 12)));\n            m_RangeDecoder.Create(1 << 20);\n        }\n        return true;\n    }\n    \n    boolean setLcLpPb(int lc, int lp, int pb) {\n        if (lc > Base.kNumLitContextBitsMax || lp > 4 || pb > Base.kNumPosStatesBitsMax)\n            return false;\n        m_LiteralDecoder.Create(lp, lc);\n        int numPosStates = 1 << pb;\n        m_LenDecoder.Create(numPosStates);\n        m_RepLenDecoder.Create(numPosStates);\n        m_posStateMask = numPosStates - 1;\n        return true;\n    }\n    \n    \n    public long GetInStreamProcessedSize() {\n        throw new UnknownError(\"GetInStreamProcessedSize\");\n        // return m_RangeDecoder.GetProcessedSize();\n    }\n    \n    public int ReleaseInStream() throws IOException {\n        m_RangeDecoder.ReleaseStream();\n        return com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.HRESULT.S_OK;\n    }\n    \n    public int SetInStream(java.io.InputStream inStream) { // Common.ISequentialInStream\n        m_RangeDecoder.SetStream(inStream);\n        return com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.HRESULT.S_OK;\n    }\n    \n    long _outSize = 0;\n    boolean _outSizeDefined = false;\n    int _remainLen; // -1 means end of stream. // -2 means need Init\n    static final int kLenIdFinished = -1;\n    static final int kLenIdNeedInit = -2;\n    int _rep0;\n    int _rep1;\n    int _rep2;\n    int _rep3;\n    int _state;\n    \n    public int SetOutStreamSize(long outSize /* const UInt64 *outSize*/ ) {\n        _outSizeDefined = (outSize != com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICompressSetOutStreamSize.INVALID_OUTSIZE);\n        if (_outSizeDefined)\n            _outSize = outSize;\n        _remainLen = kLenIdNeedInit;\n        m_OutWindow.Init();\n        return com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.HRESULT.S_OK;\n    }\n    \n    // #ifdef _ST_MODE\n    public int read() throws java.io.IOException {\n        throw new java.io.IOException(\"LZMA Decoder - read() not implemented\");\n    }\n    \n    public int read(byte [] data, int off, int size) throws IOException  {\n        if (off  != 0)throw new java.io.IOException(\"LZMA Decoder - read(byte [] data, int off != 0, int size)) not implemented\");\n        \n        long startPos = m_OutWindow.GetProcessedSize();\n        m_OutWindow.SetMemStream(data);\n        int res = CodeSpec(size);\n        if (res != HRESULT.S_OK) throw new IOException(\"Read - CodeSpec = \" + res);\n        \n        res = Flush();\n        if (res != HRESULT.S_OK) throw new IOException(\"Read - Flush = \" + res);\n        int ret = (int)(m_OutWindow.GetProcessedSize() - startPos);\n        if (ret == 0) ret = -1;\n        return ret;\n    }\n    \n    // #endif // _ST_MODE\n    \n    void Init() {\n        m_OutWindow.Init(false);\n        \n        com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Decoder.InitBitModels(m_IsMatchDecoders);\n        com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Decoder.InitBitModels(m_IsRep0LongDecoders);\n        com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Decoder.InitBitModels(m_IsRepDecoders);\n        com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Decoder.InitBitModels(m_IsRepG0Decoders);\n        com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Decoder.InitBitModels(m_IsRepG1Decoders);\n        com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Decoder.InitBitModels(m_IsRepG2Decoders);\n        com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Decoder.InitBitModels(m_PosDecoders);\n        \n        _rep0 = _rep1 = _rep2 = _rep3 = 0;\n        _state = Base.StateInit();\n        \n        m_LiteralDecoder.Init();\n        int i;\n        for (i = 0; i < Base.kNumLenToPosStates; i++)\n            m_PosSlotDecoder[i].Init();\n        m_LenDecoder.Init();\n        m_RepLenDecoder.Init();\n        m_PosAlignDecoder.Init();\n    }\n    \n    public int Flush() throws IOException {\n        m_OutWindow.Flush();\n        return HRESULT.S_OK;\n    }\n    \n    void ReleaseStreams() throws IOException  {\n        m_OutWindow.ReleaseStream();\n        ReleaseInStream();\n    }\n    \n    public int CodeReal(\n            java.io.InputStream inStream, // , ISequentialInStream\n            java.io.OutputStream outStream, // ISequentialOutStream\n            long outSize,\n            ICompressProgressInfo progress // useless_progress\n            ) throws IOException {\n        \n        SetInStream(inStream);\n        m_OutWindow.SetStream(outStream);\n        SetOutStreamSize(outSize);\n        \n        for (;;) {\n            int curSize = 1 << 18;\n            int res = CodeSpec(curSize);\n            if (res != HRESULT.S_OK) {\n                return res;\n            }\n            if (_remainLen == kLenIdFinished)\n                break;\n            \n            if (progress != null) {\n                long inSize = m_RangeDecoder.GetProcessedSize();\n                long nowPos64 = m_OutWindow.GetProcessedSize();\n                res = progress.SetRatioInfo(inSize, nowPos64);\n                if (res != HRESULT.S_OK) {\n                    return res;\n                }\n            }\n            \n            if (_outSizeDefined)\n                if (m_OutWindow.GetProcessedSize() >= _outSize)\n                    break;\n        }\n        return Flush();\n    }\n    \n    public int Code(\n            java.io.InputStream inStream, // , ISequentialInStream\n            java.io.OutputStream outStream, // ISequentialOutStream\n            long outSize,\n            ICompressProgressInfo progress // useless_progress\n            ) throws IOException {\n        \n        int ret = HRESULT.S_FALSE;\n        try {\n            ret = CodeReal(inStream,outStream,outSize,progress);\n        } catch (IOException e) {\n            e.printStackTrace(); // TBD\n            this.Flush();\n            this.ReleaseStreams();\n            throw e;\n        } finally {\n            this.Flush();\n            this.ReleaseStreams();\n        }\n        return ret;\n    }\n    \n    int CodeSpec(int curSize)  throws IOException // UInt32\n    {\n        if (_outSizeDefined) {\n            long rem = _outSize - m_OutWindow.GetProcessedSize();\n            if (curSize > rem)\n                curSize = (int)rem;\n        }\n        \n        if (_remainLen == kLenIdFinished)\n            return HRESULT.S_OK;\n        if (_remainLen == kLenIdNeedInit) {\n            m_RangeDecoder.Init();\n            Init();\n            _remainLen = 0;\n        }\n        if (curSize == 0)\n            return HRESULT.S_OK;\n        \n        int rep0 = _rep0;\n        int rep1 = _rep1;\n        int rep2 = _rep2;\n        int rep3 = _rep3;\n        int state = _state;\n        byte prevByte;\n        \n        while(_remainLen > 0 && curSize > 0) {\n            prevByte = m_OutWindow.GetByte(rep0);\n            m_OutWindow.PutByte(prevByte);\n            _remainLen--;\n            curSize--;\n        }\n        long nowPos64 = m_OutWindow.GetProcessedSize();\n        if (nowPos64 == 0)\n            prevByte = 0;\n        else\n            prevByte = m_OutWindow.GetByte(0);\n        \n        while(curSize > 0) {\n            \n            if (m_RangeDecoder.bufferedStream.WasFinished())\n                return HRESULT.S_FALSE;\n            int posState = (int)nowPos64 & m_posStateMask;\n            \n            if (m_RangeDecoder.DecodeBit(m_IsMatchDecoders, (state << Base.kNumPosStatesBitsMax) + posState) == 0) {\n                LiteralDecoder.Decoder2 decoder2 = m_LiteralDecoder.GetDecoder((int)nowPos64, prevByte);\n                if (!Base.StateIsCharState(state))\n                    prevByte = decoder2.DecodeWithMatchByte(m_RangeDecoder, m_OutWindow.GetByte(rep0));\n                else\n                    prevByte = decoder2.DecodeNormal(m_RangeDecoder);\n                m_OutWindow.PutByte(prevByte);\n                state = Base.StateUpdateChar(state);\n                curSize--;\n                nowPos64++;\n            } else {\n                int len;\n                if (m_RangeDecoder.DecodeBit(m_IsRepDecoders, state) == 1) {\n                    len = 0;\n                    if (m_RangeDecoder.DecodeBit(m_IsRepG0Decoders, state) == 0) {\n                        if (m_RangeDecoder.DecodeBit(m_IsRep0LongDecoders, (state << Base.kNumPosStatesBitsMax) + posState) == 0) {\n                            state = Base.StateUpdateShortRep(state);\n                            len = 1;\n                        }\n                    } else {\n                        int distance;\n                        if (m_RangeDecoder.DecodeBit(m_IsRepG1Decoders, state) == 0)\n                            distance = rep1;\n                        else {\n                            if (m_RangeDecoder.DecodeBit(m_IsRepG2Decoders, state) == 0)\n                                distance = rep2;\n                            else {\n                                distance = rep3;\n                                rep3 = rep2;\n                            }\n                            rep2 = rep1;\n                        }\n                        rep1 = rep0;\n                        rep0 = distance;\n                    }\n                    if (len == 0) {\n                        len = m_RepLenDecoder.Decode(m_RangeDecoder, posState) + Base.kMatchMinLen;\n                        state = Base.StateUpdateRep(state);\n                    }\n                } else {\n                    rep3 = rep2;\n                    rep2 = rep1;\n                    rep1 = rep0;\n                    len = Base.kMatchMinLen + m_LenDecoder.Decode(m_RangeDecoder, posState);\n                    state = Base.StateUpdateMatch(state);\n                    int posSlot = m_PosSlotDecoder[Base.GetLenToPosState(len)].Decode(m_RangeDecoder);\n                    if (posSlot >= Base.kStartPosModelIndex) {\n                        int numDirectBits = (posSlot >> 1) - 1;\n                        rep0 = ((2 | (posSlot & 1)) << numDirectBits);\n                        if (posSlot < Base.kEndPosModelIndex)\n                            rep0 += BitTreeDecoder.ReverseDecode(m_PosDecoders,\n                                    rep0 - posSlot - 1, m_RangeDecoder, numDirectBits);\n                        else {\n                            rep0 += (m_RangeDecoder.DecodeDirectBits(\n                                    numDirectBits - Base.kNumAlignBits) << Base.kNumAlignBits);\n                            rep0 += m_PosAlignDecoder.ReverseDecode(m_RangeDecoder);\n                            if (rep0 < 0) {\n                                if (rep0 == -1)\n                                    break;\n                                return HRESULT.S_FALSE;\n                            }\n                        }\n                    } else\n                        rep0 = posSlot;\n                }\n                if (rep0 >= nowPos64 || rep0 >= m_DictionarySizeCheck) {\n                    // m_OutWindow.Flush();\n                    _remainLen = kLenIdFinished;\n                    return HRESULT.S_FALSE;\n                }\n\n\n                int locLen = Math.min(len, curSize);\n                // if (!m_OutWindow.CopyBlock(rep0, locLen))\n                //    return HRESULT.S_FALSE;\n                m_OutWindow.CopyBlock(rep0, locLen);\n                prevByte = m_OutWindow.GetByte(0);\n                curSize -= locLen;\n                nowPos64 += locLen;\n                len -= locLen;\n                if (len != 0) {\n                    _remainLen = len;\n                    break;\n                }\n            }\n        }\n        if (m_RangeDecoder.bufferedStream.WasFinished())\n            return HRESULT.S_FALSE;\n        \n        _rep0 = rep0;\n        _rep1 = rep1;\n        _rep2 = rep2;\n        _rep3 = rep3;\n        _state = state;\n        \n        return HRESULT.S_OK;\n    }\n    \n    public boolean SetDecoderProperties2(byte[] properties) {\n        if (properties.length < 5)\n            return false;\n        int val = properties[0] & 0xFF;\n        int lc = val % 9;\n        int remainder = val / 9;\n        int lp = remainder % 5;\n        int pb = remainder / 5;\n        int dictionarySize = 0;\n        for (int i = 0; i < 4; i++)\n            dictionarySize += ((int) (properties[1 + i]) & 0xFF) << (i * 8);\n        return setLcLpPb(lc, lp, pb) && SetDictionarySize(dictionarySize);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Compression/LZMA/Encoder.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.LZMA;\n\nimport java.io.IOException;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.ICodeProgress;\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.BitTreeEncoder;\n\npublic class Encoder {\n\tprivate static final int EMatchFinderTypeBT2 = 0;\n\tprivate static final int EMatchFinderTypeBT4 = 1;\n\n\tstatic final int kInfinityPrice = 0xFFFFFFF;\n\n\tstatic byte[] g_FastPos = new byte[1 << 11];\n\n\tstatic {\n\t\tint kFastSlots = 22;\n\t\tint c = 2;\n\t\tg_FastPos[0] = 0;\n\t\tg_FastPos[1] = 1;\n\t\tfor (int slotFast = 2; slotFast < kFastSlots; slotFast++)\n\t\t{\n\t\t\tint k = (1 << ((slotFast >> 1) - 1));\n\t\t\tfor (int j = 0; j < k; j++, c++)\n\t\t\t\tg_FastPos[c] = (byte)slotFast;\n\t\t}\n\t}\n\n\tstatic int GetPosSlot(int pos) {\n\t\tif (pos < (1 << 11))\n\t\t\treturn g_FastPos[pos];\n\t\tif (pos < (1 << 21))\n\t\t\treturn (g_FastPos[pos >> 10] + 20);\n\t\treturn (g_FastPos[pos >> 20] + 40);\n\t}\n\n\tstatic int GetPosSlot2(int pos)\n\t{\n\t\tif (pos < (1 << 17))\n\t\t\treturn (g_FastPos[pos >> 6] + 12);\n\t\tif (pos < (1 << 27))\n\t\t\treturn (g_FastPos[pos >> 16] + 32);\n\t\treturn (g_FastPos[pos >> 26] + 52);\n\t}\n\n\tint _state = Base.StateInit();\n\tbyte _previousByte;\n\tint[] _repDistances = new int[Base.kNumRepDistances];\n\n\tvoid BaseInit()\n\t{\n\t\t_state = Base.StateInit();\n\t\t_previousByte = 0;\n\t\tfor (int i = 0; i < Base.kNumRepDistances; i++)\n\t\t\t_repDistances[i] = 0;\n\t}\n\n\tstatic final int kDefaultDictionaryLogSize = 22;\n\tstatic final int kNumFastBytesDefault = 0x20;\n\n\tstatic class LiteralEncoder\n\t{\n\t\tstatic class Encoder2\n\t\t{\n\t\t\tshort[] m_Encoders = new short[0x300];\n\n\t\t\tpublic void Init() { com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.InitBitModels(m_Encoders); }\n\n\n\n\t\t\tvoid Encode(com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder rangeEncoder, byte symbol) throws IOException\n\t\t\t{\n\t\t\t\tint context = 1;\n\t\t\t\tfor (int i = 7; i >= 0; i--)\n\t\t\t\t{\n\t\t\t\t\tint bit = ((symbol >> i) & 1);\n\t\t\t\t\trangeEncoder.Encode(m_Encoders, context, bit);\n\t\t\t\t\tcontext = (context << 1) | bit;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvoid EncodeMatched(com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder rangeEncoder, byte matchByte, byte symbol) throws IOException\n\t\t\t{\n\t\t\t\tint context = 1;\n\t\t\t\tboolean same = true;\n\t\t\t\tfor (int i = 7; i >= 0; i--)\n\t\t\t\t{\n\t\t\t\t\tint bit = ((symbol >> i) & 1);\n\t\t\t\t\tint state = context;\n\t\t\t\t\tif (same)\n\t\t\t\t\t{\n\t\t\t\t\t\tint matchBit = ((matchByte >> i) & 1);\n\t\t\t\t\t\tstate += ((1 + matchBit) << 8);\n\t\t\t\t\t\tsame = (matchBit == bit);\n\t\t\t\t\t}\n\t\t\t\t\trangeEncoder.Encode(m_Encoders, state, bit);\n\t\t\t\t\tcontext = (context << 1) | bit;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpublic int GetPrice(boolean matchMode, byte matchByte, byte symbol)\n\t\t\t{\n\t\t\t\tint price = 0;\n\t\t\t\tint context = 1;\n\t\t\t\tint i = 7;\n\t\t\t\tif (matchMode)\n\t\t\t\t{\n\t\t\t\t\tfor (; i >= 0; i--)\n\t\t\t\t\t{\n\t\t\t\t\t\tint matchBit = (matchByte >> i) & 1;\n\t\t\t\t\t\tint bit = (symbol >> i) & 1;\n\t\t\t\t\t\tprice += com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice(m_Encoders[((1 + matchBit) << 8) + context], bit);\n\t\t\t\t\t\tcontext = (context << 1) | bit;\n\t\t\t\t\t\tif (matchBit != bit)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ti--;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (; i >= 0; i--)\n\t\t\t\t{\n\t\t\t\t\tint bit = (symbol >> i) & 1;\n\t\t\t\t\tprice += com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice(m_Encoders[context], bit);\n\t\t\t\t\tcontext = (context << 1) | bit;\n\t\t\t\t}\n\t\t\t\treturn price;\n\t\t\t}\n\t\t}\n\n\t\tEncoder2[] m_Coders;\n\t\tint m_NumPrevBits;\n\t\tint m_NumPosBits;\n\t\tint m_PosMask;\n\n\t\tpublic void Create(int numPosBits, int numPrevBits)\n\t\t{\n\t\t\tif (m_Coders != null && m_NumPrevBits == numPrevBits && m_NumPosBits == numPosBits)\n\t\t\t\treturn;\n\t\t\tm_NumPosBits = numPosBits;\n\t\t\tm_PosMask = (1 << numPosBits) - 1;\n\t\t\tm_NumPrevBits = numPrevBits;\n\t\t\tint numStates = 1 << (m_NumPrevBits + m_NumPosBits);\n\t\t\tm_Coders = new Encoder2[numStates];\n\t\t\tfor (int i = 0; i < numStates; i++)\n\t\t\t\tm_Coders[i] = new Encoder2();\n\t\t}\n\n\t\tpublic void Init()\n\t\t{\n\t\t\tint numStates = 1 << (m_NumPrevBits + m_NumPosBits);\n\t\t\tfor (int i = 0; i < numStates; i++)\n\t\t\t\tm_Coders[i].Init();\n\t\t}\n\n\t\tpublic Encoder2 GetSubCoder(int pos, byte prevByte)\n\t\t{ return m_Coders[((pos & m_PosMask) << m_NumPrevBits) + ((prevByte & 0xFF) >>> (8 - m_NumPrevBits))]; }\n\t}\n\n\tstatic class LenEncoder\n\t{\n\t\tshort[] _choice = new short[2];\n\t\tBitTreeEncoder[] _lowCoder = new BitTreeEncoder[Base.kNumPosStatesEncodingMax];\n\t\tBitTreeEncoder[] _midCoder = new BitTreeEncoder[Base.kNumPosStatesEncodingMax];\n\t\tBitTreeEncoder _highCoder = new BitTreeEncoder(Base.kNumHighLenBits);\n\n\n\t\tpublic LenEncoder()\n\t\t{\n\t\t\tfor (int posState = 0; posState < Base.kNumPosStatesEncodingMax; posState++)\n\t\t\t{\n\t\t\t\t_lowCoder[posState] = new BitTreeEncoder(Base.kNumLowLenBits);\n\t\t\t\t_midCoder[posState] = new BitTreeEncoder(Base.kNumMidLenBits);\n\t\t\t}\n\t\t}\n\n\t\tpublic void Init(int numPosStates)\n\t\t{\n\t\t\tcom.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.InitBitModels(_choice);\n\n\t\t\tfor (int posState = 0; posState < numPosStates; posState++)\n\t\t\t{\n\t\t\t\t_lowCoder[posState].Init();\n\t\t\t\t_midCoder[posState].Init();\n\t\t\t}\n\t\t\t_highCoder.Init();\n\t\t}\n\n\t\tpublic void Encode(com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder rangeEncoder, int symbol, int posState) throws IOException\n\t\t{\n\t\t\tif (symbol < Base.kNumLowLenSymbols)\n\t\t\t{\n\t\t\t\trangeEncoder.Encode(_choice, 0, 0);\n\t\t\t\t_lowCoder[posState].Encode(rangeEncoder, symbol);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tsymbol -= Base.kNumLowLenSymbols;\n\t\t\t\trangeEncoder.Encode(_choice, 0, 1);\n\t\t\t\tif (symbol < Base.kNumMidLenSymbols)\n\t\t\t\t{\n\t\t\t\t\trangeEncoder.Encode(_choice, 1, 0);\n\t\t\t\t\t_midCoder[posState].Encode(rangeEncoder, symbol);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\trangeEncoder.Encode(_choice, 1, 1);\n\t\t\t\t\t_highCoder.Encode(rangeEncoder, symbol - Base.kNumMidLenSymbols);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tpublic void SetPrices(int posState, int numSymbols, int[] prices, int st)\n\t\t{\n\t\t\tint a0 = com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice0(_choice[0]);\n\t\t\tint a1 = com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice1(_choice[0]);\n\t\t\tint b0 = a1 + com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice0(_choice[1]);\n\t\t\tint b1 = a1 + com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice1(_choice[1]);\n\t\t\tint i;\n\t\t\tfor (i = 0; i < Base.kNumLowLenSymbols; i++)\n\t\t\t{\n\t\t\t\tif (i >= numSymbols)\n\t\t\t\t\treturn;\n\t\t\t\tprices[st + i] = a0 + _lowCoder[posState].GetPrice(i);\n\t\t\t}\n\t\t\tfor (; i < Base.kNumLowLenSymbols + Base.kNumMidLenSymbols; i++)\n\t\t\t{\n\t\t\t\tif (i >= numSymbols)\n\t\t\t\t\treturn;\n\t\t\t\tprices[st + i] = b0 + _midCoder[posState].GetPrice(i - Base.kNumLowLenSymbols);\n\t\t\t}\n\t\t\tfor (; i < numSymbols; i++)\n\t\t\t\tprices[st + i] = b1 + _highCoder.GetPrice(i - Base.kNumLowLenSymbols - Base.kNumMidLenSymbols);\n\t\t}\n\t}\n\n\tpublic static final int kNumLenSpecSymbols = Base.kNumLowLenSymbols + Base.kNumMidLenSymbols;\n\n\tclass LenPriceTableEncoder extends LenEncoder\n\t{\n\t\tint[] _prices = new int[Base.kNumLenSymbols<<Base.kNumPosStatesBitsEncodingMax];\n\t\tint _tableSize;\n\t\tint[] _counters = new int[Base.kNumPosStatesEncodingMax];\n\n\t\tpublic void SetTableSize(int tableSize) { _tableSize = tableSize; }\n\n\t\tpublic int GetPrice(int symbol, int posState)\n\t\t{\n\t\t\treturn _prices[posState * Base.kNumLenSymbols + symbol];\n\t\t}\n\n\t\tvoid UpdateTable(int posState)\n\t\t{\n\t\t\tSetPrices(posState, _tableSize, _prices, posState * Base.kNumLenSymbols);\n\t\t\t_counters[posState] = _tableSize;\n\t\t}\n\n\t\tpublic void UpdateTables(int numPosStates)\n\t\t{\n\t\t\tfor (int posState = 0; posState < numPosStates; posState++)\n\t\t\t\tUpdateTable(posState);\n\t\t}\n\n\t\tpublic void Encode(com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder rangeEncoder, int symbol, int posState) throws IOException\n\t\t{\n\t\t\tsuper.Encode(rangeEncoder, symbol, posState);\n\t\t\tif (--_counters[posState] == 0)\n\t\t\t\tUpdateTable(posState);\n\t\t}\n\t}\n\n\tstatic final int kNumOpts = 1 << 12;\n\tclass Optimal\n\t{\n\t\tpublic int State;\n\n\t\tpublic boolean Prev1IsChar;\n\t\tpublic boolean Prev2;\n\n\t\tpublic int PosPrev2;\n\t\tpublic int BackPrev2;\n\n\t\tpublic int Price;\n\t\tpublic int PosPrev;\n\t\tpublic int BackPrev;\n\n\t\tpublic int Backs0;\n\t\tpublic int Backs1;\n\t\tpublic int Backs2;\n\t\tpublic int Backs3;\n\n\t\tpublic void MakeAsChar() { BackPrev = -1; Prev1IsChar = false; }\n\t\tpublic void MakeAsShortRep() { BackPrev = 0; Prev1IsChar = false; }\n\t\tpublic boolean IsShortRep() { return (BackPrev == 0); }\n\t}\n\tOptimal[] _optimum = new Optimal[kNumOpts];\n\tcom.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.LZ.BinTree _matchFinder = null;\n\tcom.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder _rangeEncoder = new com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder();\n\n\tshort[] _isMatch = new short[Base.kNumStates<<Base.kNumPosStatesBitsMax];\n\tshort[] _isRep = new short[Base.kNumStates];\n\tshort[] _isRepG0 = new short[Base.kNumStates];\n\tshort[] _isRepG1 = new short[Base.kNumStates];\n\tshort[] _isRepG2 = new short[Base.kNumStates];\n\tshort[] _isRep0Long = new short[Base.kNumStates<<Base.kNumPosStatesBitsMax];\n\n\tBitTreeEncoder[] _posSlotEncoder = new BitTreeEncoder[Base.kNumLenToPosStates]; // kNumPosSlotBits\n\n\tshort[] _posEncoders = new short[Base.kNumFullDistances-Base.kEndPosModelIndex];\n\tBitTreeEncoder _posAlignEncoder = new BitTreeEncoder(Base.kNumAlignBits);\n\n\tLenPriceTableEncoder _lenEncoder = new LenPriceTableEncoder();\n\tLenPriceTableEncoder _repMatchLenEncoder = new LenPriceTableEncoder();\n\n\tLiteralEncoder _literalEncoder = new LiteralEncoder();\n\n\tint[] _matchDistances = new int[Base.kMatchMaxLen*2+2];\n\n\tint _numFastBytes = kNumFastBytesDefault;\n\tint _longestMatchLength;\n\tint _numDistancePairs;\n\n\tint _additionalOffset;\n\n\tint _optimumEndIndex;\n\tint _optimumCurrentIndex;\n\n\tboolean _longestMatchWasFound;\n\n\tint[] _posSlotPrices = new int[1<<(Base.kNumPosSlotBits+Base.kNumLenToPosStatesBits)];\n\tint[] _distancesPrices = new int[Base.kNumFullDistances<<Base.kNumLenToPosStatesBits];\n\tint[] _alignPrices = new int[Base.kAlignTableSize];\n\tint _alignPriceCount;\n\n\tint _distTableSize = (kDefaultDictionaryLogSize * 2);\n\n\tint _posStateBits = 2;\n\tint _posStateMask = (4 - 1);\n\tint _numLiteralPosStateBits = 0;\n\tint _numLiteralContextBits = 3;\n\n\tint _dictionarySize = (1 << kDefaultDictionaryLogSize);\n\tint _dictionarySizePrev = -1;\n\tint _numFastBytesPrev = -1;\n\n\tlong nowPos64;\n\tboolean _finished;\n\tjava.io.InputStream _inStream;\n\n\tint _matchFinderType = EMatchFinderTypeBT4;\n\tboolean _writeEndMark = false;\n\n\tboolean _needReleaseMFStream = false;\n\n\tvoid Create()\n\t{\n\t\tif (_matchFinder == null)\n\t\t{\n\t\t\tcom.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.LZ.BinTree bt = new com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.LZ.BinTree();\n\t\t\tint numHashBytes = 4;\n\t\t\tif (_matchFinderType == EMatchFinderTypeBT2)\n\t\t\t\tnumHashBytes = 2;\n\t\t\tbt.setType(numHashBytes);\n\t\t\t_matchFinder = bt;\n\t\t}\n\t\t_literalEncoder.Create(_numLiteralPosStateBits, _numLiteralContextBits);\n\n\t\tif (_dictionarySize == _dictionarySizePrev && _numFastBytesPrev == _numFastBytes)\n\t\t\treturn;\n\t\t_matchFinder.Create(_dictionarySize, kNumOpts, _numFastBytes, Base.kMatchMaxLen + 1);\n\t\t_dictionarySizePrev = _dictionarySize;\n\t\t_numFastBytesPrev = _numFastBytes;\n\t}\n\n\tpublic Encoder()\n\t{\n\t\tfor (int i = 0; i < kNumOpts; i++)\n\t\t\t_optimum[i] = new Optimal();\n\t\tfor (int i = 0; i < Base.kNumLenToPosStates; i++)\n\t\t\t_posSlotEncoder[i] = new BitTreeEncoder(Base.kNumPosSlotBits);\n\t}\n\n\tvoid SetWriteEndMarkerMode(boolean writeEndMarker)\n\t{\n\t\t_writeEndMark = writeEndMarker;\n\t}\n\n\tvoid Init()\n\t{\n\t\tBaseInit();\n\t\t_rangeEncoder.Init();\n\n\t\tcom.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.InitBitModels(_isMatch);\n\t\tcom.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.InitBitModels(_isRep0Long);\n\t\tcom.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.InitBitModels(_isRep);\n\t\tcom.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.InitBitModels(_isRepG0);\n\t\tcom.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.InitBitModels(_isRepG1);\n\t\tcom.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.InitBitModels(_isRepG2);\n\t\tcom.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.InitBitModels(_posEncoders);\n\n\n\n\n\n\n\n\t\t_literalEncoder.Init();\n\t\tfor (int i = 0; i < Base.kNumLenToPosStates; i++)\n\t\t\t_posSlotEncoder[i].Init();\n\n\n\n\t\t_lenEncoder.Init(1 << _posStateBits);\n\t\t_repMatchLenEncoder.Init(1 << _posStateBits);\n\n\t\t_posAlignEncoder.Init();\n\n\t\t_longestMatchWasFound = false;\n\t\t_optimumEndIndex = 0;\n\t\t_optimumCurrentIndex = 0;\n\t\t_additionalOffset = 0;\n\t}\n\n\tint ReadMatchDistances() throws java.io.IOException\n\t{\n\t\tint lenRes = 0;\n\t\t_numDistancePairs = _matchFinder.GetMatches(_matchDistances);\n\t\tif (_numDistancePairs > 0)\n\t\t{\n\t\t\tlenRes = _matchDistances[_numDistancePairs - 2];\n\t\t\tif (lenRes == _numFastBytes)\n\t\t\t\tlenRes += _matchFinder.getMatchLen(lenRes - 1, _matchDistances[_numDistancePairs - 1],\n                        Base.kMatchMaxLen - lenRes);\n\t\t}\n\t\t_additionalOffset++;\n\t\treturn lenRes;\n\t}\n\n\tvoid MovePos(int num) throws java.io.IOException\n\t{\n\t\tif (num > 0)\n\t\t{\n\t\t\t_matchFinder.Skip(num);\n\t\t\t_additionalOffset += num;\n\t\t}\n\t}\n\n\tint GetRepLen1Price(int state, int posState)\n\t{\n\t\treturn com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice0(_isRepG0[state]) +\n\t\t\t\tcom.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice0(_isRep0Long[(state << Base.kNumPosStatesBitsMax) + posState]);\n\t}\n\n\tint GetPureRepPrice(int repIndex, int state, int posState)\n\t{\n\t\tint price;\n\t\tif (repIndex == 0)\n\t\t{\n\t\t\tprice = com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice0(_isRepG0[state]);\n\t\t\tprice += com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice1(_isRep0Long[(state << Base.kNumPosStatesBitsMax) + posState]);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tprice = com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice1(_isRepG0[state]);\n\t\t\tif (repIndex == 1)\n\t\t\t\tprice += com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice0(_isRepG1[state]);\n\t\t\telse\n\t\t\t{\n\t\t\t\tprice += com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice1(_isRepG1[state]);\n\t\t\t\tprice += com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice(_isRepG2[state], repIndex - 2);\n\t\t\t}\n\t\t}\n\t\treturn price;\n\t}\n\n\tint GetRepPrice(int repIndex, int len, int state, int posState)\n\t{\n\t\tint price = _repMatchLenEncoder.GetPrice(len - Base.kMatchMinLen, posState);\n\t\treturn price + GetPureRepPrice(repIndex, state, posState);\n\t}\n\n\tint GetPosLenPrice(int pos, int len, int posState)\n\t{\n\t\tint price;\n\t\tint lenToPosState = Base.GetLenToPosState(len);\n\t\tif (pos < Base.kNumFullDistances)\n\t\t\tprice = _distancesPrices[(lenToPosState * Base.kNumFullDistances) + pos];\n\t\telse\n\t\t\tprice = _posSlotPrices[(lenToPosState << Base.kNumPosSlotBits) + GetPosSlot2(pos)] +\n\t\t\t\t_alignPrices[pos & Base.kAlignMask];\n\t\treturn price + _lenEncoder.GetPrice(len - Base.kMatchMinLen, posState);\n\t}\n\n\tint Backward(int cur)\n\t{\n\t\t_optimumEndIndex = cur;\n\t\tint posMem = _optimum[cur].PosPrev;\n\t\tint backMem = _optimum[cur].BackPrev;\n\t\tdo\n\t\t{\n\t\t\tif (_optimum[cur].Prev1IsChar)\n\t\t\t{\n\t\t\t\t_optimum[posMem].MakeAsChar();\n\t\t\t\t_optimum[posMem].PosPrev = posMem - 1;\n\t\t\t\tif (_optimum[cur].Prev2)\n\t\t\t\t{\n\t\t\t\t\t_optimum[posMem - 1].Prev1IsChar = false;\n\t\t\t\t\t_optimum[posMem - 1].PosPrev = _optimum[cur].PosPrev2;\n\t\t\t\t\t_optimum[posMem - 1].BackPrev = _optimum[cur].BackPrev2;\n\t\t\t\t}\n\t\t\t}\n\t\t\tint posPrev = posMem;\n\t\t\tint backCur = backMem;\n\n\t\t\tbackMem = _optimum[posPrev].BackPrev;\n\t\t\tposMem = _optimum[posPrev].PosPrev;\n\n\t\t\t_optimum[posPrev].BackPrev = backCur;\n\t\t\t_optimum[posPrev].PosPrev = cur;\n\t\t\tcur = posPrev;\n\t\t}\n\t\twhile (cur > 0);\n\t\tbackRes = _optimum[0].BackPrev;\n\t\t_optimumCurrentIndex = _optimum[0].PosPrev;\n\t\treturn _optimumCurrentIndex;\n\t}\n\n\tint[] reps = new int[Base.kNumRepDistances];\n\tint[] repLens = new int[Base.kNumRepDistances];\n\tint backRes;\n\n\tint GetOptimum(int position) throws IOException\n\t{\n\t\tif (_optimumEndIndex != _optimumCurrentIndex)\n\t\t{\n\t\t\tint lenRes = _optimum[_optimumCurrentIndex].PosPrev - _optimumCurrentIndex;\n\t\t\tbackRes = _optimum[_optimumCurrentIndex].BackPrev;\n\t\t\t_optimumCurrentIndex = _optimum[_optimumCurrentIndex].PosPrev;\n\t\t\treturn lenRes;\n\t\t}\n\t\t_optimumCurrentIndex = _optimumEndIndex = 0;\n\n\t\tint lenMain, numDistancePairs;\n\t\tif (!_longestMatchWasFound)\n\t\t{\n\t\t\tlenMain = ReadMatchDistances();\n\t\t}\n\t\telse\n\t\t{\n\t\t\tlenMain = _longestMatchLength;\n\t\t\t_longestMatchWasFound = false;\n\t\t}\n\t\tnumDistancePairs = _numDistancePairs;\n\n\t\tint numAvailableBytes = _matchFinder.getNumAvailableBytes() + 1;\n\t\tif (numAvailableBytes < 2)\n\t\t{\n\t\t\tbackRes = -1;\n\t\t\treturn 1;\n\t\t}\n\t\tif (numAvailableBytes > Base.kMatchMaxLen)\n\t\t\tnumAvailableBytes = Base.kMatchMaxLen;\n\n\t\tint repMaxIndex = 0;\n\t\tint i;\n\t\tfor (i = 0; i < Base.kNumRepDistances; i++)\n\t\t{\n\t\t\treps[i] = _repDistances[i];\n\t\t\trepLens[i] = _matchFinder.getMatchLen(0 - 1, reps[i], Base.kMatchMaxLen);\n\t\t\tif (repLens[i] > repLens[repMaxIndex])\n\t\t\t\trepMaxIndex = i;\n\t\t}\n\t\tif (repLens[repMaxIndex] >= _numFastBytes)\n\t\t{\n\t\t\tbackRes = repMaxIndex;\n\t\t\tint lenRes = repLens[repMaxIndex];\n\t\t\tMovePos(lenRes - 1);\n\t\t\treturn lenRes;\n\t\t}\n\n\t\tif (lenMain >= _numFastBytes)\n\t\t{\n\t\t\tbackRes = _matchDistances[numDistancePairs - 1] + Base.kNumRepDistances;\n\t\t\tMovePos(lenMain - 1);\n\t\t\treturn lenMain;\n\t\t}\n\n\t\tbyte currentByte = _matchFinder.getIndexByte(0 - 1);\n\t\tbyte matchByte = _matchFinder.getIndexByte(0 - _repDistances[0] - 1 - 1);\n\n\t\tif (lenMain < 2 && currentByte != matchByte && repLens[repMaxIndex] < 2)\n\t\t{\n\t\t\tbackRes = -1;\n\t\t\treturn 1;\n\t\t}\n\n\t\t_optimum[0].State = _state;\n\n\t\tint posState = (position & _posStateMask);\n\n\t\t_optimum[1].Price = com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice0(_isMatch[(_state << Base.kNumPosStatesBitsMax) + posState]) +\n\t\t\t\t_literalEncoder.GetSubCoder(position, _previousByte).GetPrice(!Base.StateIsCharState(_state), matchByte, currentByte);\n\t\t_optimum[1].MakeAsChar();\n\n\t\tint matchPrice = com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice1(_isMatch[(_state << Base.kNumPosStatesBitsMax) + posState]);\n\t\tint repMatchPrice = matchPrice + com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice1(_isRep[_state]);\n\n\t\tif (matchByte == currentByte)\n\t\t{\n\t\t\tint shortRepPrice = repMatchPrice + GetRepLen1Price(_state, posState);\n\t\t\tif (shortRepPrice < _optimum[1].Price)\n\t\t\t{\n\t\t\t\t_optimum[1].Price = shortRepPrice;\n\t\t\t\t_optimum[1].MakeAsShortRep();\n\t\t\t}\n\t\t}\n\n\t\tint lenEnd = ((lenMain >= repLens[repMaxIndex]) ? lenMain : repLens[repMaxIndex]);\n\n\t\tif (lenEnd < 2)\n\t\t{\n\t\t\tbackRes = _optimum[1].BackPrev;\n\t\t\treturn 1;\n\t\t}\n\n\t\t_optimum[1].PosPrev = 0;\n\n\t\t_optimum[0].Backs0 = reps[0];\n\t\t_optimum[0].Backs1 = reps[1];\n\t\t_optimum[0].Backs2 = reps[2];\n\t\t_optimum[0].Backs3 = reps[3];\n\n\t\tint len = lenEnd;\n\t\tdo\n\t\t\t_optimum[len--].Price = kInfinityPrice;\n\t\twhile (len >= 2);\n\n\t\tfor (i = 0; i < Base.kNumRepDistances; i++)\n\t\t{\n\t\t\tint repLen = repLens[i];\n\t\t\tif (repLen < 2)\n\t\t\t\tcontinue;\n\t\t\tint price = repMatchPrice + GetPureRepPrice(i, _state, posState);\n\t\t\tdo\n\t\t\t{\n\t\t\t\tint curAndLenPrice = price + _repMatchLenEncoder.GetPrice(repLen - 2, posState);\n\t\t\t\tOptimal optimum = _optimum[repLen];\n\t\t\t\tif (curAndLenPrice < optimum.Price)\n\t\t\t\t{\n\t\t\t\t\toptimum.Price = curAndLenPrice;\n\t\t\t\t\toptimum.PosPrev = 0;\n\t\t\t\t\toptimum.BackPrev = i;\n\t\t\t\t\toptimum.Prev1IsChar = false;\n\t\t\t\t}\n\t\t\t}\n\t\t\twhile (--repLen >= 2);\n\t\t}\n\n\t\tint normalMatchPrice = matchPrice + com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice0(_isRep[_state]);\n\n\t\tlen = ((repLens[0] >= 2) ? repLens[0] + 1 : 2);\n\t\tif (len <= lenMain)\n\t\t{\n\t\t\tint offs = 0;\n\t\t\twhile (len > _matchDistances[offs])\n\t\t\t\toffs += 2;\n\t\t\tfor (; ; len++)\n\t\t\t{\n\t\t\t\tint distance = _matchDistances[offs + 1];\n\t\t\t\tint curAndLenPrice = normalMatchPrice + GetPosLenPrice(distance, len, posState);\n\t\t\t\tOptimal optimum = _optimum[len];\n\t\t\t\tif (curAndLenPrice < optimum.Price)\n\t\t\t\t{\n\t\t\t\t\toptimum.Price = curAndLenPrice;\n\t\t\t\t\toptimum.PosPrev = 0;\n\t\t\t\t\toptimum.BackPrev = distance + Base.kNumRepDistances;\n\t\t\t\t\toptimum.Prev1IsChar = false;\n\t\t\t\t}\n\t\t\t\tif (len == _matchDistances[offs])\n\t\t\t\t{\n\t\t\t\t\toffs += 2;\n\t\t\t\t\tif (offs == numDistancePairs)\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tint cur = 0;\n\n\t\twhile (true)\n\t\t{\n\t\t\tcur++;\n\t\t\tif (cur == lenEnd)\n\t\t\t\treturn Backward(cur);\n\t\t\tint newLen = ReadMatchDistances();\n\t\t\tnumDistancePairs = _numDistancePairs;\n\t\t\tif (newLen >= _numFastBytes)\n\t\t\t{\n\n\t\t\t\t_longestMatchLength = newLen;\n\t\t\t\t_longestMatchWasFound = true;\n\t\t\t\treturn Backward(cur);\n\t\t\t}\n\t\t\tposition++;\n\t\t\tint posPrev = _optimum[cur].PosPrev;\n\t\t\tint state;\n\t\t\tif (_optimum[cur].Prev1IsChar)\n\t\t\t{\n\t\t\t\tposPrev--;\n\t\t\t\tif (_optimum[cur].Prev2)\n\t\t\t\t{\n\t\t\t\t\tstate = _optimum[_optimum[cur].PosPrev2].State;\n\t\t\t\t\tif (_optimum[cur].BackPrev2 < Base.kNumRepDistances)\n\t\t\t\t\t\tstate = Base.StateUpdateRep(state);\n\t\t\t\t\telse\n\t\t\t\t\t\tstate = Base.StateUpdateMatch(state);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tstate = _optimum[posPrev].State;\n\t\t\t\tstate = Base.StateUpdateChar(state);\n\t\t\t}\n\t\t\telse\n\t\t\t\tstate = _optimum[posPrev].State;\n\t\t\tif (posPrev == cur - 1)\n\t\t\t{\n\t\t\t\tif (_optimum[cur].IsShortRep())\n\t\t\t\t\tstate = Base.StateUpdateShortRep(state);\n\t\t\t\telse\n\t\t\t\t\tstate = Base.StateUpdateChar(state);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tint pos;\n\t\t\t\tif (_optimum[cur].Prev1IsChar && _optimum[cur].Prev2)\n\t\t\t\t{\n\t\t\t\t\tposPrev = _optimum[cur].PosPrev2;\n\t\t\t\t\tpos = _optimum[cur].BackPrev2;\n\t\t\t\t\tstate = Base.StateUpdateRep(state);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tpos = _optimum[cur].BackPrev;\n\t\t\t\t\tif (pos < Base.kNumRepDistances)\n\t\t\t\t\t\tstate = Base.StateUpdateRep(state);\n\t\t\t\t\telse\n\t\t\t\t\t\tstate = Base.StateUpdateMatch(state);\n\t\t\t\t}\n\t\t\t\tOptimal opt = _optimum[posPrev];\n\t\t\t\tif (pos < Base.kNumRepDistances)\n\t\t\t\t{\n\t\t\t\t\tif (pos == 0)\n\t\t\t\t\t{\n\t\t\t\t\t\treps[0] = opt.Backs0;\n\t\t\t\t\t\treps[1] = opt.Backs1;\n\t\t\t\t\t\treps[2] = opt.Backs2;\n\t\t\t\t\t\treps[3] = opt.Backs3;\n\t\t\t\t\t}\n\t\t\t\t\telse if (pos == 1)\n\t\t\t\t\t{\n\t\t\t\t\t\treps[0] = opt.Backs1;\n\t\t\t\t\t\treps[1] = opt.Backs0;\n\t\t\t\t\t\treps[2] = opt.Backs2;\n\t\t\t\t\t\treps[3] = opt.Backs3;\n\t\t\t\t\t}\n\t\t\t\t\telse if (pos == 2)\n\t\t\t\t\t{\n\t\t\t\t\t\treps[0] = opt.Backs2;\n\t\t\t\t\t\treps[1] = opt.Backs0;\n\t\t\t\t\t\treps[2] = opt.Backs1;\n\t\t\t\t\t\treps[3] = opt.Backs3;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\treps[0] = opt.Backs3;\n\t\t\t\t\t\treps[1] = opt.Backs0;\n\t\t\t\t\t\treps[2] = opt.Backs1;\n\t\t\t\t\t\treps[3] = opt.Backs2;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\treps[0] = (pos - Base.kNumRepDistances);\n\t\t\t\t\treps[1] = opt.Backs0;\n\t\t\t\t\treps[2] = opt.Backs1;\n\t\t\t\t\treps[3] = opt.Backs2;\n\t\t\t\t}\n\t\t\t}\n\t\t\t_optimum[cur].State = state;\n\t\t\t_optimum[cur].Backs0 = reps[0];\n\t\t\t_optimum[cur].Backs1 = reps[1];\n\t\t\t_optimum[cur].Backs2 = reps[2];\n\t\t\t_optimum[cur].Backs3 = reps[3];\n\t\t\tint curPrice = _optimum[cur].Price;\n\n\t\t\tcurrentByte = _matchFinder.getIndexByte(0 - 1);\n\t\t\tmatchByte = _matchFinder.getIndexByte(0 - reps[0] - 1 - 1);\n\n\t\t\tposState = (position & _posStateMask);\n\n\t\t\tint curAnd1Price = curPrice +\n\t\t\t\tcom.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice0(_isMatch[(state << Base.kNumPosStatesBitsMax) + posState]) +\n\t\t\t\t_literalEncoder.GetSubCoder(position, _matchFinder.getIndexByte(0 - 2)).\n\t\t\t\tGetPrice(!Base.StateIsCharState(state), matchByte, currentByte);\n\n\t\t\tOptimal nextOptimum = _optimum[cur + 1];\n\n\t\t\tboolean nextIsChar = false;\n\t\t\tif (curAnd1Price < nextOptimum.Price)\n\t\t\t{\n\t\t\t\tnextOptimum.Price = curAnd1Price;\n\t\t\t\tnextOptimum.PosPrev = cur;\n\t\t\t\tnextOptimum.MakeAsChar();\n\t\t\t\tnextIsChar = true;\n\t\t\t}\n\n\t\t\tmatchPrice = curPrice + com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice1(_isMatch[(state << Base.kNumPosStatesBitsMax) + posState]);\n\t\t\trepMatchPrice = matchPrice + com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice1(_isRep[state]);\n\n\t\t\tif (matchByte == currentByte &&\n\t\t\t\t!(nextOptimum.PosPrev < cur && nextOptimum.BackPrev == 0))\n\t\t\t{\n\t\t\t\tint shortRepPrice = repMatchPrice + GetRepLen1Price(state, posState);\n\t\t\t\tif (shortRepPrice <= nextOptimum.Price)\n\t\t\t\t{\n\t\t\t\t\tnextOptimum.Price = shortRepPrice;\n\t\t\t\t\tnextOptimum.PosPrev = cur;\n\t\t\t\t\tnextOptimum.MakeAsShortRep();\n\t\t\t\t\tnextIsChar = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tint numAvailableBytesFull = _matchFinder.getNumAvailableBytes() + 1;\n\t\t\tnumAvailableBytesFull = Math.min(kNumOpts - 1 - cur, numAvailableBytesFull);\n\t\t\tnumAvailableBytes = numAvailableBytesFull;\n\n\t\t\tif (numAvailableBytes < 2)\n\t\t\t\tcontinue;\n\t\t\tif (numAvailableBytes > _numFastBytes)\n\t\t\t\tnumAvailableBytes = _numFastBytes;\n\t\t\tif (!nextIsChar && matchByte != currentByte)\n\t\t\t{\n\t\t\t\t// try Literal + rep0\n\t\t\t\tint t = Math.min(numAvailableBytesFull - 1, _numFastBytes);\n\t\t\t\tint lenTest2 = _matchFinder.getMatchLen(0, reps[0], t);\n\t\t\t\tif (lenTest2 >= 2)\n\t\t\t\t{\n\t\t\t\t\tint state2 = Base.StateUpdateChar(state);\n\n\t\t\t\t\tint posStateNext = (position + 1) & _posStateMask;\n\t\t\t\t\tint nextRepMatchPrice = curAnd1Price +\n\t\t\t\t\t\tcom.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice1(_isMatch[(state2 << Base.kNumPosStatesBitsMax) + posStateNext]) +\n\t\t\t\t\t\tcom.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice1(_isRep[state2]);\n\t\t\t\t\t{\n\t\t\t\t\t\tint offset = cur + 1 + lenTest2;\n\t\t\t\t\t\twhile (lenEnd < offset)\n\t\t\t\t\t\t\t_optimum[++lenEnd].Price = kInfinityPrice;\n\t\t\t\t\t\tint curAndLenPrice = nextRepMatchPrice + GetRepPrice(\n\t\t\t\t\t\t\t\t0, lenTest2, state2, posStateNext);\n\t\t\t\t\t\tOptimal optimum = _optimum[offset];\n\t\t\t\t\t\tif (curAndLenPrice < optimum.Price)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\toptimum.Price = curAndLenPrice;\n\t\t\t\t\t\t\toptimum.PosPrev = cur + 1;\n\t\t\t\t\t\t\toptimum.BackPrev = 0;\n\t\t\t\t\t\t\toptimum.Prev1IsChar = true;\n\t\t\t\t\t\t\toptimum.Prev2 = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tint startLen = 2; // speed optimization \n\n\t\t\tfor (int repIndex = 0; repIndex < Base.kNumRepDistances; repIndex++)\n\t\t\t{\n\t\t\t\tint lenTest = _matchFinder.getMatchLen(0 - 1, reps[repIndex], numAvailableBytes);\n\t\t\t\tif (lenTest < 2)\n\t\t\t\t\tcontinue;\n\t\t\t\tint lenTestTemp = lenTest;\n\t\t\t\tdo\n\t\t\t\t{\n\t\t\t\t\twhile (lenEnd < cur + lenTest)\n\t\t\t\t\t\t_optimum[++lenEnd].Price = kInfinityPrice;\n\t\t\t\t\tint curAndLenPrice = repMatchPrice + GetRepPrice(repIndex, lenTest, state, posState);\n\t\t\t\t\tOptimal optimum = _optimum[cur + lenTest];\n\t\t\t\t\tif (curAndLenPrice < optimum.Price)\n\t\t\t\t\t{\n\t\t\t\t\t\toptimum.Price = curAndLenPrice;\n\t\t\t\t\t\toptimum.PosPrev = cur;\n\t\t\t\t\t\toptimum.BackPrev = repIndex;\n\t\t\t\t\t\toptimum.Prev1IsChar = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\twhile (--lenTest >= 2);\n\t\t\t\tlenTest = lenTestTemp;\n\n\t\t\t\tif (repIndex == 0)\n\t\t\t\t\tstartLen = lenTest + 1;\n\n\t\t\t\t// if (_maxMode)\n\t\t\t\tif (lenTest < numAvailableBytesFull)\n\t\t\t\t{\n\t\t\t\t\tint t = Math.min(numAvailableBytesFull - 1 - lenTest, _numFastBytes);\n\t\t\t\t\tint lenTest2 = _matchFinder.getMatchLen(lenTest, reps[repIndex], t);\n\t\t\t\t\tif (lenTest2 >= 2)\n\t\t\t\t\t{\n\t\t\t\t\t\tint state2 = Base.StateUpdateRep(state);\n\n\t\t\t\t\t\tint posStateNext = (position + lenTest) & _posStateMask;\n\t\t\t\t\t\tint curAndLenCharPrice =\n\t\t\t\t\t\t\t\trepMatchPrice + GetRepPrice(repIndex, lenTest, state, posState) +\n\t\t\t\t\t\t\t\tcom.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice0(_isMatch[(state2 << Base.kNumPosStatesBitsMax) + posStateNext]) +\n\t\t\t\t\t\t\t\t_literalEncoder.GetSubCoder(position + lenTest,\n\t\t\t\t\t\t\t\t_matchFinder.getIndexByte(lenTest - 1 - 1)).GetPrice(true,\n\t\t\t\t\t\t\t\t_matchFinder.getIndexByte(lenTest - 1 - (reps[repIndex] + 1)),\n\t\t\t\t\t\t\t\t_matchFinder.getIndexByte(lenTest - 1));\n\t\t\t\t\t\tstate2 = Base.StateUpdateChar(state2);\n\t\t\t\t\t\tposStateNext = (position + lenTest + 1) & _posStateMask;\n\t\t\t\t\t\tint nextMatchPrice = curAndLenCharPrice + com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice1(_isMatch[(state2 << Base.kNumPosStatesBitsMax) + posStateNext]);\n\t\t\t\t\t\tint nextRepMatchPrice = nextMatchPrice + com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice1(_isRep[state2]);\n\n\t\t\t\t\t\t// for(; lenTest2 >= 2; lenTest2--)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tint offset = lenTest + 1 + lenTest2;\n\t\t\t\t\t\t\twhile (lenEnd < cur + offset)\n\t\t\t\t\t\t\t\t_optimum[++lenEnd].Price = kInfinityPrice;\n\t\t\t\t\t\t\tint curAndLenPrice = nextRepMatchPrice + GetRepPrice(0, lenTest2, state2, posStateNext);\n\t\t\t\t\t\t\tOptimal optimum = _optimum[cur + offset];\n\t\t\t\t\t\t\tif (curAndLenPrice < optimum.Price)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\toptimum.Price = curAndLenPrice;\n\t\t\t\t\t\t\t\toptimum.PosPrev = cur + lenTest + 1;\n\t\t\t\t\t\t\t\toptimum.BackPrev = 0;\n\t\t\t\t\t\t\t\toptimum.Prev1IsChar = true;\n\t\t\t\t\t\t\t\toptimum.Prev2 = true;\n\t\t\t\t\t\t\t\toptimum.PosPrev2 = cur;\n\t\t\t\t\t\t\t\toptimum.BackPrev2 = repIndex;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (newLen > numAvailableBytes)\n\t\t\t{\n\t\t\t\tnewLen = numAvailableBytes;\n\t\t\t\tfor (numDistancePairs = 0; newLen > _matchDistances[numDistancePairs]; numDistancePairs += 2) ;\n\t\t\t\t_matchDistances[numDistancePairs] = newLen;\n\t\t\t\tnumDistancePairs += 2;\n\t\t\t}\n\t\t\tif (newLen >= startLen)\n\t\t\t{\n\t\t\t\tnormalMatchPrice = matchPrice + com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice0(_isRep[state]);\n\t\t\t\twhile (lenEnd < cur + newLen)\n\t\t\t\t\t_optimum[++lenEnd].Price = kInfinityPrice;\n\n\t\t\t\tint offs = 0;\n\t\t\t\twhile (startLen > _matchDistances[offs])\n\t\t\t\t\toffs += 2;\n\n\t\t\t\tfor (int lenTest = startLen; ; lenTest++)\n\t\t\t\t{\n\t\t\t\t\tint curBack = _matchDistances[offs + 1];\n\t\t\t\t\tint curAndLenPrice = normalMatchPrice + GetPosLenPrice(curBack, lenTest, posState);\n\t\t\t\t\tOptimal optimum = _optimum[cur + lenTest];\n\t\t\t\t\tif (curAndLenPrice < optimum.Price)\n\t\t\t\t\t{\n\t\t\t\t\t\toptimum.Price = curAndLenPrice;\n\t\t\t\t\t\toptimum.PosPrev = cur;\n\t\t\t\t\t\toptimum.BackPrev = curBack + Base.kNumRepDistances;\n\t\t\t\t\t\toptimum.Prev1IsChar = false;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (lenTest == _matchDistances[offs])\n\t\t\t\t\t{\n\t\t\t\t\t\tif (lenTest < numAvailableBytesFull)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tint t = Math.min(numAvailableBytesFull - 1 - lenTest, _numFastBytes);\n\t\t\t\t\t\t\tint lenTest2 = _matchFinder.getMatchLen(lenTest, curBack, t);\n\t\t\t\t\t\t\tif (lenTest2 >= 2)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tint state2 = Base.StateUpdateMatch(state);\n\n\t\t\t\t\t\t\t\tint posStateNext = (position + lenTest) & _posStateMask;\n\t\t\t\t\t\t\t\tint curAndLenCharPrice = curAndLenPrice +\n\t\t\t\t\t\t\t\t\tcom.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice0(_isMatch[(state2 << Base.kNumPosStatesBitsMax) + posStateNext]) +\n\t\t\t\t\t\t\t\t\t_literalEncoder.GetSubCoder(position + lenTest,\n\t\t\t\t\t\t\t\t\t_matchFinder.getIndexByte(lenTest - 1 - 1)).\n\t\t\t\t\t\t\t\t\tGetPrice(true,\n\t\t\t\t\t\t\t\t\t_matchFinder.getIndexByte(lenTest - (curBack + 1) - 1),\n\t\t\t\t\t\t\t\t\t_matchFinder.getIndexByte(lenTest - 1));\n\t\t\t\t\t\t\t\tstate2 = Base.StateUpdateChar(state2);\n\t\t\t\t\t\t\t\tposStateNext = (position + lenTest + 1) & _posStateMask;\n\t\t\t\t\t\t\t\tint nextMatchPrice = curAndLenCharPrice + com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice1(_isMatch[(state2 << Base.kNumPosStatesBitsMax) + posStateNext]);\n\t\t\t\t\t\t\t\tint nextRepMatchPrice = nextMatchPrice + com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.GetPrice1(_isRep[state2]);\n\n\t\t\t\t\t\t\t\tint offset = lenTest + 1 + lenTest2;\n\t\t\t\t\t\t\t\twhile (lenEnd < cur + offset)\n\t\t\t\t\t\t\t\t\t_optimum[++lenEnd].Price = kInfinityPrice;\n\t\t\t\t\t\t\t\tcurAndLenPrice = nextRepMatchPrice + GetRepPrice(0, lenTest2, state2, posStateNext);\n\t\t\t\t\t\t\t\toptimum = _optimum[cur + offset];\n\t\t\t\t\t\t\t\tif (curAndLenPrice < optimum.Price)\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\toptimum.Price = curAndLenPrice;\n\t\t\t\t\t\t\t\t\toptimum.PosPrev = cur + lenTest + 1;\n\t\t\t\t\t\t\t\t\toptimum.BackPrev = 0;\n\t\t\t\t\t\t\t\t\toptimum.Prev1IsChar = true;\n\t\t\t\t\t\t\t\t\toptimum.Prev2 = true;\n\t\t\t\t\t\t\t\t\toptimum.PosPrev2 = cur;\n\t\t\t\t\t\t\t\t\toptimum.BackPrev2 = curBack + Base.kNumRepDistances;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\toffs += 2;\n\t\t\t\t\t\tif (offs == numDistancePairs)\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tboolean ChangePair(int smallDist, int bigDist)\n\t{\n\t\tint kDif = 7;\n\t\treturn (smallDist < (1 << (32 - kDif)) && bigDist >= (smallDist << kDif));\n\t}\n\n\tvoid WriteEndMarker(int posState) throws IOException\n\t{\n\t\tif (!_writeEndMark)\n\t\t\treturn;\n\n\t\t_rangeEncoder.Encode(_isMatch, (_state << Base.kNumPosStatesBitsMax) + posState, 1);\n\t\t_rangeEncoder.Encode(_isRep, _state, 0);\n\t\t_state = Base.StateUpdateMatch(_state);\n\t\tint len = Base.kMatchMinLen;\n\t\t_lenEncoder.Encode(_rangeEncoder, len - Base.kMatchMinLen, posState);\n\t\tint posSlot = (1 << Base.kNumPosSlotBits) - 1;\n\t\tint lenToPosState = Base.GetLenToPosState(len);\n\t\t_posSlotEncoder[lenToPosState].Encode(_rangeEncoder, posSlot);\n\t\tint footerBits = 30;\n\t\tint posReduced = (1 << footerBits) - 1;\n\t\t_rangeEncoder.EncodeDirectBits(posReduced >> Base.kNumAlignBits, footerBits - Base.kNumAlignBits);\n\t\t_posAlignEncoder.ReverseEncode(_rangeEncoder, posReduced & Base.kAlignMask);\n\t}\n\n\tvoid Flush(int nowPos) throws IOException\n\t{\n\t\tReleaseMFStream();\n\t\tWriteEndMarker(nowPos & _posStateMask);\n\t\t_rangeEncoder.FlushData();\n\t\t_rangeEncoder.FlushStream();\n\t}\n\n\tpublic void CodeOneBlock(long[] inSize, long[] outSize, boolean[] finished) throws IOException\n\t{\n\t\tinSize[0] = 0;\n\t\toutSize[0] = 0;\n\t\tfinished[0] = true;\n\n\t\tif (_inStream != null)\n\t\t{\n\t\t\t_matchFinder.SetStream(_inStream);\n\t\t\t_matchFinder.Init();\n\t\t\t_needReleaseMFStream = true;\n\t\t\t_inStream = null;\n\t\t}\n\n\t\tif (_finished)\n\t\t\treturn;\n\t\t_finished = true;\n\n\n\t\tlong progressPosValuePrev = nowPos64;\n\t\tif (nowPos64 == 0)\n\t\t{\n\t\t\tif (_matchFinder.getNumAvailableBytes() == 0)\n\t\t\t{\n\t\t\t\tFlush((int)nowPos64);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tReadMatchDistances();\n\t\t\tint posState = (int)(nowPos64) & _posStateMask;\n\t\t\t_rangeEncoder.Encode(_isMatch, (_state << Base.kNumPosStatesBitsMax) + posState, 0);\n\t\t\t_state = Base.StateUpdateChar(_state);\n\t\t\tbyte curByte = _matchFinder.getIndexByte(0 - _additionalOffset);\n\t\t\t_literalEncoder.GetSubCoder((int)(nowPos64), _previousByte).Encode(_rangeEncoder, curByte);\n\t\t\t_previousByte = curByte;\n\t\t\t_additionalOffset--;\n\t\t\tnowPos64++;\n\t\t}\n\t\tif (_matchFinder.getNumAvailableBytes() == 0)\n\t\t{\n\t\t\tFlush((int)nowPos64);\n\t\t\treturn;\n\t\t}\n\t\twhile (true)\n\t\t{\n\n\t\t\tint len = GetOptimum((int)nowPos64);\n\t\t\tint pos = backRes;\n\t\t\tint posState = ((int)nowPos64) & _posStateMask;\n\t\t\tint complexState = (_state << Base.kNumPosStatesBitsMax) + posState;\n\t\t\tif (len == 1 && pos == -1)\n\t\t\t{\n\t\t\t\t_rangeEncoder.Encode(_isMatch, complexState, 0);\n\t\t\t\tbyte curByte = _matchFinder.getIndexByte(-_additionalOffset);\n\t\t\t\tLiteralEncoder.Encoder2 subCoder = _literalEncoder.GetSubCoder((int)nowPos64, _previousByte);\n\t\t\t\tif (!Base.StateIsCharState(_state))\n\t\t\t\t{\n\t\t\t\t\tbyte matchByte = _matchFinder.getIndexByte((-_repDistances[0] - 1 - _additionalOffset));\n\t\t\t\t\tsubCoder.EncodeMatched(_rangeEncoder, matchByte, curByte);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tsubCoder.Encode(_rangeEncoder, curByte);\n\t\t\t\t_previousByte = curByte;\n\t\t\t\t_state = Base.StateUpdateChar(_state);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t_rangeEncoder.Encode(_isMatch, complexState, 1);\n\t\t\t\tif (pos < Base.kNumRepDistances)\n\t\t\t\t{\n\t\t\t\t\t_rangeEncoder.Encode(_isRep, _state, 1);\n\t\t\t\t\tif (pos == 0)\n\t\t\t\t\t{\n\t\t\t\t\t\t_rangeEncoder.Encode(_isRepG0, _state, 0);\n\t\t\t\t\t\tif (len == 1)\n\t\t\t\t\t\t\t_rangeEncoder.Encode(_isRep0Long, complexState, 0);\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\t_rangeEncoder.Encode(_isRep0Long, complexState, 1);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t_rangeEncoder.Encode(_isRepG0, _state, 1);\n\t\t\t\t\t\tif (pos == 1)\n\t\t\t\t\t\t\t_rangeEncoder.Encode(_isRepG1, _state, 0);\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t_rangeEncoder.Encode(_isRepG1, _state, 1);\n\t\t\t\t\t\t\t_rangeEncoder.Encode(_isRepG2, _state, pos - 2);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (len == 1)\n\t\t\t\t\t\t_state = Base.StateUpdateShortRep(_state);\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t_repMatchLenEncoder.Encode(_rangeEncoder, len - Base.kMatchMinLen, posState);\n\t\t\t\t\t\t_state = Base.StateUpdateRep(_state);\n\t\t\t\t\t}\n\t\t\t\t\tint distance = _repDistances[pos];\n\t\t\t\t\tif (pos != 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tfor (int i = pos; i >= 1; i--)\n\t\t\t\t\t\t\t_repDistances[i] = _repDistances[i - 1];\n\t\t\t\t\t\t_repDistances[0] = distance;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t_rangeEncoder.Encode(_isRep, _state, 0);\n\t\t\t\t\t_state = Base.StateUpdateMatch(_state);\n\t\t\t\t\t_lenEncoder.Encode(_rangeEncoder, len - Base.kMatchMinLen, posState);\n\t\t\t\t\tpos -= Base.kNumRepDistances;\n\t\t\t\t\tint posSlot = GetPosSlot(pos);\n\t\t\t\t\tint lenToPosState = Base.GetLenToPosState(len);\n\t\t\t\t\t_posSlotEncoder[lenToPosState].Encode(_rangeEncoder, posSlot);\n\n\t\t\t\t\tif (posSlot >= Base.kStartPosModelIndex)\n\t\t\t\t\t{\n\t\t\t\t\t\tint footerBits = (posSlot >> 1) - 1;\n\t\t\t\t\t\tint baseVal = ((2 | (posSlot & 1)) << footerBits);\n\t\t\t\t\t\tint posReduced = pos - baseVal;\n\n\t\t\t\t\t\tif (posSlot < Base.kEndPosModelIndex)\n\t\t\t\t\t\t\tBitTreeEncoder.ReverseEncode(_posEncoders,\n\t\t\t\t\t\t\t\t\tbaseVal - posSlot - 1, _rangeEncoder, footerBits, posReduced);\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t_rangeEncoder.EncodeDirectBits(posReduced >> Base.kNumAlignBits, footerBits - Base.kNumAlignBits);\n\t\t\t\t\t\t\t_posAlignEncoder.ReverseEncode(_rangeEncoder, posReduced & Base.kAlignMask);\n\t\t\t\t\t\t\t_alignPriceCount++;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tint distance = pos;\n\t\t\t\t\tfor (int i = Base.kNumRepDistances - 1; i >= 1; i--)\n\t\t\t\t\t\t_repDistances[i] = _repDistances[i - 1];\n\t\t\t\t\t_repDistances[0] = distance;\n\t\t\t\t\t_matchPriceCount++;\n\t\t\t\t}\n\t\t\t\t_previousByte = _matchFinder.getIndexByte(len - 1 - _additionalOffset);\n\t\t\t}\n\t\t\t_additionalOffset -= len;\n\t\t\tnowPos64 += len;\n\t\t\tif (_additionalOffset == 0)\n\t\t\t{\n\t\t\t\t// if (!_fastMode)\n\t\t\t\tif (_matchPriceCount >= (1 << 7))\n\t\t\t\t\tFillDistancesPrices();\n\t\t\t\tif (_alignPriceCount >= Base.kAlignTableSize)\n\t\t\t\t\tFillAlignPrices();\n\t\t\t\tinSize[0] = nowPos64;\n\t\t\t\toutSize[0] = _rangeEncoder.GetProcessedSizeAdd();\n\t\t\t\tif (_matchFinder.getNumAvailableBytes() == 0)\n\t\t\t\t{\n\t\t\t\t\tFlush((int)nowPos64);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (nowPos64 - progressPosValuePrev >= (1 << 12))\n\t\t\t\t{\n\t\t\t\t\t_finished = false;\n\t\t\t\t\tfinished[0] = false;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid ReleaseMFStream()\n\t{\n\t\tif (_matchFinder != null && _needReleaseMFStream)\n\t\t{\n\t\t\t_matchFinder.ReleaseStream();\n\t\t\t_needReleaseMFStream = false;\n\t\t}\n\t}\n\n\tvoid SetOutStream(java.io.OutputStream outStream)\n\t{ _rangeEncoder.SetStream(outStream); }\n\tvoid ReleaseOutStream()\n\t{ _rangeEncoder.ReleaseStream(); }\n\n\tvoid ReleaseStreams()\n\t{\n\t\tReleaseMFStream();\n\t\tReleaseOutStream();\n\t}\n\n\tvoid SetStreams(java.io.InputStream inStream, java.io.OutputStream outStream,\n\t\t\tlong inSize, long outSize)\n\t{\n\t\t_inStream = inStream;\n\t\t_finished = false;\n\t\tCreate();\n\t\tSetOutStream(outStream);\n\t\tInit();\n\n\t\t// if (!_fastMode)\n\t\t{\n\t\t\tFillDistancesPrices();\n\t\t\tFillAlignPrices();\n\t\t}\n\n\t\t_lenEncoder.SetTableSize(_numFastBytes + 1 - Base.kMatchMinLen);\n\t\t_lenEncoder.UpdateTables(1 << _posStateBits);\n\t\t_repMatchLenEncoder.SetTableSize(_numFastBytes + 1 - Base.kMatchMinLen);\n\t\t_repMatchLenEncoder.UpdateTables(1 << _posStateBits);\n\n\t\tnowPos64 = 0;\n\t}\n\n\tlong[] processedInSize = new long[1]; long[] processedOutSize = new long[1]; boolean[] finished = new boolean[1];\n\tpublic void Code(java.io.InputStream inStream, java.io.OutputStream outStream,\n\t\t\tlong inSize, long outSize, ICodeProgress progress) throws IOException\n\t{\n\t\t_needReleaseMFStream = false;\n\t\ttry\n\t\t{\n\t\t\tSetStreams(inStream, outStream, inSize, outSize);\n\t\t\twhile (true)\n\t\t\t{\n\n\n\n\t\t\t\tCodeOneBlock(processedInSize, processedOutSize, finished);\n\t\t\t\tif (finished[0])\n\t\t\t\t\treturn;\n\t\t\t\tif (progress != null)\n\t\t\t\t{\n\t\t\t\t\tprogress.SetProgress(processedInSize[0], processedOutSize[0]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfinally\n\t\t{\n\t\t\tReleaseStreams();\n\t\t}\n\t}\n\n\tpublic static final int kPropSize = 5;\n\tbyte[] properties = new byte[kPropSize];\n\n\tpublic void WriteCoderProperties(java.io.OutputStream outStream) throws IOException\n\t{\n\t\tproperties[0] = (byte)((_posStateBits * 5 + _numLiteralPosStateBits) * 9 + _numLiteralContextBits);\n\t\tfor (int i = 0; i < 4; i++)\n\t\t\tproperties[1 + i] = (byte)(_dictionarySize >> (8 * i));\n\t\toutStream.write(properties, 0, kPropSize);\n\t}\n\n\tint[] tempPrices = new int[Base.kNumFullDistances];\n\tint _matchPriceCount;\n\n\tvoid FillDistancesPrices()\n\t{\n\t\tfor (int i = Base.kStartPosModelIndex; i < Base.kNumFullDistances; i++)\n\t\t{\n\t\t\tint posSlot = GetPosSlot(i);\n\t\t\tint footerBits = (posSlot >> 1) - 1;\n\t\t\tint baseVal = ((2 | (posSlot & 1)) << footerBits);\n\t\t\ttempPrices[i] = BitTreeEncoder.ReverseGetPrice(_posEncoders,\n\t\t\t\tbaseVal - posSlot - 1, footerBits, i - baseVal);\n\t\t}\n\n\t\tfor (int lenToPosState = 0; lenToPosState < Base.kNumLenToPosStates; lenToPosState++)\n\t\t{\n\t\t\tint posSlot;\n\t\t\tBitTreeEncoder encoder = _posSlotEncoder[lenToPosState];\n\n\t\t\tint st = (lenToPosState << Base.kNumPosSlotBits);\n\t\t\tfor (posSlot = 0; posSlot < _distTableSize; posSlot++)\n\t\t\t\t_posSlotPrices[st + posSlot] = encoder.GetPrice(posSlot);\n\t\t\tfor (posSlot = Base.kEndPosModelIndex; posSlot < _distTableSize; posSlot++)\n\t\t\t\t_posSlotPrices[st + posSlot] += ((((posSlot >> 1) - 1) - Base.kNumAlignBits) << com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder.Encoder.kNumBitPriceShiftBits);\n\n\t\t\tint st2 = lenToPosState * Base.kNumFullDistances;\n\t\t\tint i;\n\t\t\tfor (i = 0; i < Base.kStartPosModelIndex; i++)\n\t\t\t\t_distancesPrices[st2 + i] = _posSlotPrices[st + i];\n\t\t\tfor (; i < Base.kNumFullDistances; i++)\n\t\t\t\t_distancesPrices[st2 + i] = _posSlotPrices[st + GetPosSlot(i)] + tempPrices[i];\n\t\t}\n\t\t_matchPriceCount = 0;\n\t}\n\n\tvoid FillAlignPrices()\n\t{\n\t\tfor (int i = 0; i < Base.kAlignTableSize; i++)\n\t\t\t_alignPrices[i] = _posAlignEncoder.ReverseGetPrice(i);\n\t\t_alignPriceCount = 0;\n\t}\n\n\n\tpublic boolean SetAlgorithm(int algorithm)\n\t{\n\t\t/*\n\t\t_fastMode = (algorithm == 0);\n\t\t_maxMode = (algorithm >= 2);\n\t\t*/\n\t\treturn true;\n\t}\n\n\tpublic boolean SetDictionarySize(int dictionarySize)\n\t{\n\t\tint kDicLogSizeMaxCompress = 29;\n\t\tif (dictionarySize < (1 << Base.kDicLogSizeMin) || dictionarySize > (1 << kDicLogSizeMaxCompress))\n\t\t\treturn false;\n\t\t_dictionarySize = dictionarySize;\n\t\tint dicLogSize;\n\t\tfor (dicLogSize = 0; dictionarySize > (1 << dicLogSize); dicLogSize++) ;\n\t\t_distTableSize = dicLogSize * 2;\n\t\treturn true;\n\t}\n\n\tpublic boolean SeNumFastBytes(int numFastBytes)\n\t{\n\t\tif (numFastBytes < 5 || numFastBytes > Base.kMatchMaxLen)\n\t\t\treturn false;\n\t\t_numFastBytes = numFastBytes;\n\t\treturn true;\n\t}\n\n\tpublic boolean SetMatchFinder(int matchFinderIndex)\n\t{\n\t\tif (matchFinderIndex < 0 || matchFinderIndex > 2)\n\t\t\treturn false;\n\t\tint matchFinderIndexPrev = _matchFinderType;\n\t\t_matchFinderType = matchFinderIndex;\n\t\tif (_matchFinder != null && matchFinderIndexPrev != _matchFinderType)\n\t\t{\n\t\t\t_dictionarySizePrev = -1;\n\t\t\t_matchFinder = null;\n\t\t}\n\t\treturn true;\n\t}\n\n\tpublic boolean SetLcLpPb(int lc, int lp, int pb)\n\t{\n\t\tif (\n\t\t\t\tlp < 0 || lp > Base.kNumLitPosStatesBitsEncodingMax ||\n\t\t\t\tlc < 0 || lc > Base.kNumLitContextBitsMax ||\n\t\t\t\tpb < 0 || pb > Base.kNumPosStatesBitsEncodingMax)\n\t\t\treturn false;\n\t\t_numLiteralPosStateBits = lp;\n\t\t_numLiteralContextBits = lc;\n\t\t_posStateBits = pb;\n\t\t_posStateMask = ((1) << _posStateBits) - 1;\n\t\treturn true;\n\t}\n\n\tpublic void SetEndMarkerMode(boolean endMarkerMode)\n\t{\n\t\t_writeEndMark = endMarkerMode;\n\t}\n}\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Compression/RangeCoder/BitDecoder.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder;\n\n\npublic class BitDecoder extends BitModel\n{\n    public BitDecoder(int num) {\n        super(num);\n    }\n  public int Decode(Decoder decoder)  throws java.io.IOException\n  {\n    int newBound = (decoder.Range >>> kNumBitModelTotalBits) * this.Prob;\n    if ((decoder.Code ^ 0x80000000) < (newBound ^ 0x80000000))\n    {\n      decoder.Range = newBound;\n      this.Prob += (kBitModelTotal - this.Prob) >>> numMoveBits;\n      if ((decoder.Range & kTopMask) == 0)\n      {\n        decoder.Code = (decoder.Code << 8) | decoder.bufferedStream.read();\n        decoder.Range <<= 8;\n      }\n      return 0;\n    }\n    else\n    {\n      decoder.Range -= newBound;\n      decoder.Code -= newBound;\n      this.Prob -= (this.Prob) >>> numMoveBits;\n      if ((decoder.Range & kTopMask) == 0)\n      {\n        decoder.Code = (decoder.Code << 8) | decoder.bufferedStream.read();\n        decoder.Range <<= 8;\n      }\n      return 1;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Compression/RangeCoder/BitModel.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder;\n\n\npublic class BitModel\n{\n\n    public static final int kTopMask = ~((1 << 24) - 1);\n\tpublic static final int kNumBitModelTotalBits  = 11;\n\tpublic static final int kBitModelTotal = (1 << kNumBitModelTotalBits);\n\n\tint numMoveBits;\n\n\tint Prob;\n\n\tpublic BitModel(int num)  {\n\t\tnumMoveBits = num;\n\t}\n\t/*\n  public void UpdateModel(UInt32 symbol)\n  {\n    if (symbol == 0)\n      Prob += (kBitModelTotal - Prob) >> numMoveBits;\n    else\n      Prob -= (Prob) >> numMoveBits;\n  }\n  \t*/\n  public void Init() { Prob = kBitModelTotal / 2; }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Compression/RangeCoder/BitTreeDecoder.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder;\n\npublic class BitTreeDecoder\n{\n\tshort[] Models;\n\tint NumBitLevels;\n\t\n\tpublic BitTreeDecoder(int numBitLevels)\n\t{\n\t\tNumBitLevels = numBitLevels;\n\t\tModels = new short[1 << numBitLevels];\n\t}\n\t\n\tpublic void Init()\n\t{\n\t\tDecoder.InitBitModels(Models);\n\t}\n\t\n\tpublic int Decode(Decoder rangeDecoder) throws java.io.IOException\n\t{\n\t\tint m = 1;\n\t\tfor (int bitIndex = NumBitLevels; bitIndex != 0; bitIndex--)\n\t\t\tm = (m << 1) + rangeDecoder.DecodeBit(Models, m);\n\t\treturn m - (1 << NumBitLevels);\n\t}\n\t\n\tpublic int ReverseDecode(Decoder rangeDecoder) throws java.io.IOException\n\t{\n\t\tint m = 1;\n\t\tint symbol = 0;\n\t\tfor (int bitIndex = 0; bitIndex < NumBitLevels; bitIndex++)\n\t\t{\n\t\t\tint bit = rangeDecoder.DecodeBit(Models, m);\n\t\t\tm <<= 1;\n\t\t\tm += bit;\n\t\t\tsymbol |= (bit << bitIndex);\n\t\t}\n\t\treturn symbol;\n\t}\n\t\n\tpublic static int ReverseDecode(short[] Models, int startIndex,\n\t\t\tDecoder rangeDecoder, int NumBitLevels) throws java.io.IOException\n\t{\n\t\tint m = 1;\n\t\tint symbol = 0;\n\t\tfor (int bitIndex = 0; bitIndex < NumBitLevels; bitIndex++)\n\t\t{\n\t\t\tint bit = rangeDecoder.DecodeBit(Models, startIndex + m);\n\t\t\tm <<= 1;\n\t\t\tm += bit;\n\t\t\tsymbol |= (bit << bitIndex);\n\t\t}\n\t\treturn symbol;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Compression/RangeCoder/BitTreeEncoder.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder;\nimport java.io.IOException;\n\npublic class BitTreeEncoder\n{\n\tshort[] Models;\n\tint NumBitLevels;\n\t\n\tpublic BitTreeEncoder(int numBitLevels)\n\t{\n\t\tNumBitLevels = numBitLevels;\n\t\tModels = new short[1 << numBitLevels];\n\t}\n\t\n\tpublic void Init()\n\t{\n\t\tDecoder.InitBitModels(Models);\n\t}\n\t\n\tpublic void Encode(Encoder rangeEncoder, int symbol) throws IOException\n\t{\n\t\tint m = 1;\n\t\tfor (int bitIndex = NumBitLevels; bitIndex != 0; )\n\t\t{\n\t\t\tbitIndex--;\n\t\t\tint bit = (symbol >>> bitIndex) & 1;\n\t\t\trangeEncoder.Encode(Models, m, bit);\n\t\t\tm = (m << 1) | bit;\n\t\t}\n\t}\n\t\n\tpublic void ReverseEncode(Encoder rangeEncoder, int symbol) throws IOException\n\t{\n\t\tint m = 1;\n\t\tfor (int  i = 0; i < NumBitLevels; i++)\n\t\t{\n\t\t\tint bit = symbol & 1;\n\t\t\trangeEncoder.Encode(Models, m, bit);\n\t\t\tm = (m << 1) | bit;\n\t\t\tsymbol >>= 1;\n\t\t}\n\t}\n\t\n\tpublic int GetPrice(int symbol)\n\t{\n\t\tint price = 0;\n\t\tint m = 1;\n\t\tfor (int bitIndex = NumBitLevels; bitIndex != 0; )\n\t\t{\n\t\t\tbitIndex--;\n\t\t\tint bit = (symbol >>> bitIndex) & 1;\n\t\t\tprice += Encoder.GetPrice(Models[m], bit);\n\t\t\tm = (m << 1) + bit;\n\t\t}\n\t\treturn price;\n\t}\n\t\n\tpublic int ReverseGetPrice(int symbol)\n\t{\n\t\tint price = 0;\n\t\tint m = 1;\n\t\tfor (int i = NumBitLevels; i != 0; i--)\n\t\t{\n\t\t\tint bit = symbol & 1;\n\t\t\tsymbol >>>= 1;\n\t\t\tprice += Encoder.GetPrice(Models[m], bit);\n\t\t\tm = (m << 1) | bit;\n\t\t}\n\t\treturn price;\n\t}\n\t\n\tpublic static int ReverseGetPrice(short[] Models, int startIndex,\n\t\t\tint NumBitLevels, int symbol)\n\t{\n\t\tint price = 0;\n\t\tint m = 1;\n\t\tfor (int i = NumBitLevels; i != 0; i--)\n\t\t{\n\t\t\tint bit = symbol & 1;\n\t\t\tsymbol >>>= 1;\n\t\t\tprice += Encoder.GetPrice(Models[startIndex + m], bit);\n\t\t\tm = (m << 1) | bit;\n\t\t}\n\t\treturn price;\n\t}\n\t\n\tpublic static void ReverseEncode(short[] Models, int startIndex,\n\t\t\tEncoder rangeEncoder, int NumBitLevels, int symbol) throws IOException\n\t{\n\t\tint m = 1;\n\t\tfor (int i = 0; i < NumBitLevels; i++)\n\t\t{\n\t\t\tint bit = symbol & 1;\n\t\t\trangeEncoder.Encode(Models, startIndex + m, bit);\n\t\t\tm = (m << 1) | bit;\n\t\t\tsymbol >>= 1;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Compression/RangeCoder/Decoder.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder;\nimport java.io.IOException;\n\npublic class Decoder\n{\n\tstatic final int kTopMask = ~((1 << 24) - 1);\n\t\n\tstatic final int kNumBitModelTotalBits = 11;\n\tstatic final int kBitModelTotal = (1 << kNumBitModelTotalBits);\n\tstatic final int kNumMoveBits = 5;\n\t\n\tint Range;\n\tint Code;\n        \n        // boolean _wasFinished;\n        // long _processedSize;\n        \n        // public boolean WasFinished() { return _wasFinished; }\n\n\t// public java.io.InputStream Stream;\n        \n        public com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Common.InBuffer bufferedStream = new com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Common.InBuffer();\n        \n        int read() throws IOException {\n            return bufferedStream.read();\n        }\n        \n        public void Create(int bufferSize) { bufferedStream.Create(bufferSize); }\n        \n        public long GetProcessedSize() {\n            return bufferedStream.GetProcessedSize();\n        }\n\t\n\tpublic final void SetStream(java.io.InputStream stream)\n\t{ \n\t\tbufferedStream.SetStream(stream);  \n\t}\n\t\n\tpublic final void ReleaseStream() throws IOException\n\t{ \n\t\tbufferedStream.ReleaseStream();\n\t}\n\t\n\tpublic final void Init() throws IOException\n\t{\n                bufferedStream.Init();\n\t\tCode = 0;\n\t\tRange = -1;\n\t\tfor (int i = 0; i < 5; i++) {\n                    Code = (Code << 8) | this.read();\n                }\n\t\t\t\n\t}\n\t\n\tpublic final int DecodeDirectBits(int numTotalBits) throws IOException\n\t{\n\t\tint result = 0;\n\t\tfor (int i = numTotalBits; i != 0; i--)\n\t\t{\n\t\t\tRange >>>= 1;\n\t\t\tint t = ((Code - Range) >>> 31);\n\t\t\tCode -= Range & (t - 1);\n\t\t\tresult = (result << 1) | (1 - t);\n\t\t\t\n\t\t\tif ((Range & kTopMask) == 0)\n\t\t\t{\n\t\t\t\tCode = (Code << 8) | this.read();\n\t\t\t\tRange <<= 8;\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\t\n\tpublic int DecodeBit(short []probs, int index) throws IOException\n\t{\n\t\tint prob = probs[index];\n\t\tint newBound = (Range >>> kNumBitModelTotalBits) * prob;\n\t\tif ((Code ^ 0x80000000) < (newBound ^ 0x80000000))\n\t\t{\n\t\t\tRange = newBound;\n\t\t\tprobs[index] = (short)(prob + ((kBitModelTotal - prob) >>> kNumMoveBits));\n\t\t\tif ((Range & kTopMask) == 0)\n\t\t\t{\n\t\t\t\tCode = (Code << 8) | this.read();\n\t\t\t\tRange <<= 8;\n\t\t\t}\n\t\t\treturn 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tRange -= newBound;\n\t\t\tCode -= newBound;\n\t\t\tprobs[index] = (short)(prob - ((prob) >>> kNumMoveBits));\n\t\t\tif ((Range & kTopMask) == 0)\n\t\t\t{\n\t\t\t\tCode = (Code << 8) | this.read();\n\t\t\t\tRange <<= 8;\n\t\t\t}\n\t\t\treturn 1;\n\t\t}\n\t}\n\t\n\tpublic static void InitBitModels(short []probs)\n\t{\n\t\tfor (int i = 0; i < probs.length; i++)\n\t\t\tprobs[i] = (kBitModelTotal >>> 1);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/Compression/RangeCoder/Encoder.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.RangeCoder;\nimport java.io.IOException;\n\npublic class Encoder\n{\n\tstatic final int kTopMask = ~((1 << 24) - 1);\n\t\n\tstatic final int kNumBitModelTotalBits = 11;\n\tstatic final int kBitModelTotal = (1 << kNumBitModelTotalBits);\n\tstatic final int kNumMoveBits = 5;\n\t\n\tjava.io.OutputStream Stream;\n\n\tlong Low;\n\tint Range;\n\tint _cacheSize;\n\tint _cache;\n\t\n\tlong _position;\n\t\n\tpublic void SetStream(java.io.OutputStream stream)\n\t{\n\t\tStream = stream;\n\t}\n\t\n\tpublic void ReleaseStream()\n\t{\n\t\tStream = null;\n\t}\n\t\n\tpublic void Init()\n\t{\n\t\t_position = 0;\n\t\tLow = 0;\n\t\tRange = -1;\n\t\t_cacheSize = 1;\n\t\t_cache = 0;\n\t}\n\t\n\tpublic void FlushData() throws IOException\n\t{\n\t\tfor (int i = 0; i < 5; i++)\n\t\t\tShiftLow();\n\t}\n\t\n\tpublic void FlushStream() throws IOException\n\t{\n\t\tStream.flush();\n\t}\n\t\n\tpublic void ShiftLow() throws IOException\n\t{\n\t\tint LowHi = (int)(Low >>> 32);\n\t\tif (LowHi != 0 || Low < 0xFF000000L)\n\t\t{\n\t\t\t_position += _cacheSize;\n\t\t\tint temp = _cache;\n\t\t\tdo\n\t\t\t{\n\t\t\t\tStream.write(temp + LowHi);\n\t\t\t\ttemp = 0xFF;\n\t\t\t}\n\t\t\twhile(--_cacheSize != 0);\n\t\t\t_cache = (((int)Low) >>> 24);\n\t\t}\n\t\t_cacheSize++;\n\t\tLow = (Low & 0xFFFFFF) << 8;\n\t}\n\t\n\tpublic void EncodeDirectBits(int v, int numTotalBits) throws IOException\n\t{\n\t\tfor (int i = numTotalBits - 1; i >= 0; i--)\n\t\t{\n\t\t\tRange >>>= 1;\n\t\t\tif (((v >>> i) & 1) == 1)\n\t\t\t\tLow += Range;\n\t\t\tif ((Range & Encoder.kTopMask) == 0)\n\t\t\t{\n\t\t\t\tRange <<= 8;\n\t\t\t\tShiftLow();\n\t\t\t}\n\t\t}\n\t}\n\t\n\t\n\tpublic long GetProcessedSizeAdd()\n\t{\n\t\treturn _cacheSize + _position + 4;\n\t}\n\t\n\t\n\t\n\tstatic final int kNumMoveReducingBits = 2;\n\tpublic static final int kNumBitPriceShiftBits = 6;\n\t\n\tpublic static void InitBitModels(short []probs)\n\t{\n\t\tfor (int i = 0; i < probs.length; i++)\n\t\t\tprobs[i] = (kBitModelTotal >>> 1);\n\t}\n\t\n\tpublic void Encode(short []probs, int index, int symbol) throws IOException\n\t{\n\t\tint prob = probs[index];\n\t\tint newBound = (Range >>> kNumBitModelTotalBits) * prob;\n\t\tif (symbol == 0)\n\t\t{\n\t\t\tRange = newBound;\n\t\t\tprobs[index] = (short)(prob + ((kBitModelTotal - prob) >>> kNumMoveBits));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tLow += (newBound & 0xFFFFFFFFL);\n\t\t\tRange -= newBound;\n\t\t\tprobs[index] = (short)(prob - ((prob) >>> kNumMoveBits));\n\t\t}\n\t\tif ((Range & kTopMask) == 0)\n\t\t{\n\t\t\tRange <<= 8;\n\t\t\tShiftLow();\n\t\t}\n\t}\n\t\n\tprivate static int[] ProbPrices = new int[kBitModelTotal >>> kNumMoveReducingBits];\n\t\n\tstatic\n\t{\n\t\tint kNumBits = (kNumBitModelTotalBits - kNumMoveReducingBits);\n\t\tfor (int i = kNumBits - 1; i >= 0; i--)\n\t\t{\n\t\t\tint start = 1 << (kNumBits - i - 1);\n\t\t\tint end = 1 << (kNumBits - i);\n\t\t\tfor (int j = start; j < end; j++)\n\t\t\t\tProbPrices[j] = (i << kNumBitPriceShiftBits) +\n\t\t\t\t\t\t(((end - j) << kNumBitPriceShiftBits) >>> (kNumBits - i - 1));\n\t\t}\n\t}\n\t\n\tstatic public int GetPrice(int Prob, int symbol)\n\t{\n\t\treturn ProbPrices[(((Prob - symbol) ^ ((-symbol))) & (kBitModelTotal - 1)) >>> kNumMoveReducingBits];\n\t}\n\tstatic public int GetPrice0(int Prob)\n\t{ \n\t\treturn ProbPrices[Prob >>> kNumMoveReducingBits]; \n\t}\n\tstatic public int GetPrice1(int Prob)\n\t{ \n\t\treturn ProbPrices[(kBitModelTotal - Prob) >>> kNumMoveReducingBits]; \n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/HRESULT.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip;\n\npublic class HRESULT {\n    public static final int S_OK = 0;\n    public static final int S_FALSE = 1;\n    \n    public static final int E_NOTIMPL    = 0x80004001;\n    public static final int E_FAIL       = 0x80004005;\n    public static final int E_INVALIDARG = 0x80070057;\n}\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/ICodeProgress.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip;\n\npublic interface ICodeProgress\n{\n\tvoid SetProgress(long inSize, long outSize);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/ICompressCoder.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip;\n\npublic interface ICompressCoder {\n    int Code(\n            java.io.InputStream inStream, // , ISequentialInStream\n            java.io.OutputStream outStream, // ISequentialOutStream\n            long outSize, ICompressProgressInfo progress) throws java.io.IOException ;\n    \n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/ICompressCoder2.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip;\n\nimport com.mucommander.commons.file.impl.sevenzip.provider.Common.RecordVector;\n\npublic interface ICompressCoder2 {\n    int Code(\n            RecordVector<java.io.InputStream>  inStreams,\n            Object useless1, // const UInt64 ** /* inSizes */,\n            int numInStreams,\n            RecordVector<java.io.OutputStream> outStreams,\n            Object useless2, // const UInt64 ** /* outSizes */,\n            int numOutStreams,\n            ICompressProgressInfo progress) throws java.io.IOException;\n    \n    void close() throws java.io.IOException ; // destructor\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/ICompressFilter.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip;\n\npublic interface ICompressFilter {\n  int Init();\n  int Filter(byte [] data, int size);\n  // Filter return outSize (UInt32)\n  // if (outSize <= size): Filter have converted outSize bytes\n  // if (outSize > size): Filter have not converted anything.\n  //      and it needs at least outSize bytes to convert one block \n  //      (it's for crypto block algorithms).\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/ICompressGetInStreamProcessedSize.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip;\n\npublic interface ICompressGetInStreamProcessedSize {\n    long GetInStreamProcessedSize();\n}\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/ICompressProgressInfo.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip;\n\npublic interface ICompressProgressInfo {\n    long INVALID = -1;\n\n    int SetRatioInfo(long inSize, long outSize);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/ICompressSetDecoderProperties2.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip;\n\npublic interface ICompressSetDecoderProperties2 {\n    boolean SetDecoderProperties2(byte[] properties);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/ICompressSetInStream.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip;\n\npublic interface ICompressSetInStream {\n    int SetInStream(java.io.InputStream inStream);\n    int ReleaseInStream() throws java.io.IOException ;\n}\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/ICompressSetOutStream.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip;\n\npublic interface ICompressSetOutStream {\n    int SetOutStream(java.io.OutputStream inStream);\n    int ReleaseOutStream() throws java.io.IOException;\n}\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/ICompressSetOutStreamSize.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip;\n\npublic interface ICompressSetOutStreamSize {\n    int INVALID_OUTSIZE = -1;\n\n    int SetOutStreamSize(long outSize);\n}\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/IInStream.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip;\n\npublic abstract class IInStream extends java.io.InputStream\n{\n  static public final int STREAM_SEEK_SET\t= 0;\n  static public final int STREAM_SEEK_CUR\t= 1;\n  // static public final int STREAM_SEEK_END\t= 2;\n  public abstract long Seek(long offset, int seekOrigin)  throws java.io.IOException ;\n}\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/IProgress.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip;\n\npublic interface IProgress {\n    int SetTotal(long total);\n    int SetCompleted(long completeValue);\n}\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/J7zip.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip;\r\n\r\n\r\nimport java.text.DateFormat;\r\n\r\nimport java.util.Arrays;\r\nimport java.util.Vector;\r\n\r\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.IArchiveExtractCallback;\r\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.IInArchive;\r\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZipEntry;\r\nimport com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Archive.SevenZip.Handler;\r\n\r\npublic class J7zip {\r\n    static void PrintHelp() {\r\n        System.out.println(\r\n                \"\\nUsage:  JZip <l|t|x> <archive_name> [<file_names>...]\\n\" +\r\n                \"  l : Lists files\\n\" +\r\n                \"  t : Tests archive.7z\\n\" +\r\n                \"  x : eXtracts files\\n\");\r\n    }\r\n    \r\n    static void listing(IInArchive archive,Vector<String> listOfNames,boolean techMode) {\r\n        \r\n        if (!techMode) {\r\n            System.out.println(\"  Date   Time   Attr         Size   Compressed  Name\");\r\n            System.out.println(\"-------------- ----- ------------ ------------  ------------\");\r\n        }\r\n        \r\n        long size = 0;\r\n        long packSize = 0;\r\n        long nbFiles = 0;\r\n        \r\n        for(int i = 0; i < archive.size() ; i++) {\r\n            SevenZipEntry item = archive.getEntry(i);\r\n            \r\n            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.SHORT , DateFormat.SHORT );\r\n            String str_tm = formatter.format(new java.util.Date(item.getTime()));\r\n            \r\n            if (listOfNames.contains(item.getName())) {\r\n                if (techMode) {\r\n                    System.out.println(\"Path = \" + item.getName());\r\n                    System.out.println(\"Size = \" + item.getSize());\r\n                    System.out.println(\"Packed Size = \" + item.getCompressedSize());\r\n                    System.out.println(\"Modified = \" + str_tm);\r\n                    System.out.println(\"   Attributes : \" + item.getAttributesString());\r\n                    long crc = item.getCrc();\r\n                    if (crc != -1)\r\n                        System.out.println(\"CRC = \" + Long.toHexString(crc).toUpperCase());\r\n                    else\r\n                        System.out.println(\"CRC =\");\r\n                    System.out.println(\"Method = \" + item.getMethods() );\r\n                    System.out.println();\r\n                    \r\n                } else {\r\n                    System.out.print(str_tm + \" \" + item.getAttributesString());\r\n                    \r\n                    System.out.print(String.format(\"%13d\",item.getSize()));\r\n                    \r\n                    System.out.print(String.format(\"%13d\",item.getCompressedSize()));\r\n                    \r\n                    System.out.println(\"  \" + item.getName());\r\n                }\r\n                \r\n                size += item.getSize();\r\n                packSize += item.getCompressedSize();\r\n                nbFiles ++;\r\n            }\r\n        }\r\n        \r\n        if (!techMode) {\r\n            System.out.println(\"-------------- ----- ------------ ------------  ------------\");\r\n            System.out.print(String.format(\"                    %13d%13d %d files\",size,packSize,nbFiles));\r\n        }\r\n    }\r\n    \r\n    static void testOrExtract(IInArchive archive,Vector<String> listOfNames,int mode) {\r\n        \r\n        ArchiveExtractCallback extractCallbackSpec = new ArchiveExtractCallback();\r\n        IArchiveExtractCallback extractCallback = extractCallbackSpec;\r\n        extractCallbackSpec.Init(archive);\r\n        extractCallbackSpec.PasswordIsDefined = false;\r\n        \r\n        try {  \r\n            int len = 0;\r\n            int arrays []  = null;\r\n            \r\n            if (listOfNames.size() >= 1) {\r\n                arrays = new int[listOfNames.size()];\r\n                for(int i = 0 ; i < archive.size() ; i++) {\r\n                    if (listOfNames.contains(archive.getEntry(i).getName())) {\r\n                        arrays[len++] = i;\r\n                    }\r\n                }\r\n            }\r\n                \r\n            int res;\r\n            \r\n            if (len == 0) {\r\n                res = archive.Extract(null, -1, mode , extractCallback);\r\n            } else {\r\n                res = archive.Extract(arrays, len, mode, extractCallback);\r\n            }\r\n            \r\n            if (res == HRESULT.S_OK) {\r\n                if (extractCallbackSpec.NumErrors == 0)\r\n                    System.out.println(\"Ok Done\");\r\n                else\r\n                    System.out.println(\" \" + extractCallbackSpec.NumErrors + \" errors\");\r\n            } else {\r\n                System.out.println(\"ERROR !!\");\r\n            }\r\n        } catch (java.io.IOException e) {\r\n            System.out.println(\"IO error : \" + e.getLocalizedMessage());\r\n        }\r\n    }\r\n    \r\n    public static void main(String[] args) throws Exception {\r\n        System.out.println(\"\\nJ7zip 4.43 ALPHA 2 (\" + Runtime.getRuntime().availableProcessors() + \" CPUs)\");\r\n        \r\n        if (args.length < 2) {\r\n            PrintHelp();\r\n            return ;\r\n        }\r\n        \r\n        final int MODE_LISTING = 0;\r\n        final int MODE_TESTING = 1;\r\n        final int MODE_EXTRACT = 2;\r\n        \r\n        int mode = -1;\r\n\r\n        Vector<String> listOfNames = new Vector<>(Arrays.asList(args).subList(2, args.length));\r\n        \r\n        switch (args[0]) {\r\n            case \"l\":\r\n            mode = MODE_LISTING;\r\n                break;\r\n            case \"t\":\r\n            mode = MODE_TESTING;\r\n                break;\r\n            case \"x\":\r\n            mode = MODE_EXTRACT;\r\n                break;\r\n            default:\r\n            PrintHelp();\r\n                return;\r\n        }\r\n        \r\n        String filename = args[1];\r\n        \r\n        MyRandomAccessFile istream = new MyRandomAccessFile(filename,\"r\");\r\n        \r\n        IInArchive archive = new Handler();\r\n        \r\n        int ret = archive.Open( istream );\r\n        \r\n        if (ret != 0) {\r\n            System.out.println(\"ERROR !\");\r\n            return ;\r\n        }\r\n        \r\n        switch(mode) {\r\n            case MODE_LISTING:\r\n                listing(archive,listOfNames,false);\r\n                break;\r\n            case MODE_TESTING:\r\n                testOrExtract(archive,listOfNames,IInArchive.NExtract_NAskMode_kTest);\r\n                break;\r\n            case MODE_EXTRACT:\r\n                testOrExtract(archive,listOfNames,IInArchive.NExtract_NAskMode_kExtract);\r\n                break;\r\n        }\r\n        \r\n        archive.close();\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/LzmaAlone.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip;\r\n\r\npublic class LzmaAlone\r\n{\r\n\tstatic public class CommandLine\r\n\t{\r\n\t\tpublic static final int kEncode = 0;\r\n\t\tpublic static final int kDecode = 1;\r\n\t\tpublic static final int kBenchmak = 2;\r\n\t\t\r\n\t\tpublic int Command = -1;\r\n\t\tpublic int NumBenchmarkPasses = 10;\r\n\t\t\r\n\t\tpublic int dictionarySize = 1 << 23;\r\n\t\tpublic boolean dictionarySizeIsDefined = false;\r\n\t\t\r\n\t\tpublic int Lc = 3;\r\n\t\tpublic int Lp = 0;\r\n\t\tpublic int Pb = 2;\r\n\t\t\r\n\t\tpublic int fb = 128;\r\n\t\tpublic boolean fbIsDefined = false;\r\n\t\t\r\n\t\tpublic boolean Eos = false;\r\n\t\t\r\n\t\tpublic int algorithm = 2;\r\n\t\tpublic int matchFinder = 1;\r\n\t\t\r\n\t\tpublic String inFile;\r\n\t\tpublic String outFile;\r\n\t\t\r\n\t\tboolean ParseSwitch(String s) {\r\n\t\t\tif (s.startsWith(\"d\")) {\r\n\t\t\t\tdictionarySize = 1 << Integer.parseInt(s.substring(1));\r\n\t\t\t\tdictionarySizeIsDefined = true;\r\n\t\t\t} else if (s.startsWith(\"fb\")) {\r\n\t\t\t\tfb = Integer.parseInt(s.substring(2));\r\n\t\t\t\tfbIsDefined = true;\r\n\t\t\t} else if (s.startsWith(\"a\")) {\r\n\t\t\t\talgorithm = Integer.parseInt(s.substring(1));\r\n\t\t\t} else if (s.startsWith(\"lc\")) {\r\n\t\t\t\tLc = Integer.parseInt(s.substring(2));\r\n\t\t\t} else if (s.startsWith(\"lp\")) {\r\n\t\t\t\tLp = Integer.parseInt(s.substring(2));\r\n\t\t\t} else if (s.startsWith(\"pb\")) {\r\n\t\t\t\tPb = Integer.parseInt(s.substring(2));\r\n\t\t\t} else if (s.startsWith(\"eos\")) {\r\n\t\t\t\tEos = true;\r\n\t\t\t} else if (s.startsWith(\"mf\")) {\r\n\t\t\t\tString mfs = s.substring(2);\r\n\t\t\t\tswitch (mfs) {\r\n\t\t\t\t\tcase \"bt2\":\r\n\t\t\t\t\t\tmatchFinder = 0;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase \"bt4\":\r\n\t\t\t\t\t\tmatchFinder = 1;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase \"bt4b\":\r\n\t\t\t\t\t\tmatchFinder = 2;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tdefault:\r\n\t\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\treturn true;\r\n\t\t}\r\n\t\t\r\n\t\tpublic boolean Parse(String[] args) {\r\n\t\t\tint pos = 0;\r\n\t\t\tboolean switchMode = true;\r\n\t\t\tfor (String s : args) {\r\n\t\t\t\tif (s.isEmpty())\r\n\t\t\t\t\treturn false;\r\n\t\t\t\tif (switchMode) {\r\n\t\t\t\t\tif (s.compareTo(\"--\") == 0) {\r\n\t\t\t\t\t\tswitchMode = false;\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (s.charAt(0) == '-') {\r\n\t\t\t\t\t\tString sw = s.substring(1).toLowerCase();\r\n\t\t\t\t\t\tif (sw.isEmpty())\r\n\t\t\t\t\t\t\treturn false;\r\n\t\t\t\t\t\ttry {\r\n\t\t\t\t\t\t\tif (!ParseSwitch(sw))\r\n\t\t\t\t\t\t\t\treturn false;\r\n\t\t\t\t\t\t} catch (NumberFormatException e) {\r\n\t\t\t\t\t\t\treturn false;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (pos == 0) {\r\n\t\t\t\t\tif (s.equalsIgnoreCase(\"e\"))\r\n\t\t\t\t\t\tCommand = kEncode;\r\n\t\t\t\t\telse if (s.equalsIgnoreCase(\"d\"))\r\n\t\t\t\t\t\tCommand = kDecode;\r\n\t\t\t\t\telse if (s.equalsIgnoreCase(\"b\"))\r\n\t\t\t\t\t\tCommand = kBenchmak;\r\n\t\t\t\t\telse\r\n\t\t\t\t\t\treturn false;\r\n\t\t\t\t} else if (pos == 1) {\r\n\t\t\t\t\tif (Command == kBenchmak) {\r\n\t\t\t\t\t\ttry {\r\n\t\t\t\t\t\t\tNumBenchmarkPasses = Integer.parseInt(s);\r\n\t\t\t\t\t\t\tif (NumBenchmarkPasses < 1)\r\n\t\t\t\t\t\t\t\treturn false;\r\n\t\t\t\t\t\t} catch (NumberFormatException e) {\r\n\t\t\t\t\t\t\treturn false;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else\r\n\t\t\t\t\t\tinFile = s;\r\n\t\t\t\t} else if (pos == 2)\r\n\t\t\t\t\toutFile = s;\r\n\t\t\t\t\telse\r\n\t\t\t\t\treturn false;\r\n\t\t\t\tpos++;\r\n\t\t\t\t//continue;\r\n\t\t\t}\r\n\t\t\treturn true;\r\n\t\t}\r\n\t}\r\n\r\n\t\r\n\t\r\n\tstatic void PrintHelp()\r\n\t{\r\n\t\tSystem.out.println(\r\n\t\t\t\t\"\\nUsage:  LZMA <e|d> [<switches>...] inputFile outputFile\\n\" +\r\n\t\t\t\t\"  e: encode file\\n\" +\r\n\t\t\t\t\"  d: decode file\\n\" +\r\n\t\t\t\t\"  b: Benchmark\\n\" +\r\n\t\t\t\t\"<Switches>\\n\" +\r\n\t\t\t\t// \"  -a{N}:  set compression mode - [0, 1], default: 1 (max)\\n\" +\r\n\t\t\t\t\"  -d{N}:  set dictionary - [0,28], default: 23 (8MB)\\n\" +\r\n\t\t\t\t\"  -fb{N}: set number of fast bytes - [5, 273], default: 128\\n\" +\r\n\t\t\t\t\"  -lc{N}: set number of literal context bits - [0, 8], default: 3\\n\" +\r\n\t\t\t\t\"  -lp{N}: set number of literal pos bits - [0, 4], default: 0\\n\" +\r\n\t\t\t\t\"  -pb{N}: set number of pos bits - [0, 4], default: 2\\n\" +\r\n\t\t\t\t\"  -mf{MF_ID}: set Match Finder: [bt2, bt4], default: bt4\\n\" +\r\n\t\t\t\t\"  -eos:   write End Of Stream marker\\n\"\r\n\t\t\t\t);\r\n\t}\r\n\t\r\n\tpublic static void main(String[] args) throws Exception\r\n\t{\r\n\t\tSystem.out.println(\"\\nLZMA (Java) 4.42 Copyright (c) 1999-2006 Igor Pavlov  2006-05-15\\n\");\r\n\t\t\r\n\t\tif (args.length < 1)\r\n\t\t{\r\n\t\t\tPrintHelp();\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tCommandLine params = new CommandLine();\r\n\t\tif (!params.Parse(args))\r\n\t\t{\r\n\t\t\tSystem.out.println(\"\\nIncorrect command\");\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tif (params.Command == CommandLine.kBenchmak)\r\n\t\t{\r\n\t\t\tint dictionary = (1 << 21);\r\n\t\t\tif (params.dictionarySizeIsDefined)\r\n\t\t\t\tdictionary = params.dictionarySize;\r\n\t\t\tif (params.matchFinder > 1)\r\n\t\t\t\tthrow new Exception(\"Unsupported match finder\");\r\n\t\t\tcom.mucommander.commons.file.impl.sevenzip.provider.SevenZip.LzmaBench.LzmaBenchmark(params.NumBenchmarkPasses, dictionary);\r\n\t\t}\r\n\t\telse if (params.Command == CommandLine.kEncode || params.Command == CommandLine.kDecode)\r\n\t\t{\r\n\t\t\tjava.io.File inFile = new java.io.File(params.inFile);\r\n\t\t\tjava.io.File outFile = new java.io.File(params.outFile);\r\n\t\t\t\r\n\t\t\tjava.io.BufferedInputStream inStream  = new java.io.BufferedInputStream(new java.io.FileInputStream(inFile));\r\n\t\t\tjava.io.BufferedOutputStream outStream = new java.io.BufferedOutputStream(new java.io.FileOutputStream(outFile));\r\n\t\t\t\r\n\t\t\tboolean eos = false;\r\n\t\t\tif (params.Eos)\r\n\t\t\t\teos = true;\r\n\t\t\tif (params.Command == CommandLine.kEncode)\r\n\t\t\t{\r\n\t\t\t\tcom.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.LZMA.Encoder encoder = new com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.LZMA.Encoder();\r\n\t\t\t\tif (!encoder.SetAlgorithm(params.algorithm))\r\n\t\t\t\t\tthrow new Exception(\"Incorrect compression mode\");\r\n\t\t\t\tif (!encoder.SetDictionarySize(params.dictionarySize))\r\n\t\t\t\t\tthrow new Exception(\"Incorrect dictionary size\");\r\n\t\t\t\tif (!encoder.SeNumFastBytes(params.fb))\r\n\t\t\t\t\tthrow new Exception(\"Incorrect -fb value\");\r\n\t\t\t\tif (!encoder.SetMatchFinder(params.matchFinder))\r\n\t\t\t\t\tthrow new Exception(\"Incorrect -mf value\");\r\n\t\t\t\tif (!encoder.SetLcLpPb(params.Lc, params.Lp, params.Pb))\r\n\t\t\t\t\tthrow new Exception(\"Incorrect -lc or -lp or -pb value\");\r\n\t\t\t\tencoder.SetEndMarkerMode(eos);\r\n\t\t\t\tencoder.WriteCoderProperties(outStream);\r\n\t\t\t\tlong fileSize;\r\n\t\t\t\tif (eos)\r\n\t\t\t\t\tfileSize = -1;\r\n\t\t\t\telse\r\n\t\t\t\t\tfileSize = inFile.length();\r\n\t\t\t\tfor (int i = 0; i < 8; i++)\r\n\t\t\t\t\toutStream.write((int)(fileSize >>> (8 * i)) & 0xFF);\r\n\t\t\t\tencoder.Code(inStream, outStream, -1, -1, null);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tint propertiesSize = 5;\r\n\t\t\t\tbyte[] properties = new byte[propertiesSize];\r\n\t\t\t\tif (inStream.read(properties, 0, propertiesSize) != propertiesSize)\r\n\t\t\t\t\tthrow new Exception(\"input .lzma file is too short\");\r\n\t\t\t\tcom.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.LZMA.Decoder decoder = new com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.LZMA.Decoder();\r\n\t\t\t\tif (!decoder.SetDecoderProperties2(properties))\r\n\t\t\t\t\tthrow new Exception(\"Incorrect stream properties\");\r\n\t\t\t\tlong outSize = 0;\r\n\t\t\t\tfor (int i = 0; i < 8; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tint v = inStream.read();\r\n\t\t\t\t\tif (v < 0)\r\n\t\t\t\t\t\tthrow new Exception(\"Can't read stream size\");\r\n\t\t\t\t\toutSize |= ((long)v) << (8 * i);\r\n\t\t\t\t}\r\n\t\t\t\tif (decoder.Code(inStream, outStream, outSize,null) != HRESULT.S_OK)\r\n\t\t\t\t\tthrow new Exception(\"Error in data stream\");\r\n\t\t\t}\r\n\t\t\toutStream.flush();\r\n\t\t\toutStream.close();\r\n\t\t\tinStream.close();\r\n\t\t}\r\n\t\telse\r\n\t\t\tthrow new Exception(\"Incorrect command\");\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/LzmaBench.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\n\npublic class LzmaBench\n{\n\tstatic final int kAdditionalSize = (1 << 21);\n\tstatic final int kCompressedAdditionalSize = (1 << 10);\n\t\n\tstatic class CRandomGenerator\n\t{\n\t\tint A1;\n\t\tint A2;\n\t\tpublic CRandomGenerator() { Init(); }\n\t\tpublic void Init() { A1 = 362436069; A2 = 521288629; }\n\t\tpublic int GetRnd()\n\t\t{\n\t\t\treturn\n\t\t\t\t((A1 = 36969 * (A1 & 0xffff) + (A1 >>> 16)) << 16) ^\n\t\t\t\t((A2 = 18000 * (A2 & 0xffff) + (A2 >>> 16)));\n\t\t}\n\t}\n\t\n\tstatic class CBitRandomGenerator\n\t{\n\t\tCRandomGenerator RG = new CRandomGenerator();\n\t\tint Value;\n\t\tint NumBits;\n\t\tpublic void Init()\n\t\t{\n\t\t\tValue = 0;\n\t\t\tNumBits = 0;\n\t\t}\n\t\tpublic int GetRnd(int numBits)\n\t\t{\n\t\t\tint result;\n\t\t\tif (NumBits > numBits)\n\t\t\t{\n\t\t\t\tresult = Value & ((1 << numBits) - 1);\n\t\t\t\tValue >>>= numBits;\n\t\t\t\tNumBits -= numBits;\n\t\t\t\treturn result;\n\t\t\t}\n\t\t\tnumBits -= NumBits;\n\t\t\tresult = (Value << numBits);\n\t\t\tValue = RG.GetRnd();\n\t\t\tresult |= Value & ((1 << numBits) - 1);\n\t\t\tValue >>>= numBits;\n\t\t\tNumBits = 32 - numBits;\n\t\t\treturn result;\n\t\t}\n\t}\n\t\n\tstatic class CBenchRandomGenerator\n\t{\n\t\tCBitRandomGenerator RG = new CBitRandomGenerator();\n\t\tint Pos;\n\t\tint Rep0;\n\n\t\tpublic int BufferSize;\n\t\tpublic byte[] Buffer = null;\n\n\t\tpublic CBenchRandomGenerator() { }\n\t\tpublic void Set(int bufferSize)\n\t\t{\n\t\t\tBuffer = new byte[bufferSize];\n\t\t\tPos = 0;\n\t\t\tBufferSize = bufferSize;\n\t\t}\n\t\tint GetRndBit() { return RG.GetRnd(1); }\n\t\tint GetLogRandBits(int numBits)\n\t\t{\n\t\t\tint len = RG.GetRnd(numBits);\n\t\t\treturn RG.GetRnd(len);\n\t\t}\n\t\tint GetOffset()\n\t\t{\n\t\t\tif (GetRndBit() == 0)\n\t\t\t\treturn GetLogRandBits(4);\n\t\t\treturn (GetLogRandBits(4) << 10) | RG.GetRnd(10);\n\t\t}\n\t\tint GetLen1() { return RG.GetRnd(1 + RG.GetRnd(2)); }\n\t\tint GetLen2() { return RG.GetRnd(2 + RG.GetRnd(2)); }\n\t\tpublic void Generate()\n\t\t{\n\t\t\tRG.Init();\n\t\t\tRep0 = 1;\n\t\t\twhile (Pos < BufferSize)\n\t\t\t{\n\t\t\t\tif (GetRndBit() == 0 || Pos < 1)\n\t\t\t\t\tBuffer[Pos++] = (byte)(RG.GetRnd(8));\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tint len;\n\t\t\t\t\tif (RG.GetRnd(3) == 0)\n\t\t\t\t\t\tlen = 1 + GetLen1();\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tdo\n\t\t\t\t\t\t\tRep0 = GetOffset();\n\t\t\t\t\t\twhile (Rep0 >= Pos);\n\t\t\t\t\t\tRep0++;\n\t\t\t\t\t\tlen = 2 + GetLen2();\n\t\t\t\t\t}\n\t\t\t\t\tfor (int i = 0; i < len && Pos < BufferSize; i++, Pos++)\n\t\t\t\t\t\tBuffer[Pos] = Buffer[Pos - Rep0];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t\n\tstatic class CrcOutStream extends java.io.OutputStream\n\t{\n\t\tpublic com.mucommander.commons.file.impl.sevenzip.provider.Common.CRC CRC = new com.mucommander.commons.file.impl.sevenzip.provider.Common.CRC();\n\t\t\n\t\tpublic void Init()\n\t\t{ \n\t\t\tCRC.Init(); \n\t\t}\n\t\tpublic int GetDigest()\n\t\t{ \n\t\t\treturn CRC.getDigest();\n\t\t}\n\t\tpublic void write(byte[] b)\n\t\t{\n\t\t\tCRC.Update(b);\n\t\t}\n\t\tpublic void write(byte[] b, int off, int len)\n\t\t{\n\t\t\tCRC.Update(b, off, len);\n\t\t}\n\t\tpublic void write(int b)\n\t\t{\n\t\t\tCRC.updateByte(b);\n\t\t}\n\t}\n\n\tstatic class MyOutputStream extends java.io.OutputStream\n\t{\n\t\tbyte[] _buffer;\n\t\tint _size;\n\t\tint _pos;\n\t\t\n\t\tpublic MyOutputStream(byte[] buffer)\n\t\t{\n\t\t\t_buffer = buffer;\n\t\t\t_size = _buffer.length;\n\t\t}\n\t\t\n\t\tpublic void reset()\n\t\t{ \n\t\t\t_pos = 0; \n\t\t}\n\t\t\n\t\tpublic void write(int b) throws IOException\n\t\t{\n\t\t\tif (_pos >= _size)\n\t\t\t\tthrow new IOException(\"Error\");\n\t\t\t_buffer[_pos++] = (byte)b;\n\t\t}\n\t\t\n\t\tpublic int size()\n\t\t{\n\t\t\treturn _pos;\n\t\t}\n\t}\n\n\tstatic class MyInputStream extends java.io.InputStream\n\t{\n\t\tbyte[] _buffer;\n\t\tint _size;\n\t\tint _pos;\n\t\t\n\t\tpublic MyInputStream(byte[] buffer, int size)\n\t\t{\n\t\t\t_buffer = buffer;\n\t\t\t_size = size;\n\t\t}\n\t\t\n\t\tpublic void reset()\n\t\t{ \n\t\t\t_pos = 0; \n\t\t}\n\t\t\n\t\tpublic int read()\n\t\t{\n\t\t\tif (_pos >= _size)\n\t\t\t\treturn -1;\n\t\t\treturn _buffer[_pos++] & 0xFF;\n\t\t}\n\t}\n\t\n\tstatic class CProgressInfo implements ICodeProgress\n\t{\n\t\tpublic long ApprovedStart;\n\t\tpublic long InSize;\n\t\tpublic long Time;\n\t\tpublic void Init()\n\t\t{ InSize = 0; }\n\t\tpublic void SetProgress(long inSize, long outSize)\n\t\t{\n\t\t\tif (inSize >= ApprovedStart && InSize == 0)\n\t\t\t{\n\t\t\t\tTime = System.currentTimeMillis();\n\t\t\t\tInSize = inSize;\n\t\t\t}\n\t\t}\n\t}\n\tstatic final int kSubBits = 8;\n\t\n\tstatic int GetLogSize(int size)\n\t{\n\t\tfor (int i = kSubBits; i < 32; i++)\n\t\t\tfor (int j = 0; j < (1 << kSubBits); j++)\n\t\t\t\tif (size <= ((1) << i) + (j << (i - kSubBits)))\n\t\t\t\t\treturn (i << kSubBits) + j;\n\t\treturn (32 << kSubBits);\n\t}\n\t\n\tstatic long MyMultDiv64(long value, long elapsedTime)\n\t{\n\t\tlong freq = 1000; // ms\n\t\tlong elTime = elapsedTime;\n\t\twhile (freq > 1000000)\n\t\t{\n\t\t\tfreq >>>= 1;\n\t\t\telTime >>>= 1;\n\t\t}\n\t\tif (elTime == 0)\n\t\t\telTime = 1;\n\t\treturn value * freq / elTime;\n\t}\n\t\n\tstatic long GetCompressRating(int dictionarySize, long elapsedTime, long size)\n\t{\n\t\tlong t = GetLogSize(dictionarySize) - (18 << kSubBits);\n\t\tlong numCommandsForOne = 1060 + ((t * t * 10) >> (2 * kSubBits));\n\t\tlong numCommands = (size) * numCommandsForOne;\n\t\treturn MyMultDiv64(numCommands, elapsedTime);\n\t}\n\t\n\tstatic long GetDecompressRating(long elapsedTime, long outSize, long inSize)\n\t{\n\t\tlong numCommands = inSize * 220 + outSize * 20;\n\t\treturn MyMultDiv64(numCommands, elapsedTime);\n\t}\n\t\n\tstatic long GetTotalRating(\n\t\t\tint dictionarySize,\n\t\t\tlong elapsedTimeEn, long sizeEn,\n\t\t\tlong elapsedTimeDe,\n\t\t\tlong inSizeDe, long outSizeDe)\n\t{\n\t\treturn (GetCompressRating(dictionarySize, elapsedTimeEn, sizeEn) +\n\t\t\t\tGetDecompressRating(elapsedTimeDe, inSizeDe, outSizeDe)) / 2;\n\t}\n\t\n\tstatic void PrintValue(long v)\n\t{\n\t\tString s = \"\";\n\t\ts += v;\n\t\tfor (int i = 0; i + s.length() < 6; i++)\n\t\t\tSystem.out.print(\" \");\n\t\tSystem.out.print(s);\n\t}\n\t\n\tstatic void PrintRating(long rating)\n\t{\n\t\tPrintValue(rating / 1000000);\n\t\tSystem.out.print(\" MIPS\");\n\t}\n\t\n\tstatic void PrintResults(\n\t\t\tint dictionarySize,\n\t\t\tlong elapsedTime,\n\t\t\tlong size,\n\t\t\tboolean decompressMode, long secondSize)\n\t{\n\t\tlong speed = MyMultDiv64(size, elapsedTime);\n\t\tPrintValue(speed / 1024);\n\t\tSystem.out.print(\" KB/s  \");\n\t\tlong rating;\n\t\tif (decompressMode)\n\t\t\trating = GetDecompressRating(elapsedTime, size, secondSize);\n\t\telse\n\t\t\trating = GetCompressRating(dictionarySize, elapsedTime, size);\n\t\tPrintRating(rating);\n\t}\n\t\n\tstatic public int LzmaBenchmark(int numIterations, int dictionarySize) throws Exception\n\t{\n\t\tif (numIterations <= 0)\n\t\t\treturn 0;\n\t\tif (dictionarySize < (1 << 18))\n\t\t{\n\t\t\tSystem.out.println(\"\\nError: dictionary size for benchmark must be >= 18 (256 KB)\");\n\t\t\treturn 1;\n\t\t}\n\t\tSystem.out.print(\"\\n       Compressing                Decompressing\\n\\n\");\n\t\t\n\t\tcom.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.LZMA.Encoder encoder = new com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.LZMA.Encoder();\n\t\tcom.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.LZMA.Decoder decoder = new com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.Compression.LZMA.Decoder();\n\t\t\n\t\tif (!encoder.SetDictionarySize(dictionarySize))\n\t\t\tthrow new Exception(\"Incorrect dictionary size\");\n\t\t\n\t\tint kBufferSize = dictionarySize + kAdditionalSize;\n\t\tint kCompressedBufferSize = (kBufferSize / 2) + kCompressedAdditionalSize;\n\t\t\n\t\tByteArrayOutputStream propStream = new ByteArrayOutputStream();\n\t\tencoder.WriteCoderProperties(propStream);\n\t\tbyte[] propArray = propStream.toByteArray();\n\t\tdecoder.SetDecoderProperties2(propArray);\n\t\t\n\t\tCBenchRandomGenerator rg = new CBenchRandomGenerator();\n\n\t\trg.Set(kBufferSize);\n\t\trg.Generate();\n\t\tcom.mucommander.commons.file.impl.sevenzip.provider.Common.CRC crc = new com.mucommander.commons.file.impl.sevenzip.provider.Common.CRC();\n\t\tcrc.Init();\n\t\tcrc.Update(rg.Buffer, 0, rg.BufferSize);\n\t\t\n\t\tCProgressInfo progressInfo = new CProgressInfo();\n\t\tprogressInfo.ApprovedStart = dictionarySize;\n\t\t\n\t\tlong totalBenchSize = 0;\n\t\tlong totalEncodeTime = 0;\n\t\tlong totalDecodeTime = 0;\n\t\tlong totalCompressedSize = 0;\n\t\t\n\t\tMyInputStream inStream = new MyInputStream(rg.Buffer, rg.BufferSize);\n\n\t\tbyte[] compressedBuffer = new byte[kCompressedBufferSize];\n\t\tMyOutputStream compressedStream = new MyOutputStream(compressedBuffer);\n\t\tCrcOutStream crcOutStream = new CrcOutStream();\n\t\tMyInputStream inputCompressedStream = null;\n\t\tint compressedSize = 0;\n\t\tfor (int i = 0; i < numIterations; i++)\n\t\t{\n\t\t\tprogressInfo.Init();\n\t\t\tinStream.reset();\n\t\t\tcompressedStream.reset();\n\t\t\tencoder.Code(inStream, compressedStream, -1, -1, progressInfo);\n\t\t\tlong encodeTime = System.currentTimeMillis() - progressInfo.Time;\n\t\t\t\n\t\t\tif (i == 0)\n\t\t\t{\n\t\t\t\tcompressedSize = compressedStream.size();\n\t\t\t\tinputCompressedStream = new MyInputStream(compressedBuffer, compressedSize);\n\t\t\t}\n\t\t\telse if (compressedSize != compressedStream.size())\n\t\t\t\tthrow (new Exception(\"Encoding error\"));\n\t\t\t\t\n\t\t\tif (progressInfo.InSize == 0)\n\t\t\t\tthrow (new Exception(\"Internal ERROR 1282\"));\n\n\t\t\tlong decodeTime = 0;\n\t\t\tfor (int j = 0; j < 2; j++)\n\t\t\t{\n\t\t\t\tinputCompressedStream.reset();\n\t\t\t\tcrcOutStream.Init();\n\n                long startTime = System.currentTimeMillis();\n\t\t\t\tif (decoder.Code(inputCompressedStream, crcOutStream, (long) kBufferSize, null) != HRESULT.S_OK)\n\t\t\t\t\tthrow (new Exception(\"Decoding Error\"));\n\t\t\t\tdecodeTime = System.currentTimeMillis() - startTime;\n\t\t\t\tif (crcOutStream.GetDigest() != crc.getDigest())\n\t\t\t\t\tthrow (new Exception(\"CRC Error\"));\n\t\t\t}\n\t\t\tlong benchSize = kBufferSize - progressInfo.InSize;\n\t\t\tPrintResults(dictionarySize, encodeTime, benchSize, false, 0);\n\t\t\tSystem.out.print(\"     \");\n\t\t\tPrintResults(dictionarySize, decodeTime, kBufferSize, true, compressedSize);\n\t\t\tSystem.out.println();\n\t\t\t\n\t\t\ttotalBenchSize += benchSize;\n\t\t\ttotalEncodeTime += encodeTime;\n\t\t\ttotalDecodeTime += decodeTime;\n\t\t\ttotalCompressedSize += compressedSize;\n\t\t}\n\t\tSystem.out.println(\"---------------------------------------------------\");\n\t\tPrintResults(dictionarySize, totalEncodeTime, totalBenchSize, false, 0);\n\t\tSystem.out.print(\"     \");\n\t\tPrintResults(dictionarySize, totalDecodeTime,\n\t\t\t\tkBufferSize * (long)numIterations, true, totalCompressedSize);\n\t\tSystem.out.println(\"    Average\");\n\t\treturn 0;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sevenzip/provider/SevenZip/MyRandomAccessFile.java",
    "content": "package com.mucommander.commons.file.impl.sevenzip.provider.SevenZip;\n\npublic  class MyRandomAccessFile extends com.mucommander.commons.file.impl.sevenzip.provider.SevenZip.IInStream  {\n    \n    java.io.RandomAccessFile _file;\n    \n    MyRandomAccessFile(String filename,String mode)  throws java.io.IOException {\n        _file  = new java.io.RandomAccessFile(filename,mode);\n    }\n    \n    public long Seek(long offset, int seekOrigin)  throws java.io.IOException {\n        if (seekOrigin == STREAM_SEEK_SET) {\n            _file.seek(offset);\n        }\n        else if (seekOrigin == STREAM_SEEK_CUR) {\n            _file.seek(offset + _file.getFilePointer());\n        }\n        return _file.getFilePointer();\n    }\n    \n    public int read() throws java.io.IOException {\n        return _file.read();\n    }\n \n    public int read(byte [] data, int off, int size) throws java.io.IOException {\n        return _file.read(data,off,size);\n    }\n        \n    public int read(byte [] data, int size) throws java.io.IOException {\n        return _file.read(data,0,size);\n    }\n    \n    public void close() throws java.io.IOException {\n        _file.close();\n        _file = null;\n    }   \n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sftp/SFTPConnectionHandler.java",
    "content": "package com.mucommander.commons.file.impl.sftp;\n\nimport com.mucommander.commons.file.AuthException;\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.connection.ConnectionHandler;\nimport com.sshtools.net.SocketTransport;\nimport com.sshtools.publickey.InvalidPassphraseException;\nimport com.sshtools.publickey.SshPrivateKeyFile;\nimport com.sshtools.publickey.SshPrivateKeyFileFactory;\nimport com.sshtools.sftp.SftpClient;\nimport com.sshtools.sftp.SftpStatusException;\nimport com.sshtools.sftp.SftpSubsystemChannel;\nimport com.sshtools.ssh.*;\nimport com.sshtools.ssh.components.SshKeyPair;\nimport com.sshtools.ssh2.*;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * Handles connections to SFTP servers.\n *\n * @author Maxence Bernard, Vassil Dichev\n */\nclass SFTPConnectionHandler extends ConnectionHandler {\n    private static final Logger LOGGER = LoggerFactory.getLogger(SFTPConnectionHandler.class);\n\n    Ssh2Client sshClient;\n    SftpClient sftpClient;\n    SftpSubsystemChannel sftpSubsystem;\n\n    /** 'Password' SSH authentication method */\n    private final static String PASSWORD_AUTH_METHOD = \"password\";\n\n    /** 'Keyboard interactive' SSH authentication method */\n    private final static String KEYBOARD_INTERACTIVE_AUTH_METHOD = \"keyboard-interactive\";\n\n    /** 'Public key' SSH authentication method, not supported at the moment */\n    private final static String PUBLIC_KEY_AUTH_METHOD = \"publickey\";\n\n\n    SFTPConnectionHandler(FileURL location) {\n        super(location);\n    }\n\n\n    @Override\n    public void startConnection() throws IOException {\n        LOGGER.info(\"starting connection to {}\", realm);\n        try {\n            FileURL realm = getRealm();\n\n            // Retrieve credentials to be used to authenticate\n            final Credentials credentials = getCredentials();\n\n            // Throw an AuthException if no auth information, required for SSH\n            if (credentials == null) {\n                throwAuthException(\"Login and password required\");  // Todo: localize this entry\n            }\n\n            LOGGER.trace(\"creating SshClient\");\n\n\n            // Override default port (22) if a custom port was specified in the URL\n            int port = realm.getPort();\n            if (port == -1) {\n                port = 22;\n            }\n\n            // Connect to server, no host key verification\n            SshConnector con = SshConnector.createInstance();\n            // Lets do some host key verification\n            HostKeyVerification hkv = (hostname, key) -> {\n                try {\n                    System.out.println(\"The connected host's key (\"+ key.getAlgorithm() + \") is\");\n                    System.out.println(key.getFingerprint());\n                } catch (SshException ignore) {}\n                return true;\n            };\n\n            con.getContext().setHostKeyVerification(hkv);\n            con.getContext().setPreferredPublicKey(Ssh2Context.PUBLIC_KEY_SSHDSS);\n\n            // Init SSH client\n            sshClient = (Ssh2Client) con.connect(new SocketTransport(realm.getHost(), port), credentials.getLogin(), true);\n\n\n//            sshClient.connect(realm.getHost(), port, new IgnoreHostKeyVerification());\n\n            // Retrieve a list of available authentication methods on the server.\n            // Some SSH servers support the 'password' auth method (e.g. OpenSSH on Debian unstable), some don't\n            // and only support the 'keyboard-interactive' method.\n            List<String> authMethods = new ArrayList<>(Arrays.asList(sshClient.getAuthenticationMethods(credentials.getLogin())));\n            LOGGER.info(\"getAvailableAuthMethods()={}\", sshClient.getAuthenticationMethods(credentials.getLogin()));\n\n            SshAuthentication authClient = null;\n            String privateKeyPath = realm.getProperty(SFTPFile.PRIVATE_KEY_PATH_PROPERTY_NAME);\n            // Try public key first. Don't try other methods if there's a key file defined\n            if (authMethods.contains(PUBLIC_KEY_AUTH_METHOD) && privateKeyPath != null) {\n                LOGGER.info(\"Using {} authentication method\", PUBLIC_KEY_AUTH_METHOD);\n\n                Ssh2PublicKeyAuthentication pk = new Ssh2PublicKeyAuthentication();\n                pk.setUsername(credentials.getLogin());\n\n                // Throw an AuthException if problems with private key file\n                try {\n                    SshPrivateKeyFile pkfile = SshPrivateKeyFileFactory.parse(new FileInputStream(privateKeyPath));\n                    SshKeyPair pair = pkfile.toKeyPair(pkfile.isPassphraseProtected() ? credentials.getPassword() : null);\n                    pk.setPrivateKey(pair.getPrivateKey());\n                    pk.setPublicKey(pair.getPublicKey());\n                } catch (IOException | InvalidPassphraseException e) {\n                    LOGGER.error(\"Keys error\", e);\n                    privateKeyPath = null;  // try to authorize via password on error\n//                    throwAuthException(\"Invalid private key file or passphrase\");  // Todo: localize this entry\n//                } catch (IOException e) {\n//                    e.printStackTrace();\n//                    throwAuthException(\"Error reading private key file\");  // Todo: localize this entry\n                }\n\n                authClient = pk;\n            }\n            // Use 'keyboard-interactive' method only if 'password' auth method is not available and\n            // 'keyboard-interactive' is supported by the server\n            //else\n            if (!authMethods.contains(PASSWORD_AUTH_METHOD) && authMethods.contains(KEYBOARD_INTERACTIVE_AUTH_METHOD) &&\n                    privateKeyPath == null) {\n                LOGGER.info(\"Using {} authentication method\", KEYBOARD_INTERACTIVE_AUTH_METHOD);\n\n                KBIAuthentication kbi = new KBIAuthentication();\n                kbi.setUsername(credentials.getLogin());\n\n                // Fake keyboard password input\n                kbi.setKBIRequestHandler((name, instruction, prompts) -> {\n                    // Workaround for what seems to be a bug in J2SSH: this method is called twice, first time\n                    // with a valid KBIPrompt array, second time with null\n                    if (prompts == null) {\n                        LOGGER.trace(\"prompts is null!\");\n                        return false;\n                    }\n\n                    for (int i = 0; i < prompts.length; i++) {\n                        LOGGER.trace(\"prompts[{}]={}\", i, prompts[i].getPrompt());\n                        prompts[i].setResponse(credentials.getPassword());\n                    }\n                    return true;\n                });\n\n                authClient = kbi;\n            }\n            // Default to 'password' method, even if server didn't report as being supported\n            else if (privateKeyPath == null) {\n                LOGGER.info(\"Using {} authentication method\", PASSWORD_AUTH_METHOD);\n\n                Ssh2PasswordAuthentication pwd = new Ssh2PasswordAuthentication();\n                pwd.setUsername(credentials.getLogin());\n                pwd.setPassword(credentials.getPassword());\n\n                authClient = pwd;\n            }\n\n            authenticate(authClient);\n            // Init SFTP connections\n            sftpClient = new SftpClient(sshClient);\n            SshSession session = sshClient.openSessionChannel();\n\n            if (session instanceof Ssh2Session) {\n                ((Ssh2Session) session).startSubsystem(\"sftp\");\n            }\n            sftpSubsystem = new SftpSubsystemChannel(session);\n            sftpSubsystem.initialize();\n        } catch(IOException | SftpStatusException | SshException | ChannelOpenException e) {\n            LOGGER.info(\"IOException thrown while starting connection\", e);\n            // Disconnect if something went wrong\n            if (sshClient != null && sshClient.isConnected()) {\n                sshClient.disconnect();\n            }\n\n            sshClient = null;\n            sftpClient = null;\n            sftpSubsystem = null;\n\n            // Re-throw exception\n            if (e instanceof IOException) {\n                throw (IOException)e;\n            } else {\n                throw new IOException(e);\n            }\n        }\n    }\n\n    private void authenticate(SshAuthentication authClient) throws SshException, AuthException {\n        try {\n            int authResult = sshClient.authenticate(authClient);\n\n            // Throw an AuthException if authentication failed\n            if (authResult != SshAuthentication.COMPLETE) {\n                throwAuthException(\"Login or password rejected\");   // Todo: localize this entry\n            }\n\n            LOGGER.info(\"authentication complete, authResult={}\", authResult);\n        } catch(AuthException e) {\n            LOGGER.info(\"Caught exception while authenticating\", e);\n            throw  e;//throwAuthException(e.getMessage());\n        }\n    }\n\n\n    @Override\n    public synchronized boolean isConnected() {\n        return sshClient != null && sshClient.isConnected()\n            && sftpClient != null && !sftpClient.isClosed()\n            && sftpSubsystem !=null && !sftpSubsystem.isClosed();\n    }\n\n\n    @Override\n    public synchronized void closeConnection() {\n        if (sftpClient != null) {\n            try {\n                sftpClient.quit();\n            } catch(SshException e) {\n                LOGGER.info(\"IOException caught while calling sftpClient.quit()\", e);\n            }\n        }\n\n        if (sftpSubsystem != null) {\n            try {\n                sftpSubsystem.close();\n            } catch(IOException e) {\n                LOGGER.info(\"IOException caught while calling sftpChannel.close ()\");\n            }\n        }\n\n        if (sshClient != null) {\n            sshClient.disconnect();\n        }\n    }\n\n\n    @Override\n    public void keepAlive() {\n        // No-op, keep alive is not available and shouldn't really be necessary, SSH servers such as OpenSSH usually\n        // maintain connections open without limit.\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sftp/SFTPConnectionHandlerFactory.java",
    "content": "package com.mucommander.commons.file.impl.sftp;\n\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.connection.ConnectionHandler;\nimport com.mucommander.commons.file.connection.ConnectionHandlerFactory;\n\n/**\n * <code>ConnectionHandlerFactory</code> that creates {@link SFTPConnectionHandler} instances.\n *\n * @author Maxence Bernard\n */\npublic class SFTPConnectionHandlerFactory implements ConnectionHandlerFactory {\n\n    public ConnectionHandler createConnectionHandler(FileURL location) {\n        return new SFTPConnectionHandler(location);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sftp/SFTPFile.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.impl.sftp;\n\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.file.connection.ConnectionHandler;\nimport com.mucommander.commons.file.connection.ConnectionPool;\nimport com.mucommander.commons.io.*;\nimport com.sshtools.sftp.*;\nimport com.sshtools.ssh.SshException;\nimport com.sshtools.util.UnsignedInteger32;\nimport com.sshtools.util.UnsignedInteger64;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n\n/**\n * SFTPFile provides access to files located on an SFTP server.\n *\n * <p>The associated {@link FileURL} scheme is {@link FileProtocols#SFTP}. The host part of the URL designates the\n * SFTP server. Credentials must be specified in the login and password parts as SFTP servers require a login and\n * password. The path separator is <code>'/'</code>.\n *\n * <p>Here are a few examples of valid SFTP URLs:\n * <code>\n * sftp://server/pathto/somefile<br>\n * sftp://login:password@server/pathto/somefile<br>\n * </code>\n *\n * <p>Internally, SFTPFile uses {@link ConnectionPool} to create SFTP connections as needed and allows them to be\n * reused by SFTPFile instances located on the same server, dealing with concurrency issues. Connections are\n * thus managed transparently and need not be manually managed.\n *\n * <p>Low-level SFTP implementation is provided by the <code>J2SSH</code> library distributed under the LGPL license.\n *\n * @see ConnectionPool\n * @author Maxence Bernard\n */\npublic class SFTPFile extends ProtocolFile {\n    private static final Logger LOGGER = LoggerFactory.getLogger(SFTPFile.class);\n\n    /** The absolute path to the file on the remote server, not the full URL */\n    private final String absPath;\n\n    /** Contains the file attribute values */\n    private final SFTPFileAttributes fileAttributes;\n\n    /** Cached parent file instance, null if not created yet or if this file has no parent */\n    private AbstractFile parent;\n    /** Has the parent file been determined yet? */\n    private boolean parentValSet;\n\n    /** Cached canonical path value, null if the canonical path hasn't been fetched yet */\n    private String canonicalPath;\n    /** Timestamp when the canonical path value was fetched */\n    private long canonicalPathFetchedTime;\n\n\n    /** Period of time during which file attributes are cached, before being fetched again from the server. */\n    private static long attributeCachingPeriod = 60000;\n\n    /** a SFTPConnectionHandlerFactory instance */\n    private final static SFTPConnectionHandlerFactory CONN_HANDLER_FACTORY = new SFTPConnectionHandlerFactory();\n\n    /** Name of the property that holds the path to a private key. This property is optional; if it is set, private key\n     * authentication is used. */\n    public final static String PRIVATE_KEY_PATH_PROPERTY_NAME = \"privateKeyPath\";\n\n    private final static String SEPARATOR = DEFAULT_SEPARATOR;\n\n\n    /**\n     * Creates a new instance of SFTPFile and initializes the SSH/SFTP connection to the server.\n     * @throws IOException if an I/O error occurred\n     */\n    SFTPFile(FileURL fileURL) throws IOException {\n        this(fileURL, null);\n    }\n\n    \n    SFTPFile(FileURL fileURL, SFTPFileAttributes fileAttributes) throws IOException {\n        super(fileURL);\n//        // Throw an AuthException if the url doesn't contain any credentials\n//        if(!fileURL.containsCredentials())\n//            throw new AuthException(fileURL);\n\n        this.absPath = fileURL.getPath();\n        this.fileAttributes = fileAttributes == null ? new SFTPFileAttributes(fileURL) : fileAttributes;\n    }\n\n    /**\n     * Sets the time period during which attributes values (e.g. isDirectory, last modified, ...) are cached.\n     * The higher this value, the lower the number of network requests but also the longer it takes\n     * before those attributes can be refreshed. A value of <code>0</code> disables attributes caching.\n     *\n     * <p>This class ensures that the attributes changed remotely by one of its methods are always updated locally, even\n     * with attributes caching enabled. To illustrate, after a call to {@link #mkdir()}, {@link #isDirectory()} will\n     * return <code>true</code>, even if the attributes haven't been refreshed. The attributes will however not be\n     * consistent if they have been changed by another {@link SFTPFile} or by another process, and will remain\n     * inconsistent for up to <code>period</code> milliseconds.\n     *\n     * @param period time period during which attributes values are cached, in milliseconds. 0 disables attributes caching.\n     */\n    public static void setAttributeCachingPeriod(long period) {\n        attributeCachingPeriod = period;\n    }\n\n    private OutputStream getOutputStream(boolean append) throws IOException {\n        // Retrieve a ConnectionHandler and lock it\n        final SFTPConnectionHandler connHandler = (SFTPConnectionHandler)ConnectionPool.getConnectionHandler(CONN_HANDLER_FACTORY, fileURL, true);\n        try {\n            // Makes sure the connection is started, if not starts it\n            connHandler.checkConnection();\n\n            SftpFile sftpFile;\n            if (exists()) {\n                sftpFile = connHandler.sftpSubsystem.openFile(absPath, append ? SftpSubsystemChannel.OPEN_WRITE | SftpSubsystemChannel.OPEN_APPEND\n                    : SftpSubsystemChannel.OPEN_WRITE | SftpSubsystemChannel.OPEN_TRUNCATE);\n\n                // Update local attributes\n                if (!append) {\n                    fileAttributes.setSize(0);\n                }\n            } else {\n                // Set new file permissions to 644 octal (420 dec): \"rw-r--r--\"\n                // Note: by default, permissions for files freshly created is 0 (not readable/writable/executable by anyone)!\n                // TODO pass real type\n                SftpFileAttributes atts = new SftpFileAttributes(connHandler.sftpSubsystem, SftpFileAttributes.SSH_FILEXFER_TYPE_REGULAR);\n                atts.setPermissions(new UnsignedInteger32(0644));\n                sftpFile = connHandler.sftpSubsystem.openFile(absPath, SftpSubsystemChannel.OPEN_WRITE|SftpSubsystemChannel.OPEN_CREATE, atts);\n\n                // Update local attributes\n                fileAttributes.setExists(true);\n                fileAttributes.setDate(System.currentTimeMillis());\n                fileAttributes.setSize(0);\n            }\n\n            // Custom SftpFileOutputStream constructor, not part of the official J2SSH API\n            OutputStream os = new SftpFileOutputStreamEx(sftpFile, append ? getSize() : 0L) {\n                @Override\n                public void close() throws IOException {\n                    // SftpFileOutputStream.close() closes the open SftpFile file handle\n                    super.close();\n\n                    // Release the lock on the ConnectionHandler\n                    connHandler.releaseLock();\n                }\n            };\n            ByteCounter byteCounter = new ByteCounter() {\n                @Override\n                public synchronized void add(long nbBytes) {\n                    fileAttributes.addToSize(nbBytes);\n                    fileAttributes.setDate(System.currentTimeMillis());\n                }\n            };\n            return new CounterOutputStream(os, byteCounter);\n        } catch(IOException e) {\n            // Release the lock on the ConnectionHandler if the OutputStream could not be created\n            connHandler.releaseLock();\n\n            // Re-throw IOException\n            throw e;\n        } catch (SftpStatusException | SshException e) {\n            // Release the lock on the ConnectionHandler if the OutputStream could not be created\n            connHandler.releaseLock();\n            throw new IOException(e);\n        }\n    }\n\n\n    public ConnectionHandler createConnectionHandler(FileURL location) {\n        return new SFTPConnectionHandler(location);\n    }\n\n\n    /**\n     * Implementation note: the value returned by this method will always be <code>false</code> if this file was\n     * created by the public constructor. If this file was created by the private constructor (by {@link #ls()},\n     * the value will be accurate (<code>true</code> if this file is a symlink) but will never get updated.\n     * See {@link com.mucommander.commons.file.impl.sftp.SFTPFile.SFTPFileAttributes} for more information.\n     */\n    @Override\n    public boolean isSymlink() {\n        return fileAttributes.isSymlink();\n    }\n\n    @Override\n    public boolean isSystem() {\n        return false;\n    }\n\n    /**\n     * Implementation note: for symlinks, returns the date of the link's target.\n     */\n    @Override\n    public long getLastModifiedDate() {\n        return ((SFTPFileAttributes)getCanonicalFile().getUnderlyingFileObject()).getLastModifiedDate();\n    }\n\n    @Override\n    public void setLastModifiedDate(long lastModified) throws IOException {\n        SFTPConnectionHandler connHandler = null;\n        SftpFile sftpFile = null;\n        try {\n            // Retrieve a ConnectionHandler and lock it\n            connHandler = (SFTPConnectionHandler)ConnectionPool.getConnectionHandler(CONN_HANDLER_FACTORY, fileURL, true);\n\n            // Makes sure the connection is started, if not starts it\n            connHandler.checkConnection();\n\n            // Retrieve an SftpFile instance for write, will throw an IOException if the file does not exist or cannot\n            // be written.\n            // /!\\ SftpFile instance must be closed afterwards to release its file handle\n            sftpFile = connHandler.sftpSubsystem.openFile(absPath, SftpSubsystemChannel.OPEN_WRITE);\n            SftpFileAttributes attributes = sftpFile.getAttributes();\n            attributes.setTimes(attributes.getAccessedTime(), new UnsignedInteger64(lastModified/1000));\n            connHandler.sftpSubsystem.setAttributes(sftpFile, attributes);\n            // Update local attribute copy\n            fileAttributes.setDate(lastModified);\n        } catch (SshException | SftpStatusException e) {\n            LOGGER.error(\"failed to change the modification date of \" + absPath, e);\n            throw new IOException(e);\n        } finally {\n            // Close SftpFile instance to release its handle\n            if (sftpFile != null) {\n                try {\n                    sftpFile.close();\n                } catch (SftpStatusException | SshException ignore) {}\n            }\n\n            // Release the lock on the ConnectionHandler\n            if (connHandler != null) {\n                connHandler.releaseLock();\n            }\n        }\n    }\n\n    /**\n     * Implementation note: for symlinks, returns the size of the link's target.\n     */\n    @Override\n    public long getSize() {\n        return ((SFTPFileAttributes)getCanonicalFile().getUnderlyingFileObject()).getSize();\n    }\n\t\n\t\n    @Override\n    public AbstractFile getParent() {\n        if(!parentValSet) {\n            FileURL parentFileURL = this.fileURL.getParent();\n            if (parentFileURL != null) {\n                parent = FileFactory.getFile(parentFileURL);\n                // Note: parent may be null if it can't be resolved\n            }\n\n            parentValSet = true;\n        }\n\t\t\n        return parent;\n    }\n\t\n\t\n    @Override\n    public void setParent(AbstractFile parent) {\n        this.parent = parent;\n        this.parentValSet = true;\n    }\n\t\n\t\n    /**\n     * Implementation note: for symlinks, returns the value of the link's target.\n     */\n    @Override\n    public boolean exists() {\n        return fileAttributes.exists();\n    }\n\n    /**\n     * Implementation note: for symlinks, returns the permissions of the link's target.\n     */\n    @Override\n    public FilePermissions getPermissions() {\n        return ((SFTPFileAttributes)getCanonicalFile().getUnderlyingFileObject()).getPermissions();\n    }\n\n    @Override\n    public PermissionBits getChangeablePermissions() {\n        return PermissionBits.FULL_PERMISSION_BITS;     // Full permission support (777 octal)\n    }\n\n    @Override\n    public void changePermission(int access, int permission, boolean enabled) throws IOException {\n        changePermissions(ByteUtils.setBit(getPermissions().getIntValue(), (permission << (access*3)), enabled));\n    }\n\n    @Override\n    public String getOwner() {\n        return fileAttributes.getOwner();\n    }\n\n    @Override\n    public boolean canGetOwner() {\n        return true;\n    }\n\n    @Override\n    public String getGroup() {\n        return fileAttributes.getGroup();\n    }\n\n    @Override\n    public boolean canGetGroup() {\n        return true;\n    }\n\n    /**\n     * Implementation note: for symlinks, returns the value of the link's target.\n     */\n    @Override\n    public boolean isDirectory() {\n        return ((SFTPFileAttributes)getCanonicalFile().getUnderlyingFileObject()).isDirectory();\n    }\n\t\n    @Override\n    public InputStream getInputStream() throws IOException {\n        return getInputStream(0);\n    }\n\n    @Override\n    public OutputStream getOutputStream() throws IOException {\n        return getOutputStream(false);\n    }\n\n    @Override\n    public OutputStream getAppendOutputStream() throws IOException {\n        return getOutputStream(true);\n    }\n\n    @Override\n    public RandomAccessInputStream getRandomAccessInputStream() throws IOException {\n        return new SFTPRandomAccessInputStream();\n    }\n\n    @Override\n    public void delete() throws IOException {\n        // Retrieve a ConnectionHandler and lock it\n        SFTPConnectionHandler connHandler = null;\n        try {\n            // Retrieve a ConnectionHandler and lock it\n            connHandler = (SFTPConnectionHandler)ConnectionPool.getConnectionHandler(CONN_HANDLER_FACTORY, fileURL, true);\n\n            // Makes sure the connection is started, if not starts it\n            connHandler.checkConnection();\n\n            try {\n                if (isDirectory()) {\n                    connHandler.sftpSubsystem.removeDirectory(absPath);\n                } else {\n                    connHandler.sftpSubsystem.removeFile(absPath);\n                }\n            } catch (SftpStatusException | SshException e) {\n                e.printStackTrace();\n                throw new IOException(e);\n            }\n\n            // Update local attributes\n            fileAttributes.setExists(false);\n            fileAttributes.setDirectory(false);\n            fileAttributes.setSymlink(false);\n            fileAttributes.setSize(0);\n        } finally {\n            // Release the lock on the ConnectionHandler if the OutputStream could not be created\n            if (connHandler != null) {\n                connHandler.releaseLock();\n            }\n        }\n    }\n\n\n    @Override\n    public AbstractFile[] ls() throws IOException {\n        SftpFile[] files = getSftpFiles();\n        int nbFiles = files.length;\n\n        // File doesn't exist, return an empty file array\n        if (nbFiles == 0) {\n            return new AbstractFile[] {};\n        }\n\n        AbstractFile[] children = new AbstractFile[nbFiles];\n\n        int fileCount = 0;\n        String parentPath = fileURL.getPath();\n        if (!parentPath.endsWith(SEPARATOR)) {\n            parentPath += SEPARATOR;\n        }\n\n        // Fill AbstractFile array and discard '.' and '..' files\n        for (SftpFile file : files) {\n            String filename = file.getFilename();\n            // Discard '.' and '..' files, dunno why these are returned\n            if (filename.equals(\".\") || filename.equals(\"..\")) {\n                continue;\n            }\n\n            FileURL childURL = (FileURL) fileURL.clone();\n            childURL.setPath(parentPath + filename);\n\n            try {\n                children[fileCount++] = FileFactory.getFile(childURL, this, new SFTPFileAttributes(childURL, file.getAttributes()));\n            } catch (SftpStatusException | SshException e) {\n                e.printStackTrace();\n                throw new IOException(e);\n            }\n        }\n\n        // create new array of the exact file count\n        if (fileCount < nbFiles) {\n            AbstractFile[] newChildren = new AbstractFile[fileCount];\n            System.arraycopy(children, 0, newChildren, 0, fileCount);\n            return newChildren;\n        }\n\n        return children;\n    }\n\n    private SftpFile[] getSftpFiles() throws IOException {\n        // Retrieve a ConnectionHandler and lock it\n        SFTPConnectionHandler connHandler = (SFTPConnectionHandler)ConnectionPool.getConnectionHandler(CONN_HANDLER_FACTORY, fileURL, true);\n        SftpFile[] files;\n        try {\n            connHandler.checkConnection();  // Makes sure the connection is started, if not starts it\n    //        connHandler.sftpSubsystem.listChildren(file, files);        // Modified J2SSH method to remove the 100 files limitation\n            // Use SftpClient.ls() rather than SftpChannel.listChildren() as it seems to be working better\n            files = connHandler.sftpClient.ls(absPath);\n        } catch (SftpStatusException | SshException e) {\n            e.printStackTrace();\n            throw new IOException(e);\n        } finally {\n            // Release the lock on the ConnectionHandler\n            connHandler.releaseLock();\n        }\n        return files;\n    }\n\n\n    @Override\n    public void mkdir() throws IOException {\n        // Retrieve a ConnectionHandler and lock it\n        SFTPConnectionHandler connHandler = (SFTPConnectionHandler)ConnectionPool.getConnectionHandler(CONN_HANDLER_FACTORY, fileURL, true);\n        try {\n            // Makes sure the connection is started, if not starts it\n            connHandler.checkConnection();\n\n            // Note: this J2SSH method has been patched to set the permissions of the new directory to 0755 (rwxr-xr-x)\n            // instead of 0. This patches allows to avoid a 'change permissions' request (cf comment code hereunder).\n            connHandler.sftpSubsystem.makeDirectory(absPath);\n\n//            // Set new directory permissions to 755 octal (493 dec): \"rwxr-xr-x\"\n//            // Note: by default, permissions for files freshly created is 0 (not readable/writable/executable by anyone)!\n//            connHandler.sftpSubsystem.changePermissions(absPath, 493);\n\n            // Update local attributes\n            fileAttributes.setExists(true);\n            fileAttributes.setDirectory(true);\n            fileAttributes.setDate(System.currentTimeMillis());\n            fileAttributes.setSize(0);\n        } catch (SftpStatusException | SshException e) {\n            e.printStackTrace();\n            throw new IOException(e);\n        } finally {\n            // Release the lock on the ConnectionHandler\n            connHandler.releaseLock();\n        }\n    }\n\n    /**\n     * Implementation notes: server-to-server renaming will work if the destination file also uses the 'SFTP' scheme\n     * and is located on the same host.\n     */\n    @Override\n    public void renameTo(AbstractFile destFile) throws IOException {\n        // Throw an exception if the file cannot be renamed to the specified destination.\n        // Fail in situations where SFTPFile#renameTo() does not, for instance when the source and destination are the same.\n        checkRenamePrerequisites(destFile, true, false);\n\n        // Retrieve a ConnectionHandler and lock it\n        SFTPConnectionHandler connHandler = null;\n        try {\n            connHandler = (SFTPConnectionHandler)ConnectionPool.getConnectionHandler(CONN_HANDLER_FACTORY, fileURL, true);\n\n            // Makes sure the connection is started, if not starts it\n            connHandler.checkConnection();\n\n            // SftpClient#rename() throws an IOException if the destination exists (instead of overwriting the file)\n            if (destFile.exists()) {\n                destFile.delete();\n            }\n\n            // Will throw an IOException if the operation failed\n            try {\n                connHandler.sftpClient.rename(absPath, destFile.getURL().getPath());\n            } catch (SftpStatusException | SshException e) {\n                e.printStackTrace();\n                throw new IOException(e);\n            }\n\n            // Update destination file attributes by fetching them from the server\n            ((SFTPFileAttributes)destFile.getUnderlyingFileObject()).fetchAttributes();\n\n            // Update this file's attributes locally\n            fileAttributes.setExists(false);\n            fileAttributes.setDirectory(false);\n            fileAttributes.setSize(0);\n        } finally {\n            // Release the lock on the ConnectionHandler\n            if (connHandler != null) {\n                connHandler.releaseLock();\n            }\n        }\n    }\n\n    /**\n     * Returns a {@link com.mucommander.commons.file.impl.sftp.SFTPFile.SFTPFileAttributes} instance corresponding to this file.\n     */\n    @Override\n    public Object getUnderlyingFileObject() {\n        return fileAttributes;\n    }\n\n\n    // Unsupported file operations\n\n    /**\n     * Always throws an {@link UnsupportedFileOperationException}: random write access is not supported.\n     */\n    @Override\n    @UnsupportedFileOperation\n    public RandomAccessOutputStream getRandomAccessOutputStream() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.RANDOM_WRITE_FILE);\n    }\n\n    /**\n     * Always throws {@link UnsupportedFileOperationException} when called.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public void copyRemotelyTo(AbstractFile destFile) throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.COPY_REMOTELY);\n    }\n\n    /**\n     * Always throws {@link UnsupportedFileOperationException} when called.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public long getFreeSpace() throws UnsupportedFileOperationException {\n        // No way to retrieve this information with J2SSH\n        throw new UnsupportedFileOperationException(FileOperation.GET_FREE_SPACE);\n    }\n\n    /**\n     * Always throws {@link UnsupportedFileOperationException} when called.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public long getTotalSpace() throws UnsupportedFileOperationException {\n        // No way to retrieve this information with J2SSH\n        throw new UnsupportedFileOperationException(FileOperation.GET_TOTAL_SPACE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public short getReplication() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_REPLICATION);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public long getBlocksize() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_BLOCKSIZE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public void changeReplication(short replication) throws IOException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_REPLICATION);\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n\n    @Override\n    public void changePermissions(int permissions) throws IOException {\n        // Retrieve a ConnectionHandler and lock it\n        SFTPConnectionHandler connHandler = null;\n        try {\n            connHandler = (SFTPConnectionHandler)ConnectionPool.getConnectionHandler(CONN_HANDLER_FACTORY, fileURL, true);\n\n            // Makes sure the connection is started, if not starts it\n            connHandler.checkConnection();\n\n            connHandler.sftpSubsystem.changePermissions(absPath, permissions);\n            // Update local attribute copy\n            fileAttributes.setPermissions(new SimpleFilePermissions(permissions));\n        } catch (SftpStatusException | SshException e) {\n            e.printStackTrace();\n            throw new IOException(e);\n        } finally {\n            // Release the lock on the ConnectionHandler\n            if (connHandler != null) {\n                connHandler.releaseLock();\n            }\n        }\n    }\n\n    @Override\n    public InputStream getInputStream(long offset) throws IOException {\n        // Retrieve a ConnectionHandler and lock it\n        final SFTPConnectionHandler connHandler = (SFTPConnectionHandler)ConnectionPool.getConnectionHandler(CONN_HANDLER_FACTORY, fileURL, true);\n        try {\n            // Makes sure the connection is started, if not starts it\n            connHandler.checkConnection();\n\n            SftpFile sftpFile = connHandler.sftpSubsystem.openFile(absPath, SftpSubsystemChannel.OPEN_READ);\n\n            // Custom made constructor, not part of the official J2SSH API\n            return new SftpFileInputStream(sftpFile, offset) {\n\n                    @Override\n                    public void close() throws IOException {\n                        // SftpFileInputStream.close() closes the open SftpFile file handle\n                        super.close();\n                        // Release the lock on the ConnectionHandler\n                        connHandler.releaseLock();\n                }\n\n            };\n        } catch(IOException e) {\n            // Release the lock on the ConnectionHandler if the InputStream could not be created\n            connHandler.releaseLock();\n\n            // Re-throw IOException\n            throw e;\n        } catch (SshException | SftpStatusException e) {\n            e.printStackTrace();\n            throw new IOException(e);\n        }\n    }\n\n    @Override\n    public String getCanonicalPath() {\n        if (isSymlink()) {\n            // Check if there is a previous value that hasn't expired yet\n            if (canonicalPath != null && (System.currentTimeMillis() - canonicalPathFetchedTime < attributeCachingPeriod))\n                return canonicalPath;\n\n            SFTPConnectionHandler connHandler = null;\n            try {\n                // Retrieve a ConnectionHandler and lock it\n                connHandler = (SFTPConnectionHandler)ConnectionPool.getConnectionHandler(CONN_HANDLER_FACTORY, fileURL, true);\n\n                // Makes sure the connection is started, if not starts it\n                connHandler.checkConnection();\n\n                // getSymbolicLinkTarget returns the raw symlink target which can either be an absolute path or a\n                // relative path. If the path is relative preprend the absolute path of the symlink's parent folder.\n                String symlinkTargetPath = connHandler.sftpSubsystem.getSymbolicLinkTarget(fileURL.getPath());\n                if (!symlinkTargetPath.startsWith(\"/\")) {\n                    String parentPath = fileURL.getParent().getPath();\n                    if (!parentPath.endsWith(\"/\")) {\n                        parentPath += \"/\";\n                    }\n                    symlinkTargetPath = parentPath + symlinkTargetPath;\n                }\n\n                FileURL canonicalURL = (FileURL)fileURL.clone();\n                canonicalURL.setPath(symlinkTargetPath);\n\n                // Cache the value and return it until it expires\n                canonicalPath = canonicalURL.toString(false);\n                canonicalPathFetchedTime = System.currentTimeMillis();\n                return canonicalPath;\n            } catch(IOException | SftpStatusException | SshException e) {\n                // Simply continue and return the absolute path\n            } finally {\n                // Release the lock on the ConnectionHandler\n                if (connHandler != null) {\n                    connHandler.releaseLock();\n                }\n            }\n        }\n\n        // If this file is not a symlink, or the symlink target path could not be retrieved, return the absolute path\n        return getAbsolutePath();\n    }\n\n    /**\n     * If the SFTPFile is a symbolic link, this method returns the name of the file being pointed to by the symbolic link.\n     * @return The file pointed to by the symbolic link (null if the FTPFile is not a symbolic link).\n     */\n    public String getLink() {\n        if (!isSymlink()) {\n            return null;\n        }\n        String symlinkTargetPath;\n        SFTPConnectionHandler connHandler = null;\n        // Retrieve a ConnectionHandler and lock it\n        try {\n            connHandler = (SFTPConnectionHandler)ConnectionPool.getConnectionHandler(CONN_HANDLER_FACTORY, fileURL, true);\n            // Makes sure the connection is started, if not starts it\n            connHandler.checkConnection();\n            // getSymbolicLinkTarget returns the raw symlink target which can either be an absolute path or a\n            // relative path. If the path is relative preprend the absolute path of the symlink's parent folder.\n            symlinkTargetPath = connHandler.sftpSubsystem.getSymbolicLinkTarget(fileURL.getPath());\n\n        } catch (IOException | SftpStatusException | SshException e) {\n            symlinkTargetPath = null;\n            e.printStackTrace();\n        } finally {\n            // Release the lock on the ConnectionHandler\n            if (connHandler != null) {\n                connHandler.releaseLock();\n            }\n        }\n        return symlinkTargetPath;\n    }\n\n\n    ///////////////////\n    // Inner classes //\n    ///////////////////\n\n    /**\n     * SFTPFileAttributes provides getters and setters for SFTP file attributes. By extending\n     * <code>SyncedFileAttributes</code>, this class caches attributes for a  certain amount of time\n     * ({@link SFTPFile#attributeCachingPeriod}) after which a fresh value is retrieved from the server.\n     */\n    static class SFTPFileAttributes extends SyncedFileAttributes {\n\n        /** The URL pointing to the file whose attributes are cached by this class */\n        private final FileURL url;\n\n        /** True if the file is a symlink */\n        private boolean isSymlink;\n\n        // this constructor is called by SFTPFile public constructor\n        private SFTPFileAttributes(FileURL url) throws AuthException {\n            super(attributeCachingPeriod, false);       // no initial update\n\n            this.url = url;\n            setPermissions(FilePermissions.EMPTY_FILE_PERMISSIONS);\n\n            fetchAttributes();      // throws AuthException if no or bad credentials\n\n            updateExpirationDate(); // declare the attributes as 'fresh'\n        }\n\n        // this constructor is called by #ls()\n        private SFTPFileAttributes(FileURL url, SftpFileAttributes attrs) {\n            super(attributeCachingPeriod, false);   // no initial update\n\n            this.url = url;\n            setPermissions(FilePermissions.EMPTY_FILE_PERMISSIONS);\n\n            setAttributes(attrs);\n            setExists(true);\n\n            // Some information about this value:\n            // FileAttribute#isLink() returns a proper value only for FileAttributes instances that were returned by\n            // SftpFile#ls(). FileAttributes that are returned by SftpSubsystemClient#getAttributes(String) always\n            // return false for isLink().\n            // That means the value of isSymlink is not updated by fetchAttributes(), because if it was, isSymlink\n            // would be false after the first attributes update.\n            this.isSymlink = attrs.isLink();\n\n            updateExpirationDate(); // declare the attributes as 'fresh'\n        }\n\n        private void fetchAttributes() throws AuthException {\n            SFTPConnectionHandler connHandler = null;\n            try {\n                // Retrieve a ConnectionHandler and lock it\n                connHandler = (SFTPConnectionHandler)ConnectionPool.getConnectionHandler(SFTPFile.CONN_HANDLER_FACTORY, url, true);\n\n                // Makes sure the connection is started, if not starts it\n                connHandler.checkConnection();\n\n                // Retrieve the file attributes from the server. This will throws an IOException if the file doesn't\n                // exist on the server\n                // Note for symlinks: the FileAttributes returned by SftpSubsystemClient#getAttributes(String)\n                // returns the values of the symlink's target, not the symlink file itself. In other words: the size,\n                // date, isDirectory, isLink values are those of the linked file. This is not a problem, except for\n                // isLink because it makes impossible to detect changes in the isLink state. Changes should not happen\n                // very often, but still.\n                // Todo: try and fix for this in J2SSH\n                setAttributes(connHandler.sftpSubsystem.getAttributes(url.getPath()));\n                setExists(true);\n            } catch (IOException | SftpStatusException | SshException e) {\n                e.printStackTrace();\n                // File doesn't exist on the server\n                setExists(false);\n\n                // Rethrow AuthException\n                if (e instanceof AuthException) {\n                    throw (AuthException) e;\n                }\n            } finally {\n                // Release the lock on the ConnectionHandler\n                if (connHandler != null) {\n                    connHandler.releaseLock();\n                }\n            }\n        }\n\n        /**\n         * Sets the file attributes using the values contained in the specified J2SSH FileAttributes instance.\n         *\n         * @param attrs J2SSH FileAttributes instance that contains the values to use\n         */\n        private void setAttributes(SftpFileAttributes attrs) {\n            setDirectory(attrs.isDirectory());\n            setDate(attrs.getModifiedTime().longValue()*1000);\n            setSize(attrs.getSize().longValue());\n            setPermissions(new SimpleFilePermissions(\n               attrs.getPermissions().intValue() & PermissionBits.FULL_PERMISSION_INT\n            ));\n            setOwner(attrs.getUID());\n            setGroup(attrs.getGID());\n            setSymlink(isSymlink);\n        }\n\n        /**\n         * Increments the size attribute's value by the given number of bytes.\n         *\n         * @param increment number of bytes to add to the current size attribute's value\n         */\n        private void addToSize(long increment) {\n            setSize(getSize()+increment);\n        }\n\n        /**\n         * Returns <code>true</code> if the file is a symlink.\n         *\n         * @return <code>true</code> if the file is a symlink\n         */\n        private boolean isSymlink() {\n            checkForExpiration(false);\n\n            return isSymlink;\n        }\n\n        /**\n         * Sets whether the file is a symlink.\n         *\n         * @param isSymlink <code>true</code> if the file is a symlink\n         */\n        private void setSymlink(boolean isSymlink) {\n            this.isSymlink = isSymlink;\n        }\n\n\n        ////////////////////////////////////////////\n        // SyncedFileAttributes implementation //\n        ////////////////////////////////////////////\n\n        @Override\n        public void updateAttributes() {\n            try {\n                fetchAttributes();\n            } catch(Exception e) {        // AuthException\n                LOGGER.info(\"Failed to refresh attributes\", e);\n            }\n        }\n    }\n\n\n    /**\n     * SFTPRandomAccessInputStream extends RandomAccessInputStream to provide random read access to an SFTPFile.\n     */\n    private class SFTPRandomAccessInputStream extends RandomAccessInputStream {\n\n        private final SftpFileInputStreamEx in;\n\n        private SFTPRandomAccessInputStream() throws IOException {\n            try {\n                final SFTPConnectionHandler connHandler = (SFTPConnectionHandler)ConnectionPool.getConnectionHandler(CONN_HANDLER_FACTORY, fileURL, true);\n                    // Makes sure the connection is started, if not starts it\n                    connHandler.checkConnection();\n                SftpFile sftpFile = connHandler.sftpSubsystem.openFile(absPath, SftpSubsystemChannel.OPEN_READ);\n                this.in = new SftpFileInputStreamEx(sftpFile);//SftpFileInputStreamEx)getInputStream();\n            } catch (SftpStatusException | SshException e) {\n                e.printStackTrace();\n                throw new IOException(e);\n            }\n        }\n\n        @Override\n        public int read(byte[] b, int off, int len) throws IOException {\n            return in.read(b, off, len);\n        }\n\n        @Override\n        public int read() throws IOException {\n            return in.read();\n        }\n\n        public long getOffset() {\n            // Custom method, not part of the official J2SSH API\n            return in.getPosition();\n        }\n\n        public long getLength() {\n            return getSize();\n        }\n\n        public void seek(long offset) {\n            // Custom method, not part of the official J2SSH API\n            in.setPosition(offset);\n        }\n\n        @Override\n        public void close() throws IOException {\n            in.close();\n        }\n    }\n\n\n//    private class SFTPProcess extends AbstractProcess {\n//\n//        private boolean success;\n//        private SessionChannelClient sessionClient;\n//        private SFTPConnectionHandler connHandler;\n//\n//        private SFTPProcess(String tokens[]) throws IOException {\n//\n//            try {\n//                // Retrieve a ConnectionHandler and lock it\n//                connHandler = (SFTPConnectionHandler)ConnectionPool.getConnectionHandler(CONN_HANDLER_FACTORY, fileURL, true);\n//                // Makes sure the connection is started, if not starts it\n//                connHandler.checkConnection();\n//\n//                sessionClient = connHandler.sshClient.openSessionChannel();\n////                sessionClient.startShell();\n//\n////                success = sessionClient.executeCommand(\"cd \"+(isDirectory()?fileURL.getPath():fileURL.getParent().getPath()));\n////FileLogger.finest(\"commmand=\"+(\"cd \"+(isDirectory()?fileURL.getPath():fileURL.getParent().getPath()))+\" returned \"+success);\n//\n//                // Environment variables are refused by most servers for security reasons\n////                sessionClient.setEnvironmentVariable(\"cd\", isDirectory()?fileURL.getPath():fileURL.getParent().getPath());\n//\n//                // No way to set the current working directory:\n//                // 1/ when executing a single command:\n//                //  + environment variables are ignored by most server, so can't use PWD for that.\n//                //  + could send 'cd dir ; command' but it's not platform independant and prevents the command from being\n//                //    executed under Windows\n//                // 2/ when starting a shell, no problem to change the current working directory (cd dir\\n is sent before\n//                // the command), but there is no reliable way to detect the end of the command execution, as confirmed\n//                // by one of the J2SSH developers : http://sourceforge.net/forum/message.php?msg_id=1826569\n//\n//                // Concatenates all tokens to create the command string\n//                StringBuffer command = new StringBuffer();\n//                int nbTokens = tokens.length;\n//                for(int i=0; i<nbTokens; i++) {\n//                    command.append(tokens[i]);\n//                    if(i!=nbTokens-1)\n//                        command.append(\" \");\n//                }\n//\n//                success = sessionClient.executeCommand(command.toString());\n//                FileLogger.finest(\"commmand=\"+command+\" returned \"+success);\n//            }\n//            catch(IOException e) {\n//                // Release the lock on the ConnectionHandler\n//                connHandler.releaseLock();\n//\n//                sessionClient.close();\n//\n//                // Re-throw exception\n//                throw e;\n//            }\n//        }\n//\n//        public boolean usesMergedStreams() {\n//            return false;\n//        }\n//\n//        public int waitFor() throws InterruptedException, IOException {\n//            return sessionClient.getExitCode().intValue();\n//        }\n//\n//        protected void destroyProcess() throws IOException {\n//            // Release the lock on the ConnectionHandler\n//            connHandler.releaseLock();\n//\n//            sessionClient.close();\n//        }\n//\n//        public int exitValue() {\n//            return sessionClient.getExitCode().intValue();\n//        }\n//\n//        public OutputStream getOutputStream() throws IOException {\n//            return sessionClient.getOutputStream();\n//        }\n//\n//        public InputStream getInputStream() throws IOException {\n//            return sessionClient.getInputStream();\n//        }\n//\n//        public InputStream getErrorStream() throws IOException {\n//            return sessionClient.getStderrInputStream();\n//        }\n//    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sftp/SFTPProtocolProvider.java",
    "content": "package com.mucommander.commons.file.impl.sftp;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.ProtocolProvider;\n\nimport java.io.IOException;\n\n/**\n * This class is the provider for the FTP filesystem implemented by {@link com.mucommander.commons.file.impl.ftp.FTPFile}.\n *\n * @author Nicolas Rinaudo, Maxence Bernard\n * @see com.mucommander.commons.file.impl.sftp.SFTPFile\n */\npublic class SFTPProtocolProvider implements ProtocolProvider {\n\n    public AbstractFile getFile(FileURL url, Object... instantiationParams) throws IOException {\n        return instantiationParams.length == 0 ?\n                new SFTPFile(url) : new SFTPFile(url, (SFTPFile.SFTPFileAttributes)instantiationParams[0]);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/sftp/package.html",
    "content": "<body>\n  Provides an implementation of the SFTP protocol.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/smb/SMBFile.java",
    "content": "package com.mucommander.commons.file.impl.smb;\n\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.file.filter.FilenameFilter;\nimport com.mucommander.commons.io.RandomAccessInputStream;\nimport com.mucommander.commons.io.RandomAccessOutputStream;\nimport jcifs.smb.*;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.MalformedURLException;\n\n\n/**\n * SMBFile provides access to files located on an SMB/CIFS server.\n * <p>\n * The associated {@link FileURL} scheme is {@link FileProtocols#SMB}. The host part of the URL designates the\n * SMB server. Credentials are specified in the login and password parts. The path separator is '/'.\n * <p>\n * Here are a few examples of valid SMB URLs:\n * <code>\n * smb://server/path/to/file<br>\n * smb://domain;username:password@server/path/to/file<br>\n * smb://workgroup/<br>\n * </code>\n * <p>\n * The special 'smb://' URL represents the SMB root and lists all workgroups that are available on the network,\n * akin to Windows' network neighborhood.\n * <p>\n * Access to SMB files is provided by the <code>jCIFS</code> library distributed under the LGPL license.\n * The {@link #getUnderlyingFileObject()} method allows to retrieve a <code>jcifs.smb.SmbFile</code> instance\n * corresponding to this <code>SMBFile</code>.\n *\n * @author Maxence Bernard\n */\n public class SMBFile extends ProtocolFile {\n    private static final Logger LOGGER = LoggerFactory.getLogger(SMBFile.class);\n\n    private SmbFile file;\n    private FilePermissions permissions;\n\n    private AbstractFile parent;\n    private boolean parentValSet;\n\n    /** Bit mask that indicates which permissions can be changed. Only the 'write' permission for 'user' access can\n     *  be changed. */\n    private final static PermissionBits CHANGEABLE_PERMISSIONS = new GroupedPermissionBits(128);   // -w------- (200 octal)\n\n    \n    SMBFile(FileURL fileURL) throws IOException {\n        this(fileURL, null);\n    }\n\n    SMBFile(FileURL fileURL, SmbFile smbFile) throws IOException {\n        super(fileURL);\n\n        if (!fileURL.containsCredentials()) {\n            throw new AuthException(fileURL, \"Authentication required\");\n        }\n\n        if (smbFile == null) {\n            while(true) {\n                file = createSmbFile(fileURL);\n\n                // The following test comes at a cost, so it's only used by the public constructor, SmbFile instances\n                // created by this class are considered OK.\n                try {\n                    // SmbFile requires a trailing slash for directories otherwise listFiles() will throw an SmbException.\n                    // As we cannot guarantee that the path will contain a trailing slash for directories, we test if the\n                    // SmbFile is a directory and if it doesn't contain a trailing slash, we create a new SmbFile with\n                    // a trailing slash.\n                    // SmbFile.isDirectory() will throw an SmbAuthException if access to the file requires different credentials.\n                    if(file.isDirectory() && !getURL().getPath().endsWith(\"/\")) {\n                        // Add trailing slash and loop to create a new SmbFile\n                        fileURL.setPath(fileURL.getPath()+'/');\n                        continue;\n                    }\n\n                    break;\n                } catch(SmbException e) {\n                    // SmbFile.isDirectory() threw an exception. We distinguish 2 types of SmbException:\n                    // 1) SmbAuthException, caused by a credentials problem -> turn it into an AuthException and throw it\n                    // 2) any other SmbException -> this may happen if access to the file was denied for example, this\n                    //    shouldn't prevent this SMBFile from being created.\n\n                    // 1) create an AuthException out of the SmbAuthException and throw it\n                    if(e instanceof SmbAuthException)\n                        throw new AuthException(fileURL, e.getMessage());\n\n                    // 2) Swallow the exception to let this SMBFile be created\n                    break;\n                }\n            }\n        } else {                      // The private constructor was called directly\n            file = smbFile;\n        }\n\n        permissions = new SMBFilePermissions(file);\n    }\n\n\n    /**\n     * Creates and returns a <code>jcifs.smb.SmbFile</code> for the given location. The credentials contained by\n     * the {@link FileURL} (if any) are passed along to the <code>SmbFile</code>.\n     *\n     * @param url the location to the SmbFile file to create\n     * @return an SmbFile corresponding to the given location\n     * @throws MalformedURLException if an error occurred while creating the SmbFile instance\n     */\n    private static SmbFile createSmbFile(FileURL url) throws MalformedURLException {\n        Credentials credentials = url.getCredentials();\n        if (credentials == null)\n            return new SmbFile(url.toString(false));\n\n        // Extract the domain (if any) from the username\n        String login = credentials.getLogin();\n        String domain;\n        int domainStart = login.indexOf(\";\");\n        if (domainStart != -1) {\n            domain = login.substring(0, domainStart);\n            login = login.substring(domainStart+1);\n        } else {\n            domain = null;\n        }\n\n        // A NtlmPasswordAuthentication is created from the FileURL credentials and passed to a specific SmbFile constructor.\n        // The reason for doing this rather than using the SmbFile(String) constructor is that SmbFile uses java.net.URL\n        // for the URL parsing which is unable to properly parse urls where the password contains a '@' character,\n        // such as smb://user:p@ssword@host/path . \n        return new SmbFile(url.toString(false), new NtlmPasswordAuthentication(domain, login, credentials.getPassword()));\n    }\n\n\n    /**\n     * Background information: <code>jcifs.smb.SmbFile</code> is a tad cumbersome to work with because it requires its\n     * file path to end with '/' when the file is a directory and vice-versa.\n     * This method ensures that the path of the current <code>jcifs.smb.SmbFile</code> instance matches the\n     * <code>directory</code> argument and if not, recreates it with the proper path.\n     *\n     * @param directory true if the current <code>jcifs.smb.SmbFile</code> designates a directory\n     */\n    private void checkSmbFile(boolean directory) {\n        try {\n            String path = file.getURL().getPath();\n            boolean endsWithSeparator = path.endsWith(\"/\");\n\n            if (directory) {\n                if (!endsWithSeparator) {\n                    fileURL.setPath(path+\"/\");\n                    file = createSmbFile(fileURL);\n                }\n            } else {\n                if (endsWithSeparator) {\n                    fileURL.setPath(removeTrailingSeparator(path));\n                    file = createSmbFile(fileURL);\n                }\n            }\n        } catch(MalformedURLException e) {\n            // This should never happen. If some reason wicked reason it ever did, SmbFile would just not be changed.\n        }\n    }\n\n    /**\n     * Sets the time period during which attributes values (e.g. isDirectory, last modified, ...) are cached by\n     * jcifs.smb.SmbFile. The higher this value, the lower the number of network requests but also the longer it takes\n     * before those attributes can be refreshed.\n     *\n     * @param period time period during which attributes values are cached, in milliseconds\n     */\n    static void setAttributeCachingPeriod(long period) {\n        jcifs.Config.setProperty(\"jcifs.smb.client.attrExpirationPeriod\", \"\"+period);\n    }\n\n\n    /////////////////////////////////////////\n    // AbstractFile methods implementation //\n    /////////////////////////////////////////\n\n    @Override\n    public long getLastModifiedDate() {\n        try {\n            return file.lastModified();\n        } catch(SmbException e) {\n            return 0;\n        }\n    }\n\n    @Override\n    public void setLastModifiedDate(long lastModified) throws IOException {\n        file.setLastModified(lastModified);\n    }\n\n    @Override\n    public long getSize() {\n        try {\n            return file.length();\n        } catch(SmbException e) {\n            return 0;\n        }\n    }\n\n    @Override\n    public AbstractFile getParent() {\n        if (!parentValSet) {\n            FileURL parentURL = fileURL.getParent();\n            if (parentURL!=null) {\n                parent = FileFactory.getFile(parentURL);\n                // Note: parent may be null if it can't be resolved\n            }\n            // Note: do not make the special smb:// file a parent of smb://host/, this would cause parent unit tests to fail\n\n            parentValSet = true;\n        }\n\n        return parent;\n    }\n\n    @Override\n    public void setParent(AbstractFile parent) {\n        this.parent = parent;\n        this.parentValSet = true;\n    }\n\n    @Override\n    public boolean exists() {\n        // Unlike java.io.File, SmbFile.exists() can throw an SmbException\n        try {\n            return file.exists();\n        } catch(IOException e) {\n            LOGGER.info(\"Exception caught while calling SmbFile#exists(): \" +   e.getMessage());\n\n            return e instanceof SmbAuthException;\n        }\n    }\n\n    @Override\n    public FilePermissions getPermissions() {\n        return permissions;\n    }\n\n    @Override\n    public PermissionBits getChangeablePermissions() {\n        return CHANGEABLE_PERMISSIONS;\n    }\n\n    @Override\n    public void changePermission(int access, int permission, boolean enabled) throws IOException {\n        if (access!=USER_ACCESS || permission!=WRITE_PERMISSION) {\n            throw new IOException();\n        }\n\n        if (enabled) {\n            file.setReadWrite();\n        } else {\n            file.setReadOnly();\n        }\n    }\n\n    /**\n     * Always returns <code>null</code>, this information is not available unfortunately.\n     */\n    @Override\n    public String getOwner() {\n        return null;\n    }\n\n    /**\n     * Always returns <code>false</code>, this information is not available unfortunately.\n     */\n    @Override\n    public boolean canGetOwner() {\n        return false;\n    }\n\n    /**\n     * Always returns <code>null</code>, this information is not available unfortunately.\n     */\n    @Override\n    public String getGroup() {\n        return null;\n    }\n\n    /**\n     * Always returns <code>false</code>, this information is not available unfortunately.\n     */\n    @Override\n    public boolean canGetGroup() {\n        return false;\n    }\n\n    @Override\n    public boolean isDirectory() {\n        try {\n            return file.isDirectory();\n        } catch(SmbException e) {\n            return false;\n        }\n    }\n\n    @Override\n    public boolean isSymlink() {\n        // Symlinks are not supported by jCIFS (or maybe by CIFS/SMB?)\n        return false;\n    }\n\n    @Override\n    public boolean isSystem() {\n        return false;\n    }\n\n    @Override\n    public InputStream getInputStream() throws IOException {\n        return new SmbFileInputStream(file);\n    }\n\n    @Override\n    public OutputStream getOutputStream() throws IOException {\n        return new SmbFileOutputStream(file, false);\n    }\n\n    @Override\n    public OutputStream getAppendOutputStream() throws IOException {\n        return new SmbFileOutputStream(file, true);\n    }\n\n    @Override\n    public RandomAccessInputStream getRandomAccessInputStream() throws IOException {\n        // This needs to be checked explicitly (SmbRandomAccessFile can be created even if the file does not exist)\n        if(!exists())\n            throw new IOException();\n\n//        // Explicitly allow the file to be read/write/delete by another random access file while this one is open\n//        return new SMBRandomAccessInputStream(new SmbRandomAccessFile(fileURL.toString(true), \"r\", SmbFile.FILE_SHARE_READ | SmbFile.FILE_SHARE_WRITE | SmbFile.FILE_SHARE_DELETE));\n        return new SMBRandomAccessInputStream(new SmbRandomAccessFile(file, \"r\"));\n    }\n\n    @Override\n    public RandomAccessOutputStream getRandomAccessOutputStream() throws IOException {\n//        // Explicitly allow the file to be read/write/delete by another random access file while this one is open\n//        return new SMBRandomAccessOutputStream(new SmbRandomAccessFile(fileURL.toString(true), \"rw\", SmbFile.FILE_SHARE_READ | SmbFile.FILE_SHARE_WRITE | SmbFile.FILE_SHARE_DELETE));\n        return new SMBRandomAccessOutputStream(new SmbRandomAccessFile(file, \"rw\"));\n    }\n\n    @Override\n    public void delete() throws IOException {\n        file.delete();\n        checkSmbFile(false);\n    }\n\n    @Override\n    public AbstractFile[] ls() throws IOException {\n        return ls(null);\n    }\n\n    @Override\n    public void mkdir() throws IOException {\n        // Ensure that the jcifs.smb.SmbFile's path ends with a '/' otherwise it will throw an exception\n        checkSmbFile(true);\n\n        // Note: unlike java.io.File.mkdir(), SmbFile does not return a boolean value\n        // to indicate if the folder could be created\n        file.mkdir();\n    }\n\n    @Override\n    public void copyRemotelyTo(AbstractFile destFile) throws IOException {\n        // Throw an exception if the file cannot be renamed to the specified destination.\n        // This method fails in situations where SmbFile#copyTo() doesn't, for instance:\n        // - when the destination file exists (the destination is simply overwritten)\n        // - when the source file doesn't exist\n        checkCopyRemotelyPrerequisites(destFile, false, false);\n\n        // Reuse the destination SmbFile instance\n        SmbFile destSmbFile = ((SMBFile)destFile).file;\n\n        // Remotely copy the file\n        file.copyTo(destSmbFile);\n\n        // Ensure that the destination jcifs.smb.SmbFile's path is consistent with its new directory/non-directory state\n        ((SMBFile)destFile).checkSmbFile(file.isDirectory());\n    }\n\n    /**\n     * Implementation notes: server-to-server renaming will work if the destination file also uses the 'SMB' scheme.\n     * Hosts do not necessarily have to be the same for this operation to succeed.\n     */\n    @Override\n    public void renameTo(AbstractFile destFile) throws IOException {\n        // Throw an exception if the file cannot be renamed to the specified destination.\n        // This method fails in situations where SFTPFile#renameTo() doesn't, for instance:\n        // - when the source and destination are the same\n        // - when the source file doesn't exist\n        checkRenamePrerequisites(destFile, true, true);\n\n        // Attempt to move the file using jcifs.smb.SmbFile#renameTo.\n\n        boolean isDirectory = file.isDirectory();\n\n//        // SmbFile#renameTo() throws an IOException if the destination exists (instead of overwriting the file)\n//        if(destFile.exists())\n//            destFile.delete();\n\n        // Rename the file\n        file.renameTo(((SMBFile)destFile).file);\n\n        // Ensure that the destination jcifs.smb.SmbFile's path is consistent with its new directory/non-directory state\n        ((SMBFile)destFile).checkSmbFile(isDirectory);\n    }\n\n    @Override\n    public long getFreeSpace() throws IOException {\n        return file.getDiskFreeSpace();\n    }\n\n    /**\n     * Always throws {@link UnsupportedFileOperationException} when called.\n     *\n     * @throws UnsupportedFileOperationException always\n     */\n    @Override\n    @UnsupportedFileOperation\n    public long getTotalSpace() throws UnsupportedFileOperationException {\n        // No way to retrieve this information with jCIFS\n        throw new UnsupportedFileOperationException(FileOperation.GET_TOTAL_SPACE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public short getReplication() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_REPLICATION);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public long getBlocksize() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_BLOCKSIZE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public void changeReplication(short replication) throws IOException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_REPLICATION);\n    }\n\n    /**\n     * Returns a <code>jcifs.smb.SmbFile</code> instance corresponding to this file.\n     */\n    @Override\n    public Object getUnderlyingFileObject() {\n        return file;\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    @Override\n    public AbstractFile[] ls(FilenameFilter filenameFilter) throws IOException {\n        try {\n            SmbFile smbFiles[] = file.listFiles(filenameFilter==null?null:new SMBFilenameFilter(filenameFilter));\n\n            if(smbFiles==null)\n                throw new IOException();\n\n            // Count the number of files to exclude: excluded files are those that are not file share/ not browsable\n            // (Printers, named pipes, comm ports)\n            int nbSmbFiles = smbFiles.length;\n            int nbSmbFilesToExclude = 0;\n            int smbFileType;\n            for (SmbFile smbFile1 : smbFiles) {\n                smbFileType = smbFile1.getType();\n                if (smbFileType == SmbFile.TYPE_PRINTER || smbFileType == SmbFile.TYPE_NAMED_PIPE || smbFileType == SmbFile.TYPE_COMM)\n                    nbSmbFilesToExclude++;\n            }\n\n            // create SMBFile by using SmbFile instance and sharing parent instance among children\n            AbstractFile children[] = new AbstractFile[nbSmbFiles-nbSmbFilesToExclude];\n            FileURL childURL;\n            SmbFile smbFile;\n            int currentIndex = 0;\n\n            for (SmbFile smbFile1 : smbFiles) {\n                smbFile = smbFile1;\n                smbFileType = smbFile.getType();\n                if (smbFileType == SmbFile.TYPE_PRINTER || smbFileType == SmbFile.TYPE_NAMED_PIPE || smbFileType == SmbFile.TYPE_COMM)\n                    continue;\n\n                // Note: properties and credentials are cloned for every children's url\n                childURL = (FileURL) fileURL.clone();\n                childURL.setHost(smbFile.getServer());\n                childURL.setPath(smbFile.getURL().getPath());\n\n                // Use SMBFile private constructor to recycle the SmbFile instance\n                children[currentIndex++] = FileFactory.getFile(childURL, this, smbFile);\n            }\n\n            return children;\n        }\n        catch(SmbAuthException e) {\n            throw new AuthException(fileURL, e.getMessage());\n        }\n    }\n\n    @Override\n    public boolean isHidden() {\n        try {\n            return file.isHidden();\n        }\n        catch(SmbException e) {\n            return false;\n        }\n    }\n\n\n    @Override\n    public boolean equalsCanonical(Object f) {\n        if(!(f instanceof SMBFile))\n            return super.equalsCanonical(f);\t\t// could be equal to an AbstractArchiveFile\n\n        // SmbFile's equals method is just perfect: compares canonical paths\n        // and IP addresses\n        return file.equals(((SMBFile)f).file);\n    }\n\n\n    ///////////////////\n    // Inner classes //\n    ///////////////////\n\n    /**\n     * SMBRandomAccessInputStream extends RandomAccessInputStream to provide random read access to an SMBFile.\n     */\n    public static class SMBRandomAccessInputStream extends RandomAccessInputStream {\n\n        private SmbRandomAccessFile raf;\n\n        public SMBRandomAccessInputStream(SmbRandomAccessFile raf) {\n            this.raf = raf;\n        }\n\n        @Override\n        public int read() throws IOException {\n            return raf.read();\n        }\n\n        @Override\n        public int read(byte b[], int off, int len) throws IOException {\n            return raf.read(b, off, len);\n        }\n\n        @Override\n        public void close() throws IOException {\n            raf.close();\n        }\n\n        public long getOffset() throws IOException {\n            return raf.getFilePointer();\n        }\n\n        public long getLength() throws IOException {\n            return raf.length();\n        }\n\n        public void seek(long offset) throws IOException {\n            raf.seek(offset);\n        }\n    }\n\n    /**\n     * SMBRandomAccessOutputStream extends RandomAccessOutputStream to provide random write access to an SMBFile.\n     */\n    public static class SMBRandomAccessOutputStream extends RandomAccessOutputStream {\n\n        private SmbRandomAccessFile raf;\n\n        public SMBRandomAccessOutputStream(SmbRandomAccessFile raf) {\n            this.raf = raf;\n        }\n\n        @Override\n        public void write(int i) throws IOException {\n            raf.write(i);\n        }\n\n        @Override\n        public void write(byte b[]) throws IOException {\n            raf.write(b);\n        }\n\n        @Override\n        public void write(byte b[], int off, int len) throws IOException {\n            raf.write(b, off, len);\n        }\n\n        @Override\n        public void close() throws IOException {\n            raf.close();\n        }\n\n        public long getOffset() throws IOException {\n            return raf.getFilePointer();\n        }\n\n        public long getLength() throws IOException {\n            return raf.length();\n        }\n\n        public void seek(long offset) throws IOException {\n            raf.seek(offset);\n        }\n\n        @Override\n        public void setLength(long newLength) throws IOException {\n            raf.setLength(newLength);\n\n            // jCIFS doesn't automatically position the offset to the end of the file when it is truncated.\n            // We have to do it ourselves to honour this method's contract.   \n            if(getOffset()>newLength)\n                raf.seek(newLength);\n        }\n    }\n\n\n    /**\n     * A Permissions implementation for SMBFile.\n     */\n    private static class SMBFilePermissions extends IndividualPermissionBits implements FilePermissions {\n\n        private SmbFile file;\n\n        private final static PermissionBits MASK = new GroupedPermissionBits(384);  // rw------- (300 octal)\n\n        public SMBFilePermissions(SmbFile file) {\n            this.file = file;\n        }\n\n        public boolean getBitValue(int access, int type) {\n            if(access!=USER_ACCESS)\n                return false;\n\n            try {\n                if(type==READ_PERMISSION)\n                    return file.canRead();\n                else if(type==WRITE_PERMISSION)\n                    return file.canWrite();\n                else\n                    return false;\n            }\n            // Unlike java.io.File, SmbFile#canRead() and SmbFile#canWrite() can throw an SmbException\n            catch(SmbException e) {\n                return false;\n            }\n        }\n\n        public PermissionBits getMask() {\n            return MASK;\n        }\n    }\n\n\n    /**\n     * Turns a {@link FilenameFilter} into a {@link jcifs.smb.SmbFilenameFilter}.\n     */\n    private static class SMBFilenameFilter implements jcifs.smb.SmbFilenameFilter {\n\n        private FilenameFilter filter;\n\n        private SMBFilenameFilter(FilenameFilter filter) {\n            this.filter = filter;\n        }\n\n\n        ////////////////////////////////////////////////\n        // jicfs.smb.SmbFilenameFilter implementation //\n        ////////////////////////////////////////////////\n\n        public boolean accept(SmbFile dir, String name) {\n            return filter.accept(name);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/smb/SMBProtocolProvider.java",
    "content": "package com.mucommander.commons.file.impl.smb;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.ProtocolProvider;\nimport jcifs.smb.SmbFile;\n\nimport java.io.IOException;\n\n/**\n * This class is the provider for the SMB filesystem implemented by {@link com.mucommander.commons.file.impl.smb.SMBFile}.\n *\n * @author Nicolas Rinaudo, Maxence Bernard\n * @see com.mucommander.commons.file.impl.smb.SMBFile\n */\npublic class SMBProtocolProvider implements ProtocolProvider {\n\n    static {\n        // Silence jCIFS's output if not in debug mode\n        // Quote from jCIFS's documentation : \"0 - No log messages are printed -- not even crticial exceptions.\"\n        System.setProperty(\"jcifs.util.loglevel\", \"0\");\n\n        // Lower the timeout values\n\n        // \"The time period in milliseconds that the client will wait for a response to a request from the server.\n        // The default value is 30000.\"\n        System.setProperty(\"jcifs.smb.client.responseTimeout\", \"10000\");\n\n        // \"To prevent the client from holding server resources unnecessarily, sockets are closed after this time period\n        // if there is no activity. This time is specified in milliseconds. The default is 35000.\"\n        System.setProperty(\"jcifs.smb.client.soTimeout\", \"15000\");\n\n        // Leaving this option enabled has a serious impact on performance (observed with jCIFS 1.2.25).\n        // \"If this property is true, domain based DFS referrals will be disabled. The default value is false.\n        // This property can be important in non-domain environments where domain-based DFS referrals that normally run\n        // when JCIFS first tries to resolve a path would timeout causing a long startup delay (e.g. running JCIFS only\n        // on the local machine without a network like on a laptop).\"\n        System.setProperty(\"jcifs.smb.client.dfs.disabled\", \"true\");\n    }\n\n\n    /**\n     * Sets the authentication protocol to use when connecting to SMB servers. This configuration method must be called\n     * before {@link SMBFile} is first instantiated ; calling it after that will have no effect.\n     * <p>\n     * This configuration option is mapped onto jCIFS's <code>jcifs.smb.lmCompatibility</code> client property.\n     * jCIFS's default will be used if this method is not called.\n     * <p>\n     * Here's a list of allowed values ; refer to JCIFS's documentation for more information:\n     * <dl>\n     *   <dt>0,1</dt><dd>Sends LM and NTLM responses</dd>\n     *   <dt>2</dt><dd>Sends only the NTLM response. This is more secure than Levels 0 and 1, because it eliminates the\n     * cryptographically-weak LM response</dd>\n     *   <dt>3,4,5</dt><dd>Sends LMv2 and NTLMv2 data. NTLMv2 session security is also negotiated if the server supports\n     * it. This is the default behavior (in 1.3.0 or later)</dd>\n     * </dl>\n     *\n     * @param value one of the allowed values, refer to JCIFS's documentation for more information.\n     */\n    public static void setSmbLmCompatibility(int value) {\n        // Since jCIFS 1.3.0, the default is to use NTLM v2 authentication (value=3).\n        // Note: jCIFS configuration is unfortunately global and cannot be set per connection.\n        System.setProperty(\"jcifs.smb.lmCompatibility\", Integer.toString(value));\n    }\n\n    /**\n     * Sets whether 'extended security' should be used when connecting to SMB servers. This configuration method\n     * must be called before {@link SMBFile} is first instantiated ; calling it after that will have no effect.\n     * <p>\n     * This configuration option is mapped onto jCIFS's <code>jcifs.smb.client.useExtendedSecurity</code> client\n     * property. jCIFS's default value will be used if this method is not called, which is <code>true</code> since\n     * jCIFS 1.3.0.\n     *\n     * @param value <code>true</code> to enable extended security, refer to JCIFS's documentation for more information.\n     */\n    public static void setExtendedSecurity(boolean value) {\n        // Since jCIFS 1.3.0, extended security is turned on by default, which causes issues when connecting to older\n        // SMB servers such as Samba 3.0.\n        // Note jCIFS configuration is unfortunately global and cannot be set per connection.\n        System.setProperty(\"jcifs.smb.client.useExtendedSecurity\", Boolean.toString(value));\n    }\n\n\n    @Override\n    public AbstractFile getFile(FileURL url, Object... instantiationParams) throws IOException {\n        return instantiationParams.length == 0\n            ? new SMBFile(url)\n            : new SMBFile(url, (SmbFile)instantiationParams[0]);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/smb/package.html",
    "content": "<body>\n  Provides an implementation of the SMB protocol.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/tar/TarArchiveFile.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.commons.file.impl.tar;\n\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.file.impl.tar.provider.TarEntry;\nimport com.mucommander.commons.file.impl.tar.provider.TarInputStream;\nimport com.mucommander.commons.io.StreamUtils;\nimport com.mucommander.commons.util.StringUtils;\nimport org.apache.hadoop.io.compress.bzip2.CBZip2InputStream;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.BufferedInputStream;\nimport java.io.FilterInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.zip.GZIPInputStream;\n\n\n/**\n * TarArchiveFile provides read-only access to archives in the Tar/Tgz format.\n *\n * <p>The actual decompression work is performed by the <code>Apache Ant</code> library under the terms of the\n * Apache Software License.\n *\n * @see com.mucommander.commons.file.impl.tar.TarFormatProvider\n * @author Maxence Bernard\n */\npublic class TarArchiveFile extends AbstractROArchiveFile {\n    private static final Logger LOGGER = LoggerFactory.getLogger(TarArchiveFile.class);\n\n    /**\n     * Creates a TarArchiveFile on of the given file.\n     *\n     * @param file the underlying archive file\n     */\n    public TarArchiveFile(AbstractFile file) {\n        super(file);\n    }\n\n\n    /**\n     * Returns a TarInputStream which can be used to read TAR entries.\n     *\n     * @param entryOffset offset from the start of the archive to an entry. Must be a multiple of recordSize, or\n     * <code>0</code> to start at the first entry.\n     * @return a TarInputStream which can be used to read TAR entries\n     * @throws IOException if an error occurred while create the stream\n     */\n    private TarInputStream createTarStream(long entryOffset) throws IOException {\n        InputStream in = file.getInputStream();\n\n        String name = getName();\n            // Gzip-compressed file\n        if (StringUtils.endsWithIgnoreCase(name, \"tgz\") || StringUtils.endsWithIgnoreCase(name, \"tar.gz\"))\n                // Note: this will fail for gz/tgz entries inside a tar file (IOException: Not in GZIP format),\n                // why is a complete mystery: the gz/tgz entry can be extracted and then properly browsed\n            in = new GZIPInputStream(in);\n\n        // Bzip2-compressed file\n        else if (StringUtils.endsWithIgnoreCase(name, \"tbz2\") || StringUtils.endsWithIgnoreCase(name, \"tar.bz2\")) {\n            try {\n                // Skips the 2 magic bytes 'BZ', as required by CBZip2InputStream. Quoted from CBZip2InputStream's Javadoc:\n                // \"Although BZip2 headers are marked with the magic 'Bz'. this constructor expects the next byte in the\n                // stream to be the first one after the magic.  Thus callers have to skip the first two bytes. Otherwise\n                // this constructor will throw an exception.\"\n                StreamUtils.skipFully(in, 2);\n\n                // Quoted from CBZip2InputStream's Javadoc:\n                // \"CBZip2InputStream reads bytes from the compressed source stream via the single byte {@link java.io.InputStream#read()\n                // read()} method exclusively. Thus you should consider to use a buffered source stream.\"\n                in = new CBZip2InputStream(new BufferedInputStream(in));\n            } catch (Exception e) {\n                // CBZip2InputStream is known to throw NullPointerException if file is not properly Bzip2-encoded\n                // so we need to catch those and throw them as IOException\n                LOGGER.info(\"Exception caught while creating CBZip2InputStream, throwing IOException\", e);\n\n                throw new IOException();\n            }\n        }\n\n        return new TarInputStream(in, entryOffset);\n    }\n\n\n    ////////////////////////////////////////\n    // AbstractArchiveFile implementation //\n    ////////////////////////////////////////\n\n    @Override\n    public ArchiveEntryIterator getEntryIterator() throws IOException {\n        return new TarEntryIterator(createTarStream(0));\n    }\n\n\n    @Override\n    public InputStream getEntryInputStream(ArchiveEntry entry, ArchiveEntryIterator entryIterator) throws IOException {\n        if (entry.isDirectory()) {\n            throw new IOException();\n        }\n        // Optimization: first check if the specified iterator is positionned at the beginning of the entry.\n        // This will typically be the case if an iterator is being used to read all the archive's entries\n        // (unpack operation). In that case, we save the cost of looking for the entry in the archive, which is all\n        // the more expensive if the TAR archive is GZipped.\n        if ((entryIterator instanceof TarEntryIterator)) {\n            ArchiveEntry currentEntry = ((TarEntryIterator)entryIterator).getCurrentEntry();\n            if (currentEntry.getPath().equals(entry.getPath())) {\n                // The entry/tar stream is wrapped in a FilterInputStream where #close is implemented as a no-op:\n                // we don't want the TarInputStream to be closed when the caller closes the entry's stream.\n                return new FilterInputStream(((TarEntryIterator)entryIterator).getTarInputStream()) {\n                    @Override\n                    public void close() {\n                        // No-op\n                    }\n                };\n            }\n            // This is not the one, look for the entry from the beginning of the archive\n        }\n\n        // Iterate through the archive until we've found the entry\n        TarEntry tarEntry = (TarEntry)entry.getEntryObject();\n        if (tarEntry != null) {\n            TarInputStream tin = createTarStream(tarEntry.getOffset());\n            tin.getNextEntry();\n\n            return tin;\n        }\n\n        throw new IOException(\"Unknown TAR entry: \"+entry.getName());\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/tar/TarEntryIterator.java",
    "content": "package com.mucommander.commons.file.impl.tar;\n\nimport com.mucommander.commons.file.ArchiveEntry;\nimport com.mucommander.commons.file.ArchiveEntryIterator;\nimport com.mucommander.commons.file.PermissionBits;\nimport com.mucommander.commons.file.SimpleFilePermissions;\nimport com.mucommander.commons.file.impl.tar.provider.TarEntry;\nimport com.mucommander.commons.file.impl.tar.provider.TarInputStream;\n\nimport java.io.IOException;\n\n/**\n * An <code>ArchiveEntryIterator</code> that iterates through a {@link TarInputStream}.\n *\n * @author Maxence Bernard\n */\nclass TarEntryIterator implements ArchiveEntryIterator {\n\n    /** InputStream to the archive file */\n    private final TarInputStream tin;\n\n    /** The current entry, where the TarInputStream is currently positionned */\n    private ArchiveEntry currentEntry;\n\n\n    /**\n     * Creates a new TarEntryIterator that iterates through the entries of the given {@link TarInputStream}.\n     *\n     * @param tin the TarInputStream to iterate through\n     */\n    TarEntryIterator(TarInputStream tin) {\n        this.tin = tin;\n    }\n\n    /**\n     * Returns the {@link TarInputStream} instance that was used to create this object.\n     *\n     * @return the {@link TarInputStream} instance that was used to create this object.\n     */\n    TarInputStream getTarInputStream() {\n        return tin;\n    }\n\n    /**\n     * Returns the current entry where the {@link #getTarInputStream()} TarInputStream} is currently positionned.\n     * The returned value is <code>null</code> until {@link #nextEntry()} is called for the first time.\n     *\n     * @return the current entry where the {@link #getTarInputStream()} TarInputStream} is currently positionned.\n     */\n    ArchiveEntry getCurrentEntry() {\n        return currentEntry;\n    }\n\n    /**\n     * Creates and return an {@link ArchiveEntry()} whose attributes are fetched from the given\n     * <code>org.apache.tools.tar.TarEntry</code>.\n     *\n     * @param tarEntry the object that serves to initialize the attributes of the returned ArchiveEntry\n     * @return an ArchiveEntry whose attributes are fetched from the given org.apache.tools.tar.TarEntry\n     */\n    private ArchiveEntry createArchiveEntry(TarEntry tarEntry) {\n        ArchiveEntry entry = new ArchiveEntry(tarEntry.getName(), tarEntry.isDirectory(), tarEntry.getModTime().getTime(), tarEntry.getSize(), true);\n        entry.setPermissions(new SimpleFilePermissions(tarEntry.getMode() & PermissionBits.FULL_PERMISSION_INT));\n        entry.setOwner(tarEntry.getUserName());\n        entry.setGroup(tarEntry.getGroupName());\n        entry.setEntryObject(tarEntry);\n\n        return entry;\n    }\n\n    /**\n     * Advances the {@link TarInputStream} to the next entry and returns the corresponding {@link ArchiveEntry}.\n     *\n     * @return the next ArchiveEntry\n     * @throws IOException if an I/O error occurred\n     */\n    private ArchiveEntry getNextEntry() throws IOException {\n        TarEntry entry = tin.getNextEntry();\n        return entry == null ? null : createArchiveEntry(entry);\n    }\n\n\n    /////////////////////////////////////////\n    // ArchiveEntryIterator implementation //\n    /////////////////////////////////////////\n\n    public ArchiveEntry nextEntry() throws IOException {\n        // Get the next entry, if any\n        this.currentEntry = getNextEntry();\n        return currentEntry;\n    }\n\n    public void close() throws IOException {\n        tin.close();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/tar/TarFormatProvider.java",
    "content": "package com.mucommander.commons.file.impl.tar;\n\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.ArchiveFormatProvider;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\n\nimport java.io.IOException;\n\n/**\n * This class is the provider for the 'Tar' archive format implemented by {@link TarArchiveFile}.\n *\n * @see com.mucommander.commons.file.impl.tar.TarArchiveFile\n * @author Nicolas Rinaudo, Maxence Bernard\n */\npublic class TarFormatProvider implements ArchiveFormatProvider {\n\n    private static final String[] EXTENSIONS = {\".tar\", \".tar.gz\", \".tgz\", \".tar.bz2\", \".tbz2\", \".cbt\"};\n\n    /**\n     * Static instance of the filename filter that matches archive filenames\n     * */\n    private final static ExtensionFilenameFilter FILENAME_FILTER = new ExtensionFilenameFilter(EXTENSIONS);\n\n\n\n    @Override\n    public AbstractArchiveFile getFile(AbstractFile file) throws IOException {\n        return new TarArchiveFile(file);\n    }\n\n    @Override\n    public FilenameFilter getFilenameFilter() {\n        return FILENAME_FILTER;\n    }\n\n    @Override\n    public String[] getFileExtensions() {\n        return EXTENSIONS;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/tar/package.html",
    "content": "<body>\n  Provides an implementation of the tar archive format.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/tar/provider/TarBuffer.java",
    "content": "package com.mucommander.commons.file.impl.tar.provider;\n\nimport com.mucommander.commons.io.BufferPool;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.Arrays;\n\n/**\n * The TarBuffer class implements the tar archive concept\n * of a buffered input stream. This concept goes back to the\n * days of blocked tape drives and special io devices. In the\n * Java universe, the only real function that this class\n * performs is to ensure that files have the correct \"block\"\n * size, or other tars will complain.\n * <p>\n * You should never have a need to access this class directly.\n * TarBuffers are created by Tar IO Streams.\n *\n * <p>This class is based off the <code>org.apache.tools.tar</code> package of the <i>Apache Ant</i> project. The Ant\n * code has been modified under the terms of the Apache License which you can find in the bundled muCommander license\n * file. It was forked at version 1.7.1 of Ant.\n * \n * @author Apache Ant, Maxence Bernard\n */\npublic class TarBuffer {\n\n    /** Default record size */\n    static final int DEFAULT_RCDSIZE = (512);\n\n    /** Default block size */\n    static final int DEFAULT_BLKSIZE = (DEFAULT_RCDSIZE * 20);\n\n    private InputStream     inStream;\n    private OutputStream    outStream;\n    private byte[]          blockBuffer;\n    private int             currBlkIdx;\n    private int             currRecIdx;\n    private int             blockSize;\n    private int             recordSize;\n    private int             recsPerBlock;\n    private boolean         debug;\n\n    /**\n     * Constructor for a TarBuffer on an input stream.\n     * @param inStream the input stream to use\n     */\n    public TarBuffer(InputStream inStream) {\n        this(inStream, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE);\n    }\n\n    /**\n     * Constructor for a TarBuffer on an input stream.\n     * @param inStream the input stream to use\n     * @param blockSize the block size to use\n     * @param recordSize the record size to use\n     */\n    TarBuffer(InputStream inStream, int blockSize, int recordSize) {\n        this.inStream = inStream;\n        this.outStream = null;\n\n        this.initialize(blockSize, recordSize);\n    }\n\n    /**\n     * Constructor for a TarBuffer on an output stream.\n     * @param outStream the output stream to use\n     */\n    public TarBuffer(OutputStream outStream) {\n        this(outStream, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE);\n    }\n\n    /**\n     * Constructor for a TarBuffer on an output stream.\n     * @param outStream the output stream to use\n     * @param blockSize the block size to use\n     * @param recordSize the record size to use\n     */\n    TarBuffer(OutputStream outStream, int blockSize, int recordSize) {\n        this.inStream = null;\n        this.outStream = outStream;\n\n        this.initialize(blockSize, recordSize);\n    }\n\n    /**\n     * Initialization common to all constructors.\n     *\n     * @param blockSize the block size to use\n     * @param recordSize the record size to use\n     */\n    private void initialize(int blockSize, int recordSize) {\n        this.debug = false;\n        this.blockSize = blockSize;\n        this.recordSize = recordSize;\n        this.recsPerBlock = (this.blockSize / this.recordSize);\n        this.blockBuffer = BufferPool.getByteArray(this.blockSize);\n\n        if (this.inStream != null) {\n            this.currBlkIdx = -1;\n            this.currRecIdx = this.recsPerBlock;\n        } else {\n            this.currBlkIdx = 0;\n            this.currRecIdx = 0;\n        }\n    }\n\n    /**\n     * Get the TAR Buffer's block size. Blocks consist of multiple records.\n     * @return the block size\n     */\n    int getBlockSize() {\n        return this.blockSize;\n    }\n\n    /**\n     * Get the TAR Buffer's record size.\n     * @return the record size\n     */\n    int getRecordSize() {\n        return this.recordSize;\n    }\n\n    /**\n     * Set the debugging flag for the buffer.\n     *\n     * @param debug If true, print debugging output.\n     */\n    public void setDebug(boolean debug) {\n        this.debug = debug;\n    }\n\n    /**\n     * Determine if an archive record indicate End of Archive. End of\n     * archive is indicated by a record that consists entirely of null bytes.\n     *\n     * @param record The record data to check.\n     * @return true if the record data is an End of Archive\n     */\n    boolean isEOFRecord(byte[] record) {\n        for (int i = 0, sz = getRecordSize(); i < sz; ++i) {\n            if (record[i] != 0) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * Skip over a record on the input stream.\n     *\n     * @return <code>true</code> if the record has been skipped, <code>false</code> if EOF has been reached\n     * @throws IOException on error\n     */\n    boolean skipRecord() throws IOException {\n        if (debug) {\n            System.err.println(\"SkipRecord: recIdx = \" + currRecIdx\n                               + \" blkIdx = \" + currBlkIdx);\n        }\n\n        if (inStream == null) {\n            throw new IOException(\"reading (via skip) from an output buffer\");\n        }\n\n        if (currRecIdx >= recsPerBlock) {\n            if (!readBlock()) {\n                return false;\n            }\n        }\n\n        currRecIdx++;\n        return true;\n    }\n\n\n    /**\n     * Read a record from the input stream and stores it into the specified buffer.\n     *\n     * @param recordBuf the buffer into which the record will be stored. Its length must be {@link #getRecordSize()}.\n     * @return <code>true</code> if the record has been read, <code>false</code> if EOF has been reached\n     * @throws IOException on error\n     */\n    boolean readRecord(byte[] recordBuf) throws IOException {\n        if (debug) {\n            System.err.println(\"ReadRecord: recIdx = \" + currRecIdx\n                               + \" blkIdx = \" + currBlkIdx);\n        }\n\n        if(recordBuf.length!=recordSize)\n            throw new IOException(\"specified record buffer doesn't match record size: \"+recordSize);\n\n        if (inStream == null) {\n            throw new IOException(\"reading from an output buffer\");\n        }\n\n        if (currRecIdx >= recsPerBlock) {\n            if (!readBlock()) {\n                return false;\n            }\n        }\n\n        System.arraycopy(blockBuffer,\n                         (currRecIdx * recordSize), recordBuf, 0,\n                         recordSize);\n\n        currRecIdx++;\n\n        return true;\n    }\n\n    /**\n     * Read a block from the input stream and stores it into the block buffer.\n     *\n     * @return true if a block was read, false if EOF was reached\n     * @throws IOException on error\n     */\n    private boolean readBlock() throws IOException {\n        if (debug) {\n            System.err.println(\"readBlock: blkIdx = \" + currBlkIdx);\n        }\n\n        if (inStream == null) {\n            throw new IOException(\"reading from an output buffer\");\n        }\n\n        currRecIdx = 0;\n\n        int offset = 0;\n        int bytesNeeded = blockSize;\n\n        while (bytesNeeded > 0) {\n            long numBytes = inStream.read(blockBuffer, offset, bytesNeeded);\n\n            //\n            // NOTE\n            // We have fit EOF, and the block is not full!\n            //\n            // This is a broken archive. It does not follow the standard\n            // blocking algorithm. However, because we are generous, and\n            // it requires little effort, we will simply ignore the error\n            // and continue as if the entire block were read. This does\n            // not appear to break anything upstream. We used to return\n            // false in this case.\n            //\n            // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix.\n            //\n            if (numBytes == -1) {\n                if (offset == 0) {\n                    // Ensure that we do not read gigabytes of zeros\n                    // for a corrupt tar file.\n                    // See http://issues.apache.org/bugzilla/show_bug.cgi?id=39924\n                    return false;\n                }\n                // However, just leaving the unread portion of the buffer dirty does\n                // cause problems in some cases.  This problem is described in\n                // http://issues.apache.org/bugzilla/show_bug.cgi?id=29877\n                //\n                // The solution is to fill the unused portion of the buffer with zeros.\n\n                Arrays.fill(blockBuffer, offset, offset + bytesNeeded, (byte) 0);\n\n                break;\n            }\n\n            offset += numBytes;\n            bytesNeeded -= numBytes;\n\n            if (numBytes != blockSize) {\n                if (debug) {\n                    System.err.println(\"readBlock: INCOMPLETE READ \"\n                                       + numBytes + \" of \" + blockSize\n                                       + \" bytes read.\");\n                }\n            }\n        }\n\n        currBlkIdx++;\n\n        return true;\n    }\n\n    /**\n     * Skip over a block on the input stream.\n     *\n     * @return true if a block was read, false if EOF was reached\n     * @throws IOException on error\n     */\n    boolean skipBlock() throws IOException {\n        int bytesToSkip = blockSize;\n\n        int noSkipCnt = 0;\n        while (bytesToSkip > 0) {\n            long numBytes = inStream.skip(bytesToSkip);\n            if (numBytes == 0) {\n                noSkipCnt++;\n                if (noSkipCnt >= 10) {\n                    return false;\n                }\n                try {\n                    Thread.sleep(10);\n                } catch (InterruptedException ignore) {}\n            }\n            // Adopt the same 'generous' behavior as #readBlock(), i.e. allow a premature EOF only if at least\n            // a byte was properly skipped.\n            if (numBytes == -1) {\n                return bytesToSkip != blockSize;\n            }\n\n            bytesToSkip -= numBytes;\n        }\n\n        currBlkIdx++;\n        currRecIdx = recsPerBlock;\n\n        return true;\n    }\n\n\n    /**\n     * Get the current block number, zero based.\n     *\n     * @return The current zero based block number.\n     */\n    int getCurrentBlockNum() {\n        return currBlkIdx;\n    }\n\n    /**\n     * Sets the current block number, zero based.\n     *\n     * @param blockNum the current block number, zero based\n     */\n    public void setCurrentBlockNum(int blockNum) {\n        this.currBlkIdx = blockNum;\n    }\n\n    /**\n     * Get the current record number, within the current block, zero based.\n     * Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum.\n     *\n     * @return The current zero based record number.\n     */\n    int getCurrentRecordNum() {\n        return currRecIdx - 1;\n    }\n\n\n    /**\n     * Returns the number of records per block.\n     *\n     * @return the number of records per block\n     */\n    int getRecordsPerBlock() {\n        return recsPerBlock;\n    }\n\n    /**\n     * Write an archive record to the archive.\n     *\n     * @param record The record data to write to the archive.\n     * @throws IOException on error\n     */\n    void writeRecord(byte[] record) throws IOException {\n        if (debug) {\n            System.err.println(\"WriteRecord: recIdx = \" + currRecIdx\n                               + \" blkIdx = \" + currBlkIdx);\n        }\n\n        if (outStream == null) {\n            throw new IOException(\"writing to an input buffer\");\n        }\n\n        if (record.length != recordSize) {\n            throw new IOException(\"record to write has length '\"\n                                  + record.length\n                                  + \"' which is not the record size of '\"\n                                  + recordSize + \"'\");\n        }\n\n        if (currRecIdx >= recsPerBlock) {\n            writeBlock();\n        }\n\n        System.arraycopy(record, 0, blockBuffer,\n                         (currRecIdx * recordSize),\n                         recordSize);\n\n        currRecIdx++;\n    }\n\n    /**\n     * Write an archive record to the archive, where the record may be\n     * inside of a larger array buffer. The buffer must be \"offset plus\n     * record size\" long.\n     *\n     * @param buf The buffer containing the record data to write.\n     * @param offset The offset of the record data within buf.\n     * @throws IOException on error\n     */\n    void writeRecord(byte[] buf, int offset) throws IOException {\n        if (debug) {\n            System.err.println(\"WriteRecord: recIdx = \" + currRecIdx\n                               + \" blkIdx = \" + currBlkIdx);\n        }\n\n        if (outStream == null) {\n            throw new IOException(\"writing to an input buffer\");\n        }\n\n        if ((offset + recordSize) > buf.length) {\n            throw new IOException(\"record has length '\" + buf.length\n                                  + \"' with offset '\" + offset\n                                  + \"' which is less than the record size of '\"\n                                  + recordSize + \"'\");\n        }\n\n        if (currRecIdx >= recsPerBlock) {\n            writeBlock();\n        }\n\n        System.arraycopy(buf, offset, blockBuffer,\n                         (currRecIdx * recordSize),\n                         recordSize);\n\n        currRecIdx++;\n    }\n\n    /**\n     * Write a TarBuffer block to the archive.\n     */\n    private void writeBlock() throws IOException {\n        if (debug) {\n            System.err.println(\"WriteBlock: blkIdx = \" + currBlkIdx);\n        }\n\n        if (outStream == null) {\n            throw new IOException(\"writing to an input buffer\");\n        }\n\n        outStream.write(blockBuffer, 0, blockSize);\n        outStream.flush();\n\n        currRecIdx = 0;\n        currBlkIdx++;\n    }\n\n    /**\n     * Flush the current data block if it has any data in it.\n     */\n    private void flushBlock() throws IOException {\n        if (debug) {\n            System.err.println(\"TarBuffer.flushBlock() called.\");\n        }\n\n        if (outStream == null) {\n            throw new IOException(\"writing to an input buffer\");\n        }\n\n        if (currRecIdx > 0) {\n            writeBlock();\n        }\n    }\n\n    /**\n     * Close the TarBuffer. If this is an output buffer, also flush the\n     * current block before closing.\n     * @throws IOException on error\n     */\n    public void close() throws IOException {\n        if (debug) {\n            System.err.println(\"TarBuffer.closeBuffer().\");\n        }\n\n        try {\n            if (outStream != null) {\n                flushBlock();\n\n                if (outStream != System.out\n                        && outStream != System.err) {\n                    outStream.close();\n\n                    outStream = null;\n                }\n            } else if (inStream != null) {\n                if (inStream != System.in) {\n                    inStream.close();\n\n                    inStream = null;\n                }\n            }\n        }\n        finally {\n            if (blockBuffer!=null) {\n                BufferPool.releaseByteArray(blockBuffer);\n                blockBuffer = null;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/tar/provider/TarConstants.java",
    "content": "package com.mucommander.commons.file.impl.tar.provider;\n\n/**\n * This interface contains all the definitions used in the package.\n * <p>\n * This class is based off the <code>org.apache.tools.tar</code> package of the <i>Apache Ant</i> project. The Ant\n * code has been modified under the terms of the Apache License which you can find in the bundled muCommander license\n * file. It was forked at version 1.7.1 of Ant.\n * \n * @author Apache Ant\n */\npublic interface TarConstants {\n\n    /**\n     * The length of the name field in a header buffer.\n     */\n    int    NAMELEN = 100;\n\n    /**\n     * The length of the mode field in a header buffer.\n     */\n    int    MODELEN = 8;\n\n    /**\n     * The length of the user id field in a header buffer.\n     */\n    int    UIDLEN = 8;\n\n    /**\n     * The length of the group id field in a header buffer.\n     */\n    int    GIDLEN = 8;\n\n    /**\n     * The length of the checksum field in a header buffer.\n     */\n    int    CHKSUMLEN = 8;\n\n    /**\n     * The length of the size field in a header buffer.\n     */\n    int    SIZELEN = 12;\n\n    /**\n     * The maximum size of a file in a tar archive (That's 11 sevens, octal).\n     */\n    long   MAXSIZE = 077777777777L;\n\n    /**\n     * The length of the magic field in a header buffer.\n     */\n    int    MAGICLEN = 8;\n\n    /**\n     * The length of the modification time field in a header buffer.\n     */\n    int    MODTIMELEN = 12;\n\n    /**\n     * The length of the user name field in a header buffer.\n     */\n    int    UNAMELEN = 32;\n\n    /**\n     * The length of the group name field in a header buffer.\n     */\n    int    GNAMELEN = 32;\n\n    /**\n     * The length of the devices field in a header buffer.\n     */\n    int    DEVLEN = 8;\n\n    /**\n     * LF_ constants represent the \"link flag\" of an entry, or more commonly,\n     * the \"entry type\". This is the \"old way\" of indicating a normal file.\n     */\n    byte   LF_OLDNORM = 0;\n\n    /**\n     * Normal file type.\n     */\n    byte   LF_NORMAL = (byte) '0';\n\n    /**\n     * Link file type.\n     */\n    byte   LF_LINK = (byte) '1';\n\n    /**\n     * Symbolic link file type.\n     */\n    byte   LF_SYMLINK = (byte) '2';\n\n    /**\n     * Character device file type.\n     */\n    byte   LF_CHR = (byte) '3';\n\n    /**\n     * Block device file type.\n     */\n    byte   LF_BLK = (byte) '4';\n\n    /**\n     * Directory file type.\n     */\n    byte   LF_DIR = (byte) '5';\n\n    /**\n     * FIFO (pipe) file type.\n     */\n    byte   LF_FIFO = (byte) '6';\n\n    /**\n     * Contiguous file type.\n     */\n    byte   LF_CONTIG = (byte) '7';\n\n    /**\n     * The magic tag representing a POSIX tar archive.\n     */\n    String TMAGIC = \"ustar\";\n\n    /**\n     * The magic tag representing a GNU tar archive.\n     */\n    String GNU_TMAGIC = \"ustar  \";\n\n    /**\n     * The namr of the GNU tar entry which contains a long name.\n     */\n    String GNU_LONGLINK = \"././@LongLink\";\n\n    /**\n     * Identifies the *next* file on the tape as having a long name.\n     */\n    byte LF_GNUTYPE_LONGNAME = (byte) 'L';\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/tar/provider/TarEntry.java",
    "content": "package com.mucommander.commons.file.impl.tar.provider;\n\nimport java.util.Date;\n\n/**\n * This class represents an entry in a Tar archive. It consists\n * of the entry's header, as well as the entry's File. Entries\n * can be instantiated in one of three ways, depending on how\n * they are to be used.\n * <p>\n * TarEntries that are created from the header bytes read from\n * an archive are instantiated with the TarEntry( byte[] )\n * constructor. These entries will be used when extracting from\n * or listing the contents of an archive. These entries have their\n * header filled in using the header bytes. They also set the File\n * to null, since they reference an archive entry not a file.\n * <p>\n * TarEntries that are created from Files that are to be written\n * into an archive are instantiated with the TarEntry( File )\n * constructor. These entries have their header filled in using\n * the File's information. They also keep a reference to the File\n * for convenience when writing entries.\n * <p>\n * Finally, TarEntries can be constructed from nothing but a name.\n * This allows the programmer to construct the entry by hand, for\n * instance when only an InputStream is available for writing to\n * the archive, and the header information is constructed from\n * other information. In this case the header fields are set to\n * defaults and the File is set to null.\n *\n * <p>\n * The C structure for a Tar Entry's header is:\n * <pre>\n * struct header {\n * char name[NAMSIZ];\n * char mode[8];\n * char uid[8];\n * char gid[8];\n * char size[12];\n * char mtime[12];\n * char chksum[8];\n * char linkflag;\n * char linkname[NAMSIZ];\n * char magic[8];\n * char uname[TUNMLEN];\n * char gname[TGNMLEN];\n * char devmajor[8];\n * char devminor[8];\n * } header;\n * </pre>\n *\n * <p>This class is based off the <code>org.apache.tools.tar</code> package of the <i>Apache Ant</i> project. The Ant\n * code has been modified under the terms of the Apache License which you can find in the bundled muCommander license\n * file. It was forked at version 1.7.1 of Ant.\n * \n * @author Apache Ant, Maxence Bernard\n */\npublic class TarEntry implements TarConstants {\n    /** The entry's name. */\n    private StringBuffer name;\n\n    /** The entry's permission mode. */\n    private int mode;\n\n    /** The entry's user id. */\n    private int userId;\n\n    /** The entry's group id. */\n    private int groupId;\n\n    /** The entry's size. */\n    private long size;\n\n    /** The entry's modification time. */\n    private long modTime;\n\n    /** The entry's link flag. */\n    private byte linkFlag;\n\n    /** The entry's link name. */\n    private StringBuffer linkName;\n\n    /** The entry's magic tag. */\n    private StringBuffer magic;\n\n    /** The entry's user name. */\n    private StringBuffer userName;\n\n    /** The entry's group name. */\n    private StringBuffer groupName;\n\n    /** The entry's major device number. */\n    private int devMajor;\n\n    /** The entry's minor device number. */\n    private int devMinor;\n\n    /** The entry's offset from the start of the archive */\n    private long offset;\n\n    /** Maximum length of a user's name in the tar file */\n    public static final int MAX_NAMELEN = 31;\n\n    /** Default permissions bits for directories */\n    public static final int DEFAULT_DIR_MODE = 040755;\n\n    /** Default permissions bits for files */\n    public static final int DEFAULT_FILE_MODE = 0100644;\n\n    /** Convert millis to seconds */\n    public static final int MILLIS_PER_SECOND = 1000;\n\n    /**\n     * Construct an empty entry and prepares the header values.\n     */\n    private TarEntry () {\n        this.magic = new StringBuffer(TMAGIC);\n        this.name = new StringBuffer();\n        this.linkName = new StringBuffer();\n\n        String user = System.getProperty(\"user.name\", \"\");\n\n        if (user.length() > MAX_NAMELEN) {\n            user = user.substring(0, MAX_NAMELEN);\n        }\n\n        this.userId = 0;\n        this.groupId = 0;\n        this.userName = new StringBuffer(user);\n        this.groupName = new StringBuffer();\n    }\n\n    /**\n     * Construct an entry with only a name. This allows the programmer\n     * to construct the entry's header \"by hand\". File is set to null.\n     *\n     * @param name the entry name\n     */\n    public TarEntry(String name) {\n        this();\n\n        boolean isDir = name.endsWith(\"/\");\n\n        this.devMajor = 0;\n        this.devMinor = 0;\n        this.name = new StringBuffer(name);\n        this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE;\n        this.linkFlag = isDir ? LF_DIR : LF_NORMAL;\n        this.userId = 0;\n        this.groupId = 0;\n        this.size = 0;\n        this.modTime = (new Date()).getTime() / MILLIS_PER_SECOND;\n        this.linkName = new StringBuffer();\n        this.userName = new StringBuffer();\n        this.groupName = new StringBuffer();\n        this.devMajor = 0;\n        this.devMinor = 0;\n\n    }\n\n    /**\n     * Construct an entry with a name and a link flag.\n     *\n     * @param name the entry name\n     * @param linkFlag the entry link flag.\n     */\n    public TarEntry(String name, byte linkFlag) {\n        this(name);\n        this.linkFlag = linkFlag;\n    }\n\n    /**\n     * Construct an entry from an archive's header bytes. File is set\n     * to null.\n     *\n     * @param headerBuf The header bytes from a tar archive entry.\n     */\n    public TarEntry(byte[] headerBuf) {\n        this();\n        parseTarHeader(headerBuf);\n    }\n\n    /**\n     * Determine if the two entries are equal. Equality is determined\n     * by the header names being equal.\n     *\n     * @param it Entry to be checked for equality.\n     * @return True if the entries are equal.\n     */\n    public boolean equals(TarEntry it) {\n        return getName().equals(it.getName());\n    }\n\n    /**\n     * Determine if the two entries are equal. Equality is determined\n     * by the header names being equal.\n     *\n     * @param it Entry to be checked for equality.\n     * @return True if the entries are equal.\n     */\n    public boolean equals(Object it) {\n        if (it == null || getClass() != it.getClass()) {\n            return false;\n        }\n        return equals((TarEntry) it);\n    }\n\n    /**\n     * Hashcodes are based on entry names.\n     *\n     * @return the entry hashcode\n     */\n    public int hashCode() {\n        return getName().hashCode();\n    }\n\n    /**\n     * Determine if the given entry is a descendant of this entry.\n     * Descendancy is determined by the name of the descendant\n     * starting with this entry's name.\n     *\n     * @param desc Entry to be checked as a descendent of this.\n     * @return True if entry is a descendant of this.\n     */\n    public boolean isDescendent(TarEntry desc) {\n        return desc.getName().startsWith(getName());\n    }\n\n    /**\n     * Get this entry's name.\n     *\n     * @return This entry's name.\n     */\n    public String getName() {\n        return name.toString();\n    }\n\n    /**\n     * Set this entry's name.\n     *\n     * @param name This entry's new name.\n     */\n    public void setName(String name) {\n        this.name = new StringBuffer(name);\n    }\n\n    /**\n     * Set the mode for this entry\n     *\n     * @param mode the mode for this entry\n     */\n    public void setMode(int mode) {\n        this.mode = mode;\n    }\n\n    /**\n     * Get this entry's link name.\n     *\n     * @return This entry's link name.\n     */\n    public String getLinkName() {\n        return linkName.toString();\n    }\n\n    /**\n     * Get this entry's user id.\n     *\n     * @return This entry's user id.\n     */\n    public int getUserId() {\n        return userId;\n    }\n\n    /**\n     * Set this entry's user id.\n     *\n     * @param userId This entry's new user id.\n     */\n    public void setUserId(int userId) {\n        this.userId = userId;\n    }\n\n    /**\n     * Get this entry's group id.\n     *\n     * @return This entry's group id.\n     */\n    public int getGroupId() {\n        return groupId;\n    }\n\n    /**\n     * Set this entry's group id.\n     *\n     * @param groupId This entry's new group id.\n     */\n    public void setGroupId(int groupId) {\n        this.groupId = groupId;\n    }\n\n    /**\n     * Get this entry's user name.\n     *\n     * @return This entry's user name.\n     */\n    public String getUserName() {\n        return userName.toString();\n    }\n\n    /**\n     * Set this entry's user name.\n     *\n     * @param userName This entry's new user name.\n     */\n    public void setUserName(String userName) {\n        this.userName = new StringBuffer(userName);\n    }\n\n    /**\n     * Get this entry's group name.\n     *\n     * @return This entry's group name.\n     */\n    public String getGroupName() {\n        return groupName.toString();\n    }\n\n    /**\n     * Set this entry's group name.\n     *\n     * @param groupName This entry's new group name.\n     */\n    public void setGroupName(String groupName) {\n        this.groupName = new StringBuffer(groupName);\n    }\n\n    /**\n     * Convenience method to set this entry's group and user ids.\n     *\n     * @param userId This entry's new user id.\n     * @param groupId This entry's new group id.\n     */\n    public void setIds(int userId, int groupId) {\n        setUserId(userId);\n        setGroupId(groupId);\n    }\n\n    /**\n     * Convenience method to set this entry's group and user names.\n     *\n     * @param userName This entry's new user name.\n     * @param groupName This entry's new group name.\n     */\n    public void setNames(String userName, String groupName) {\n        setUserName(userName);\n        setGroupName(groupName);\n    }\n\n    /**\n     * Set this entry's modification time. The parameter passed\n     * to this method is in \"Java time\".\n     *\n     * @param time This entry's new modification time.\n     */\n    public void setModTime(long time) {\n        modTime = time / MILLIS_PER_SECOND;\n    }\n\n    /**\n     * Set this entry's modification time.\n     *\n     * @param time This entry's new modification time.\n     */\n    public void setModTime(Date time) {\n        modTime = time.getTime() / MILLIS_PER_SECOND;\n    }\n\n    /**\n     * Set this entry's modification time.\n     *\n     * @return time This entry's new modification time.\n     */\n    public Date getModTime() {\n        return new Date(modTime * MILLIS_PER_SECOND);\n    }\n\n    /**\n     * Get this entry's mode.\n     *\n     * @return This entry's mode.\n     */\n    public int getMode() {\n        return mode;\n    }\n\n    /**\n     * Get this entry's file size.\n     *\n     * @return This entry's file size.\n     */\n    public long getSize() {\n        return size;\n    }\n\n    /**\n     * Set this entry's file size.\n     *\n     * @param size This entry's new file size.\n     */\n    public void setSize(long size) {\n        this.size = size;\n    }\n\n    /**\n     * Returns this entry's offset from the start of the archive.\n     *\n     * @return this entry's offset from the start of the archive\n     */\n    public long getOffset() {\n        return offset;\n    }\n\n    /**\n     * Sets this entry's offset from the start of the archive.\n     *\n     * @param offset this entry's offset from the start of the archive\n     */\n    public void setOffset(long offset) {\n        this.offset = offset;\n    }\n\n    /**\n     * Indicate if this entry is a GNU long name block\n     *\n     * @return true if this is a long name extension provided by GNU tar\n     */\n    public boolean isGNULongNameEntry() {\n        return linkFlag == LF_GNUTYPE_LONGNAME\n                           && name.toString().equals(GNU_LONGLINK);\n    }\n\n    /**\n     * Return whether this entry represents a directory.\n     *\n     * @return True if this entry is a directory.\n     */\n    public boolean isDirectory() {\n        if (linkFlag == LF_DIR) {\n            return true;\n        }\n\n        return getName().endsWith(\"/\");\n    }\n\n    /**\n     * Write an entry's header information to a header buffer.\n     *\n     * @param outbuf The tar entry header buffer to fill in.\n     */\n    public void writeEntryHeader(byte[] outbuf) {\n        int offset = 0;\n\n        offset = TarUtils.getNameBytes(name, outbuf, offset, NAMELEN);\n        offset = TarUtils.getOctalBytes(mode, outbuf, offset, MODELEN);\n        offset = TarUtils.getOctalBytes(userId, outbuf, offset, UIDLEN);\n        offset = TarUtils.getOctalBytes(groupId, outbuf, offset, GIDLEN);\n        offset = TarUtils.getLongOctalBytes(size, outbuf, offset, SIZELEN);\n        offset = TarUtils.getLongOctalBytes(modTime, outbuf, offset, MODTIMELEN);\n\n        int csOffset = offset;\n\n        for (int c = 0; c < CHKSUMLEN; ++c) {\n            outbuf[offset++] = (byte) ' ';\n        }\n\n        outbuf[offset++] = linkFlag;\n        offset = TarUtils.getNameBytes(linkName, outbuf, offset, NAMELEN);\n        offset = TarUtils.getNameBytes(magic, outbuf, offset, MAGICLEN);\n        offset = TarUtils.getNameBytes(userName, outbuf, offset, UNAMELEN);\n        offset = TarUtils.getNameBytes(groupName, outbuf, offset, GNAMELEN);\n        offset = TarUtils.getOctalBytes(devMajor, outbuf, offset, DEVLEN);\n        offset = TarUtils.getOctalBytes(devMinor, outbuf, offset, DEVLEN);\n\n        while (offset < outbuf.length) {\n            outbuf[offset++] = 0;\n        }\n\n        long chk = TarUtils.computeCheckSum(outbuf);\n\n        TarUtils.getCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);\n    }\n\n    /**\n     * Parse an entry's header information from a header buffer.\n     *\n     * @param header The tar entry header buffer to get information from.\n     */\n    public void parseTarHeader(byte[] header) {\n        int offset = 0;\n\n        name = TarUtils.parseName(header, offset, NAMELEN);\n        offset += NAMELEN;\n        mode = (int) TarUtils.parseOctal(header, offset, MODELEN);\n        offset += MODELEN;\n        userId = (int) TarUtils.parseOctal(header, offset, UIDLEN);\n        offset += UIDLEN;\n        groupId = (int) TarUtils.parseOctal(header, offset, GIDLEN);\n        offset += GIDLEN;\n        size = TarUtils.parseOctal(header, offset, SIZELEN);\n        offset += SIZELEN;\n        modTime = TarUtils.parseOctal(header, offset, MODTIMELEN);\n        offset += MODTIMELEN;\n        offset += CHKSUMLEN;\n        linkFlag = header[offset++];\n        linkName = TarUtils.parseName(header, offset, NAMELEN);\n        offset += NAMELEN;\n        magic = TarUtils.parseName(header, offset, MAGICLEN);\n        offset += MAGICLEN;\n        userName = TarUtils.parseName(header, offset, UNAMELEN);\n        offset += UNAMELEN;\n        groupName = TarUtils.parseName(header, offset, GNAMELEN);\n        offset += GNAMELEN;\n        devMajor = (int) TarUtils.parseOctal(header, offset, DEVLEN);\n        offset += DEVLEN;\n        devMinor = (int) TarUtils.parseOctal(header, offset, DEVLEN);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/tar/provider/TarInputStream.java",
    "content": "package com.mucommander.commons.file.impl.tar.provider;\n\nimport com.mucommander.commons.io.BufferPool;\n\nimport java.io.EOFException;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * The TarInputStream reads a UNIX tar archive as an InputStream.\n * methods are provided to position at each successive entry in\n * the archive, and the read each entry as a normal input stream\n * using read().\n *\n * <p>This class is based off the <code>org.apache.tools.tar</code> package of the <i>Apache Ant</i> project. The Ant\n * code has been modified under the terms of the Apache License which you can find in the bundled muCommander license\n * file. It was forked at version 1.7.1 of Ant.\n * \n * @author Apache Ant, Maxence Bernard\n */\npublic class TarInputStream extends InputStream {\n    private static final int NAME_BUFFER_SIZE = 256;\n    private static final int BYTE_MASK = 0xFF;\n\n    protected boolean debug;\n    protected boolean hasHitEOF;\n    protected long entrySize;\n    protected long entryOffset;\n    protected byte[] recordBuf;\n    protected byte[] nameBuf;\n    protected int recordBufPos;\n    protected int recordBufLeft;\n    protected TarBuffer buffer;\n    protected TarEntry currEntry;\n    protected boolean closed;\n\n    /**\n     * This contents of this array is not used at all in this class,\n     * it is only here to avoid repreated object creation during calls\n     * to the no-arg read method.\n     */\n    protected byte[] oneBuf;\n\n    /**\n     * Creates a new <code>TarInputStream</code> over the specified input stream using the default block size and\n     * record size and starting at the first entry.\n     *\n     * @param is the input stream providing the actual TAR data\n     * @throws IOException if an error ocurred while initializing the stream\n     */\n    public TarInputStream(InputStream is) throws IOException  {\n        this(is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE, 0);\n    }\n\n\n    /**\n     * Creates a new <code>TarInputStream</code> over the specified input stream, starting at the specified\n     * entry offset.\n     *\n     * @param is the input stream providing the actual TAR data\n     * @param entryOffset offset from the start of the archive to an entry. Must be a multiple of recordSize, or\n     * <code>0</code> to start at the first entry.\n     * @throws IOException if an error ocurred while initializing the stream\n     */\n    public TarInputStream(InputStream is, long entryOffset) throws IOException {\n        this(is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE, entryOffset);\n    }\n\n\n    /**\n     * Creates a new <code>TarInputStream</code> over the specified input stream, using the specified\n     * block size, record size and start offset.\n     *\n     * @param is the input stream to use\n     * @param blockSize the block size to use\n     * @param recordSize the record size to use\n     * @param entryOffset offset from the start of the archive to an entry. Must be a multiple of recordSize, or\n     * <code>0</code> to start at the first entry.\n     * @throws IOException if an error ocurred while initializing the stream\n     */\n    public TarInputStream(InputStream is, int blockSize, int recordSize, long entryOffset) throws IOException {\n        this.buffer = new TarBuffer(is, blockSize, recordSize);\n        this.recordBuf = BufferPool.getByteArray(buffer.getRecordSize());\n        this.nameBuf = BufferPool.getByteArray(NAME_BUFFER_SIZE);\n        this.oneBuf = BufferPool.getByteArray(1);\n        this.debug = false;\n        this.hasHitEOF = false;\n\n        if (entryOffset > 0) {\n            if ((entryOffset % recordSize) != 0)\n                throw new IllegalArgumentException(\"entryOffset (\"+entryOffset+\") is not a multiple of recordSize (\"+recordSize+\")\");\n\n            skipBytes(entryOffset);\n        }\n\n    }\n\n    /**\n     * Sets the debugging flag.\n     *\n     * @param debug True to turn on debugging.\n     */\n    public void setDebug(boolean debug) {\n        this.debug = debug;\n        buffer.setDebug(debug);\n    }\n\n    /**\n     * Closes this stream. Calls the TarBuffer's close() method.\n     * @throws IOException on error\n     */\n    @Override\n    public void close() throws IOException {\n        if (!closed) {\n            try {\n                buffer.close();\n            } finally {\n                BufferPool.releaseByteArray(recordBuf);\n                BufferPool.releaseByteArray(nameBuf);\n                BufferPool.releaseByteArray(oneBuf);\n\n                closed = true;\n            }\n        }\n    }\n\n    /**\n     * Get the record size being used by this stream's TarBuffer.\n     *\n     * @return The TarBuffer record size.\n     */\n    public int getRecordSize() {\n        return buffer.getRecordSize();\n    }\n\n    /**\n     * Get the available data that can be read from the current\n     * entry in the archive. This does not indicate how much data\n     * is left in the entire archive, only in the current entry.\n     * This value is determined from the entry's size header field\n     * and the amount of data already read from the current entry.\n     * Integer.MAX_VALUE is returen in case more than Integer.MAX_VALUE\n     * bytes are left in the current entry in the archive.\n     *\n     * @return The number of available bytes for the current entry.\n     */\n    @Override\n    public int available() {\n        if (entrySize - entryOffset > Integer.MAX_VALUE) {\n            return Integer.MAX_VALUE;\n        }\n        return (int) (entrySize - entryOffset);\n    }\n\n    /**\n     * Since we do not support marking just yet, we return false.\n     *\n     * @return False.\n     */\n    @Override\n    public boolean markSupported() {\n        return false;\n    }\n\n    /**\n     * Since we do not support marking just yet, we do nothing.\n     *\n     * @param markLimit The limit to mark.\n     */\n    @Override\n    public void mark(int markLimit) {\n    }\n\n    /**\n     * Since we do not support marking just yet, we do nothing.\n     */\n    @Override\n    public void reset() {\n    }\n\n\n    /**\n     * Reads a whole new record from the {@link TarBuffer} into the {@link #recordBuf record buffer} and resets\n     * {@link #recordBufPos} and {@link #recordBufLeft} fields accordingly.\n     *\n     * @return <code>true</code> if the record has been read, <code>false</code> if EOF has been reached\n     * @throws IOException on error\n     */\n    public boolean readRecord() throws IOException {\n        boolean ret = buffer.readRecord(recordBuf);\n\n        recordBufPos = 0;\n        recordBufLeft = ret?recordBuf.length:0;\n\n        return ret;\n    }\n\n\n    /**\n     * Returns the current entry where this <code>TarInputStream</code> is currently positionned.\n     *\n     * @return the current entry where this <code>TarInputStream</code> is currently positionned\n     */\n    public TarEntry getCurrentEntry() {\n        return currEntry;\n    }\n\n    /**\n     * Get the next entry in this tar archive. This will skip\n     * over any remaining data in the current entry, if there\n     * is one, and place the input stream at the header of the\n     * next entry, and read the header and instantiate a new\n     * TarEntry from the header bytes and return that entry.\n     * If there are no more entries in the archive, null will\n     * be returned to indicate that the end of the archive has\n     * been reached.\n     *\n     * @return The next TarEntry in the archive, or null.\n     * @throws IOException on error\n     */\n    public TarEntry getNextEntry() throws IOException {\n        if (hasHitEOF) {\n            return null;\n        }\n\n        if (currEntry != null) {\n            long numToSkip = entrySize - entryOffset;\n\n            if (debug) {\n                System.err.println(\"TarInputStream: SKIP currENTRY '\"\n                        + currEntry.getName() + \"' SZ \"\n                        + entrySize + \" OFF \"\n                        + entryOffset + \"  skipping \"\n                        + numToSkip + \" bytes\");\n            }\n\n            if (numToSkip > 0) {\n                skipBytes(numToSkip);\n            }\n        }\n\n        // Read the header record\n        if (!readRecord()) {\n            if (debug) {\n                System.err.println(\"READ NULL RECORD\");\n            }\n            hasHitEOF = true;\n        } else if (buffer.isEOFRecord(recordBuf)) {\n            if (debug) {\n                System.err.println(\"READ EOF RECORD\");\n            }\n            hasHitEOF = true;\n        }\n\n        if (hasHitEOF) {\n            currEntry = null;\n        } else {\n            currEntry = new TarEntry(recordBuf);\n\n            // Offset of the current entry from the start of the archive,\n            // allows to reposition the stream at the start of the entry\n            currEntry.setOffset(buffer.getCurrentBlockNum()*buffer.getBlockSize()\n                               + buffer.getCurrentRecordNum()*buffer.getRecordSize());\n\n            if (debug) {\n                System.err.println(\"TarInputStream: SET CURRENTRY '\"\n                        + currEntry.getName()\n                        + \"' size = \"\n                        + currEntry.getSize());\n            }\n\n            // Update the current entry offset and size\n            entryOffset = 0;\n            entrySize = currEntry.getSize();\n\n            // Consume the rest of the record\n            recordBufPos = 0;\n            recordBufLeft = 0;\n        }\n\n        if (currEntry != null && currEntry.isGNULongNameEntry()) {\n            // read in the name\n            StringBuilder longName = new StringBuilder();\n            int length;\n            while ((length = read(nameBuf)) >= 0) {\n                longName.append(new String(nameBuf, 0, length));\n            }\n            getNextEntry();\n            if (currEntry == null) {\n                // Bugzilla: 40334\n                // Malformed tar file - long entry name not followed by entry\n                return null;\n            }\n            // remove trailing null terminator\n            if (longName.length() > 0\n                && longName.charAt(longName.length() - 1) == 0) {\n                longName.deleteCharAt(longName.length() - 1);\n            }\n            currEntry.setName(longName.toString());\n        }\n\n        return currEntry;\n    }\n\n    /**\n     * Reads a byte from the current tar archive entry.\n     *\n     * This method simply calls read( byte[], int, int ).\n     *\n     * @return The byte read, or -1 at EOF.\n     * @throws IOException on error\n     */\n    @Override\n    public int read() throws IOException {\n        int num = read(oneBuf, 0, 1);\n        return num == -1 ? -1 : ((int) oneBuf[0]) & BYTE_MASK;\n    }\n\n\n\n    /**\n     * Reads bytes from the current tar archive entry.\n     *\n     * This method is aware of the boundaries of the current\n     * entry in the archive and will deal with them as if they\n     * were this stream's start and EOF.\n     *\n     * @param buf The buffer into which to place bytes read.\n     * @param offset The offset at which to place bytes read.\n     * @param numToRead The number of bytes to read.\n     * @return The number of bytes read, or -1 at EOF.\n     * @throws IOException on error\n     */\n    @Override\n    public int read(byte[] buf, int offset, int numToRead) throws IOException {\n        int totalRead = 0;\n\n        // Have we already reached the end of file/entry ?\n        if (entryOffset >= entrySize) {\n            return -1;\n        }\n\n        // Can't read more than the entry's size\n        if ((numToRead + entryOffset) > entrySize) {\n            numToRead = (int) (entrySize - entryOffset);\n        }\n\n        // Read data one record (at most) at a time. The record buffer is first emptied before reading a new record.\n        while (numToRead > 0) {\n            // If there is no more data left to read from the current record buffer,\n            // read a new record  \n            if(recordBufLeft<=0) {\n                if (!readRecord()) {\n                    // Unexpected EOF!\n                    throw new EOFException(\"unexpected EOF with \" + numToRead + \" bytes unread\");\n                }\n            }\n\n            int sz = (numToRead > recordBufLeft)\n                    ? recordBufLeft\n                    : numToRead;\n\n            System.arraycopy(recordBuf, recordBufPos, buf, offset, sz);\n\n            recordBufPos += sz;\n            recordBufLeft -= sz;\n\n            totalRead += sz;\n            numToRead -= sz;\n            offset += sz;\n            entryOffset += sz;\n        }\n\n        return totalRead;\n    }\n\n    /**\n     * Skip bytes in the input buffer. This skips bytes in the\n     * current entry's data, not the entire archive, and will\n     * stop at the end of the current entry's data if the number\n     * to skip extends beyond that point.\n     *\n     * @param numToSkip the number of bytes to skip.\n     * @return the number actually skipped\n     * @throws IOException on error\n     */\n    @Override\n    public long skip(long numToSkip) throws IOException {\n        // Have we already reached the end of file/entry ?\n        if (entryOffset >= entrySize) {\n            return -1;\n        }\n\n        // Can't read more than the entry's size\n        if ((numToSkip + entryOffset) > entrySize) {\n            numToSkip = (int) (entrySize - entryOffset);\n        }\n\n        return skipBytes(numToSkip);\n    }\n\n    /**\n     * Skips the specified number of bytes, without checking for the current entry's boundaries.\n     *\n     * @param numToSkip the number of bytes to skip.\n     * @return the number actually skipped\n     * @throws IOException on error\n     */\n    private long skipBytes(long numToSkip) throws IOException {\n        int totalSkipped = 0;\n\n        int recordSize = buffer.getRecordSize();\n        int blockSize = buffer.getBlockSize();\n\n        while (numToSkip > 0) {\n            // If the record buffer has some data left, empty it\n            if (recordBufLeft > 0) {\n                int sz = (numToSkip > recordBufLeft)\n                        ? recordBufLeft\n                        : (int)numToSkip;\n\n                recordBufPos += sz;\n                recordBufLeft -= sz;\n\n                totalSkipped += sz;\n                numToSkip -= sz;\n                entryOffset += sz;\n            }\n            // Skip a whole block if there are enough bytes left to skip, and if we are at the end of the current block\n            else if (numToSkip >= blockSize && buffer.getCurrentRecordNum() == buffer.getRecordsPerBlock()-1) {\n                if (!buffer.skipBlock()) {\n                    // Unexpected EOF!\n                    throw new EOFException(\"unexpected EOF with \" + numToSkip + \" bytes unskipped\");\n                }\n\n                totalSkipped += blockSize;\n                numToSkip -= blockSize;\n                entryOffset += blockSize;\n            }\n            // Skip a whole record if there are enough bytes left to skip\n            else if (numToSkip >= recordSize) {\n                if (!buffer.skipRecord()) {\n                    // Unexpected EOF!\n                    throw new EOFException(\"unexpected EOF with \" + numToSkip + \" bytes unskipped\");\n                }\n\n                totalSkipped += recordSize;\n                numToSkip -= recordSize;\n                entryOffset += recordSize;\n            }\n            // There is less than a record to skip -> read the record and skip\n            else {\n                if (!readRecord()) {\n                    // Unexpected EOF!\n                    throw new EOFException(\"unexpected EOF with \" + numToSkip + \" bytes unskipped\");\n                }\n\n                // if(recordBufLeft>0) will be matched on the next loop\n            }\n        }\n\n        return totalSkipped;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/tar/provider/TarOutputStream.java",
    "content": "package com.mucommander.commons.file.impl.tar.provider;\n\nimport com.mucommander.commons.io.BufferPool;\n\nimport java.io.FilterOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * The TarOutputStream writes a UNIX tar archive as an OutputStream.\n * Methods are provided to put entries, and then write their contents\n * by writing to this stream using write().\n *\n * <p>This class is based off the <code>org.apache.tools.tar</code> package of the <i>Apache Ant</i> project. The Ant\n * code has been modified under the terms of the Apache License which you can find in the bundled muCommander license\n * file. It was forked at version 1.7.1 of Ant.\n * \n * @author Apache Ant, Maxence Bernard\n */\npublic class TarOutputStream extends FilterOutputStream {\n    /** Fail if a long file name is required in the archive. */\n    public static final int LONGFILE_ERROR = 0;\n\n    /** Long paths will be truncated in the archive. */\n    public static final int LONGFILE_TRUNCATE = 1;\n\n    /** GNU tar extensions are used to store long file names in the archive. */\n    public static final int LONGFILE_GNU = 2;\n\n    protected boolean   debug;\n    protected long      currSize;\n    protected String    currName;\n    protected long      currBytes;\n    protected byte[]    oneBuf;\n    protected byte[]    recordBuf;\n    protected int       assemLen;\n    protected byte[]    assemBuf;\n    protected TarBuffer buffer;\n    protected int       longFileMode = LONGFILE_ERROR;\n\n    private boolean closed = false;\n\n    /**\n     * Constructor for TarInputStream.\n     * @param os the output stream to use\n     */\n    public TarOutputStream(OutputStream os) {\n        this(os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE);\n    }\n\n    /**\n     * Constructor for TarInputStream.\n     * @param os the output stream to use\n     * @param blockSize the block size to use\n     */\n    public TarOutputStream(OutputStream os, int blockSize) {\n        this(os, blockSize, TarBuffer.DEFAULT_RCDSIZE);\n    }\n\n    /**\n     * Constructor for TarInputStream.\n     * @param os the output stream to use\n     * @param blockSize the block size to use\n     * @param recordSize the record size to use\n     */\n    public TarOutputStream(OutputStream os, int blockSize, int recordSize) {\n        super(os);\n\n        this.buffer = new TarBuffer(os, blockSize, recordSize);\n        this.debug = false;\n        this.assemLen = 0;\n        this.assemBuf = BufferPool.getByteArray(recordSize);\n        this.recordBuf = BufferPool.getByteArray(recordSize);\n        this.oneBuf = BufferPool.getByteArray(1);\n    }\n\n    /**\n     * Set the long file mode.\n     * This can be LONGFILE_ERROR(0), LONGFILE_TRUNCATE(1) or LONGFILE_GNU(2).\n     * This specifies the treatment of long file names (names &gt;= TarConstants.NAMELEN).\n     * Default is LONGFILE_ERROR.\n     * @param longFileMode the mode to use\n     */\n    public void setLongFileMode(int longFileMode) {\n        this.longFileMode = longFileMode;\n    }\n\n\n    /**\n     * Sets the debugging flag.\n     *\n     * @param debugF True to turn on debugging.\n     */\n    public void setDebug(boolean debugF) {\n        this.debug = debugF;\n    }\n\n    /**\n     * Sets the debugging flag in this stream's TarBuffer.\n     *\n     * @param debug True to turn on debugging.\n     */\n    public void setBufferDebug(boolean debug) {\n        buffer.setDebug(debug);\n    }\n\n    /**\n     * Ends the TAR archive without closing the underlying OutputStream.\n     * The result is that the two EOF records of nulls are written.\n     * @throws IOException on error\n     */\n    public void finish() throws IOException {\n        // See Bugzilla 28776 for a discussion on this\n        // http://issues.apache.org/bugzilla/show_bug.cgi?id=28776\n        writeEOFRecord();\n        writeEOFRecord();\n    }\n\n    /**\n     * Ends the TAR archive and closes the underlying OutputStream.\n     * This means that finish() is called followed by calling the\n     * TarBuffer's close().\n     * @throws IOException on error\n     */\n    @Override\n    public void close() throws IOException {\n        if (!closed) {\n            try {\n                finish();\n            \n                buffer.close();\n            }\n            finally {\n                BufferPool.releaseByteArray(assemBuf);\n                BufferPool.releaseByteArray(recordBuf);\n                BufferPool.releaseByteArray(oneBuf);\n\n                closed = true;\n            }\n        }\n    }\n\n    /**\n     * Get the record size being used by this stream's TarBuffer.\n     *\n     * @return The TarBuffer record size.\n     */\n    public int getRecordSize() {\n        return buffer.getRecordSize();\n    }\n\n    /**\n     * Put an entry on the output stream. This writes the entry's\n     * header record and positions the output stream for writing\n     * the contents of the entry. Once this method is called, the\n     * stream is ready for calls to write() to write the entry's\n     * contents. Once the contents are written, closeEntry()\n     * <B>MUST</B> be called to ensure that all buffered data\n     * is completely written to the output stream.\n     *\n     * @param entry The TarEntry to be written to the archive.\n     * @throws IOException on error\n     */\n    public void putNextEntry(TarEntry entry) throws IOException {\n        if (entry.getName().length() >= TarConstants.NAMELEN) {\n\n            if (longFileMode == LONGFILE_GNU) {\n                // create a TarEntry for the LongLink, the contents\n                // of which are the entry's name\n                TarEntry longLinkEntry = new TarEntry(TarConstants.GNU_LONGLINK,\n                                                      TarConstants.LF_GNUTYPE_LONGNAME);\n\n                longLinkEntry.setSize(entry.getName().length() + 1);\n                putNextEntry(longLinkEntry);\n                write(entry.getName().getBytes());\n                write(0);\n                closeEntry();\n            } else if (longFileMode != LONGFILE_TRUNCATE) {\n                throw new RuntimeException(\"file name '\" + entry.getName()\n                                             + \"' is too long ( > \"\n                                             + TarConstants.NAMELEN + \" bytes)\");\n            }\n        }\n\n        entry.writeEntryHeader(recordBuf);\n        buffer.writeRecord(recordBuf);\n\n        currBytes = 0;\n\n        if (entry.isDirectory()) {\n            currSize = 0;\n        } else {\n            currSize = entry.getSize();\n        }\n        currName = entry.getName();\n    }\n\n    /**\n     * Close an entry. This method MUST be called for all file\n     * entries that contain data. The reason is that we must\n     * buffer data written to the stream in order to satisfy\n     * the buffer's record based writes. Thus, there may be\n     * data fragments still being assembled that must be written\n     * to the output stream before this entry is closed and the\n     * next entry written.\n     * @throws IOException on error\n     */\n    public void closeEntry() throws IOException {\n        if (assemLen > 0) {\n            for (int i = assemLen; i < assemBuf.length; ++i) {\n                assemBuf[i] = 0;\n            }\n\n            buffer.writeRecord(assemBuf);\n\n            currBytes += assemLen;\n            assemLen = 0;\n        }\n\n        if (currBytes < currSize) {\n            throw new IOException(\"entry '\" + currName + \"' closed at '\"\n                                  + currBytes\n                                  + \"' before the '\" + currSize\n                                  + \"' bytes specified in the header were written\");\n        }\n    }\n\n    /**\n     * Writes a byte to the current tar archive entry.\n     *\n     * This method simply calls read( byte[], int, int ).\n     *\n     * @param b The byte written.\n     * @throws IOException on error\n     */\n    @Override\n    public void write(int b) throws IOException {\n        oneBuf[0] = (byte) b;\n\n        write(oneBuf, 0, 1);\n    }\n\n    /**\n     * Writes bytes to the current tar archive entry.\n     *\n     * This method simply calls write( byte[], int, int ).\n     *\n     * @param wBuf The buffer to write to the archive.\n     * @throws IOException on error\n     */\n    @Override\n    public void write(byte[] wBuf) throws IOException {\n        write(wBuf, 0, wBuf.length);\n    }\n\n    /**\n     * Writes bytes to the current tar archive entry. This method\n     * is aware of the current entry and will throw an exception if\n     * you attempt to write bytes past the length specified for the\n     * current entry. The method is also (painfully) aware of the\n     * record buffering required by TarBuffer, and manages buffers\n     * that are not a multiple of recordsize in length, including\n     * assembling records from small buffers.\n     *\n     * @param wBuf The buffer to write to the archive.\n     * @param wOffset The offset in the buffer from which to get bytes.\n     * @param numToWrite The number of bytes to write.\n     * @throws IOException on error\n     */\n    @Override\n    public void write(byte[] wBuf, int wOffset, int numToWrite) throws IOException {\n        if ((currBytes + numToWrite) > currSize) {\n            throw new IOException(\"request to write '\" + numToWrite\n                                  + \"' bytes exceeds size in header of '\"\n                                  + currSize + \"' bytes for entry '\"\n                                  + currName + \"'\");\n\n            //\n            // We have to deal with assembly!!!\n            // The programmer can be writing little 32 byte chunks for all\n            // we know, and we must assemble complete records for writing.\n            // REVIEW Maybe this should be in TarBuffer? Could that help to\n            // eliminate some of the buffer copying.\n            //\n        }\n\n        if (assemLen > 0) {\n            if ((assemLen + numToWrite) >= recordBuf.length) {\n                int aLen = recordBuf.length - assemLen;\n\n                System.arraycopy(assemBuf, 0, recordBuf, 0, assemLen);\n                System.arraycopy(wBuf, wOffset, recordBuf, assemLen, aLen);\n                buffer.writeRecord(recordBuf);\n\n                currBytes += recordBuf.length;\n                wOffset += aLen;\n                numToWrite -= aLen;\n                assemLen = 0;\n            } else {\n                System.arraycopy(wBuf, wOffset, assemBuf, assemLen, numToWrite);\n\n                wOffset += numToWrite;\n                assemLen += numToWrite;\n                numToWrite -= numToWrite;\n            }\n        }\n\n        //\n        // When we get here we have EITHER:\n        // o An empty \"assemble\" buffer.\n        // o No bytes to write (numToWrite == 0)\n        //\n        while (numToWrite > 0) {\n            if (numToWrite < recordBuf.length) {\n                System.arraycopy(wBuf, wOffset, assemBuf, assemLen,\n                                 numToWrite);\n\n                assemLen += numToWrite;\n\n                break;\n            }\n\n            buffer.writeRecord(wBuf, wOffset);\n\n            int num = recordBuf.length;\n\n            currBytes += num;\n            numToWrite -= num;\n            wOffset += num;\n        }\n    }\n\n    /**\n     * Write an EOF (end of archive) record to the tar archive.\n     * An EOF record consists of a record of all zeros.\n     */\n    private void writeEOFRecord() throws IOException {\n        for (int i = 0; i < recordBuf.length; ++i) {\n            recordBuf[i] = 0;\n        }\n\n        buffer.writeRecord(recordBuf);\n    }\n}\n\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/tar/provider/TarUtils.java",
    "content": "package com.mucommander.commons.file.impl.tar.provider;\n\n/**\n * This class provides static utility methods to work with byte streams.\n *\n * <p>This class is based off the <code>org.apache.tools.tar</code> package of the <i>Apache Ant</i> project. The Ant\n * code has been modified under the terms of the Apache License which you can find in the bundled muCommander license\n * file. It was forked at version 1.7.1 of Ant.\n *\n * @author Apache Ant\n */\npublic class TarUtils {\n\n    private static final int BYTE_MASK = 255;\n\n    /**\n     * Parse an octal string from a header buffer. This is used for the\n     * file permission mode value.\n     *\n     * @param header The header buffer from which to parse.\n     * @param offset The offset into the buffer from which to parse.\n     * @param length The number of header bytes to parse.\n     * @return The long value of the octal string.\n     */\n    public static long parseOctal(byte[] header, int offset, int length) {\n        long    result = 0;\n        boolean stillPadding = true;\n        int     end = offset + length;\n\n        for (int i = offset; i < end; ++i) {\n            if (header[i] == 0) {\n                break;\n            }\n\n            if (header[i] == (byte) ' ' || header[i] == '0') {\n                if (stillPadding) {\n                    continue;\n                }\n\n                if (header[i] == (byte) ' ') {\n                    break;\n                }\n            }\n\n            stillPadding = false;\n            result = (result << 3) + (header[i] - '0');\n        }\n\n        return result;\n    }\n\n    /**\n     * Parse an entry name from a header buffer.\n     *\n     * @param header The header buffer from which to parse.\n     * @param offset The offset into the buffer from which to parse.\n     * @param length The number of header bytes to parse.\n     * @return The header's entry name.\n     */\n    public static StringBuffer parseName(byte[] header, int offset, int length) {\n        StringBuffer result = new StringBuffer(length);\n        int          end = offset + length;\n\n        for (int i = offset; i < end; ++i) {\n            if (header[i] == 0) {\n                break;\n            }\n\n            result.append((char) header[i]);\n        }\n\n        return result;\n    }\n\n    /**\n     * Determine the number of bytes in an entry name.\n     *\n     * @param name The header name from which to parse.\n     * @param buf The buffer from which to parse.\n     * @param offset The offset into the buffer from which to parse.\n     * @param length The number of header bytes to parse.\n     * @return The number of bytes in a header's entry name.\n     */\n    public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) {\n        int i;\n\n        for (i = 0; i < length && i < name.length(); ++i) {\n            buf[offset + i] = (byte) name.charAt(i);\n        }\n\n        for (; i < length; ++i) {\n            buf[offset + i] = 0;\n        }\n\n        return offset + length;\n    }\n\n    /**\n     * Parse an octal integer from a header buffer.\n     *\n     * @param value The header value\n     * @param buf The buffer from which to parse.\n     * @param offset The offset into the buffer from which to parse.\n     * @param length The number of header bytes to parse.\n     * @return The integer value of the octal bytes.\n     */\n    public static int getOctalBytes(long value, byte[] buf, int offset, int length) {\n        int    idx = length - 1;\n\n        buf[offset + idx] = 0;\n        --idx;\n        buf[offset + idx] = (byte) ' ';\n        --idx;\n\n        if (value == 0) {\n            buf[offset + idx] = (byte) '0';\n            --idx;\n        } else {\n            for (long val = value; idx >= 0 && val > 0; --idx) {\n                buf[offset + idx] = (byte) ((byte) '0' + (byte) (val & 7));\n                val = val >> 3;\n            }\n        }\n\n        for (; idx >= 0; --idx) {\n            buf[offset + idx] = (byte) ' ';\n        }\n\n        return offset + length;\n    }\n\n    /**\n     * Parse an octal long integer from a header buffer.\n     *\n     * @param value The header value\n     * @param buf The buffer from which to parse.\n     * @param offset The offset into the buffer from which to parse.\n     * @param length The number of header bytes to parse.\n     * @return The long value of the octal bytes.\n     */\n    public static int getLongOctalBytes(long value, byte[] buf, int offset, int length) {\n        byte[] temp = new byte[length + 1];\n\n        getOctalBytes(value, temp, 0, length + 1);\n        System.arraycopy(temp, 0, buf, offset, length);\n\n        return offset + length;\n    }\n\n    /**\n     * Parse the checksum octal integer from a header buffer.\n     *\n     * @param value The header value\n     * @param buf The buffer from which to parse.\n     * @param offset The offset into the buffer from which to parse.\n     * @param length The number of header bytes to parse.\n     * @return The integer value of the entry's checksum.\n     */\n    public static int getCheckSumOctalBytes(long value, byte[] buf, int offset, int length) {\n        getOctalBytes(value, buf, offset, length);\n\n        buf[offset + length - 1] = (byte) ' ';\n        buf[offset + length - 2] = 0;\n\n        return offset + length;\n    }\n\n    /**\n     * Compute the checksum of a tar entry header.\n     *\n     * @param buf The tar entry's header buffer.\n     * @return The computed checksum.\n     */\n    public static long computeCheckSum(byte[] buf) {\n        long sum = 0;\n\n        for (byte b : buf) {\n            sum += BYTE_MASK & b;\n        }\n\n        return sum;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/udf/UdfFormatProvider.java",
    "content": "package com.mucommander.commons.file.impl.udf;\n\nimport java.io.IOException;\n\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.ArchiveFormatProvider;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\nimport com.mucommander.commons.file.impl.SevenZipJBindingROArchiveFile;\n\nimport net.sf.sevenzipjbinding.ArchiveFormat;\n\npublic class UdfFormatProvider implements ArchiveFormatProvider {\n\n    private static final String[] EXTENSIONS = { \".udf\" };\n\n    private final static ExtensionFilenameFilter FILENAME_FILTER = new ExtensionFilenameFilter(EXTENSIONS);\n\n    private final static byte[] SIGNATURE = {};\n\n    @Override\n    public AbstractArchiveFile getFile(AbstractFile file) throws IOException {\n        return new SevenZipJBindingROArchiveFile(file, ArchiveFormat.UDF, SIGNATURE);\n    }\n\n    @Override\n    public FilenameFilter getFilenameFilter() {\n        return FILENAME_FILTER;\n    }\n\n    @Override\n    public String[] getFileExtensions() {\n        return EXTENSIONS;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/vsphere/ManagedObjectReferenceWrapper.java",
    "content": "package com.mucommander.commons.file.impl.vsphere;\r\n\r\nimport com.vmware.vim25.ManagedObjectReference;\r\n\r\n/**\r\n * A wrapper for ManagedObjectReferenceWrapper that adds equals and hashCode for it.\r\n * \r\n * @author Yuval Kohavi, yuval.kohavi@intigua.com\r\n *\r\n */\r\npublic class ManagedObjectReferenceWrapper {\r\n\tprivate final ManagedObjectReference mor;\r\n\r\n\tpublic ManagedObjectReferenceWrapper(ManagedObjectReference mor) {\r\n\t\tthis.mor = mor;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic int hashCode() {\r\n\t\tfinal int prime = 31;\r\n\t\tint result = 1;\r\n\t\tresult = prime * result\r\n\t\t\t\t+ ((mor.getValue() == null) ? 0 : mor.getValue().hashCode());\r\n\t\tresult = prime * result\r\n\t\t\t\t+ ((mor.getType() == null) ? 0 : mor.getType().hashCode());\r\n\t\treturn result;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic boolean equals(Object obj) {\r\n\t\tif (this == obj)\r\n\t\t\treturn true;\r\n\t\tif (obj == null)\r\n\t\t\treturn false;\r\n\t\tif (getClass() != obj.getClass())\r\n\t\t\treturn false;\r\n\t\tManagedObjectReferenceWrapper other = (ManagedObjectReferenceWrapper) obj;\r\n\t\tif (mor.getValue() == null) {\r\n\t\t\tif (other.mor.getValue() != null)\r\n\t\t\t\treturn false;\r\n\t\t} else if (!mor.getValue().equals(other.mor.getValue()))\r\n\t\t\treturn false;\r\n\t\tif (mor.getType() == null) {\r\n            return other.mor.getType() == null;\r\n\t\t} else return mor.getType().equals(other.mor.getType());\r\n    }\r\n\r\n\t@Override\r\n\tpublic String toString() {\r\n\t\treturn \"ManagedObjectReferenceWrapper [type=\" + mor.getType() + \", value=\" + mor.getValue() + \"]\";\r\n\t}\r\n\r\n\t/**\r\n\t * @return the mor\r\n\t */\r\n\tpublic ManagedObjectReference getMor() {\r\n\t\treturn mor;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/vsphere/VSphereClient.java",
    "content": "package com.mucommander.commons.file.impl.vsphere;\r\n\r\nimport com.mucommander.commons.util.StringUtils;\r\nimport com.vmware.vim25.*;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport javax.net.ssl.HostnameVerifier;\r\nimport javax.net.ssl.HttpsURLConnection;\r\nimport java.io.Closeable;\r\nimport java.io.IOException;\r\nimport java.security.KeyManagementException;\r\nimport java.security.NoSuchAlgorithmException;\r\nimport java.util.Arrays;\r\nimport java.util.Collections;\r\nimport java.util.List;\r\n\r\n/**\r\n * Wrapper over the vSphere API\r\n * \r\n * @author Yuval Kohavi, yuval.kohavi@intigua.com\r\n * \r\n */\r\npublic class VSphereClient implements Closeable {\r\n\r\n\tprivate static final String TYPE_SERVICE_INSTANCE = \"ServiceInstance\";\r\n\tprivate static final Logger log = LoggerFactory.getLogger(VSphereClient.class);\r\n\r\n\tprivate final String server;\r\n\tprivate final String user;\r\n\tprivate final String password;\r\n\r\n\tprivate ServiceContent serviceContent;\r\n\tprivate VimPortType vimPort;\r\n\r\n\tprivate boolean connected = false;\r\n\r\n\tprivate Integer port;\r\n\r\n\tpublic String getServer() {\r\n\t\treturn server;\r\n\t}\r\n\t\r\n\tboolean isConnected() {\r\n\t\treturn connected;\r\n\t}\r\n\r\n\tVSphereClient(String server, String user, String password) {\r\n\t\tthis.server = server;\r\n\t\tthis.user = user;\r\n\t\tthis.password = password;\r\n\t}\r\n\r\n\tprivate String getVSphereServiceUrl() {\r\n\t\tif (StringUtils.isNullOrEmpty((server))) {\r\n\t\t\tlog.warn(\"Can't construct vim service url, vSphere host name is empty\");\r\n\t\t\tthrow new IllegalArgumentException();\r\n\t\t}\r\n\r\n\t\tStringBuilder builder = new StringBuilder();\r\n\t\tbuilder.append(\"https://\");\r\n\t\tbuilder.append(server);\r\n\t\tif (port != null) {\r\n\t\t\tbuilder.append(\":\");\r\n\t\t\tbuilder.append(port);\r\n\t\t}\r\n\t\tbuilder.append(\"/sdk/vimService\");\r\n\t\treturn builder.toString();\r\n\t}\r\n\r\n\t/**\r\n\t * Establishes session with the vSphere server.\r\n\t * \r\n\t * @throws RuntimeFaultFaultMsg\r\n\t * @throws InvalidLoginFaultMsg\r\n\t * @throws InvalidLocaleFaultMsg\r\n\t *\r\n\t */\r\n\tpublic void connect() throws RuntimeFaultFaultMsg, InvalidLocaleFaultMsg, InvalidLoginFaultMsg {\r\n\t\tString connectionUrl = getVSphereServiceUrl();\r\n\r\n\t\tdoTrust();\r\n\r\n\t\tManagedObjectReference serviceInstanceMOR = getServiceInstance();\r\n\r\n\t\tVimService vimService = new VimService();\r\n\r\n\t\tlog.trace(\"Getting vimPort from vimService\");\r\n\t\t//vimPort = vimService.getVimPort();\r\n\t\tlog.trace(\"vimPort is gotT successfully\");\r\n\t\tlog.trace(\"Getting context from vimPort\");\r\n\t\t//Map<String, Object> requestContext = ((BindingProvider) vimPort).getRequestContext();\r\n\t\tlog.trace(\"Context from vimPort is got successfully\");\r\n\r\n\t\tlog.trace(\"URL to connect to vSphere host '{}'\", connectionUrl);\r\n//\t\trequestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, connectionUrl);\r\n//\t\trequestContext.put(BindingProvider.SESSION_MAINTAIN_PROPERTY, true);\r\n\t\t// do trust ? TODO\r\n/*\t\trequestContext.put(JAXWSProperties.SSL_SOCKET_FACTORY, getSSLContext()\r\n\t\t\t\t.getSocketFactory());\r\n\t\tHostnameVerifier hv = new HostnameVerifier() {\r\n\t\t\t@Override\r\n\t\t\tpublic boolean verify(String urlHostName, SSLSession session) {\r\n\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t};\r\n\t\trequestContext.put(JAXWSProperties.HOSTNAME_VERIFIER, hv);\r\n*/\r\n\t\tlog.trace(\"Retrieving service content\");\r\n\r\n\t\tserviceContent = vimPort.retrieveServiceContent(serviceInstanceMOR);\r\n\r\n\t\tlog.trace(\"Service content retrieved successfully\");\r\n\t\tlog.trace(\"Logging in to vSphere host '{}'\", server);\r\n\t\tUserSession userSession = vimPort.login(serviceContent.getSessionManager(), user, password, null);\r\n\t\tlog.trace(\"Logged in successfully to vSphere host '{}'\", server);\r\n\t\tconnected = true;\r\n\t}\r\n\r\n\tpublic ManagedObjectReference getServiceInstance() {\r\n\t\tManagedObjectReference serviceInstanceMOR = new ManagedObjectReference();\r\n\t\tserviceInstanceMOR.setType(TYPE_SERVICE_INSTANCE);\r\n\t\tserviceInstanceMOR.setValue(TYPE_SERVICE_INSTANCE);\r\n\t\treturn serviceInstanceMOR;\r\n\t}\r\n\r\n\tprivate void doTrust() {\r\n\t\tHostnameVerifier hv = (urlHostName, session) -> true;\r\n\t\ttry {\r\n\t\t\ttrustAllHttpsCertificates();\r\n\t\t} catch (NoSuchAlgorithmException e) {\r\n\t\t\tthrow new IllegalStateException(\"SSL init problems\", e);\r\n\t\t}\r\n\t\tHttpsURLConnection.setDefaultHostnameVerifier(hv);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void close() throws IOException {\r\n\t\ttry {\r\n\t\t\tdisconnect();\r\n\t\t} catch (RuntimeFaultFaultMsg e) {\r\n\t\t\tthrow new IOException(e);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Disconnects the user session.\r\n\t * \r\n\t * @throws RuntimeFaultFaultMsg\r\n\t * \r\n\t */\r\n\tpublic void disconnect() throws RuntimeFaultFaultMsg {\r\n\t\tif (connected) {\r\n\t\t\tlog.trace(\"Logging out from vSphere host '{}'\", server);\r\n\t\t\tvimPort.logout(serviceContent.getSessionManager());\r\n\t\t\tlog.trace(\"Logged out successfully from vSphere host '{}'\", server);\r\n\t\t}\r\n\t\tconnected = false;\r\n\t}\r\n\r\n\tpublic ManagedObjectReference findVmByUuid(String uuid, boolean instanceUuid) throws RuntimeFaultFaultMsg {\r\n\t\treturn vimPort.findByUuid(this.serviceContent.getSearchIndex(), null, uuid, true, instanceUuid);\r\n\t}\r\n\t\r\n\tpublic ManagedObjectReference findVmByIp(String ip) throws RuntimeFaultFaultMsg {\r\n\t\treturn vimPort.findByIp(this.serviceContent.getSearchIndex(), null, ip, true);\r\n\t}\r\n\r\n\t/* taken from vmware samples */\r\n\tpublic Object[] getProperties(ManagedObjectReference moRef,\r\n\t\t\tString... properties) throws\r\n            InvalidPropertyFaultMsg, RuntimeFaultFaultMsg {\r\n\t\t// PropertySpec specifies what properties to\r\n\t\t// retrieve and from type of Managed Object\r\n\t\tPropertySpec pSpec = new PropertySpec();\r\n\t\tpSpec.setType(moRef.getType());\r\n\t\tpSpec.getPathSet().addAll(Arrays.asList(properties));\r\n\r\n\t\t// ObjectSpec specifies the starting object and\r\n\t\t// any TraversalSpecs used to specify other objects\r\n\t\t// for consideration\r\n\t\tObjectSpec oSpec = new ObjectSpec();\r\n\t\toSpec.setObj(moRef);\r\n\r\n\t\t// PropertyFilterSpec is used to hold the ObjectSpec and\r\n\t\t// PropertySpec for the call\r\n\t\tPropertyFilterSpec pfSpec = new PropertyFilterSpec();\r\n\t\tpfSpec.getPropSet().addAll(Collections.singletonList(pSpec));\r\n\t\tpfSpec.getObjectSet().addAll(Collections.singletonList(oSpec));\r\n\r\n\t\t// retrieveProperties() returns the properties\r\n\t\t// selected from the PropertyFilterSpec\r\n\t\tList<ObjectContent> ocs = vimPort.retrieveProperties(\r\n\t\t\t\tserviceContent.getPropertyCollector(),\r\n\t\t\t\t  Collections.singletonList(pfSpec));\r\n\r\n\t\t// Return value, one object for each property specified\r\n\t\tObject[] ret = new Object[properties.length];\r\n\r\n\t\tif (ocs != null) {\r\n\t\t\tfor (ObjectContent oc : ocs) {\r\n\r\n\t\t\t\tList<DynamicProperty> dps = oc.getPropSet();\r\n\t\t\t\tif (dps != null) {\r\n\t\t\t\t\tfor (DynamicProperty dp : dps) {\r\n\t\t\t\t\t\t// find property path index\r\n\t\t\t\t\t\tfor (int p = 0; p < ret.length; ++p) {\r\n\t\t\t\t\t\t\tif (properties[p].equals(dp.getName())) {\r\n\t\t\t\t\t\t\t\tret[p] = dp.getVal();\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn ret;\r\n\t}\r\n\r\n\tprivate void trustAllHttpsCertificates() throws NoSuchAlgorithmException {\r\n\t\tjavax.net.ssl.SSLContext sc = getSSLContext();\r\n\t\tjavax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(sc\r\n\t\t\t\t.getSocketFactory());\r\n\t}\r\n\r\n\tprivate javax.net.ssl.SSLContext getSSLContext() {\r\n\t\t// create a trust manager that does not validate certificate chains:\r\n\t\tjavax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1];\r\n\t\tjavax.net.ssl.TrustManager tm = new TrustAllTrustManager();\r\n\t\ttrustAllCerts[0] = tm;\r\n\r\n\t\ttry {\r\n\r\n\t\t\tjavax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext.getInstance(\"SSL\");\r\n\t\t\tjavax.net.ssl.SSLSessionContext sslsc = sc.getServerSessionContext();\r\n\t\t\tsslsc.setSessionTimeout(0);\r\n\t\t\tsc.init(null, trustAllCerts, null);\r\n\t\t\treturn sc;\r\n\t\t} catch (KeyManagementException | NoSuchAlgorithmException e) {\r\n\t\t\tthrow new IllegalStateException(\"SSL init problems\", e);\r\n\t\t}\r\n\t}\r\n\r\n\tprivate class TrustAllTrustManager implements javax.net.ssl.TrustManager,\r\n\t\t\tjavax.net.ssl.X509TrustManager {\r\n\r\n\t\t@Override\r\n\t\tpublic java.security.cert.X509Certificate[] getAcceptedIssuers() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\t@Override\r\n\t\tpublic void checkServerTrusted(\r\n\t\t\t\tjava.security.cert.X509Certificate[] certs, String authType) {\r\n\t\t}\r\n\r\n\t\t@Override\r\n\t\tpublic void checkClientTrusted(\r\n\t\t\t\tjava.security.cert.X509Certificate[] certs, String authType) {\r\n\t\t}\r\n\t}\r\n\r\n\tpublic VimPortType getVimPort() {\r\n\t\treturn this.vimPort;\r\n\t}\r\n\r\n\tpublic ServiceContent getServiceContent() {\r\n\t\treturn this.serviceContent;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/vsphere/VSphereFile.java",
    "content": "package com.mucommander.commons.file.impl.vsphere;\r\n\r\nimport com.mucommander.commons.file.*;\r\nimport com.mucommander.commons.file.connection.ConnectionHandler;\r\nimport com.mucommander.commons.file.connection.ConnectionHandlerFactory;\r\nimport com.mucommander.commons.file.connection.ConnectionPool;\r\nimport com.mucommander.commons.file.util.PathUtils;\r\nimport com.mucommander.commons.io.FileTransferException;\r\nimport com.mucommander.commons.io.RandomAccessInputStream;\r\nimport com.mucommander.commons.io.RandomAccessOutputStream;\r\nimport com.mucommander.commons.io.StreamUtils;\r\nimport com.vmware.vim25.*;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport javax.net.ssl.HttpsURLConnection;\r\nimport javax.xml.datatype.DatatypeConfigurationException;\r\nimport javax.xml.datatype.DatatypeFactory;\r\nimport javax.xml.datatype.XMLGregorianCalendar;\r\nimport java.io.*;\r\nimport java.net.*;\r\nimport java.rmi.RemoteException;\r\nimport java.util.*;\r\n\r\n/**\r\n * VSphereFile provides access to files on virtual machines on vSphere 5+\r\n * servers.\r\n * \r\n * <p>\r\n * The associated {@link FileURL} scheme is {@link FileProtocols#VSPHERE}. The\r\n * host part of the URL designates the vSphere server. Credentials must be\r\n * specified in the login and password parts as vSphere servers require a login\r\n * and password. The path separator is '/', even on windows virtual machines.\r\n * \r\n * The first path of the path is the identifier of the guest VM (see below) we\r\n * want to access. That part may also contain username and password for the\r\n * guest VMs - though it is less secure, so it is better to use the GUI dialog.\r\n * \r\n * Note that the machine credentials cab also be set as a property named\r\n * \"guestCredentials\" of this object.\r\n * \r\n * The identifier of a VM can be one of the following: - Instance UUID - BIOS\r\n * UUID - IP address\r\n * \r\n * Note that even if you use IP to identify the machine, you don't need network\r\n * connectivity for accessing the machine. all access is done via vSphere APIs.\r\n * \r\n * <p>\r\n * Here are a few examples of valid vSphere URLs: <code>\r\n * vsphere://vsphere5-server/501fc8db-f9dc-562b-6310-3c6b7ace2377/C:<br>\r\n * vsphere://admin:pass@vsphere5-server/501fc8db-f9dc-562b-6310-3c6b7ace2377/C:<br>\r\n * vsphere://admin:pass@vsphere5-server/guestadmin:guestpass@501fc8db-f9dc-562b-6310-3c6b7ace2377/C:<br>\r\n * </code>\r\n * \r\n * <p>\r\n * Access to vSphere files is provided by the <code>vim25</code> library\r\n * distributed under the VMware Software Development Kit (SDK) License\r\n * Agreement. See: http://www.vmware.com/go/vwssdk-redistribution-info\r\n * \r\n * @see ConnectionPool\r\n * @author Yuval Kohavi, yuval.kohavi@intigua.com\r\n */\r\npublic class VSphereFile extends ProtocolFile implements ConnectionHandlerFactory {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(VSphereFile.class);\r\n\r\n\tpublic static final String GUEST_CREDENTIALS = \"guestCredentials\";\r\n\r\n\tprivate static final String SEPARATOR = \"/\";\r\n\r\n\tprivate String guestPass;\r\n\r\n\tprivate String guestUser;\r\n\r\n\tprivate String vmIdentifier;\r\n\r\n\tprivate String pathInsideVm;\r\n\r\n\tprivate ManagedObjectReference vm;\r\n\r\n\tprivate NamePasswordAuthentication credentials;\r\n\r\n\tprivate ManagedObjectReference guestOperationsManager;\r\n\r\n\tprivate long size = -1;\r\n\r\n\tprivate boolean isFile;\r\n\r\n\tprivate boolean isSymLink;\r\n\r\n\tprivate boolean isDir;\r\n\r\n\tprivate long date;\r\n\r\n\tprivate VSphereFile parent;\r\n\r\n\tprivate String guestOsId;\r\n\r\n\tpublic VSphereFile(FileURL url) throws IOException {\r\n\t\tsuper(url);\r\n\t\tVsphereConnHandler connHandler = null;\r\n\t\ttry {\r\n\t\t\tsetPath(url.getPath());\r\n\r\n\t\t\tconnHandler = getConnHandler();\r\n\t\t\tguestOperationsManager = connHandler.getClient()\r\n\t\t\t\t\t.getServiceContent().getGuestOperationsManager();\r\n\r\n\t\t\tgetMor(connHandler);\r\n\t\t\tfixPathInVmIfNeeded(connHandler);\r\n\r\n\t\t\tcheckAttributes(connHandler);\r\n\t\t} catch (RuntimeFaultFaultMsg | FileFaultFaultMsg | GuestOperationsFaultFaultMsg | TaskInProgressFaultMsg |\r\n\t\t\t\tInvalidPropertyFaultMsg | URISyntaxException | InvalidStateFaultMsg e) {\r\n\t\t\ttranslateandLogException(e);\r\n\t\t} finally {\r\n\t\t\treleaseConnHandler(connHandler);\r\n\t\t}\r\n\r\n\t}\r\n\r\n\tprivate void releaseConnHandler(VsphereConnHandler connHandler) {\r\n\t\tif (connHandler != null) {\r\n\t\t\tconnHandler.releaseLock();\r\n\t\t}\r\n\t}\r\n\r\n\tprivate VSphereFile(FileURL url, VSphereFile related)\r\n\t\t\tthrows URISyntaxException, IOException, RuntimeFaultFaultMsg,\r\n\t\t\tInvalidPropertyFaultMsg, FileFaultFaultMsg,\r\n\t\t\tGuestOperationsFaultFaultMsg, InvalidStateFaultMsg,\r\n\t\t\tTaskInProgressFaultMsg {\r\n\t\tsuper(url);\r\n\t\tsetPath(url.getPath());\r\n\r\n\t\tthis.vm = related.vm;\r\n\t\tthis.guestOsId = related.guestOsId;\r\n\t\tthis.guestOperationsManager = related.guestOperationsManager;\r\n\r\n\t\tfixPathInVmIfNeeded(null);\r\n\r\n\t\tVsphereConnHandler connHandler = null;\r\n\t\ttry {\r\n\t\t\tconnHandler = getConnHandler();\r\n\t\t\tcheckAttributes(connHandler);\r\n\t\t} finally {\r\n\t\t\treleaseConnHandler(connHandler);\r\n\t\t}\r\n\t}\r\n\r\n\tpublic VSphereFile(FileURL url, VSphereFile parent,\r\n\t\t\tGuestFileInfo guestFileInfo) throws RuntimeFaultFaultMsg,\r\n\t\t\tIOException,\r\n\t\t\tInvalidPropertyFaultMsg, URISyntaxException {\r\n\t\tsuper(url);\r\n\t\tsetPath(url.getPath());\r\n\r\n\t\tthis.parent = parent;\r\n\t\tthis.vm = parent.vm;\r\n\t\tthis.guestOsId = parent.guestOsId;\r\n\t\tthis.guestOperationsManager = parent.guestOperationsManager;\r\n\r\n\t\tfixPathInVmIfNeeded(null);\r\n\r\n\t\tupdateAttributes(guestFileInfo);\r\n\r\n\t}\r\n\r\n\tprivate void getMor(VsphereConnHandler connHandler)\r\n\t\t\tthrows RuntimeFaultFaultMsg {\r\n\t\tvm = connHandler.getClient().findVmByIp(vmIdentifier);\r\n\t\tif (vm == null) {\r\n\t\t\tvm = connHandler.getClient().findVmByUuid(vmIdentifier, true);\r\n\t\t}\r\n\t\tif (vm == null) {\r\n\t\t\tvm = connHandler.getClient().findVmByUuid(vmIdentifier, false);\r\n\t\t}\r\n\r\n\t\tif (vm == null) {\r\n\t\t\tthrow new IllegalArgumentException(\"Machine identifier \"\r\n\t\t\t\t\t+ vmIdentifier + \" not found.\");\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void fixPathInVmIfNeeded(VsphereConnHandler connHandler)\r\n\t\t\tthrows RuntimeFaultFaultMsg, RemoteException, InvalidPropertyFaultMsg {\r\n\r\n\t\tif (this.guestOsId == null) {\r\n\t\t\tif (connHandler != null) {\r\n\t\t\t\tthis.guestOsId = (String) connHandler.getClient()\r\n\t\t\t\t\t\t.getProperties(vm, \"config.guestId\")[0];\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tboolean isWin = false;\r\n\t\tif (this.guestOsId != null) {\r\n\t\t\t// is it a windows machine ?\r\n\t\t\t// see possible values for guest id here:\r\n\t\t\t// http://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.vm.GuestOsDescriptor.GuestOsIdentifier.html\r\n\t\t\tisWin = guestOsId.startsWith(\"win\");\r\n\t\t}\r\n\r\n\t\tif (pathInsideVm.isEmpty()) {\r\n\r\n\t\t\tif (isWin) {\r\n\t\t\t\t// we assume that \"C:\" is a good default for windows\r\n\t\t\t\tpathInsideVm = \"C:\";\r\n\t\t\t} else {\r\n\t\t\t\tpathInsideVm = \"/\";\r\n\t\t\t}\r\n\r\n\t\t\t// set the url to reflect the path inside the vm\r\n\t\t\tfileURL.setPath(fileURL.getPath() + pathInsideVm);\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void setPath(String path) throws URISyntaxException, IOException {\r\n\t\t// path starts with a /\r\n\r\n\t\t// get first component:\r\n\t\tint index = path.indexOf('/', 1);\r\n\t\tString first = path.substring(1, index);\r\n\t\tString rest = path.substring(index + 1);\r\n\r\n\t\t// the first part of the path is very similar to a url, due to the fact\r\n\t\t// it\r\n\t\t// may contain guest credentials. So I use the URI class to help me\r\n\t\t// parse it.\r\n\t\t// first as a url of its own. http = dymm\r\n\t\tURI url2 = new URI(\"dummy://\" + first);\r\n\t\tString uinfo;\r\n\t\tcredentials = new NamePasswordAuthentication();\r\n\t\tvmIdentifier = url2.getHost();\r\n\r\n\t\tString guestCred = fileURL.getProperty(GUEST_CREDENTIALS);\r\n\t\tif ((guestCred != null) && (!guestCred.isEmpty())) {\r\n\t\t\tuinfo = guestCred;\r\n\t\t} else {\r\n\t\t\tuinfo = url2.getUserInfo();\r\n\t\t}\r\n\r\n\t\tif (uinfo == null) {\r\n\t\t\tthrow new IOException(\r\n\t\t\t\t\t\"No guest credentials provided. please start the connection from the UI\");\r\n\t\t}\r\n\r\n\t\tint indexOf = uinfo.indexOf(\":\");\r\n\t\tif (indexOf == -1) {\r\n\t\t\tthrow new IOException(\r\n\t\t\t\t\t\"No guest credentials provided. please start the connection from the UI\");\r\n\t\t}\r\n\r\n\t\tguestUser = uinfo.substring(0, indexOf);\r\n\t\tguestPass = uinfo.substring(indexOf + 1);\r\n\t\tcredentials.setInteractiveSession(false);\r\n\t\tcredentials.setUsername(guestUser);\r\n\t\tcredentials.setPassword(guestPass);\r\n\r\n\t\tpathInsideVm = rest;\r\n\r\n\t}\r\n\r\n\tprivate ManagedObjectReference getFileManager(VsphereConnHandler connHandler)\r\n\t\t\tthrows RemoteException, InvalidPropertyFaultMsg, RuntimeFaultFaultMsg {\r\n\t\tManagedObjectReference fileManager = (ManagedObjectReference) connHandler\r\n\t\t\t\t.getClient().getProperties(guestOperationsManager,\r\n\t\t\t\t\t\t\"fileManager\")[0];\r\n\t\treturn fileManager;\r\n\t}\r\n\r\n\tprivate void checkAttributes(VsphereConnHandler connHandler)\r\n\t\t\tthrows IOException, FileFaultFaultMsg,\r\n\t\t\tGuestOperationsFaultFaultMsg, InvalidStateFaultMsg,\r\n\t\t\tRuntimeFaultFaultMsg, TaskInProgressFaultMsg,\r\n\t\t\tInvalidPropertyFaultMsg {\r\n\r\n\t\tManagedObjectReference fileManager = getFileManager(connHandler);\r\n\t\tGuestListFileInfo res;\r\n\t\ttry {\r\n\t\t\tres = connHandler\r\n\t\t\t\t\t.getClient()\r\n\t\t\t\t\t.getVimPort()\r\n\t\t\t\t\t.listFilesInGuest(fileManager, vm, credentials,\r\n\t\t\t\t\t\t\tgetPathInVm(), null, null, null);\r\n\t\t} catch (Exception e) {\r\n\t\t\tif (isFileNotFound(e)) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\tthrow e;\r\n\t\t}\r\n\t\tif (res.getFiles().size() == 1) {\r\n\t\t\t// only one result - it's a file\r\n\t\t\tGuestFileInfo guestFileInfo = res.getFiles().get(0);\r\n\r\n\t\t\tupdateAttributes(guestFileInfo);\r\n\t\t} else {\r\n\t\t\t// more than one result - it's a directory.\r\n\t\t\t// find the entry for \".\"\r\n\t\t\tfor (GuestFileInfo f : res.getFiles()) {\r\n\t\t\t\tif (f.getPath().equals(\".\")) {\r\n\t\t\t\t\tupdateAttributes(f);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t}\r\n\r\n\tprivate boolean isFileNotFound(Exception e) {\r\n//\t\tDetail detail = e.getFault().getDetail();\r\n//\t\tNodeList childNodes = detail.getChildNodes();\r\n//\r\n//\t\tfor (int i = 0; i < childNodes.getLength(); ++i) {\r\n//\t\t\t// I hate soap...\r\n//\t\t\tif (childNodes.item(i).getNodeName().equals(\"FileNotFoundFault\")) {\r\n//\t\t\t\treturn true;\r\n//\t\t\t}\r\n//\t\t}\r\n\t\treturn false;\r\n\t}\r\n\r\n\tvoid updateAttributes(GuestFileInfo guestFileInfo) {\r\n\t\tif (guestFileInfo.getType().equals(GuestFileType.FILE.value())) {\r\n\t\t\tisFile = true;\r\n\t\t\tsize = guestFileInfo.getSize();\r\n\t\t} else if (guestFileInfo.getType().equals(GuestFileType.SYMLINK.value())) {\r\n\t\t\tisSymLink = true;\r\n\t\t} else if (guestFileInfo.getType().equals(GuestFileType.DIRECTORY.value())) {\r\n\t\t\tisDir = true;\r\n\t\t}\r\n\t\tdate = guestFileInfo.getAttributes().getModificationTime().toGregorianCalendar().getTimeInMillis();\r\n\r\n\t}\r\n\r\n\tprivate VsphereConnHandler getConnHandler() throws IOException {\r\n\t\tVsphereConnHandler connHandler = (VsphereConnHandler) ConnectionPool\r\n\t\t\t\t.getConnectionHandler(this, fileURL, true);\r\n\t\ttry {\r\n\t\t\tconnHandler.checkConnection();\r\n\t\t} catch (RuntimeException | IOException e) {\r\n\t\t\treleaseConnHandler(connHandler);\r\n\t\t\tthrow e;\r\n\t\t}\r\n\t\treturn connHandler;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic ConnectionHandler createConnectionHandler(FileURL location) {\r\n\t\treturn new VsphereConnHandler(location);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic long getLastModifiedDate() {\r\n\t\treturn date;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void setLastModifiedDate(long lastModified) throws IOException {\r\n\r\n\t\tVsphereConnHandler connHandler = null;\r\n\t\ttry {\r\n\t\t\tGuestFileAttributes gfa = new GuestFileAttributes();\r\n\t\t\tgfa.setModificationTime(getTimeToXmlTime(lastModified));\r\n\t\t\tconnHandler = getConnHandler();\r\n\t\t\tconnHandler.getClient().getVimPort().changeFileAttributesInGuest(\r\n\t\t\t\t\tgetFileManager(connHandler), vm, credentials, getPathInVm(), gfa);\r\n\t\t} catch (FileFaultFaultMsg | RuntimeFaultFaultMsg | GuestOperationsFaultFaultMsg | InvalidStateFaultMsg | TaskInProgressFaultMsg | InvalidPropertyFaultMsg | DatatypeConfigurationException e) {\r\n\t\t\ttranslateandLogException(e);\r\n\t\t} finally {\r\n\t\t\treleaseConnHandler(connHandler);\r\n\t\t}\r\n\r\n\t}\r\n\r\n\tprivate XMLGregorianCalendar getTimeToXmlTime(long lastModified) throws DatatypeConfigurationException {\r\n\t\tGregorianCalendar gc = new GregorianCalendar(TimeZone.getTimeZone(\"UTC\"));\r\n\t\tgc.setTime(new Date(lastModified));\r\n\t\treturn DatatypeFactory.newInstance().newXMLGregorianCalendar(gc);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic long getSize() {\r\n\t\treturn size;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic AbstractFile getParent() {\r\n\t\tif (parent != null) {\r\n\t\t\treturn parent;\r\n\t\t}\r\n\t\tString rootPath = getRootPath();\r\n\r\n\t\tif (rootPath.equals(pathInsideVm) || pathInsideVm.equals(\"/\")) {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\ttry {\r\n\t\t\tparent = new VSphereFile(fileURL.getParent(), this);\r\n\t\t} catch (URISyntaxException | IOException | InvalidPropertyFaultMsg | RuntimeFaultFaultMsg | InvalidStateFaultMsg\r\n\t\t\t\t| TaskInProgressFaultMsg | FileFaultFaultMsg | GuestOperationsFaultFaultMsg e) {\r\n\t\t\treturn null;\r\n\t\t}\r\n\t\treturn parent;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void setParent(AbstractFile parent) {\r\n\t\tthis.parent = (VSphereFile) parent;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic boolean exists() {\r\n\t\treturn isDir || isFile || isSymLink;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic FilePermissions getPermissions() {\r\n\t\treturn isDir ? FilePermissions.DEFAULT_DIRECTORY_PERMISSIONS\r\n\t\t\t\t: FilePermissions.DEFAULT_FILE_PERMISSIONS;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic PermissionBits getChangeablePermissions() {\r\n\t\treturn PermissionBits.EMPTY_PERMISSION_BITS;\r\n\t}\r\n\r\n\t@Override\r\n\t@UnsupportedFileOperation\r\n\tpublic void changePermission(int access, int permission, boolean enabled)\r\n\t\t\tthrows IOException {\r\n\t\tthrow new UnsupportedFileOperationException(\r\n\t\t\t\tFileOperation.CHANGE_PERMISSION);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic String getOwner() {\r\n\t\treturn null;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic boolean canGetOwner() {\r\n\t\treturn false;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic String getGroup() {\r\n\t\treturn null;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic boolean canGetGroup() {\r\n\t\treturn false;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic boolean isDirectory() {\r\n\t\treturn isDir;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic boolean isSymlink() {\r\n\t\treturn isSymLink;\r\n\t}\r\n\r\n\t@Override\r\n    public boolean isSystem() {\r\n        return false;\r\n    }\r\n\r\n\t@Override\r\n\tpublic AbstractFile[] ls() throws IOException {\r\n\t\tList<GuestFileInfo> fileInfos = new ArrayList<>();\r\n\t\tint index = 0;\r\n\t\tVsphereConnHandler connHandler = null;\r\n\t\ttry {\r\n\r\n\t\t\tconnHandler = getConnHandler();\r\n\r\n\t\t\tManagedObjectReference fileManager = getFileManager(connHandler);\r\n\t\t\tboolean haveRemaining;\r\n\t\t\tdo {\r\n\t\t\t\tGuestListFileInfo res = connHandler\r\n\t\t\t\t\t\t.getClient()\r\n\t\t\t\t\t\t.getVimPort()\r\n\t\t\t\t\t\t.listFilesInGuest(fileManager, vm, credentials,\r\n\t\t\t\t\t\t\t\tgetPathInVm(), index, null, null);\r\n\t\t\t\thaveRemaining = (res.getRemaining() != 0);\r\n\r\n\t\t\t\tfileInfos.addAll(res.getFiles());\r\n\t\t\t\tindex = fileInfos.size();\r\n\t\t\t} while (haveRemaining);\r\n\r\n\t\t\tString parentPath = PathUtils.removeTrailingSeparator(fileURL\r\n\t\t\t\t\t.getPath()) + SEPARATOR;\r\n\r\n\t\t\tCollection<AbstractFile> res = new ArrayList<>();\r\n\t\t\tfor (GuestFileInfo f : fileInfos) {\r\n\t\t\t\tfinal String name = getFileName(f.getPath());\r\n\t\t\t\tif (name.equals(\".\") || name.equals(\"..\")) {\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tFileURL childURL = (FileURL) fileURL.clone();\r\n\t\t\t\tchildURL.setPath(parentPath + name);\r\n\r\n\t\t\t\tAbstractFile newFile = new VSphereFile(childURL, this, f);\r\n\t\t\t\tres.add(newFile);\r\n\t\t\t}\r\n\t\t\treturn res.toArray(new AbstractFile[0]);\r\n\t\t} catch (FileFaultFaultMsg | GuestOperationsFaultFaultMsg | InvalidStateFaultMsg | RuntimeFaultFaultMsg | TaskInProgressFaultMsg | InvalidPropertyFaultMsg | URISyntaxException e) {\r\n\t\t\ttranslateandLogException(e);\r\n\t\t} finally {\r\n\t\t\treleaseConnHandler(connHandler);\r\n\t\t}\r\n\t\t// we never get here..\r\n\t\treturn null;\r\n\t}\r\n\r\n\tprivate String getFileName(String path) {\r\n\t\t// don't search for the slash as the last character.\r\n\t\tint lastIndex = path.length() - 2;\r\n\t\t// find the last \"\\\\\" or \"/\"\r\n\t\tint index = Math.max(path.lastIndexOf('\\\\', lastIndex),\r\n\t\t\t\tpath.lastIndexOf('/', lastIndex));\r\n\t\tif (index == -1) {\r\n\t\t\treturn path;\r\n\t\t}\r\n\r\n\t\treturn path.substring(index + 1);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void mkdir() throws IOException {\r\n\t\tVsphereConnHandler connHandler = null;\r\n\t\ttry {\r\n\t\t\tconnHandler = getConnHandler();\r\n\t\t\tconnHandler\r\n\t\t\t\t\t.getClient()\r\n\t\t\t\t\t.getVimPort()\r\n\t\t\t\t\t.makeDirectoryInGuest(getFileManager(connHandler), vm,\r\n\t\t\t\t\t\t\tcredentials, getPathInVm(), false);\r\n\t\t} catch (FileFaultFaultMsg | GuestOperationsFaultFaultMsg | RuntimeFaultFaultMsg | InvalidStateFaultMsg | InvalidPropertyFaultMsg | TaskInProgressFaultMsg e) {\r\n\t\t\ttranslateandLogException(e);\r\n\t\t} finally {\r\n\t\t\treleaseConnHandler(connHandler);\r\n\t\t}\r\n\t}\r\n\r\n\t@Override\r\n\tpublic InputStream getInputStream() throws IOException {\r\n\t\tVsphereConnHandler connHandler = null;\r\n\t\ttry {\r\n\t\t\tconnHandler = getConnHandler();\r\n\t\t\tManagedObjectReference fileManager = getFileManager(connHandler);\r\n\r\n\t\t\tFileTransferInformation fileDlInfo = connHandler\r\n\t\t\t\t\t.getClient()\r\n\t\t\t\t\t.getVimPort()\r\n\t\t\t\t\t.initiateFileTransferFromGuest(fileManager, vm,\r\n\t\t\t\t\t\t\tcredentials, getPathInVm());\r\n\t\t\tString fileDlUrl = fileDlInfo.getUrl().replace(\"*\",\r\n\t\t\t\t\tconnHandler.getClient().getServer());\r\n\r\n\t\t\t// http://stackoverflow.com/questions/921262/how-to-download-and-save-a-file-from-internet-using-java\r\n\t\t\tURL website = new URI(fileDlUrl).toURL();\r\n\t\t\treturn website.openStream();\r\n\r\n\t\t} catch (URISyntaxException | InvalidPropertyFaultMsg | RuntimeFaultFaultMsg | GuestOperationsFaultFaultMsg | FileFaultFaultMsg | TaskInProgressFaultMsg | InvalidStateFaultMsg e) {\r\n\t\t\ttranslateandLogException(e);\r\n\t\t} finally {\r\n\t\t\treleaseConnHandler(connHandler);\r\n\t\t}\r\n\t\treturn null;\r\n\r\n\t}\r\n\r\n\tprivate String getPathInVm() {\r\n\t\tif (isWinPath()) {\r\n\t\t\treturn pathInsideVm.replace(\"/\", \"\\\\\");\r\n\t\t}\r\n\t\treturn pathInsideVm;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void copyStream(InputStream in, boolean append, long length) throws FileTransferException {\r\n\t\tif (append || length == -1) {\r\n\t\t\tsuper.copyStream(in, append, length);\r\n\t\t} else {\r\n\t\t\ttry {\r\n\t\t\t\tdoCopyRemoteFileName(in, length);\r\n\t\t\t} catch (IOException e) {\r\n\t\t\t\tLOGGER.error(\"Failed copying stream\", e);\r\n\t\t\t\tthrow new FileTransferException(\r\n\t\t\t\t\t\tFileTransferException.UNKNOWN_REASON);\r\n\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void doCopyRemoteFileName(InputStream in, long length)\r\n\t\t\tthrows IOException {\r\n\t\tVsphereConnHandler connHandler = null;\r\n\t\ttry {\r\n\t\t\tconnHandler = getConnHandler();\r\n\t\t\tManagedObjectReference fileManager = getFileManager(connHandler);\r\n\r\n\t\t\tcopyFileToRemote(getPathInVm(), in, length, connHandler,\r\n\t\t\t\t\tfileManager);\r\n\t\t} catch (InvalidPropertyFaultMsg | RuntimeFaultFaultMsg | IOException e) {\r\n\t\t\ttranslateandLogException(e);\r\n\t\t} finally {\r\n\t\t\treleaseConnHandler(connHandler);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * vSphere APIs require the file size when copying a file.\r\n\t * mucommander enables file copy with known size, and also with unknown\r\n\t * size. This class supports the unknown size use case. It saves all the\r\n\t * data to a temp file and copies it on close, when its size is known.\r\n\t * \r\n\t */\r\n\tpublic class VSphereOutputStream extends FileOutputStream {\r\n\r\n\t\tprivate final ManagedObjectReference fileManager;\r\n\t\tprivate VsphereConnHandler connHandler;\r\n\t\tprivate final String fileName;\r\n\t\tprivate final File tmpFile;\r\n\r\n\t\tpublic VSphereOutputStream(VsphereConnHandler connHandler,\r\n\t\t\t\tManagedObjectReference fileManager, ManagedObjectReference vm,\r\n\t\t\t\tNamePasswordAuthentication credentials, String fileName)\r\n\t\t\t\tthrows IOException {\r\n\t\t\tthis(connHandler, fileManager, vm, credentials, File\r\n\t\t\t\t\t.createTempFile(\"tmpVixCopy\", \".tmp\"), fileName);\r\n\t\t}\r\n\r\n\t\tprivate VSphereOutputStream(VsphereConnHandler connHandler,\r\n\t\t\t\tManagedObjectReference fileManager, ManagedObjectReference vm,\r\n\t\t\t\tNamePasswordAuthentication credentials, File tmpFile,\r\n\t\t\t\tString fileName) throws IOException {\r\n\t\t\tsuper(tmpFile);\r\n\t\t\tthis.tmpFile = tmpFile;\r\n\t\t\tthis.connHandler = connHandler;\r\n\t\t\tthis.fileManager = fileManager;\r\n\t\t\tthis.fileName = fileName;\r\n\t\t}\r\n\r\n\t\t@Override\r\n\t\tpublic void close() throws IOException {\r\n\t\t\tif (connHandler == null) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\tsuper.close();\r\n\t\t\ttry (InputStream in = new FileInputStream(tmpFile)) {\r\n\t\t\t\tcopyFileToRemote(fileName, in, this.tmpFile.length(), connHandler, fileManager);\r\n\t\t\t} finally {\r\n\t\t\t\tconnHandler.releaseLock();\r\n\t\t\t\tconnHandler = null;\r\n\r\n\t\t\t\ttmpFile.delete();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void copyFileToRemote(String fileName, InputStream in, long length,\r\n\t\t\tVsphereConnHandler connHandler, ManagedObjectReference fileManager)\tthrows IOException {\r\n\t\ttry {\r\n\t\t\tString fileUploadUrl = getFileUploadUrl(fileName, length, connHandler, fileManager);\r\n\t\t\tURLConnection conn = prepareConnection(fileUploadUrl, length);\r\n\r\n\t\t\tsendFile(in, conn);\r\n\r\n\t\t\tparseResponse(conn);\r\n\r\n\t\t} catch (RuntimeFaultFaultMsg | GuestOperationsFaultFaultMsg | FileFaultFaultMsg\r\n\t\t\t\t| TaskInProgressFaultMsg | InvalidStateFaultMsg | URISyntaxException e) {\r\n\t\t\ttranslateandLogException(e);\r\n\t\t}\r\n\r\n\t}\r\n\r\n\tprivate void parseResponse(URLConnection conn) throws IOException {\r\n\t\tint responseCode = 0;\r\n\t\tString message = \"\";\r\n\t\tif (conn instanceof HttpURLConnection) {\r\n\t\t\tresponseCode = ((HttpURLConnection) conn).getResponseCode();\r\n\t\t\tmessage = ((HttpsURLConnection) conn).getResponseMessage();\r\n\t\t}\r\n\r\n\t\tif (responseCode != 200) {\r\n\t\t\tthrow new IOException(\"Failed to copy file using vsphere: \" + message);\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void sendFile(InputStream in, URLConnection conn) throws IOException {\r\n\t\tconn.connect();\r\n\r\n\t\ttry (OutputStream out = conn.getOutputStream()) {\r\n\t\t\tStreamUtils.copyStream(in, out, IO_BUFFER_SIZE);\r\n\t\t}\r\n\t}\r\n\r\n\tprivate URLConnection prepareConnection(String fileUploadUrl, long fileSize)\r\n            throws TaskInProgressFaultMsg, IOException, URISyntaxException {\r\n\r\n\t\t// http://stackoverflow.com/questions/3386832/upload-a-file-using-http-put-in-java\r\n\t\tURL url = new URI(fileUploadUrl).toURL();\r\n\t\tURLConnection conn = url.openConnection();\r\n\t\tconn.setDoInput(true);\r\n\t\tconn.setDoOutput(true);\r\n\t\tif (conn instanceof HttpURLConnection) {\r\n\t\t\t((HttpURLConnection) conn).setRequestMethod(\"PUT\");\r\n\t\t} else {\r\n\t\t\tthrow new IllegalStateException(\"Unknown connection type\");\r\n\t\t}\r\n\r\n\t\tconn.setRequestProperty(\"Content-type\", \"application/octet-stream\");\r\n\t\tconn.setRequestProperty(\"Content-length\", \"\" + fileSize);\r\n\t\treturn conn;\r\n\t}\r\n\r\n\tprivate String getFileUploadUrl(String remotePathName, long fileSize,\r\n\t\t\tVsphereConnHandler connHandler, ManagedObjectReference fileManager)\r\n\t\t\tthrows\r\n            RuntimeFaultFaultMsg, FileFaultFaultMsg,\r\n\t\t\tGuestOperationsFaultFaultMsg, InvalidStateFaultMsg,\r\n\t\t\tTaskInProgressFaultMsg {\r\n\r\n\t\tGuestFileAttributes gfa = new GuestFileAttributes();\r\n\t\tfinal boolean override = true;\r\n\t\tString fileUploadUrl = connHandler\r\n\t\t\t\t.getClient()\r\n\t\t\t\t.getVimPort()\r\n\t\t\t\t.initiateFileTransferToGuest(fileManager, vm, credentials,\r\n\t\t\t\t\t\tremotePathName, gfa, fileSize, override);\r\n\r\n\t\t// replace * with the address of the server. see vsphere docs.\r\n\t\tfileUploadUrl = fileUploadUrl.replace(\"*\", connHandler.getClient().getServer());\r\n\t\treturn fileUploadUrl;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic OutputStream getOutputStream() throws IOException {\r\n\t\tVsphereConnHandler connHandler = null;\r\n\t\ttry {\r\n\t\t\tconnHandler = getConnHandler();\r\n\t\t\tManagedObjectReference fileManager = getFileManager(connHandler);\r\n\r\n\t\t\tVSphereOutputStream c = new VSphereOutputStream(connHandler,\r\n\t\t\t\t\tfileManager, vm, credentials, getPathInVm());\r\n\t\t\t// passed owner ship\r\n\t\t\tconnHandler = null;\r\n\t\t\treturn c;\r\n\t\t} catch (InvalidPropertyFaultMsg | RuntimeFaultFaultMsg e) {\r\n\t\t\ttranslateandLogException(e);\r\n\t\t} finally {\r\n\t\t\treleaseConnHandler(connHandler);\r\n\t\t}\r\n\t\tthrow new RuntimeException(\"Should never get here\");\r\n\t}\r\n\r\n\t@Override\r\n\t@UnsupportedFileOperation\r\n\tpublic OutputStream getAppendOutputStream() throws IOException {\r\n\t\tthrow new UnsupportedFileOperationException(FileOperation.APPEND_FILE);\r\n\t}\r\n\r\n\t@Override\r\n\t@UnsupportedFileOperation\r\n\tpublic RandomAccessInputStream getRandomAccessInputStream()\r\n\t\t\tthrows IOException {\r\n\t\tthrow new UnsupportedFileOperationException(\r\n\t\t\t\tFileOperation.RANDOM_READ_FILE);\r\n\t}\r\n\r\n\t@Override\r\n\t@UnsupportedFileOperation\r\n\tpublic RandomAccessOutputStream getRandomAccessOutputStream()\r\n\t\t\tthrows IOException {\r\n\t\tthrow new UnsupportedFileOperationException(FileOperation.RANDOM_WRITE_FILE);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void delete() throws IOException {\r\n\t\tVsphereConnHandler connHandler = null;\r\n\t\ttry {\r\n\t\t\tconnHandler = getConnHandler();\r\n\t\t\tManagedObjectReference fileManager = getFileManager(connHandler);\r\n\t\t\tif (isDirectory()) {\r\n\t\t\t\tconnHandler\r\n\t\t\t\t\t\t.getClient()\r\n\t\t\t\t\t\t.getVimPort()\r\n\t\t\t\t\t\t.deleteDirectoryInGuest(fileManager, vm, credentials,\r\n\t\t\t\t\t\t\t\tgetPathInVm(), false);\r\n\t\t\t\tisDir = false;\r\n\r\n\t\t\t} else {\r\n\t\t\t\tconnHandler\r\n\t\t\t\t\t\t.getClient()\r\n\t\t\t\t\t\t.getVimPort()\r\n\t\t\t\t\t\t.deleteFileInGuest(fileManager, vm, credentials,\r\n\t\t\t\t\t\t\t\tgetPathInVm());\r\n\t\t\t\tisFile = isSymLink = false;\r\n\t\t\t}\r\n\t\t} catch (FileFaultFaultMsg | GuestOperationsFaultFaultMsg | InvalidStateFaultMsg | RuntimeFaultFaultMsg |\r\n                TaskInProgressFaultMsg | InvalidPropertyFaultMsg e) {\r\n\t\t\ttranslateandLogException(e);\r\n\t\t} finally {\r\n\t\t\treleaseConnHandler(connHandler);\r\n\t\t}\r\n\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void renameTo(AbstractFile destFile) throws IOException {\r\n\r\n\t\t// can't copy\\rename to a different host\r\n\t\t// os might be windows, so can't rename with different case.\r\n\t\tcheckRenamePrerequisites(destFile, false, false);\r\n\r\n\t\tVsphereConnHandler connHandler = null;\r\n\t\ttry {\r\n\t\t\tconnHandler = getConnHandler();\r\n\t\t\tManagedObjectReference fileManager = getFileManager(connHandler);\r\n\t\t\tif (isDirectory()) {\r\n\t\t\t\tconnHandler\r\n\t\t\t\t\t\t.getClient()\r\n\t\t\t\t\t\t.getVimPort()\r\n\t\t\t\t\t\t.moveDirectoryInGuest(fileManager, vm, credentials,\r\n\t\t\t\t\t\t\t\tgetPathInVm(),\r\n\t\t\t\t\t\t\t\t((VSphereFile) destFile).getPathInVm());\r\n\r\n\t\t\t} else {\r\n\t\t\t\tconnHandler\r\n\t\t\t\t\t\t.getClient()\r\n\t\t\t\t\t\t.getVimPort()\r\n\t\t\t\t\t\t.moveFileInGuest(fileManager, vm, credentials,\r\n\t\t\t\t\t\t\t\tgetPathInVm(),\r\n\t\t\t\t\t\t\t\t((VSphereFile) destFile).getPathInVm(), true);\r\n\t\t\t}\r\n\t\t} catch (FileFaultFaultMsg | GuestOperationsFaultFaultMsg | RuntimeFaultFaultMsg | InvalidStateFaultMsg | InvalidPropertyFaultMsg | TaskInProgressFaultMsg e) {\r\n\t\t\ttranslateandLogException(e);\r\n\t\t} finally {\r\n\t\t\treleaseConnHandler(connHandler);\r\n\t\t}\r\n\r\n\t}\r\n\r\n\tprivate static void translateandLogException(Exception e)\r\n\t\t\tthrows IOException {\r\n\t\tLOGGER.error(\"Error with vsphere remote ops\", e);\r\n\t\tthrow new IOException(e);\r\n\t}\r\n\r\n\t@Override\r\n\t@UnsupportedFileOperation\r\n\tpublic void copyRemotelyTo(AbstractFile destFile) throws IOException {\r\n\t\tthrow new UnsupportedFileOperationException(FileOperation.COPY_REMOTELY);\r\n\t}\r\n\r\n\t@Override\r\n\t@UnsupportedFileOperation\r\n\tpublic long getFreeSpace() throws IOException {\r\n\t\tthrow new UnsupportedFileOperationException(\r\n\t\t\t\tFileOperation.GET_FREE_SPACE);\r\n\t}\r\n\r\n\t@Override\r\n\t@UnsupportedFileOperation\r\n\tpublic long getTotalSpace() throws IOException {\r\n\r\n\t\tthrow new UnsupportedFileOperationException(\r\n\t\t\t\tFileOperation.GET_TOTAL_SPACE);\r\n\t}\r\n\r\n\t@Override\r\n\t@UnsupportedFileOperation\r\n\tpublic short getReplication() throws UnsupportedFileOperationException {\r\n\t\tthrow new UnsupportedFileOperationException(FileOperation.GET_REPLICATION);\r\n\t}\r\n\r\n\t@Override\r\n\t@UnsupportedFileOperation\r\n\tpublic long getBlocksize() throws UnsupportedFileOperationException {\r\n\t\tthrow new UnsupportedFileOperationException(FileOperation.GET_BLOCKSIZE);\r\n\t}\r\n\r\n\t@Override\r\n\t@UnsupportedFileOperation\r\n\tpublic void changeReplication(short replication) throws IOException {\r\n\t\tthrow new UnsupportedFileOperationException(FileOperation.CHANGE_REPLICATION);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic Object getUnderlyingFileObject() {\r\n\t\treturn null;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic AbstractFile getRoot() {\r\n\t\tFileURL rootURL = (FileURL) getURL().clone();\r\n\t\tString rootPath = getRootPath();\r\n\t\trootURL.setPath(\"/\" + vmIdentifier + \"/\" + rootPath);\r\n\r\n\t\treturn FileFactory.getFile(rootURL);\r\n\t}\r\n\r\n\tprivate String getRootPath() {\r\n\t\tif (isWinPath()) {\r\n\t\t\treturn getPathInVm().substring(0, 2);\r\n\t\t}\r\n\r\n\t\treturn \"\";\r\n\t}\r\n\r\n\tprivate boolean isWinPath() {\r\n\t\treturn (pathInsideVm.length() >= 2) && (pathInsideVm.charAt(1) == ':');\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/vsphere/VSphereProtocolProvider.java",
    "content": "package com.mucommander.commons.file.impl.vsphere;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileURL;\r\nimport com.mucommander.commons.file.ProtocolProvider;\r\n\r\nimport java.io.IOException;\r\n\r\n/**\r\n * This class is the provider for the VSphere filesystem implemented by\r\n * {@link com.mucommander.commons.file.impl.vsphere.VSphereFile}.\r\n * \r\n * @author Yuval Kohavi yuval.kohavi@intigua.com\r\n * @see com.mucommander.commons.file.impl.vsphere.VSphereFile\r\n */\r\npublic class VSphereProtocolProvider implements ProtocolProvider {\r\n\r\n\t// ///////////////////////////////////\r\n\t// ProtocolProvider Implementation //\r\n\t// ///////////////////////////////////\r\n\r\n\tpublic AbstractFile getFile(FileURL url, Object... instantiationParams) throws IOException {\r\n\t\treturn new VSphereFile(url);\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/vsphere/VsphereConnHandler.java",
    "content": "package com.mucommander.commons.file.impl.vsphere;\r\n\r\nimport java.io.IOException;\r\n\r\nimport com.mucommander.commons.file.AuthException;\r\nimport com.mucommander.commons.file.FileURL;\r\nimport com.mucommander.commons.file.connection.ConnectionHandler;\r\nimport com.vmware.vim25.InvalidLocaleFaultMsg;\r\nimport com.vmware.vim25.InvalidLoginFaultMsg;\r\nimport com.vmware.vim25.RuntimeFaultFaultMsg;\r\n\r\n/**\r\n * Manage VSphere connections.\r\n * \r\n * @author Yuval Kohavi, yuval.kohavi@intigua.com\r\n * \r\n */\r\npublic class VsphereConnHandler extends ConnectionHandler {\r\n\r\n\tprivate VSphereClient client = null;\r\n\tprivate FileURL location;\r\n\r\n\tVSphereClient getClient() {\r\n\t\treturn client;\r\n\t}\r\n\r\n\tVsphereConnHandler(FileURL serverURL) {\r\n\t\tsuper(serverURL);\r\n\t\tlocation = serverURL;\r\n\t}\r\n\r\n\tprivate void initClientIfNeeded() throws RuntimeFaultFaultMsg,\r\n\t\t\tInvalidLocaleFaultMsg, InvalidLoginFaultMsg {\r\n\t\tif (client == null) {\r\n\t\t\tclient = new VSphereClient(location.getHost(), location\r\n\t\t\t\t\t.getCredentials().getLogin(), location.getCredentials()\r\n\t\t\t\t\t.getPassword());\r\n\t\t\tclient.connect();\r\n\t\t}\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void startConnection() throws IOException {\r\n\t\ttry {\r\n\t\t\tinitClientIfNeeded();\r\n\t\t} catch (RuntimeFaultFaultMsg | InvalidLocaleFaultMsg e) {\r\n\t\t\tthrow new IOException(e);\r\n\t\t} catch (InvalidLoginFaultMsg e) {\r\n\t\t\tthrow new AuthException(location, e.getMessage());\r\n\t\t}\r\n\t}\r\n\r\n\t@Override\r\n\tpublic boolean isConnected() {\r\n\t\treturn client != null && client.isConnected();\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void closeConnection() {\r\n\t\ttry {\r\n\t\t\tclient.disconnect();\r\n\t\t} catch (RuntimeFaultFaultMsg e) {\r\n\t\t\t// nothing we can do... ignore..\r\n\t\t\te.printStackTrace();\r\n\t\t}\r\n\t\tclient = null;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void keepAlive() {\r\n\r\n\t\tif (client != null) {\r\n\t\t\ttry {\r\n\t\t\t\tdoKeepAlive();\r\n\t\t\t} catch (RuntimeFaultFaultMsg e) {\r\n\t\t\t\tclient = null;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void doKeepAlive() throws RuntimeFaultFaultMsg {\r\n\t\t// do nothing, to keep alive\r\n\t\tclient.getVimPort().currentTime(client.getServiceInstance());\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/vsphere/package.html",
    "content": "<body>\r\n  Provides an implementation of the vSphere protocol.\r\n</body>\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/webdav/WebDAVFile.java",
    "content": "package com.mucommander.commons.file.impl.webdav;\n\nimport com.github.sardine.DavResource;\nimport com.github.sardine.Sardine;\nimport com.github.sardine.SardineFactory;\nimport com.github.sardine.impl.SardineException;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.FileOperation;\nimport com.mucommander.commons.file.FilePermissions;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.PermissionBits;\nimport com.mucommander.commons.file.ProtocolFile;\nimport com.mucommander.commons.file.SimpleFilePermissions;\nimport com.mucommander.commons.file.UnsupportedFileOperation;\nimport com.mucommander.commons.file.UnsupportedFileOperationException;\nimport com.mucommander.commons.io.RandomAccessInputStream;\nimport com.mucommander.commons.io.RandomAccessOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.util.List;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\n/**\n *\n * @author Mathias\n */\npublic class WebDAVFile extends ProtocolFile {\n\n    private final Sardine sardine = SardineFactory.begin();\n    private final URI PATH;\n\n    protected AbstractFile parent;\n    private boolean parentSet;\n\n    private final static String SEPARATOR = \"/\";\n\n    WebDAVFile(FileURL fileURL) throws URISyntaxException {\n        super(fileURL);\n               \n        String scheme = \"http\";\n        \n        if (fileURL.getPort() == 443){\n            scheme = \"https\";\n        }\n\n        PATH = new URI(scheme, fileURL.getLogin() + \":\" + fileURL.getPassword(), fileURL.getHost(), fileURL.getPort(), fileURL.getPath(), null, null);\n    }\n\n    @Override\n    public long getLastModifiedDate() {\n        return 1L;\n    }\n\n    @Override\n    public void setLastModifiedDate(long lastModified) {\n        throw new UnsupportedOperationException(\"Not supported yet.\");\n    }\n\n    @Override\n    public long getSize() {\n        return 1L;\n    }\n\n    @Override\n    public AbstractFile getParent() {\n        if (!parentSet) {\n            FileURL parentFileURL = this.fileURL.getParent();\n            if (parentFileURL != null) {\n                try {\n                    parent = FileFactory.getFile(parentFileURL, this);\n                } catch (IOException e) {\n                    // No parent\n                }\n            }\n\n            parentSet = true;\n        }\n\n        return parent;\n    }\n\n    @Override\n    public void setParent(AbstractFile parent) {\n        this.parent = parent;\n        this.parentSet = true;\n    }\n\n    @Override\n    public boolean exists() {\n        List<DavResource> resources;\n        try {\n            resources = sardine.list(PATH.toString());\n        } catch (IOException ex) {\n            Logger.getLogger(WebDAVFile.class.getName()).log(Level.SEVERE, null, ex);\n            return false;\n        }\n        return !resources.isEmpty();\n    }\n\n    @Override\n    public FilePermissions getPermissions() {\n        return new SimpleFilePermissions(448);\n    }\n\n    @Override\n    public PermissionBits getChangeablePermissions() {\n        throw new UnsupportedOperationException(\"Not supported yet.\");\n    }\n\n    @Override\n    public void changePermission(int access, int permission, boolean enabled) {\n        throw new UnsupportedOperationException(\"Not supported yet.\");\n    }\n\n    @Override\n    public String getOwner() {\n        return \"N/A\";\n    }\n\n    @Override\n    public boolean canGetOwner() {\n        return false;\n    }\n\n    @Override\n    public String getGroup() {\n        return \"N/A\";\n    }\n\n    @Override\n    public boolean canGetGroup() {\n        return false;\n    }\n\n    @Override\n    public boolean isDirectory() {\n        List<DavResource> resources;\n        try {\n            resources = sardine.list(PATH.toString());\n        } catch (IOException ex) {\n            Logger.getLogger(WebDAVFile.class.getName()).log(Level.SEVERE, null, ex);\n            return true;\n        }\n\n        return !resources.isEmpty() && resources.getFirst().isDirectory();\n    }\n\n    @Override\n    public boolean isSymlink() {\n        return false;\n    }\n\n    @Override\n    public AbstractFile[] ls() throws IOException {\n\n        List<DavResource> files;\n        try {\n            files = sardine.list(PATH.toString());\n        } catch (SardineException e) {\n            return new AbstractFile[]{};\n        }\n\n        if (files == null || files.isEmpty()) {\n            return new AbstractFile[]{};\n        }\n\n        AbstractFile[] children = new AbstractFile[files.size()];\n        AbstractFile child;\n        FileURL childURL;\n        String childName;\n        int nbFiles = files.size();\n        int fileCount = 0;\n        String parentPath = fileURL.getPath();\n        if (!parentPath.endsWith(SEPARATOR)) {\n            parentPath += SEPARATOR;\n        }\n\n        for (DavResource file : files) {\n            if (file == null) {\n                continue;\n            }\n\n            childName = file.getName();\n\n            //Skip current path (Like skipping \".\" and \"..\"\n            if (parentPath.equals(file.getPath())) {\n                continue;\n            }\n\n            // Note: properties and credentials are cloned for every children's url\n            childURL = (FileURL) fileURL.clone();\n            childURL.setPath(parentPath + childName);\n\n            child = FileFactory.getFile(childURL, this, file);\n            children[fileCount++] = child;\n        }\n\n        // Create new array of the exact file count\n        if (fileCount < nbFiles) {\n            AbstractFile[] newChildren = new AbstractFile[fileCount];\n            System.arraycopy(children, 0, newChildren, 0, fileCount);\n            return newChildren;\n        }\n\n        return children;\n    }\n\n    @Override\n    public void mkdir() throws IOException {\n        throw new UnsupportedOperationException(\"Not supported yet.\");\n    }\n\n    @Override\n    public InputStream getInputStream() throws IOException {\n        throw new UnsupportedOperationException(\"Not supported yet.\");\n    }\n\n    @Override\n    public OutputStream getOutputStream() throws IOException {\n        throw new UnsupportedOperationException(\"Not supported yet.\");\n    }\n\n    @Override\n    public OutputStream getAppendOutputStream() {\n        throw new UnsupportedOperationException(\"Not supported yet.\");\n    }\n\n    @Override\n    public RandomAccessInputStream getRandomAccessInputStream() throws IOException {\n        throw new UnsupportedOperationException(\"Not supported yet.\");\n    }\n\n    @Override\n    public RandomAccessOutputStream getRandomAccessOutputStream() {\n        throw new UnsupportedOperationException(\"Not supported yet.\");\n    }\n\n    @Override\n    public void delete() throws IOException {\n        throw new UnsupportedOperationException(\"Not supported yet.\");\n    }\n\n    @Override\n    public void renameTo(AbstractFile destFile) throws IOException {\n        throw new UnsupportedOperationException(\"Not supported yet.\");\n    }\n\n    @Override\n    public void copyRemotelyTo(AbstractFile destFile) throws IOException {\n        throw new UnsupportedOperationException(\"Not supported yet.\");\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public long getFreeSpace() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_FREE_SPACE);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public long getTotalSpace() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_TOTAL_SPACE);\n    }\n\n    @Override\n    public Object getUnderlyingFileObject() {\n        throw new UnsupportedOperationException(\"Not supported yet.\");\n    }\n    @Override\n    @UnsupportedFileOperation\n    public short getReplication() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_REPLICATION);\n    }\n    @Override\n    @UnsupportedFileOperation\n    public void changeReplication(short replication) throws IOException {\n        throw new UnsupportedFileOperationException(FileOperation.CHANGE_REPLICATION);\n    }\n\n    @Override\n    @UnsupportedFileOperation\n    public long getBlocksize() throws UnsupportedFileOperationException {\n        throw new UnsupportedFileOperationException(FileOperation.GET_BLOCKSIZE);\n    }\n\n\n\t@Override\n\tpublic boolean isSystem() {\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/webdav/WebDAVProvider.java",
    "content": "package com.mucommander.commons.file.impl.webdav;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.ProtocolProvider;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\n/**\n *\n * @author Mathias\n */\npublic class WebDAVProvider implements ProtocolProvider {\n\n    @Override\n    public AbstractFile getFile(FileURL url, Object... instantiationParams) throws IOException {\n\n        try {\n            return new WebDAVFile(url);\n        } catch (URISyntaxException ex) {\n            Logger.getLogger(WebDAVProvider.class.getName()).log(Level.SEVERE, null, ex);\n        }\n        \n        return null;\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/wim/WimFormatProvider.java",
    "content": "package com.mucommander.commons.file.impl.wim;\n\nimport java.io.IOException;\n\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.ArchiveFormatProvider;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\nimport com.mucommander.commons.file.impl.SevenZipJBindingROArchiveFile;\n\nimport net.sf.sevenzipjbinding.ArchiveFormat;\n\npublic class WimFormatProvider implements ArchiveFormatProvider {\n\n    private static final String[] EXTENSIONS = { \".wim\" };\n\n    private final static ExtensionFilenameFilter FILENAME_FILTER = new ExtensionFilenameFilter(EXTENSIONS);\n\n    private final static byte[] SIGNATURE = {0x4D, 0x53, 0x57, 0x49, 0x4D};\n\n    @Override\n    public AbstractArchiveFile getFile(AbstractFile file) throws IOException {\n        return new SevenZipJBindingROArchiveFile(file, ArchiveFormat.WIM, SIGNATURE);\n    }\n\n    @Override\n    public FilenameFilter getFilenameFilter() {\n        return FILENAME_FILTER;\n    }\n\n    @Override\n    public String[] getFileExtensions() {\n        return EXTENSIONS;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/xar/XarFormatProvider.java",
    "content": "package com.mucommander.commons.file.impl.xar;\n\nimport java.io.IOException;\n\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.ArchiveFormatProvider;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\nimport com.mucommander.commons.file.impl.SevenZipJBindingROArchiveFile;\n\nimport net.sf.sevenzipjbinding.ArchiveFormat;\n\npublic class XarFormatProvider implements ArchiveFormatProvider {\n\n    private static final String[] EXTENSIONS = { \".xar\" };\n\n    private final static ExtensionFilenameFilter FILENAME_FILTER = new ExtensionFilenameFilter(EXTENSIONS);\n\n    private final static byte[] SIGNATURE = {0x78, 0x61, 0x72, 0x21};\n\n    @Override\n    public AbstractArchiveFile getFile(AbstractFile file) throws IOException {\n        return new SevenZipJBindingROArchiveFile(file, ArchiveFormat.XAR, SIGNATURE);\n    }\n\n    @Override\n    public FilenameFilter getFilenameFilter() {\n        return FILENAME_FILTER;\n    }\n\n    @Override\n    public String[] getFileExtensions() {\n        return EXTENSIONS;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/z/ZFormatProvider.java",
    "content": "package com.mucommander.commons.file.impl.z;\n\nimport java.io.IOException;\n\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.ArchiveFormatProvider;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\nimport com.mucommander.commons.file.impl.SevenZipJBindingROArchiveFile;\n\nimport net.sf.sevenzipjbinding.ArchiveFormat;\n\npublic class ZFormatProvider implements ArchiveFormatProvider {\n\n    private static final String[] EXTENSIONS = { \".z\" };\n\n    private final static ExtensionFilenameFilter FILENAME_FILTER = new ExtensionFilenameFilter(EXTENSIONS);\n\n    private final static byte[] SIGNATURE = {0x1F, (byte) 0x9D};\n\n    @Override\n    public AbstractArchiveFile getFile(AbstractFile file) throws IOException {\n        return new SevenZipJBindingROArchiveFile(file, ArchiveFormat.Z, SIGNATURE);\n    }\n\n    @Override\n    public FilenameFilter getFilenameFilter() {\n        return FILENAME_FILTER;\n    }\n\n    @Override\n    public String[] getFileExtensions() {\n        return EXTENSIONS;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/zip/JavaUtilZipEntryIterator.java",
    "content": "package com.mucommander.commons.file.impl.zip;\n\nimport com.mucommander.commons.file.ArchiveEntry;\nimport com.mucommander.commons.file.ArchiveEntryIterator;\n\nimport java.io.IOException;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipInputStream;\n\n/**\n * An <code>ArchiveEntryIterator</code> that iterates through a {@link ZipInputStream}.\n *\n * @author Maxence Bernard\n */\npublic class JavaUtilZipEntryIterator implements ArchiveEntryIterator  {\n\n    /** InputStream to the archive file */\n    private ZipInputStream zin;\n\n    /** The current entry, where the ZipInputStream is currently positionned */\n    private ArchiveEntry currentEntry;\n\n\n    /**\n     * Creates a new TarEntryIterator that iterates through the entries of the given {@link ZipInputStream}.\n     *\n     * @param zin the TarInputStream to iterate through\n     */\n    JavaUtilZipEntryIterator(ZipInputStream zin) {\n        this.zin = zin;\n    }\n\n    /**\n     * Returns the {@link ZipInputStream} instance that was used to create this object.\n     *\n     * @return the {@link ZipInputStream} instance that was used to create this object.\n     */\n    ZipInputStream getZipInputStream() {\n        return zin;\n    }\n\n    /**\n     * Returns the current entry, where the <code>ZipInputStream</code> is currently positionned.\n     *\n     * @return the current entry, where the <code>ZipInputStream</code> is currently positionned.\n     */\n    ArchiveEntry getCurrentEntry() {\n        return currentEntry;\n    }\n\n    /**\n     * Advances the {@link ZipInputStream} to the next entry and returns the corresponding {@link ArchiveEntry}.\n     *\n     * @return the next ArchiveEntry\n     * @throws java.io.IOException if an I/O error occurred\n     */\n    private ArchiveEntry getNextEntry() throws IOException {\n        try {\n            ZipEntry entry = zin.getNextEntry();\n\n            if(entry==null)\n                return null;\n\n            return ZipArchiveFile.createArchiveEntry(new com.mucommander.commons.file.impl.zip.provider.ZipEntry(entry));\n        }\n        catch(Exception e) {\n            // java.util.zip.ZipInputStream can throw an IllegalArgumentException when the filename/comment encoding\n            // is not UTF-8 as expected (ZipInputStream always expects UTF-8). The more general Exception is caught\n            // (just to be safe) and an IOException thrown.\n            throw new IOException();\n        } catch(Error e) {\n            // ZipInputStream#getNextEntry() will throw a java.lang.InternalError (\"invalid compression method\")\n            // if the compression method is different from DEFLATED or STORED (happens with IMPLODED for example).\n            throw new IOException();\n        }\n    }\n\n\n    /////////////////////////////////////////\n    // ArchiveEntryIterator implementation //\n    /////////////////////////////////////////\n\n    public ArchiveEntry nextEntry() throws IOException {\n        // Get the next entry, if any\n        this.currentEntry = getNextEntry();\n\n        return currentEntry;\n    }\n\n    public void close() throws IOException {\n        zin.close();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/zip/ZipArchiveFile.java",
    "content": "package com.mucommander.commons.file.impl.zip;\n\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.file.impl.zip.provider.ZipConstants;\nimport com.mucommander.commons.file.impl.zip.provider.ZipEntry;\nimport com.mucommander.commons.file.impl.zip.provider.ZipFile;\nimport com.mucommander.commons.io.FilteredOutputStream;\n\nimport java.io.*;\nimport java.util.Iterator;\nimport java.util.zip.ZipInputStream;\n\n\n/**\n * ZipArchiveFile provides read and write access (under certain conditions) to archives in the Zip format.\n * <p>\n * Two different packages that implement the actual Zip compression format are used: the homemade\n * <code>com.mucommander.commons.file.impl.zip.provider</code> package and Java's <code>java.util.zip</code>.\n * <code>com.mucommander.commons.file.impl.zip.provider</code> provides additional functionality and improved performance over\n * <code>java.util.zip</code> but requires the underlying file to supply a <code>RandomAccessInputStream</code> for read\n * access and a <code>RandomAccessOutputStream</code> for write access. If the underlying file can't provide at least a\n * <code>RandomAccessInputStream</code>, the lesser <code>java.util.zip</code> package is used.\n *\n * @see com.mucommander.commons.file.impl.zip.ZipFormatProvider\n * @see com.mucommander.commons.file.impl.zip.provider.ZipFile\n * @author Maxence Bernard\n */\npublic class ZipArchiveFile extends AbstractRWArchiveFile {\n\n    /** The ZipFile object that actually reads and modifies the entries in the Zip file */\n    private ZipFile zipFile;\n\n    /** The date at which the current ZipFile object was created */\n    private long lastZipFileDate;\n\n    /** Contents of an empty Zip file, 22 bytes long */\n    private final static byte EMPTY_ZIP_BYTES[] = {\n        0x50, 0x4B, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00,\n        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n        0x00, 0x00, 0x00, 0x00, 0x00, 0x00\n    };\n\n\n    /**\n     * Creates a new ZipArchiveFile on top of the given file.\n     *\n     * @param file the underlying archive file\n     */\n    public ZipArchiveFile(AbstractFile file) {\n        super(file);\n    }\n\n    /**\n     * Checks if the underlying Zip file is up-to-date, i.e. exists and has not changed without this archive file\n     * being aware of it. If one of those 2 conditions are not met, (re)load the ZipFile instance (parse the entries)\n     * and declare the Zip file as up-to-date.\n     *\n     * @throws IOException if an error occurred while reloading\n     */\n    private void checkZipFile() throws IOException {\n        long currentDate = file.getLastModifiedDate();\n\n        if(zipFile==null || currentDate!=lastZipFileDate) {\n            zipFile = new ZipFile(file);\n            declareZipFileUpToDate();\n        }\n    }\n\n    /**\n     * Declare the underlying Zip file as up-to-date. Calling this method after the Zip file has been modified prevents\n     * {@link #checkZipFile()} from being reloaded.\n     */\n    private void declareZipFileUpToDate() {\n        lastZipFileDate = file.getLastModifiedDate();\n    }\n\n    /**\n     * Creates and returns a {@link com.mucommander.commons.file.impl.zip.provider.ZipEntry} instance using the attributes\n     * of the given {@link ArchiveEntry}.\n     *\n     * @param entry the object that serves to initialize the attributes of the returned ZipEntry\n     * @return a ZipEntry whose attributes are fetched from the given ZipEntry\n     */\n    private ZipEntry createZipEntry(ArchiveEntry entry) {\n        boolean isDirectory = entry.isDirectory();\n        String path = entry.getPath();\n        if(isDirectory && !path.endsWith(\"/\"))\n            path += \"/\";\n\n        com.mucommander.commons.file.impl.zip.provider.ZipEntry zipEntry = new com.mucommander.commons.file.impl.zip.provider.ZipEntry(path);\n        zipEntry.setMethod(ZipConstants.DEFLATED);\n        zipEntry.setTime(System.currentTimeMillis());\n        zipEntry.setUnixMode(SimpleFilePermissions.padPermissions(entry.getPermissions(), isDirectory\n                    ? FilePermissions.DEFAULT_DIRECTORY_PERMISSIONS\n                    : FilePermissions.DEFAULT_FILE_PERMISSIONS).getIntValue());\n\n        return zipEntry;\n    }\n\n    /**\n     * Creates and return an {@link ArchiveEntry()} whose attributes are fetched from the given {@link com.mucommander.commons.file.impl.zip.provider.ZipEntry}.\n     * It is worth noting that the returned entry has the {@link ArchiveEntry#exists exists} flag set to <code>true</code>.\n     *\n     * @param zipEntry the object that serves to initialize the attributes of the returned ArchiveEntry\n     * @return an ArchiveEntry whose attributes are fetched from the given ZipEntry\n     */\n    static ArchiveEntry createArchiveEntry(ZipEntry zipEntry) {\n        ArchiveEntry entry = new ArchiveEntry(zipEntry.getName(), zipEntry.isDirectory(), zipEntry.getTime(), zipEntry.getSize(), true);\n\n        if(zipEntry.hasUnixMode())\n            entry.setPermissions(new SimpleFilePermissions(zipEntry.getUnixMode()));\n\n        entry.setEntryObject(zipEntry);\n\n        return entry;\n    }\n\n    /**\n     * Adds the given {@link ArchiveEntry} to the entries tree and declares the Zip file and entries tree up-to-date.\n     *\n     * @param entry the entry to add to the entries tree\n     * @throws IOException if an error occurred while adding the entry to the tree\n     * @throws UnsupportedFileOperationException if this operation is not supported by the underlying filesystem,\n     * or is not implemented.\n     */\n    private void finishAddEntry(ArchiveEntry entry) throws IOException, UnsupportedFileOperationException {\n        // Declare the zip file and entries tree up-to-date\n        declareZipFileUpToDate();\n        declareEntriesTreeUpToDate();\n\n        // Add the new entry to the entries tree\n        addToEntriesTree(entry);\n    }\n\n\n    //////////////////////////////////////////\n    // AbstractROArchiveFile implementation //\n    //////////////////////////////////////////\n\n    @Override\n    public synchronized ArchiveEntryIterator getEntryIterator() throws IOException {\n        // If the underlying AbstractFile has random read access, use our own ZipFile implementation to read entries\n        if (file.isFileOperationSupported(FileOperation.RANDOM_READ_FILE)) {\n            checkZipFile();\n\n            final Iterator<ZipEntry> iterator = zipFile.getEntries();\n\n            return new ArchiveEntryIterator() {\n                public ArchiveEntry nextEntry() {\n                    ZipEntry entry;\n\n                    if(!iterator.hasNext() || (entry = iterator.next())==null)\n                        return null;\n\n                    return createArchiveEntry(entry);\n                }\n\n                public void close() {\n                }\n            };\n        }\n        // If the underlying AbstractFile doesn't have random read access, use java.util.zip.ZipInputStream to\n        // read the entries. This is much slower than the former method as the file cannot be sought through and needs\n        // to be traversed.\n        else {\n            return new JavaUtilZipEntryIterator(new ZipInputStream(file.getInputStream()));\n        }\n    }\n\n\n    @Override\n    public synchronized InputStream getEntryInputStream(ArchiveEntry entry, ArchiveEntryIterator entryIterator) throws IOException {\n        // If the underlying AbstractFile has random read access, use our own ZipFile implementation to read the entry\n        if (file.isFileOperationSupported(FileOperation.RANDOM_READ_FILE)) {\n            checkZipFile();\n\n            ZipEntry zipEntry = (com.mucommander.commons.file.impl.zip.provider.ZipEntry)entry.getEntryObject();\n            if(zipEntry==null)  // Should not normally happen\n                throw new IOException();\n\n            return zipFile.getInputStream(zipEntry);\n        }\n        // If the underlying AbstractFile doesn't have random read access, use java.util.InputStream to\n        // read the entry. This is much slower than the former method as the file cannot be seeked and needs\n        // to be traversed to locate the entry we're interested in.\n        else {\n            // Optimization: first check if the specified iterator is positionned at the beginning of the entry.\n            // This will typically be the case if an iterator is being used to read all the archive's entries\n            // (unpack operation). In that case, we save the cost of looking for the entry in the archive.\n            if(entryIterator!=null && (entryIterator instanceof JavaUtilZipEntryIterator)) {\n                ArchiveEntry currentEntry = ((JavaUtilZipEntryIterator)entryIterator).getCurrentEntry();\n                if(currentEntry.getPath().equals(entry.getPath())) {\n                    // The entry/zip stream is wrapped in a FilterInputStream where #close is implemented as a no-op:\n                    // we don't want the ZipInputStream to be closed when the caller closes the entry's stream.\n                    return new FilterInputStream(((JavaUtilZipEntryIterator)entryIterator).getZipInputStream()) {\n                        @Override\n                        public void close() {\n                            // No-op\n                        }\n                    };\n                }\n\n                // This is not the one, look for the entry from the beginning of the archive\n            }\n\n            // Iterate through the archive until we've found the entry\n            java.util.zip.ZipInputStream zin = new java.util.zip.ZipInputStream(file.getInputStream());\n            java.util.zip.ZipEntry zipEntry;\n            String entryPath = entry.getPath();\n            // Iterate until we find the entry we're looking for\n            while ((zipEntry=zin.getNextEntry())!=null)\n                if (zipEntry.getName().equals(entryPath)) // That's the one, return it\n                    return zin;\n\n            throw new IOException(\"Unknown Zip entry: \"+entry.getName());\n        }\n    }\n\n    //////////////////////////////////////////\n    // AbstractRWArchiveFile implementation //\n    //////////////////////////////////////////\n\n    @Override\n    public synchronized OutputStream addEntry(final ArchiveEntry entry) throws IOException {\n        checkZipFile();\n\n        final ZipEntry zipEntry = createZipEntry(entry);\n\n        if(zipEntry.isDirectory()) {\n            // Add the new directory entry to the zip file (physically)\n            zipFile.addEntry(zipEntry);\n\n            // Set the ZipEntry object into the ArchiveEntry\n            entry.setEntryObject(zipEntry);\n\n            // Declare the zip file and entries tree up-to-date and add the new entry to the entries tree\n            finishAddEntry(entry);\n\n            return null;\n        }\n        else {\n            // Set the ZipEntry object into the ArchiveEntry\n            entry.setEntryObject(zipEntry);\n\n            return new FilteredOutputStream(zipFile.addEntry(zipEntry)) {\n                @Override\n                public void close() throws IOException {\n                    super.close();\n\n                    // Declare the zip file and entries tree up-to-date and add the new entry to the entries tree\n                        finishAddEntry(entry);\n                    }\n            };\n        }\n    }\n\n    @Override\n    public synchronized void deleteEntry(ArchiveEntry entry) throws IOException {\n        ZipEntry zipEntry = (com.mucommander.commons.file.impl.zip.provider.ZipEntry)entry.getEntryObject();\n\n        // Most of the time, the ZipEntry will not be null. However, it can be null in some rare cases, when directory\n        // entries have been created in the entries tree but don't exist in the Zip file.\n        // That is the case when a file entry exists in the Zip file but has no directory entry for the parent.\n        if(zipEntry!=null) {\n            // Entry exists physically in the zip file\n\n            checkZipFile();\n\n            // Delete the entry from the zip file (physically)\n            zipFile.deleteEntry(zipEntry);\n\n            // Remove the ZipEntry object from the AchiveEntry\n            entry.setEntryObject(null);\n\n            // Declare the zip file and entries tree up-to-date\n            declareZipFileUpToDate();\n            declareEntriesTreeUpToDate();\n        }\n        // Else entry doesn't physically exist in the zip file, only in the entries tree\n\n        // Remove the entry from the entries tree\n        removeFromEntriesTree(entry);\n    }\n\n    @Override\n    public void updateEntry(ArchiveEntry entry) throws IOException {\n        ZipEntry zipEntry = (com.mucommander.commons.file.impl.zip.provider.ZipEntry)entry.getEntryObject();\n\n        // Most of the time, the ZipEntry will not be null. However, it can be null in some rare cases, when directory\n        // entries have been created in the entries tree but don't exist in the Zip file.\n        // That is the case when a file entry exists in the Zip file but has no directory entry for the parent.\n        if(zipEntry!=null) {\n            // Entry exists physically in the zip file\n\n            checkZipFile();\n\n            zipEntry.setTime(entry.getLastModifiedDate());\n            zipEntry.setUnixMode(entry.getPermissions().getIntValue());\n            \n            // Physically update the entry's attributes in the Zip file\n            zipFile.updateEntry(zipEntry);\n\n            // Declare the zip file and entries tree up-to-date\n            declareZipFileUpToDate();\n            declareEntriesTreeUpToDate();\n        }\n    }\n\n    @Override\n    public synchronized void optimizeArchive() throws IOException {\n        checkZipFile();\n\n        // Defragment the zip file\n        zipFile.defragment();\n\n        // Declare the zip file and entries tree up-to-date\n        declareZipFileUpToDate();\n        declareEntriesTreeUpToDate();\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    /**\n     * Returns <code>true</code> only if the proxied archive file has random read and write access, as reported\n     * by {@link AbstractFile#isFileOperationSupported(FileOperation)}. If that is not the case, this archive has \n     * read-only access and behaves just like a {@link com.mucommander.commons.file.AbstractROArchiveFile}.\n     *\n     * @return true only if the proxied archive file has random read and write access\n     */\n    @Override\n    public boolean isWritable() {\n        return file.isFileOperationSupported(FileOperation.RANDOM_READ_FILE)\n            && file.isFileOperationSupported(FileOperation.RANDOM_WRITE_FILE);\n    }\n\n    /**\n     * Creates an empty, valid Zip file. The resulting file is 22 bytes long.\n     */\n    @Override\n    public void mkfile() throws IOException {\n        if(exists())\n            throw new IOException();\n\n        copyStream(new ByteArrayInputStream(EMPTY_ZIP_BYTES), false, EMPTY_ZIP_BYTES.length);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/zip/ZipFormatProvider.java",
    "content": "package com.mucommander.commons.file.impl.zip;\n\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.ArchiveFormatProvider;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\n\nimport java.io.IOException;\n\n/**\n * This class is the provider for the 'Zip' archive format implemented by {@link ZipArchiveFile}.\n *\n * @see com.mucommander.commons.file.impl.zip.ZipArchiveFile\n * @author Nicolas Rinaudo, Maxence Bernard\n */\npublic class ZipFormatProvider implements ArchiveFormatProvider {\n    private static final String[] EXTENSIONS =\n            {\".zip\", \".jar\", \".war\", \".wal\", \".wmz\", \".xpi\", \".ear\", \".sar\", \".odt\", \".ods\", \".odp\", \".odg\", \".odf\", \".egg\", \".epub\", \".cbz\", \".kar\"};\n\n\n    /**\n     * Static instance of the filename filter that matches archive filenames\n     * */\n    private static final ExtensionFilenameFilter FILENAME_FILTER = new ExtensionFilenameFilter(EXTENSIONS);\n\n\n    @Override\n    public AbstractArchiveFile getFile(AbstractFile file) throws IOException {\n        return new ZipArchiveFile(file);\n    }\n\n    @Override\n    public FilenameFilter getFilenameFilter() {\n        return FILENAME_FILTER;\n    }\n\n    @Override\n    public String[] getFileExtensions() {\n        return EXTENSIONS;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/zip/package.html",
    "content": "<body>\n  Provides an implementation of the ZIP archive format.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/zip/provider/AsiExtraField.java",
    "content": "/*\n *  Licensed to the Apache Software Foundation (ASF) under one or more\n *  contributor license agreements.  See the NOTICE file distributed with\n *  this work for additional information regarding copyright ownership.\n *  The ASF licenses this file to You under the Apache License, Version 2.0\n *  (the \"License\"); you may not use this file except in compliance with\n *  the License.  You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n *  Unless required by applicable law or agreed to in writing, software\n *  distributed under the License is distributed on an \"AS IS\" BASIS,\n *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *  See the License for the specific language governing permissions and\n *  limitations under the License.\n *\n */\n\npackage com.mucommander.commons.file.impl.zip.provider;\n\nimport java.util.zip.CRC32;\nimport java.util.zip.ZipException;\n\n/**\n * Adds Unix file permission and UID/GID fields as well as symbolic\n * link handling.\n *\n * <p>This class uses the ASi extra field in the format:\n * <pre>\n *         Value         Size            Description\n *         -----         ----            -----------\n * (Unix3) 0x756e        Short           tag for this extra block type\n *         TSize         Short           total data size for this block\n *         CRC           Long            CRC-32 of the remaining data\n *         Mode          Short           file permissions\n *         SizDev        Long            symlink'd size OR major/minor dev num\n *         UID           Short           user ID\n *         GID           Short           group ID\n *         (var.)        variable        symbolic link filename\n * </pre>\n * taken from appnote.iz (Info-ZIP note, 981119) found at <a\n * href=\"ftp://ftp.uu.net/pub/archiving/zip/doc/\">ftp://ftp.uu.net/pub/archiving/zip/doc/</a>\n *\n * <p>Short is two bytes and Long is four bytes in big endian byte and\n * word order, device numbers are currently not supported.\n *\n * <p>--------------------------------------------------------------------------------------------------------------<br>\n * <br>\n * This class is based off the <code>org.apache.tools.zip</code> package of the <i>Apache Ant</i> project. The Ant\n * code has been modified under the terms of the Apache License which you can find in the bundled muCommander license\n * file. It was forked at version 1.7.0 of Ant.\n * \n * @author Apache Ant, Maxence Bernard\n */\npublic class AsiExtraField implements ZipExtraField, UnixStat, Cloneable {\n\n    private static final ZipShort HEADER_ID = new ZipShort(0x756E);\n\n    /**\n     * Standard Unix stat(2) file mode.\n     */\n    private int mode = 0;\n    /**\n     * User ID.\n     */\n    private int uid = 0;\n    /**\n     * Group ID.\n     */\n    private int gid = 0;\n    /**\n     * File this entry points to, if it is a symbolic link.\n     *\n     * <p>empty string - if entry is not a symbolic link.\n     */\n    private String link = \"\";\n    /**\n     * Is this an entry for a directory?\n     */\n    private boolean dirFlag = false;\n\n    /**\n     * Instance used to calculate checksums.\n     */\n    private CRC32 crc = new CRC32();\n\n    /** Constructor for AsiExtraField. */\n    public AsiExtraField() {\n    }\n\n    /**\n     * The Header-ID.\n     * @return the value for the header id for this extrafield\n     */\n    public ZipShort getHeaderId() {\n        return HEADER_ID;\n    }\n\n    /**\n     * Length of the extra field in the local file data - without\n     * Header-ID or length specifier.\n     * @return a <code>ZipShort</code> for the length of the data of this extra field\n     */\n    public ZipShort getLocalFileDataLength() {\n        return new ZipShort(4         // CRC\n                          + 2         // Mode\n                          + 4         // SizDev\n                          + 2         // UID\n                          + 2         // GID\n                          + getLinkedFile().getBytes().length);\n    }\n\n    /**\n     * Delegate to local file data.\n     * @return the centralDirectory length\n     */\n    public ZipShort getCentralDirectoryLength() {\n        return getLocalFileDataLength();\n    }\n\n    /**\n     * The actual data to put into local file data - without Header-ID\n     * or length specifier.\n     * @return get the data\n     */\n    public byte[] getLocalFileDataData() {\n        // CRC will be added later\n        byte[] data = new byte[getLocalFileDataLength().getValue() - 4];\n        ZipShort.getBytes(getMode(), data, 0);\n\n        byte[] linkArray = getLinkedFile().getBytes();\n        ZipLong.getBytes(linkArray.length, data, 2);\n        ZipShort.getBytes(getUserId(), data, 6);\n        ZipShort.getBytes(getGroupId(), data, 8);\n        System.arraycopy(linkArray, 0, data, 10, linkArray.length);\n\n        crc.reset();\n        crc.update(data);\n        long checksum = crc.getValue();\n\n        byte[] result = new byte[data.length + 4];\n        ZipLong.getBytes(checksum, result, 0);\n        System.arraycopy(data, 0, result, 4, data.length);\n\n        return result;\n    }\n\n    /**\n     * Delegate to local file data.\n     * @return the local file data\n     */\n    public byte[] getCentralDirectoryData() {\n        return getLocalFileDataData();\n    }\n\n    /**\n     * Set the user id.\n     * @param uid the user id\n     */\n    public void setUserId(int uid) {\n        this.uid = uid;\n    }\n\n    /**\n     * Get the user id.\n     * @return the user id\n     */\n    public int getUserId() {\n        return uid;\n    }\n\n    /**\n     * Set the group id.\n     * @param gid the group id\n     */\n    public void setGroupId(int gid) {\n        this.gid = gid;\n    }\n\n    /**\n     * Get the group id.\n     * @return the group id\n     */\n    public int getGroupId() {\n        return gid;\n    }\n\n    /**\n     * Indicate that this entry is a symbolic link to the given filename.\n     *\n     * @param name Name of the file this entry links to, empty String\n     *             if it is not a symbolic link.\n     */\n    public void setLinkedFile(String name) {\n        link = name;\n        mode = getMode(mode);\n    }\n\n    /**\n     * Name of linked file\n     *\n     * @return name of the file this entry links to if it is a\n     *         symbolic link, the empty string otherwise.\n     */\n    public String getLinkedFile() {\n        return link;\n    }\n\n    /**\n     * Is this entry a symbolic link?\n     * @return true if this is a symbolic link\n     */\n    public boolean isLink() {\n        return getLinkedFile().length() != 0;\n    }\n\n    /**\n     * File mode of this file.\n     * @param mode the file mode\n     */\n    public void setMode(int mode) {\n        this.mode = getMode(mode);\n    }\n\n    /**\n     * File mode of this file.\n     * @return the file mode\n     */\n    public int getMode() {\n        return mode;\n    }\n\n    /**\n     * Indicate whether this entry is a directory.\n     * @param dirFlag if true, this entry is a directory\n     */\n    public void setDirectory(boolean dirFlag) {\n        this.dirFlag = dirFlag;\n        mode = getMode(mode);\n    }\n\n    /**\n     * Is this entry a directory?\n     * @return true if this entry is a directory\n     */\n    public boolean isDirectory() {\n        return dirFlag && !isLink();\n    }\n\n    /**\n     * Populate data from this array as if it was in local file data.\n     * @param data an array of bytes\n     * @param offset the start offset\n     * @param length the number of bytes in the array from offset\n     * @throws ZipException on error\n     */\n    public void parseFromLocalFileData(byte[] data, int offset, int length)\n        throws ZipException {\n\n        long givenChecksum = ZipLong.getValue(data, offset);\n        byte[] tmp = new byte[length - 4];\n        System.arraycopy(data, offset + 4, tmp, 0, length - 4);\n        crc.reset();\n        crc.update(tmp);\n        long realChecksum = crc.getValue();\n        if (givenChecksum != realChecksum) {\n            throw new ZipException(\"bad CRC checksum \"\n                                   + Long.toHexString(givenChecksum)\n                                   + \" instead of \"\n                                   + Long.toHexString(realChecksum));\n        }\n\n        int newMode = ZipShort.getValue(tmp, 0);\n        byte[] linkArray = new byte[(int) ZipLong.getValue(tmp, 2)];\n        uid = ZipShort.getValue(tmp, 6);\n        gid = ZipShort.getValue(tmp, 8);\n\n        if (linkArray.length == 0) {\n            link = \"\";\n        } else {\n            System.arraycopy(tmp, 10, linkArray, 0, linkArray.length);\n            link = new String(linkArray);\n        }\n        setDirectory((newMode & DIR_FLAG) != 0);\n        setMode(newMode);\n    }\n\n    /**\n     * Get the file mode for given permissions with the correct file type.\n     * @param mode the mode\n     * @return the type with the mode\n     */\n    protected int getMode(int mode) {\n        int type = FILE_FLAG;\n        if (isLink()) {\n            type = LINK_FLAG;\n        } else if (isDirectory()) {\n            type = DIR_FLAG;\n        }\n        return type | (mode & PERM_MASK);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/zip/provider/DeflatedOutputStream.java",
    "content": "package com.mucommander.commons.file.impl.zip.provider;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.zip.Deflater;\n\n/**\n * DeflatedOutputStream compresses data using the DEFLATED compression method.\n *\n * <p>\n * <br>\n * This class is based off the <code>org.apache.tools.zip</code> package of the <i>Apache Ant</i> project. The Ant\n * code has been modified under the terms of the Apache License which you can find in the bundled muCommander license\n * file. It was forked at version 1.7.0 of Ant.\n *\n * @author Maxence Bernard\n */\npublic class DeflatedOutputStream extends ZipEntryOutputStream {\n\n    /** Deflater instance that does the actual compression work */\n    protected Deflater deflater;\n\n    /** Buffer used to deflate data */\n    protected byte[] buf;\n\n\n    /**\n     * Creates a new <code>DeflatedOutputStream</code> that writes compressed data to the given <code>OutputStream</code>\n     * and automatically updates the supplied CRC32 checksum.\n     *\n     * @param out the OutputStream where the compressed data is sent to\n     * @param deflater the Deflater that compresses data, reset before first use\n     * @param buf the buffer used to deflate data\n     */\n    public DeflatedOutputStream(OutputStream out, Deflater deflater, byte buf[]) {\n        super(out, ZipConstants.DEFLATED);\n\n        this.deflater = deflater;\n        this.buf = buf;\n\n        deflater.reset();\n    }\n\n    /**\n     * Writes next block of compressed data to the output stream.\n     *\n     * @throws java.io.IOException on error\n     */\n    protected void deflate() throws IOException {\n        int len = deflater.deflate(buf, 0, buf.length);\n        if (len > 0) {\n            out.write(buf, 0, len);\n        }\n    }\n\n    /**\n     * Finishes writing the DEFLATED-compressed data.\n     *\n     * @throws IOException if an I/O occurred\n     */\n    public void finishDeflate() throws IOException {\n        deflater.finish();\n        while (!deflater.finished()) {\n            deflate();\n        }\n    }\n\n\n    /////////////////////////////////////////\n    // ZipEntryOutputStream implementation //\n    /////////////////////////////////////////\n\n    @Override\n    public int getTotalIn() {\n        return deflater.getTotalIn();\n    }\n\n    @Override\n    public int getTotalOut() {\n        return deflater.getTotalOut();\n    }\n\n\n    /////////////////////////////////\n    // OutputStream implementation //\n    /////////////////////////////////\n\n    /**\n     * Writes the given bytes to the Zip entry.\n     *\n     * @param b the byte array to write\n     * @param offset the start position to write from\n     * @param length the number of bytes to write\n     * @throws java.io.IOException on error\n     */\n    @Override\n    public void write(byte[] b, int offset, int length) throws IOException {\n        if (length > 0) {\n            if (!deflater.finished()) {\n                deflater.setInput(b, offset, length);\n                while (!deflater.needsInput()) {\n                    deflate();\n                }\n            }\n        }\n\n        crc.update(b, offset, length);\n    }\n\n    /**\n     * Completes writing the entry <b>without</b> closing the underlying <code>OutputStream</code>.\n     *\n     * @throws IOException\n     */\n    @Override\n    public void close() throws IOException {\n        finishDeflate();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/zip/provider/ExtraFieldUtils.java",
    "content": "/*\n *  Licensed to the Apache Software Foundation (ASF) under one or more\n *  contributor license agreements.  See the NOTICE file distributed with\n *  this work for additional information regarding copyright ownership.\n *  The ASF licenses this file to You under the Apache License, Version 2.0\n *  (the \"License\"); you may not use this file except in compliance with\n *  the License.  You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n *  Unless required by applicable law or agreed to in writing, software\n *  distributed under the License is distributed on an \"AS IS\" BASIS,\n *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *  See the License for the specific language governing permissions and\n *  limitations under the License.\n *\n */\n\npackage com.mucommander.commons.file.impl.zip.provider;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.ArrayList;\nimport java.util.Hashtable;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.zip.ZipException;\n\n/**\n * ZipExtraField related methods.\n *\n * <p>\n * This class is based off the <code>org.apache.tools.zip</code> package of the <i>Apache Ant</i> project. The Ant\n * code has been modified under the terms of the Apache License which you can find in the bundled muCommander license\n * file. It was forked at version 1.7.0 of Ant.\n *\n * @author Apache Ant, Maxence Bernard\n */\npublic class ExtraFieldUtils {\n\n    /**\n     * Static registry of known extra fields.\n     */\n    private static final Map<ZipShort, Class<? extends ZipExtraField>> implementations = new Hashtable<>();\n\n    static {\n        register(AsiExtraField.class);\n        register(JarMarker.class);\n    }\n\n    /**\n     * Register a ZipExtraField implementation.\n     *\n     * <p>The given class must have a no-arg constructor and implement\n     * the {@link ZipExtraField ZipExtraField interface}.\n     * @param c the class to register\n     */\n    public static void register(Class<? extends ZipExtraField> c) {\n        try {\n            ZipExtraField ze = c.getDeclaredConstructor().newInstance();\n            implementations.put(ze.getHeaderId(), c);\n        } catch (ClassCastException cc) {\n            throw new RuntimeException(c + \" doesn't implement ZipExtraField\");\n        } catch (InstantiationException ie) {\n            throw new RuntimeException(c + \" is not a concrete class\");\n        } catch (IllegalAccessException | InvocationTargetException ie) {\n            throw new RuntimeException(c + \"'s no-arg constructor is not public\");\n        } catch (NoSuchMethodException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * create an instance of the appropriate ExtraField, falls back to\n     * {@link UnrecognizedExtraField UnrecognizedExtraField}.\n     * @param headerId the header identifier\n     * @return an instance of the appropriate ExtraField\n     * @exception InstantiationException if unable to instantiate the class\n     * @exception IllegalAccessException if not allowed to instantiate the class\n     */\n    public static ZipExtraField createExtraField(ZipShort headerId) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {\n        Class<? extends ZipExtraField> c = implementations.get(headerId);\n        if (c != null) {\n            return c.getDeclaredConstructor().newInstance();\n        }\n        UnrecognizedExtraField u = new UnrecognizedExtraField();\n        u.setHeaderId(headerId);\n        return u;\n    }\n\n    /**\n     * Split the array into ExtraFields and populate them with the\n     * give data.\n     * @param data an array of bytes\n     * @return an array of ExtraFields\n     * @throws ZipException on error\n     */\n    public static ZipExtraField[] parse(byte[] data) throws ZipException {\n        List<ZipExtraField> v = new ArrayList<>();\n        int start = 0;\n        while (start <= data.length - 4) {\n            ZipShort headerId = new ZipShort(data, start);\n            int length = (new ZipShort(data, start + 2)).getValue();\n            if (start + 4 + length > data.length) {\n                throw new ZipException(\"data starting at \" + start+ \" is in unknown format\");\n            }\n            try {\n                ZipExtraField ze = createExtraField(headerId);\n                ze.parseFromLocalFileData(data, start + 4, length);\n                v.add(ze);\n            } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException ie) {\n                throw new ZipException(ie.getMessage());\n            }\n            start += (length + 4);\n        }\n        if (start != data.length) { // array not exhausted\n            throw new ZipException(\"data starting at \" + start + \" is in unknown format\");\n        }\n        return v.toArray(new ZipExtraField[0]);\n    }\n\n    /**\n     * Merges the local file data fields of the given ZipExtraFields.\n     * @param data an array of ExtraFiles\n     * @return an array of bytes\n     */\n    public static byte[] mergeLocalExtraFields(ZipExtraField[] data) {\n        int sum = 4 * data.length;\n        for (ZipExtraField d : data) {\n            sum += d.getLocalFileDataLength().getValue();\n        }\n        byte[] result = new byte[sum];\n        int start = 0;\n        for (ZipExtraField d : data) {\n            System.arraycopy(d.getHeaderId().getBytes(),\n                    0, result, start, 2);\n            System.arraycopy(d.getLocalFileDataLength().getBytes(),\n                    0, result, start + 2, 2);\n            byte[] local = d.getLocalFileDataData();\n            System.arraycopy(local, 0, result, start + 4, local.length);\n            start += (local.length + 4);\n        }\n        return result;\n    }\n\n    /**\n     * Merges the central directory fields of the given ZipExtraFields.\n     * @param data an array of ExtraFields\n     * @return an array of bytes\n     */\n    public static byte[] mergeCentralExtraFields(ZipExtraField[] data) {\n        int sum = 4 * data.length;\n        for (ZipExtraField d : data) {\n            sum += d.getCentralDirectoryLength().getValue();\n        }\n        byte[] result = new byte[sum];\n        int start = 0;\n        for (ZipExtraField d : data) {\n            System.arraycopy(d.getHeaderId().getBytes(),\n                    0, result, start, 2);\n            System.arraycopy(d.getCentralDirectoryLength().getBytes(),\n                    0, result, start + 2, 2);\n            byte[] local = d.getCentralDirectoryData();\n            System.arraycopy(local, 0, result, start + 4, local.length);\n            start += (local.length + 4);\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/zip/provider/JarMarker.java",
    "content": "/*\n *  Licensed to the Apache Software Foundation (ASF) under one or more\n *  contributor license agreements.  See the NOTICE file distributed with\n *  this work for additional information regarding copyright ownership.\n *  The ASF licenses this file to You under the Apache License, Version 2.0\n *  (the \"License\"); you may not use this file except in compliance with\n *  the License.  You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n *  Unless required by applicable law or agreed to in writing, software\n *  distributed under the License is distributed on an \"AS IS\" BASIS,\n *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *  See the License for the specific language governing permissions and\n *  limitations under the License.\n *\n */\n\npackage com.mucommander.commons.file.impl.zip.provider;\n\nimport java.util.zip.ZipException;\n\n/**\n * If this extra field is added as the very first extra field of the\n * archive, Solaris will consider it an executable jar file.\n *\n * <p>\n * This class is based off the <code>org.apache.tools.zip</code> package of the <i>Apache Ant</i> project. The Ant\n * code has been modified under the terms of the Apache License which you can find in the bundled muCommander license\n * file. It was forked at version 1.7.0 of Ant.\n *\n * @author Apache Ant, Maxence Bernard\n */\npublic final class JarMarker implements ZipExtraField {\n\n    private static final ZipShort ID = new ZipShort(0xCAFE);\n    private static final ZipShort NULL = new ZipShort(0);\n    private static final byte[] NO_BYTES = new byte[0];\n    private static final JarMarker DEFAULT = new JarMarker();\n\n    /** No-arg constructor */\n    public JarMarker() {\n        // empty\n    }\n\n    /**\n     * Since JarMarker is stateless we can always use the same instance.\n     * @return the DEFAULT jarmaker.\n     */\n    public static JarMarker getInstance() {\n        return DEFAULT;\n    }\n\n    /**\n     * The Header-ID.\n     * @return the header id\n     */\n    public ZipShort getHeaderId() {\n        return ID;\n    }\n\n    /**\n     * Length of the extra field in the local file data - without\n     * Header-ID or length specifier.\n     * @return 0\n     */\n    public ZipShort getLocalFileDataLength() {\n        return NULL;\n    }\n\n    /**\n     * Length of the extra field in the central directory - without\n     * Header-ID or length specifier.\n     * @return 0\n     */\n    public ZipShort getCentralDirectoryLength() {\n        return NULL;\n    }\n\n    /**\n     * The actual data to put into local file data - without Header-ID\n     * or length specifier.\n     * @return the data\n     */\n    public byte[] getLocalFileDataData() {\n        return NO_BYTES;\n    }\n\n    /**\n     * The actual data to put central directory - without Header-ID or\n     * length specifier.\n     * @return the data\n     */\n    public byte[] getCentralDirectoryData() {\n        return NO_BYTES;\n    }\n\n    /**\n     * Populate data from this array as if it was in local file data.\n     * @param data an array of bytes\n     * @param offset the start offset\n     * @param length the number of bytes in the array from offset\n     *\n     * @throws ZipException on error\n     */\n    public void parseFromLocalFileData(byte[] data, int offset, int length)\n        throws ZipException {\n        if (length != 0) {\n            throw new ZipException(\"JarMarker doesn't expect any data\");\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/zip/provider/StoredOutputStream.java",
    "content": "package com.mucommander.commons.file.impl.zip.provider;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n\n/**\n * StoredOutputStream compresses data using the STORED compression method (i.e. no compression).\n *\n * <p>--------------------------------------------------------------------------------------------------------------<br>\n * <br>\n * This class is based off the <code>org.apache.tools.zip</code> package of the <i>Apache Ant</i> project. The Ant\n * code has been modified under the terms of the Apache License which you can find in the bundled muCommander license\n * file. It was forked at version 1.7.0 of Ant.\n *\n * @author Maxence Bernard\n */\npublic class StoredOutputStream extends ZipEntryOutputStream {\n\n    /** Number of bytes in/out so far */\n    private int storedCount;\n\n\n    /**\n     * Creates a new <code>StoredOutputStream</code> that writes compressed data to the given <code>OutputStream</code>\n     * and automatically updates the supplied CRC32 checksum.\n     *\n     * @param out the OutputStream where the compressed data is sent to\n     */\n    public StoredOutputStream(OutputStream out) {\n        super(out, ZipConstants.STORED);\n    }\n\n\n    /////////////////////////////////////////\n    // ZipEntryOutputStream implementation //\n    /////////////////////////////////////////\n\n    @Override\n    public int getTotalIn() {\n        return storedCount;\n    }\n\n    @Override\n    public int getTotalOut() {\n        return storedCount;\n    }\n\n\n    /////////////////////////////////\n    // OutputStream implementation //\n    /////////////////////////////////\n\n    @Override\n    public void write(byte[] b, int offset, int length) throws IOException {\n        out.write(b, offset, length);\n        storedCount += length;\n\n        crc.update(b, offset, length);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/zip/provider/UnixStat.java",
    "content": "/*\n *  Licensed to the Apache Software Foundation (ASF) under one or more\n *  contributor license agreements.  See the NOTICE file distributed with\n *  this work for additional information regarding copyright ownership.\n *  The ASF licenses this file to You under the Apache License, Version 2.0\n *  (the \"License\"); you may not use this file except in compliance with\n *  the License.  You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n *  Unless required by applicable law or agreed to in writing, software\n *  distributed under the License is distributed on an \"AS IS\" BASIS,\n *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *  See the License for the specific language governing permissions and\n *  limitations under the License.\n *\n */\n\npackage com.mucommander.commons.file.impl.zip.provider;\n\n/**\n * Constants from stat.h on Unix systems.\n *\n * <p>--------------------------------------------------------------------------------------------------------------<br>\n * <br>\n * This class is based off the <code>org.apache.tools.zip</code> package of the <i>Apache Ant</i> project. The Ant\n * code has been modified under the terms of the Apache License which you can find in the bundled muCommander license\n * file. It was forked at version 1.7.0 of Ant.\n *\n * @author Apache Ant, Maxence Bernard\n */\npublic interface UnixStat {\n\n    /**\n     * Bits used for permissions (and sticky bit)\n     */\n    int PERM_MASK =           07777;\n    /**\n     * Indicates symbolic links.\n     */\n    int LINK_FLAG =         0120000;\n    /**\n     * Indicates plain files.\n     */\n    int FILE_FLAG =         0100000;\n    /**\n     * Indicates directories.\n     */\n    int DIR_FLAG =           040000;\n\n    // ----------------------------------------------------------\n    // somewhat arbitrary choices that are quite common for shared\n    // installations\n    // -----------------------------------------------------------\n\n    /**\n     * Default permissions for symbolic links.\n     */\n    int DEFAULT_LINK_PERM =    0777;\n    /**\n     * Default permissions for directories.\n     */\n    int DEFAULT_DIR_PERM =     0755;\n    /**\n     * Default permissions for plain files.\n     */\n    int DEFAULT_FILE_PERM =    0644;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/zip/provider/UnrecognizedExtraField.java",
    "content": "/*\n *  Licensed to the Apache Software Foundation (ASF) under one or more\n *  contributor license agreements.  See the NOTICE file distributed with\n *  this work for additional information regarding copyright ownership.\n *  The ASF licenses this file to You under the Apache License, Version 2.0\n *  (the \"License\"); you may not use this file except in compliance with\n *  the License.  You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n *  Unless required by applicable law or agreed to in writing, software\n *  distributed under the License is distributed on an \"AS IS\" BASIS,\n *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *  See the License for the specific language governing permissions and\n *  limitations under the License.\n *\n */\n\npackage com.mucommander.commons.file.impl.zip.provider;\n\n/**\n * Simple placeholder for all those extra fields we don't want to deal\n * with.\n *\n * <p>Assumes local file data and central directory entries are\n * identical - unless told the opposite.\n *\n * <p>\n * This class is based off the <code>org.apache.tools.zip</code> package of the <i>Apache Ant</i> project. The Ant\n * code has been modified under the terms of the Apache License which you can find in the bundled muCommander license\n * file. It was forked at version 1.7.0 of Ant.\n *\n * @author Apache Ant, Maxence Bernard\n */\npublic class UnrecognizedExtraField implements ZipExtraField {\n\n    /**\n     * The Header-ID.\n     */\n    private ZipShort headerId;\n\n    /**\n     * Set the header id.\n     * @param headerId the header id to use\n     */\n    public void setHeaderId(ZipShort headerId) {\n        this.headerId = headerId;\n    }\n\n    /**\n     * Get the header id.\n     * @return the header id\n     */\n    public ZipShort getHeaderId() {\n        return headerId;\n    }\n\n    /**\n     * Extra field data in local file data - without\n     * Header-ID or length specifier.\n     */\n    private byte[] localData;\n\n    /**\n     * Set the extra field data in the local file data -\n     * without Header-ID or length specifier.\n     * @param data the field data to use\n     */\n    public void setLocalFileDataData(byte[] data) {\n        localData = data;\n    }\n\n    /**\n     * Get the length of the local data.\n     * @return the length of the local data\n     */\n    public ZipShort getLocalFileDataLength() {\n        return new ZipShort(localData.length);\n    }\n\n    /**\n     * Get the local data.\n     * @return the local data\n     */\n    public byte[] getLocalFileDataData() {\n        return localData;\n    }\n\n    /**\n     * Extra field data in central directory - without\n     * Header-ID or length specifier.\n     */\n    private byte[] centralData;\n\n    /**\n     * Set the extra field data in central directory.\n     * @param data the data to use\n     */\n    public void setCentralDirectoryData(byte[] data) {\n        centralData = data;\n    }\n\n    /**\n     * Get the central data length.\n     * If there is no central data, get the local file data length.\n     * @return the central data length\n     */\n    public ZipShort getCentralDirectoryLength() {\n        if (centralData != null) {\n            return new ZipShort(centralData.length);\n        }\n        return getLocalFileDataLength();\n    }\n\n    /**\n     * Get the central data.\n     * @return the central data if present, else return the local file data\n     */\n    public byte[] getCentralDirectoryData() {\n        if (centralData != null) {\n            return centralData;\n        }\n        return getLocalFileDataData();\n    }\n\n    /**\n     * @param data the array of bytes.\n     * @param offset the source location in the data array.\n     * @param length the number of bytes to use in the data array.\n     * @see ZipExtraField#parseFromLocalFileData(byte[], int, int)\n     */\n    public void parseFromLocalFileData(byte[] data, int offset, int length) {\n        byte[] tmp = new byte[length];\n        System.arraycopy(data, offset, tmp, 0, length);\n        setLocalFileDataData(tmp);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/zip/provider/ZipBuffer.java",
    "content": "package com.mucommander.commons.file.impl.zip.provider;\n\n/**\n * ZipBuffer is a C struct-like class that holds byte buffers that are used to convert Java values to Big Endian byte\n * arrays. It allows to reuse the same byte buffers instead of instanciating new ones for each conversion.\n *\n * @see ZipShort#getBytes(int, byte[], int)\n * @see ZipLong#getBytes(long, byte[], int)\n * @author Maxence Bernard\n */\npublic class ZipBuffer {\n\n    /**  2-byte buffer that can hold a Zip short value */\n    byte[] shortBuffer = new byte[2];\n\n    /**  2-byte buffer that can hold a Zip long value */\n    byte[] longBuffer = new byte[4];\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/zip/provider/ZipConstants.java",
    "content": "package com.mucommander.commons.file.impl.zip.provider;\n\nimport java.util.zip.Deflater;\n\n/**\n * Contains the various constants that are used by several classes of this package.\n *\n * @author Maxence Bernard\n */\npublic interface ZipConstants {\n\n    /**\n     * DEFLATED compression method\n     */\n    int DEFLATED = java.util.zip.ZipEntry.DEFLATED;\n\n    /**\n     * STORED compression method (raw storage, no compression)\n     */\n    int STORED = java.util.zip.ZipEntry.STORED;\n\n    /**\n     * Default compression level for DEFLATED compression\n     */\n    int DEFAULT_DEFLATER_COMPRESSION = Deflater.DEFAULT_COMPRESSION;\n\n    /**\n     * Default size of the buffer used by Deflater.\n     */\n    // /!\\ For some unknown reason, using a larger buffer *hurts* performance.\n    int DEFAULT_DEFLATER_BUFFER_SIZE = 512;\n\n    /**\n     * Maximum size of a Zip32 entry or a Zip32 file as a whole, i.e. (2^32)-1.\n     * */\n    long MAX_ZIP32_SIZE = 4294967295l;\n\n    /**\n     * Size of write buffers\n     */\n    int WRITE_BUFFER_SIZE = 65536;\n\n    /**\n     * UTF-8 encoding String\n     */\n    String UTF_8 = \"UTF-8\";\n\n    /**\n     * Local file header signature\n     */\n    byte[] LFH_SIG = ZipLong.getBytes(0X04034B50L);\n\n    /**\n     * Data descriptor signature\n     */\n    byte[] DD_SIG = ZipLong.getBytes(0X08074B50L);\n\n    /**\n     * Central file header signature\n     */\n    byte[] CFH_SIG = ZipLong.getBytes(0X02014B50L);\n\n    /**\n     * End of central dir signature\n     */\n    byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/zip/provider/ZipEntry.java",
    "content": "/*\n *  Licensed to the Apache Software Foundation (ASF) under one or more\n *  contributor license agreements.  See the NOTICE file distributed with\n *  this work for additional information regarding copyright ownership.\n *  The ASF licenses this file to You under the Apache License, Version 2.0\n *  (the \"License\"); you may not use this file except in compliance with\n *  the License.  You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n *  Unless required by applicable law or agreed to in writing, software\n *  distributed under the License is distributed on an \"AS IS\" BASIS,\n *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *  See the License for the specific language governing permissions and\n *  limitations under the License.\n *\n */\n\npackage com.mucommander.commons.file.impl.zip.provider;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.time.Instant;\nimport java.time.ZoneId;\nimport java.time.ZonedDateTime;\nimport java.util.*;\n\n/**\n * Extension that adds better handling of extra fields and provides\n * access to the internal and external file attributes.\n *\n * <p>\n * This class is based off the <code>org.apache.tools.zip</code> package of the <i>Apache Ant</i> project. The Ant\n * code has been modified under the terms of the Apache License which you can find in the bundled muCommander license\n * file. It was forked at version 1.7.0 of Ant.\n *\n * @author Apache Ant, Maxence Bernard\n */\npublic class ZipEntry implements Cloneable {\n\n    /** Name/path of this entry\n     * -- GETTER --\n     *  Returns the name of this entry.\n     */\n    @Getter\n    protected String name;\n\n    /** Uncompressed size of the entry data\n     * -- GETTER --\n     *  Returns the uncompressed size of the entry data, or <code>-1</code> if not known.\n     */\n    @Getter\n    protected long size = -1;\n\n    /** Compressed size of the entry data\n     * -- GETTER --\n     *  Returns the size of the compressed entry data, or <code>-1</code> if not known. In the case of a stored entry,\n     *  the compressed size will be the same as the uncompressed size of the entry.\n     */\n    @Getter\n    private long compressedSize = -1;\n\n    /** CRC-32 checksum of the uncompressed entry data\n     * -- GETTER --\n     *  Returns the CRC-32 checksum of the uncompressed entry data, or <code>-1</code> if not known.\n     */\n    @Getter\n    protected long crc = -1;\n\n    /** Data/time of this entry, in the DOS time format */\n    private long dosTime = -1;\n\n    /** Data/time of this entry, in the Java time format */\n    private long javaTime = -1;\n\n    /** Compression method that was used for the entry data\n     * -- GETTER --\n     *  Returns the compression method of the entry, or <code>-1</code> if not specified.\n     */\n    @Getter\n    protected int method = -1;\n\n    /** An optional comment for this entry\n     * -- GETTER --\n     *  Returns the comment string for the entry, or <code>null</code> if there is none.\n     */\n    @Getter\n    protected String comment;\n\n    /** Platform, part of the 'version made by' central directory field\n     * -- GETTER --\n     *  Returns the platform specification to put into the 'version made by' part of the central file header.\n     * {@link #PLATFORM_FAT} unless {@link #setUnixMode setUnixMode} has been called,\n     * in which case {@link #PLATFORM_UNIX} will be returned.\n     */\n    @Getter\n    protected int platform = PLATFORM_FAT;\n\n    /** Internal attributes (2 bytes)\n     * -- GETTER --\n     *  Retrieves the internal file attributes.\n     * -- SETTER --\n     *  Sets the internal file attributes.\n     */\n    @Setter\n    @Getter\n    private int internalAttributes = 0;\n\n    /** External attributes (4 bytes)\n     * -- GETTER --\n     *  Retrieves the external file attributes.\n     * -- SETTER --\n     *  Sets the external file attributes.\n     */\n    @Setter\n    @Getter\n    private long externalAttributes = 0;\n\n    /** List of extra fields, as ZipEntraField instances */\n    private List<ZipExtraField> extraFields;\n\n    /** Contains info about how this entry is stored in the zip file */\n    private ZipEntryInfo entryInfo;\n\n    /** Smallest DOS time (Epoch 1980) */\n    private final static long MIN_DOS_TIME = 0x00002100L;\n\n    private static final ZoneId DEFAULT_ZONE = ZoneId.systemDefault();\n\n    /** Value of the bit flag that denotes a Unix directory in the external attributes */\n    private final static int UNIX_DIRECTORY_FLAG = 16384;\n    /** Value of the bit flag that denotes a Unix file in the external attributes */\n    private final static int UNIX_FILE_FLAG = 32768;\n\n    /** Value of the bit flag that denotes an MS-DOS directory in the external attributes */\n    private final static int MSDOS_DIRECTORY_FLAG = 0x10;\n    /** Value of the bit flag that denotes a read-only MS-DOS file in the external attributes */\n    private final static int MSDOS_READ_ONLY_FLAG = 1;\n\n    /** Value of the user write permission bit */\n    private final static int USER_WRITE_PERMISSION_BIT = 128;\n\n    /** Value of the Unix platform used in the 'version made by' central directory field */\n    private static final int PLATFORM_UNIX = 3;\n    /** Value of the MSDOS/OS-2 platform (FAT filesystem) used in the 'version made by' central directory field */\n    static final int PLATFORM_FAT  = 0;\n\n\n    /**\n     * Creates a new Zip entry with an empty name.\n     */\n    public ZipEntry() {\n        this(\"\");\n    }\n\n    /**\n     * Creates a new Zip entry with the specified name.\n     *\n     * @param name the name of the entry\n     */\n    public ZipEntry(String name) {\n        this.name = name;\n    }\n\n    /**\n     * Creates a new Zip entry with fields taken from the specified zip entry.\n     *\n     * @param entry the entry to get fields from\n     */\n    public ZipEntry(java.util.zip.ZipEntry entry) {\n        this.name = entry.getName();\n        this.crc = entry.getCrc();\n        this.size = entry.getSize();\n        this.compressedSize = entry.getCompressedSize();\n        this.method = entry.getMethod();\n        this.comment = entry.getComment();\n\n        setExtra(entry.getExtra());\n\n        // ZipEntry.getTime() has to do a DOS time to Java time conversion, and we have to do the opposite.\n        // This is inefficient but there is unfortunately no way to retrieve the DOS time field as it is private.\n        setTime(entry.getTime());\n    }\n\n    /**\n     * Sets Unix permissions in a way that is understood by Info-Zip's unzip command.\n     *\n     * @param mode an <code>int</code> value\n     */\n    public void setUnixMode(int mode) {\n        boolean isDirectory = isDirectory();\n        setExternalAttributes(\n              // Unix directory flag\n              ((isDirectory ? UNIX_DIRECTORY_FLAG : UNIX_FILE_FLAG) << 16)\n              // Unix file permissions\n              | ((long) mode << 16)\n              // MS-DOS read-only attribute\n              | ((mode & USER_WRITE_PERMISSION_BIT) == 0 ? MSDOS_READ_ONLY_FLAG : 0)\n              // MS-DOS directory flag\n              | (isDirectory ? MSDOS_DIRECTORY_FLAG : 0));\n\n        platform = PLATFORM_UNIX;\n    }\n\n    /**\n     * Unix permission.\n     *\n     * @return the unix permissions\n     */\n    public int getUnixMode() {\n        return (int) ((getExternalAttributes() >> 16) & 0xFFFF);\n    }\n\n    /**\n     * Returns <code>true</code> if this ZipEntry has Unix mode/permissions.\n     * If that's not the case, the value returned by {@link #getUnixMode()} has no meaning.\n     *\n     * @return <code>true</code> if this ZipEntry has Unix mode/permissions\n     */\n    public boolean hasUnixMode() {\n        return getPlatform()==PLATFORM_UNIX;\n    }\n\n    /**\n     * Sets the platform: {@link #PLATFORM_FAT} or {@link #PLATFORM_UNIX}.\n     *\n     * @param platform {@link #PLATFORM_FAT} or {@link #PLATFORM_UNIX}\n     */\n    protected void setPlatform(int platform) {\n        this.platform = platform;\n    }\n\n    /**\n     * Replaces all current extra fields with the specified ones.\n     *\n     * @param fields an array of extra fields\n     */\n    public void setExtraFields(ZipExtraField[] fields) {\n        extraFields = new ArrayList<>();\n        Collections.addAll(extraFields, fields);\n    }\n\n    /**\n     * Returns the extra fields of this entry.\n     *\n     * @return the extra fields of this entry\n     */\n    public ZipExtraField[] getExtraFields() {\n        if (extraFields == null) {\n            return new ZipExtraField[0];\n        }\n        return extraFields.toArray(new ZipExtraField[0]);\n    }\n\n    /**\n     * Adds an extra fields, replacing any extra field of the same type previously added.\n     *\n     * @param ze the extra field to add\n     */\n    public void addExtraField(ZipExtraField ze) {\n        if (extraFields == null) {\n            extraFields = new Vector<>();\n        }\n\n        ZipShort type = ze.getHeaderId();\n        for (int i = 0, nbFields = extraFields.size(); i < nbFields; i++) {\n            if (extraFields.get(i).getHeaderId().equals(type)) {\n                extraFields.set(i, ze);\n                return;\n            }\n        }\n        extraFields.add(ze);\n    }\n\n    /**\n     * Removes the first extra field corresponding to the given type.\n     *\n     * @param type the type of extra field to remove\n     * @return <code>true</code> if an extra field corresponding to given type was removed, <code>false</code> if no\n     * matching field was found\n     */\n    public boolean removeExtraField(ZipShort type) {\n        if (extraFields == null) {\n            return false;\n        }\n        for (int i = 0; i < extraFields.size(); i++) {\n            var field = extraFields.get(i);\n            if (field != null && Objects.equals(field.getHeaderId(), type)) {\n                extraFields.remove(i);\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns the data of the local file extra fields. The returned byte array may be empty but never\n     * <code>null</code>.\n     *\n     * @return the data of the local file extra fields\n     */\n    public byte[] getLocalFileDataExtra() {\n        return ExtraFieldUtils.mergeLocalExtraFields(getExtraFields());\n    }\n\n    /**\n     * Returns the data of the central directory extra fields. The returned byte array may be empty but never\n     * <code>null</code>.\n     *\n     * @return the data of the central directory extra fields\n     */\n    public byte[] getCentralDirectoryExtra() {\n        return ExtraFieldUtils.mergeCentralExtraFields(getExtraFields());\n    }\n\n    /**\n     * Sets the name of this entry.\n     *\n     * @param name the new name for this entry\n     */\n    protected void setName(String name) {\n        this.name = name;\n    }\n\n    /**\n     * Returns <code>true</code> if the entry is a directory. Directory entries are characterized by their name\n     * ending with a '/' character.\n     *\n     * @return <code>true</code> if the entry is a directory\n     */\n    public boolean isDirectory() {\n        return getName().endsWith(\"/\");\n    }\n\n    /**\n     * Returns the {@link ZipEntryInfo} instance that contains info about how this entry is stored in the zip file.\n     *\n     * @return the {@link ZipEntryInfo} instance that contains info about how this entry is stored in the zip file\n     */\n    protected ZipEntryInfo getEntryInfo() {\n        return entryInfo;\n    }\n\n    /**\n     * Sets the {@link ZipEntryInfo} instance that contains info about how this entry is stored in the zip file.\n     *\n     * @param entryInfo the {@link ZipEntryInfo} instance that contains info about how this entry is stored in the zip file\n     */\n    protected void setEntryInfo(ZipEntryInfo entryInfo) {\n        this.entryInfo = entryInfo;\n    }\n\n    /**\n     * Sets the uncompressed size of the entry data.\n     *\n     * @param size the uncompressed size in bytes\n     * @throws IllegalArgumentException if the specified size is less than 0 or greater than 0xFFFFFFFF bytes\n     */\n    public void setSize(long size) {\n        if (!isValidUnsignedInt(size)) {\n            throw new IllegalArgumentException(\"Invalid entry size\");\n        }\n\n\t    this.size = size;\n    }\n\n    /**\n     * Sets the size of the compressed entry data.\n     *\n     * @param csize the compressed size to set to\n     */\n    public void setCompressedSize(long csize) {\n        if (!isValidUnsignedInt(csize)) {\n            throw new IllegalArgumentException(\"Invalid entry size\");\n        }\n\n        this.compressedSize = csize;\n    }\n\n    /**\n     * Sets the CRC-32 checksum of the uncompressed entry data.\n     *\n     * @param crc the new CRC-32 value\n     * @throws IllegalArgumentException if the specified CRC-32 value is less than 0 or greater than 0xFFFFFFFF\n     */\n    public void setCrc(long crc) {\n        if (!isValidUnsignedInt(crc)) {\n            throw new IllegalArgumentException(\"invalid entry crc-32\");\n        }\n\n        this.crc = crc;\n    }\n\n    /**\n     * Returns this entry's date/time expressed in the Java time format, i.e. as a number of milliseconds since\n     * the Epoch.\n     *\n     * @return this entry's date/time expressed in the Java time format\n     */\n    public long getTime() {\n        return javaTime;\n    }\n\n    /**\n     * Sets this entry's date/time to the specified one. The time must be expressed in the Java time format,\n     * i.e. as a number of milliseconds since the Epoch.\n     *\n     * @param javaTime the new time of this entry, expressed in the Java time format\n     */\n    public void setTime(long javaTime) {\n        this.javaTime = javaTime;\n        this.dosTime = javaTime == -1 ? -1 : javaToDosTime(javaTime);\n    }\n\n    /**\n     * Returns this entry's date/time expressed in the DOS time format.\n     *\n     * @return this entry's date/time expressed in the DOS time format\n     */\n    protected long getDosTime() {\n        return dosTime;\n    }\n\n    /**\n     * Sets this entry's date/time to the specified one. The time must be expressed in the DOS time format.\n     *\n     * @param dosTime the new time of this entry, expressed in the DOS time format\n     */\n    protected void setDosTime(long dosTime) {\n        this.dosTime = dosTime;\n        this.javaTime = dosTime < 0 ? -1 : dosToJavaTime(dosTime);\n    }\n\n    /**\n     * Sets the compression method for the entry.\n     *\n     * @param method the compression method, either {@link ZipConstants#STORED} or {@link ZipConstants#DEFLATED}\n     * @throws IllegalArgumentException if the specified compression method is invalid\n     */\n    public void setMethod(int method) {\n        if (method != ZipConstants.STORED && method != ZipConstants.DEFLATED) {\n            throw new IllegalArgumentException(\"Invalid compression method\");\n        }\n\n        this.method = method;\n    }\n\n    /**\n     * Sets the optional comment string for the entry.\n     *\n     * @param comment the comment string\n     * @throws IllegalArgumentException if the length of the specified comment string is greater than 0xFFFF bytes\n     */\n    public void setComment(String comment) {\n        if (comment != null && comment.length() > 0xffff/3 && getUTF8Length(comment) > 0xffff) {\n            throw new IllegalArgumentException(\"invalid entry comment length\");\n        }\n        this.comment = comment;\n    }\n\n    /**\n     * Throws an <code>IllegalArgumentException</code> if byte array cannot be parsed into extra fields.\n     *\n     * @param extra an array of bytes to be parsed into extra fields\n     * @throws IllegalArgumentException if the byte array cannot be parsed into extra fields\n     */\n    public void setExtra(byte[] extra) throws IllegalArgumentException {\n        if (extra == null || extra.length == 0) {\n            extraFields = null;\n        } else {\n            try {\n                setExtraFields(ExtraFieldUtils.parse(extra));\n            } catch (Exception e) {\n                throw new IllegalArgumentException(e.getMessage());\n            }\n        }\n    }\n\n\n    /*\n     * Converts DOS time (Epoch=1980) to Java time (Epoch=1970).\n     *\n     * @param dosTime time expressed in the convoluted DOS time format\n     * @return time expressed as the number of milliseconds since the epoch\n     */\n    protected static long dosToJavaTime(long dosTime) {\n        int year = (int) ((dosTime >> 25) & 0x7f) + 1980;\n        int month = (int) ((dosTime >> 21) & 0x0f) - 1;\n        int day = (int) (dosTime >> 16) & 0x1f;\n        int hour = (int) (dosTime >> 11) & 0x1f;\n        int minute = (int) (dosTime >> 5) & 0x3f;\n        int second = (int) (dosTime << 1) & 0x3e;\n\n        ZonedDateTime zdt = ZonedDateTime.of(year, month + 1, day, hour, minute, second, 0, DEFAULT_ZONE);\n        return zdt.toInstant().toEpochMilli();\n    }\n\n    /**\n     * Converts Java time (Epoch=1970) to DOS time (Epoch=1980).\n     *\n     * @param javaTime number of milliseconds since the epoch\n     * @return time expressed in the convoluted DOS time format\n     */\n    protected static long javaToDosTime(long javaTime) {\n        Instant instant = Instant.ofEpochMilli(javaTime);\n        ZonedDateTime zdt = instant.atZone(ZoneId.systemDefault());\n\n        long year = zdt.getYear();\n        if (year < 1980) {\n            return MIN_DOS_TIME;\n        }\n        return ((year - 1980) << 25)\n                |  ((zdt.getMonthValue()) << 21)\n                |  (zdt.getDayOfMonth() << 16)\n                |  (zdt.getHour() << 11)\n                |  (zdt.getMinute() << 5)\n                |  (zdt.getSecond() >> 1);\n    }\n\n    /*\n     * Returns the length of the given String's <code>UTF-8</code> representation.\n     */\n    protected static int getUTF8Length(String s) {\n        // This method is a dup from java.util.ZipOutputStream\n        int count = 0;\n        for (int i = 0; i < s.length(); i++) {\n            char ch = s.charAt(i);\n            if (ch <= 0x7f) {\n                count++;\n            } else if (ch <= 0x7ff) {\n                count += 2;\n            } else {\n                count += 3;\n            }\n        }\n        return count;\n    }\n\n    /**\n     * Returns <code>true</code> if the given long is a valid unsigned int value, i.e. comprised between 0 and 2^32-1.\n     *\n     * @param l the long value to test\n     * @return <code>true</code> if the given long is a valid unsigned int value, i.e. comprised between 0 and 2^32-1\n     */\n    protected boolean isValidUnsignedInt(long l) {\n        return l >= 0 && l <= 0xFFFFFFFFL;\n    }\n\n\n    /**\n     * Returns a cloned instance of this entry.\n     *\n     * @return a cloned instance of this entry\n     * @throws CloneNotSupportedException should never happen\n     */\n    @Override\n    public Object clone() throws CloneNotSupportedException {\n        ZipEntry ze = (ZipEntry)super.clone();\n\n        if (extraFields != null) {\n            ze.extraFields = new ArrayList<>(extraFields);\n        }\n\n        return ze;\n    }\n\n    /**\n     * Returns a hash of this entry's name.\n     *\n     * @return a hash of this entry's name\n     */\n    public int hashCode() {\n        return getName().hashCode();\n    }\n\n    /**\n     * Returns <code>true</code> if the given object is a <code>ZipEntry</code> that has the same name as this one.\n     *\n     * @param o the object to test for equality\n     * @return <code>true</code> if the given object is a <code>ZipEntry</code> that has the same name as this one\n     */\n    public boolean equals(Object o) {\n        return (o instanceof ZipEntry) && ((ZipEntry) o).getName().equals(getName());\n    }\n\n\n    @Override\n    public String toString() {\n        return \"ZipEntry (\" + name + \")\";\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/zip/provider/ZipEntryInfo.java",
    "content": "package com.mucommander.commons.file.impl.zip.provider;\n\n/**\n * ZipEntryInfo is a C struct-like class that holds the information about an entry that is used for parsing and writing\n * the Zip file.\n *\n * @author Maxence Bernard\n */\npublic final class ZipEntryInfo {\n\n    /** Offset to the central file header */\n    long centralHeaderOffset = -1;\n\n    /** Length of the central file header */\n    long centralHeaderLen = -1;\n\n    /** Offset to the local file header */\n    long headerOffset = -1;\n\n    /** Offset to the start of file data */\n    long dataOffset = -1;\n\n    /** <code>true</code> if this entry has a data descriptor in the Zip file */\n    boolean hasDataDescriptor;\n\n    /** The encoding used for filename and comment fields */\n    String encoding;\n\n    /** The filename's bytes */\n    byte[] filename;\n\n    /** The comment's bytes */\n    byte[] comment;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/zip/provider/ZipEntryOutputStream.java",
    "content": "package com.mucommander.commons.file.impl.zip.provider;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.zip.CRC32;\n\n/**\n * ZipEntryOutputStream is an abstract <code>OutputStream</code> used for compressing a Zip entry's data to an\n * underlying OutputStream.\n *\n * <p>The CRC32 checksum is calculated on-the-fly as data gets written to the stream, {@link #getCrc()} returns the\n * current checksum value. The {@link #getTotalIn()} and {@link #getTotalOut()} methods keep track of the uncompressed\n * and compressed of the supplied data.\n *\n * <p>There currently are two implementations of this class:\n * <ul>\n *  <li>{@link com.mucommander.commons.file.impl.zip.provider.DeflatedOutputStream}: implements the DEFLATED compression method\n *  </li>\n *  <li>{@link com.mucommander.commons.file.impl.zip.provider.StoredOutputStream}: implements the STORED compression method\n * (i.e. no compression)</li>\n * </ul>\n *\n * <p>\n * This class is based off the <code>org.apache.tools.zip</code> package of the <i>Apache Ant</i> project. The Ant\n * code has been modified under the terms of the Apache License which you can find in the bundled muCommander license\n * file. It was forked at version 1.7.0 of Ant.\n *\n * @author Maxence Bernard\n */\npublic abstract class ZipEntryOutputStream extends OutputStream {\n\n    /** The underlying stream where the compressed data is sent */\n    protected OutputStream out;\n\n    /** Compression method (DEFLATED or STORED) */\n    protected int method;\n\n    /** The CRC32 instance that calculates the checksum */\n    protected CRC32 crc = new CRC32();\n\n\n    /**\n     * Creates a new <code>EntryOutputStream</code> that writes compressed data to the given <code>OutputStream</code>\n     * and automatically updates the supplied <code>CRC32</code> checksum.\n     *\n     * @param out the OutputStream where the compressed data is sent to\n     * @param method the compression method, {@link ZipConstants#DEFLATED} or {@link ZipConstants#STORED}\n     */\n    public ZipEntryOutputStream(OutputStream out, int method) {\n        this.out = out;\n        this.method = method;\n    }\n\n    /**\n     * Returns the compression method used for writing the supplied data.\n     *\n     * @return the compression method used for writing the supplied data\n     */\n    public int getMethod() {\n        return method;\n    }\n\n    /**\n     * Returns the CRC value of the data written so far.\n     *\n     * @return the CRC value of the data written so far.\n     */\n    public long getCrc() {\n        return crc.getValue();\n    }\n\n\n    /////////////////////////////////////////\n    // Partial OutputStream implementation //\n    /////////////////////////////////////////\n\n    @Override\n    public void write(int b) throws IOException {\n        byte[] array = new byte[1];\n        array[0] = (byte) (b & 0xff);\n        write(array, 0, 1);\n    }\n\n    /**\n     * Flushes the underlying <code>OutputStream</code>.\n     */\n    @Override\n    public void flush() throws IOException {\n        out.flush();\n    }\n\n\n    //////////////////////\n    // Abstract methods //\n    //////////////////////\n\n    /**\n     * Returns the uncompressed size of the data written so far.\n     *\n     * @return the uncompressed size of the data written so far\n     */\n    public abstract int getTotalIn();\n\n    /**\n     * Returns the compressed size of the data written so far.\n     *\n     * @return the compressed size of the data written so far\n     */\n    public abstract int getTotalOut();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/zip/provider/ZipExtraField.java",
    "content": "/*\n *  Licensed to the Apache Software Foundation (ASF) under one or more\n *  contributor license agreements.  See the NOTICE file distributed with\n *  this work for additional information regarding copyright ownership.\n *  The ASF licenses this file to You under the Apache License, Version 2.0\n *  (the \"License\"); you may not use this file except in compliance with\n *  the License.  You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n *  Unless required by applicable law or agreed to in writing, software\n *  distributed under the License is distributed on an \"AS IS\" BASIS,\n *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *  See the License for the specific language governing permissions and\n *  limitations under the License.\n *\n */\n\npackage com.mucommander.commons.file.impl.zip.provider;\n\nimport java.util.zip.ZipException;\n\n/**\n * General format of extra field data.\n *\n * <p>Extra fields usually appear twice per file, once in the local\n * file data and once in the central directory.  Usually they are the\n * same, but they don't have to be.  {@link\n * java.util.zip.ZipOutputStream java.util.zip.ZipOutputStream} will\n * only use the local file data in both places.\n *\n * <p>\n * This class is based off the <code>org.apache.tools.zip</code> package of the <i>Apache Ant</i> project. The Ant\n * code has been modified under the terms of the Apache License which you can find in the bundled muCommander license\n * file. It was forked at version 1.7.0 of Ant.\n *\n * @author Apache Ant, Maxence Bernard\n */\npublic interface ZipExtraField {\n\n    /**\n     * The Header-ID.\n     * @return the header id\n     */\n    ZipShort getHeaderId();\n\n    /**\n     * Length of the extra field in the local file data - without\n     * Header-ID or length specifier.\n     * @return the length of the field in the local file data\n     */\n    ZipShort getLocalFileDataLength();\n\n    /**\n     * Length of the extra field in the central directory - without\n     * Header-ID or length specifier.\n     * @return the length of the field in the central directory\n     */\n    ZipShort getCentralDirectoryLength();\n\n    /**\n     * The actual data to put into local file data - without Header-ID\n     * or length specifier.\n     * @return the data\n     */\n    byte[] getLocalFileDataData();\n\n    /**\n     * The actual data to put central directory - without Header-ID or\n     * length specifier.\n     * @return the data\n     */\n    byte[] getCentralDirectoryData();\n\n    /**\n     * Populate data from this array as if it was in local file data.\n     * @param data an array of bytes\n     * @param offset the start offset\n     * @param length the number of bytes in the array from offset\n     *\n     * @throws ZipException on error\n     */\n    void parseFromLocalFileData(byte[] data, int offset, int length)\n        throws ZipException;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/zip/provider/ZipFile.java",
    "content": "/*\n *  Licensed to the Apache Software Foundation (ASF) under one or more\n *  contributor license agreements.  See the NOTICE file distributed with\n *  this work for additional information regarding copyright ownership.\n *  The ASF licenses this file to You under the Apache License, Version 2.0\n *  (the \"License\"); you may not use this file except in compliance with\n *  the License.  You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n *  Unless required by applicable law or agreed to in writing, software\n *  distributed under the License is distributed on an \"AS IS\" BASIS,\n *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *  See the License for the specific language governing permissions and\n *  limitations under the License.\n *\n */\n\npackage com.mucommander.commons.file.impl.zip.provider;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.UnsupportedFileOperationException;\nimport com.mucommander.commons.io.*;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.*;\nimport java.util.Hashtable;\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.Vector;\nimport java.util.zip.Deflater;\nimport java.util.zip.Inflater;\nimport java.util.zip.InflaterInputStream;\nimport java.util.zip.ZipException;\n\n/**\n * This class is a replacement for <code>java.util.ZipFile</code> with some extra functionalities:\n * <ul>\n *  <li>Ability to add or remove entries 'on-the-fly', i.e. without rewriting the whole archive.\n *  <li>Advanced encoding support for filenames and comments. UTF-8 is used for parsing entries that explicitely declare\n * using UTF-8 (as per Zip specs). For entries that do not use UTF-8, the encoding is auto-detected (best effort).\n * Alternatively, the encoding used for parsing entries can be specified if it is known in advance. For new entries\n * added with {@link #addEntry(ZipEntry)}, UTF-8 is always used and declared as such in the Zip headers.\n *  <li>Loads the internal/external file attributes and extra fields instead of ignoring them\n * </ul>\n *\n * <p>This class doesn't extend <code>java.util.zip.ZipFile</code> as it would have to reimplement all methods anyway.\n * Like <code>java.util.ZipFile</code>, it supports compressed (DEFLATED) and uncompressed (STORED) entries.\n *\n * <p>Random read access is required to instantiate a <code>ZipFile</code> and retrieve its entries. Furthermore, random\n * write access is required for methods that modify the Zip file.\n *\n * <p>The method signatures mimic the ones of <code>java.util.zip.ZipFile</code> with a few exceptions:\n * <ul>\n *   <li>There is no <code>getName</code> method.</li>\n *   <li>There is no <code>close</code> method: underlying input and output streams are opened and closed automatically\n *    as they are needed.</li>\n *   <li><code>entries</code> has been renamed to {@link #getEntries()} and returns an <code>Iterator</code> instead of\n * an <code>Enumeration</code>.</li>\n *   <li><code>size</code> has been renamed to {@link #getNbEntries()}.</li>\n * </ul>\n *\n * <p>\n * This class is based off the <code>org.apache.tools.zip</code> package of the <i>Apache Ant</i> project. The Ant\n * code has been modified under the terms of the Apache License which you can find in the bundled muCommander license\n * file. It was forked at version 1.7.0 of Ant.\n *\n * @author Apache Ant, Maxence Bernard\n */\npublic class ZipFile implements ZipConstants {\n    private static final Logger LOGGER = LoggerFactory.getLogger(ZipFile.class);\n\n    /** The underlying archive file */\n    private AbstractFile file;\n\n    /** The currently opened RandomAccessInputStream to the zip file (may be null) */\n    private RandomAccessInputStream rais;\n\n    /** The currently opened RandomAccessInputStream to the zip file (may be null) */\n    private RandomAccessOutputStream raos;\n\n    /** Contains ZipEntry instances corresponding to the archive's entries, in the order they were found in the archive. */\n    private Vector<ZipEntry> entries = new Vector<>();\n\n    /** Maps entry paths to corresponding ZipEntry instances */\n    private Map<String, ZipEntry> nameMap = new Hashtable<>();\n\n    /** Global zip file comment */\n    private String comment;\n\n    /**\n     * The default encoding to use for parsing filenames and comments. This value is only used for Zip entries that do\n     * not have the UTF-8 flag set. If not specified (null), then automatic encoding detection is used (default).\n     */\n    private String defaultEncoding = null;\n\n    /** Holds byte buffer instance used to convert short and longs, avoids creating lots of small arrays */\n    private ZipBuffer zipBuffer = new ZipBuffer();\n\n    \n    /**\n     * Opens the given Zip file and parses information about the entries it contains.\n     *\n     * <p>The given {@link AbstractFile} must have random read access. If not, an <code>IOException</code> will be\n     * thrown.\n     *\n     * @param f the archive file\n     * @throws IOException if an error occurred while reading the Zip file.\n     * @throws ZipException if this file is not a valid Zip file\n     * @throws UnsupportedFileOperationException if a required operation is not supported by the underlying filesystem.\n     */\n    public ZipFile(AbstractFile f) throws IOException, ZipException, UnsupportedFileOperationException {\n        this.file = f;\n\n        try {\n            openRead();\n            parseCentralDirectory();\n        }\n        finally {\n            closeRead();\n        }\n    }\n\n\n    /**\n     * Opens the zip file for random read access.\n     *\n     * @throws IOException if an error occured while opening the zip file for random read access.\n     * @throws UnsupportedFileOperationException if a required operation is not supported by the underlying filesystem.\n     */\n    private void openRead() throws IOException, UnsupportedFileOperationException {\n        if(rais!=null) {\n            LOGGER.info(\"Warning: an existing RandomAccessInputStream was found, closing it now\");\n            rais.close();\n        }\n\n        rais = file.getRandomAccessInputStream();\n    }\n\n    /**\n     * Closes the current RandomAccessInputStream to the zip file.\n     *\n     * @throws IOException if an error occurred\n     */\n    private void closeRead() throws IOException {\n        if(rais!=null) {\n            try {\n                rais.close();\n            }\n            finally {\n                rais = null;\n            }\n        }\n    }\n\n    /**\n     * Opens the zip file for random write access.\n     *\n     * @throws IOException if an error occured while opening the zip file for random read access.\n     * @throws UnsupportedFileOperationException if a required operation is not supported by the underlying filesystem.\n     */\n    private void openWrite() throws IOException {\n        if (raos != null) {\n            LOGGER.info(\"Warning: an existing RandomAccessOutputStream was found, closing it now\");\n            raos.close();\n        }\n\n        // create a buffered output stream to improve write performance, as headers are written by small chunks\n        raos = new BufferedRandomOutputStream(file.getRandomAccessOutputStream(), WRITE_BUFFER_SIZE);\n    }\n\n    /**\n     * Closes the current RandomAccessOutputStream to the zip file.\n     *\n     * @throws IOException if an error occurred\n     */\n    private void closeWrite() throws IOException {\n        if(raos!=null) {\n            try {\n                raos.close();\n            }\n            finally {\n                raos = null;\n            }\n        }\n    }\n\n    /**\n     * Returns the default encoding to use for parsing filenames and comments. This value is not used for Zip entries\n     * that explicitely declare using UTF-8 (in the general purpose bit flag).\n     *\n     * <p>By default, this method returns <code>null</code> to indicate that automatic encoding detection is used. \n     * Although it is not 100% accurate, encoding detection is the preferred approach, unless the encoding is known\n     * in advance which is rather uncommon.\n     *\n     * <p>Note that this value only affects entries <i>parsing</i>. Written entries are systematically encoded in\n     * <code>UTF-8</code> and declared as such in the general purpose bit flag so that proper zip unpackers know what\n     * encoding to expect.\n     *\n     * @return the default encoding to use for parsing filenames and comments\n     */\n    public String getDefaultEncoding() {\n        return defaultEncoding;\n    }\n\n    /**\n     * Sets the default encoding to use for parsing filenames and comments. This value is not used for Zip entries\n     * that explicitely declare using UTF-8 (in the general purpose bit flag).\n     *\n     * <p>By default, the encoding is <code>null</code> to indicate that automatic encoding detection is used.\n     * Although it is not 100% accurate, encoding detection is the preferred approach, unless the encoding is known\n     * in advance which is rather uncommon.\n     *\n     * <p>Note that this value only affects entries <i>parsing</i>. Written entries are systematically encoded in\n     * <code>UTF-8</code> and declared as such in the general purpose bit flag so that proper zip unpackers know what\n     * encoding to expect.\n     *\n     * @param defaultEncoding the default encoding to use for parsing filenames and comments\n     */\n    public void setDefaultEncoding(String defaultEncoding) {\n        this.defaultEncoding = defaultEncoding;\n    }\n\n    /**\n     * Returns all entries as an <code>Iterator</code> of {@link ZipEntry} instances.\n     *\n     * @return Returns all entries as an <code>Iterator</code> of ZipEntry instances.\n     */\n    public Iterator<ZipEntry> getEntries() {\n        return entries.iterator();\n    }\n\n    /**\n     * Returns the number of entries contained by this Zip file.\n     *\n     * @return the number of entries contained by this Zip file\n     */\n    public int getNbEntries() {\n        return entries.size();\n    }\n\n    /**\n     * Returns a named entry or <code>null</code> if no entry by that name exists.\n     *\n     * @param name name of the entry.\n     * @return the ZipEntry corresponding to the given name or <code>null</code> if not present.\n     */\n    public ZipEntry getEntry(String name) {\n        return nameMap.get(name);\n    }\n\n    /**\n     * Returns an InputStream for reading the contents of the given entry.\n     *\n     * @param ze the entry to get the stream for.\n     * @return a stream to read the entry from.\n     * @throws IOException if unable to create an input stream from the zipentry\n     * @throws ZipException if the zipentry has an unsupported compression method\n     * @throws UnsupportedFileOperationException if a required operation is not supported by the underlying filesystem.\n     */\n    public InputStream getInputStream(ZipEntry ze) throws IOException, ZipException, UnsupportedFileOperationException {\n\n        ZipEntryInfo entryInfo = ze.getEntryInfo();\n        if (entryInfo == null)\n            throw new ZipException(\"Unknown entry: \"+ze.getName());\n\n        openRead();\n        RandomAccessInputStream entryIn = this.rais;\n\n        // If data offset is -1 (not calculated yet), calculate it now\n        if (entryInfo.dataOffset == -1)\n            calculateDataOffset(entryInfo);\n\n        this.rais = null;\n        \n        long start = entryInfo.dataOffset;\n        BoundedInputStream bis = new BoundedInputStream(entryIn, start, ze.getCompressedSize());\n        switch (ze.getMethod()) {\n            case ZipConstants.STORED:\n                return bis;\n            case ZipConstants.DEFLATED:\n                bis.addDummy();\n                return new InflaterInputStream(bis, new Inflater(true));\n            default:\n                throw new ZipException(\"Found unsupported compression method \"\n                                       + ze.getMethod());\n        }\n    }\n\n\n    /**\n     * Deletes the given entry from this zip file. For performance reasons, this method removes the central file\n     * header and zero out the local file header and data so that the entry cannot be retrieved, but it does\n     * not reclaim the freed space and produces fragmentation. In other words, the resulting zip file will not be\n     * smaller after the entry has been deleted and will contain an area of unused space. The {@link #defragment} method\n     * can be called to reclaim the free space.\n     *\n     * <p>There is one case where this method reclaims the free space: when the specified entry is the last one in the\n     * zip file. In this case, the resulting zip file will be smaller.\n     *\n     * <p>Note that 'fragmented' zip files are perfectly valid zip files, any zip parser should be able to cope with\n     * such files.\n     *\n     * <p>The underlying {@link AbstractFile} must have random write access. If not, an <code>IOException</code> will be\n     * thrown.\n     *\n     * @param ze the ZipEntry to delete\n     * @throws IOException if an I/O error occurred\n     * @throws ZipException if the specified ZipEntry cannot be found in this zip file\n     * @throws UnsupportedFileOperationException if a required operation is not supported by the underlying filesystem.\n     */\n    public void deleteEntry(ZipEntry ze) throws IOException, ZipException, UnsupportedFileOperationException {\n        openRead();\n        openWrite();\n\n        try {\n            ZipEntryInfo entryInfo = ze.getEntryInfo();\n            if (entryInfo == null) {\n                // Fail silently if the entry is a directory as specific directory entries do not always exist\n                // in zip files.\n                if(ze.isDirectory())\n                    return;\n\n                throw new ZipException(\"Unknown entry: \"+ze.getName());\n            }\n\n            // Strip out central file header of deleted entry\n\n            int entryIndex = entries.indexOf(ze);\n            int nbEntries = entries.size();\n\n            long cdStartOffset;\n            long cdEndOffset;\n\n            if(nbEntries==1) {\n                // Special case if the deleted entry is the only one, the zip file will become empty.\n                // Note: empty zip files must have the central directory start at offset 0.\n                cdStartOffset = 0;\n                cdEndOffset = 0;\n\n                raos.seek(0);\n            }\n            else {\n                cdStartOffset = entries.elementAt(0).getEntryInfo().centralHeaderOffset;\n                long shift;\n\n                if(entryIndex==nbEntries-1) {\n                    // Lucky case! If the entry to delete is the last one, we can easily/quickly reclaim the space used\n                    // by this entry by moving the central directory (minus the last header corresponding to the deleted\n                    // entry) to where the entry's local header started.\n\n                    // The entry before the deleted one, will become the last one\n                    ZipEntryInfo lastEntryInfo = entries.elementAt(nbEntries-2).getEntryInfo();\n\n                    // Destination offset\n                    long newCdStartOffset = entryInfo.headerOffset;\n\n                    cdEndOffset = lastEntryInfo.centralHeaderOffset + lastEntryInfo.centralHeaderLen;\n                    long cdLength = cdEndOffset-cdStartOffset;\n\n                    // Copy the central directory\n                    StreamUtils.copyChunk(rais, raos, cdStartOffset, newCdStartOffset, cdLength);\n\n                    // Update central directory header offsets\n                    shift = cdStartOffset-newCdStartOffset;\n                    for(int i=0; i<nbEntries-1; i++)\n                        entries.elementAt(i).getEntryInfo().centralHeaderOffset -= shift;\n\n                    cdStartOffset = newCdStartOffset;\n                    cdEndOffset = newCdStartOffset + cdLength;\n                }\n                else {\n                    // Most frequent case: the entry to delete is neither the only one nor the last one.\n                    // In this case, we don't reclaim the space (would be too slow) but simply zero out\n                    // the local file header and data (so that the data entry can't be retrieved)\n\n                    // If data offset is -1 (not calculated yet), calculate it now\n                    if (entryInfo.dataOffset == -1)\n                        calculateDataOffset(entryInfo);\n\n                    // Zero out all bytes of the local file header+data for the deleted entry\n                    // Note: the data descriptor (if any) is not erased, this would require some extra check and it is\n                    // not really necessary, as the information it contains is not sensitive\n                    raos.seek(entryInfo.headerOffset);\n                    StreamUtils.fillWithConstant(raos, (byte)0, entryInfo.dataOffset-entryInfo.headerOffset + ze.getCompressedSize(), WRITE_BUFFER_SIZE);\n\n                    // Update the central directory :\n                    // - do not touch the file headers that are located before the deleted entry\n                    // - move the file headers that are located after the deleted entry to where the deleted entry's\n                    // header was (this will remove the deleted entry's file header)\n                    \n                    ZipEntryInfo lastEntryInfo = entries.elementAt(nbEntries-1).getEntryInfo();\n\n                    long startOffset = entries.elementAt(entryIndex+1).getEntryInfo().centralHeaderOffset;\n                    cdEndOffset = lastEntryInfo.centralHeaderOffset + lastEntryInfo.centralHeaderLen;\n\n                    StreamUtils.copyChunk(rais, raos, startOffset, entryInfo.centralHeaderOffset, cdEndOffset-startOffset);\n\n                    // Update central directory header offsets for files located after the deleted entry as their\n                    // offset has changed\n                    shift = entryInfo.centralHeaderLen;\n                    for(int i=entryIndex+1; i<nbEntries; i++)\n                        entries.elementAt(i).getEntryInfo().centralHeaderOffset -= shift;\n\n                    cdEndOffset -= shift;\n                }\n            }\n\n            // Write the central directory end section\n            ZipOutputStream.writeCentralDirectoryEnd(raos, nbEntries-1, cdEndOffset - cdStartOffset, cdStartOffset, comment, UTF_8, zipBuffer);\n\n            // Truncate the zip file to reclaim the trailing unused space\n            raos.setLength(raos.getOffset());\n\n            // All good, remove the deleted entry from the lists\n            entries.removeElementAt(entryIndex);\n            nameMap.remove(ze.getName());\n        } finally {\n            try {\n                closeRead();\n            } catch(IOException ignore) {\n            }\n            try {\n                closeWrite();\n            } catch(IOException ignore) {\n            }\n        }\n    }\n\n\n    /**\n     * Appends the given entry to the end of this zip file and returns an <code>OutputStream</code> that allows to write\n     * the contents of the entry. The returned <code>OutputStream</code> must always be closed for the zip file to be\n     * properly modified. Not doing will leave this zip file in a inconsistent, corrupted state.\n     *\n     * <p>The underlying {@link AbstractFile} must have random write access. If not, an <code>IOException</code> will be\n     * thrown.\n     *\n     * @param entry the entry to add to this zip file\n     * @return an OutputStream to write the contents of the entry\n     * @throws IOException if an I/O error occurred\n     * @throws UnsupportedFileOperationException if a required operation is not supported by the underlying filesystem.\n     * or is not implemented.\n     */\n    public OutputStream addEntry(final ZipEntry entry) throws IOException, UnsupportedFileOperationException {\n        try {\n            // Open the zip file for random read and write access\n            openRead();\n            openWrite();\n\n            // Write the new entry's local file header right before the central directory start\n            positionAtCentralDirectory();\n            long centralDirectoryStart = rais.getOffset();\n            raos.seek(centralDirectoryStart);\n\n            final ZipEntryInfo entryInfo = new ZipEntryInfo();\n            entryInfo.encoding = UTF_8;   // Always use UTF-8 for new entries\n            entryInfo.headerOffset = centralDirectoryStart;\n            entryInfo.dataOffset = entryInfo.headerOffset +\n                                     ZipOutputStream.writeLocalFileHeader(entry, raos, entryInfo.encoding, false, zipBuffer);\n\n            // Add the new entry to the internal lists\n            entry.setEntryInfo(entryInfo);\n            entries.add(entry);\n            nameMap.put(entry.getName(), entry);\n\n            // create the ZipEntryOutputStream to write the entry's contents\n\n            // Use BufferPool to avoid excessive memory allocation and garbage collection.\n            final byte[] deflaterBuf = BufferPool.getByteArray(DEFAULT_DEFLATER_BUFFER_SIZE);\n            ZipEntryOutputStream zeos = new DeflatedOutputStream(raos, new Deflater(DEFAULT_DEFLATER_COMPRESSION, true), deflaterBuf) {\n                // Post-data file info and central directory get written when the stream is closed\n                @Override\n                public void close() throws IOException {\n                    // Write data info in the local file header\n                    ZipOutputStream.finalizeEntryData(entry, this, raos, false, zipBuffer);\n\n                    // Write the central directory that was squashed by the new entry (at least partially)\n                    ZipEntry tempZe;\n                    ZipEntryInfo tempEntryInfo;\n                    int nbEntries = entries.size();\n                    long cdLength = 0;                  // Length of central directory\n                    long cdOffset = raos.getOffset();   // Offset of central directory\n                    for(int i=0; i<nbEntries; i++) {\n                        tempZe = entries.elementAt(i);\n                        tempEntryInfo = tempZe.getEntryInfo();\n\n                        // Update offset to central header\n                        tempEntryInfo.centralHeaderOffset = raos.getOffset();\n\n                        cdLength += ZipOutputStream.writeCentralFileHeader(\n                                        tempZe,\n                                        raos,\n                                        tempEntryInfo.encoding,     // Preserve existing encoding so that LFH and CFH match\n                                        tempEntryInfo.headerOffset,\n                                        tempEntryInfo.hasDataDescriptor,\n                                        zipBuffer);\n\n                        // Update length of central header\n                        tempEntryInfo.centralHeaderLen = raos.getOffset() - tempEntryInfo.centralHeaderOffset;\n                    }\n\n                    ZipOutputStream.writeCentralDirectoryEnd(raos, nbEntries, cdLength, cdOffset, comment, UTF_8, zipBuffer);\n\n                    // In some rare cases, the resulting zip file may be smaller.\n                    // Truncate the file to ensure that it ends at the central directory end position.\n                    raos.setLength(raos.getOffset());\n\n                    // Release the buffer for reuse\n                    BufferPool.releaseByteArray(deflaterBuf);\n\n                    super.close();\n                    closeWrite();\n                }\n            };\n\n            // Directory entries cannot contain data, close the stream now and return null\n            if(entry.isDirectory()) {\n                zeos.close();\n                return null;\n            }\n\n            return zeos;\n        }\n        finally {\n            closeRead();\n            // Note: RandomAccessOutputStream is closed by ZipEntryOutputStream#close()\n        }\n    }\n\n\n    /**\n     * Updates the date and permissions of the entry designated by the given ZipEntry object. The specified entry must\n     * exist in this Zip file.\n     *\n     * <p>The underlying {@link AbstractFile} must have random write access. If not, an <code>IOException</code> will be\n     * thrown.\n     *\n     * @param entry the entry to update\n     * @throws IOException if an I/O error occurred\n     * @throws UnsupportedFileOperationException if a required operation is not supported by the underlying filesystem.\n     */\n    public void updateEntry(ZipEntry entry) throws IOException {\n        try {\n            // Open the zip file for write\n            openWrite();\n\n            ZipEntryInfo entryInfo = entry.getEntryInfo();\n\n            /* Local file header */\n\n            // Update time and date\n            raos.seek(entryInfo.headerOffset+10);\n            raos.write(ZipLong.getBytes(entry.getDosTime(), zipBuffer.longBuffer));\n\n            // Note: external attributes are not present in the local file header\n\n            /* Central file header */\n\n            // Update 'Version made by', platform might have changed if the Zip didn't contain Unix permissions\n            raos.seek(entryInfo.centralHeaderOffset+4);\n            ZipOutputStream.writeVersionMadeBy(entry, raos, zipBuffer);\n\n            // Update time and date\n            raos.seek(entryInfo.centralHeaderOffset+12);\n            raos.write(ZipLong.getBytes(entry.getDosTime(), zipBuffer.longBuffer));\n\n            // Update 'external attributes' for permissions\n            raos.seek(entryInfo.centralHeaderOffset+38);\n            raos.write(ZipLong.getBytes(entry.getExternalAttributes(), zipBuffer.longBuffer));\n        } finally {\n            closeWrite();\n        }\n\n    }\n\n    \n    /**\n     * Removes free space fragments from this zip file, thus reducing the size of the zip file. If this zip file does\n     * not contain any free space fragments, the zip file is not modified.\n     *\n     * <p>Fragmentation occurs when deleting entries with {@link #deleteEntry(ZipEntry)}. When deleting several entries,\n     * this method should be called once after all entries have deleted.\n     *\n     * <p>The underlying {@link AbstractFile} must have random write access. If not, an <code>IOException</code> will be\n     * thrown.\n     *\n     * @throws IOException if an I/O error occurred\n     * @throws UnsupportedFileOperationException if a required operation is not supported by the underlying filesystem.\n     */\n    public void defragment() throws IOException {\n        int nbEntries = entries.size();\n        if(nbEntries==0)\n            return;\n\n        try {\n            openRead();\n            openWrite();\n\n            ZipEntry currentEntry, previousEntry;\n            ZipEntryInfo currentEntryInfo, previousEntryInfo;\n            long shift = 0;\n\n            // Special case for the first entry\n\n            currentEntry = entries.elementAt(0);\n            currentEntryInfo = currentEntry.getEntryInfo();\n\n            // If data offset is -1 (not calculated yet), calculate it now\n            if (currentEntryInfo.dataOffset == -1)\n                calculateDataOffset(currentEntryInfo);\n\n            if(currentEntryInfo.headerOffset>0) {\n                StreamUtils.copyChunk(rais, raos, currentEntryInfo.headerOffset, 0, (currentEntryInfo.dataOffset- currentEntryInfo.headerOffset)+currentEntry.getCompressedSize());\n                shift = currentEntryInfo.headerOffset;\n\n                currentEntryInfo.headerOffset = 0;\n                currentEntryInfo.dataOffset -= shift;\n            }\n\n            previousEntry = currentEntry;\n            previousEntryInfo = currentEntryInfo;\n\n            // Process all other entries\n\n            for(int i=1; i<nbEntries; i++) {\n                currentEntry = entries.elementAt(i);\n                currentEntryInfo = currentEntry.getEntryInfo();\n\n                // If data offset is -1 (not calculated yet), calculate it now\n                if (currentEntryInfo.dataOffset == -1)\n                    calculateDataOffset(currentEntryInfo);\n\n                // Calculate the offset to the end of the previous entry based on its data offset and compressed size\n                // and taking into account a potential data descriptor\n                long previousCompressedSize = previousEntry.getCompressedSize();\n                long previousEntryEnd = previousEntryInfo.dataOffset+previousCompressedSize;\n                if(previousEntryInfo.hasDataDescriptor)\n                    previousEntryEnd += 16;\n\n                // Tests if there is some unused space between the 2 entries\n                if(previousEntryEnd < currentEntryInfo.headerOffset) {\n                    StreamUtils.copyChunk(rais, raos, currentEntryInfo.headerOffset, previousEntryInfo.dataOffset+previousCompressedSize, (currentEntryInfo.dataOffset- currentEntryInfo.headerOffset)+currentEntry.getCompressedSize());\n                    shift = currentEntryInfo.headerOffset - (previousEntryInfo.dataOffset+previousCompressedSize);\n\n                    currentEntryInfo.headerOffset -= shift;\n                    currentEntryInfo.dataOffset -= shift;\n                }\n\n                previousEntry = currentEntry;\n                previousEntryInfo = currentEntryInfo;\n            }\n\n            // Rewrite central directory with updated offsets\n            if(shift!=0) {\n                long cdLength = 0;                  // Length of central directory\n                long cdOffset = raos.getOffset();   // Offset of central directory\n                ZipEntry ze;\n                ZipEntryInfo entryInfo;\n\n                for(int i=0; i<nbEntries; i++) {\n                    ze = entries.elementAt(i);\n                    entryInfo = ze.getEntryInfo();\n\n                    // Update offset to central directory file header\n                    entryInfo.centralHeaderOffset = raos.getOffset();\n\n                    // Preserve existing encoding when rewriting CFH so that it matches LFH\n                    cdLength += ZipOutputStream.writeCentralFileHeader(ze, raos, entryInfo.encoding, entryInfo.headerOffset, entryInfo.hasDataDescriptor, zipBuffer);\n\n                    // Update length of central directory file header\n                    entryInfo.centralHeaderLen = raos.getOffset() - entryInfo.centralHeaderOffset;\n                }\n\n                ZipOutputStream.writeCentralDirectoryEnd(raos, nbEntries, cdLength, cdOffset, comment, UTF_8, zipBuffer);\n\n                // Truncate the zip file to reclaim the trailing unused space\n                raos.setLength(raos.getOffset());\n            }\n        }\n        finally {\n            try {\n                closeRead();\n            } catch(IOException ignore) {}\n\n            try {\n                closeWrite();\n            } catch(IOException ignore) {}\n        }\n    }\n\n\n    /**\n     * Calulcates the data offset of the entry which starts at the given ZipEntryInfo.headerOffset and stores the result\n     * in ZipEntryInfo.dataOffset. After calling this method, the RandomAccessInputStream will be positionned at the\n     * beginning of the filename field.\n     *\n     * @param entryInfo the ZipEntryInfo object in which to store the data offset\n     * @throws IOException if an unexpected I/O error occurred\n     */\n    private void calculateDataOffset(ZipEntryInfo entryInfo) throws IOException {\n        // Skip the following fields:\n        //  local file header signature     4 bytes\n        //  version needed to extract       2 bytes\n        //  general purpose bit flag        2 bytes\n        //  compression method              2 bytes\n        //  last mod file time              2 bytes\n        //  last mod file date              2 bytes\n        //  crc-32                          4 bytes\n        //  compressed size                 4 bytes\n        //  uncompressed size               4 bytes\n        // Total nb of bytes to skip:      26\n\n        long dataOffset = entryInfo.headerOffset + 26;\n        rais.seek(dataOffset);\n\n        // Advance the offset of the filename field's length (plus the filename length field: 2 bytes)\n        byte[] b = new byte[2];\n        rais.readFully(b);\n        dataOffset += 2 + ZipShort.getValue(b);\n\n        // Advance the offset of the extra field's length (plus the extra field length field: 2 bytes)\n        rais.readFully(b);\n        dataOffset += 2 + ZipShort.getValue(b);\n\n        entryInfo.dataOffset = dataOffset;\n    }\n\n\n    /** Combined length of all constant-size fields of the Central File Header */\n    private static final int CFH_LEN =\n        /* version made by                 */ 2\n        /* version needed to extract       */ + 2\n        /* general purpose bit flag        */ + 2\n        /* compression method              */ + 2\n        /* last mod file time              */ + 2\n        /* last mod file date              */ + 2\n        /* crc-32                          */ + 4\n        /* compressed size                 */ + 4\n        /* uncompressed size               */ + 4\n        /* filename length                 */ + 2\n        /* extra field length              */ + 2\n        /* file comment length             */ + 2\n        /* disk number start               */ + 2\n        /* internal file attributes        */ + 2\n        /* external file attributes        */ + 4\n        /* relative offset of local header */ + 4;\n\n    /**\n     * Reads the central directory of the given archive and populates\n     * the internal tables with ZipEntry instances.\n     *\n     * <p>The ZipEntrys will know all data that can be obtained from\n     * the central directory alone, but not the data that requires the\n     * local file header or additional data to be read.\n     *\n     * @throws IOException if an I/O error occurred\n     * @throws ZipException if this file is not a valid Zip file\n     */\n    private void parseCentralDirectory() throws IOException {\n\n        positionAtCentralDirectory();\n\n        byte[] cfh = new byte[CFH_LEN];\n\n        byte[] signatureBytes = new byte[4];\n        rais.readFully(signatureBytes);\n        long sig = ZipLong.getValue(signatureBytes);\n        final long cfhSig = ZipLong.getValue(CFH_SIG);\n\n        boolean defaultEncodingSet = defaultEncoding!=null;\n        ByteArrayOutputStream encodingAccumulator = defaultEncodingSet?null:new ByteArrayOutputStream();\n\n        while (sig == cfhSig) {\n            ZipEntryInfo entryInfo = new ZipEntryInfo();\n\n            // Set Central directory file header offset\n            entryInfo.centralHeaderOffset = rais.getOffset() - 4;     // 4 for the header signature\n\n            rais.readFully(cfh);\n            ZipEntry ze = new ZipEntry();\n\n            int versionMadeBy = ZipShort.getValue(cfh, 0);\n            // off += 2;\n            ze.setPlatform((versionMadeBy >> 8) & 0x0F);\n\n            // skip version info\n            // off += 2;\n\n            int gp = ZipShort.getValue(cfh, 4);   // General purpose bit flag\n            boolean isUTF8 = (gp&0x800)!=0;         // Tests if bit 11 is set, signaling UTF-8 is used for filename and comment\n\n            if(isUTF8) {\n                entryInfo.encoding = UTF_8;\n                LOGGER.debug(\"Entry declared as UTF-8\");\n            }\n            else if(defaultEncodingSet) {\n                entryInfo.encoding = defaultEncoding;\n                LOGGER.debug(\"Using default encoding: \"+defaultEncoding);\n            }\n            else {\n//                FileLogger.finest(\"Encoding will be detected later\");\n            }\n\n            entryInfo.hasDataDescriptor = (gp&8)!=0;\n            // off += 2;\n\n            int method = ZipShort.getValue(cfh, 6);\n            // Note: ZipEntry#setMethod(int) will throw a java.lang.InternalError (\"invalid compression method\") if the\n            // method is different from DEFLATED or STORED (happens with IMPLODED for example).\n            // Thus we check the method ourselves to fail gracefully.\n            if(method!=DEFLATED && method!=STORED)\n                throw new ZipException(\"Unsupported compression method\");\n\n            ze.setMethod(method);\n            // off += 2;\n\n            ze.setDosTime(ZipLong.getValue(cfh, 8));\n            // off += 4;\n\n            ze.setCrc(ZipLong.getValue(cfh, 12));\n            // off += 4;\n\n            ze.setCompressedSize(ZipLong.getValue(cfh, 16));\n            // off += 4;\n\n            ze.setSize(ZipLong.getValue(cfh, 20));\n            // off += 4;\n\n            int fileNameLen = ZipShort.getValue(cfh, 24);\n            // off += 2;\n\n            int extraLen = ZipShort.getValue(cfh, 26);\n            // off += 2;\n\n            int commentLen = ZipShort.getValue(cfh, 28);\n            // off += 2;\n\n            // skip disk number\n            // off += 2;\n\n            ze.setInternalAttributes(ZipShort.getValue(cfh, 32));\n            // off += 2;\n\n            ze.setExternalAttributes(ZipLong.getValue(cfh, 34));\n            // off += 4;\n\n            // Read filename bytes\n            byte[] filename = new byte[fileNameLen];\n            rais.readFully(filename);\n\n            // If the encoding is known already, set the String now\n            if(entryInfo.encoding!=null) {\n                setFilename(ze, getString(filename, entryInfo.encoding));\n            }\n            else {\n                // Keep the filename bytes, String will be encoded after\n                entryInfo.filename = filename;\n                // Accumulate those unidentified bytes for encoding detection\n                feedEncodingAccumulator(encodingAccumulator, filename);\n            }\n\n            // Offset to local file header\n            entryInfo.headerOffset = ZipLong.getValue(cfh, 38);\n            // data offset will be filled later\n\n            // Read and set extra bytes\n            byte extra[] = new byte[extraLen];\n            rais.readFully(extra);\n            ze.setExtra(extra);\n\n            // Read comment bytes\n            byte[] comment = new byte[commentLen];\n            rais.readFully(comment);\n\n            // If the encoding is known already, set the String now\n            if(entryInfo.encoding!=null) {\n                ze.setComment(getString(comment, entryInfo.encoding));\n            }\n            else {\n                // Keep the comment bytes, String will be encoded after\n                entryInfo.comment = comment;\n                // Accumulate those unidentified bytes for encoding detection\n                feedEncodingAccumulator(encodingAccumulator, comment);\n            }\n\n            entryInfo.centralHeaderLen = 46 + fileNameLen + extraLen + commentLen;\n\n            // Add the new entry to the internal lists\n            ze.setEntryInfo(entryInfo);\n            entries.add(ze);\n            nameMap.put(ze.getName(), ze);\n\n            // Swallow signature\n            rais.readFully(signatureBytes);\n            sig = ZipLong.getValue(signatureBytes);\n        }\n\n        if(encodingAccumulator!=null && encodingAccumulator.size()>0) {\n            int nbEntries = entries.size();\n            // Note: guessedEncoding may be null if no encoding could be detected.\n            // In that case, the default system encoding will be used to create the string\n            String guessedEncoding = EncodingDetector.detectEncoding(encodingAccumulator.toByteArray());\n\n            LOGGER.info(\"Guessed encoding: \"+guessedEncoding);\n\n            ZipEntry entry;\n            ZipEntryInfo entryInfo;\n            for(int i=0; i<nbEntries; i++) {\n                entry = entries.elementAt(i);\n                entryInfo = entry.getEntryInfo();\n\n                // Skip those entries for which we know the encoding already\n                if(entryInfo.encoding != null)\n                    continue;\n\n                entryInfo.encoding = guessedEncoding;\n\n                setFilename(entry, getString(entryInfo.filename, guessedEncoding));\n                entryInfo.filename = null;\n\n                entry.setComment(getString(entryInfo.comment, guessedEncoding));\n                entryInfo.comment = null;\n            }\n        }\n    }\n\n    /**\n     * Sets the given filename in the ZipEntry.\n     *\n     * <p>This method detects filenames that use '\\' as a path separator and replaces occurrences of '\\' to '/'.\n     * Zip specifications make it clear that '/' should always be used, even under FAT platforms, but some packers\n     * (IZArc for instance) don't comply with the specs and use '/' anyway. We handle those paths only if the archive\n     * was created under a FAT platform ; '\\' is an allowed character under UNIX platforms: replacing them\n     * would be a bad idea.\n     *\n     * @param ze the ZipEntry object in which to set the filename\n     * @param filename the filename to set \n     */\n    private static void setFilename(ZipEntry ze, String filename) {\n        if (ze.getPlatform() == ZipEntry.PLATFORM_FAT) {\n            filename = filename.replace('\\\\', '/');\n        }\n        ze.setName(filename);\n    }\n\n    /**\n     * Feeds the given bytes to the encoding accumulator (used for encoding detection). The bytes will be ignored if\n     * the accumulator has enough data already.\n     *\n     * @param encodingAccumulator the ByteArrayOutputStream that holds filename and comment bytes\n     * @param bytes the bytes to feed to the encoding accumulator\n     * @throws IOException if an I/O occurs (should never happen)\n     */\n    private static void feedEncodingAccumulator(ByteArrayOutputStream encodingAccumulator, byte bytes[]) throws IOException {\n        if(encodingAccumulator.size() < EncodingDetector.MAX_RECOMMENDED_BYTE_SIZE)\n            encodingAccumulator.write(bytes);\n        // Else accumulator has enough bytes, ignore the given bytes\n    }\n\n    /** Minimum possible size for the End Of Central Directory record (no comment) */\n    private static final int MIN_EOCD_SIZE =\n        /* end of central dir signature    */ 4\n        /* number of this disk             */ + 2\n        /* number of the disk with the     */\n        /* start of the central directory  */ + 2\n        /* total number of entries in      */\n        /* the central dir on this disk    */ + 2\n        /* total number of entries in      */\n        /* the central dir                 */ + 2\n        /* size of the central directory   */ + 4\n        /* offset of start of central      */\n        /* directory with respect to       */\n        /* the starting disk number        */ + 4\n        /* zipfile comment length          */ + 2\n        /* zipfile comment                 */ + 0;\n\n    /** Maximum possible size for the End Of Central Directory record (max comment size: 65535) */\n    private static final int MAX_EOCD_SIZE =\n        /* end of central dir signature    */ 4\n        /* number of this disk             */ + 2\n        /* number of the disk with the     */\n        /* start of the central directory  */ + 2\n        /* total number of entries in      */\n        /* the central dir on this disk    */ + 2\n        /* total number of entries in      */\n        /* the central dir                 */ + 2\n        /* size of the central directory   */ + 4\n        /* offset of start of central      */\n        /* directory with respect to       */\n        /* the starting disk number        */ + 4\n        /* zipfile comment length          */ + 2\n        /* zipfile comment                 */ + 65535;\n\n    private static final int CFD_LOCATOR_OFFSET =\n        /* end of central dir signature    */ 4\n        /* number of this disk             */ + 2\n        /* number of the disk with the     */\n        /* start of the central directory  */ + 2\n        /* total number of entries in      */\n        /* the central dir on this disk    */ + 2\n        /* total number of entries in      */\n        /* the central dir                 */ + 2\n        /* size of the central directory   */ + 4;\n\n    /**\n     * Searches for the end of central dir record, parses\n     * it and positions the stream at the first central directory\n     * record.\n     *\n     * @throws IOException if an I/O error occurs\n     * @throws ZipException if the end of central directory signature could not be found. This can be interpreted as the\n     * underlying file not being a Zip file\n     */\n    private void positionAtCentralDirectory() throws IOException {\n        long length = rais.getLength();\n        if(length<MIN_EOCD_SIZE)\n            throw new ZipException(\"Invalid Zip file (too small)\");\n\n        // Use a constant buffer size to always reuse the same instance\n        byte[] buf = BufferPool.getByteArray(MAX_EOCD_SIZE);\n        try {\n            // Actual buffer length\n            int bufLen = (int)Math.min(length, MAX_EOCD_SIZE);\n\n            // Read the maximum size the EOCD can take. Much more effective than seeking backwards like we used to do.\n            rais.seek(length-bufLen);\n            StreamUtils.readFully(rais, buf, 0, bufLen);\n\n            // Look for the EOCD signature by starting at the end and moving backwards\n            boolean signatureFound = false;\n            int off = bufLen - MIN_EOCD_SIZE;\n\n            while (off>=0) {\n                if (buf[off] == EOCD_SIG[0]) {\n                    if (buf[off+1] == EOCD_SIG[1]) {\n                        if (buf[off+2] == EOCD_SIG[2]) {\n                            if (buf[off+3] == EOCD_SIG[3]) {\n                                signatureFound = true;\n                                break;\n                            }\n                        }\n                    }\n                }\n\n                off--;\n            }\n\n            if (!signatureFound) {\n                throw new ZipException(\"Invalid Zip stream (EOCD signature not found)\");\n            }\n\n            // Parse the offset to the central directory start\n            off += CFD_LOCATOR_OFFSET;\n            byte[] cdStart = new byte[4];\n            System.arraycopy(buf, off, cdStart, 0, 4);\n            off += 4;\n\n            // Fetch the global zip file comment\n            byte[] commentLen = new byte[2];\n            System.arraycopy(buf, off, commentLen, 0, 2);\n            off += 2;\n\n            // Fetch the global zip file comment\n            byte commentBytes[] = new byte[ZipShort.getValue(commentLen)];\n            System.arraycopy(buf, off, commentBytes, 0, commentBytes.length);\n\n            // If no default encoding has been specified, try to guess the comment's encoding.\n            // Note that the Zip format doesn't provide any way of knowing the encoding, not even a bit to indicate UTF-8\n            // like bit 11 in GPBF.\n            comment = getString(commentBytes, defaultEncoding!=null?defaultEncoding:EncodingDetector.detectEncoding(commentBytes));\n\n            // Seek to the start of the central directory\n            rais.seek(ZipLong.getValue(cdStart));\n        }\n        finally {\n            BufferPool.releaseByteArray(buf);\n        }\n    }\n\n    /**\n     * Creates and returns a String created using the given bytes and encoding.\n     * If the specified encoding isn't supported, the platform's default encoding will be used.\n     *\n     * @param bytes the byte array to transform\n     * @param encoding the encoding to use to instantiate the String\n     * @return String instance that was created with the given encoding\n     */\n    private static String getString(byte[] bytes, String encoding) {\n        if(bytes.length==0)\n            return \"\";\n\n        if(encoding!=null) {\n            try {\n                return new String(bytes, encoding);\n            }\n            catch(UnsupportedEncodingException e) {\n                LOGGER.info(\"Error: unsupported encoding: {}, falling back to default encoding\", encoding);\n            }\n        }\n\n        // Fall back to platform's default encoding\n        return new String(bytes);\n    }\n\n\n    ///////////////////\n    // Inner classes //\n    ///////////////////\n    \n    /**\n     * InputStream that delegates requests to the underlying RandomAccessFile, making sure that only bytes from a\n     * certain range can be read.\n     */\n    private static class BoundedInputStream extends InputStream {\n\n        private final RandomAccessInputStream rais;\n\n        private long remaining;\n        private long loc;\n        private boolean addDummyByte = false;\n\n        BoundedInputStream(RandomAccessInputStream rais, long start, long remaining) {\n            this.rais = rais;\n            this.remaining = remaining;\n            loc = start;\n        }\n\n        @Override\n        public int read() throws IOException {\n            if (remaining-- <= 0) {\n                if (addDummyByte) {\n                    addDummyByte = false;\n                    return 0;\n                }\n                return -1;\n            }\n            synchronized (rais) {\n                rais.seek(loc++);\n                return rais.read();\n            }\n        }\n\n        @Override\n        public int read(byte[] b, int off, int len) throws IOException {\n            if (remaining <= 0) {\n                if (addDummyByte) {\n                    addDummyByte = false;\n                    b[off] = 0;\n                    return 1;\n                }\n                return -1;\n            }\n\n            if (len <= 0) {\n                return 0;\n            }\n\n            if (len > remaining) {\n                len = (int) remaining;\n            }\n            int ret;\n            synchronized (rais) {\n                rais.seek(loc);\n                ret = rais.read(b, off, len);\n            }\n            if (ret > 0) {\n                loc += ret;\n                remaining -= ret;\n            }\n            return ret;\n        }\n\n        @Override\n        public void close() throws IOException {\n            rais.close();\n        }\n\n        /**\n         * Inflater needs an extra dummy byte for nowrap - see Inflater's javadocs.\n         */\n        void addDummy() {\n            addDummyByte = true;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/zip/provider/ZipLong.java",
    "content": "/*\n *  Licensed to the Apache Software Foundation (ASF) under one or more\n *  contributor license agreements.  See the NOTICE file distributed with\n *  this work for additional information regarding copyright ownership.\n *  The ASF licenses this file to You under the Apache License, Version 2.0\n *  (the \"License\"); you may not use this file except in compliance with\n *  the License.  You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n *  Unless required by applicable law or agreed to in writing, software\n *  distributed under the License is distributed on an \"AS IS\" BASIS,\n *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *  See the License for the specific language governing permissions and\n *  limitations under the License.\n *\n */\n\npackage com.mucommander.commons.file.impl.zip.provider;\n\n/**\n * Utility class that represents a four byte integer with conversion\n * rules for the big endian byte order of ZIP files.\n *\n * <p>\n * This class is based off the <code>org.apache.tools.zip</code> package of the <i>Apache Ant</i> project. The Ant\n * code has been modified under the terms of the Apache License which you can find in the bundled muCommander license\n * file. It was forked at version 1.7.0 of Ant.\n *\n * @author Apache Ant, Maxence Bernard\n */\npublic final class ZipLong implements Cloneable {\n\n    private long value;\n\n    /**\n     * create instance from a number.\n     * @param value the long to store as a ZipLong\n     */\n    public ZipLong(long value) {\n        this.value = value;\n    }\n\n    /**\n     * create instance from bytes.\n     * @param bytes the bytes to store as a ZipLong\n     */\n    public ZipLong (byte[] bytes) {\n        this(bytes, 0);\n    }\n\n    /**\n     * create instance from the four bytes starting at offset.\n     * @param bytes the bytes to store as a ZipLong\n     * @param offset the offset to start\n     */\n    public ZipLong (byte[] bytes, int offset) {\n        value = ZipLong.getValue(bytes, offset);\n    }\n\n    /**\n     * Get value as four bytes in big endian byte order.\n     * @return value as four bytes in big endian order\n     */\n    public byte[] getBytes() {\n        return ZipLong.getBytes(value);\n    }\n\n    /**\n     * Get value as Java long.\n     * @return value as a long\n     */\n    public long getValue() {\n        return value;\n    }\n\n    /**\n     * Converts the given int value as four bytes in big endian byte order.\n     * @param value the unsigned int value (stored as a long) to convert\n     * @return the converted value as a byte array in big endian byte order\n     */\n    public static byte[] getBytes(long value) {\n        return getBytes(value, new byte[4], 0);\n    }\n\n    /**\n     * Converts the given int value as four bytes in big endian byte order. The specified byte array is used to store\n     * the result, starting at offset 0. The returned byte array is the same as the given one.\n     * @param value the unsigned int value (stored as a long) to convert\n     * @param result the byte array in which to store the value in big endian byte order\n     * @return the converted value as a byte array in big endian byte order\n     */\n    public static byte[] getBytes(long value, byte[] result) {\n        return getBytes(value, result, 0);\n    }\n\n    /**\n     * Converts the given int value as four bytes in big endian byte order. The specified byte array is used to store\n     * the result, starting at the given offset. The returned byte array is the same as the given one.\n     * @param value the unsigned int value (stored as a long) to convert\n     * @param result the byte array in which to store the value in big endian byte order\n     * @param off offset at which to start writing the result in the array\n     * @return the converted value as a byte array in big endian byte order\n     */\n    public static byte[] getBytes(long value, byte[] result, int off) {\n        result[off] = (byte) ((value & 0xFF));\n        result[++off] = (byte) ((value & 0xFF00) >> 8);\n        result[++off] = (byte) ((value & 0xFF0000) >> 16);\n        result[off+1] = (byte) ((value & 0xFF000000L) >> 24);\n        return result;\n    }\n\n    /**\n     * Helper method to get the value as a Java long from four bytes starting at given array offset\n     * @param bytes the array of bytes\n     * @param offset the offset to start\n     * @return the corresponding Java long value\n     */\n    public static long getValue(byte[] bytes, int offset) {\n        long value = (bytes[offset + 3] << 24) & 0xFF000000L;\n        value += (bytes[offset + 2] << 16) & 0xFF0000;\n        value += (bytes[offset + 1] << 8) & 0xFF00;\n        value += (bytes[offset] & 0xFF);\n        return value;\n    }\n\n    /**\n     * Helper method to get the value as a Java long from a four-byte array\n     * @param bytes the array of bytes\n     * @return the corresponding Java long value\n     */\n    public static long getValue(byte[] bytes) {\n        return getValue(bytes, 0);\n    }\n\n    /**\n     * Override to make two instances with same value equal.\n     * @param o an object to compare\n     * @return true if the objects are equal\n     */\n    public boolean equals(Object o) {\n        if (o == null || !(o instanceof ZipLong)) {\n            return false;\n        }\n        return value == ((ZipLong) o).getValue();\n    }\n\n    /**\n     * Override to make two instances with same value equal.\n     * @return the value stored in the ZipLong\n     */\n    public int hashCode() {\n        return (int) value;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/zip/provider/ZipOutputStream.java",
    "content": "/*\r\n *  Licensed to the Apache Software Foundation (ASF) under one or more\r\n *  contributor license agreements.  See the NOTICE file distributed with\r\n *  this work for additional information regarding copyright ownership.\r\n *  The ASF licenses this file to You under the Apache License, Version 2.0\r\n *  (the \"License\"); you may not use this file except in compliance with\r\n *  the License.  You may obtain a copy of the License at\r\n *\r\n *      http://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n *  Unless required by applicable law or agreed to in writing, software\r\n *  distributed under the License is distributed on an \"AS IS\" BASIS,\r\n *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n *  See the License for the specific language governing permissions and\r\n *  limitations under the License.\r\n *\r\n */\r\n\r\npackage com.mucommander.commons.file.impl.zip.provider;\r\n\r\nimport com.mucommander.commons.io.BufferPool;\r\nimport com.mucommander.commons.io.RandomAccessOutputStream;\r\n\r\nimport java.io.IOException;\r\nimport java.io.OutputStream;\r\nimport java.io.UnsupportedEncodingException;\r\nimport java.util.Vector;\r\nimport java.util.zip.Deflater;\r\nimport java.util.zip.ZipException;\r\n\r\n/**\r\n * Reimplementation of {@link java.util.zip.ZipOutputStream java.util.zip.ZipOutputStream} that handles the extended\r\n * functionality of this package, especially internal/external file attributes and extra fields with different layouts\r\n * for local file data and central directory entries.\r\n *\r\n * <p>--------------------------------------------------------------------------------------------------------------<br>\r\n * <br>\r\n * This class is based off the <code>org.apache.tools.zip</code> package of the <i>Apache Ant</i> project. The Ant\r\n * code has been modified under the terms of the Apache License which you can find in the bundled muCommander license\r\n * file. It was forked at version 1.7.0 of Ant.\r\n *\r\n * @author Apache Ant, Maxence Bernard\r\n */\r\npublic class ZipOutputStream extends OutputStream implements ZipConstants {\r\n\r\n    /** Current entry */\r\n    private ZipEntry entry;\r\n\r\n    /** Current ZipEntryOutputStream corresponding to the entry being written */\r\n    private ZipEntryOutputStream zeos;\r\n\r\n    /** Additional info about current entry */\r\n    private ZipEntryInfo entryInfo;\r\n\r\n    /** The global zip file comment */\r\n    private String comment = \"\";\r\n\r\n    /** Compression level for zip entries */\r\n    private int level = DEFAULT_DEFLATER_COMPRESSION;\r\n\r\n    /** Compression method zip entries */\r\n    private int method = DEFLATED;\r\n\r\n    /** Deflater instance that is used to compress DEFLATED entries */\r\n    protected Deflater deflater = new Deflater(level, true);\r\n\r\n    /** Buffer used by Deflater to deflate data */\r\n    protected byte[] deflaterBuf;\r\n\r\n    /** List of zip entries written so far */\r\n    private Vector<ZipEntry> entries;\r\n\r\n    /** Count the bytes written to out */\r\n    private long written = 0;\r\n\r\n    /** The encoding to use for filenames and the file comment, UTF-8 by default */\r\n    private String encoding = UTF_8;\r\n\r\n    /** Holds byte buffer instance used to convert short and longs, avoids creating lots of small arrays */\r\n    private ZipBuffer zipBuffer = new ZipBuffer();\r\n\r\n    /** 0 (zero) as ZipShort */\r\n    private static final byte[] SHORT_0 = ZipShort.getBytes(0);\r\n\r\n    /** 0 (zero) as ZipLong */\r\n    private static final byte[] LONG_0 = ZipLong.getBytes(0);\r\n\r\n    /** Three ZipLong zeros */\r\n    private static final byte[] LONG_TRIPLE_0 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};\r\n\r\n    /** 8 as ZipShort */\r\n    private static final byte[] SHORT_8 = ZipShort.getBytes(8);\r\n\r\n    /** 10 as ZipShort */\r\n    private static final byte[] SHORT_10 = ZipShort.getBytes(10);\r\n\r\n    /** 20 as ZipShort */\r\n    private static final byte[] SHORT_20 = ZipShort.getBytes(20);\r\n\r\n    /** 2048 as ZipShort */\r\n    private static final byte[] SHORT_2048 = ZipShort.getBytes(2048);\r\n\r\n    /** 2056 as ZipShort */\r\n    private static final byte[] SHORT_2056 = ZipShort.getBytes(2056);\r\n\r\n\r\n    /**\r\n     * The underlying stream this ZipOutputStream writes zip-compressed data to.\r\n     */\r\n    protected OutputStream out;\r\n\r\n    /**\r\n     * Is the underlying stream a RandomAccessOutputStream? Avoids excessive instanceof comparisons.\r\n     */\r\n    private boolean hasRandomAccess;\r\n\r\n\r\n    /**\r\n     * Creates a new <code>ZipOutputStream</code> that writes Zip-compressed data to the given <code>OutputStream</code>.\r\n     * If a {@link RandomAccessOutputStream} is supplied, the Zip entries will be written without data descriptor,\r\n     * which will yield a slightly smaller file.\r\n     *\r\n     * @param out the underlying OutputStream stream where compressed data is written to\r\n     */\r\n    public ZipOutputStream(OutputStream out) {\r\n        this.out = out;\r\n        this.hasRandomAccess = out instanceof RandomAccessOutputStream;\r\n\r\n        // Use BufferPool to avoid excessive memory allocation and garbage collection.\r\n        deflaterBuf = BufferPool.getByteArray(DEFAULT_DEFLATER_BUFFER_SIZE);\r\n        entries = new Vector<>();\r\n    }\r\n\r\n\r\n    /**\r\n     * This method indicates whether this archive is writing to a {@link RandomAccessOutputStream}.\r\n     *\r\n     * @return true if seekable\r\n     */\r\n    public boolean isSeekable() {\r\n        return hasRandomAccess;\r\n    }\r\n\r\n    /**\r\n     * The encoding to use for filenames and the file comment.\r\n     *\r\n     * <p>For a list of possible values see <a\r\n     * href=\"http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html\">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>.\r\n     * Defaults to the platform's default character encoding.\r\n     *\r\n     * @param encoding the encoding value\r\n     */\r\n    public void setEncoding(String encoding) {\r\n        this.encoding = encoding;\r\n    }\r\n\r\n    /**\r\n     * The encoding to use for filenames and the file comment.\r\n     *\r\n     * @return null if using the platform's default character encoding.\r\n     */\r\n    public String getEncoding() {\r\n        return encoding;\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the given encoding is either \"UTF-8\", \"UTF8\" (case-insensitive) or\r\n     * <code>null</code>.\r\n     *\r\n     * @param encoding the encoding String to test\r\n     * @return true if the given encoding is either \"UTF-8\", \"UTF8\" or null\r\n     */\r\n    private static boolean isUTF8(String encoding) {\r\n        return encoding==null || encoding.equalsIgnoreCase(\"UTF-8\") || encoding.equalsIgnoreCase(\"UTF8\");\r\n    }\r\n\r\n    /**\r\n     * Finishs writing the contents and closes this as well as the\r\n     * underlying stream.\r\n     *\r\n     * @throws IOException on error\r\n     */\r\n    public void finish() throws IOException {\r\n        closeEntry();\r\n        long cdOffset = written;\r\n        int nbEntries = entries.size();\r\n        ZipEntry ze;\r\n        for (int i=0; i <nbEntries; i++) {\r\n            ze =  entries.elementAt(i);\r\n            written += writeCentralFileHeader(ze, out, encoding, ze.getEntryInfo().headerOffset, !hasRandomAccess, zipBuffer);\r\n        }\r\n        long cdLength = written - cdOffset;\r\n        writeCentralDirectoryEnd(out, nbEntries, cdLength, cdOffset, comment, encoding, zipBuffer);\r\n        entries.removeAllElements();\r\n    }\r\n\r\n    /**\r\n     * Writes all necessary data for this entry. This method must be called after an entry opened by\r\n     * {@link #putNextEntry(ZipEntry)} has finished being written.\r\n     *\r\n     * @throws IOException on error\r\n     */\r\n    public void closeEntry() throws IOException {\r\n        if (entry == null)\r\n            return;\r\n\r\n        finalizeEntryData(entry, zeos, out, !hasRandomAccess, zipBuffer);\r\n        written += entry.getCompressedSize();\r\n\r\n        if(!hasRandomAccess)\r\n            written += writeDataDescriptor(entry, out, zipBuffer);\r\n\r\n        entry = null;\r\n        entryInfo = null;\r\n\r\n        zeos.close();\r\n        zeos = null;\r\n    }\r\n\r\n    /**\r\n     * Writes the size and CRC information of an entry. This method is to be called right after a file entry's data\r\n     * has been written.\r\n     *\r\n     * <p>The size and CRC information is written to the given <code>OutputStream</code>, either as a data descriptor or\r\n     * in the entry's local file header, and is set in the given {@link ZipEntry} instance.\r\n     *\r\n     * @param entry the entry\r\n     * @param zeos the Zip entry's output stream\r\n     * @param out the\r\n     * @param useDataDescriptor if true, a data descriptor will be written to out. If false, size and CRC information\r\n     * will be written in the local file header (requires out to be a RandomAccessOutputStream).\r\n     * @param zipBuffer a ZipBuffer instance used to convert integer values to Zip variants\r\n     * @throws IOException if an I/O error occurred\r\n     */\r\n    protected static void finalizeEntryData(ZipEntry entry, ZipEntryOutputStream zeos, OutputStream out, boolean useDataDescriptor, ZipBuffer zipBuffer) throws IOException {\r\n        long crc = zeos.getCrc();\r\n\r\n        if (entry.getMethod() == DEFLATED) {\r\n            ((DeflatedOutputStream)zeos).finishDeflate();\r\n\r\n            entry.setSize(adjustToLong(zeos.getTotalIn()));\r\n            long compressedSize = adjustToLong(zeos.getTotalOut());\r\n            entry.setCompressedSize(compressedSize);\r\n            entry.setCrc(crc);\r\n        }\r\n        else {      // Method is STORED\r\n            long size = zeos.getTotalOut();\r\n\r\n            entry.setSize(size);\r\n            entry.setCompressedSize(size);\r\n            entry.setCrc(crc);\r\n        }\r\n\r\n        // If random access output, write the local file header containing\r\n        // the correct CRC and compressed/uncompressed sizes\r\n        if (!useDataDescriptor) {\r\n            RandomAccessOutputStream raos = (RandomAccessOutputStream)out;\r\n\r\n            long save = raos.getOffset();\r\n\r\n            raos.seek(entry.getEntryInfo().headerOffset + 14);\r\n            raos.write(ZipLong.getBytes(entry.getCrc(), zipBuffer.longBuffer));\r\n            raos.write(ZipLong.getBytes(entry.getCompressedSize(), zipBuffer.longBuffer));\r\n            raos.write(ZipLong.getBytes(entry.getSize(), zipBuffer.longBuffer));\r\n            raos.seek(save);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Start writing the given entry. The entry is written by calling the <code>write()</code> of this class.\r\n     * When the entry has finished being written, {@link #closeEntry()} must be called.\r\n     *\r\n     * @param ze the entry to write\r\n     * @throws IOException on error\r\n     */\r\n    public void putNextEntry(ZipEntry ze) throws IOException {\r\n        closeEntry();\r\n\r\n        entry = ze;\r\n        entryInfo = new ZipEntryInfo();\r\n        entry.setEntryInfo(entryInfo);\r\n        entries.addElement(entry);\r\n\r\n        int entryMethod = entry.getMethod();\r\n        if (entryMethod == -1) {\r\n            // method not specified in the entry, use the one set in this ZipOutputStream\r\n            entryMethod = method;\r\n            entry.setMethod(method);\r\n        }\r\n\r\n        if (entry.getTime() == -1) {\r\n            // date not specified in the entry, set it to now\r\n            entry.setTime(System.currentTimeMillis());\r\n        }\r\n\r\n        if(entryMethod == DEFLATED) {\r\n            deflater.reset();\r\n            deflater.setLevel(level);\r\n\r\n            zeos = new DeflatedOutputStream(out, deflater, deflaterBuf);\r\n        }\r\n        else {\r\n            zeos = new StoredOutputStream(out);\r\n        }\r\n\r\n        entryInfo.headerOffset = written;\r\n        written += writeLocalFileHeader(entry, out, encoding, !hasRandomAccess, zipBuffer);\r\n        entryInfo.dataOffset = written;\r\n    }\r\n\r\n    /**\r\n     * Sets the file comment.\r\n     *\r\n     * @param comment the comment\r\n     */\r\n    public void setComment(String comment) {\r\n        this.comment = comment;\r\n    }\r\n\r\n    /**\r\n     * Sets the compression level for subsequent entries.\r\n     *\r\n     * <p>Default is Deflater.DEFAULT_COMPRESSION.\r\n     *\r\n     * @param level the compression level.\r\n     * @throws IllegalArgumentException if an invalid compression level is specified.\r\n     */\r\n    public void setLevel(int level) {\r\n        if (level < Deflater.DEFAULT_COMPRESSION\r\n            || level > Deflater.BEST_COMPRESSION) {\r\n            throw new IllegalArgumentException(\r\n                \"Invalid compression level: \" + level);\r\n        }\r\n        this.level = level;\r\n    }\r\n\r\n    /**\r\n     * Sets the default compression method for subsequent entries.\r\n     *\r\n     * <p>Default is DEFLATED.\r\n     *\r\n     * @param method an <code>int</code> from java.util.zip.ZipEntry\r\n     */\r\n    public void setMethod(int method) {\r\n        this.method = method;\r\n    }\r\n\r\n    /**\r\n     * Writes the local file header entry.\r\n     *\r\n     * @param ze the entry to write\r\n     * @param out the OutputStream to write the header to\r\n     * @param encoding the encoding to use for writing the entry's filename. If UTF-8 is used, the general purpose bit\r\n     * flag will be set accordingly.\r\n     * @param useDataDescriptor indicates whether a data descriptor will follow the file entry's data. The general\r\n     * purpose bit flag will be set accordingly.\r\n     * @param zipBuffer a ZipBuffer instance used to convert integer values to Zip variants\r\n     * @return the size (number of bytes) of the written local file header\r\n     * @throws IOException if an I/O error occurred\r\n     */\r\n    protected static long writeLocalFileHeader(ZipEntry ze, OutputStream out, String encoding, boolean useDataDescriptor, ZipBuffer zipBuffer) throws IOException {\r\n        out.write(LFH_SIG);\r\n        // written += 4;\r\n\r\n        int zipMethod = ze.getMethod();\r\n\r\n        // version needed to extract\r\n        // general purpose bit flag\r\n        writeVersionAndGPBF(out, encoding, useDataDescriptor);\r\n        // nbWritten += 4;\r\n\r\n        // compression method\r\n        out.write(ZipShort.getBytes(zipMethod, zipBuffer.shortBuffer));\r\n        // written += 2;\r\n\r\n        // last mod. time and date\r\n        out.write(ZipLong.getBytes(ze.getDosTime(), zipBuffer.longBuffer));\r\n        //  written += 4;\r\n\r\n        // CRC\r\n        // compressed length\r\n        // uncompressed length\r\n\r\n        // this information is not known at this stage so it will be set after the data has been written,\r\n        // either in the data descriptor (if used), or here by seeking (requires random access)\r\n        out.write(LONG_TRIPLE_0);   // 12 zero bytes\r\n        // written += 12;\r\n\r\n        // file name length\r\n        byte[] name = getBytes(ze.getName(), encoding);\r\n        out.write(ZipShort.getBytes(name.length, zipBuffer.shortBuffer));\r\n        // written += 2;\r\n\r\n        // extra field length\r\n        byte[] extra = ze.getLocalFileDataExtra();\r\n        out.write(ZipShort.getBytes(extra.length, zipBuffer.shortBuffer));\r\n        // written += 2;\r\n\r\n        // Number of bytes written by this method so far\r\n        long written = 30;\r\n\r\n        // file name\r\n        out.write(name);\r\n        written += name.length;\r\n\r\n        // extra field\r\n        out.write(extra);\r\n        written += extra.length;\r\n\r\n        return written;\r\n    }\r\n\r\n    /**\r\n     * Writes the data descriptor, using the CRC, compressed and uncompressed size attributes contained in the\r\n     * given ZipEntry.\r\n     * The length of the field is returned, it is always 16 bytes.\r\n     *\r\n     * @param ze the entry for which to write the data descriptor\r\n     * @param out the OutputStream where to write the data descriptor to\r\n     * @param zipBuffer a ZipBuffer instance used to convert integer values to Zip variants\r\n     * @return the number of bytes that were written, i.e. the size of the data descriptor (16 bytes)\r\n     * @throws IOException if an I/O error occurred\r\n     */\r\n    protected static long writeDataDescriptor(ZipEntry ze, OutputStream out, ZipBuffer zipBuffer) throws IOException {\r\n        out.write(DD_SIG);\r\n        out.write(ZipLong.getBytes(ze.getCrc(), zipBuffer.longBuffer));\r\n        out.write(ZipLong.getBytes(ze.getCompressedSize(), zipBuffer.longBuffer));\r\n        out.write(ZipLong.getBytes(ze.getSize(), zipBuffer.longBuffer));\r\n\r\n        return 16;\r\n    }\r\n\r\n    /**\r\n     * Writes central file header's 'Version made by' field, using the platform contained in the given ZipEntry.\r\n     * The length of the field is returned, it is always 2 bytes.\r\n     *\r\n     * @param ze the entry for which to write the 'Version made by' field\r\n     * @param out the OutputStream where to write the field\r\n     * @param zipBuffer a ZipBuffer instance used to convert integer values to Zip variants\r\n     * @return the number of bytes that were written, i.e. the size of the 'Version made by' field (2 bytes)\r\n     * @throws IOException if an I/O error occurred\r\n     */\r\n    protected static long writeVersionMadeBy(ZipEntry ze, OutputStream out, ZipBuffer zipBuffer) throws IOException {\r\n        out.write(ZipShort.getBytes((ze.getPlatform() << 8) | 20, zipBuffer.shortBuffer));\r\n\r\n        return 2;\r\n    }\r\n\r\n    /**\r\n     * Writes the central file header for the given entry.\r\n     *\r\n     * @param ze the entry for which to write the central file header\r\n     * @param out the OutputStream to write the central file header to\r\n     * @param encoding the encoding to use for writing the filename and optional comment\r\n     * @param localFileHeaderOffset the offset to the local file header start\r\n     * @param useDataDescriptor true if a data descriptor is used for the entry\r\n     * @param zipBuffer a ZipBuffer instance used to convert integer values to Zip variants\r\n     * @throws IOException if an I/O error occurred\r\n     * @return the number of bytes that were written, i.e. the size of the central file header \r\n     */\r\n    protected static long writeCentralFileHeader(ZipEntry ze, OutputStream out, String encoding, long localFileHeaderOffset, boolean useDataDescriptor, ZipBuffer zipBuffer) throws IOException {\r\n        out.write(CFH_SIG);\r\n        // nbWritten += 4;\r\n\r\n        // version made by\r\n        writeVersionMadeBy(ze, out, zipBuffer);\r\n        // nbWritten += 2;\r\n\r\n        // version needed to extract\r\n        // general purpose bit flag\r\n        writeVersionAndGPBF(out, encoding, useDataDescriptor);\r\n        // nbWritten += 4;\r\n\r\n        // compression method\r\n        out.write(ZipShort.getBytes(ze.getMethod(), zipBuffer.shortBuffer));\r\n        // nbWritten += 2;\r\n\r\n        // last mod. time and date\r\n        out.write(ZipLong.getBytes(ze.getDosTime(), zipBuffer.longBuffer));\r\n        // nbWritten += 4;\r\n\r\n        // CRC\r\n        // compressed length\r\n        // uncompressed length\r\n        out.write(ZipLong.getBytes(ze.getCrc(), zipBuffer.longBuffer));\r\n        out.write(ZipLong.getBytes(ze.getCompressedSize(), zipBuffer.longBuffer));\r\n        out.write(ZipLong.getBytes(ze.getSize(), zipBuffer.longBuffer));\r\n        // nbWritten += 12;\r\n\r\n        // file name length\r\n        byte[] name = getBytes(ze.getName(), encoding);\r\n        out.write(ZipShort.getBytes(name.length, zipBuffer.shortBuffer));\r\n        // nbWritten += 2;\r\n\r\n        // extra field length\r\n        byte[] extra = ze.getCentralDirectoryExtra();\r\n        out.write(ZipShort.getBytes(extra.length, zipBuffer.shortBuffer));\r\n        // nbWritten += 2;\r\n\r\n        // file comment length\r\n        String comm = ze.getComment();\r\n        if (comm == null) {\r\n            comm = \"\";\r\n        }\r\n        byte[] commentB = getBytes(comm, encoding);\r\n        out.write(ZipShort.getBytes(commentB.length, zipBuffer.shortBuffer));\r\n        // nbWritten += 2;\r\n\r\n        // disk number start\r\n        out.write(SHORT_0);\r\n        // nbWritten += 2;\r\n\r\n        // internal file attributes\r\n        out.write(ZipShort.getBytes(ze.getInternalAttributes(), zipBuffer.shortBuffer));\r\n        // nbWritten += 2;\r\n\r\n        // external file attributes\r\n        out.write(ZipLong.getBytes(ze.getExternalAttributes(), zipBuffer.longBuffer));\r\n        // nbWritten += 4;\r\n\r\n        // relative offset of LFH\r\n        out.write(ZipLong.getBytes(localFileHeaderOffset, zipBuffer.longBuffer));\r\n        // nbWritten += 4;\r\n\r\n        long nbWritten = 46;\r\n\r\n        // file name\r\n        out.write(name);\r\n        nbWritten += name.length;\r\n\r\n        // extra field\r\n        out.write(extra);\r\n        nbWritten += extra.length;\r\n\r\n        // file comment\r\n        out.write(commentB);\r\n        nbWritten += commentB.length;\r\n\r\n        return nbWritten;\r\n    }\r\n\r\n\r\n    /**\r\n     * Writes the 'version needed to extract' (2 bytes) and 'general purpose bit flag' (2 bytes) fields.\r\n     *\r\n     * @param out the OutputStream to write the fields to\r\n     * @param encoding the encoding used for writing the filename and optional comment\r\n     * @param useDataDescriptor true if a data descriptor is used for the entry\r\n     * @return the number of bytes that were written, i.e. 4\r\n     * @throws IOException if an I/O error occurred\r\n     */\r\n    protected static long writeVersionAndGPBF(OutputStream out, String encoding, boolean useDataDescriptor) throws IOException {\r\n        boolean isUTF8 = isUTF8(encoding);\r\n\r\n        // General purpose bit flag :\r\n        // Bit 11 signals UTF-8 is used\r\n        // Bit 3 signals a data descriptor is used\r\n\r\n        if (useDataDescriptor) {\r\n            // requires version 2 as we are going to store length info in the data descriptor\r\n            out.write(SHORT_20);\r\n\r\n            // General purpose bit flag\r\n            out.write(isUTF8?\r\n                SHORT_2056                  // Bit 3 | Bit 11 = 2056\r\n                :SHORT_8                    // Bit 3          = 8\r\n            );\r\n        }\r\n        else {\r\n            // Version\r\n            out.write(SHORT_10);\r\n\r\n            // General purpose bit flag\r\n            out.write(isUTF8?\r\n                SHORT_2048                  // Bit 11 = 2048\r\n                :SHORT_0                    // No bit set\r\n            );\r\n        }\r\n\r\n        return 4;\r\n    }\r\n\r\n\r\n    /**\r\n     * Writes the end of the central directory record.\r\n     *\r\n     * @param out the OutputStream to write the end of the central directory record to\r\n     * @param nbEntries number of entries the Zip file contains\r\n     * @param cdLength length (in bytes) of the central directory record\r\n     * @param cdOffset offset from the beginning of the Zip file to the start of the central directory record\r\n     * @param comment the optional Zip file comment\r\n     * @param encoding the encoding to use for writing the optional Zip comment\r\n     * @param zipBuffer a ZipBuffer instance used to convert integer values to Zip variants\r\n     * @throws IOException if an I/O error occurred\r\n     */\r\n    protected static void writeCentralDirectoryEnd(OutputStream out, int nbEntries, long cdLength, long cdOffset, String comment, String encoding, ZipBuffer zipBuffer)\r\n            throws IOException {\r\n\r\n        out.write(EOCD_SIG);\r\n\r\n        // disk numbers\r\n        out.write(LONG_0);      // 2x SHORT_0\r\n\r\n        // number of entries\r\n        ZipShort.getBytes(nbEntries, zipBuffer.shortBuffer);\r\n        out.write(zipBuffer.shortBuffer);\r\n        out.write(zipBuffer.shortBuffer);\r\n\r\n        // length and location of CD\r\n        out.write(ZipLong.getBytes(cdLength, zipBuffer.longBuffer));\r\n        out.write(ZipLong.getBytes(cdOffset, zipBuffer.longBuffer));\r\n\r\n        // ZIP file comment\r\n        byte[] data = getBytes(comment, encoding);\r\n        out.write(ZipShort.getBytes(data.length, zipBuffer.shortBuffer));\r\n        out.write(data);\r\n    }\r\n\r\n    /**\r\n     * Retrieve the bytes for the given String in the encoding set for\r\n     * this Stream.\r\n     * @param name the string to get bytes from\r\n     * @param encoding the encoding the string is encoded with\r\n     * @return the bytes as a byte array\r\n     * @throws ZipException on error\r\n     */\r\n    protected static byte[] getBytes(String name, String encoding) throws ZipException {\r\n        if (encoding == null) {\r\n            return name.getBytes();\r\n        } else {\r\n            try {\r\n                return name.getBytes(encoding);\r\n            } catch (UnsupportedEncodingException uee) {\r\n                throw new ZipException(uee.getMessage());\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Returns a long that is the unsigned intepretation of the given (signed) int.\r\n     *\r\n     * @param i the value to treat as unsigned int\r\n     * @return the unsigned int as a long\r\n     */\r\n    protected static long adjustToLong(int i) {\r\n        return i & 0xFFFFFFFFl;\r\n    }\r\n\r\n\r\n    /////////////////////////////////\r\n    // OutputStream implementation //\r\n    /////////////////////////////////\r\n\r\n    /**\r\n     * Writes the given bytes to the current Zip entry opened with {@link #putNextEntry(ZipEntry)}, using the entry's\r\n     * compression method. If no entry is currently open, the bytes will be written as-is to the underlying\r\n     * <code>OutputStream</code>.\r\n     *\r\n     * @param b the byte array to write\r\n     * @param offset the start position to write from\r\n     * @param length the number of bytes to write\r\n     * @throws IOException on error\r\n     */\r\n    @Override\r\n    public void write(byte[] b, int offset, int length) throws IOException {\r\n        (zeos==null?out:zeos).write(b, offset, length);\r\n    }\r\n\r\n    /**\r\n     * Writes the given bytes to the current Zip entry opened with {@link #putNextEntry(ZipEntry)}, using the entry's\r\n     * compression method. If no entry is currently open, the bytes will be written as-is to the underlying\r\n     * <code>OutputStream</code>.\r\n     *\r\n     * @param b the byte array to write\r\n     * @throws IOException on error\r\n     */\r\n    @Override\r\n    public void write(byte[] b) throws IOException {\r\n        (zeos==null?out:zeos).write(b, 0, b.length);\r\n    }\r\n\r\n    /**\r\n     * Writes a single byte to the current Zip entry opened with {@link #putNextEntry(ZipEntry)}, using the entry's\r\n     * compression method. If no entry is currently open, the bytes will be written as-is to the underlying\r\n     * <code>OutputStream</code>.\r\n     *\r\n     * <p>Delegates to the three arg method.\r\n     * @param b the byte to write\r\n     * @throws IOException on error\r\n     */\r\n    @Override\r\n    public void write(int b) throws IOException {\r\n        (zeos==null?out:zeos).write(b);\r\n    }\r\n\r\n    /**\r\n     * Closes this output stream and releases any system resources associated with the stream.\r\n     *\r\n     * @exception IOException  if an I/O error occurs.\r\n     */\r\n    @Override\r\n    public void close() throws IOException {\r\n        finish();\r\n\r\n        if(deflaterBuf !=null) {         // Only if close() has not already been called already\r\n            BufferPool.releaseByteArray(deflaterBuf);\r\n            deflaterBuf = null;\r\n        }\r\n\r\n        out.close();\r\n    }\r\n\r\n    /**\r\n     * Flushes this output stream and forces any buffered output bytes to be written out to the stream.\r\n     *\r\n     * @exception  IOException  if an I/O error occurs.\r\n     */\r\n    @Override\r\n    public void flush() throws IOException {\r\n        out.flush();\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/impl/zip/provider/ZipShort.java",
    "content": "/*\n *  Licensed to the Apache Software Foundation (ASF) under one or more\n *  contributor license agreements.  See the NOTICE file distributed with\n *  this work for additional information regarding copyright ownership.\n *  The ASF licenses this file to You under the Apache License, Version 2.0\n *  (the \"License\"); you may not use this file except in compliance with\n *  the License.  You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n *  Unless required by applicable law or agreed to in writing, software\n *  distributed under the License is distributed on an \"AS IS\" BASIS,\n *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *  See the License for the specific language governing permissions and\n *  limitations under the License.\n *\n */\n\npackage com.mucommander.commons.file.impl.zip.provider;\n\n/**\n * Utility class that represents a two byte integer with conversion\n * rules for the big endian byte order of ZIP files.\n *\n * <p>\n * This class is based off the <code>org.apache.tools.zip</code> package of the <i>Apache Ant</i> project. The Ant\n * code has been modified under the terms of the Apache License which you can find in the bundled muCommander license\n * file. It was forked at version 1.7.0 of Ant.\n *\n * @author Apache Ant, Maxence Bernard\n */\npublic final class ZipShort implements Cloneable {\n\n    private int value;\n\n    /**\n     * create instance from a number.\n     * @param value the int to store as a ZipShort\n     */\n    public ZipShort (int value) {\n        this.value = value;\n    }\n\n    /**\n     * create instance from bytes.\n     * @param bytes the bytes to store as a ZipShort\n     */\n    public ZipShort (byte[] bytes) {\n        this(bytes, 0);\n    }\n\n    /**\n     * create instance from the two bytes starting at offset.\n     * @param bytes the bytes to store as a ZipShort\n     * @param offset the offset to start\n     */\n    public ZipShort (byte[] bytes, int offset) {\n        value = ZipShort.getValue(bytes, offset);\n    }\n\n    /**\n     * Get value as two bytes in big endian byte order.\n     * @return the value as a a two byte array in big endian byte order\n     */\n    public byte[] getBytes() {\n        byte[] result = new byte[2];\n        result[0] = (byte) (value & 0xFF);\n        result[1] = (byte) ((value & 0xFF00) >> 8);\n        return result;\n    }\n\n    /**\n     * Get value as Java int.\n     * @return value as a Java int\n     */\n    public int getValue() {\n        return value;\n    }\n\n    /**\n     * Converts the given short value as two bytes in big endian byte order.\n     * @param value the short value (stored as an int) to convert\n     * @return the converted value as a byte array in big endian byte order\n     */\n    public static byte[] getBytes(int value) {\n        return getBytes(value, new byte[2], 0);\n    }\n\n    /**\n     * Converts the given short value as two bytes in big endian byte order. The specified byte array is used to store\n     * the result, starting at offset 0. The returned byte array is the same as the given one.\n     * @param value the short value (stored as an int) to convert\n     * @param result the byte array in which to store the value in big endian byte order\n     * @return the converted value as a byte array in big endian byte order\n     */\n    public static byte[] getBytes(int value, byte[] result) {\n        return getBytes(value, result, 0);\n    }\n\n    /**\n     * Converts the given short value as two bytes in big endian byte order. The specified byte array is used to store\n     * the result, starting at the given offset. The returned byte array is the same as the given one.\n     * @param value the short value (stored as an int) to convert\n     * @param result the byte array in which to store the value in big endian byte order\n     * @param off offset at which to start writing the result in the array\n     * @return the converted value as a byte array in big endian byte order\n     */\n    public static byte[] getBytes(int value, byte[] result, int off) {\n        result[off] = (byte) (value & 0xFF);\n        result[off+1] = (byte) ((value & 0xFF00) >> 8);\n        return result;\n    }\n\n    /**\n     * Helper method to get the value as a java int from two bytes starting at given array offset\n     * @param bytes the array of bytes\n     * @param offset the offset to start\n     * @return the corresponding java int value\n     */\n    public static int getValue(byte[] bytes, int offset) {\n        int value = (bytes[offset + 1] << 8) & 0xFF00;\n        value += (bytes[offset] & 0xFF);\n        return value;\n    }\n\n    /**\n     * Helper method to get the value as a java int from a two-byte array\n     * @param bytes the array of bytes\n     * @return the corresponding java int value\n     */\n    public static int getValue(byte[] bytes) {\n        return getValue(bytes, 0);\n    }\n\n    /**\n     * Override to make two instances with same value equal.\n     * @param o an object to compare\n     * @return true if the objects are equal\n     */\n    public boolean equals(Object o) {\n        if (o == null || !(o instanceof ZipShort)) {\n            return false;\n        }\n        return value == ((ZipShort) o).getValue();\n    }\n\n    /**\n     * Override to make two instances with same value equal.\n     * @return the value stored in the ZipShort\n     */\n    public int hashCode() {\n        return value;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/package.html",
    "content": "<body>\n  API used to access, read and write files over any file system.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/util/C.java",
    "content": "package com.mucommander.commons.file.util;\n\nimport com.sun.jna.Native;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * This class provides access to a static instance of the {@link CLibrary} interface, allowing to invoke selected\n * functions of the C standard library.\n *\n * <p>The C standard library and the JNA library (which is used to access native libraries) may not be available on\n * all OS/CPU architectures: {@link #isAvailable()} can be used to determine that at runtime.\n *\n * @see CLibrary\n * @author Maxence Bernard\n */\npublic class C {\n    private static final Logger LOGGER = LoggerFactory.getLogger(C.class);\n\n    /** Singleton instance */\n    private static CLibrary INSTANCE;\n\n    static {\n        try {\n            INSTANCE = Native.load(\"c\", CLibrary.class);\n        } catch(Throwable e) {\n            LOGGER.info(\"Unable to load C library\", e);\n            // java.lang.UnsatisfiedLinkError is thrown if the CPU architecture is not supported by JNA.\n            INSTANCE = null;\n        }\n    }\n\n    /**\n     * Returns <code>true</code> if the C standard library can be accessed on the current OS/CPU architecture.\n     *\n     * @return <code>true</code> if the C standard library can be accessed on the current OS/CPU architecture\n     */\n    public static boolean isAvailable() {\n        return INSTANCE!=null;\n    }\n\n    /**\n     * Returns a static instance of the {@link CLibrary} interface, allowing to invoke selected functions of the C\n     * standard library. <code>null</code> will be returned if {@link #isAvailable()} returned <code>false</code>.\n     *\n     * @return a static instance of the {@link CLibrary} interface, <code>null</code> if it is not available on the\n     * current OS/CPU architecture\n     */\n    public static CLibrary getInstance() {\n        return INSTANCE;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/util/CLibrary.java",
    "content": "package com.mucommander.commons.file.util;\n\nimport com.sun.jna.Library;\nimport com.sun.jna.Structure;\n\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * Exposes parts of the C standard library using JNA (Java Native Access).\n *\n * @author Maxence Bernard\n */\npublic interface CLibrary extends Library {\n\n    //////////////////////\n    // statvfs function //\n    //////////////////////\n    \n    /**\n     * Structure that holds the information returned by {@link CLibrary#statvfs(String, STATVFSSTRUCT)}.\n     */\n    class STATVFSSTRUCT extends Structure {\n        /* file system block size */\n        public int f_bsize;\n        /* fragment size */\n        public int f_frsize;\n        /* size of fs in f_frsize units */\n        public int f_blocks;\n        /* # free blocks */\n        public int f_bfree;\n        /* # free blocks for non-root */\n        public int f_bavail;\n        /* # inodes */\n        public int f_files;\n        /* # free inodes */\n        public int f_ffree;\n        /* # free inodes for non-root */\n        public int f_favail;\n        /* file system ID */\n        public long f_fsid;\n        /* mount flags */\n        public int f_flag;\n        /* maximum filename length */\n        public int f_namemax;\n\n        @Override\n        protected List<String> getFieldOrder() {\n            return Arrays.asList(\"f_bsize\", \"f_frsize\", \"f_blocks\", \"f_bfree\", \"f_bavail\", \"f_files\", \"f_ffree\", \"f_favail\", \"f_fsid\", \"f_flag\", \"f_namemax\");\n        }\n    }\n\n    /**\n     * Returns information about the filesystem on which the specified file resides.\n     *\n     * @param path pathname of any file within the mounted filesystem\n     * @param struct a {@link STATVFSSTRUCT} object\n     * @return 0 on success, -1 on error\n     */\n    int statvfs(String path, STATVFSSTRUCT struct);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/util/Chmod.java",
    "content": "package com.mucommander.commons.file.util;\n\nimport com.mucommander.commons.file.AbstractFile;\n\n/**\n * This class is a gateway to the <code>chmod</code> UNIX command. It provides static methods that allow to change a\n * file's permissions, overcoming the limitations of <code>java.io.File</code>.\n *\n * <p>The <code>chmod</code> command is available only under {@link com.mucommander.commons.runtime.OsFamily#isUnixBased() UNIX-based}\n * systems -- a call to any of this class' methods under other OS will likely fail.\n *\n * @author Maxence Bernard\n * @see com.mucommander.commons.file.FilePermissions\n */\npublic class Chmod {\n\n    /**\n     * Attemps to change the permissions of the given file and returns <code>true</code> if the <code>chmod</code>\n     * command reported a success.\n     *\n     * @param file the file whose permissions are to be changed\n     * @param permissions the new permissions\n     * @see com.mucommander.commons.file.FilePermissions\n     * @return true if the <code>chmod</code> command reported a success\n     */\n    public static boolean chmod(AbstractFile file, int permissions) {\n        return chmod(new AbstractFile[]{file}, Integer.toOctalString(permissions));\n    }\n\n    /**\n     * Attemps to change the permissions of the given file and returns <code>true</code> if the <code>chmod</code>\n     * command reported a success.\n     *\n     * @param file the file whose permissions are to be changed\n     * @param permissions the new permissions, in any form accepted by the chmod command\n     * @return true if the <code>chmod</code> command reported a success\n     */\n    public static boolean chmod(AbstractFile file, String permissions) {\n        return chmod(new AbstractFile[]{file}, permissions);\n    }\n\n    /**\n     * Attemps to change the permissions of the given files and returns <code>true</code> if the <code>chmod</code>\n     * command reported a success.\n     *\n     * @param files the files whose permissions are to be changed\n     * @param permissions the new permissions\n     * @see com.mucommander.commons.file.FilePermissions\n     * @return true if the <code>chmod</code> command reported a success\n     */\n    public static boolean chmod(AbstractFile files[], int permissions) {\n        return chmod(files, Integer.toOctalString(permissions));\n    }\n\n    /**\n     * Attemps to change the permissions of the given files and returns <code>true</code> if the <code>chmod</code>\n     * command reported a success.\n     *\n     * @param files the files whose permissions are to be changed\n     * @param permissions the new permissions, in any form accepted by the chmod command\n     * @return true if the <code>chmod</code> command reported a success\n     */\n    public static boolean chmod(AbstractFile files[], String permissions) {\n        // create the command token array\n        String[] tokens = new String[files.length+2];\n        tokens[0] = \"chmod\";\n        tokens[1] = permissions;\n        int fileIndex = 0;\n        for(int i=2; i<tokens.length; i++)\n            tokens[i] = files[fileIndex++].getAbsolutePath();\n\n        try {\n            Process process = Runtime.getRuntime().exec(tokens);\n            process.waitFor();\n            return process.exitValue()==0;\n        }\n        catch(Exception e) {        // IOException, InterruptedException\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/util/FileChangeListener.java",
    "content": "package com.mucommander.commons.file.util;\n\nimport com.mucommander.commons.file.AbstractFile;\n\n/**\n * Interface to be implemented by classes that wish to be notified when changes are made to files monitored by\n * {@link FileMonitor}.\n *\n * <p>FileChangeListener instances must register themselves with FileMonitor using\n * {@link FileMonitor#addFileChangeListener(FileChangeListener)}, in order for {@link #fileChanged(AbstractFile, int)}\n * to be called whenever a file monitored by a FileMonitor has changed.\n *\n * @see FileMonitor\n * @author Maxence Bernard\n */\npublic interface FileChangeListener extends FileMonitorConstants {\n\n    /**\n     * This method is called whenever a change in one or several attributes of the given file has changed. The\n     * <code>changedAttributes</code> parameter may contain several attributes, use the binary AND operator with\n     * {@link FileMonitor} constant attribute fields to read them.\n     *\n     * @param file the AbstractFile for which an attribute change has been detected\n     * @param changedAttributes a set of attributes that have changed, see FileMonitor constant fields for possible values \n     */\n    void fileChanged(AbstractFile file, int changedAttributes);\n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/util/FileComparator.java",
    "content": "package com.mucommander.commons.file.util;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.quicksearch.QuickSearch;\n\nimport java.util.Comparator;\nimport java.util.regex.Pattern;\n\n\n/**\n * FileComparator compares {@link AbstractFile} instances using a comparison criterion order (ascending or descending).\n * Directories can either be mixed with regular files (compared just as regular files), or always precede regular files.\n\n * <p>FileComparator extends Comparator and thus can be used wherever a Comparator is accepted. In particular, it\n * can be used with <code>java.util.Arrays</code> sort methods to easily sort an array of files.\n *\n * <p>The following criteria are available:\n * <ul>\n * <li>{@link #NAME_CRITERION}: compares filenames returned by {@link AbstractFile#getName()}\n * <li>{@link #SIZE_CRITERION}: compares file sizes returned by {@link AbstractFile#getSize()}. Note: size for\n * directories is always considered as 0, even if {@link AbstractFile#getSize()} returns something else. \n * <li>{@link #DATE_CRITERION}: compares file dates returned by {@link AbstractFile#getLastModifiedDate()}\n * <li>{@link #EXTENSION_CRITERION}: compares file extensions returned by {@link AbstractFile#getExtension()}\n * <li>{@link #PERMISSIONS_CRITERION}: compares file permissions returned by {@link AbstractFile#getPermissions()}\n * </ul>\n *\n * @author Maxence Bernard\n */\npublic class FileComparator implements Comparator<AbstractFile> {\n\n    /** Comparison criterion */\n    private final int criterion;\n    /** Ascending or descending order ? */\n    private final boolean ascending;\n    /** Specifies whether directories should precede files or be handled as regular files */\n    private final boolean directoriesFirst;\n    /** Specifies whether directories always alphabetical sorted */\n    private final boolean foldersAlwaysAlphabetical;\n\n    /** Criterion for filename comparison. */\n    public final static int NAME_CRITERION = 0;\n    /** Criterion for file size comparison. */\n    public final static int SIZE_CRITERION = 1;\n    /** Criterion for file date comparison. */\n    public final static int DATE_CRITERION = 2;\n    /** Criterion for file extension comparison. */\n    public final static int EXTENSION_CRITERION = 3;\n    /** Criterion for file permissions comparison. */\n    public final static int PERMISSIONS_CRITERION = 4;\n    /** Criterion for owner comparison. */\n    public final static int OWNER_CRITERION = 5;\n    /** Criterion for group comparison. */\n    public final static int GROUP_CRITERION = 6;\n\n    /** Matches filenames that contain a number, like \"01 - Do the Joy.mp3\" */\n    private final static Pattern FILENAME_WITH_NUMBER_PATTERN = Pattern.compile(\"\\\\d+\");\n\n    private final QuickSearch quickSearch;\n\n\n    /**\n     * Creates a new FileComparator using the specified comparison criterion, order (ascending or descending) and\n     * directory handling rule.\n     *\n     * @param criterion comparison criterion, see constant fields\n     * @param ascending if true, ascending order will be used, descending order otherwise\n     * @param directoriesFirst specifies whether directories should precede files or be handled as regular files\n     * @param foldersAlwaysAlphabetical specifies whether directories are sorted always alphabetical if they stay first\n     */\n    public FileComparator(int criterion, boolean ascending, boolean directoriesFirst, boolean foldersAlwaysAlphabetical, QuickSearch quickSearch) {\n        this.criterion = criterion;\n        this.ascending = ascending;\n        this.directoriesFirst = directoriesFirst;\n        this.foldersAlwaysAlphabetical = foldersAlwaysAlphabetical && directoriesFirst;\n        this.quickSearch = quickSearch;\n    }\n\n    public FileComparator(int criterion, boolean ascending, boolean directoriesFirst, boolean foldersAlwaysAlphabetical) {\n        this(criterion, ascending, directoriesFirst, foldersAlwaysAlphabetical, null);\n    }\n\n\n    /**\n     * Returns a <code>value</code> for the given character. Using this function in a comparator will separator\n     * symbols for digits and letters and put in the following order:\n     * <ul>\n     *   <li>symbols first</li>\n     *   <li>digits second</li>\n     *   <li>letters third</li>\n     * </ul>\n     *\n     * <p>This character order was suggested in ticket #282.\n     *\n     * @param c character for which to return a value\n     * @return a <code>value</code> for the given character\n     */\n    private static int getCharacterValue(int c) {\n        // Note: max char value is 65535\n        if (Character.isLetter(c))\n            c += 131070;    // yields a value higher than any other symbol or digit\n        else if (Character.isDigit(c))\n            c += 65535;     // yields a value higher than any other symbol\n\n        // else we have a symbol\n        return c;\n    }\n\n    /**\n     * Removes leading zeros ('0') from the given string (if it contains any), and returns the trimmed string.\n     *\n     * @param s the string from which to remove leading zeros\n     * @return a string without leading zeros\n     */\n    private static String removeLeadingZeros(String s) {\n        int len = s.length();\n        int i = 0;\n        while (i < len && s.charAt(i) == '0') {\n            i++;\n        }\n\n        if (i > 0) {\n            return s.substring(i, len);\n        }\n        return s;\n    }\n\n    /**\n     * Compare the specified strings, following the contract of {@link Comparator#compare(Object, Object)}.\n     *\n     * @param s1 first string to compare\n     * @param s2 second string to compare.\n     * @param ignoreCase <code>true</code> to perform a case-insensitive string comparison, <code>false</code> to take\n     * the case into account.\n     * @param nullProtection <code>true</code> if any of s1 or s2 can be <code>null</code>, <code>false</code>\n     * if strings cannot be <code>null</code>.\n     * @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater\n     * than the second.\n     */\n    private static int compareStrings(String s1, String s2, boolean ignoreCase, boolean nullProtection) {\n        // Protect against null values, only if requested\n        if (nullProtection) {\n            if (s1 == null && s2 != null) {\t    // s1 is null, s2 isn't\n                return -1;\n            } else if (s1 != null && s2 == null) {    // s2 is null, s1 isn't\n                return 1;\n                // At this point, either both strings are null, or none of them are\n            } else {\n                if (s1 == null)\t\t        // Both strings are null\n                    return 0;\n                // else: Both strings are not null, go on with the comparison\n            }\n        }\n        return compareStrings(s1, s2, ignoreCase);\n    }\n\n    /**\n     * Compare the specified strings, following the contract of {@link Comparator#compare(Object, Object)}.\n     *\n     * @param s1 first string to compare\n     * @param s2 second string to compare.\n     * @param ignoreCase <code>true</code> to perform a case-insensitive string comparison, <code>false</code> to take\n     * the case into account.\n     * @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater\n     * than the second.\n     */\n    private static int compareStrings(String s1, String s2, boolean ignoreCase) {\n        // Special treatment for strings that contain a number, so they are ordered by the number's value, e.g.:\n        // 1 < 1a < 2 < 10, like Mac OS X Finder and Windows Explorer do.\n        //\n        // This special order applies only if both strings contain a number and have the same prefix. Otherwise, the general order applies.\n        int digitIndex1 = firstDigitPos(s1);\n        if (digitIndex1 >= 0) {\n            int digitIndex2 = firstDigitPos(s2);\n            if (digitIndex2 >= 0) {\n                // So we got two filenames that both contain a number, check if they have the same prefix\n\n                // Note: compare prefixes only if start indexes match, faster that way\n                if (digitIndex1 == digitIndex2 && (digitIndex1==0 || s1.substring(0, digitIndex1).equals(s2.substring(0, digitIndex2)))) {\n                    int g1Len = 0;\n                    int l1 = s1.length();\n                    for (int i = digitIndex1; i < l1; i++) {\n                        char c = s1.charAt(i);\n                        if (c >= '0' && c <= '9') {\n                            g1Len++;\n                        } else {\n                            break;\n                        }\n                    }\n                    int g2Len = 0;\n                    int l2 = s2.length();\n                    for (int i = digitIndex2; i < l2; i++) {\n                        char c = s2.charAt(i);\n                        if (c >= '0' && c <= '9') {\n                            g2Len++;\n                        } else {\n                            break;\n                        }\n                    }\n\n                    if (g1Len != g2Len) {\n                        return g1Len - g2Len;\n                    }\n\n                    for (int i = 0; i < g1Len && i < g2Len; i++) {\n                        int c1 = s1.charAt(i + digitIndex1);\n                        int c2 = s2.charAt(i + digitIndex2);\n                        if (c1 != c2) {\n                            return c1 - c2;\n                        }\n                    }\n                }\n            }\n        }\n\n        int n1 = s1.length();\n        int n2 = s2.length();\n\n        for (int i=0; i<n1 && i<n2; i++) {\n            int c1 = s1.charAt(i);\n            int c2 = s2.charAt(i);\n\n            if (ignoreCase) {\n                if (c1 != c2) {\n                    c1 = Character.toUpperCase(c1);\n                    c2 = Character.toUpperCase(c2);\n                    if (c1 != c2) {\n                        // Quote from String#regionsMatches:\n                        // \"Unfortunately, conversion to uppercase does not work properly for the Georgian alphabet, which\n                        // has strange rules about case conversion. So we need to make one last check before exiting.\"\n                        c1 = Character.toLowerCase(c1);\n                        c2 = Character.toLowerCase(c2);\n\n                        if (c1 != c2)\n                            return getCharacterValue(c1) -  getCharacterValue(c2);\n                    }\n                }\n            } else if (c1 != c2) {\n                return getCharacterValue(c1) -  getCharacterValue(c2);\n            }\n        }\n\n        return n1 - n2;\n    }\n\n\n    private static int firstDigitPos(String s) {\n        final int len = s.length();\n        for (int i = 0; i < len; i++) {\n            char ch = s.charAt(i);\n            if (ch >= '0' && ch <= '9') {\n                return i;\n            }\n        }\n        return -1;\n    }\n\n\n    ///////////////////////////////\n    // Comparator implementation //\n    ///////////////////////////////\n    \n    public int compare(AbstractFile f1, AbstractFile f2) {\n        if (quickSearch != null) {\n            boolean m1 = quickSearch.matches(f1);\n            boolean m2 = quickSearch.matches(f2);\n            if (m1 & !m2) {\n                return -1;\n            } else if (m2 && !m1) {\n                return 1;\n            }\n        }\n\n        boolean is1Directory = f1.isDirectory();\n        boolean is2Directory = f2.isDirectory();\n        long diff;\n\n        if (directoriesFirst) {\n            if (is1Directory && !is2Directory) {\n                return -1;    // ascending has no effect on the result (a directory is always first) so let's return\n            } else if (is2Directory && !is1Directory) {\n                return 1;    // ascending has no effect on the result (a directory is always first) so let's return\n            }\n            if (foldersAlwaysAlphabetical && is1Directory) {\n                diff = compareStrings(f1.getName(), f2.getName(), true);\n                if (diff == 0) {\n                    // Case-sensitive name comparison\n                    diff = compareStrings(f1.getName(), f2.getName(), false);\n                }\n                return (int) diff;\n            }\n            // At this point, either both files are directories or none of them are\n        }\n\n        if (criterion == SIZE_CRITERION)  {\n            // Consider that directories have a size of 0\n            long fileSize1 = is1Directory ? 0 : f1.getSize();\n            long fileSize2 = is2Directory ? 0 : f2.getSize();\n\n            // Returns file1 size - file2 size, file size of -1 (unavailable) is considered as enormous (max long value)\n            diff = (fileSize1 == -1 ? Long.MAX_VALUE : fileSize1) - (fileSize2 == -1 ? Long.MAX_VALUE : fileSize2);\n        } else if (criterion == DATE_CRITERION) {\n            diff = f1.getLastModifiedDate() - f2.getLastModifiedDate();\n        } else if (criterion == PERMISSIONS_CRITERION) {\n            diff = f1.getPermissions().getIntValue() - f2.getPermissions().getIntValue();\n        } else if (criterion == EXTENSION_CRITERION) {\n            diff = compareStrings(f1.getExtension(), f2.getExtension(), true, true);\n        } else if (criterion == OWNER_CRITERION) {\n            diff = compareStrings(f1.getOwner(), f2.getOwner(), true, true);\n        } else if (criterion == GROUP_CRITERION) {\n            diff = compareStrings(f1.getGroup(), f2.getGroup(), true, true);\n        } else {      // criterion == NAME_CRITERION\n            diff = compareStrings(f1.getName(), f2.getName(), true);\n            if (diff == 0) {\n                // This should never happen unless the current filesystem allows a directory to have\n                // several files with different case variations of the same name.\n                // AFAIK, no OS/filesystem allows this, but just to be safe.\n\n                // Case-sensitive name comparison\n                diff = compareStrings(f1.getName(), f2.getName(), false);\n            }\n        }\n\n        if (criterion != NAME_CRITERION && diff==0)\t// If both files have the same criterion's value, compare names\n            diff = compareStrings(f1.getName(), f2.getName(), true, false);\n\n        // Cast long value to int, without overflowing the int if the long value exceeds the min or max int value\n        int intValue;\n        \n        if (diff > Integer.MAX_VALUE) {\n            intValue = Integer.MAX_VALUE;   // 2147483647\n        } else if (diff < Integer.MIN_VALUE+1) {  // Need that +1 so that the int is not overflowed if ascending order is enabled (i.e. int is negated)\n            intValue = Integer.MIN_VALUE + 1; // 2147483647\n        } else {\n            intValue = (int) diff;\n        }\n\n        return ascending ? intValue : -intValue; // Note: ascending is used more often, more efficient to negate for descending\n    }\n\n\n    /**\n     * Returns true only if the given object is a FileComparator using the same criterion and ascending/descending order.\n     */\n    public boolean equals(Object o) {\n        if (! (o instanceof FileComparator fc)) {\n            return false;\n        }\n\n        return criterion == fc.criterion && ascending == fc.ascending;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/util/FileMonitor.java",
    "content": "package com.mucommander.commons.file.util;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.WeakHashMap;\n\n/**\n * <code>FileMonitor</code> allows to monitor a file and detect changes in the file's attributes and notify registered\n * {@link FileChangeListener} listeners accordingly.\n *\n * <p>\n * FileMonitor detects attributes changes by polling the file's attributes at a given frequency and comparing their\n * values with the previous ones. If any of the monitored attributes has changed, {@link FileChangeListener#fileChanged(AbstractFile, int)}\n * is called on each of the registered listeners to notify them of the file attributes that have changed.\n * <br>Here's the list of file attributes that can be monitored:\n * <ul>\n *  <li>{@link #DATE_ATTRIBUTE}\n *  <li>{@link #SIZE_ATTRIBUTE}\n *  <li>{@link #PERMISSIONS_ATTRIBUTE}\n *  <li>{@link #IS_DIRECTORY_ATTRIBUTE}\n *  <li>{@link #EXISTS_ATTRIBUTE}\n * </ul>\n *\n * <p>The polling frequency is controlled by the poll period. This parameter determines how often the file's attributes\n * are checked. The lower this period is, the faster changes will be reported to listeners, but also the higher the\n * impact on I/O and CPU. This parameter should be carefully specified to avoid hogging resources excessively.\n *\n * <p>Note that FileMonitor uses file attributes polling because the Java API doesn't currently provide any better way\n * to do detect file changes. If Java ever does provide a callback mechanism for detecting file changes, this class\n * will be modified to take advantage of it. Another possible improvement would be to add JNI hooks for platform-specific\n * filesystem events such as 'inotify' (Linux Kernel), 'kqueue' (BSD, Mac OS X), PAM (Solaris), ...\n *\n * @see FileChangeListener\n * @author Maxence Bernard\n */\npublic class FileMonitor implements FileMonitorConstants, Runnable {\n    private static final Logger LOGGER = LoggerFactory.getLogger(FileMonitor.class);\n\n    /** Monitored file */\n    private AbstractFile file;\n    /** Monitored attributes */\n    private int attributes;\n    /** Poll period in milliseconds, i.e. the time to elapse between two file attributes polls */\n    private long pollPeriod;\n\n    /** The thread that actually does the file attributes polling and event firing */\n    private Thread monitorThread;\n\n    /**\n     * True once this monitor is ready to catch file changes, that is when the monitor thread has been started and\n     * initial file attributes have been fetched.\n     */\n    private boolean isInitialized;\n\n    /** Registered FileChangeListener instances, stored as weak references */\n    private WeakHashMap<FileChangeListener, ?> listeners = new WeakHashMap<>();\n\n\n    /**\n     * Creates a new FileMonitor that monitors the given file for changes, using the default attribute set (as defined\n     * by {@link #DEFAULT_ATTRIBUTES}) and default poll period (as defined by {@link #DEFAULT_POLL_PERIOD}).\n     *\n     * <p>See the general constructor {@link #FileMonitor(AbstractFile, int, long)} for more information.\n     *\n     * @param file the AbstractFile to monitor for changes\n     */\n    public FileMonitor(AbstractFile file) {\n        this(file, DEFAULT_ATTRIBUTES, DEFAULT_POLL_PERIOD);\n    }\n\n    /**\n     * Creates a new FileMonitor that monitors the given file for changes, using the specified attribute set and\n     * default poll period as defined by {@link #DEFAULT_POLL_PERIOD}.\n     *\n     * <p>See the general constructor {@link #FileMonitor(AbstractFile, int, long)} for more information.\n     *\n     * @param file the AbstractFile to monitor for changes\n     * @param attributes the set of attributes to monitor, see constant fields for a list of possible attributes\n     */\n    public FileMonitor(AbstractFile file, int attributes) {\n        this(file, attributes, DEFAULT_POLL_PERIOD);\n    }\n\n    /**\n     * Creates a new FileMonitor that monitors the given file for changes, using the specified poll period and\n     * default attribute set as defined by {@link #DEFAULT_ATTRIBUTES}).\n     *\n     * <p>See the general constructor {@link #FileMonitor(AbstractFile, int, long)} for more information.\n     *\n     * @param file the AbstractFile to monitor for changes\n     * @param pollPeriod number of milliseconds between two file attributes polls\n     */\n    public FileMonitor(AbstractFile file, long pollPeriod) {\n        this(file, DEFAULT_ATTRIBUTES, pollPeriod);\n    }\n\n    /**\n     * Creates a new FileMonitor that monitors the given file for changes, using the specified attribute set\n     * and poll period.\n     *\n     * <p>Note that monitoring will only start after {@link #startMonitoring()} has been called.\n     *\n     * <p>\n     * The following attributes can be monitored:\n     * <ul>\n     *  <li>{@link #DATE_ATTRIBUTE}\n     *  <li>{@link #SIZE_ATTRIBUTE}\n     *  <li>{@link #PERMISSIONS_ATTRIBUTE}\n     *  <li>{@link #IS_DIRECTORY_ATTRIBUTE}\n     *  <li>{@link #EXISTS_ATTRIBUTE}\n     * </ul>\n     * Several attributes can be specified by combining them with the binary OR operator.\n     *\n     * <p>\n     * The poll period specified in the constructor determines how often the file's attributes will be checked.\n     * The lower this period is, the faster changes will be reported to registered listeners, but also the higher the\n     * impact on I/O and CPU.\n     * <br>Note that the time spent for polling is taken into account for the poll period. For example, if the poll\n     * period is 1000ms, and polling the file's attributes took 50ms, the next poll will happen in 950ms.\n     *\n     * @param file the AbstractFile to monitor for changes\n     * @param attributes the set of attributes to monitor, see constant fields for a list of possible attributes\n     * @param pollPeriod number of milliseconds between two file attributes polls\n     */\n    FileMonitor(AbstractFile file, int attributes, long pollPeriod) {\n        this.file = file;\n        this.attributes = attributes;\n        this.pollPeriod = pollPeriod;\n    }\n\n\n    /**\n     * Adds the given {@link FileChangeListener} instance to the list of registered listeners.\n     *\n     * <p>Listeners are stored as weak references so {@link #removeFileChangeListener(FileChangeListener)}\n     * doesn't need to be called for listeners to be garbage collected when they're not used anymore.\n     *\n     * @param listener the FileChangeListener to add to the list of registered listeners.\n     * @see   #removeFileChangeListener(FileChangeListener)\n     */\n    void addFileChangeListener(FileChangeListener listener) {\n        listeners.put(listener, null);\n    }\n\n    /**\n     * Removes the given {@link FileChangeListener} instance to the list of registered listeners.\n     *\n     * @param listener the FileChangeListener to remove from the list of registered listeners.\n     * @see   #addFileChangeListener(FileChangeListener)\n     */\n    private void removeFileChangeListener(FileChangeListener listener) {\n        listeners.remove(listener);\n    }\n\n\n    /**\n     * Starts monitoring the monitored file in a dedicated thread. Does nothing if monitoring has already been started\n     * and not stopped yet. Calling this method after {@link #stopMonitoring()} has been called will resume monitoring.\n\n     * <p>Once started, the monitoring thread will check for changes in the monitored file attributes specified in\n     * the constructor, and call registered {@link FileChangeListener} instances whenever a change in one or several\n     * attributes has been detected. The poll period specified in the constructor determines how often the file's\n     * attributes will be checked.\n     *\n     * <p>This method waits until the thread is started effectively and the monitor is ready to monitor file changes.\n     * This guarantees that all changes made to the monitored file after this method returns will be caught and properly\n     * reported to listeners.\n     *\n     * <p><code>FileMonitor</code> will keep monitoring the file until {@link #stopMonitoring()} is called, even if the\n     * monitored file doesn't exist anymore. Thus, it is important not to forget to call {@link #stopMonitoring()} when\n     * monitoring is not needed anymore, in order to prevent unnecessary resource hogging.\n     */\n    synchronized void startMonitoring() {\n        if(monitorThread ==null) {\n            monitorThread = new Thread(this);\n            monitorThread.start();\n\n            isInitialized = false;\n            // Wait until the thread has been started and initial file attributes have been fetched\n            while(!isInitialized) {\n                try {\n                    wait();     // run() will notify when initialization is complete\n                }\n                catch(InterruptedException e) {}\n            }\n        }\n    }\n\n    /**\n     * Stops monitoring the monitored file. Does nothing if monitoring has not yet been started.\n     */\n    public synchronized void stopMonitoring() {\n        monitorThread = null;\n    }\n\n    /**\n     * Returns <code>true</code> if this FileMonitor is currently monitoring the file.\n     *\n     * @return true if this FileMonitor is currently monitoring the file.\n     */\n    public synchronized boolean isMonitoring() {\n        return monitorThread!=null;\n    }\n\n\n    /**\n     * Notifies all registered FileChangeListener instances that the monitored file has changed, specifying which\n     * file attributes have changed.\n     *\n     * @param changedAttributes the set of attributes that have changed\n     */\n    private void fireFileChangeEvent(int changedAttributes) {\n        LOGGER.info(\"firing an event to registered listeners, changed attributes={}\", changedAttributes);\n\n        // Iterate on all listeners\n        for(FileChangeListener listener : listeners.keySet())\n            listener.fileChanged(file, changedAttributes);\n    }\n\n    \n    /////////////////////////////\n    // Runnable implementation //\n    /////////////////////////////\n\n    public void run() {\n        Thread thisThread = monitorThread;\n\n        long lastDate = (attributes&DATE_ATTRIBUTE)!=0?file.getLastModifiedDate():0;\n        long lastSize = (attributes&SIZE_ATTRIBUTE)!=0?file.getSize():0;\n        int lastPermissions = (attributes&PERMISSIONS_ATTRIBUTE)!=0?file.getPermissions().getIntValue():0;\n        boolean lastIsDirectory = (attributes&IS_DIRECTORY_ATTRIBUTE)!=0 && file.isDirectory();\n        boolean lastExists = (attributes&EXISTS_ATTRIBUTE)!=0 && file.exists();\n\n        synchronized(this) {\n            // We are now ready to detect file changes, notify the thread that started this thread\n            isInitialized = true;\n            notify();\n        }\n\n        long now;\n        int changedAttributes;\n\n        long tempLong;\n        int tempInt;\n        boolean tempBool;\n\n        while(monitorThread ==thisThread) {\n            changedAttributes = 0;\n            now = System.currentTimeMillis();\n\n            if((attributes&DATE_ATTRIBUTE)!=0) {\n                if((tempLong=file.getLastModifiedDate())!=lastDate) {\n                    lastDate = tempLong;\n                    changedAttributes |= DATE_ATTRIBUTE;\n                }\n            }\n\n            if(monitorThread ==thisThread && (attributes&SIZE_ATTRIBUTE)!=0) {\n                if((tempLong=file.getSize())!=lastSize) {\n                    lastSize = tempLong;\n                    changedAttributes |= SIZE_ATTRIBUTE;\n                }\n            }\n\n            if(monitorThread ==thisThread && (attributes&PERMISSIONS_ATTRIBUTE)!=0) {\n                if((tempInt=file.getPermissions().getIntValue())!=lastPermissions) {\n                    lastPermissions = tempInt;\n                    changedAttributes |= PERMISSIONS_ATTRIBUTE;\n                }\n            }\n\n            if(monitorThread ==thisThread && (attributes& IS_DIRECTORY_ATTRIBUTE)!=0) {\n                if((tempBool=file.isDirectory())!=lastIsDirectory) {\n                    lastIsDirectory = tempBool;\n                    changedAttributes |= IS_DIRECTORY_ATTRIBUTE;\n                }\n            }\n\n            if(monitorThread ==thisThread && (attributes&EXISTS_ATTRIBUTE)!=0) {\n                if((tempBool=file.exists())!=lastExists) {\n                    lastExists = tempBool;\n                    changedAttributes |= EXISTS_ATTRIBUTE;\n                }\n            }\n\n            if(changedAttributes!=0)\n                fireFileChangeEvent(changedAttributes);\n\n            // Get some well-deserved rest: sleep for the specified poll period minus the time we spent\n            // for this iteration\n            try {\n                Thread.sleep(Math.max(pollPeriod-(System.currentTimeMillis()-now), 0));\n            }\n            catch(InterruptedException e) {\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/util/FileMonitorConstants.java",
    "content": "package com.mucommander.commons.file.util;\n\n/**\n * Contains constants used by {@link com.mucommander.commons.file.util.FileMonitor}.\n *\n * @author Maxence Bernard\n */\npublic interface FileMonitorConstants {\n\n    /** File date attribute, as returned by {@link com.mucommander.commons.file.AbstractFile#getLastModifiedDate()}. */\n    int DATE_ATTRIBUTE = 1;\n\n    /** File size attribute, as returned by {@link com.mucommander.commons.file.AbstractFile#getSize()}. */\n    int SIZE_ATTRIBUTE = 2;\n\n    /** File permissions attribute, as returned by {@link com.mucommander.commons.file.AbstractFile#getPermissions()}. */\n    int PERMISSIONS_ATTRIBUTE = 4;\n\n    /** File 'is directory' attribute, as returned by {@link com.mucommander.commons.file.AbstractFile#isDirectory()}. */\n    int IS_DIRECTORY_ATTRIBUTE = 8;\n\n    /** File 'exists' attribute, as returned by {@link com.mucommander.commons.file.AbstractFile#exists()}. */\n    int EXISTS_ATTRIBUTE = 16;\n\n    /** Default attribute set: {@link #DATE_ATTRIBUTE}. */\n    int DEFAULT_ATTRIBUTES = DATE_ATTRIBUTE;\n\n    /** Designates all attributes. */\n    int ALL_ATTRIBUTES = DATE_ATTRIBUTE|SIZE_ATTRIBUTE|PERMISSIONS_ATTRIBUTE|IS_DIRECTORY_ATTRIBUTE|EXISTS_ATTRIBUTE;\n\n    /** Default poll period in milliseconds. */\n    long DEFAULT_POLL_PERIOD = 10000;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/util/FilePool.java",
    "content": "package com.mucommander.commons.file.util;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport org.apache.commons.collections4.map.ReferenceMap;\n\n/**\n * This class allows {@link AbstractFile} instances to be pooled, so that existing file instances can be reused,\n * and to guarantee that only one instance of the same file may exist at any given time.\n * File keys are mapped onto {@link AbstractFile} instances. Any kind of Object may be used as the key,\n * but a sensible choice is to use the {@link AbstractFile#getURL() file's URL}.\n *\n * <p>Files are stored as {@link java.lang.ref.WeakReference weak references} so they can be garbage collected\n * when they are no longer hard-referenced.\n *\n * <p>This class uses the {@link ReferenceMap} class part of the <code>Apache Commons Collection</code> library.\n * All accesses to the underlying map is synchronized, making this class thread-safe.\n *\n * @author Maxence Bernard\n */\npublic class FilePool {\n\n    /** The actual hash map */\n    protected final ReferenceMap<Object, AbstractFile> hashMap = new ReferenceMap<>(ReferenceMap.ReferenceStrength.HARD, ReferenceMap.ReferenceStrength.WEAK);\n\n    /**\n     * Creates a new file pool.\n     */\n    public FilePool() {\n    }\n\n    /**\n     * Adds a new key/file mapping to the pool. If a mapping with the same key exists, it is replaced and the previous\n     * value returned.\n     *\n     * @param key the key that will later allow to retrieve the pooled file instance\n     * @param value the file instance to add to the pool\n     * @return returns the file instance previously mapped onto the given key, <code>null</code> if no\n     * such mapping existed\n     */\n    public synchronized AbstractFile put(Object key, AbstractFile value) {\n        return hashMap.put(key, value);\n    }\n\n    /**\n     * Returns the {@link AbstractFile} instance mapped onto the given key if there is one,\n     * <code>null</code> otherwise\n     *\n     * @param key key of the file instance to retrieve\n     * @return the {@link AbstractFile} instance mapped onto the given key if there is one,\n     * <code>null</code> otherwise\n     */\n    public synchronized AbstractFile get(Object key) {\n        return hashMap.get(key);\n    }\n\n    /**\n     * Returns <code>true</code> if this pool currently contains a key/file mapping where the given key is used as\n     * the mapping's key.\n     *\n     * @param key key to lookup\n     * @return <code>true</code> if this pool currently contains a key/file mapping where the given key is used as\n     * the mapping's key.\n     */\n    public synchronized boolean containsKey(Object key) {\n        return hashMap.containsKey(key);\n    }\n\n    /**\n     * Returns <code>true</code> if this pool currently contains a key/file mapping where the given file is used as\n     * the mapping's value.\n     *\n     * @param file file to lookup\n     * @return <code>true</code> if this pool currently contains a key/file mapping where the given file is used as\n     * the mapping's key.\n     */\n    public synchronized boolean containsValue(AbstractFile file) {\n        return hashMap.containsValue(file);\n    }\n\n    /**\n     * Removes all existing key/file mapping from this pool, leaving the pool in the same state as it was right after\n     * its creation.\n     */\n    public synchronized void clear() {\n        hashMap.clear();\n    }\n\n    /**\n     * Returns the number of key/file mapping this pool currently contains.\n     *\n     * @return the number of key/file mapping this pool currently contains.\n     */\n    public synchronized int size() {\n        return hashMap.size();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/util/FileSet.java",
    "content": "package com.mucommander.commons.file.util;\n\nimport com.mucommander.commons.file.AbstractFile;\n\nimport java.util.Vector;\n\n\n/**\n * FileSet is a <code>java.util.Vector</code> of {@link AbstractFile} instances, and an optional base folder which is\n * the folder containing the files.\n *\n * @author Maxence Bernard\n */\npublic class FileSet extends Vector<AbstractFile> {\n\n    /** The base/parent folder of the files this Vector contains */\n    private AbstractFile baseFolder;\n\n    /**\n     * Creates a new empty FileSet.\n     */\n    public FileSet() {\n    }\n\n    /**\n     * Creates a new empty FileSet with the specified base folder.\n     *\n     * @param baseFolder the folder containing the files\n     */\n    public FileSet(AbstractFile baseFolder) {\n        this.baseFolder = baseFolder;\n    }\n\n    /**\n     * Creates a new empty FileSet with the specified base folder.\n     *\n     * @param baseFolder the folder containing the files\n     * @param initialCapacity initial capacity of the vector\n     */\n    public FileSet(AbstractFile baseFolder, int initialCapacity) {\n        super(initialCapacity);\n\n        this.baseFolder = baseFolder;\n    }\n\n    /**\n     * Creates a new empty FileSet with the specified base folder, and adds the given file.\n     *\n     * @param baseFolder the folder containing the files\n     * @param file the file to add\n     */\n    public FileSet(AbstractFile baseFolder, AbstractFile file) {\n        this.baseFolder = baseFolder;\n        add(file);\n    }\n\n\t\n    /**\n     * Returns the base folder associated with this FileSet, <code>null</code> if there isn't any.\n     *\n     * @return the base folder associated with this FileSet, null if there isn't any.\n     */\n    public AbstractFile getBaseFolder() {\n        return baseFolder;\n    }\n\t\n\n    /**\n     * Adds all the files in the given array to this FileSet. Does nothing if the specified array is {@code null}.\n     *\n     * @param files the files to add to this FileSet.\n     */\n    public void addAll(AbstractFile[] files) {\n        if (files == null) {\n            return;\n        }\n\n        for (AbstractFile file : files) {\n            add(file);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/util/Kernel32.java",
    "content": "package com.mucommander.commons.file.util;\n\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.sun.jna.Native;\nimport com.sun.jna.win32.W32APIOptions;\n\n/**\n * This class provides access to a static instance of the {@link com.mucommander.commons.file.util.Kernel32API} interface,\n * allowing to invoke selected Kernel32 Windows DLL functions.\n *\n * <p>The Kernel32 DLL and the JNA library (which is used to access native libraries) may not be available on\n * all OS/CPU architectures: {@link #isAvailable()} can be used to determine that at runtime.\n\n * @see Kernel32API\n * @author Maxence Bernard\n */\npublic class Kernel32 {\n\n    /** An instance of the Kernel32 DLL */\n    private static Kernel32API INSTANCE;\n\n    static {\n        if(OsFamily.WINDOWS.isCurrent()) {        // Don't even bother if we're not running Windows\n            try {\n                INSTANCE = Native.load(\"Kernel32\", Kernel32API.class, W32APIOptions.UNICODE_OPTIONS);\n            } catch(Throwable e) {\n                // java.lang.UnsatisfiedLinkError is thrown if the CPU architecture is not supported by JNA.\n                INSTANCE = null;\n            }\n        }\n    }\n\n    /**\n     * Returns <code>true</code> if the Kernel32 API can be accessed on the current OS/CPU architecture.\n     *\n     * @return <code>true</code> if the Kernel32 API can be accessed on the current OS/CPU architecture\n     */\n    public static boolean isAvailable() {\n        return INSTANCE!=null;\n    }\n\n    /**\n     * Returns a static instance of the {@link com.mucommander.commons.file.util.Kernel32API} interface, allowing to invoke\n     * some Kernel32 Windows DLL functions. <code>null</code> will be returned if {@link #isAvailable()} returned\n     * <code>false</code>.\n     *\n     * @return a static instance of the {@link com.mucommander.commons.file.util.Kernel32API} interface, <code>null</code>\n     * if it is not available on the current OS/CPU architecture\n     */\n    public static Kernel32API getInstance() {\n        return INSTANCE;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/util/Kernel32API.java",
    "content": "package com.mucommander.commons.file.util;\r\n\r\nimport com.sun.jna.Structure;\r\nimport com.sun.jna.platform.win32.WinNT;\r\nimport com.sun.jna.ptr.LongByReference;\r\nimport com.sun.jna.win32.StdCallLibrary;\r\n\r\nimport java.nio.CharBuffer;\r\nimport java.util.Arrays;\r\nimport java.util.List;\r\n\r\n/**\r\n * Exposes parts of the Windows Kernel32 API using the JNA (Java Native Access) library.\r\n * The {@link Kernel32} class should be used to retrieve an instance of this interface.\r\n *\r\n * @see Kernel32\r\n * @author Maxence Bernard\r\n */\r\npublic interface Kernel32API extends StdCallLibrary {\r\n\r\n    /** Custom alignment of structures. */\r\n    int STRUCTURE_ALIGNMENT = Structure.ALIGN_NONE;\r\n\r\n\r\n    /**\r\n     * Retrieves the calling thread's last-error code value. \r\n     * The last-error code is maintained on a per-thread basis. Multiple threads do not overwrite each other's last-error code.\r\n     * @return The return value is the calling thread's last-error code.\r\n     */\r\n    int GetLastError();\r\n    \r\n    \r\n    ///////////////////////////\r\n    // SetErrorMode Function //\r\n    ///////////////////////////\r\n\r\n    /** Use the system default, which is to display all error dialog boxes. */\r\n    int SEM_DEFAULT = 0;\r\n    /** The system does not display the critical-error-handler message box. Instead, the system sends the error to the\r\n     *  calling process. */\r\n    int SEM_FAILCRITICALERRORS = 0x0001;\r\n    /** The system automatically fixes memory alignment faults and makes them invisible to the application. It does this\r\n     *  for the calling process and any descendant processes. This feature is only supported by certain processor\r\n     *  architectures. For more information, see the Remarks sections. After this value is set for a process, subsequent\r\n     *  attempts to clear the value are ignored. */\r\n    int SEM_NOALIGNMENTFAULTEXCEPT = 0x0004;\r\n    /** The system does not display the general-protection-fault message box. This flag should only be set by debugging\r\n     *  applications that handle general protection (GP) faults themselves with an exception handler. */\r\n    int SEM_NOGPFAULTERRORBOX = 0x0002;\r\n    /** The system does not display a message box when it fails to find a file. Instead, the error is returned to the\r\n     *  calling process. */\r\n    int SEM_NOOPENFILEERRORBOX = 0x8000;\r\n\r\n    /**\r\n     * Controls whether the system will handle the specified types of serious errors or whether the process will handle\r\n     * them.\r\n     *\r\n     * <p>Remarks: Each process has an associated error mode that indicates to the system how the application is going\r\n     * to respond to serious errors. A child process inherits the error mode of its parent process. To retrieve the\r\n     * process error mode, use the GetErrorMode function.\r\n     * Because the error mode is set for the entire process, you must ensure that multi-threaded applications do not set\r\n     * different error-mode flags. Doing so can lead to inconsistent error handling.\r\n     * The system does not make alignment faults visible to an application on all processor architectures. Therefore,\r\n     * specifying SEM_NOALIGNMENTFAULTEXCEPT is not an error on such architectures, but the system is free to silently\r\n     * ignore the request.\r\n     *\r\n     * @param uMode The process error mode. This parameter can be one or more of the following values:\r\n     * SEM_DEFAULT (alone), SEM_FAILCRITICALERRORS, SEM_NOALIGNMENTFAULTEXCEPT, SEM_NOGPFAULTERRORBOX and\r\n     * SEM_NOOPENFILEERRORBOX.\r\n     * @return the previous state of the error-mode bit flags.\r\n     */\r\n    int SetErrorMode(int uMode);\r\n\r\n\r\n    /////////////////////////////////\r\n    // GetDiskFreeSpaceEx function //\r\n    /////////////////////////////////\r\n\r\n    /**\r\n     * Retrieves information about the amount of space that is available on a disk volume, which is the total amount of\r\n     * space, the total amount of free space, and the total amount of free space available to the user that is \r\n     * associated with the calling thread.\r\n     *\r\n     * @param lpDirectoryName A directory on the disk.\r\n     * If this parameter is NULL, the function uses the root of the current disk.\r\n     * If this parameter is a UNC name, it must include a trailing backslash, for example, \"\\\\MyServer\\MyShare\\\".\r\n     * This parameter does not have to specify the root directory on a disk. The function accepts any directory on a disk.\r\n     * The calling application must have FILE_LIST_DIRECTORY access rights for this directory.\r\n     * @param lpFreeBytesAvailable A pointer to a variable that receives the total number of free bytes on a disk that\r\n     * are available to the user who is associated with the calling thread. This parameter can be NULL.\r\n     * If per-user quotas are being used, this value may be less than the total number of free bytes on a disk.\r\n     * @param lpTotalNumberOfBytes A pointer to a variable that receives the total number of bytes on a disk that are\r\n     * available to the user who is associated with the calling thread. This parameter can be NULL.\r\n     * If per-user quotas are being used, this value may be less than the total number of bytes on a disk.\r\n     * To determine the total number of bytes on a disk or volume, use IOCTL_DISK_GET_LENGTH_INFO.\r\n     * @param lpTotalNumberOfFreeBytes A pointer to a variable that receives the total number of free bytes on a disk.\r\n     * This parameter can be NULL.\r\n     * @return If the function succeeds, the return value is nonzero. If the function fails, the return value is\r\n     * zero (0). To get extended error information, call GetLastError.\r\n     */\r\n    boolean GetDiskFreeSpaceEx(String lpDirectoryName,\r\n    \t\tLongByReference lpFreeBytesAvailable,\r\n    \t\tLongByReference lpTotalNumberOfBytes,\r\n    \t\tLongByReference lpTotalNumberOfFreeBytes);\r\n\r\n\r\n    ///////////////////////////\r\n    // GetDriveType function //\r\n    ///////////////////////////\r\n\r\n    /** The drive type cannot be determined. */\r\n    int DRIVE_UNKNOWN = 0;\r\n\r\n    /** The root path is invalid; for example, there is no volume is mounted at the path. */\r\n    int DRIVE_NO_ROOT_DIR = 1;\r\n\r\n    /** The drive has removable media; for example, a floppy drive, thumb drive, or flash card reader. */\r\n    int DRIVE_REMOVABLE = 2;\r\n\r\n    /** The drive has fixed media; for example, a hard drive or flash drive. */\r\n    int DRIVE_FIXED = 3;\r\n\r\n    /** The drive is a remote (network) drive. */\r\n    int DRIVE_REMOTE = 4;\r\n\r\n    /** The drive is a CD-ROM drive. */\r\n    int DRIVE_CDROM = 5;\r\n\r\n    /** The drive is a RAM disk. */\r\n    int DRIVE_RAMDISK = 6;\r\n\r\n    /**\r\n     * Determines whether a disk drive is a removable, fixed, CD-ROM, RAM disk, or network drive.\r\n     * \r\n     * @param lpRootPathName The root directory for the drive. A trailing backslash is required. If this parameter is\r\n     * NULL, the function uses the root of the current directory.\r\n     * @return The return value specifies the type of drive, which can be one of the above values.\r\n     */\r\n    int GetDriveType(String lpRootPathName);\r\n\r\n\r\n    /////////////////////////\r\n    // MoveFileEx function //\r\n    /////////////////////////\r\n\r\n    /** If a file named lpNewFileName exists, the function replaces its contents with the contents of the\r\n     * lpExistingFileName file. This value cannot be used if lpNewFileName or lpExistingFileName names a directory. */\r\n    int MOVEFILE_REPLACE_EXISTING = 1;\r\n\r\n    /** If the file is to be moved to a different volume, the function simulates the move by using the CopyFile and\r\n     * DeleteFile functions.<br>\r\n     * This value cannot be used with MOVEFILE_DELAY_UNTIL_REBOOT. */\r\n    int MOVEFILE_COPY_ALLOWED = 2;\r\n\r\n    /** The system does not move the file until the operating system is restarted. The system moves the file immediately\r\n     * after AUTOCHK is executed, but before creating any paging files. Consequently, this parameter enables the\r\n     * function to delete paging files from previous startups.<br>\r\n     * This value can be used only if the process is in the context of a user who belongs to the administrators group or\r\n     * the LocalSystem account.<br>\r\n     * This value cannot be used with MOVEFILE_COPY_ALLOWED.<br>\r\n     * <b>Windows 2000</b>:  If you specify the MOVEFILE_DELAY_UNTIL_REBOOT flag for dwFlags, you cannot also prepend\r\n     * the filename that is specified by lpExistingFileName with \"\\\\?\". */\r\n    int MOVEFILE_DELAY_UNTIL_REBOOT = 4;\r\n\r\n    /** The function does not return until the file is actually moved on the disk.<br>\r\n     * Setting this value guarantees that a move performed as a copy and delete operation is flushed to disk before the\r\n     * function returns. The flush occurs at the end of the copy operation.<br>\r\n     * This value has no effect if MOVEFILE_DELAY_UNTIL_REBOOT is set. */\r\n    int MOVEFILE_WRITE_THROUGH = 8;\r\n\r\n    /** Reserved for future use. */\r\n    int MOVEFILE_CREATE_HARDLINK = 16;\r\n\r\n    /** The function fails if the source file is a link source, but the file cannot be tracked after the move.\r\n     * This situation can occur if the destination is a volume formatted with the FAT file system. */\r\n    int MOVEFILE_FAIL_IF_NOT_TRACKABLE = 32;\r\n\r\n    /**\r\n     * Moves an existing file or directory, including its children, with various move options.\r\n     *\r\n     * <p><b>Warning</b>: this method is NOT available on Windows 95, 98 and Me.\r\n     * \r\n     * @param lpExistingFileName The current name of the file or directory on the local computer. If dwFlags specifies\r\n     * MOVEFILE_DELAY_UNTIL_REBOOT, the file cannot exist on a remote share, because delayed operations are performed\r\n     * before the network is available.\r\n     * @param lpNewFileName The new name of the file or directory on the local computer. When moving a file, the\r\n     * destination can be on a different file system or volume. If the destination is on another drive, you must set the\r\n     * MOVEFILE_COPY_ALLOWED flag in dwFlags. When moving a directory, the destination must be on the same drive.\r\n     * @param dwFlags This parameter can be one or more of the 'MOVEFILE_' constant values. \r\n     * @return If the function succeeds, the return value is nonzero. If the function fails, the return value is\r\n     * zero (0). To get extended error information, call GetLastError.\r\n     */\r\n    boolean MoveFileEx(String lpExistingFileName, String lpNewFileName, int dwFlags);\r\n\r\n\r\n    ///////////////////////////////////\r\n    // GetVolumeInformation function //\r\n    ///////////////////////////////////\r\n\r\n    /** The file system preserves the case of file names when it places a name on disk. */\r\n    int FILE_CASE_PRESERVED_NAMES = 0x00000002;\r\n\r\n    /** The file system supports case-sensitive file names. */\r\n    int FILE_CASE_SENSITIVE_SEARCH = 0x00000001;\r\n\r\n    /** The file system supports file-based compression. */\r\n    int FILE_FILE_COMPRESSION = 0x00000010;\r\n\r\n    /** The file system supports named streams. */\r\n    int FILE_NAMED_STREAMS = 0x00040000;\r\n\r\n    /** The file system preserves and enforces access control lists (ACL). For example, the NTFS file system preserves\r\n     * and enforces ACLs, and the FAT file system does not. */\r\n    int FILE_PERSISTENT_ACLS = 0x00000008;\r\n\r\n    /** The specified volume is read-only. Windows 2000:  This value is not supported.*/\r\n    int FILE_READ_ONLY_VOLUME = 0x00080000;\r\n\r\n    /** The volume supports a single sequential write. */\r\n    int FILE_SEQUENTIAL_WRITE_ONCE = 0x00100000;\r\n\r\n    /** The file system supports the Encrypted File System (EFS). */\r\n    int FILE_SUPPORTS_ENCRYPTION = 0x00020000;\r\n\r\n    /** The file system supports object identifiers. */\r\n    int FILE_SUPPORTS_OBJECT_IDS = 0x00010000;\r\n\r\n    /** The file system supports re-parse points. */\r\n    int FILE_SUPPORTS_REPARSE_POINTS = 0x00000080;\r\n\r\n    /** The file system supports sparse files. */\r\n    int FILE_SUPPORTS_SPARSE_FILES = 0x00000040;\r\n\r\n    /** The volume supports transactions. */\r\n    int FILE_SUPPORTS_TRANSACTIONS = 0x00200000;\r\n\r\n    /** The file system supports Unicode in file names as they appear on disk. */\r\n    int FILE_UNICODE_ON_DISK = 0x00000004;\r\n\r\n    /** The specified volume is a compressed volume, for example, a DoubleSpace volume. */\r\n    int FILE_VOLUME_IS_COMPRESSED = 0x00008000;\r\n\r\n    /** The file system supports disk quotas. */\r\n    int FILE_VOLUME_QUOTAS = 0x00000020;\r\n\r\n    /**\r\n     * Retrieves information about the file system and volume associated with the specified root directory.\r\n     *\r\n     * @param lpRootPathName A pointer to a string that contains the root directory of the volume to be described.\r\n     * If this parameter is NULL, the root of the current directory is used. A trailing backslash is required.\r\n     * For example, you specify \\\\MyServer\\MyShare as \"\\\\MyServer\\MyShare\\\", or the C drive as \"C:\\\".\r\n     * @param lpVolumeNameBuffer A pointer to a buffer that receives the name of a specified volume. The maximum buffer\r\n     * size is MAX_PATH+1.\r\n     * @param nVolumeNameSize The length of a volume name buffer, in TCHARs. The maximum buffer size is MAX_PATH+1.\r\n     * This parameter is ignored if the volume name buffer is not supplied.\r\n     * @param lpVolumeSerialNumber A pointer to a variable that receives the volume serial number.\r\n     * This parameter can be NULL if the serial number is not required.\r\n     * @param lpMaximumComponentLength A pointer to a variable that receives the maximum length, in TCHARs, of a file\r\n     * name component that a specified file system supports. A file name component is the portion of a file name between\r\n     * backslashes. The value that is stored in the variable that *lpMaximumComponentLength points to is used to\r\n     * indicate that a specified file system supports long names. For example, for a FAT file system that supports long\r\n     * names, the function stores the value 255, rather than the previous 8.3 indicator. Long names can also be\r\n     * supported on systems that use the NTFS file system.\r\n     * @param lpFileSystemFlags A pointer to a variable that receives flags associated with the specified file system.\r\n     * This parameter can be one or more of the following flags ; FS_FILE_COMPRESSION and FS_VOL_IS_COMPRESSED\r\n     * are mutually exclusive:\r\n     * FILE_CASE_PRESERVED_NAMES, FILE_CASE_SENSITIVE_SEARCH, FILE_FILE_COMPRESSION, FILE_NAMED_STREAMS,\r\n     * FILE_PERSISTENT_ACLS, FILE_READ_ONLY_VOLUME, FILE_SEQUENTIAL_WRITE_ONCE, FILE_SUPPORTS_ENCRYPTION,\r\n     * FILE_SUPPORTS_OBJECT_IDS, FILE_SUPPORTS_REPARSE_POINTS, FILE_SUPPORTS_SPARSE_FILES, FILE_SUPPORTS_TRANSACTIONS,\r\n     * FILE_UNICODE_ON_DISK, FILE_VOLUME_IS_COMPRESSED, FILE_VOLUME_QUOTAS.\r\n     * @param lpFileSystemNameBuffer A pointer to a buffer that receives the name of the file system, for example, the\r\n     * FAT file system or the NTFS file system. The maximum buffer size is MAX_PATH+1.\r\n     * @param nFileSystemNameSize The length of the file system name buffer, in TCHARs. The maximum buffer size is\r\n     * MAX_PATH+1. This parameter is ignored if the file system name buffer is not supplied.\r\n     * @return If all the requested information is retrieved, the return value is true. If not all the requested\r\n     * information is retrieved, the return value is false. To get extended error information, call GetLastError.\r\n     */\r\n    boolean GetVolumeInformation(\r\n            char[] lpRootPathName,\r\n            CharBuffer lpVolumeNameBuffer,\r\n            int nVolumeNameSize,\r\n            LongByReference lpVolumeSerialNumber,\r\n            LongByReference lpMaximumComponentLength,\r\n            LongByReference lpFileSystemFlags,\r\n            CharBuffer lpFileSystemNameBuffer,\r\n            int nFileSystemNameSize\r\n    \t\t);\r\n\r\n    ////////////////////////////////\r\n    // GetFileAttributes function //\r\n    ////////////////////////////////\r\n\r\n    /** Failed to retrieve file attributes  */\r\n    int INVALID_FILE_ATTRIBUTES = -1;\r\n\r\n    /** A file or directory that the operating system uses a part of, or uses exclusively. */\r\n    int FILE_ATTRIBUTE_SYSTEM =  0x00000004;\r\n\r\n    /**\r\n     * Retrieves file system attributes for a specified file or directory.\r\n     * \r\n     * @param fileName The name of the file or directory.\r\n     * @return If the function succeeds, the return value contains the attributes of the specified file or directory.\r\n     * If the function fails, the return value is INVALID_FILE_ATTRIBUTES. To get extended error information, call GetLastError.\r\n     */\r\n    int GetFileAttributes(String fileName);\r\n\r\n    ////////////////////////////\r\n    // FindFirstFile function //\r\n    ////////////////////////////\r\n    /** Alias class for W32API.HANDLE. */\r\n    final class FindFileHandle extends WinNT.HANDLE {\r\n    \tpublic boolean isValid() {\r\n    \t\treturn this != WinNT.INVALID_HANDLE_VALUE;\r\n    \t}\r\n    }\r\n\r\n    /** Contains information about the file that is found by the FindFirstFile, FindFirstFileEx, or FindNextFile function. */\r\n    class WIN32_FIND_DATA extends Structure {\r\n    \t/** The file attributes of a file. */\r\n    \tpublic int dwFileAttributes;\r\n    \t/** A FILETIME structure that specifies when a file or directory was created. */\r\n    \tpublic long ftCreationTime;\r\n    \t/** For a file, the structure specifies when the file was last read from, written to, or for executable files, run.\r\n    \t *  For a directory, the structure specifies when the directory is created. If the underlying file system does not support last access time, this member is zero. */\r\n    \tpublic long ftLastAccessTime;\r\n    \t/** For a file, the structure specifies when the file was last written to, truncated, or overwritten, for example, when WriteFile or SetEndOfFile are used. The date and time are not updated when file attributes or security descriptors are changed.\r\n    \t *  For a directory, the structure specifies when the directory is created. If the underlying file system does not support last write time, this member is zero. */\r\n    \tpublic long ftLastWriteTime;\r\n    \t/** The high-order DWORD value of the file size, in bytes.\r\n    \t *  This value is zero unless the file size is greater than MAXDWORD.\r\n    \t *  The size of the file is equal to (nFileSizeHigh * (MAXDWORD+1)) + nFileSizeLow. */\r\n    \tpublic int nFileSizeHigh;\r\n    \t/** The low-order DWORD value of the file size, in bytes. */\r\n    \tpublic int nFileSizeLow;\r\n    \t/** If the dwFileAttributes member includes the FILE_ATTRIBUTE_REPARSE_POINT attribute, this member specifies the reparse point tag.\r\n    \t *  Otherwise, this value is undefined and should not be used. */\r\n    \tpublic int dwReserved0;\r\n    \t/** Reserved for future use. */\r\n    \tpublic int dwReserved1;\r\n    \t/** The name of the file. */\r\n    \tpublic char[] cFileName = new char[250];\r\n    \t/** An alternative name for the file.\r\n    \t * This name is in the classic 8.3 file name format. */\r\n    \tpublic char[] cAlternateFileName = new char[14];\r\n\r\n        @Override\r\n        protected List<String> getFieldOrder() {\r\n            return Arrays.asList(\"dwFileAttributes\", \"ftCreationTime\", \"ftLastAccessTime\", \"ftLastWriteTime\", \"nFileSizeHigh\",\r\n                    \"nFileSizeLow\", \"dwReserved0\", \"dwReserved1\", \"cFileName\", \"cAlternateFileName\");\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Searches a directory for a file or subdirectory with a name that matches a specific name (or partial name if wildcards are used).\r\n     * \r\n     * @param fileName The directory or path, and the file name, which can include wildcard characters, for example, an asterisk (*) or a question mark (?).\r\n     * @param findFileData A pointer to the WIN32_FIND_DATA structure that receives information about a found file or directory.\r\n     * @return If the function succeeds, the return value is a search handle used in a subsequent call to FindNextFile or FindClose, and the lpFindFileData parameter contains information about the first file or directory found.\r\n     */\r\n    FindFileHandle FindFirstFile(String fileName, WIN32_FIND_DATA findFileData);\r\n\r\n    /////////////////////////\r\n    // FindClose function //\r\n    ////////////////////////\r\n    /**\r\n     * Closes a file search handle opened by the FindFirstFile, FindFirstFileEx, FindFirstFileNameW, FindFirstFileNameTransactedW, FindFirstFileTransacted, FindFirstStreamTransactedW, or FindFirstStreamW functions.\r\n     * \r\n     * @param hFindFile The file search handle.\r\n     * @return If the function succeeds, the return value is nonzero.\r\n     * If the function fails, the return value is zero. To get extended error information, call GetLastError.\r\n     */\r\n    boolean FindClose(FindFileHandle hFindFile);\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/util/OSXFileUtils.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.commons.file.util;\n\nimport com.dd.plist.BinaryPropertyListParser;\nimport com.dd.plist.NSString;\nimport com.dd.plist.PropertyListFormatException;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.runtime.OsVersion;\nimport com.sun.jna.platform.mac.XAttrUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.UnsupportedEncodingException;\n\n/**\n * This class contains methods for file operations that are specific to Mac OS X.\n *\n * @author Maxence Bernard\n */\npublic class OSXFileUtils {\n\n    /** AppleScript that sets file comment */\n    public static final String SET_COMMENT_APPLESCRIPT = \"tell application \\\"Finder\\\" to set comment of file {posix file \\\"%s\\\"} to \\\"%s\\\"\";\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(OSXFileUtils.class);\n\n    /**\n     * Returns the Spotlight/Finder comment of the given file. The specified file must be a LocalFile,\n     * or have a LocalFile as an ancestor.\n     *\n     * <p>\n     *  <code>null</code> is returned in any of the following cases:\n     *  <ul>\n     *   <li>if the current OS is not Mac OS X or if the version is not 10.4 or higher (i.e. Spotlight is not available)</li>\n     *   <li>if the specified file is not a LocalFile and does not have a LocalFile ancestor</li>\n     *   <li>if the specified file has no comment</li>\n     *   <li>if the comment could not be retrieved (for any reason)</li>\n     *  </ul>\n     *\n     * @param file a local file\n     * @return the Spotlight/Finder comment of the specified file\n     */\n    public static String getSpotlightComment(AbstractFile file) {\n        if (!OsVersion.MAC_OS_X_10_4.isCurrentOrHigher()) {\n            return null;\n        }\n        byte[] bytes = XAttrUtils.read(file.getAbsolutePath(), XAttrUtils.COMMENT);\n        if (bytes == null)\n            return null;\n\n        try {\n            NSString comment = (NSString) BinaryPropertyListParser.parse(bytes);\n            return comment.getContent();\n        } catch (UnsupportedEncodingException | PropertyListFormatException e) {\n            LOGGER.error(\"failed to read comment of: \" + file.getAbsolutePath());\n            return null;\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/util/PathTokenizer.java",
    "content": "package com.mucommander.commons.file.util;\n\nimport java.util.Enumeration;\nimport java.util.NoSuchElementException;\nimport java.util.StringTokenizer;\nimport java.util.Vector;\n\n\n/**\n * This class allows to break a path into filename tokens. The default separator characters are '/' and '\\', but any\n * other separator characters can be specified. The {@link #hasMoreFilenames()} and {@link #nextFilename()} methods\n * can be used to iterate through all filename tokens. The {@link #getLastSeparator()} returns the last separator\n * string that appeared before the last filename token returned by {@link #nextFilename()}. Initially, this method\n * returns any leading separators the path may contain.\n * The {@link #getCurrentPath()} returns the current part of the path that has been tokenized.\n *\n * <p>To illustrate the use of PathTokenizer, the following piece of code iterates through all filename tokens and\n * reconstructs the original path :\n * <code>\n * PathTokenizer pt = new PathTokenizer(path);\n * String reconstructedPath = pt.getLastSeparator();\n * while(pt.hasMoreFilenames()) {\n *   String nextToken = pt.nextFilename();\n *   String lastSeparator = pt.getLastSeparator();\n *   reconstructedPath += nextToken+lastSeparator;\n * }\n * </code>\n *\n * <p>Note that PathTokenizer does not enforce valid file paths, that means a path with an incorrect syntax can still\n * be tokenized. In particular:\n * <ul>\n * <li>A path can contain mixed separators, e.g. C:\\temp/file\n * <li>Filename tokens can be separated by multiple separator characters, e.g. /usr//local, {@link #getLastSeparator()}\n * will return the complete separator string, in this case \"//\" for the 'usr' filename token.\n * </ul>\n *\n * @author Maxence Bernard\n */\npublic class PathTokenizer implements Enumeration<String> {\n\n    /** Separator characters */\n    private String separators;\n\n    /** True if this PathTokenizer tokenizes the path in reverse order, from right to left. */\n    private boolean reverseOrder;\n\n    /** Path tokens: separators and filenames. */\n    private String[] tokens;\n    /** Current index in the token array. */\n    private int currentIndex;\n    /** Path part that has been tokenized. */\n    private StringBuffer currentPath;\n    /** Last separators token. */ \n    private String lastSeparator;\n\n    /** Default separator characters. */\n    public final static String DEFAULT_SEPARATORS = \"/\\\\\";\n\n\n    /**\n     * Creates a new PathTokenizer using the given path and default separator characters ({@link #DEFAULT_SEPARATORS}},\n     * in forward order, tokenizing the path from left to right.\n     *\n     * @param path the path to break into tokens\n     */\n    public PathTokenizer(String path) {\n        this(path, DEFAULT_SEPARATORS, false);\n    }\n\n\n    /**\n     * Creates a new PathTokenizer using the given path and separator character(s).\n     *\n     * @param path the path to break into tokens\n     * @param separators the character(s) that separate tokens\n     * @param reverseOrder if true, the path will be tokenized in reverse order, from right to left \n     */\n    public PathTokenizer(String path, String separators, boolean reverseOrder) {\n        this.separators = separators;\n        this.reverseOrder = reverseOrder;\n\n        // Split the path into tokens\n        StringTokenizer st = new StringTokenizer(path, separators, true);\n        Vector<String> tokensV = new Vector<>();\n        while(st.hasMoreTokens()) {\n            tokensV.add(st.nextToken());\n        }\n\n        // Convert Vector into array\n        tokens = new String[tokensV.size()];\n        int nbTokens = tokens.length;\n\n        if(reverseOrder) {\n            for(int i=0; i<nbTokens; i++)\n                tokens[i] = tokensV.elementAt(nbTokens-i-1);\n        }\n        else {\n            tokensV.toArray(tokens);\n        }\n\n        // Initialize current path\n        if(reverseOrder)\n            currentPath = new StringBuffer(path);\n        else\n            currentPath = new StringBuffer(path.length());\n\n        // Skip leading separator\n        skipSeparators();\n    }\n\n\n    /**\n     * Skips separator tokens and advances the internal token index, until either a filename token has been found\n     * or the end of the path has been reached.\n     */\n    private void skipSeparators() {\n        lastSeparator = \"\";\n\n        String token;\n        while (currentIndex<tokens.length && separators.contains(token = tokens[currentIndex])) {\n            // Update last separator\n            lastSeparator += token;\n\n            // Update current path\n            handleToken(token);\n\n            currentIndex++;\n        }\n    }\n\n\n    /**\n     * Returns <code>true</code> if this PathTokenizer has more filename tokens.\n     * @return <code>true</code> if this PathTokenizer has more filename tokens, <code>false</code> otherwise.\n     */\n    public boolean hasMoreFilenames() {\n        return currentIndex<tokens.length;\n    }\n\n    private void handleToken(String token) {\n        if(reverseOrder)\n            currentPath.setLength(currentPath.length()-token.length());\n        else\n            currentPath.append(token);\n    }\n\n    /**\n     * Returns the next filename token from this PathTokenizer. Throws a NoSuchElementException if no more filename\n     * tokens are available. After calling this method, the values returned by {@link #getLastSeparator()} and\n     * {@link #getCurrentPath()} will be updated.\n     *\n     * @return the next filename token from this PathTokenizer\n     * @throws NoSuchElementException if no more tokens are available\n     */\n    public String nextFilename() throws NoSuchElementException {\n        if(currentIndex<tokens.length) {\n            String token = tokens[currentIndex++];\n\n            // Update current path\n            handleToken(token);\n\n            // Skip separators after the filename \n            skipSeparators();\n\n            return token;\n        }\n        else\n            throw new NoSuchElementException();\n    }\n\n\n    /**\n     * Returns the current path part that has been tokenized, i.e. that ends with the last filename token returned by\n     * {@link #nextFilename()} and separator string returned by {@link #getLastSeparator()}.<br>\n     * If this PathTokenizer operates in reverse order, the returned path is the path part that has not yet been \n     * tokenized.\n     * @return the current path part that has been tokenized.\n     */\n    public String getCurrentPath() {\n        return currentPath.toString();\n    }\n\n\n    /**\n     * Returns the last separator string that appeared in the path after the last filename token returned by\n     * {@link #nextFilename()} and before the next filename, or an empty string \"\" if there isn't any separator\n     * character after the filename (path ends without a trailing separator).<br>\n     * Note: the returned string can be made of several consecutive separator characters.\n     *\n     * <p>Initially, before any calls to {@link #nextFilename()} have been made, this method will return any leading\n     * separator string in the path string, or an empty string if the path doesn't start with a separator.\n     * @return the last separator string that appeared in the path.\n     */\n    public String getLastSeparator() {\n        return lastSeparator;\n    }\n\n\n    ////////////////////////////////\n    // Enumeration implementation //\n    ////////////////////////////////\n\n    /**\n     * Enumeration implementation, returns the same value as {@link #hasMoreFilenames()}.\n     */\n    public boolean hasMoreElements() {\n        return hasMoreFilenames();\n    }\n\n    /**\n     * Enumeration implementation, returns the same value as {@link #nextFilename()}.\n     */\n    public String nextElement() throws NoSuchElementException {\n        return nextFilename();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/util/PathUtils.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\npackage com.mucommander.commons.file.util;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.FileURL;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.net.MalformedURLException;\n\n/**\n * This class contains static helper methods that operate on file paths.\n *\n * @author Maxence Bernard\n */\npublic class PathUtils {\n    private static final Logger LOGGER = LoggerFactory.getLogger(PathUtils.class);\n\n    /**\n     * This class represents a destination entered by the user and resolved by {@link PathUtils#resolveDestination(String, com.mucommander.commons.file.AbstractFile)}\n     * into an <code>AbstractFile</code> and a destination type.\n     *\n     * @see PathUtils#resolveDestination(String, com.mucommander.commons.file.AbstractFile)\n     */\n    public static class ResolvedDestination {\n\n        /** The destination AbstractFile, may be a regular file or a folder */\n        private final AbstractFile file;\n\n        /** The destination's folder, the file itself for {@link #EXISTING_FOLDER}, the destination file's parent for\n         * other types */\n        private final AbstractFile folder;\n\n        /** The destination type, see constant values */\n        private final int type;\n\n        /** Designates a folder, either a directory or archive, that exists on the filesystem. */\n        public static final int EXISTING_FOLDER = 0;\n        /** Designates a regular file that exists on the filesystem. The file may be a browsable archive but that was\n         * referred to as a regular file, i.e. without a trailing separator character in the path. */\n        public static final int EXISTING_FILE = 1;\n        /** Designates a new file that doesn't exist on the filesystem. The file's parent however does always exist. */\n        public static final int NEW_FILE = 2;\n\n        /** Designates a new directory that doesn't exist on the filesystem. The file's parent directory does not exist too. */\n        public static final int NEW_DIRECTORIES = 3;\n\n        /**\n         * Creates a new <code>ResolvedDestination</code> with the specified destination file and type.\n         *\n         * @param destinationFile the destination file\n         * @param destinationType the destination type\n         * @param destinationFolder the destination folder\n         */\n        private ResolvedDestination(AbstractFile destinationFile, int destinationType, AbstractFile destinationFolder) {\n            this.file = destinationFile;\n            this.type = destinationType;\n            this.folder = destinationFolder;\n        }\n\n        /**\n         * Returns the resolved destination file. The returned file may or may not physically exist on the filesystem.\n         * If it exists, the returned file may be a folder (directory or browsable archive) or a regular file.\n         *\n         * @return the resolved destination file\n         * @see #getDestinationType()\n         */\n        public AbstractFile getDestinationFile() {\n            return file;\n        }\n\n        /**\n         * Returns the resolved destination's folder. Depending on the {@link #getDestinationType() destination type},\n         * the destination folder is:\n         * <dl>\n         *  <dt>for {@link #EXISTING_FOLDER}</dt><dd>the {@link #getDestinationFile() destination file} itself</dd>\n         *  <dt>for {@link #EXISTING_FILE} or {@link #NEW_FILE}</dt><dd>the {@link #getDestinationFile() destination file}'s parent</dd>\n         * </dl>\n         *  The returned <code>AbstractFile</code> is always a folder that exists.\n         *\n         * @return the resolved destination file\n         * @see #getDestinationType()\n         */\n        public AbstractFile getDestinationFolder() {\n            return folder;\n        }\n\n        /**\n         * Returns the type of destination that was resolved. The returned value will be one of the following constant\n         * fields defined in this class:\n         * <dl>\n         *  <dt>{@link #EXISTING_FOLDER}</dt><dd>if the path denotes a folder, either a directory or a browsable\n         * archive.</dd>\n         *  <dt>{@link #EXISTING_FILE}</dt><dd>if the path denotes a regular file. The file may be a browsable archive,\n         * see below.</dd>\n         *  <dt>{@link #NEW_FILE}</dt><dd>if the path denotes a non-existing file whose parent exists.</dd>\n         * </dl>\n         * Paths to browsable archives are considered as denoting a folder only if they end with a trailing separator\n         * character. If they don't, they're considered as denoting a regular file. For example,\n         * <code>/existing_folder/existing_archive.zip/</code> refers to the archive as a folder where as\n         * <code>/existing_folder/existing_archive.zip</code> refers to the archive as a regular file.\n         *\n         * @return the type of destination that was resolved\n         */\n        public int getDestinationType() {\n            return type;\n        }\n    }\n\n    /**\n     * Resolves a destination path entered by the user and returns a {@link ResolvedDestination} object that\n     * that contains a {@link AbstractFile} instance corresponding to the path and a type that describes the kind of\n     * destination that was resolved. <code>null</code> is returned if the path is not a valid destination (see below)\n     * or could not be resolved, for example because of I/O or authentication error.\n     * <p>\n     * The given path may be either absolute or relative to the specified base folder. If the base folder argument is\n     * <code>null</code> and the path is relative, <code>null</code> will always be returned.\n     * The path may contain '.', '..' and '~' tokens which will be left for the corresponding\n     * {@link com.mucommander.commons.file.SchemeParser} to canonize.\n     *\n     * <p>\n     * The path may refer to the following listed destination types. In all cases, the destination's parent folder must\n     * exist, if it doesn't <code>null</code> will always be returned. For example, <code>/non_existing_folder/file</code>\n     * is not a valid destination (provided that '/non_existing_folder' does not exist).\n     * <dl>\n     *  <dt>{@link ResolvedDestination#EXISTING_FOLDER}</dt><dd>if the path denotes a folder, either a directory or a\n     * browsable archive.</dd>\n     *  <dt>{@link ResolvedDestination#EXISTING_FILE}</dt><dd>if the path denotes a regular file. The file may be a browsable archive,\n     * see below.</dd>\n     *  <dt>{@link ResolvedDestination#NEW_FILE}</dt><dd>if the path denotes a non-existing file whose parent exists.</dd>\n     * </dl>\n     * Paths to browsable archives are considered as denoting a folder only if they end with a trailing separator\n     * character. If they don't, they're considered as denoting a regular file. For example,\n     * <code>/existing_folder/existing_archive.zip/</code> refers to the archive as a folder where as\n     * <code>/existing_folder/existing_archive.zip</code> refers to the archive as a regular file.\n     *\n     * @param destPath the destination path to resolve\n     * @param baseFolder the base folder used for relative paths, <code>null</code> to accept only absolute paths\n     * @param requireParentExists if parent directory must exists\n     * @return the object that that contains a {@link AbstractFile} instance corresponding to the path and a type that\n     * describes the kind of destination that was resolved\n     */\n    public static ResolvedDestination resolveDestination(String destPath, AbstractFile baseFolder, boolean requireParentExists) {\n        FileURL destURL;\n\n        // Try to resolve the path as a URL\n        try {\n            destURL = FileURL.getFileURL(destPath);\n            // destPath is absolute\n        } catch (MalformedURLException e) {\n            // destPath is relative (or malformed)\n\n            // Abort now if there is no base folder\n            if (baseFolder == null) {\n                return null;\n            }\n\n            String separator = baseFolder.getSeparator();\n\n            // Start by cloning the base folder's URL, including credentials and properties\n            FileURL baseFolderURL = baseFolder.getURL();\n            destURL = (FileURL)baseFolderURL.clone();\n            String basePath = destURL.getPath();\n            if (!destPath.isEmpty()) {\n                destURL.setPath(basePath + (basePath.endsWith(separator) ? \"\" : separator) + destPath);\n            }\n\n            // At this point we have the proper URL, except that the path may contain '.', '..' or '~' tokens.\n            // => parse the URL from scratch to have the SchemeParser canonize them.\n            try {\n                destURL = FileURL.getFileURL(destURL.toString(false));\n                // Import credentials separately, so that login and passwords that contain URI-unsafe characters\n                // such as '/' are properly parsed.\n                destURL.setCredentials(baseFolderURL.getCredentials());\n                destURL.importProperties(baseFolderURL);\n            } catch (MalformedURLException e2) {\n                return null;\n            }\n        }\n\n        // No point in going any further if the URL cannot be resolved into a file\n        AbstractFile destFile = FileFactory.getFile(destURL);\n        if (destFile == null) {\n            LOGGER.info(\"could not resolve a file for {}\", destURL);\n            return null;\n        }\n\n        // Test if the destination file exists\n        boolean destFileExists = destFile.exists();\n        if (destFileExists) {\n            // Note: path to archives must end with a trailing separator character to refer to the archive as a folder,\n            //  if they don't, they'll refer to the archive as a file.\n            if (destFile.isDirectory() || (destPath.endsWith(destFile.getSeparator()) && destFile.isBrowsable())) {\n                return new ResolvedDestination(destFile, ResolvedDestination.EXISTING_FOLDER, destFile);\n            }\n        }\n\n        // Test if the destination's parent exists, if not the path is not a valid destination\n        AbstractFile destParent = destFile.getParent();\n        if (!requireParentExists) {\n            return new ResolvedDestination(destFile, ResolvedDestination.NEW_DIRECTORIES, destParent);\n        }\n        if (destParent == null || !destParent.exists()) {\n            return null;\n        }\n\n        return new ResolvedDestination(destFile, destFileExists ? ResolvedDestination.EXISTING_FILE : ResolvedDestination.NEW_FILE, destParent);\n    }\n\n\n    public static ResolvedDestination resolveDestination(String destPath, AbstractFile baseFolder) {\n        return resolveDestination(destPath, baseFolder, true);\n    }\n\n\n    /**\n     * Removes any leading separator character (slash or backslash) from the given path and returns the modified path.\n     *\n     * @param path the path to modify\n     * @return the modified path, free of any leading separator\n     */\n    public static String removeLeadingSeparator(String path) {\n        char firstChar;\n        if (!path.isEmpty() && ((firstChar=path.charAt(0)) == '/' || firstChar=='\\\\')) {\n            return path.substring(1);\n        }\n\n        return path;\n    }\n\n    /**\n     * Removes any leading separator character from the given path and returns the modified path.\n     *\n     * @param path the path to modify\n     * @param separator the path separator, usually \"/\" or \"\\\\\"\n     * @return the modified path, free of any leading separator\n     */\n    public static String removeLeadingSeparator(String path, String separator) {\n        if (path.startsWith(separator)) {\n            return path.substring(separator.length());\n        }\n\n        return path;\n    }\n\n    /**\n     * Removes any trailing separator character (slash or backslash) from the given path and returns the modified path.\n     *\n     * @param path the path to modify\n     * @return the modified path, free of any trailing separator\n     */\n    public static String removeTrailingSeparator(String path) {\n        char lastChar;\n        int len = path.length();\n        if (len > 0 && ((lastChar = path.charAt(len-1)) =='/' || lastChar=='\\\\')) {\n            return path.substring(0, len - 1);\n        }\n\n        return path;\n    }\n\n    /**\n     * Removes any trailing separator character (slash or backslash) from the given path and returns the modified path.\n     *\n     * @param path the path to modify\n     * @param separator the path separator, usually \"/\" or \"\\\\\"\n     * @return the modified path, free of any trailing separator\n     */\n    public static String removeTrailingSeparator(String path, String separator) {\n        if (path.endsWith(separator)) {\n            return path.substring(0, path.length() - separator.length());\n        }\n\n        return path;\n    }\n\n    /**\n     * Returns <code>true</code> if both specified paths are equal. The path comparison is case-sensitive but trailing\n     * separator-insensitive: if the sole difference between two paths is a trailing path separator, they will be\n     * considered as equal. For example, <code>/path</code> and <code>/path/</code> are considered equal, assuming the\n     * path separator is '/'.\n     * <p>\n     * If any of the two specified paths is <code>null</code>, then the other one must also be <code>null</code> for\n     * this method to return <code>true</code>. The given <code>separator</code> must never be <code>null</code> or\n     * a {@link NullPointerException} will be thrown.\n     *\n     * @param path1 first path to test\n     * @param path2 second path to test\n     * @param separator path separator for both paths\n     * @throws NullPointerException if the given separator is <code>null</code>\n     * @return <code>true</code> if both paths are equal\n     */\n    public static boolean pathEquals(String path1, String path2, String separator) {\n        if (path1 == null) {\n            return path2 == null;\n        }\n\n        if (path2 == null) {\n            return false;\n        }\n        \n        if (path1.equals(path2)) {\n            return true;\n        }\n\n        int len1 = path1.length();\n        int len2 = path2.length();\n        int separatorLen = separator.length();\n\n        // If the difference between the 2 strings is just a trailing path separator, we consider the paths as equal\n        if (Math.abs(len1-len2)==separatorLen && (len1>len2 ? path1.startsWith(path2) : path2.startsWith(path1))) {\n            String diff = len1>len2 ? path1.substring(len1-separatorLen) : path2.substring(len2-separatorLen);\n            return separator.equals(diff);\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns a hashcode for the given path. The returned hashcode is consistent with\n     * {@link #pathEquals(String, String, String)} in that hashcodes are trailing separator-invariant:\n     * <code>path1.equals(path2)</code> implies <code>path1Hashcode==path2Hashcode.hashCode()</code>, even if path1\n     * ends with a separator and path2 does not or vice-versa.\n     * \n     * @param path the path for which to return a hashcode\n     * @param separator separator of the given path\n     * @return a trailing separator-insensitive hashcode\n     */\n    public static int getPathHashCode(String path, String separator) {\n        // #equals(Object) is trailing separator insensitive, so the hashCode must be trailing separator invariant\n        return path.endsWith(separator)\n                ?path.substring(0, path.length()-separator.length()).hashCode()\n                :path.hashCode();\n    }\n\n\n    /**\n     * Removes the specified number of fragments from the beginning of the given path and returns the modified path,\n     * free of a leading separator. Returns an empty string (<code>\"\"</code>) if the path does not contain less or\n     * exactly that many fragments.\n     *\n     * <p>\n     * For instance, calling this method with\n     * <ul>\n     *   <li><code>(\"/home/maxence/, \"/\", 0)</code> will return \"home/maxence/\"</li>\n     *   <li><code>(\"/home/maxence/, \"/\", 1)</code> will return \"maxence/\"</li>\n     *   <li><code>(\"/home/maxence/, \"/\", 2)</code> will return \"\"</li>\n     *   <li><code>(\"/home/maxence/, \"/\", 3)</code> will return \"\"</li>\n     * </ul>\n     *\n     * @param path the path to modify\n     * @param separator the path separator, usually \"/\" or \"\\\\\"\n     * @param nbFragments number of path fragments to remove from the path\n     * @return the modified path, free of any leading separator\n     */\n    public static String removeLeadingFragments(String path, String separator, int nbFragments) {\n        path = removeLeadingSeparator(path, separator);\n\n        if (nbFragments == 0) {\n            return path;\n        }\n\n        int pos = -1;\n        for (int i = 0; i < nbFragments && (pos=path.indexOf(separator, pos+1)) >= 0; i++);\n\n        if (pos < 0 || pos == path.length()-1) {\n            return \"\";\n        }\n\n        return path.substring(pos+1);\n    }\n\n\n    /**\n     * Returns the depth of the specified path, based on the number of path separators it contains, excluding those\n     * occurring at the beginning and at the end. The minimum depth of a path is 0.<br>\n     * Here are a few examples when the path separator is <code>\"/\"</code>:\n     * <dl>\n     *   <dt>/</dt><dd>0</dd>\n     *   <dt>/home</dt><dd>1</dd>\n     *   <dt>/home/maxence</dt><dd>2</dd>\n     * </dl>\n     *\n     * <p>\n     * It is worth noting that this method relies strictly on the occurences of path separators and nothing else.\n     * Therefore, Windows-like paths that start with a drive letter will always have a minimum depth\n     * of 1.<br>\n     * Here are a few examples when the path separator is {@code \"\\\\\"}:\n     * <dl>\n     *   <dt>C:\\\\</dt><dd>1</dd>\n     *   <dt>C:\\\\home</dt><dd>2</dd>\n     *   <dt>C:\\\\home\\\\maxence</dt><dd>1</dd>\n     * </dl>\n     *\n     * @param path the path for which to calculate the depth\n     * @param separator the path separator, usually \"/\" or \"\\\\\"\n     * @return the depth of the given path\n     */\n    public static int getDepth(String path, String separator) {\n        if (path.isEmpty() || path.equals(separator)) {\n            return 0;\n        }\n\n        int pos = path.startsWith(separator) ? 1 : 0;\n        int depth = 1;\n        while ((pos=path.indexOf(separator, pos+1)) >= 0) {\n            depth++;\n        }\n\n        if (path.endsWith(separator)) {\n            depth--;\n        }\n\n        return depth;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/util/ResourceLoader.java",
    "content": "package com.mucommander.commons.file.util;\n\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.UnsupportedEncodingException;\nimport java.net.URL;\nimport java.net.URLDecoder;\nimport java.util.Enumeration;\n\n/**\n * This class provides methods to load resources located within the reach of a <code>ClassLoader</code>. Those resources\n * can reside either in a JAR file or in a local directory that are in the classpath -- all methods of this class are\n * agnostic to either type of location.\n *\n * <p>The <code>getResourceAsURL</code> and <code>getResourceAsStream</code> methods are akin to those of\n * <code>java.lang.Class</code> and <code>java.lang.ClassLoader</code>, except that they are not sensitive to the\n * presence of a leading forward-slash separator in the resource path, and that they allow the search to be limited\n * to a particular classpath location.\n *\n * <p>But the real fun lies in the <code>getResourceAsFile</code> methods, which allow to manipulate resources as\n * regular files -- again, whether they be in a regular directory or in a JAR file. Likewise,\n * the {@link #getRootPackageAsFile(Class)} allows to dynamically explore and manipulate the resource files contained\n * in a particular classpath's location.\n *\n * @author Maxence Bernard\n */\npublic class ResourceLoader {\n    private static final Logger LOGGER = LoggerFactory.getLogger(ResourceLoader.class);\n\n    /** the default ClassLoader that is used by methods without a ClassLoader argument */\n    private static ClassLoader defaultClassLoader = ResourceLoader.class.getClassLoader();\n\n    /**\n     * Returns the default <code>ClassLoader</code> that is used by methods without a <code>ClassLoader</code> argument.\n     * This default <code>ClassLoader</code> is the one that loaded this class, and <b>not</b> the system <code>ClassLoader</code>. \n     *\n     * @return the default <code>ClassLoader</code> that is used by methods without a <code>ClassLoader</code> argument\n     */\n    public static ClassLoader getDefaultClassLoader() {\n        // We do not use the system class loader because it does not work with JNLP/Webstart applications.\n        // A quote from a FAQ at java.sun.com:\n        //\n        // Java Web Start uses a user-level classloader to load all the application resources specified in the JNLP file.\n        // This classloader implements the security model and the downloading model defined by the JNLP specification.\n        // This is no different than how the AppletViewer or the Java Plug-In works.\n        // This has the, unfortunate, side-effect that Class.forName will not find any resources that are defined in the\n        // JNLP file. The same is true for looking up resources and classes using the system class loader\n        // (ClassLoader.getSystemClassLoader).\n        //\n        return defaultClassLoader;\n    }\n\n\n    /**\n     * This method is similar to {@link #getResourceAsURL(String)} except that it looks for a resource with a given\n     * name in a specific package.\n     *\n     * @param ppackage package serving as a base folder for the resource to retrieve\n     * @param name name of the resource in the package. This is a filename only, not a path.\n     * @return a URL pointing to the resource, or <code>null</code> if the resource couldn't be located\n     */\n    public static URL getPackageResourceAsURL(Package ppackage, String name) {\n        return getPackageResourceAsURL(ppackage, name, getDefaultClassLoader(), null);\n    }\n\n    /**\n     * This method is similar to {@link #getResourceAsURL(String)} except that it looks for a resource with a given\n     * name in a specific package.\n     *\n     * @param ppackage package serving as a base folder for the resource to retrieve\n     * @param classLoader the ClassLoader used for locating the resource. May not be <code>null</code>.\n     * @param rootPackageFile root package location (JAR file or directory) that limits the scope of the search,\n     * <code>null</code> to look for the resource in the whole class path.\n     * @param name name of the resource in the package. This is a filename only, not a path.\n     * @return a URL pointing to the resource, or <code>null</code> if the resource couldn't be located\n     * @see #getRootPackageAsFile(Class)\n     */\n    public static URL getPackageResourceAsURL(Package ppackage, String name, ClassLoader classLoader, AbstractFile rootPackageFile) {\n        return ResourceLoader.getResourceAsURL(getRelativePackagePath(ppackage)+\"/\"+name, classLoader, rootPackageFile);\n    }\n\n    /**\n     * Shorthand for {@link #getResourceAsURL(String, ClassLoader, AbstractFile)} called with the\n     * {@link #getDefaultClassLoader() default class loader} and a <code>null</code> root package file.\n     *\n     * @param path forward slash-separated path to the resource to look for, relative to the parent classpath\n     * location (directory or JAR file) that contains it.\n     * @return a URL pointing to the resource, or <code>null</code> if the resource couldn't be located\n     */\n    public static URL getResourceAsURL(String path) {\n        return getResourceAsURL(path, getDefaultClassLoader(), null);\n    }\n\n    /**\n     * Finds the resource with the given path and returns a URL pointing to its location, or <code>null</code>\n     * if the resource couldn't be located. The given <code>ClassLoader</code> is used for locating the resource.\n     *\n     * <p>The given resource path must be forward slash (<code>/</code>) separated. It may or may not start with a\n     * leading forward slash character, this doesn't affect the way it is interpreted.\n     *\n     * <p>The <code>rootPackageFile</code> argument can be used to limit the scope of the search to a specific\n     * location (JAR file or directory) in the classpath: resources located outside of this location will not be matched.\n     * This avoids potential ambiguities that can arise if the specified resource path exists in several locations.\n     * If this parameter is <code>null</code>, the resource is looked up in the whole class path. In that case and if\n     * several resources with the specified path exist, the choice of the resource to return is arbitrary.\n     *\n     * @param path forward slash-separated path to the resource to look for, relative to the parent classpath\n     * location (directory or JAR file) that contains it.\n     * @param classLoader the ClassLoader used for locating the resource. May not be <code>null</code>.\n     * @param rootPackageFile root package location (JAR file or directory) that limits the scope of the search,\n     * <code>null</code> to look for the resource in the whole class path.\n     * @return a URL pointing to the resource, or <code>null</code> if the resource couldn't be located\n     */\n    public static URL getResourceAsURL(String path, ClassLoader classLoader, AbstractFile rootPackageFile) {\n        path = removeLeadingSlash(path);\n\n        if (rootPackageFile == null) {\n            return classLoader.getResource(path);\n        }\n\n        String separator = rootPackageFile.getSeparator();\n        String nativePath = separator.equals(\"/\") ? path : path.replace(\"/\", separator);\n\n        try {\n            // Iterate through all resources that match the given path, and return the one located inside the\n            // given root package file.\n            Enumeration<URL> resourceEnum = classLoader.getResources(path);\n            String rootPackagePath = rootPackageFile.getAbsolutePath();\n            String resourcePath = rootPackageFile.getAbsolutePath(true)+nativePath;\n            URL resourceURL;\n            while(resourceEnum.hasMoreElements()) {\n                resourceURL = resourceEnum.nextElement();\n\n                if (\"jar\".equals(resourceURL.getProtocol())) {\n                    if (getJarFilePath(resourceURL).equals(rootPackagePath)) {\n                        return resourceURL;\n                    }\n                } else {\n                    if (normalizeUrlPath(getDecodedURLPath(resourceURL)).equals(resourcePath)) {\n                        return resourceURL;\n                    }\n                }\n            }\n        } catch(IOException e) {\n            LOGGER.info(\"Failed to lookup resource {}\", path, e);\n            return null;\n        }\n\n        return null;\n    }\n\n    /**\n     * This method is similar to {@link #getResourceAsStream(String)} except that it looks for a resource with a given\n     * name in a specific package.\n     *\n     * @param ppackage package serving as a base folder for the resource to retrieve\n     * @param name name of the resource in the package. This is a filename only, not a path.\n     * @return an InputStream that allows to read the resource, or <code>null</code> if the resource couldn't be located\n     */\n    public static InputStream getPackageResourceAsStream(Package ppackage, String name) {\n        return getPackageResourceAsStream(ppackage, name, getDefaultClassLoader(), null);\n    }\n\n    /**\n     * This method is similar to {@link #getResourceAsStream(String, ClassLoader, AbstractFile)} except that it looks\n     * for a resource with a given name in a specific package.\n     *\n     * @param ppackage package serving as a base folder for the resource to retrieve\n     * @param name name of the resource in the package. This is a filename only, not a path.\n     * @param classLoader the ClassLoader used for locating the resource. May not be <code>null</code>.\n     * @param rootPackageFile root package location (JAR file or directory) that limits the scope of the search,\n     * <code>null</code> to look for the resource in the whole class path.\n     * @return an InputStream that allows to read the resource, or <code>null</code> if the resource couldn't be located\n     */\n    public static InputStream getPackageResourceAsStream(Package ppackage, String name, ClassLoader classLoader, AbstractFile rootPackageFile) {\n        return getResourceAsStream(getRelativePackagePath(ppackage)+\"/\"+name, classLoader, rootPackageFile);\n    }\n\n    /**\n     * Shorthand for {@link #getResourceAsStream(String, ClassLoader, AbstractFile)} called with the\n     * {@link #getDefaultClassLoader() default class loader} and a <code>null</code> root package file.\n     *\n     * @param path forward slash-separated path to the resource to look for, relative to the parent classpath\n     * location (directory or JAR file) that contains it.\n     * @return an InputStream that allows to read the resource, or <code>null</code> if the resource couldn't be located\n     */\n    public static InputStream getResourceAsStream(String path) {\n        return getResourceAsStream(path, getDefaultClassLoader(), null);\n    }\n\n    /**\n     * Finds the resource with the given path and returns an <code>InputStream</code> to read it, or <code>null</code>\n     * if the resource couldn't be located. The given <code>ClassLoader</code> is used for locating the resource.\n     *\n     * <p>The given resource path must be forward slash (<code>/</code>) separated. It may or may not start with a\n     * leading forward slash character, this doesn't affect the way it is interpreted.\n     *\n     * <p>The <code>rootPackageFile</code> argument can be used to limit the scope of the search to a specific\n     * location (JAR file or directory) in the classpath: resources located outside of this location will not be matched.\n     * This avoids potential ambiguities that can arise if the specified resource path exists in several locations.\n     * If this parameter is <code>null</code>, the resource is looked up in the whole class path. In that case and if\n     * several resources with the specified path exist, the choice of the resource to return is arbitrary.\n     *\n     * @param path forward slash-separated path to the resource to look for, relative to the parent classpath\n     * location (directory or JAR file) that contains it.\n     * @param classLoader the ClassLoader used for locating the resource. May not be <code>null</code>.\n     * @param rootPackageFile root package location (JAR file or directory) that limits the scope of the search,\n     * <code>null</code> to look for the resource in the whole class path.\n     * @return an InputStream that allows to read the resource, or <code>null</code> if the resource couldn't be located\n     */\n    public static InputStream getResourceAsStream(String path, ClassLoader classLoader, AbstractFile rootPackageFile) {\n        try {\n            URL resourceURL = getResourceAsURL(path, classLoader, rootPackageFile);\n            return resourceURL == null ? null : resourceURL.openStream();\n        } catch(IOException e) {\n            return null;\n        }\n    }\n\n    /**\n     * This method is similar to {@link #getResourceAsFile(String)} except that it looks for a resource with a given\n     * name in a specific package.\n     *\n     * @param ppackage package serving as a base folder for the resource to retrieve\n     * @param name name of the resource in the package. This is a filename only, not a path.\n     * @return an AbstractFile that represents the resource, or <code>null</code> if the resource couldn't be located\n     */\n    public static AbstractFile getPackageResourceAsFile(Package ppackage, String name) {\n        return getPackageResourceAsFile(ppackage, name, getDefaultClassLoader(), null);\n    }\n\n    /**\n     * This method is similar to {@link #getResourceAsFile(String, ClassLoader, AbstractFile)} except that it looks for\n     * a resource with a given name in a specific package.\n     *\n     * @param ppackage package serving as a base folder for the resource to retrieve\n     * @param name name of the resource in the package. This is a filename only, not a path.\n     * @param classLoader the ClassLoader used for locating the resource. May not be <code>null</code>.\n     * @param rootPackageFile root package location (JAR file or directory) that limits the scope of the search,\n     * <code>null</code> to look for the resource in the whole class path.\n     * @return an AbstractFile that represents the resource, or <code>null</code> if the resource couldn't be located\n     */\n    public static AbstractFile getPackageResourceAsFile(Package ppackage, String name, ClassLoader classLoader, AbstractFile rootPackageFile) {\n        return ResourceLoader.getResourceAsFile(getRelativePackagePath(ppackage)+\"/\"+name, classLoader, rootPackageFile);\n    }\n\n    /**\n     * Shorthand for {@link #getResourceAsFile(String, ClassLoader, AbstractFile)} called with the\n     * {@link #getDefaultClassLoader() default class loader} and a <code>null</code> root package file.\n     *\n     * @param path forward slash-separated path to the resource to look for, relative to the parent classpath\n     * location (directory or JAR file) that contains it.\n     * @return an AbstractFile that represents the resource, or <code>null</code> if the resource couldn't be located\n     */\n    public static AbstractFile getResourceAsFile(String path) {\n        return getResourceAsFile(removeLeadingSlash(path), getDefaultClassLoader(), null);\n    }\n\n    /**\n     * Finds the resource with the given path and returns an {@link AbstractFile} that gives full access to it,\n     * or <code>null</code> if the resource couldn't be located. The given <code>ClassLoader</code> is used for locating\n     * the resource.\n     *\n     * <p>The given resource path must be forward slash (<code>/</code>) separated. It may or may not start with a \n     * leading forward slash character, this doesn't affect the way it is interpreted.\n     *\n     * <p>It is worth noting that this method may be slower than {@link #getResourceAsStream(String)} if\n     * the resource is located inside a JAR file, because the Zip file headers will have to be parsed the first time\n     * the archive is accessed. Therefore, the latter approach should be favored if the file is simply used for\n     * reading the resource.\n     *\n     * <p>The <code>rootPackageFile</code> argument can be used to limit the scope of the search to a specific\n     * location (JAR file or directory) in the classpath: resources located outside of this location will not be matched.\n     * This avoids potential ambiguities that can arise if the specified resource path exists in several locations.\n     * If this parameter is <code>null</code>, the resource is looked up in the whole class path. In that case and if\n     * several resources with the specified path exist, the choice of the resource to return is arbitrary.\n     *\n     * @param path forward slash-separated path to the resource to look for, relative to the parent classpath\n     * location (directory or JAR file) that contains it.\n     * @param classLoader the ClassLoader is used for locating the resource\n     * @param rootPackageFile root package location (JAR file or directory) that limits the scope of the search,\n     * <code>null</code> to look for the resource in the whole class path.\n     * @return an AbstractFile that represents the resource, or <code>null</code> if the resource couldn't be located\n     */\n    public static AbstractFile getResourceAsFile(String path, ClassLoader classLoader, AbstractFile rootPackageFile) {\n        if(classLoader==null)\n            classLoader = getDefaultClassLoader();\n\n        path = removeLeadingSlash(path);\n\n        URL aClassURL = getResourceAsURL(path, classLoader, rootPackageFile);\n        if(aClassURL==null)\n            return null;        // no resource under that path\n\n        if(\"jar\".equals(aClassURL.getProtocol())) {\n            try {\n                return ((AbstractArchiveFile)FileFactory.getFile(getJarFilePath(aClassURL))).getArchiveEntryFile(path);\n            }\n            catch(Exception e) {\n                // Shouldn't normally happen, unless the JAR file is corrupt or cannot be parsed by the file API\n                return null;\n            }\n        }\n\n        return FileFactory.getFile(getLocalFilePath(aClassURL));\n    }\n\n\n    /**\n     * Returns an {@link AbstractFile} to the root package of the given <code>Class</code>. For example, if the\n     * specified <code>Class</code> is <code>java.lang.Object</code>'s, the returned file will be the Java runtime\n     * JAR file, which on most platforms is <code>$JAVA_HOME/lib/jre/rt.jar</code>.<br>\n     * The returned file can be used to list or manipulate all resource files contained in a particular classpath's\n     * location, including the .class files.\n     *\n     * @param aClass the class for which to locate the root package.\n     * @return an AbstractFile to the root package of the given <code>Class</code>\n     */\n    public static AbstractFile getRootPackageAsFile(Class<?> aClass) {\n        ClassLoader classLoader = aClass.getClassLoader();\n        if(classLoader==null)\n            classLoader = getDefaultClassLoader();\n\n        String aClassRelPath = getRelativeClassPath(aClass);\n        URL aClassURL = getResourceAsURL(aClassRelPath, classLoader, null);\n\n        if(aClassURL==null)\n            return null;    // no resource under that path\n\n        if(\"jar\".equals(aClassURL.getProtocol()))\n            return FileFactory.getFile(getJarFilePath(aClassURL));\n\n        String aClassPath = getLocalFilePath(aClassURL);\n        return FileFactory.getFile(aClassPath.substring(0, aClassPath.length()-aClassRelPath.length()));\n    }\n\n    /**\n     * Returns a path to the given package. The returned path is relative, forward slash-separated and does not end\n     * with a trailing separator. For example, if the package <code>com.mucommander.commons.file</code> is passed, the returned\n     * path will be <code>com/mucommander/commons/file</code>.\n     *\n     * @param ppackage the package for which to return a path\n     * @return a path to the given package\n     */\n    public static String getRelativePackagePath(Package ppackage) {\n        return ppackage.getName().replace('.', '/');\n    }\n\n    /**\n     * Returns a path to the given class. The returned path is relative, forward slash-separated. For example, if the\n     * class <code>com.mucommander.commons.file.AbstractFile</code> is passed,  the returned path will be\n     * <code>com/mucommander/commons/file/AbstractFile.class</code>.\n     *\n     * @param cclass the class for which to return a path\n     * @return a path to the given package\n     */\n    public static String getRelativeClassPath(Class<?> cclass) {\n        return cclass.getName().replace('.', '/')+\".class\";\n    }\n\n\n    /////////////////////\n    // Private methods //\n    /////////////////////\n\n    /**\n     * Extracts and returns the path to the JAR file from a URL that points to a resource inside a JAR file.\n     * The returned path is in a format that {@link FileFactory} can turn into an {@link AbstractFile}.\n     *\n     * @param url a URL that points to a resource inside a JAR file\n     * @return returns the path to the JAR file\n     */\n    private static String getJarFilePath(URL url) {\n        // URL-decode the path\n        String path = getDecodedURLPath(url);\n\n        // Here are a couple examples of such paths:\n        // file:/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Classes/classes.jar!/java/lang/Object.class\n        // http://www.mucommander.com/webstart/nightly/mucommander.jar!/com/mucommander/RuntimeConstants.class\n\n        int pos = path.indexOf(\".jar!\");\n        if(pos==-1)\n            return path;\n\n        // Strip out the part after \".jar\" and normalize the path\n        return normalizeUrlPath(path.substring(0, pos+4));\n    }\n\n    /**\n     * Extracts and returns the path to a local file represented by the given URL.\n     * The returned path is in a format that {@link FileFactory} can turn into an {@link AbstractFile}.\n     *\n     * @param url a URL that points to a resource inside a JAR file\n     * @return returns the path to the JAR file\n     */\n    private static String getLocalFilePath(URL url) {\n        // Here's an example of such a path under Windows:\n        // /C:/cygwin/home/Administrator/mucommander/tmp/compile/classes/\n\n        // URL-decode the path and normalize it\n        return normalizeUrlPath(getDecodedURLPath(url));\n    }\n\n    /**\n     * Removes any leading slash from the given path and returns it. Does nothing if the path does not have a\n     * leading path.\n     *\n     * @param path the path to normalize\n     * @return the path without a leading slash\n     */\n    private static String removeLeadingSlash(String path) {\n        return PathUtils.removeLeadingSeparator(path, \"/\");\n    }\n\n    /**\n     * Normalizes the specified path issued from a <code>java.net.URL</code> and returns it.\n     * The returned path is in a format that {@link FileFactory} can turn into an {@link AbstractFile}.\n     *\n     * @param path the URL path to normalize\n     * @return the normalized path\n     */\n    private static String normalizeUrlPath(String path) {\n        // Don't touch http/https URLs\n        if(path.startsWith(\"http:\") || path.startsWith(\"https:\"))\n            return path;\n\n        // Remove the leading \"file:\" (if any)\n        if(path.startsWith(\"file:\"))\n            path = path.substring(5);\n\n        // Under platforms that use root drives (Windows and OS/2), strip out the leading '/'\n        if(LocalFile.hasRootDrives() && path.startsWith(\"/\"))\n            path = removeLeadingSlash(path);\n\n        // Use the local file separator\n        String separator = LocalFile.SEPARATOR;\n        if(!\"/\".equals(separator))\n            path = path.replace(\"/\", separator);\n\n        return path;\n    }\n\n    /**\n     * Returns the URL-decoded path of the given <code>java.net.URL</code>. The encoding used for URL-decoding is\n     * <code>UTF-8</code>.\n     *\n     * @param url the URL for which to decode the path\n     * @return the URL-decoded path of the given URL\n     */\n    private static String getDecodedURLPath(URL url) {\n        try {\n            // Decode the URL's path which may contain URL-encoded characters such as %20 for spaces, or non-ASCII\n            // characters.\n            // Note: the Java API's javadoc doesn't specify which encoding has been used to encoded URL paths.\n            // The only indication is in URLDecoder#decode(String, String) javadoc which says:\n            // \"The World Wide Web Consortium Recommendation states that UTF-8 should be used. Not doing so may\n            // introduce incompatibilites.\"\n            // Also Note that URLDecoder#decode(String) uses System.getProperty(\"file.encoding\") as the default encoding,\n            // using this value has been tested without luck under Mac OS X where the value equals \"MacRoman\" but\n            // URL are actually encoded in UTF-8. The bottom line is that we blindly use UTF-8 to decode resource URLs.\n            return URLDecoder.decode(url.getPath(), \"UTF-8\");\n        }\n        catch(UnsupportedEncodingException e) {\n            // This should never happen, UTF-8 is necessarily supported by the Java runtime\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/util/Shell32.java",
    "content": "package com.mucommander.commons.file.util;\n\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.sun.jna.Native;\nimport com.sun.jna.win32.W32APIOptions;\n\n/**\n * This class provides access to a static instance of the {@link com.mucommander.commons.file.util.Shell32API} interface,\n * allowing to invoke selected Shell32 Windows DLL functions.\n *\n * <p>The Kernel32 DLL and the JNA library (which is used to access native libraries) may not be available on\n * all OS/CPU architectures: {@link #isAvailable()} can be used to determine that at runtime.\n *\n * @see Shell32API\n * @author Maxence Bernard\n */\npublic class Shell32 {\n\n    /** An instance of the Shell32 DLL */\n    private static Shell32API INSTANCE;\n\n    static {\n        if (OsFamily.WINDOWS.isCurrent()) {        // Don't even bother if we're not running Windows\n            try {\n                INSTANCE = Native.load(\"shell32\", Shell32API.class, W32APIOptions.UNICODE_OPTIONS);\n            } catch(Throwable e) {\n                // java.lang.UnsatisfiedLinkError is thrown if the CPU architecture is not supported by JNA.\n                INSTANCE = null;\n            }\n        }\n    }\n\n    /**\n     * Returns <code>true</code> if the Shell32 API can be accessed on the current OS/CPU architecture.\n     *\n     * @return <code>true</code> if the Shell32 API can be accessed on the current OS/CPU architecture\n     */\n    public static boolean isAvailable() {\n        return INSTANCE != null;\n    }\n\n    /**\n     * Returns a static instance of the {@link com.mucommander.commons.file.util.Shell32API} interface, allowing to invoke\n     * some Shell32 Windows DLL functions. <code>null</code> will be returned if {@link #isAvailable()} returned\n     * <code>false</code>.\n     *\n     * @return a static instance of the {@link com.mucommander.commons.file.util.Shell32API} interface, <code>null</code> if it\n     * is not available on the current OS/CPU architecture\n     */\n    public static Shell32API getInstance() {\n        return INSTANCE;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/util/Shell32API.java",
    "content": "package com.mucommander.commons.file.util;\n\nimport com.mucommander.commons.runtime.JavaVersion;\nimport com.sun.jna.Pointer;\nimport com.sun.jna.Structure;\nimport com.sun.jna.platform.win32.WinNT;\nimport com.sun.jna.win32.StdCallLibrary;\n\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * Exposes parts of the Windows Shell32 API using the JNA (Java Native Access) library.\n * The {@link Shell32} class should be used to retrieve an instance of this interface.\n *\n * @see Shell32\n * @author Maxence Bernard\n */\npublic interface Shell32API extends StdCallLibrary {\n    //\n    // Note that the C header \"shellapi.h\" includes \"pshpack1.h\", which disables automatic alignment of structure fields.\n    //\n\n    /** Custom alignment of structures. */\n    int STRUCTURE_ALIGNMENT = JavaVersion.isAmd64Architecture() ? Structure.ALIGN_DEFAULT : Structure.ALIGN_NONE;\n\n\n\n    // Allowed wFunc values\n\n    /** Copies the files specified in the pFrom member to the location specified in the pTo member. */\n    int FO_MOVE = 1;\n    /** Copies the files specified in the pFrom member to the location specified in the pTo member. */\n    int FO_COPY = 2;\n    /** Deletes the files specified in pFrom. */\n    int FO_DELETE = 3;\n    /** Renames the file specified in pFrom. You cannot use this flag to rename multiple files with a single\n     * function call. Use FO_MOVE instead. */\n    int FO_RENAME = 4;\n\n    // Allowed fFlags values\n\n    /** Not supported. */\n    int FOF_MULTIDESTFILES = 1;\n    /** Not supported. */\n    int FOF_CONFIRMMOUSE = 2;\n    /** Do not display a progress dialog box. */\n    int FOF_SILENT = 4;\n    /** Give the file being operated on a new name in a move, copy, or rename operation if a file with the target\n     *  name already exists. */\n    int FOF_RENAMEONCOLLISION = 8;\n    /** Respond with \"Yes to All\" for any dialog box that is displayed. */\n    int FOF_NOCONFIRMATION = 16;\n    /** Not supported. */\n    int FOF_WANTMAPPINGHANDLE = 32;\n    /** Preserve Undo information, if possible. If pFrom does not contain fully qualified path and file names, this\n     *  flag is ignored. */\n    int FOF_ALLOWUNDO = 64;\n    /** Not supported. */\n    int FOF_FILESONLY = 128;\n    /** Display a progress dialog box but do not show the file names. */\n    int FOF_SIMPLEPROGRESS = 256;\n    /** Do not confirm the creation of a new directory if the operation requires one to be created. */\n    int FOF_NOCONFIRMMKDIR = 512;\n    /** Do not display a user interface if an error occurs. */\n    int FOF_NOERRORUI = 1024;\n    /** Not supported. */\n    int FOF_NOCOPYSECURITYATTRIBS = 2048;\n\n    /**\n     * This structure contains information that the SHFileOperation function uses to perform file operations.\n     */\n    class SHFILEOPSTRUCT extends Structure {\n\n        /** Window handle to the dialog box to display information about the status of the file operation. */\n        public WinNT.HANDLE hwnd;\n        /** Value that indicates which operation to perform. The following values are accepted:\n         *  FO_COPY, FO_DELETE, FO_MOVE or FO_RENAME */\n        public int wFunc;\n        /** Specifies one or more source file names. These names must be fully qualified paths. Standard\n         *  Microsoft MS-DOS wildcards, such as \"*\", are permitted in the file name position. Although this member\n         *  is declared as a null-terminated string, it is used as a buffer to hold multiple file names. Each file\n         *  name must be terminated by a single NULL character. An additional NULL character must be appended to the\n         *  end of the final name to indicate the end of pFrom. */\n        public String pFrom;\n        /** Contain the name of the destination file or directory. This parameter must be set to NULL if it is not\n         * used. Like pFrom, the pTo member is also a double-null terminated string and is handled in much the same\n         * way. */\n        public String pTo;\n        /** Flags that control the file operation (see constant fields for allowed values). */\n        public short fFlags;\n        /** Not supported. */\n        public boolean fAnyOperationsAborted;\n        /** Not supported. */\n        public Pointer pNameMappings;\n        /** String to use as the title of a progress dialog box. This member is used only if fFlags includes the\n         * FOF_SIMPLEPROGRESS flag. */\n        public String lpszProgressTitle;\n\n        /**\n         * Encodes <code>pFrom/pTo</code> paths, terminating them with NUL characters as required.\n         *\n         * @param paths a list of paths to encode\n         * @return the encoded path\n         */\n        public String encodePaths(String[] paths) {\n            StringBuilder encodedPaths = new StringBuilder();\n            for (String path : paths) {\n                encodedPaths.append(path);\n                encodedPaths.append('\\0');\n            }\n            encodedPaths.append('\\0');\n\n            return encodedPaths.toString();\n        }\n\n        protected List<String> getFieldOrder() {\n            return Arrays.asList(\"hwnd\", \"wFunc\", \"pFrom\", \"pTo\", \"fFlags\", \"fAnyOperationsAborted\", \"pNameMappings\", \"lpszProgressTitle\");\n        }\n    }\n\n    /**\n     * This function can be used to copy, move, rename, or delete a file system object.\n     *\n     * <p>Remarks: You should use fully qualified path names with this function. Using it with relative path names\n     * is not thread-safe.<br>\n     * When used to delete a file, SHFileOperation attempts to place the deleted file in the Recycle Bin. If you\n     * wish to delete a file and guarantee that it is not placed in the Recycle Bin, use the DeleteFile function.\n     *\n     * @param lpFileOp a SHFILEOPSTRUCT structure that contains information this function needs to carry out the\n     * specified operation.\n     * @return Returns zero if successful, or nonzero otherwise.\n     */\n    int SHFileOperation(SHFILEOPSTRUCT lpFileOp);\n\n\n    ////////////////////////////////\n    // SHEmptyRecycleBin function //\n    ////////////////////////////////\n\n    /** No dialog box confirming the deletion of the objects will be displayed. */\n    int SHERB_NOCONFIRMATION = 0x00000001;\n    /** No dialog box indicating the progress will be displayed. */\n    int SHERB_NOPROGRESSUI = 0x00000002;\n    /** No sound will be played when the operation is complete. */\n    int SHERB_NOSOUND = 0x00000004;\n\n    /**\n     * Empties the Recycle Bin on the specified drive.\n     *\n     * @param hwnd A handle to the parent window of any dialog boxes that might be displayed during the operation.\n     * This parameter can be NULL.\n     * @param pszRootPath a null-terminated string of maximum length MAX_PATH that contains the path of the root\n     * drive on which the Recycle Bin is located. This parameter can contain a string formatted with the drive,\n     * folder, and subfolder names, for example c:\\windows\\system\\, etc. It can also contain an empty string or\n     * NULL. If this value is an empty string or NULL, all Recycle Bins on all drives will be emptied.\n     * @param dwFlags a bitwise combination of SHERB_NOCONFIRMATION, SHERB_NOPROGRESSUI and SHERB_NOSOUND.\n     * @return Returns S_OK (0) if successful, or a COM-defined error value otherwise.\n     */\n    int SHEmptyRecycleBin(WinNT.HANDLE hwnd, String pszRootPath, int dwFlags);\n\n\n    ////////////////////////////////\n    // SHQueryRecycleBin function //\n    ////////////////////////////////\n\n    /**\n     * Contains the size and item count information retrieved by the SHQueryRecycleBin function.\n     */\n    class SHQUERYRBINFO extends Structure {\n\n        /** The size of the structure, in bytes. This member must be filled in prior to calling SHQueryRecycleBin. */\n        public int cbSize = 20;     // 1 DWORD + 2 DWORDLONG = 4 + 2*8 = 20 bytes\n        /** The total size of all the objects in the specified Recycle Bin, in bytes. */\n        public long i64Size;\n        /** The total number of items in the specified Recycle Bin. */\n        public long i64NumItems;\n\n        @Override\n        protected List<String> getFieldOrder() {\n            return Arrays.asList(\"cbSize\", \"i64Size\", \"i64NumItems\");\n        }\n    }\n\n    /**\n     * Retrieves the size of the Recycle Bin and the number of items in it, for a specified drive.\n     *\n     * <p>Remarks: With Microsoft Windows 2000, if NULL is passed in the pszRootPath parameter, the function fails\n     * and returns an E_INVALIDARG error code. In earlier versions of the operating system, you can pass an empty\n     * string or NULL. If pszRootPath contains an empty string or NULL, information is retrieved for all\n     * Recycle Bins on all drives.\n     *\n     * @param pszRootPath a null-terminated string of maximum length MAX_PATH to contain the path of the root drive\n     * on which the Recycle Bin is located. This parameter can contain a string formatted with the drive, folder,\n     * and subfolder names (C:\\Windows\\System...).\n     * @param pSHQueryRBInfo a SHQUERYRBINFO structure that receives the Recycle Bin information. The cbSize member\n     * of the structure must be set to the size of the structure before calling this API.\n     * @return Returns S_OK (0) if successful, or a COM-defined error value otherwise.\n     */\n    int SHQueryRecycleBin(String pszRootPath, SHQUERYRBINFO pSHQueryRBInfo);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/file/util/SymLinkUtils.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.commons.file.util;\n\nimport com.mucommander.commons.file.AbstractFile;\n\nimport java.io.IOException;\nimport java.nio.file.FileSystems;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\n/**\n * @author Oleg Trifonov\n * Created on 10/11/14.\n */\npublic class SymLinkUtils {\n\n    /**\n     * Returns symbolic link target value\n     * @param symLink symbolic link path\n     * @return symbolic link target path\n     */\n    public static String getTargetPath(AbstractFile symLink) {\n        Path path = FileSystems.getDefault().getPath(symLink.getAbsolutePath(), \"\");\n        if (!Files.isSymbolicLink(path)) {\n            return symLink.getAbsolutePath();\n        }\n        try {\n            Path linkTargetPath = Files.readSymbolicLink(path);\n            return linkTargetPath.toString();\n        } catch (IOException e) {\n            e.printStackTrace();\n            return symLink.getAbsolutePath();\n        }\n    }\n\n    public static boolean createSymlink(AbstractFile symLink, String target) {\n        Path linkPath = FileSystems.getDefault().getPath(symLink.getAbsolutePath(), \"\");\n        Path targetPath = FileSystems.getDefault().getPath(target, \"\");\n        try {\n            Files.createSymbolicLink(linkPath, targetPath);\n            return true;\n        } catch (IOException e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     *\n     * @param symLink symlink path\n     * @param target target file/directory path\n     * @throws IOException if an I/O error occurs.\n     *      java.nio.file.AccessDeniedException\n     *      java.nio.file.FileAlreadyExistsException\n     */\n    public static void createSymlink(String symLink, String target) throws IOException {\n        Path linkPath = FileSystems.getDefault().getPath(symLink, \"\");\n        Path targetPath = FileSystems.getDefault().getPath(target, \"\");\n        Files.createSymbolicLink(linkPath, targetPath);\n    }\n\n    public static boolean editSymlink(AbstractFile symLink, String target) {\n        Path linkPath = FileSystems.getDefault().getPath(symLink.getAbsolutePath(), \"\");\n        Path targetPath = FileSystems.getDefault().getPath(target, \"\");\n        try {\n            Files.delete(linkPath);\n            Files.createSymbolicLink(linkPath, targetPath);\n            return true;\n        } catch (IOException e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/BinaryDetector.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport com.mucommander.commons.io.bom.BOM;\nimport com.mucommander.commons.io.bom.BOMConstants;\nimport com.mucommander.commons.io.bom.BOMInputStream;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.PushbackInputStream;\n\n/**\n * This class provides methods to determine whether some data is binary data or text data.\n * As there is no formal characterization of what binary data really is, this method is an approximation at best\n * and should not be trusted for anything critical.\n *\n * <p>The {@link #RECOMMENDED_BYTE_SIZE} field indicates how many bytes should be provided for the detector to be\n * confident enough.\n *\n * @see com.mucommander.commons.io.EncodingDetector\n * @author Maxence Bernard\n */\npublic class BinaryDetector {\n\n    /** Provides an indication as to the number of bytes that should fed to the detector for it to have enough\n     * confidence. */\n    private final static int RECOMMENDED_BYTE_SIZE = 1024*16;\n\n\n    /**\n     * This method is a shorthand for {@link #guessBinary(byte[], int, int) guessBinary(b, 0, b.length)}.\n     *\n     * @param b the data to analyze\n     * @return true if BinaryDetector thinks that the specified data is binary\n     */\n    public static boolean guessBinary(byte b[]) {\n        return guessBinary(b, 0, b.length);\n    }\n\n    /**\n     * Tries and detect whether the given bytes correspond to binary or text data. The specified bytes can typically\n     * be the beginning of a file.</br>\n     * This method returns <code>true</code> if it thinks that the bytes correspond to binary data.\n     *\n     * @param b the data to analyze\n     * @param off specifies where to start reading the array\n     * @param len specifies where to stop reading the array\n     * @return true if BinaryDetector thinks that the specified data is binary\n     */\n    private static boolean guessBinary(byte b[], int off, int len) {\n        // binary .torrent files etc. doesn't contains any 0x0A, 0x0D or 0x00 bytes\n        int x0Acnt = 0;\n        int x0Dcnt = 0;\n        boolean containsZero = false;\n        for (int i = 0; i < len; i++) {\n            byte v = b[i+off];\n            if (v == 0x0A) {\n                x0Acnt++;\n                if (x0Acnt > 16) {\n                    break;\n                }\n            } else if (v == 0x0D) {\n                x0Dcnt++;\n                if (x0Dcnt > 16) {\n                    break;\n                }\n            } else if (v == 0) {\n                containsZero = true;\n            }\n        }\n        if (x0Acnt == 0 && x0Dcnt == 0 && len > 1024*32) {\n            return true;\n        }\n        try {\n            // Returns true if any of the bytes are the NUL character. The NUL character is usually not found in a text\n            // file, except for UTF-16 and UTF-32 streams.\n            // So first, we try and look for a BOM (byte-order mark) to see if the stream is UTF-16 or UTF-32 encoded.\n            BOMInputStream bin = new BOMInputStream(new ByteArrayInputStream(b, off, len));\n            BOM bom = bin.getBOM();\n            if (bom != null) {\n                if (bom.equals(BOMConstants.UTF16_BE_BOM) || bom.equals(BOMConstants.UTF16_LE_BOM)\n                || bom.equals(BOMConstants.UTF32_BE_BOM) || bom.equals(BOMConstants.UTF32_LE_BOM)) {\n                    return false;\n                }\n            }\n        } catch (IOException e) {\n            // Can never happen in practice with a ByteArrayInputStream.\n        }\n        // No BOM, start looking for zeros\n        return containsZero;\n    }\n\n    /**\n     * Tries and detect whether the given stream contains binary or text data.<br>\n     * This method returns <code>true</code> if it thinks that the bytes correspond to binary data.\n     *\n     * <p>A maximum of {@link #RECOMMENDED_BYTE_SIZE} will be read from the <code>InputStream</code>. The\n     * stream will not be closed and will not be repositioned after the bytes have been read. It is up to the calling\n     * method to use the <code>InputStream#mark()</code> and <code>InputStream#reset()</code> methods (if supported)\n     * or reopen the stream if needed.\n     *\n     * @param in the stream to analyze\n     * @return true if BinaryDetector thinks that the specified data is binary\n     * @throws IOException if an error occurred while reading the InputStream.\n     */\n    public static boolean guessBinary(InputStream in) throws IOException {\n        byte[] bytes = new byte[RECOMMENDED_BYTE_SIZE];\n        return guessBinary(bytes, 0, StreamUtils.readUpTo(in, bytes));\n    }\n\n    public static boolean guessBinary(PushbackInputStream in) throws IOException {\n        byte[] bytes = new byte[RECOMMENDED_BYTE_SIZE];\n        int read = StreamUtils.readUpTo(in, bytes);\n        boolean result = guessBinary(bytes, 0, read);\n        in.unread(bytes, 0, read);\n        return result;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/BlockRandomInputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.IOException;\n\n/**\n * <code>BlockRandomInputStream</code> is a specialized-yet-still-abstract <code>RandomAccessInputStream</code> that\n * is geared towards resources that are read block by block, either because of a particular constrain or for performance\n * reasons. This class typically comes in handy for network resources such as HTTP which have to request a block range\n * for reading the resource.\n *\n * <p>Seeking inside the file is implemented transparently by reading a block starting at the seek offset.\n * If {@link #seek(long)} is called with an offset that is within the current block, no read occurs.\n * The block size should be carefully chosen as it affects seek performance and thus overall performance greatly:\n * the larger the block size, the more data is fetched when seeking outside the current block and consequently the\n * longer it takes to reposition the stream. On the other hand, a larger block size will yield better performance when\n * reading the resource sequentially, as it lessens the overhead of requesting a particular block.\n *\n * @author Maxence Bernard\n */\npublic abstract class BlockRandomInputStream extends RandomAccessInputStream {\n\n    /** Block size, i.e. length of the {@link #block} array */\n    protected final int blockSize;\n\n    /** Contains the current file block. Data may end before the array does. */\n    private final byte block[];\n\n    /** Current offset within the block array to the next byte to return */\n    private int blockOff;\n\n    /** Length of the current block */\n    private int blockLen;\n\n    /** Global offset within the file */\n    private long offset;\n\n\n    /**\n     * Creates a new <code>BlockRandomInputStream</code> using the specified block size.\n     *\n     * <p>The block size should be carefully chosen as it affects seek performance and thus overall performance greatly:\n     * the larger the block size, the more data is fetched when seeking outside the current block and consequently the\n     * longer it takes to reposition the stream. On the other hand, a larger block size will yield better performance\n     * when reading the resource sequentially, as it lessens the overhead of requesting a particular block.\n     *\n     * @param blockSize controls the amount of data requested when reading a block\n     */\n    protected BlockRandomInputStream(int blockSize) {\n        this.blockSize = blockSize;\n        block = new byte[blockSize];\n    }\n\n    /**\n     * Returns <code>true</code> if the end of file has been reached.\n     *\n     * @return true if the end of file has been reached.\n     * @throws IOException if an I/O error occurred\n     */\n    private boolean eofReached() throws IOException {\n        return offset>=getLength();\n    }\n\n    /**\n     * Checks if the current buffered block has been read completely (i.e. no more data is available) and if it has,\n     * calls {@link #readBlock(long, byte[], int)} to fetch the next block.\n     *\n     * @throws IOException if an I/O error occurred\n     */\n    private void checkBuffer() throws IOException {\n        if(blockOff >= blockLen)      // True initially\n            readBlock();\n    }\n\n    /**\n     * Calls {@link #readBlock(long, byte[], int)} to read a block of up to <code>blockSize</code>, less if the\n     * the end of file is near.\n     *\n     * @throws IOException if an I/O error occurred\n     */\n    private void readBlock() throws IOException {\n        int len = Math.min((int)(getLength()-offset), blockSize);\n        // update len with the number of bytes actually read\n        len = readBlock(offset, block, len);\n\n        // Note: these fields won't be updated if an I/O error occurs\n        this.blockOff = 0;\n        this.blockLen = len;\n    }\n\n\n    ////////////////////////////////////////////\n    // RandomAccessInputStream implementation //\n    ////////////////////////////////////////////\n\n    @Override\n    public int read() throws IOException {\n        if(eofReached())\n            return -1;\n\n        checkBuffer();\n\n        int ret = block[blockOff];\n\n        blockOff++;\n        offset ++;\n\n        return ret;\n    }\n\n    @Override\n    public int read(byte b[], int off, int len) throws IOException {\n        if(len==0)\n            return 0;\n\n        if(eofReached())\n            return -1;\n\n        checkBuffer();\n\n        int nbBytes = Math.min(len, blockLen - blockOff);\n        System.arraycopy(block, blockOff, b, off, nbBytes);\n\n        blockOff += nbBytes;\n        offset += nbBytes;\n\n        return nbBytes;\n    }\n\n    public long getOffset() {\n        return offset;\n    }\n\n    public void seek(long newOffset) throws IOException {\n        // If the new offset is within the current buffer's range, simply reposition the offsets\n        if(newOffset>=offset && newOffset<offset+ blockLen) {\n            blockOff += (int)(newOffset-offset);\n            offset = newOffset;\n        }\n        // If not, retrieve a block of data starting at the new offset and fill the buffer with it\n        else {\n            offset = newOffset;\n            readBlock();\n        }\n    }\n\n\n    ///////////////////////\n    // Abstract methods //\n    ///////////////////////\n\n    /**\n     * Reads a block, that spawns from <code>fileOffset</code> to <code>fileOffset+blockLen</code>, an returns\n     * the number of bytes that could be read, normally <code>blockLen</code> but can be less.\n     *\n     * <p>Note that <code>blockLen</code> may be smaller than {@link #blockSize} if the end of file is near, to prevent\n     * <code>EOF</code> from being reached. In other words, <code>fileOffset+blockLen</code> should theoretically not\n     * exceed the file's length, but this could happen in the unlikely event that the file just shrunk after\n     * {@link #getLength()} was last called. So this method's implementation should handle the case where\n     * <code>EOF</code> is reached prematurely and return the number of bytes that were actually read.\n     *\n     * @param fileOffset global file offset that marks the beginning of the block\n     * @param block the array to fill with data, starting at 0\n     * @param blockLen number of bytes to read\n     * @return the number of bytes that were actually read, normally blockLen unless\n     * @throws IOException if an I/O error occurred\n     */\n    protected abstract int readBlock(long fileOffset, byte block[], int blockLen) throws IOException;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/Bounded.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\n/**\n * This interface defines methods that are common to bounded streams, whether they be input streams or output streams.\n *\n * @author Maxence Bernard\n * @see BoundedInputStream\n * @see BoundedOutputStream\n */\npublic interface Bounded {\n\n    /**\n     * Returns the total number of bytes that are allowed to be processed (read or written) by the stream,\n     * <code>-1</code> if the stream is not bounded.\n     *\n     * @return the total number of bytes that are allowed to be processed (read or written) by the stream,\n     * <code>-1</code> if the stream is not bounded.\n     */\n    long getAllowedBytes();\n\n    /**\n     * Returns the total number of bytes that have been processed (read or written) by the stream thus far.\n     *\n     * @return the total number of bytes that have been processed (read or written) by the stream thus far.\n     */\n    long getProcessedBytes();\n\n    /**\n     * Returns the remaining number of bytes that are allowed to be processed (read or written) by the stream,\n     * {@link Long#MAX_VALUE} if this stream is not bounded.\n     *\n     * @return the remaining number of bytes that are allowed to be processed (read or written) by the stream,\n     * {@link Long#MAX_VALUE} if this stream is not bounded.\n     */\n    long getRemainingBytes();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/BoundedInputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.FilterInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * <code>BoundedInputStream</code> is an InputStream that has a set limit to the number of bytes that can be read or\n * skipped from it. What happens when the limit is reached is controlled at creation time: <code>read</code> and\n * <code>skip</code> methods can either throw a {@link StreamOutOfBoundException} or simply return <code>-1</code>.\n *\n * <p>The limit has no effect if it is set to a value that is higher than the number of bytes remaining in the\n * underlying stream.\n *\n * <p>This class is particularly useful for reading archives that are a concatenation of files, tarballs for instance.\n *\n * @author Maxence Bernard\n * @see BoundedReader\n * @see BoundedOutputStream\n * @see StreamOutOfBoundException\n */\npublic class BoundedInputStream extends FilterInputStream implements Bounded {\n\n    private long totalRead;\n    private long allowedBytes;\n    private boolean throwStreamOutOfBoundException;\n\n    /**\n     * Creates a new <code>BoundedInputStream</code> over the specified stream, allowing a maximum of\n     * <code>allowedBytes</code> to be read or skipped. If <code>allowedBytes</code> is equal to <code>-1</code>, this\n     * stream is not bounded and acts as a normal stream.\n     *\n     * <p>If the <code>throwStreamOutOfBoundException</code> parameter is <code>true</code>, <code>read</code> and\n     * <code>skip</code> methods will throw a {@link StreamOutOfBoundException} when an attempt to read or skip beyond\n     * that limit is made. If <code>false</code>, <code>-1</code> will be returned.\n     *\n     * @param in the stream to be bounded\n     * @param allowedBytes the total number of bytes that are allowed to be read or skipped, <code>-1</code> for no limit\n     * @param throwStreamOutOfBoundException <code>true</code> to throw when an attempt to read or skip beyond the byte\n     * limit is made, <code>false</code> to simply return <code>-1</code>\n     */\n    public BoundedInputStream(InputStream in, long allowedBytes, boolean throwStreamOutOfBoundException) {\n        super(in);\n\n        this.allowedBytes = allowedBytes;\n        this.throwStreamOutOfBoundException = throwStreamOutOfBoundException;\n    }\n\n    /**\n     * Called when an attempt to read out of the stream's bound has been made. This method will either throw a\n     * {@link StreamOutOfBoundException} or return <code>-1</code>, depending on how the <code>BoundedInputStream</code>\n     * was created.\n     *\n     * @return -1 if this BoundedInputStream was configured not to throw a StreamOutOfBoundException\n     * @throws StreamOutOfBoundException if this BoundedInputStream was configured to throw a StreamOutOfBoundException  \n     */\n    protected int handleStreamOutOfBound() throws StreamOutOfBoundException {\n        if(throwStreamOutOfBoundException)\n            throw new StreamOutOfBoundException(allowedBytes);\n\n        return -1;\n    }\n\n\n    ////////////////////////////\n    // Bounded implementation //\n    ////////////////////////////\n\n    public long getAllowedBytes() {\n        return allowedBytes;\n    }\n\n    public synchronized long getProcessedBytes() {\n        return totalRead;\n    }\n\n    public synchronized long getRemainingBytes() {\n        return allowedBytes<=-1?Long.MAX_VALUE:allowedBytes-totalRead;\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    @Override\n    public synchronized int read() throws IOException {\n        if(getRemainingBytes()==0)\n            return handleStreamOutOfBound();\n\n        int i = in.read();\n        totalRead++;\n\n        return i;\n    }\n\n    @Override\n    public int read(byte b[]) throws IOException {\n        return read(b, 0, b.length);\n    }\n\n    @Override\n    public synchronized int read(byte b[], int off, int len) throws IOException {\n        int canRead = (int)Math.min(getRemainingBytes(), len);\n        if(canRead==0)\n            return handleStreamOutOfBound();\n\n        int nbRead = in.read(b, off, canRead);\n        if(nbRead>0)\n            totalRead += nbRead;\n\n        return nbRead;\n    }\n\n    @Override\n    public synchronized long skip(long n) throws IOException {\n        int canSkip = (int)Math.min(getRemainingBytes(), n);\n        if(canSkip==0)\n            return handleStreamOutOfBound();\n\n        long nbSkipped = in.skip(canSkip);\n        if(nbSkipped>0)\n            totalRead += nbSkipped;\n\n        return nbSkipped;\n    }\n\n    @Override\n    public synchronized int available() throws IOException {\n        return Math.min(in.available(), (int)getRemainingBytes());\n    }\n\n    // Methods not implemented\n\n    /**\n     * Always returns <code>false</code>, even if the underlying stream supports it.\n     *\n     * @return always returns <code>false</code>, even if the underlying stream supports it\n     */\n    @Override\n    public boolean markSupported() {\n        // Todo: in theory we could support mark/reset\n        return false;\n    }\n\n    /**\n     * Implemented as a no-op: the call is *not* delegated to the underlying stream.\n     */\n    @Override\n    public synchronized void mark(int readlimit) {\n        // Todo: in theory we could support mark/reset\n        // No-op\n    }\n\n    /**\n     * Always throws an <code>IOException</code>: the call is *not* delegated to the underlying stream.\n     */\n    @Override\n    public synchronized void reset() {\n        // Todo: in theory we could support mark/reset\n        // No-op\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/BoundedOutputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * <code>BoundedOutputStream</code> is an <code>OutputStream</code> that has a set limit to the number of bytes that can\n * be written to it. When that limit is reached, <code>write</code> methods throw a {@link StreamOutOfBoundException}.\n *\n * @author Maxence Bernard\n * @see BoundedInputStream\n * @see StreamOutOfBoundException\n */\npublic class BoundedOutputStream extends FilteredOutputStream implements Bounded {\n\n    protected long totalWritten;\n    protected long allowedBytes;\n\n    /**\n     * Creates a new <code>BoundedInputStream</code> over the specified stream, allowing a maximum of\n     * <code>allowedBytes</code> to be written to it. If <code>allowedBytes</code> is equal to <code>-1</code>, this\n     * stream is not bounded and acts as a normal stream.\n     *\n     * @param out the stream to be bounded\n     * @param allowedBytes the total number of bytes that are allowed to written, <code>-1</code> for no limit\n     */\n    public BoundedOutputStream(OutputStream out, long allowedBytes) {\n        super(out);\n\n        this.allowedBytes = allowedBytes;\n    }\n\n\n    ////////////////////////////\n    // Bounded implementation //\n    ////////////////////////////\n\n    public synchronized long getAllowedBytes() {\n        return allowedBytes;\n    }\n\n    public synchronized long getProcessedBytes() {\n        return totalWritten;\n    }\n\n    public synchronized long getRemainingBytes() {\n        return allowedBytes<=-1?Long.MAX_VALUE:allowedBytes-totalWritten;\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    @Override\n    public synchronized void write(int b) throws IOException {\n        if(getRemainingBytes()==0)\n            throw new StreamOutOfBoundException(allowedBytes);\n\n        out.write(b);\n        totalWritten++;\n    }\n\n    @Override\n    public synchronized void write(byte[] b) throws IOException {\n        write(b, 0, b.length);\n    }\n\n    @Override\n    public synchronized void write(byte[] b, int off, int len) throws IOException {\n        int canWrite = (int)Math.min(getRemainingBytes(), len);\n        if(canWrite==0)\n            throw new StreamOutOfBoundException(allowedBytes);\n\n        out.write(b, off, canWrite);\n        totalWritten += canWrite;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/BoundedReader.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.FilterReader;\nimport java.io.IOException;\nimport java.io.Reader;\n\n/**\n * A <code>Reader</code> that has a set limit to the number of characters that can be read from it before the EOF\n * is reached. The limit has no effect if it is set higher than the number of characters remaining in the\n * underlying reader.\n *\n * @author Maxence Bernard\n * @see StreamOutOfBoundException\n */\npublic class BoundedReader extends FilterReader {\n\n    private long totalRead;\n    private long allowedCharacters;\n    private IOException outOfBoundException;\n\n    /**\n     * Equivalent to {@link #BoundedReader(java.io.Reader, long, java.io.IOException)} called with a\n     * <code>null</code> <code>IOException</code>.\n     *\n     * @param reader the reader to limit\n     * @param allowedCharacters the total number of characters this reader allows to be read or skipped, <code>-1</code>\n     * for no limitation\n     */\n    public BoundedReader(Reader reader, long allowedCharacters) {\n        this(reader, allowedCharacters, null);\n    }\n\n    /**\n     * Creates a new <code>BounderReader</code> over the specified reader, allowing a maximum of\n     * <code>allowedCharacters</code> to be read or skipped. If <code>allowedCharacters</code> is equal to <code>-1</code>,\n     * this reader is not bounded and acts as a normal stream.\n     * <p>\n     * The specified <code>IOException</code> will be thrown when an attempt to read or skip beyond that is made.\n     * If it is <code>null</code>, read and skip methods will return <code>-1</code> instead of throwing an\n     * <code>IOException</code>.\n     *\n     * @param reader the reader to bind\n     * @param allowedCharacters the total number of characters this reader allows to be read or skipped, <code>-1</code>\n     * for no limitation\n     * @param outOfBoundException the IOException to throw when an attempt to read or skip beyond <code>allowedBytes</code>\n     * is made, <code>null</code> to return -1 instead\n     * @see StreamOutOfBoundException\n     */\n    public BoundedReader(Reader reader, long allowedCharacters, IOException outOfBoundException) {\n        super(reader);\n\n        this.allowedCharacters = allowedCharacters;\n        this.outOfBoundException = outOfBoundException;\n    }\n\n\n    /**\n     * Returns the total number of characters that this reader allows to be read, <code>-1</code> is this reader is\n     * not bounded.\n     *\n     * @return the total number of characters that this reader allows to be read, <code>-1</code> is this reader is\n     * not bounded\n     */\n    public long getAllowedCharacters() {\n        return allowedCharacters;\n    }\n\n    /**\n     * Returns the total number of characters that have been read or skipped thus far.\n     *\n     * @return the total number of characters that have been read or skipped thus far\n     */\n    public synchronized long getReadCounter() {\n        return totalRead;\n    }\n\n    /**\n     * Returns the remaining number of characters that this reader allows to be read, {@link Long#MAX_VALUE} if this\n     * reader is not bounded.\n     *\n     * @return the remaining number of characters that this reader allows to be read, {@link Long#MAX_VALUE} if this\n     * reader is not bounded.\n     */\n    public synchronized long getRemainingCharacters() {\n        return allowedCharacters<=-1 ? Long.MAX_VALUE : allowedCharacters-totalRead;\n    }\n\n\n    ///////////////////////////\n    // Reader implementation //\n    ///////////////////////////\n\n    @Override\n    public synchronized int read(char[] cbuf, int off, int len) throws IOException {\n        int canRead = (int)Math.min(getRemainingCharacters(), len);\n        if(canRead==0) {\n            if(outOfBoundException==null)\n                return -1;\n\n            throw outOfBoundException;\n        }\n\n        int nbRead = in.read(cbuf, off, canRead);\n        if(nbRead>0)\n            totalRead += nbRead;\n\n        return nbRead;\n\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    @Override\n    public synchronized int read() throws IOException {\n        if(getRemainingCharacters()==0) {\n            if(outOfBoundException==null)\n                return -1;\n\n            throw outOfBoundException;\n        }\n\n        int i = in.read();\n        totalRead++;\n\n        return i;\n    }\n\n    @Override\n    public synchronized long skip(long n) throws IOException {\n        int canSkip = (int)Math.min(getRemainingCharacters(), n);\n        if(canSkip==0) {\n            if(outOfBoundException==null)\n                return -1;\n\n            throw outOfBoundException;\n        }\n\n        long nbSkipped = in.skip(canSkip);\n        if(nbSkipped>0)\n            totalRead += nbSkipped;\n\n        return nbSkipped;\n    }\n\n    /**\n     * Always returns <code>false</code>, even if the underlying reader supports it.\n     *\n     * @return always returns <code>false</code>, even if the underlying reader supports it\n     */\n    @Override\n    public boolean markSupported() {\n        // Todo: in theory we could support mark/reset\n        return false;\n    }\n\n    /**\n     * Implemented as a no-op: the call is *not* delegated to the underlying reader.\n     */\n    @Override\n    public synchronized void mark(int readlimit) {\n        // Todo: in theory we could support mark/reset\n        // No-op\n    }\n\n    /**\n     * Always throws an <code>IOException</code>: the call is *not* delegated to the underlying reader.\n     */\n    @Override\n    public synchronized void reset() {\n        // Todo: in theory we could support mark/reset\n        // No-op\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/BufferPool.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.nio.ByteBuffer;\nimport java.nio.CharBuffer;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\n\n/**\n * This class allows to share and reuse byte buffers to avoid excessive memory allocation and garbage collection.\n * Methods that use byte buffers and that are called repeatedly will benefit from using this class.\n *\n * <p>This class works with two types of byte buffers indifferently:\n * <ul>\n *  <li>Byte array buffers (<code>byte[]</code>)</li>\n *  <li><code>java.nio.ByteBuffer</code></li>\n * </ul>\n *\n * <p>\n * Usage of this class is similar to malloc/free:\n * <ul>\n *  <li>Call <code>#get*Buffer(int)</code> to retrieve a buffer instance of a specified size</li>\n *  <li>Use the buffer</li>\n *  <li>When finished using the buffer, call <code>#release*Buffer(byte[])</code> to make this buffer available for\n * subsequent calls to <code>#get*Buffer(int)</code>. Failing to call this method will prevent the buffer from being\n * used again and from being garbage-collected.</li>\n * </ul>\n *\n * <p>Note: this class is thread safe and thus can safely be used by concurrent threads.\n *\n * @author Maxence Bernard, Nicolas Rinaudo\n * @see com.mucommander.commons.io.StreamUtils\n */\npublic class BufferPool {\n    /** Logger used by this class. */\n    private static final Logger LOGGER = LoggerFactory.getLogger(BufferPool.class);\n\n    /** List of BufferContainer instances that wraps available buffers */\n    private static final List<BufferContainer> bufferContainers = new ArrayList<>();\n\n    private static final ByteArrayFactory BYTE_ARRAY_FACTORY = new ByteArrayFactory();\n    private static final CharArrayFactory CHAR_ARRAY_FACTORY = new CharArrayFactory();\n\n    /** The initial default buffer size */\n    final static int INITIAL_DEFAULT_BUFFER_SIZE = 65536;\n\n    /** Size of buffers returned by get*Buffer methods without a size argument */\n    private static int defaultBufferSize = INITIAL_DEFAULT_BUFFER_SIZE;\n\n    /** The initial max pool size */\n    final static long INITIAL_POOL_LIMIT = 10485760;\n\n    /** Maximum combined size of all pooled buffers, in bytes */\n    private static long maxPoolSize = INITIAL_POOL_LIMIT;\n\n    /** Current combined size of all pooled buffers, in bytes */\n    private static long poolSize;\n\n\n\n    /**\n     * Convenience method that has the same effect as calling {@link #getByteArray(int)} with\n     * a length equal to {@link #getDefaultBufferSize()}.\n     *\n     * @return a byte array with a length of {@link #getDefaultBufferSize()}\n     */\n    public static synchronized byte[] getByteArray() {\n        return getByteArray(getDefaultBufferSize());\n    }\n\n    /**\n     * Returns a byte array of the specified length. This method first checks if a byte array of the specified length\n     * exists in the pool. If one is found, it is removed from the pool and returned. If not, a new instance is created\n     * and returned.\n     *\n     * <p>This method won't return the same buffer instance until it has been released with\n     * {@link #releaseByteArray(byte[])}.\n     *\n     * <p>This method is a shorthand for {@link #getBuffer(com.mucommander.commons.io.BufferPool.BufferFactory,int)} called\n     * with a {@link com.mucommander.commons.io.BufferPool.ByteArrayFactory} instance.\n     *\n     * @param length length of the byte array\n     * @return a byte array of the specified size\n     */\n    public static synchronized byte[] getByteArray(int length) {\n        return (byte[])getBuffer(BYTE_ARRAY_FACTORY, length);\n    }\n\n    /**\n     * Convenience method that has the same effect as calling {@link #getCharArray(int)} with\n     * a length equal to {@link #getDefaultBufferSize()}.\n     *\n     * @return a char array with a length of {@link #getDefaultBufferSize()}\n     */\n    public static synchronized char[] getCharArray() {\n        return getCharArray(getDefaultBufferSize());\n    }\n\n    /**\n     * Returns a char array of the specified length. This method first checks if a char array of the specified length\n     * exists in the pool. If one is found, it is removed from the pool and returned. If not, a new instance is created\n     * and returned.\n     *\n     * <p>This method won't return the same buffer instance until it has been released with\n     * {@link #releaseCharArray(char[])}.\n     *\n     * <p>This method is a shorthand for {@link #getBuffer(com.mucommander.commons.io.BufferPool.BufferFactory,int)} called\n     * with a {@link com.mucommander.commons.io.BufferPool.CharArrayFactory} instance.\n     *\n     * @param length length of the char array\n     * @return a char array of the specified length\n     */\n    public static synchronized char[] getCharArray(int length) {\n        return (char[])getBuffer(CHAR_ARRAY_FACTORY, length);\n    }\n\n    /**\n     * Convenience method that has the same effect as calling {@link #getByteBuffer(int)} with\n     * a buffer capacity of {@link #getDefaultBufferSize()}.\n     *\n     * @return a ByteBuffer with a capacity equal to {@link #getDefaultBufferSize()}\n     */\n    public static synchronized ByteBuffer getByteBuffer() {\n        return getByteBuffer(getDefaultBufferSize());\n    }\n\n    /**\n     * Returns a ByteBuffer of the specified capacity. This method first checks if a ByteBuffer instance of the\n     * specified capacity exists in the pool. If one is found, it is removed from the pool and returned. If not,\n     * a new instance is created and returned.\n     *\n     * <p>This method won't return the same buffer instance until it has been released with\n     * {@link #releaseByteBuffer(ByteBuffer)}.\n     *\n     * <p>This method is a shorthand for {@link #getBuffer(com.mucommander.commons.io.BufferPool.BufferFactory,int)} called\n     * with a {@link com.mucommander.commons.io.BufferPool.ByteBufferFactory} instance.\n\n     * @param capacity capacity of the ByteBuffer\n     * @return a ByteBuffer with the specified capacity\n     */\n    public static synchronized ByteBuffer getByteBuffer(int capacity) {\n        return (ByteBuffer)getBuffer(new ByteBufferFactory(), capacity);\n    }\n\n\n    /**\n     * Convenience method that has the same effect as calling {@link #getCharBuffer(int)} with\n     * a buffer capacity of {@link #getDefaultBufferSize()}.\n     *\n     * @return a CharBuffer with a capacity equal to {@link #getDefaultBufferSize()}\n     */\n    public static synchronized CharBuffer getCharBuffer() {\n        return getCharBuffer(getDefaultBufferSize());\n    }\n\n    /**\n     * Returns a CharBuffer of the specified capacity. This method first checks if a CharBuffer instance of the\n     * specified capacity exists in the pool. If one is found, it is removed from the pool and returned. If not,\n     * a new instance is created and returned.\n     *\n     * <p>This method won't return the same buffer instance until it has been released with\n     * {@link #releaseCharBuffer(CharBuffer)}.\n     *\n     * <p>This method is a shorthand for {@link #getBuffer(com.mucommander.commons.io.BufferPool.BufferFactory,int)} called\n     * with a {@link com.mucommander.commons.io.BufferPool.CharBufferFactory} instance.\n\n     * @param capacity capacity of the CharBuffer\n     * @return a CharBuffer with the specified capacity\n     */\n    public static synchronized CharBuffer getCharBuffer(int capacity) {\n        return (CharBuffer)getBuffer(new CharBufferFactory(), capacity);\n    }\n\n\n    /**\n     * Convenience method that has the same effect as calling {@link #getBuffer(com.mucommander.commons.io.BufferPool.BufferFactory, int)}\n     * with a size equal to {@link #getDefaultBufferSize()}.\n     *\n     * @param factory BufferFactory used to identify the target buffer class and create a new buffer (if necessary)\n     * @return a buffer with a size equal to {@link #getDefaultBufferSize()}\n     */\n    public static synchronized Object getBuffer(BufferFactory factory) {\n        return getBuffer(factory, getDefaultBufferSize());\n    }\n\n    /**\n     * Returns a byte array of the specified size. This method first checks if a buffer the same size as the specified\n     * one and a class compatible with the specified factory exists in the pool. If one is found, it is removed from the\n     * pool and returned.\n     * If not, a new instance is created and returned using {@link BufferFactory#newBuffer(int)}.\n     *\n     * <p>This method won't return the same buffer instance until it has been released with\n     * {@link #releaseBuffer(Object, BufferFactory)}.\n     *\n     * @param factory BufferFactory used to identify the target buffer class and create a new buffer (if necessary)\n     * @param size size of the buffer\n     * @return a buffer of the specified size\n     */\n    public static synchronized Object getBuffer(BufferFactory factory, int size) {\n        // Looks for a buffer container in the pool that matches the specified size and buffer class.\n        Iterator<BufferContainer> it = bufferContainers.iterator();\n        while (it.hasNext()) {\n            BufferContainer bufferContainer = it.next();\n            Object buffer = bufferContainer.getBuffer();\n\n            // Caution: mind the difference between BufferContainer#getLength() and BufferContainer#getSize()\n            if (bufferContainer.getLength() == size && (factory.matchesBufferClass(buffer.getClass()))) {\n                it.remove();\n                //bufferContainers.removeElementAt(i);\n                poolSize -= bufferContainer.getSize();\n                return buffer;\n            }\n        }\n\n        LOGGER.trace(\"Creating new buffer with {} size={}\", factory, size);\n\n        // No buffer with the same class and size found in the pool, create a new one and return it\n        return factory.newBuffer(size);\n    }\n\n\n    /**\n     * Makes the given buffer available for further calls to {@link #getByteArray(int)} with the same buffer length.\n     * Returns <code>true</code> if the buffer was added to the pool, <code>false</code> if the buffer was already in\n     * the pool.\n     *\n     * <p>After calling this method, the given buffer instance <b>must not be used</b>, otherwise it could get\n     * corrupted if other threads were using it.\n     *\n     * @param buffer the buffer instance to make available for further use\n     * @return <code>true</code> if the buffer was added to the pool, <code>false</code> if the buffer was already in the pool\n     * @throws IllegalArgumentException if specified buffer is null\n     */\n    public static synchronized boolean releaseByteArray(byte[] buffer) {\n        return releaseBuffer(buffer, BYTE_ARRAY_FACTORY);\n    }\n\n    /**\n     * Makes the given buffer available for further calls to {@link #getCharArray(int)} with the same buffer length.\n     * Returns <code>true</code> if the buffer was added to the pool, <code>false</code> if the buffer was already in\n     * the pool.\n     *\n     * <p>After calling this method, the given buffer instance <b>must not be used</b>, otherwise it could get\n     * corrupted if other threads were using it.\n     *\n     * @param buffer the buffer instance to make available for further use\n     * @return <code>true</code> if the buffer was added to the pool, <code>false</code> if the buffer was already in the pool\n     * @throws IllegalArgumentException if specified buffer is null\n     */\n    public static synchronized boolean releaseCharArray(char[] buffer) {\n        return releaseBuffer(buffer, CHAR_ARRAY_FACTORY);\n    }\n\n    /**\n     * Makes the given buffer available for further calls to {@link #getByteBuffer(int)} with the same buffer capacity.\n     * Returns <code>true</code> if the buffer was added to the pool, <code>false</code> if the buffer was already in\n     * the pool.\n     *\n     * <p>After calling this method, the given buffer instance <b>must not be used</b>, otherwise it could get\n     * corrupted if other threads were using it.\n     *\n     * @param buffer the buffer instance to make available for further use\n     * @return <code>true</code> if the buffer was added to the pool, <code>false</code> if the buffer was already in the pool\n     * @throws IllegalArgumentException if specified buffer is null\n     */\n    public static synchronized boolean releaseByteBuffer(ByteBuffer buffer) {\n        return releaseBuffer(buffer, new ByteBufferFactory());\n    }\n\n    /**\n     * Makes the given buffer available for further calls to {@link #getCharBuffer(int)} with the same buffer capacity.\n     * Returns <code>true</code> if the buffer was added to the pool, <code>false</code> if the buffer was already in\n     * the pool.\n     *\n     * <p>After calling this method, the given buffer instance <b>must not be used</b>, otherwise it could get\n     * corrupted if other threads were using it.\n     *\n     * @param buffer the buffer instance to make available for further use\n     * @return <code>true</code> if the buffer was added to the pool, <code>false</code> if the buffer was already in the pool\n     * @throws IllegalArgumentException if specified buffer is null\n     */\n    public static synchronized boolean releaseCharBuffer(CharBuffer buffer) {\n        return releaseBuffer(buffer, new CharBufferFactory());\n    }\n\n    /**\n     * Makes the given buffer available for further calls to {@link #getBuffer(com.mucommander.commons.io.BufferPool.BufferFactory,int)} with the same buffer\n     * size and factory.\n     * Returns <code>true</code> if the buffer was added to the pool, <code>false</code> if the buffer was already in \n     * the pool or the pool size limit has been reached.\n     *\n     * <p>After calling this method, the given buffer instance <b>must not be used</b>, otherwise it could get\n     * corrupted if other threads were using it.\n     *\n     * @param buffer the buffer instance to make available for further use\n     * @param factory the BufferFactory that was used to create the buffer\n     * @return <code>true</code> if the buffer was added to the pool, <code>false</code> if the buffer was already in the pool or the pool size limit has been reached\n     * @throws IllegalArgumentException if specified buffer is null\n     */\n    public static synchronized boolean releaseBuffer(Object buffer, BufferFactory factory) {\n        if (buffer == null) {\n            throw new IllegalArgumentException(\"specified buffer is null\");\n        }\n        BufferContainer bufferContainer = factory.newBufferContainer(buffer);\n        if (bufferContainers.contains(bufferContainer)) {\n            LOGGER.info(\"Warning: specified buffer is already in the pool: {}\", buffer);\n            return false;\n        }\n        long bufferSize = bufferContainer.getSize();        // size in bytes (!= length)\n\n        if (maxPoolSize >= 0 && poolSize + bufferSize > maxPoolSize) {\n            LOGGER.info(\"Warning: maximum pool size reached, buffer not added to the pool of type {}. Enable trace to get the buffer.\", buffer.getClass());\n            LOGGER.trace(\"Warning: maximum pool size reached, buffer not added to the pool of type {} : {}\", buffer.getClass(), buffer);\n            return false;\n        }\n\n        bufferContainers.add(bufferContainer);\n        poolSize += bufferSize;\n\n        return true;\n    }\n\n    /**\n     * Returns <code>true</code> if the specified buffer is currently in the pool.\n     *\n     * <p>Note that it is not necessary (and thus not recommended for performance reasons) to call this method before\n     * calling <code>release*Buffer</code> as it already performs this test before adding a buffer to the pool.\n     *\n     * @param buffer the buffer to look for in the pool\n     * @param factory the BufferFactory that was used to create the buffer\n     * @return <code>true</code> if the specified buffer is already in the pool\n     */\n    public static boolean containsBuffer(Object buffer, BufferFactory factory) {\n        return bufferContainers.contains(factory.newBufferContainer(buffer));\n    }\n\n\n    /**\n     * Returns the number of buffers that currently are in the pool. This method is provided for debugging\n     * purposes only.\n     *\n     * @return the number of buffers currently in the pool\n     */\n    public static int getBufferCount() {\n        return bufferContainers.size();\n    }\n\n    /**\n     * Returns the number of buffers that currently are in the pool and whose Class are the same as the specified\n     * factory's. This method is provided for debugging purposes only.\n     *\n     * @param factory the BufferFactory\n     * @return the number of buffers currently in the pool\n     */\n    public static int getBufferCount(BufferFactory factory) {\n        int count = 0;\n        for (BufferContainer bufferContainer : bufferContainers) {\n            if (factory.matchesBufferClass(bufferContainer.getBuffer().getClass())) {\n                count ++;\n            }\n\n        }\n        return count;\n    }\n\n    /**\n     * Returns the default size of buffers returned by <code>get*Buffer</code> methods without a <code>size</code>\n     * argument.\n     *\n     * <p>The default buffer size is initially set to {@link #INITIAL_DEFAULT_BUFFER_SIZE}.\n     *\n     * @return the default size of buffers returned by <code>get*Buffer</code> methods without a <code>size</code> argument\n     */\n    public static int getDefaultBufferSize() {\n        return defaultBufferSize;\n    }\n\n    /**\n     * Sets the default size of buffers returned by <code>get*Buffer</code> methods without a <code>size</code> argument.\n     *\n     * @param bufferSize the new buffer size\n     */\n    public static synchronized void setDefaultBufferSize(int bufferSize) {\n        BufferPool.defaultBufferSize = bufferSize;\n    }\n\n\n    /**\n     * Returns the combined size in bytes of all buffers that are currently in the pool.\n     *\n     * @return the combined size in bytes of all buffers that are currenty in the pool\n     */\n    public static long getPoolSize() {\n        return poolSize;\n    }\n\n    /**\n     * Returns the maximum combined size in bytes for all buffers in the pool, <code>-1</code> for no limit.\n     * Before adding a buffer to the pool, <code>release*Buffer</code> methods ensure that the pool size will\n     * not be exceeded. If and only if that is the case, the buffer is added to the pool.\n     *\n     * <p>The max pool size is initially set to {@link #INITIAL_POOL_LIMIT}.\n     *\n     * @return the maximum combined size in bytes for all buffers in the pool\n     */\n    public static long getMaxPoolSize() {\n        return maxPoolSize;\n    }\n\n    /**\n     * Sets the maximum combined size in bytes for all buffers in the pool, <code>-1</code> for no limit.\n     * Before adding a buffer to the pool, <code>release*Buffer</code> methods ensure that the pool size will\n     * not be exceeded. If and only if that is the case, the buffer is added to the pool.\n     *\n     * @param maxPoolSize the maximum combined size in bytes for all buffers in the pool\n     */\n    public static synchronized void setMaxPoolSize(long maxPoolSize) {\n        BufferPool.maxPoolSize = maxPoolSize;\n    }\n\n    /**\n     * Wraps a buffer instance and provides information about the wrapped buffer.\n     */\n    public static abstract class BufferContainer {\n\n        /** The wrapped buffer instance */\n        protected Object buffer;\n\n        /**\n         * Creates a new BufferContainer that wraps the given buffer.\n         *\n         * @param buffer the buffer instance to wrap\n         */\n        protected BufferContainer(Object buffer) {\n            this.buffer = buffer;\n        }\n\n        /**\n         * Returns the wrapped buffer instance.\n         *\n         * @return the wrapped buffer instance\n         */\n        protected Object getBuffer() {\n            return buffer;\n        }\n\n        /**\n         * Implements a shallow equal comparison.\n         */\n        public boolean equals(Object o) {\n            // Note: this method is used by Vector.contains()\n            return (o instanceof BufferContainer) && buffer == ((BufferContainer)o).buffer;\n        }\n\n        /**\n         * Returns the length of the wrapped buffer instance.\n         *\n         * @return the length of the wrapped buffer instance\n         */\n        protected abstract int getLength();\n\n        /**\n         * Returns the size of the wrapped buffer instance, expressed in bytes.\n         *\n         * @return the size of the wrapped buffer instance, expressed in bytes\n         */\n        protected abstract int getSize();\n    }\n\n    /**\n     * A BufferFactory is responsible for creating buffer and {@link BufferContainer} instances, and for returning the buffer\n     * Class. The Class returned by {@link #getBufferClass()} may be a superclass or superinterface of the actual\n     * objects returned by {@link #newBuffer(int)}.\n     */\n    public static abstract class BufferFactory {\n\n        /**\n         * Returns <code>true</code> if the class returned by {@link #getBufferClass()} is equal or a\n         * superclass/superinterface of the specified buffer class.\n         *\n         * @param bufferClass the buffer Class to test\n         * @return true if the class returned by <code>#getBufferClass()</code> is equal or a superclass/superinterface\n         * of the specified buffer class\n         */\n        public boolean matchesBufferClass(Class<?> bufferClass) {\n            return getBufferClass().isAssignableFrom(bufferClass);\n        }\n\n        /**\n         * Creates and returns a buffer instance of the specified size.\n         *\n         * @param size size of the buffer to create\n         * @return a buffer instance of the specified size\n         */\n        public abstract Object newBuffer(int size);\n\n        /**\n         * Creates and returns a {@link BufferContainer} for the specified buffer instance.\n         *\n         * @param buffer the buffer to wrap in a BufferContainer\n         * @return returns a BufferContainer for the specified buffer instance\n         */\n        public abstract BufferContainer newBufferContainer(Object buffer);\n\n        /**\n         * Returns the Class of buffer instances this factory creates. \n         *\n         * @return the Class of buffer instances this factory creates\n         */\n        public abstract Class<?> getBufferClass();\n    }\n\n    /**\n     * This class is a {@link BufferFactory} implementation for byte array (<code>byte[]</code>) buffers.\n     */\n    public static class ByteArrayFactory extends BufferFactory {\n        @Override\n        public Object newBuffer(int size) {\n            return new byte[size];\n        }\n\n        @Override\n        public BufferContainer newBufferContainer(Object buffer) {\n            return new BufferContainer(buffer) {\n                @Override\n                protected int getLength() {\n                    return ((byte[])buffer).length;\n                }\n\n                @Override\n                protected int getSize() {\n                    return getLength();\n                }\n            };\n        }\n\n        @Override\n        public Class<?> getBufferClass() {\n            return byte[].class;\n        }\n    }\n\n    /**\n     * This class is a {@link BufferFactory} implementation for char array (<code>char[]</code>) buffers.\n     */\n    public static class CharArrayFactory extends BufferFactory {\n        @Override\n        public Object newBuffer(int size) {\n            return new char[size];\n        }\n\n        @Override\n        public BufferContainer newBufferContainer(Object buffer) {\n            return new BufferContainer(buffer) {\n                @Override\n                protected int getLength() {\n                    return ((char[])buffer).length;\n                }\n\n                @Override\n                protected int getSize() {\n                    return 2*getLength();\n                }\n            };\n        }\n\n        @Override\n        public Class<?> getBufferClass() {\n            return char[].class;\n        }\n    }\n\n    /**\n     * This class is a {@link BufferFactory} implementation for <code>java.nio.ByteBuffer</code> buffers.\n     * ByteBuffer instances created by {@link #newBuffer(int)} are direct ; the actually Class of those instances may be actually\n     * be <code>java.nio.DirectByteBuffer</code> and not <code>java.nio.ByteBuffer</code> as returned by\n     * {@link #getBufferClass()}.\n     */\n    public static class ByteBufferFactory extends BufferFactory {\n        @Override\n        public Object newBuffer(int size) {\n            // Note: the returned instance is actually a java.nio.DirectByteBuffer, this is why it's important to\n            // compare classes using Class#isAssignableFrom(Class)\n            return ByteBuffer.allocateDirect(size);\n        }\n\n        @Override\n        public BufferContainer newBufferContainer(Object buffer) {\n            return new BufferContainer(buffer) {\n                @Override\n                protected int getLength() {\n                    return ((ByteBuffer)buffer).capacity();\n                }\n\n                @Override\n                protected int getSize() {\n                    return getLength();\n                }\n            };\n        }\n\n        @Override\n        public Class<?> getBufferClass() {\n            return ByteBuffer.class;\n        }\n    }\n\n    /**\n     * This class is a {@link BufferFactory} implementation for <code>java.nio.CharBuffer</code> buffers.\n     */\n    public static class CharBufferFactory extends BufferFactory {\n        @Override\n        public Object newBuffer(int size) {\n            return CharBuffer.allocate(size);\n        }\n\n        @Override\n        public BufferContainer newBufferContainer(Object buffer) {\n            return new BufferContainer(buffer) {\n                @Override\n                protected int getLength() {\n                    return ((CharBuffer)buffer).capacity();\n                }\n\n                @Override\n                protected int getSize() {\n                    return 2*getLength();\n                }\n            };\n        }\n\n        @Override\n        public Class<?> getBufferClass() {\n            return CharBuffer.class;\n        }\n    }\n\n\tpublic static synchronized void releaseAll() {\n\t\tbufferContainers.clear();\n\t\tpoolSize = 0;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/BufferedRandomOutputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.IOException;\n\n/**\n * BufferedRandomOutputStream is a buffered output stream for {@link RandomAccessOutputStream} which, unlike a regular\n * <code>java.io.BufferedOutputStream</code>, makes it safe to seek in the underlying <code>RandomAccessOutputStream</code>.\n *\n * <p>This class uses {@link BufferPool} to create the internal buffer, to avoid excessive memory allocation and\n * garbage collection. The buffer is released when this stream is closed.\n *\n * @author Maxence Bernard\n */\npublic class BufferedRandomOutputStream extends RandomAccessOutputStream {\n\n    /**\n     * The underlying random access output stream\n     */\n    private final RandomAccessOutputStream raos;\n\n    /**\n     * The buffer where written bytes are accumulated before being sent to the underlying output stream\n     */\n    private byte[] buffer;\n\n    /**\n     * The current number of bytes waiting to be flushed to the underlying output stream\n     */\n    private int count;\n\n    /**\n     * The default buffer size if none is specified\n     */\n    public final static int DEFAULT_BUFFER_SIZE = 65536;\n\n\n    /**\n     * Creates a new <code>BufferedRandomOutputStream</code> on top of the given {@link RandomAccessOutputStream}.\n     * An internal buffer of {@link #DEFAULT_BUFFER_SIZE} bytes is created.\n     *\n     * @param raos the underlying RandomAccessOutputStream used by this buffered output stream\n     */\n    public BufferedRandomOutputStream(RandomAccessOutputStream raos) {\n        this(raos, DEFAULT_BUFFER_SIZE);\n    }\n\n    /**\n     * Creates a new <code>BufferedRandomOutputStream</code> on top of the given {@link RandomAccessOutputStream}.\n     * An internal buffer of the specified size is created.\n     *\n     * @param raos the underlying RandomAccessOutputStream used by this buffered output stream\n     * @param size size of the buffer in bytes\n     */\n    public BufferedRandomOutputStream(RandomAccessOutputStream raos, int size) {\n        this.raos = raos;\n        this.buffer = BufferPool.getByteArray(size);\n    }\n\n    /**\n     * Flushes the internal buffer.\n     *\n     * @throws IOException if an error occurs\n     */\n    private void flushBuffer() throws IOException {\n        if (count > 0) {\n            raos.write(buffer, 0, count);\n            count = 0;\n        }\n    }\n\n\n    /**\n     * Writes the specified byte to this buffered output stream.\n     *\n     * @param b the byte to be written\n     * @throws IOException if an I/O error occurs\n     */\n    @Override\n    public synchronized void write(int b) throws IOException {\n        if (count >= buffer.length)\n            flushBuffer();\n\n        buffer[count++] = (byte) b;\n    }\n\n    /**\n     * Writes the specified byte array to this buffered output stream.\n     *\n     * @param b the bytes to be written\n     * @throws IOException if an I/O error occurs\n     */\n    @Override\n    public synchronized void write(byte[] b) throws IOException {\n        write(b, 0, b.length);\n    }\n\n    /**\n     * Writes <code>len</code> bytes from the specified byte array starting at offset <code>off</code> to this\n     * buffered output stream.\n     *\n     * <p>Usually this method stores bytes from the given array into this\n     * stream's buffer, flushing the buffer to the underlying output stream as\n     * needed. However, if the requested data length is equal or larger than this stream's\n     * buffer, then this method will flush the buffer and write the\n     * bytes directly to the underlying output stream. Thus, redundant\n     * <code>RandomBufferedOutputStream</code>s will not copy data unnecessarily.\n     *\n     * @param b   the data.\n     * @param off the start offset in the data.\n     * @param len the number of bytes to write.\n     * @throws IOException if an I/O error occurs.\n     */\n    @Override\n    public synchronized void write(byte[] b, int off, int len) throws IOException {\n        if (len >= buffer.length) {\n            /* If the request length exceeds the size of the output buffer,\n            flush the output buffer and then write the data directly.\n            In this way buffered streams will cascade harmlessly. */\n            flushBuffer();\n            raos.write(b, off, len);\n            return;\n        }\n\n        if (len > buffer.length - count)\n            flushBuffer();\n\n        System.arraycopy(b, off, buffer, count, len);\n        count += len;\n    }\n\n    /**\n     * Flushes this buffered output stream. This forces any buffered\n     * output bytes to be written out to the underlying output stream.\n     *\n     * @throws IOException if an I/O error occurs.\n     */\n    @Override\n    public synchronized void flush() throws IOException {\n        flushBuffer();\n        raos.flush();\n    }\n\n    public synchronized long getOffset() throws IOException {\n        // Add the buffered byte count\n        return raos.getOffset() + count;\n    }\n\n    public synchronized void seek(long offset) throws IOException {\n        // Flush any buffered bytes before seeking, otherwise buffered bytes would be written at the wrong offset\n        flush();\n\n        raos.seek(offset);\n    }\n\n    public synchronized long getLength() throws IOException {\n        // Anticipate if the file is to be expanded by the bytes awaiting in the buffer\n        return Math.max(raos.getLength(), getOffset());\n    }\n\n    @Override\n    public synchronized void setLength(long newLength) throws IOException {\n        // Flush before changing the file's length, otherwise the behavior of setLength() would be modified, especially\n        // when truncating the file\n        flush();\n\n        raos.setLength(newLength);\n    }\n\n\n    /**\n     * This method is overridden to release the internal buffer when this stream is closed.\n     */\n    @Override\n    public synchronized void close() throws IOException {\n        if (buffer != null) {      // buffer is null if close() was already called\n            try {\n                flush();\n            } catch (IOException ignore) {\n            }\n\n            // Release the buffer\n            BufferPool.releaseByteArray(buffer);\n            buffer = null;\n        }\n\n        raos.close();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/ByteCounter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\n/**\n * Contains a number of bytes which have been read/written from/to a {@link CounterInputStream}/{@link CounterOutputStream}.\n *\n * <p>Provided methods allow to read the the byte count, add a number bytes to it or reset it (make it zero).\n *\n * <p>This class is thread safe, ensuring that the counter is always in a consistent state.\n *\n * @see com.mucommander.commons.io.CounterInputStream\n * @see com.mucommander.commons.io.CounterOutputStream\n * @author Maxence Bernard\n */\npublic class ByteCounter {\n\n    /** Byte count */\n    private long count;\n\n    /** Byte counter to add to the value returned by {@link #getByteCount()} */\n    private ByteCounter addedCounter;\n\n    \n    /**\n     * Creates a new ByteCounter with an initial byte count equal to zero.\n     */\n    public ByteCounter() {\n    }\n\n    /**\n     * Creates a new ByteCounter with an initial byte count equal to zero and using the given ByteCounter.\n     *\n     * <p>The value returned by {@link #getByteCount()} will be the sum of the internal byte count and the one from\n     * the specified ByteCounter, as returned by its {@link #getByteCount()} method. Resetting this ByteCounter's value\n     * will only affect the internal byte count and not the one from the specified ByteCounter.\n     */\n    public ByteCounter(ByteCounter counter) {\n        this.addedCounter = counter;\n    }\n\n\n    /**\n     * Return the number of bytes which have been accounted for.\n     * @return the number of bytes which have been accounted for\n     */\n    public synchronized long getByteCount() {\n        if (addedCounter != null) {\n            return count + addedCounter.getByteCount();\n        }\n\n        return this.count;\n    }\n\n\n    /**\n     * Increases the byte counter by the provided number of bytes. If the specified number is negative,\n     * the byte counter will be left unchanged (won't be decreased).\n     *\n     * @param nbBytes number of bytes to add to the byte counter, will be ignored if negative\n     */\n    public synchronized void add(long nbBytes) {\n        if (nbBytes > 0) {\n            this.count += nbBytes;\n        }\n    }\n\n\n    /**\n     * Increases the byte counter by the number of bytes contained in the specified counter (as returned by its\n     * {@link #getByteCount()} method) and resets its byte counter after (if specified).\n     *\n     * @param counter the Bytecounter to add to this one, and reset after (if specified).\n     * @param resetAfter if true, the specified counter will be reset after its byte count has been added to this ByteCounter\n     */\n    public synchronized void add(ByteCounter counter, boolean resetAfter) {\n        // Hold a lock on the provided counter to make sure that it is not modified or accessed\n        // while this operation is carried out\n        synchronized(counter) {\n            add(counter.getByteCount());\n            if(resetAfter)\n                counter.reset();\n        }\n    }\n\n\n    /**\n     * Resets the byte counter (make it zero).\n     */\n    public synchronized void reset() {\n        this.count = 0;\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/ByteUtils.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\n/**\n * This class provides convenience static methods that operate on bits and bytes.\n *\n * @author Maxence Bernard\n */\npublic class ByteUtils {\n\n    /**\n     * Sets/unsets a bit in the given integer.\n     *\n     * @param i the permission int\n     * @param bit the bit to set\n     * @param enabled true to enable the bit, false to disable it\n     * @return the modified permission int\n     */\n    public static int setBit(int i, int bit, boolean enabled) {\n        if (enabled)\n            i |= bit;\n        else\n            i &= ~bit;\n\n        return i;\n    }\n\n    /**\n     * Returns an hexadecimal string representation of the given byte array, where each byte is represented by two\n     * hexadecimal characters and padded with a zero if its value is comprised between 0 and 15 (inclusive).\n     * As an example, this method will return \"6d75636f0a\" when called with the byte array {109, 117, 99, 111, 10}.\n     *\n     * @param bytes the array of bytes for which to get an hexadecimal string representation\n     * @return an hexadecimal string representation of the given byte array\n     */\n    public static String toHexString(byte[] bytes) {\n        StringBuilder sb = new StringBuilder();\n\n        for (byte aByte : bytes) {\n            String hexByte = Integer.toHexString(aByte & 0xFF);\n            if (hexByte.length() == 1)\n                sb.append('0');\n            sb.append(hexByte);\n        }\n\n        return sb.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/ChecksumInputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.InputStream;\nimport java.security.DigestInputStream;\nimport java.security.MessageDigest;\n\n/**\n * This class extends <code>java.security.DigestInputStream</code> and adds convenience methods that return the\n * digest/checksum expressed in various forms.\n *\n * @see com.mucommander.commons.io.ChecksumOutputStream\n * @author Maxence Bernard\n */\npublic class ChecksumInputStream extends DigestInputStream {\n\n    public ChecksumInputStream(InputStream stream, MessageDigest digest) {\n        super(stream, digest);\n    }\n\n\n    /**\n     * Returns this stream's digest, expressed as a byte array.\n     *\n     * @return this stream's digest, expressed as a byte array\n     */\n    public byte[] getChecksumBytes() {\n        return getMessageDigest().digest();\n    }\n\n    /**\n     * Returns this stream's digest, expressed as an hexadecimal string.\n     *\n     * @return this stream's digest, expressed as an hexadecimal string\n     */\n    public String getChecksumString() {\n        return ByteUtils.toHexString(getChecksumBytes());\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/ChecksumOutputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.OutputStream;\nimport java.security.DigestOutputStream;\nimport java.security.MessageDigest;\n\n/**\n * This class extends <code>java.security.DigestOutputStream</code> and adds convenience methods that return the\n * digest/checksum expressed in various forms.\n *\n * @see com.mucommander.commons.io.ChecksumInputStream\n * @author Maxence Bernard\n */\npublic class ChecksumOutputStream extends DigestOutputStream {\n\n    public ChecksumOutputStream(OutputStream stream, MessageDigest digest) {\n        super(stream, digest);\n    }\n\n\n    /**\n     * Returns this stream's digest, expressed as a byte array.\n     *\n     * @return this stream's digest, expressed as a byte array\n     */\n    public byte[] getChecksumBytes() {\n        return getMessageDigest().digest();\n    }\n\n    /**\n     * Returns this stream's digest, expressed as an hexadecimal string.\n     *\n     * @return this stream's digest, expressed as an hexadecimal string\n     */\n    public String getChecksumString() {\n        return ByteUtils.toHexString(getChecksumBytes());\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/CounterInputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * An InputStream that keeps track of the number of bytes that have been read from it. Bytes that are skipped (using\n * {@link #skip(long)} are by default accounted for, {@link #setCountSkippedBytes(boolean)} can be used to change this.\n *\n * <p>The actual number of bytes can be retrieved from the {@link ByteCounter} instance returned by {@link #getCounter()}.\n * The {@link #CounterInputStream(InputStream, ByteCounter)} constructor can be used to specify an existing\n * ByteCounter instance instead of creating a new one. The ByteCounter will always remain accessible, even\n * after this stream has been closed.\n *\n * @see ByteCounter\n * @author Maxence Bernard\n */\npublic class CounterInputStream extends InputStream {\n\n    /** Underlying InputStream */\n    private final InputStream in;\n\n    /** Byte counter */\n    private final ByteCounter counter;\n\n    /** Should skipped bytes be accounted for ? (enabled by default) */\n    private boolean countSkippedBytes = true;\n\n\n    /**\n     * Creates a new CounterInputStream using the specified InputStream. A new {@link ByteCounter} will be created.\n     *\n     * @param in the underlying InputStream the data will be read from\n     */\n    public CounterInputStream(InputStream in) {\n        this.in = in;\n        this.counter = new ByteCounter();\n    }\n\n    /**\n     * Creates a new CounterInputStream using the specified InputStream and {@link ByteCounter}.\n     * The provided <code>ByteCounter</code> will NOT be reset, whatever value it contains will be kept.\n     *\n     * @param in the underlying InputStream the data will be read from\n     */\n    public CounterInputStream(InputStream in, ByteCounter counter) {\n        this.in = in;\n        this.counter = counter;\n    }\n\n\n    /**\n     * Returns the ByteCounter that holds the number of bytes that have been read (and optionally skipped) from this\n     * InputStream.\n     */\n    public ByteCounter getCounter() {\n        return this.counter;\n    }\n\n\n    /**\n     * Specifies whether skipped bytes (using {@link #skip(long)} should be accounted for.\n     * This is by default enabled, bytes that are skipped are added to the ByteCounter.\n     *\n     * @param countSkippedBytes if true, skipped bytes will be accounted for, the ByteCounter will be increased\n     * by the number of skipped bytes\n     */\n    public void setCountSkippedBytes(boolean countSkippedBytes) {\n        this.countSkippedBytes = countSkippedBytes;\n    }\n\n    /**\n     * Returns true if skipped bytes (using {@link #skip(long)} are accounted for. \n     * This is by default enabled, bytes that are skipped are added to the ByteCounter.\n     */\n    public boolean getCountSkippedBytes() {\n        return countSkippedBytes;\n    }\n\n    @Override\n    public int read() throws IOException {\n        int i = in.read();\n        if ( i > 0) {\n            counter.add(1);\n        }\n        return i;\n    }\n\n\n    @Override\n    public int read(byte b[]) throws IOException {\n        int nbRead = in.read(b);\n        if (nbRead > 0) {\n            counter.add(nbRead);\n        }\n        return nbRead;\n    }\n\n    @Override\n    public int read(byte b[], int off, int len) throws IOException {\n        int nbRead = in.read(b, off, len);\n        if (nbRead > 0) {\n            counter.add(nbRead);\n        }\n        return nbRead;\n    }\n\n    @Override\n    public long skip(long n) throws IOException {\n        long nbSkipped = in.skip(n);\n\n        // Count skipped bytes only if this has been enabled\n        if (countSkippedBytes && nbSkipped > 0) {\n            counter.add(nbSkipped);\n        }\n\n        return nbSkipped;\n    }\n\n\n    @Override\n    public int available() throws IOException {\n        return in.available();\n    }\n\n\n    @Override\n    public void close() throws IOException {\n        in.close();\n    }\n\n\n    @Override\n    public void mark(int readLimit) {\n        in.mark(readLimit);\n    }\n\n\n    @Override\n    public boolean markSupported() {\n        return in.markSupported();\n    }\n\n\n    @Override\n    public void reset() throws IOException  {\n        in.reset();\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/CounterOutputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * An OutputStream that keeps track of the number of bytes that have been written to it.\n *\n * <p>The actual number of bytes can be retrieved from the {@link ByteCounter} instance returned by {@link #getCounter()}.\n * The {@link #CounterOutputStream(OutputStream, ByteCounter)} constructor can be used to specify an existing\n * ByteCounter instance instead of creating a new one. The ByteCounter will always remain accessible, even\n * after this stream has been closed.\n *\n * @see ByteCounter\n * @author Maxence Bernard\n */\npublic class CounterOutputStream extends OutputStream {\n\n    /** Underlying OutputStream */\n    private final OutputStream out;\n\n    /** Byte counter */\n    private final ByteCounter counter;\n\n    \n    /**\n     * Creates a new CounterOutputStream using the specified OutputStream. A new {@link ByteCounter} will be created.\n     *\n     * @param out the underlying OutputStream the data will be written to\n     */\n    public CounterOutputStream(OutputStream out) {\n        this.out = out;\n        this.counter = new ByteCounter();\n    }\n\n    /**\n     * Creates a new CounterOutputStream using the specified OutputStream and {@link ByteCounter}.\n     * The provided <code>ByteCounter</code> will NOT be reset, whatever value it contains will be kept.\n     *\n     * @param out the underlying OutputStream the data will be written to\n     */\n    public CounterOutputStream(OutputStream out, ByteCounter counter) {\n        this.out = out;\n        this.counter = counter;\n    }\n\n\n    /**\n     * Returns the ByteCounter that holds the number of bytes that have been written to this OutputStream.\n     * @return the ByteCounter that holds the number of bytes that have been written to this OutputStream\n     */\n    public ByteCounter getCounter() {\n        return this.counter;\n    }\n\n\n    @Override\n    public void write(int b) throws IOException {\n        out.write(b);\n        counter.add(1);\n    }\n\n    @Override\n    public void write(byte b[]) throws IOException {\n        out.write(b);\n        counter.add(b.length);\n    }\n    \n    @Override\n    public void write(byte b[], int off, int len) throws IOException {\n        out.write(b, off, len);\n        counter.add(len);\n    }\n    \n    @Override\n    public void flush() throws IOException {\n        out.flush();\n    }\n\n    @Override\n    public void close() throws IOException {\n        out.close();\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/EncodingDetector.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport com.ibm.icu.text.CharsetDetector;\nimport com.ibm.icu.text.CharsetMatch;\nimport org.jetbrains.annotations.Nullable;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.PushbackInputStream;\n\n/**\n * This class allows to guess at an encoding in which an array of bytes is encoded. Detecting an encoding is by no means\n * an accurate operation, as it relies on heuristics that are imprecise by nature. However, accuracy improves with the\n * quantity of bytes that is supplied: a small amount of data (say 10 bytes) has little chance of being guessed\n * correctly, whereas a larger amount of data (say 1000 bytes) is likely to provide a good result. On the other hand,\n * providing a very large amount of data will only marginally improve the accuracy, and is not worth the extra effort\n * considering that encoding detection is a costly operation which involves many comparisons per byte.\n * The {@link #MAX_RECOMMENDED_BYTE_SIZE} field controls that threshold: if a supplied byte array is larger than this\n * value, the additional bytes will not be processed by the <code>detectEncoding</code> methods. Therefore, this value\n * should be taken into account if bytes are to be fetched specifically for the purpose of detecting the encoding.\n *\n * <p>\n * EncodingDetector uses <i>ICU4J</i> under the hood. Here's a list of encodings that can currently be detected:\n * <pre>\n * UTF-8\n * UTF-16BE\n * UTF-16LE\n * UTF-32BE\n * UTF-32LE\n * Shift_JIS\n * ISO-2022-JP\n * ISO-2022-CN\n * ISO-2022-KR\n * GB18030\n * EUC-JP\n * EUC-KR\n * Big5\n * ISO-8859-1\n * ISO-8859-2\n * ISO-8859-5\n * ISO-8859-6\n * ISO-8859-7\n * ISO-8859-8\n * windows-1251\n * windows-1256\n * KOI8-R\n * ISO-8859-9\n * </pre>\n *\n * @author Maxence Bernard, Nicolas Rinaudo\n * @see <a href=\"http://philip.html5.org/data/charsets.html\">ICU charset detection accuracy</a>\n */\npublic class EncodingDetector {\n    private static final Logger LOGGER = LoggerFactory.getLogger(EncodingDetector.class);\n\n    /** Maximum number of bytes that the detectEncoding methods will process.\n     * <p>\n     * See http://philip.html5.org/data/charsets.html and http://philip.html5.org/data/encoding-detection.svg\n     * for why 4096 is the recommended size.\n     *\n     * Comment by Trol: 4096 is wrong value here. I have a lot of times situation when the encoding doesn't detected correctly\n     * in case of source file with UTF-8 comments at the end\n     */\n    public final static int MAX_RECOMMENDED_BYTE_SIZE = 1024*16;\n\n\n    /**\n     * This method is a shorthand for {@link #detectEncoding(byte[], int, int) detectEncoding(b, 0, b.length)}.\n     *\n     * @param bytes the bytes for which to detect the encoding\n     * @return the best guess at the character encoding, null if there is none (not enough data or confidence)\n     */\n    public static String detectEncoding(byte[] bytes) {\n        return detectEncoding(bytes, 0, bytes.length);\n    }\n\n    /**\n     * Try and detect the character encoding in which the given bytes are encoded, and returns the best guess or\n     * <code>null</code> if there is none (not enough data or confidence).\n     * Note that the returned character encoding may not be available on the Java runtime -- use\n     * <code>java.nio.Charset#isSupported(String)</code> to determine if it is available.\n     *\n     * <p>A maximum of {@link #MAX_RECOMMENDED_BYTE_SIZE} will be read from the array. If the array is larger than this\n     * value, all further bytes will be ignored.\n     *\n     * @param bytes the bytes for which to detect the encoding\n     * @param off the array offset at which the data to process starts\n     * @param len length of the data in the array\n     * @return the best guess at the encoding, null if there is none (not enough data or confidence)\n     */\n    public static String detectEncoding(byte[] bytes, int off, int len) {\n        // The current ICU CharsetDetector class will throw an ArrayIndexOutOfBoundsException exception if the\n        // supplied array is less than 4 bytes long. In that case, return null.\n        if (len < 4) {\n            return null;\n        }\n\n        // Trim the array if it is too long, detecting the charset is an expensive operation and past a certain point,\n        // having more bytes won't help any further        \n        if (len > MAX_RECOMMENDED_BYTE_SIZE) {\n            len = MAX_RECOMMENDED_BYTE_SIZE;\n        }\n\n        // CharsetDetector will process the array fully, so if the data does not start at 0 or ends before the array's\n        // length, create a new array that fits the data exactly\n        if (off > 0 || len < bytes.length) {\n            byte[] tmp = new byte[len];\n            System.arraycopy(bytes, off, tmp, 0, len);\n            bytes = tmp;\n        }\n        \n        CharsetDetector cd = new CharsetDetector();\n        cd.setText(bytes);\n\n\n        CharsetMatch[] matches = cd.detectAll();\n        CharsetMatch cm = getBestCharsetMatch(matches);\n\n        // Debug info\n        LOGGER.trace(\"bestMatch getName()={}, getConfidence()={}\", (cm == null ? \"null\" : cm.getName()),\n                     (cm == null ? \"null\" : Integer.toString(cm.getConfidence())));\n\n        return cm == null ? null : cm.getName();\n    }\n\n    @Nullable\n    private static CharsetMatch getBestCharsetMatch(CharsetMatch[] matches) {\n        if (matches == null || matches.length == 0) {\n            return null;\n        }\n        CharsetMatch cm = matches[0];\n        // detect win-1251 for case latin + cyrillic\n        String detectedName = cm.getName().toLowerCase();\n        if (detectedName.startsWith(\"iso-8859-\")) {\n            for (CharsetMatch match : matches) {\n                if (match.getName().toLowerCase().startsWith(\"windows-1251\")) {\n                    return match;\n                } else if (match.getName().toLowerCase().startsWith(\"utf-8\")) {\n                    return match;\n                }\n            }\n        }\n\n        return cm;\n    }\n\n\n    /**\n     * Try and detect the character encoding in which the bytes contained by the given <code>InputStream</code> are\n     * encoded, and returns the best guess or <code>null</code> if there is none (not enough data or confidence).\n     * Note that the returned character encoding may or may not be available on the Java runtime -- use\n     * <code>java.nio.Charset#isSupported(String)</code> to determine if it is available.\n     *\n     * <p>A maximum of {@link #MAX_RECOMMENDED_BYTE_SIZE} will be read from the <code>InputStream</code>. The\n     * stream will not be closed and will not be repositioned after the bytes have been read. It is up to the calling\n     * method to use the <code>InputStream#mark()</code> and <code>InputStream#reset()</code> methods (if supported) \n     * or reopen the stream if needed.\n     *\n     * @param in the InputStream that supplies the bytes\n     * @return the best guess at the character encoding, null if there is none (not enough data or confidence)\n     * @throws IOException if an error occurred while reading the stream\n     */\n    public static String detectEncoding(InputStream in) throws IOException {\n        byte[] buf = BufferPool.getByteArray(MAX_RECOMMENDED_BYTE_SIZE);\n\n        try {\n            return detectEncoding(buf, 0, StreamUtils.readUpTo(in, buf));\n        }\n        finally {\n            BufferPool.releaseByteArray(buf);\n        }\n    }\n\n    public static String detectEncoding(PushbackInputStream in) throws IOException {\n        byte[] buf = BufferPool.getByteArray(MAX_RECOMMENDED_BYTE_SIZE);\n        try {\n            int readBytes = StreamUtils.readUpTo(in, buf);\n            String result = detectEncoding(buf, 0, readBytes);\n            in.unread(buf, 0, readBytes);\n            return result;\n        } finally {\n            BufferPool.releaseByteArray(buf);\n        }\n    }\n\n    /**\n     * Returns an array of encodings that can be detected by the <code>detectEncoding</code> methods.\n     * Note that some of the returned character encodings may not be available on the Java runtime.\n     *\n     * @return an array of encodings that can be detected by the <code>detectEncoding</code> methods.\n     */\n    public static String[] getDetectableEncodings() {\n        return CharsetDetector.getAllDetectableCharsets();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/FailSafePipedInputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.IOException;\nimport java.io.PipedInputStream;\nimport java.io.PipedOutputStream;\n\n/**\n * SafePipedInputStream is a <code>PipedInputStream</code> that can be safely interrupted when the thread that\n * pushes data to this stream has encountered an error. This can be done by calling\n * {@link #setExternalFailure(java.io.IOException)} with an <code>IOException</code> that will be thrown by\n * read/skip/available methods thereafter. This method ensures that the <code>IOException</code> will be propagated\n * to any thread that is currently executing a read/skip/available method of this class.\n *\n * <p>This class overcomes a limitation of <code>PipedInputStream</code> whose read/skip/available methods do not\n * throw an <code>IOException</code> when the stream is closed from another thread in the midst of their execution.\n *\n * @author Maxence Bernard\n */\npublic class FailSafePipedInputStream extends PipedInputStream {\n\n    /** An IOException to be thrown by read/skip/available methods */\n    private volatile IOException failure;\n\n\n    public FailSafePipedInputStream() {\n        super();\n    }\n\n    public FailSafePipedInputStream(int pipeSize) {\n        super(pipeSize);\n    }\n\n    public FailSafePipedInputStream(PipedOutputStream src) throws IOException {\n        super(src);\n    }\n\n    public FailSafePipedInputStream(PipedOutputStream src, int pipeSize) throws IOException {\n        super(src, pipeSize);\n    }\n\n    /**\n     * Sets an IOException to be subsequently thrown by <code>read</code>, <code>skip</code> and\n     * <code>available</code> methods. This method calls {@link #close()} to have any other thread blocked in a\n     * read/skip/available return immediately and throw the specified exception.\n     *\n     * @param failure the IOException to be thrown by read, skip and available methods, <code>null</code> for none\n     */\n    public void setExternalFailure(IOException failure) {\n        this.failure = failure;\n\n        if (failure!=null) {\n            // Close the PipedInputStream to have any other thread blocked in a read/skip/available return immediately\n            try {\n                super.close();\n            } catch(IOException ignore) {\n            }\n        }\n    }\n\n    /**\n     * Checks whether an external failure (IOException) has been registered and if has, throws it.\n     *\n     * @throws java.io.IOException if an external failure has been registered\n     */\n    protected void checkExternalFailure() throws IOException {\n        if (failure != null) {\n            throw failure;\n        }\n    }\n\n\n    @Override\n    public synchronized int read() throws IOException {\n        int ret = super.read();\n        checkExternalFailure();\n        return ret;\n    }\n\n    @Override\n    public synchronized int read(byte b[], int off, int len) throws IOException {\n        int ret = super.read(b, off, len);\n        checkExternalFailure();\n        return ret;\n    }\n\n    @Override\n    public synchronized int read(byte b[]) throws IOException {\n        int ret = super.read(b);\n        checkExternalFailure();\n        return ret;\n    }\n\n\n    @Override\n    public long skip(long n) throws IOException {\n        long ret = super.skip(n);\n        checkExternalFailure();\n        return ret;\n    }\n\n    @Override\n    public synchronized int available() throws IOException {\n        int ret = super.available();\n        checkExternalFailure();\n        return ret;\n    }\n\n    @Override\n    public void close() throws IOException {\n        super.close();\n        checkExternalFailure();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/FileTransferException.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.IOException;\n\n/**\n * FileTransferException is an IOException which can be thrown to indicate a file transfer error.\n * {@link #getReason() getReason()} returns the reason why the transfer failed.\n *\n * @author Maxence Bernard\n */\npublic class FileTransferException extends IOException {\n\n    /** Reason not known / not specified */\n    public final static int UNKNOWN_REASON = 0;\n\n    /** Source and destination files are identical */\n    public final static int SOURCE_AND_DESTINATION_IDENTICAL = 1;\n\n    /** Source file could not be opened for read */\n    public final static int OPENING_SOURCE = 2;\n\n    /** Destination file could not be opened for write */\n    public final static int OPENING_DESTINATION = 3;\n\n    /** An error occurred while reading the source file */\n    public final static int READING_SOURCE = 4;\n\n    /** An error occurred while writing the destination file */\n    public final static int WRITING_DESTINATION = 5;\n\n    /** An error occurred while deleting the source file (used when moving a file) */\n    public final static int DELETING_SOURCE = 6;\n\n    /** Source file could not be closed */\n    public final static int CLOSING_SOURCE = 7;\n\n    /** Destination file could not be closed */\n    public final static int CLOSING_DESTINATION = 8;\n\n    /** Destination file exists */\n    public final static int DESTINATION_EXISTS = 9;\n\n    /** File not found (does not exist) */\n    public final static int FILE_NOT_FOUND = 10;\n\n    /** Source file is a parent of the destination file */\n    public final static int SOURCE_PARENT_OF_DESTINATION = 11;\n\n    /** An error occurred while reading the destination file */\n    public final static int READING_DESTINATION = 12;\n\n    /** The checksum of the source and destination files don't match */\n    public final static int CHECKSUM_MISMATCH = 13;\n\n    /** The requested operation is not supported */\n    public final static int UNSUPPORTED_OPERATION = 14;\n\n    protected int reason;\n\n\tprivate final long bytesWritten;\n\n\n    public FileTransferException(int reason) {\n        this(reason, 0, null);\n    }\n\n    public FileTransferException(int reason, Throwable cause) {\n        this(reason, 0, cause);\n    }\n\n    public FileTransferException(int reason, long bytesWritten) {\n        this(reason, bytesWritten, null);\n    }\n\n    public FileTransferException(int reason, long bytesWritten, Throwable cause) {\n        super(cause);\n        this.reason = reason;\n        this.bytesWritten = bytesWritten;\n    }\n    \n    public int getReason() {\n        return reason;\n    }\n\n\tpublic long getBytesWritten() {\n\t\treturn bytesWritten;\n\t}\n\n\tpublic String toString() {\n        return super.toString()+\" reason=\"+reason;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/FilterRandomAccessInputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.IOException;\n\n/**\n * <code>FilterRandomAccessInputStream</code> implements {@link RandomAccessInputStream} by delegating all methods\n * to an existing <code>RandomAccessInputStream</code> instance. It allows to override selected methods and\n * filter the underlying <code>RandomAccessInputStream</code>.\n *\n * @see java.io.FilterInputStream\n * @author Maxence Bernard\n */\npublic class FilterRandomAccessInputStream extends RandomAccessInputStream {\n\n    /** The RandomAccessInputStream instance to proxy */\n    protected RandomAccessInputStream rais;\n\n    public FilterRandomAccessInputStream(RandomAccessInputStream rais) {\n        this.rais = rais;\n    }\n\n\n    @Override\n    public int read() throws IOException {\n        return rais.read();\n    }\n\n    @Override\n    public int read(byte[] b, int off, int len) throws IOException {\n        return rais.read(b, off, len);\n    }\n\n    @Override\n    public void close() throws IOException {\n        rais.close();\n    }\n\n    public long getOffset() throws IOException {\n        return rais.getOffset();\n    }\n\n    public long getLength() throws IOException {\n        return rais.getLength();\n    }\n\n    public void seek(long offset) throws IOException {\n        rais.seek(offset);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/FilteredOutputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * This class provides a proper implementation of an OutputStream filter.\n *\n * <p>Unlike <code>java.io.FilterOutputStream</code>, this method delegates all methods to an underlying OutputStream\n * and nothing more. In particular, {@link #write(byte[])} and {@link #write(byte[], int, int)} do <b>not</b>\n * call {@link #write(int)} repeatedly (very inefficient) but delegate to the corresponding OutputStream methods. This\n * makes this class much safer to use from a performance perspective than <code>java.io.FilteredOutputStream</code>.\n *\n * @author Maxence Bernard\n */\npublic class FilteredOutputStream extends OutputStream {\n\n    /** The underlying OutputStream to filter */\n    protected OutputStream out;\n\n    /**\n     * Creates a new FilteredOutputStream that delegates all methods to the provided OutputStream.\n     *\n     * @param out the underlying OutputStream to filter\n     */\n    public FilteredOutputStream(OutputStream out) {\n        this.out = out;\n    }\n\n\n    @Override\n    public void write(int b) throws IOException {\n        out.write(b);\n    }\n\n\n    @Override\n    public void write(byte[] b) throws IOException {\n        out.write(b);\n    }\n\n    @Override\n    public void write(byte[] b, int off, int len) throws IOException {\n        out.write(b, off, len);\n    }\n\n    @Override\n    public void flush() throws IOException {\n        out.flush();\n    }\n\n    @Override\n    public void close() throws IOException {\n        out.close();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/FilteredRandomOutputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.IOException;\n\n/**\n * FilteredRandomOutputStream is a filtered output stream for {@link RandomAccessOutputStream} subclasses, allowing\n * to easily extend the functionality provided by the stream by overriding only a few methods.\n * In a similar way to <code>java.io.FilteredOutputStream</code>, this class delegates all method calls to the\n * specified <code>RandomAccessOutputStream</code>.\n *\n * @author Maxence Bernard\n */\npublic class FilteredRandomOutputStream extends RandomAccessOutputStream {\n\n    /** The underlying RandomAccessOutputStream */\n    protected RandomAccessOutputStream raos;\n\n    \n    /**\n     * Creates a new FilteredRandomOutputStream that delegates all method calls to the specified\n     * <code>RandomAccessOutputStream</code>.\n     *\n     * @param raos the RandomAccessOutputStream to delegate method calls to\n     */\n    public FilteredRandomOutputStream(RandomAccessOutputStream raos) {\n        this.raos = raos;\n    }\n\n\n    /////////////////////////////////////////////\n    // RandomAccessOutputStream implementation //\n    /////////////////////////////////////////////\n    \n    @Override\n    public void write(int b) throws IOException {\n        raos.write(b);\n    }\n\n    @Override\n    public void write(byte b[]) throws IOException {\n        raos.write(b);\n    }\n\n    @Override\n    public void write(byte b[], int off, int len) throws IOException {\n        raos.write(b, off, len);\n    }\n\n    @Override\n    public void setLength(long newLength) throws IOException {\n        raos.setLength(newLength);\n    }\n\n    public long getOffset() throws IOException {\n        return raos.getOffset();\n    }\n\n    public long getLength() throws IOException {\n        return raos.getLength();\n    }\n\n    public void seek(long offset) throws IOException {\n        raos.seek(offset);\n    }\n\n    @Override\n    public void flush() throws IOException {\n        raos.flush();\n    }\n\n    @Override\n    public void close() throws IOException {\n        try {\n            flush();\n        }\n        catch(IOException e) {\n            // Try closing the stream anyway\n        }\n\n        raos.close();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/FixedByteArrayOutputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * <code>FixedByteArrayOutputStream</code> writes data to a pre-allocated byte array passed to the constructor.\n * <p>\n * This class is similar to {@link ByteArrayOutputStream} except that the byte array is pre-allocated and does not\n * grow when its capacity is reached. Attempts to write more bytes than the array's length will result in\n * {@link ArrayIndexOutOfBoundsException}. To prevent {@link ArrayIndexOutOfBoundsException} from being thrown,\n * a <code>FixedByteArrayOutputStream</code> can be wrapped around a {@link BoundedOutputStream} with a limit set to\n * the byte array's length.\n *\n * @author Maxence Bernard\n */\npublic class FixedByteArrayOutputStream extends OutputStream {\n\n    private byte[] bytes;\n    private int offset;\n\n    public FixedByteArrayOutputStream(byte bytes[]) {\n        this(bytes, 0);\n    }\n\n    public FixedByteArrayOutputStream(byte bytes[], int offset) {\n        this.bytes = bytes;\n        this.offset = offset;\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    @Override\n    public synchronized void write(int b) throws IOException {\n        bytes[offset++] = (byte)b;\n    }\n\n    @Override\n    public synchronized void write(byte[] b) throws IOException {\n        write(b, 0, b.length);\n    }\n\n    @Override\n    public synchronized void write(byte[] b, int off, int len) throws IOException {\n        System.arraycopy(b, off, bytes, offset, len);\n        offset += len;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/MultiOutputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.Enumeration;\nimport java.util.Vector;\n\n/**\n * <code>MultiOutputStream</code> 'multiplies' a stream by forwarding the data that is written to it to several\n * registered output streams. Similarily, {@link #flush()} and {@link #close()} call the same method on all registered\n * output streams.\n * Until one or more OutputStream is registered, this stream acts as a sink: all OutputStream operations are no-ops.\n *\n * @author Maxence Bernard\n */\npublic class MultiOutputStream extends OutputStream {\n\n    /** Registered OutputStreams */\n    protected Vector<OutputStream> streams = new Vector<>();\n\n    /**\n     * Creates a new MultiOutputStream that initially contains no OutputStream. * Until one or more OutputStream is\n     * registered, this stream acts as a sink: all OutputStream operations are no-ops.\n     */\n    public MultiOutputStream() {\n    }\n\n    /**\n     * Adds an <code>OutputStream</code> to the list of destination output streams. This method doesn't check whether\n     * the specified stream already exists in the list, so the same output stream may be added several times.\n     *\n     * @param out the OutputStream to add\n     */\n    public synchronized void addOutputStream(OutputStream out) {\n        streams.add(out);\n    }\n\n    /**\n     * Removes the first occurrence of the given <code>OutputStream</code> from the list of destination output streams.\n     * If the same stream was added several times, this method has to be called that many times to remove it entirely.\n     *\n     * @param out the OutputStream to add\n     */\n    public synchronized void removeOutputStream(OutputStream out) {\n        streams.remove(out);\n    }\n\n    /**\n     * Returns <code>true</code> if the specified stream is present in the list of destination output streams.\n     *\n     * @param out the OutputStream to look for\n     * @return <code>true</code> if the specified stream is present in the list of destination output streams\n     */\n    public synchronized boolean containsOutputStream(OutputStream out) {\n        return streams.contains(out);\n    }\n\n    /**\n     * Returns an {@link java.util.Enumeration} of the destination output streams. This instance should be synchronized\n     * externally to ensure that the list of streams is not modified while it is being enumerated.\n     *\n     * @return an {@link java.util.Enumeration} of the destination output streams\n     */\n    public synchronized Enumeration<OutputStream> enumOutputStream() {\n        return streams.elements();\n    }\n\n\n    /////////////////////////////////\n    // OutputStream implementation //\n    /////////////////////////////////\n\n    /**\n     * Calls <code>write(b)</code> with the specified byte on each of the destination output streams.\n     * <p>\n     * Any IOException thrown by any of the destination output stream is immediately re-thrown, aborting the write\n     * as a whole.\n     */\n    @Override\n    public synchronized void write(int b) throws IOException {\n        Enumeration<OutputStream> elements = streams.elements();\n        while(elements.hasMoreElements())\n            elements.nextElement().write(b);\n    }\n\n    /**\n     * Calls <code>write(b,off,len)</code> with the specified parameters on each of the destination output streams.\n     * <p>\n     * Any IOException thrown by any of the destination output stream is immediately re-thrown, aborting the operation\n     * as a whole.\n     */\n    @Override\n    public synchronized void write(byte b[], int off, int len) throws IOException {\n        Enumeration<OutputStream> elements = streams.elements();\n        while(elements.hasMoreElements())\n            elements.nextElement().write(b, off, len);\n    }\n\n    /**\n     * Calls <code>write(b)</code> with the specified parameter on each of the destination output streams.\n     * <p>\n     * Any IOException thrown by any of the destination output stream is immediately re-thrown, aborting the operation\n     * as a whole.\n     */\n    @Override\n    public synchronized void write(byte b[]) throws IOException {\n        Enumeration<OutputStream> elements = streams.elements();\n        while(elements.hasMoreElements())\n            elements.nextElement().write(b);\n    }\n\n    /**\n     * Calls <code>flush</code> on each of the destination output streams.\n     * <p>\n     * Any IOException thrown by any of the destination output stream is immediately re-thrown, aborting the operation\n     * as a whole.\n     */\n    @Override\n    public synchronized void flush() throws IOException {\n        Enumeration<OutputStream> elements = streams.elements();\n        while(elements.hasMoreElements())\n            elements.nextElement().flush();\n    }\n\n    /**\n     * Calls <code>close</code> on each of the destination output streams.\n     * <p>\n     * Any IOException thrown by any of the destination output stream is immediately re-thrown, aborting the operation\n     * as a whole.\n     */\n    @Override\n    public void close() throws IOException {\n        Enumeration<OutputStream> elements = streams.elements();\n        while(elements.hasMoreElements())\n            elements.nextElement().close();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/RandomAccess.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.IOException;\n\n/**\n * RandomAccess provides a common interface to random access streams, whether they be input or output streams.\n *\n * @author Maxence Bernard\n */\npublic interface RandomAccess {\n\n    /**\n     * Closes the random access stream and releases any system resources associated with the stream.\n     * A closed stream cannot perform input or output operations and cannot be reopened.\n     *\n     * @throws IOException if an I/O error occurs\n     */\n    void close() throws IOException;\n\n    /**\n     * Returns the offset (in bytes) from the beginning of the file at which the next read or write occurs.\n     *\n     * @return the offset (in bytes) from the beginning of the file at which the next read or write occurs\n     * @throws IOException if an I/O error occurs.\n     */\n    long getOffset() throws IOException;\n\n    /**\n     * Returns the length of the file, in bytes.\n     *\n     * @return the length of the file, in bytes\n     * @throws IOException if an I/O error occurs\n     */\n    long getLength() throws IOException;\n\n    /**\n     * Sets the offset, measured from the beginning of the file, at which the next read or write occurs.\n     * The offset may be set beyond the end of the file. Setting the offset beyond the end of the file does not change\n     * the file length. The file length will change only by writing after the offset has been set beyond the end of the\n     * file.\n     *\n     * @param offset the new offset position, measured in bytes from the beginning of the file\n     * @throws IOException if an I/O error occurs\n     */\n    void seek(long offset) throws IOException;\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/RandomAccessInputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * <code>RandomAccessInputStream</code> is an <code>InputStream</code> with random access.\n *\n * The following <code>java.io.InputStream</code> methods are overridden to provide an improved implementation:\n * <ul>\n *   <li>{@link #mark(int)}</li>\n *   <li>{@link #reset()}</li>\n *   <li>{@link #markSupported()}</li>\n *   <li>{@link #skip(long)}</li>\n *   <li>{@link #available()}</li>\n * </ul>\n *\n * <b>Important:</b> <code>BufferedInputStream</code> or any wrapper <code>InputStream</code> class that uses a read buffer\n * CANNOT be used with a <code>RandomAccessInputStream</code> if the {@link #seek(long)} method is to be used. Doing so\n * would corrupt the read buffer and yield to data inconsistencies.\n *\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic abstract class RandomAccessInputStream extends InputStream implements RandomAccess {\n    /** Logger used by this class. */\n    private static final Logger LOGGER = LoggerFactory.getLogger(RandomAccessInputStream.class);\n\n    /** The last offset set by {@link #mark(int)} */\n    private long markOffset;\n\n\n    /**\n     * Creates a new RandomAccessInputStream.\n     */\n    public RandomAccessInputStream() {\n    }\n\n\n    /**\n     * Reads <code>b.length</code> bytes from this file into the byte array, starting at the current file pointer.\n     * This method reads repeatedly from the file until the requested number of bytes are read. This method blocks until\n     * the requested number of bytes are read, the end of the stream is detected, or an exception is thrown.\n     *\n     * @param b the buffer into which the data is read.\n     * @throws java.io.EOFException if this file reaches the end before reading all the bytes.\n     * @throws IOException if an I/O error occurs.\n     */\n    public void readFully(byte b[]) throws IOException {\n        StreamUtils.readFully(this, b, 0, b.length);\n    }\n\n    /**\n     * Reads exactly <code>len</code> bytes from this file into the byte array, starting at the current file pointer.\n     * This method reads repeatedly from the file until the requested number of bytes are read. This method blocks until\n     * the requested number of bytes are read, the end of the stream is detected, or an exception is thrown.\n     *\n     * @param b the buffer into which the data is read.\n     * @param off the start offset of the data.\n     * @param len the number of bytes to read.\n     * @throws java.io.EOFException  if this file reaches the end before reading all the bytes.\n     * @throws IOException if an I/O error occurs.\n     */\n    public void readFully(byte b[], int off, int len) throws IOException {\n        StreamUtils.readFully(this, b, off, len);\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    /**\n     * Skips (up to) the specified number of bytes and returns the number of bytes effectively skipped.\n     * The exact given number of bytes will be skipped as long as the current offset as returned by {@link #getOffset()}\n     * plus the number of bytes to skip doesn't exceed the length of this stream as returned by {@link #getLength()}.\n     * If it does, all the remaining bytes will be skipped so that the offset of this stream will be positioned to\n     * {@link #getLength()}.\n     * Returns <code>-1</code> if the offset is already positioned to the end of the stream when this method is called.\n     *\n     * @param n number of bytes to skip\n     * @return the number of bytes that have effectively been skipped, -1 if the offset is already positioned to the\n     * end of the stream when this method is called (FIXME this violates the definition of the return value\n     * of java.io.InputStream (&gt;=0) and therefore it cannot be used as an InputStream as it should when extending it.\n     * Either this class should not extend InputStream or it should not return a negative value)\n     * @throws IOException if something went wrong\n     */\n    @Override\n    public long skip(long n) throws IOException {\n        if (n <= 0) {\n            return 0;\n        }\n\n        long offset = getOffset();\n        long length = getLength();\n\n        // Return -1 if the offset is already at the end of the stream\n        if(offset>=length)\n            return -1;\n\n        // Makes sure not to go beyond the end of the stream\n        long newOffset = offset + n;\n        if (newOffset > length)\n            newOffset = length;\n\n        // Seek to the new offset\n        seek(newOffset);\n\n        // Return the actual number of bytes skipped\n        return (int) (newOffset - offset);\n    }\n\n    /**\n     * Return the number of bytes that are available for reading, that is: {@link #getLength()} - {@link #getOffset()} - 1.\n     * Since <code>InputStream.available()</code> returns an int and this method overrides it, a maximum of\n     * <code>Integer.MAX_VALUE</code> can be returned, even if this stream has more bytes available.\n     *\n     * @return the number of bytes that are available for reading.\n     * @throws IOException if something went wrong\n     */\n    @Override\n    public int available() throws IOException {\n        return (int)(getLength() - getOffset() - 1);\n    }\n\n    /**\n     * Overrides <code>InputStream.mark()</code> to provide a working implementation of the method. The given readLimit\n     * is simply ignored, the stream can be repositioned using {@link #reset()} with no limit on the number of bytes\n     * read after <code>mark()</code> has been called.\n     *\n     * @param readLimit this parameter has no effect and is simply ignored\n     */\n    @Override\n    public synchronized void mark(int readLimit) {\n        try {\n\t\t\tthis.markOffset = getOffset();\n\t\t} catch (IOException e) {\n            LOGGER.info(\"Caught exception\", e);\n\t\t}\n    }\n\n    /**\n     * Overrides <code>InputStream.mark()</code> to provide a working implementation of the method.\n     *\n     * @throws IOException if something went wrong\n     */\n    @Override\n    public synchronized void reset() throws IOException {\n        seek(this.markOffset);\n    }\n\n    /**\n     * Always returns <code>true</code>: {@link #mark(int)} and {@link #reset()} methods are supported.\n     */\n    @Override\n    public boolean markSupported() {\n        return true;\n    }\n\n\n    //////////////////////\n    // Abstract methods //\n    //////////////////////\n\n    /**\n     * Reads up to <code>len</code> bytes of data from this file into an array of bytes. This method blocks until at\n     * least one byte of input is available.\n     *\n     * @param b the buffer into which the data is read\n     * @param off the start offset of the data\n     * @param len the maximum number of bytes read\n     * @return the total number of bytes read into the buffer, or -1 if there is no more data because the end of the\n     * file has been reached.\n     * @throws IOException if an I/O error occurs\n     */\n    @Override\n    public abstract int read(byte b[], int off, int len) throws IOException;\n\n    /**\n     * Closes this stream and releases any system resources associated with the stream.\n     * A closed stream cannot perform input operations and cannot be reopened.\n     *\n     * @throws IOException if an I/O error occurs.\n     */\n    @Override\n    public abstract void close() throws IOException;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/RandomAccessOutputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * <code>RandomAccessOutputStream</code> is an <code>OutputStream</code> with random access.\n *\n * <b>Important:</b> <code>BufferedOutputStream</code> or any class wrapping a standard <code>OutputStream</code>\n * and using an internal buffer CANNOT be used with a <code>RandomAccessInputStream</code> if the {@link #seek(long)}\n * method is to be used. Doing so would corrupt the write buffer and yield to data inconsistencies.\n * {@link BufferedRandomOutputStream} provides safe buffering to RandomAccessOutputStream.\n *\n * @author Maxence Bernard\n */\npublic abstract class RandomAccessOutputStream extends OutputStream implements RandomAccess {\n\n    /**\n     * Creates a new RandomAccessOutputStream.\n     */\n    public RandomAccessOutputStream() {\n    }\n\n\n    //////////////////////\n    // Abstract methods //\n    //////////////////////\n\n    /**\n     * Writes <code>b.length</code> bytes from the specified byte array to this file, starting at the current file offset.\n     *\n     * @param b the data to write\n     * @throws IOException if an I/O error occurs\n     */\n    @Override\n    public abstract void write(byte b[]) throws IOException;\n\n    /**\n     * Writes <code>len</code> bytes from the specified byte array starting at offset <code>off</code> to this file.\n     *\n     * @param b the data to write\n     * @param off the start offset in the data array\n     * @param len the number of bytes to write\n     * @throws IOException if an I/O error occurs\n     */\n    @Override\n    public abstract void write(byte b[], int off, int len) throws IOException;\n\n    /**\n     * Sets the length of the file.\n     *\n     * <p>If the present length of the file as returned by the {@link #getLength()} method is greater than the\n     * <code>newLength</code> argument then the file will be truncated. In this case, if the file offset as returned\n     * by the {@link #getOffset()} method is greater than <code>newLength</code> then the\n     * offset will be equal to <code>newLength</code> after this method returns.\n     *\n     * <p>If the present length of the file as returned by the {@link #getLength()} method is smaller than the \n     * <code>newLength</code> argument then the file will be extended. In this case, the contents of the extended\n     * portion of the file are not defined.\n     *\n     * @param newLength the new file's length\n     * @throws IOException If an I/O error occurred while trying to change the file's length\n     */\n    public abstract void setLength(long newLength) throws IOException;\n\n    /**\n     * Closes this stream and releases any system resources associated with the stream.\n     * A closed stream cannot perform output operations and cannot be reopened.\n     *\n     * @throws IOException if an I/O error occurs.\n     */\n    @Override\n    public abstract void close() throws IOException;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/RandomGeneratorInputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Random;\n\n/**\n * This stream allows random data generated with a {@link Random} instance to be read.\n * It can be instantiated in one of the three following ways:\n * <ul>\n *   <li>with a pseudo-unique seed, leading to different random data being generated from one instance of this class\n * to the other.</li>\n *   <li>with a specified seed, leading to the same random data being generated from one instance of this class to\n * the other.</li>\n *   <li>with a specified {@link Random}.</li>\n * </ul>\n *\n * @author Maxence Bernard\n */\npublic class RandomGeneratorInputStream extends InputStream {\n\n    /** Random data generator. */\n    protected Random random;\n\n\n    public RandomGeneratorInputStream() {\n        this(new Random());\n    }\n\n    public RandomGeneratorInputStream(long seed) {\n        this(new Random(seed));\n    }\n\n    public RandomGeneratorInputStream(Random random) {\n        this.random = random;\n    }\n\n\n    @Override\n    public int read() throws IOException {\n        return random.nextInt();\n    }\n\n\n    @Override\n    public int read(byte[] b) throws IOException {\n        random.nextBytes(b);\n        return b.length;\n    }\n\n    @Override\n    public int read(byte[] b, int off, int len) throws IOException {\n        int end = off+len;\n        for(int i=off; i<end; i++)\n            b[i] = (byte)random.nextInt();\n\n        return len;\n    }\n\n    /**\n     * Always returns {@link Integer#MAX_VALUE}: this stream is bottomless.\n     */\n    @Override\n    public int available() {\n        return Integer.MAX_VALUE;\n    }\n\n    // Note: use the default skip implementation, which calls read\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/SilenceableOutputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.FilterOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * <code>SilenceableOutStream</code> is a {@link FilterOutputStream} that forwards data to an underlying\n * {@link OutputStream} and that can be silenced on demand.\n * The {@link #setSilenced(boolean)} method allows to control whether the data written to the stream should go through\n * (be written to the underlying stream) or be discarded.\n *\n * @author Maxence Bernard\n */\npublic class SilenceableOutputStream extends FilterOutputStream {\n\n    /** When true, write methods are no-op */\n    private boolean silenced;\n\n\n    /**\n     * Creates a new <code>SilenceableOutputStream</code> that forwards written data to the specified\n     * <code>OutputStream</code> when not silenced. By default, this <code>SilenceableOutputStream</code> is not\n     * silenced.\n     *\n     * @param out the OutputStream to forward the data written to when not silenced  \n     */\n    public SilenceableOutputStream(OutputStream out) {\n        super(out);\n    }\n\n    /**\n     * Creates a new <code>SilenceableOutputStream</code> that forwards written data to the specified\n     * <code>OutputStream</code> when not silenced.\n     *\n     * @param out the OutputStream to forward the data written to when not silenced\n     * @param silenced initial silenced state\n     */\n    public SilenceableOutputStream(OutputStream out, boolean silenced) {\n        super(out);\n        this.silenced = silenced;\n    }\n\n    /**\n     * Controls whether the data written to the stream goes through (be written to the underlying stream) or\n     * discarded. If called with <code>false</code>, any subsequent call to <code>write</code> methods will be\n     * ignored (they become no-op), until this method is called again with <code>false</code>. Note that un-silencing\n     * this stream will not print messages that were previously written while the stream was silenced.\n     *\n     * @param silenced <code>true</code> to have <code>write</code> methods become no-ops, <code>false</code> to have\n     * them forward the data to the underlying <code>OutputStream</code>\n     */\n    public void setSilenced(boolean silenced) {\n        this.silenced = silenced;\n    }\n\n    /**\n     * Returns <code>true</code> if this <code>SilenceableOutStream</code> is currently ignoring calls to\n     * <code>write</code> methods, <code>false</code> if it is forwarding written data to the\n     * underlying <code>OutputStream</code>.\n     *\n     * @return <code>true</code> if this <code>SilenceableOutStream</code> is currently ignoring calls to\n     * <code>write</code>, <code>false</code> if it is forwarding written data to the\n     * underlying <code>OutputStream</code>.\n     */\n    public boolean isSilenced() {\n        return silenced;\n    }\n\n\n    @Override\n    public void write(int b) throws IOException {\n        if(silenced)\n            return;\n\n        out.write(b);\n    }\n\n    @Override\n    public void write(byte[] b) throws IOException {\n        if(silenced)\n            return;\n\n        out.write(b);\n    }\n\n    @Override\n    public void write(byte[] b, int off, int len) throws IOException {\n        if(silenced)\n            return;\n\n        out.write(b, off, len);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/SinkOutputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * SinkOutputStream is an OutputStream which implements all <code>write()</code> methods as no-ops, loosing data as\n * it gets written, in a similar fashion to the <code>/dev/null</code> UNIX device.\n *\n * @author Maxence Bernard\n */\npublic class SinkOutputStream extends OutputStream {\n\n    @Override\n    public void write(int i) throws IOException {\n    }\n\n    /**\n     * Overridden for performance reasons.\n     */\n    @Override\n    public void write(byte[] bytes) throws IOException {\n    }\n\n    /**\n     * Overridden for performance reasons.\n     */\n    @Override\n    public void write(byte[] bytes, int off, int len) throws IOException {\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/StreamOutOfBoundException.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.IOException;\n\n/**\n * This <code>IOException</code> can be used when attempting to read from a {@link BoundedInputStream} or\n * {@link BoundedReader} beyond the byte or character limit set.\n *\n * @see com.mucommander.commons.io.BoundedInputStream\n * @see com.mucommander.commons.io.BoundedOutputStream\n * @see com.mucommander.commons.io.BoundedReader\n * @author Maxence Bernard\n */\npublic class StreamOutOfBoundException extends IOException {\n    \n    public StreamOutOfBoundException(long limit) {\n        super(\"Attempt to read out of bounds, limit=\"+limit);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/StreamUtils.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.*;\n\n/**\n * This class provides convenience static methods that operate on streams. All read/write buffers are allocated using\n * {@link BufferPool} for memory efficiency reasons.\n *\n * @author Maxence Bernard\n */\npublic class StreamUtils {\n\n    /**\n     * This method is a shorthand for {@link #copyStream(java.io.InputStream, java.io.OutputStream, int)} called with a\n     * {@link BufferPool#getDefaultBufferSize() default buffer size}.\n     *\n     * @param in the InputStream to read from\n     * @param out the OutputStream to write to\n     * @return the number of bytes that were copied\n     * @throws FileTransferException if something went wrong while reading from or writing to one of the provided streams\n     */\n    public static long copyStream(InputStream in, OutputStream out) throws FileTransferException {\n        return copyStream(in, out, BufferPool.getDefaultBufferSize());\n    }\n\n    /**\n     * This method is a shorthand for {@link #copyStream(java.io.InputStream, java.io.OutputStream, int, long)} called\n     * with a {@link Long#MAX_VALUE}.\n     *\n     * @param in the InputStream to read from\n     * @param out the OutputStream to write to\n     * @param bufferSize size of the buffer to use, in bytes\n     * @return the number of bytes that were copied\n     * @throws FileTransferException if something went wrong while reading from or writing to one of the provided streams\n     */\n    public static long copyStream(InputStream in, OutputStream out, int bufferSize) throws FileTransferException {\n        return copyStream(in, out, bufferSize, Long.MAX_VALUE);\n    }\n    \n    /**\n     * Shorthand for {@link #copyStream(InputStream, OutputStream, byte[], long)} called with a buffer of the specified\n     * size retrieved from {@link BufferPool}.\n     *\n     * @param in the InputStream to read from\n     * @param out the OutputStream to write to\n     * @param bufferSize size of the buffer to use, in bytes\n     * @param length number of bytes to copy from InputStream\n     * @return the number of bytes that were copied\n     * @throws FileTransferException if something went wrong while reading from or writing to one of the provided streams\n     */\n    public static long copyStream(InputStream in, OutputStream out, int bufferSize, long length) throws FileTransferException {\n        // Use BufferPool to reuse any available buffer of the same size\n        byte[] buffer = BufferPool.getByteArray(bufferSize);\n        try {\n            return copyStream(in, out, buffer, length);\n        } finally {\n            // Make the buffer available for further use\n            BufferPool.releaseByteArray(buffer);\n        }\n    }\n    \n    /**\n     * Copies up to {@code length} bytes from the given {@code InputStream} to the specified\n     * {@code OutputStream}, less if the end-of-file was reached before that.\n     * This method does *NOT* close any of the given streams.\n     *\n     * <p>Read and write operations use the specified buffer, making the use of a {@code BufferedInputStream}\n     * unnecessary. A {@code BufferedOutputStream} also isn't necessary, unless this method\n     * is called repeatedly with the same {@code OutputStream} and with potentially small {@code InputStream}\n     * (smaller than the buffer's size): in this case, providing a {@code BufferedOutputStream} will further\n     * improve performance by grouping calls to the underlying {@code OutputStream} write method.\n     *\n     * <p>Copy progress can optionally be monitored by supplying a {@link com.mucommander.commons.io.CounterInputStream} and/or\n     * {@link com.mucommander.commons.io.CounterOutputStream}.\n     *\n     * @param in the InputStream to read from\n     * @param out the OutputStream to write to\n     * @param buffer buffer to use for copying\n     * @param length number of bytes to copy from InputStream\n     * @return the number of bytes that were copied\n     * @throws FileTransferException if something went wrong while reading from or writing to one of the provided streams\n     */\n    public static long copyStream(InputStream in, OutputStream out, byte[] buffer, long length) throws FileTransferException {\n        // Copies the InputStream's content to the OutputStream chunk by chunk\n        long totalRead = 0;\n\n        int failureCounter = 0;\n        while (length > 0) {\n            int nbRead;\n            try {\n                nbRead = in.read(buffer, 0, (int)Math.min(buffer.length, length));\t// the result of min will be int\n            } catch(IOException e) {\n                throw new FileTransferException(FileTransferException.READING_SOURCE);\n            }\n\n            if (nbRead < 0) {\n                break;\n            } else if (nbRead == 0) {\n                failureCounter++;\n                if (failureCounter > 10) {\n                    throw new FileTransferException(FileTransferException.UNKNOWN_REASON);\n                }\n                sleepIfNoRead();\n            } else {\n                failureCounter = 0;\n            }\n\n            try {\n                out.write(buffer, 0, nbRead);\n            } catch(IOException e) {\n                throw new FileTransferException(FileTransferException.WRITING_DESTINATION, totalRead);\n            }\n\n            length -= nbRead;\n            totalRead += nbRead;\n        }\n\n        return totalRead;\n    }\n\n    /**\n     * This method is a shorthand for {@link #transcode(java.io.InputStream, String, java.io.OutputStream, String, int)}\n     * called with a {@link BufferPool#getDefaultBufferSize() default buffer size}.\n     *\n     * @param in the InputStream to read from\n     * @param inCharset the source charset\n     * @param out the OutputStream to write to\n     * @param outCharset the destination charset\n     * @return the number of bytes that were transcoded\n     * @throws FileTransferException if something went wrong while reading from or writing to one of the provided streams\n     * @throws UnsupportedEncodingException if any of the two charsets are not supported by the JVM\n     */\n    public static long transcode(InputStream in, String inCharset, OutputStream out, String outCharset) throws FileTransferException, UnsupportedEncodingException {\n        return transcode(in, inCharset, out, outCharset, BufferPool.getDefaultBufferSize());\n    }\n\n    /**\n     * Converts a stream from a charset to another, copying the contents of the given <code>InputStream</code> to the\n     * <code>OutputStream</code>. A {@link java.io.UnsupportedEncodingException} is thrown if any of the two charsets\n     * are not supported by the JVM.\n     *\n     * <p>Apart from the transcoding part, this method operates exactly like {@link #copyStream(java.io.InputStream, java.io.OutputStream, int)}.\n     * In particular, none of the given streams are closed.\n     *\n     * @param in the InputStream to read the data from\n     * @param inCharset the source charset\n     * @param out the OutputStream to write to\n     * @param outCharset the destination charset\n     * @param bufferSize size of the buffer to use, in bytes\n     * @return the number of bytes that were transcoded\n     * @throws FileTransferException if something went wrong while reading from or writing to one of the provided streams\n     * @throws UnsupportedEncodingException if any of the two charsets are not supported by the JVM\n     * @see #copyStream(java.io.InputStream, java.io.OutputStream, int)\n     * @see java.nio.charset.Charset#isSupported(String)\n     */\n    public static long transcode(InputStream in, String inCharset, OutputStream out, String outCharset, int bufferSize) throws FileTransferException, UnsupportedEncodingException {\n        InputStreamReader isr = new InputStreamReader(in, inCharset);\n        OutputStreamWriter osw = new OutputStreamWriter(out, outCharset);\n\n        // Use BufferPool to reuse any available buffer of the same size\n        char[] buffer = BufferPool.getCharArray(bufferSize);\n        try {\n            // Copies the InputStreamReader's content to the OutputStreamWriter chunk by chunk\n            int nbRead;\n            long totalRead = 0;\n            int failureCounter = 0;\n            while (true) {\n                try {\n                    nbRead = isr.read(buffer, 0, buffer.length);\n                } catch(IOException e) {\n                    throw new FileTransferException(FileTransferException.READING_SOURCE);\n                }\n\n                if (nbRead < 0) {\n                    break;\n                } else if (nbRead == 0) {\n                    failureCounter++;\n                    if (failureCounter > 10) {\n                        throw new FileTransferException(FileTransferException.UNKNOWN_REASON);\n                    }\n                    sleepIfNoRead();\n                } else {\n                    failureCounter = 0;\n                }\n\n                try {\n                    osw.write(buffer, 0, nbRead);\n                    // Let's not forget to flush as the writer will *not* be closed (to avoid closing the OutputStream)\n                    osw.flush();\n                } catch(IOException e) {\n                    throw new FileTransferException(FileTransferException.WRITING_DESTINATION);\n                }\n\n                totalRead += nbRead;\n            }\n\n            return totalRead;\n        } finally {\n            // Make the buffer available for further use\n            BufferPool.releaseCharArray(buffer);\n        }\n    }\n\n    /**\n     * This method is a shorthand for {@link #fillWithConstant(java.io.OutputStream, byte, long, int)} called with a\n     * {@link BufferPool#getDefaultBufferSize default buffer size}.\n     *\n     * @param out the OutputStream to write to\n     * @param value the byte constant to write len times\n     * @param len number of bytes to write\n     * @throws java.io.IOException if an error occurred while writing\n     */\n    public static void fillWithConstant(OutputStream out, byte value, long len) throws IOException {\n        fillWithConstant(out, value, len, BufferPool.getDefaultBufferSize());\n    }\n\n    /**\n     * Writes the specified byte constant <code>len</code> times to the given <code>OutputStream</code>.\n     * This method does *NOT* close the stream when it is finished.\n     *\n     * @param out the OutputStream to write to\n     * @param value the byte constant to write len times\n     * @param len number of bytes to write\n     * @param bufferSize size of the buffer to use, in bytes\n     * @throws java.io.IOException if an error occurred while writing\n     */\n    public static void fillWithConstant(OutputStream out, byte value, long len, int bufferSize) throws IOException {\n        // Use BufferPool to avoid excessive memory allocation and garbage collection\n        byte[] buffer = BufferPool.getByteArray(bufferSize);\n\n        // Fill the buffer with the constant byte value, not necessary if the value is zero\n        if (value != 0) {\n            for (int i = 0; i < bufferSize; i++) {\n                buffer[i] = value;\n            }\n        }\n\n        try {\n            long remaining = len;\n            while (remaining > 0) {\n                int nbWrite = (int)(remaining > bufferSize ? bufferSize : remaining);\n                out.write(buffer, 0, nbWrite);\n                remaining -= nbWrite;\n            }\n        } finally {\n            BufferPool.releaseByteArray(buffer);\n        }\n    }\n\n    /**\n     * This method is a shorthand for {@link #copyChunk(RandomAccessInputStream, RandomAccessOutputStream, long, long, long, int)}\n     * called with a {@link BufferPool#getDefaultBufferSize default buffer size}.\n     *\n     * @param rais the source stream\n     * @param raos the destination stream\n     * @param srcOffset offset to the beginning of the chunk in the source stream\n     * @param destOffset offset to the beginning of the chunk in the destination stream\n     * @param length number of bytes to copy\n     * @throws java.io.IOException if an error occurred while copying data\n     */\n    public static void copyChunk(RandomAccessInputStream rais, RandomAccessOutputStream raos, long srcOffset, long destOffset, long length) throws IOException {\n        copyChunk(rais, raos, srcOffset, destOffset, length, BufferPool.getDefaultBufferSize());\n    }\n\n    /**\n     * Copies a chunk of data from the given {@link com.mucommander.commons.io.RandomAccessInputStream} to the specified\n     * {@link com.mucommander.commons.io.RandomAccessOutputStream}.\n     *\n     * @param rais the source stream\n     * @param raos the destination stream\n     * @param srcOffset offset to the beginning of the chunk in the source stream\n     * @param destOffset offset to the beginning of the chunk in the destination stream\n     * @param length number of bytes to copy\n     * @param bufferSize size of the buffer to use, in bytes\n     * @throws java.io.IOException if an error occurred while copying data\n     */\n    public static void copyChunk(RandomAccessInputStream rais, RandomAccessOutputStream raos, long srcOffset, long destOffset, long length, int bufferSize) throws IOException {\n        rais.seek(srcOffset);\n        raos.seek(destOffset);\n\n        // Use BufferPool to avoid excessive memory allocation and garbage collection\n        byte[] buffer = BufferPool.getByteArray(bufferSize);\n\n        try {\n            long remaining = length;\n            while (remaining>0) {\n                int nbBytes = (int)(remaining < bufferSize ? remaining : bufferSize);\n                rais.readFully(buffer, 0, nbBytes);\n                raos.write(buffer, 0, nbBytes);\n                remaining -= nbBytes;\n            }\n        } finally {\n            BufferPool.releaseByteArray(buffer);\n        }\n    }\n\n\n    /**\n     * This method is a shorthand for {@link #readFully(java.io.InputStream, byte[], int, int)}.\n     *\n     * @param in the InputStream to read from\n     * @param b the buffer into which the stream data is copied\n     * @return the same byte array that was passed, returned only for convenience\n     * @throws java.io.EOFException if EOF is reached before all bytes have been read\n     * @throws IOException if an I/O error occurs\n     */\n    public static byte[] readFully(InputStream in, byte[] b) throws EOFException, IOException {\n        return readFully(in, b, 0, b.length);\n    }\n\n    /**\n     * Reads exactly <code>len</code> bytes from the <code>InputStream</code> and copies them into the byte array,\n     * starting at position <code>off</code>.\n     *\n     * <p>This method calls the <code>read()</code> method of the given stream until the requested number of bytes have\n     * been skipped, or throws an {@link EOFException} if the end of file has been reached prematurely.\n     *\n     * @param in the InputStream to read from\n     * @param b the buffer into which the stream data is copied\n     * @param off specifies where the copy should start in the buffer\n     * @param len the number of bytes to read\n     * @return the same byte array that was passed, returned only for convenience\n     * @throws java.io.EOFException if EOF is reached before all bytes have been read\n     * @throws IOException if an I/O error occurs\n     */\n    public static byte[] readFully(InputStream in, byte[] b, int off, int len) throws EOFException, IOException {\n        if (len > 0) {\n            int totalRead = 0;\n            int failureCounter = 0;\n            do {\n                int nbRead = in.read(b, off + totalRead, len - totalRead);\n                if (nbRead < 0) {\n                    throw new EOFException();\n                } else if (nbRead == 0) {\n                    failureCounter++;\n                    if (failureCounter > 10) {\n                        throw new FileTransferException(FileTransferException.UNKNOWN_REASON);\n                    }\n                    sleepIfNoRead();\n                } else {\n                    failureCounter = 0;\n                }\n                totalRead += nbRead;\n            }\n            while (totalRead < len);\n        }\n\n        return b;\n    }\n\n    /**\n     * Skips exactly <code>n</code>bytes from the given InputStream.\n     *\n     * <p>This method calls the <code>skip()</code> method of the given stream until the requested number of bytes have\n     * been skipped, or throws an {@link EOFException} if the end of file has been reached prematurely.\n     *\n     * @param in the InputStream to skip bytes from\n     * @param n the number of bytes to skip\n     * @throws java.io.EOFException if the EOF is reached before all bytes have been skipped\n     * @throws java.io.IOException if an I/O error occurs\n     */\n    public static void skipFully(InputStream in, long n) throws IOException {\n        if (n <= 0) {\n            return;\n        }\n        int failureCounter = 0;\n        do {\n            long nbSkipped = in.skip(n);\n            if (nbSkipped < 0) {\n                throw new EOFException();\n            } else if (nbSkipped == 0) {\n                failureCounter++;\n                if (failureCounter > 10) {\n                    throw new IOException(\"skipFully timeout\");\n                }\n                sleepIfNoRead();\n            } else {\n                failureCounter = 0;\n            }\n            n -= nbSkipped;\n        } while (n > 0);\n    }\n\n    private static void sleepIfNoRead() {\n        try {\n            Thread.sleep(50);\n        } catch (InterruptedException ignore) {}\n    }\n\n    /**\n     * This method is a shorthand for {@link #readUpTo(java.io.InputStream, byte[], int, int) readUpTo(in, b, 0, b.length)}.\n     *\n     * @param in the InputStream to read from\n     * @param b the buffer into which the stream data is copied\n     * @return the number of bytes that have been read, can be less than len if EOF has been reached prematurely\n     * @throws IOException if an I/O error occurs\n     */\n    public static int readUpTo(InputStream in, byte[] b) throws IOException {\n        return readUpTo(in, b, 0, b.length);\n    }\n\n    /**\n     * Reads up to <code>len</code> bytes from the <code>InputStream</code> and copies them into the byte array,\n     * starting at position <code>off</code>.\n     *\n     * <p>This method differs from {@link #readFully(java.io.InputStream, byte[], int, int)} in that it does not throw\n     * a <code>java.io.EOFException</code> if the end of stream is reached before all bytes have been read. In that\n     * case (and in that case only), the number of bytes returned by this method will be lower than <code>len</code>.\n     *\n     * @param in the InputStream to read from\n     * @param b the buffer into which the stream data is copied\n     * @param off specifies where the copy should start in the buffer\n     * @param len the number of bytes to read\n     * @return the number of bytes that have been read, can be less than len if EOF has been reached prematurely\n     * @throws IOException if an I/O error occurs\n     */\n    public static int readUpTo(InputStream in, byte[] b, int off, int len) throws IOException {\n        int totalRead = 0;\n        int failureCounter = 0;\n        if (len > 0) {\n            do {\n                int nbRead = in.read(b, off + totalRead, len - totalRead);\n                if (nbRead < 0) {\n                    break;\n                } else if (nbRead == 0) {\n                    failureCounter++;\n                    if (failureCounter > 10) {\n                        throw new FileTransferException(FileTransferException.UNKNOWN_REASON);\n                    }\n                    sleepIfNoRead();\n                } else {\n                    failureCounter = 0;\n                }\n                totalRead += nbRead;\n            } while (totalRead < len);\n        }\n\n        return totalRead;\n    }\n\n\n    /**\n     * This method is a shorthand for {@link #readUntilEOF(java.io.InputStream, int)} called with a\n     * {@link BufferPool#getDefaultBufferSize default buffer size}.\n     *\n     * @param in the InputStream to read\n     * @throws IOException if an I/O error occurs\n     */\n    public static void readUntilEOF(InputStream in) throws IOException {\n        readUntilEOF(in, BufferPool.getDefaultBufferSize());\n    }\n\n    /**\n     * This method reads the given InputStream until the End Of File is reached, discarding all the data that is read\n     * in the process. It is noteworthy that this method does <b>not</b> close the stream.\n     *\n     * @param in the InputStream to read\n     * @param bufferSize size of the read buffer\n     * @throws IOException if an I/O error occurs\n     */\n    public static void readUntilEOF(InputStream in, int bufferSize) throws IOException {\n        // TUse BufferPool to avoid excessive memory allocation and garbage collection\n        byte[] buffer = BufferPool.getByteArray(bufferSize);\n\n        try {\n            int failureCounter = 0;\n            while (true) {\n                int nbRead = in.read(buffer, 0, buffer.length);\n                if (nbRead < 0) {\n                    break;\n                } else if (nbRead == 0) {\n                    failureCounter++;\n                    if (failureCounter > 10) {\n                        throw new FileTransferException(FileTransferException.UNKNOWN_REASON);\n                    }\n                    sleepIfNoRead();\n                } else {\n                    failureCounter = 0;\n                }\n            }\n        } finally {\n            BufferPool.releaseByteArray(buffer);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/ThroughputLimitInputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * ThroughputLimitInputStream extends InputStream to provide control over the transfer speed and limit it to a specified\n * number of bytes per second. \n * Whenever the bytes per second quota has been reached, the read and skip methods will lock and won't return\n * until either:\n * <p><ul><li>a new second commences, bringing the bytes read count back to zero for the new second</li>\n * <li>{@link #setThroughputLimit(long)} is called with a more permissive bytes per second value (different from 0),\n * yielding to more bytes available for the current second.</li>\n * </ul><p>\n *\n * <p>Setting the throughput limit to 0 effectively blocks all read and skip calls indefinitely.\n * Any calls to the read or skip methods will lock, the only way to remove this lock being to call the\n * {@link #setThroughputLimit(long)} method with a value different from 0 from another thread.\n *\n * <p>Setting the throughput limit to -1 or any other negative values will disable any limit and make\n * this ThroughputLimitInputStream behave just like a normal InputStream.\n *\n * <p>Finally, the {@link #setUnderlyingInputStream(java.io.InputStream)} method allows to use the\n * same ThroughputLimitInputStream instance for multiple InputStream instances, keeping the bytes count for the\n * current second intact and thus the throughput limit stable. This does not hold true if a new ThroughputLimitInputStream\n * is created for each InputStream, the bytes count for the current second starting at 0.  \n *\n * @author Maxence Bernard\n */\npublic class ThroughputLimitInputStream extends InputStream {\n\n    /** Underlying InputStream */\n    private InputStream in;\n\n    /** Throughput limit in bytes per second, -1 for no limit, 0 to completely block reads */\n    private long bpsLimit;\n\n    /** Holds the current second, allowing to detect when a new second commences */\n    private long currentSecond;\n\n    /** Number of bytes that have been read or skipped this second */\n    private long nbBytesReadThisSecond;\n\n\n    /**\n     * Creates a new ThroughputLimitInputStream with no initial throughput limit (-1 value).\n     *\n     * @param in underlying stream that is used to read data from\n     */\n    public ThroughputLimitInputStream(InputStream in) {\n        this.in = in;\n        this.bpsLimit = -1;\n    }\n\n    /**\n     * Creates a new ThroughputLimitInputStream with an initial throughput limit.\n     *\n     * @param in underlying stream that is used to read data from\n     * @param bytesPerSecond initial throughput limit in bytes per second\n     * @see #setThroughputLimit(long)\n     */\n    public ThroughputLimitInputStream(InputStream in, long bytesPerSecond) {\n        this.in = in;\n        this.bpsLimit = bytesPerSecond;\n    }\n\n\n    /**\n     * Specifies a new throughput limit expressed in bytes per second.\n     * The new limit will take effect the next time one of the read or skip methods are called.\n     *\n     * <p>Setting the throughput limit to 0 effectively blocks all read and skip calls indefinitely.\n     * Any calls to the read or skip methods will lock, the only way to remove this lock being to call the\n     * {@link #setThroughputLimit(long)} method with a value different from 0 from another thread.\n     *\n     * <p>Setting the throughput limit to -1 or any other negative values will disable any limit and make\n     * this ThroughputLimitInputStream behave just like a normal InputStream.\n     *\n     * @param bytesPerSecond new throughput limit expressed in bytes, -1 to disable it, 0 to block reads.\n     */\n    public void setThroughputLimit(long bytesPerSecond) {\n        this.bpsLimit = bytesPerSecond;\n\n        // Wake up any thread waiting for data to be available to have them check the new limit counter\n        synchronized(this) {\n            notify();\n        }\n    }\n\n\n    /**\n     * Changes the underlying InputStream which data is read from, keeping the bytes count for the current second intact.\n     *\n     * <p>Note: the existing underlying InputStream will not be closed, the {@link #close()} method must be called prior\n     * to calling this method.\n     *\n     * @param in the new InputStream to read data from\n     */\n    public void setUnderlyingInputStream(InputStream in) {\n        this.in = in;\n    }\n\n\n    /**\n     * Returns the number of bytes that can be read (or skipped) without exceeding the current throughput limit.\n     * This method blocks until at least 1 byte is available. In other words the method always returns\n     * strictly positive values.\n     *\n     * <p>If the current throughput limit is negative (no limit), this method returns immediately Integer.MAX_VALUE.\n     * <p>If the byte quota for the current second has been exceeded, this method locks and returns as soon as a new second\n     * has started (i.e. bytes are available), or the {@link #setThroughputLimit(long)} with a more permissive value\n     * has been called.\n     * <p>If the current throughput limit is 0, it will lock undefinitely, until {@link #setThroughputLimit(long)} has\n     * been called from another thread with a value different from 0.\n     *\n     * @return the number of bytes available for reading without exceeding the current throughput limit\n     */\n    private int getNbAllowedBytes() {\n\n        // Update limit counter and retrieve number of milliseconds until next second\n        long msUntilNextSecond = updateLimitCounter();\n\n        long allowedBytes;\n\n        synchronized(this) {\n            // Loop while throughput limit has been exceeded\n            while((allowedBytes=bpsLimit- nbBytesReadThisSecond)<=0) {\n                // Throughput limit was removed, return max int value\n                if(bpsLimit<0)\n                    return Integer.MAX_VALUE;\n\n                try {\n                    // If limit is 0, wait indefinitely for a call to notify() from setThroughputLimit()\n                    if(bpsLimit==0)\n                        wait();\n                    // Wait until the current second is over for more bytes to be available,\n                    // or until a call to notify() is made from setThroughputLimit()\n                    else {\n                        wait(msUntilNextSecond);\n                    }\n                }\n                catch(InterruptedException e) {\n                    // No problem in this unlikely event, loop one more time and wait some more\n                }\n\n                // Update limit counter and retrieve number of milliseconds until next second\n                msUntilNextSecond = updateLimitCounter();\n            }\n        }\n\n        return (int)allowedBytes;\n    }\n\n\n    /**\n     * Checks if the current second has changed. If that's the case, updates the current second value and resets the\n     * number of bytes read this second. Returns the number of milliseconds until a new second starts.\n     */\n    private long updateLimitCounter() {\n        long now = System.currentTimeMillis();\n        long nowSecond = now/1000;\n\n        // Current second has changed\n        if(this.currentSecond!=nowSecond) {\n            this.currentSecond = nowSecond;\n            this.nbBytesReadThisSecond = 0;\n        }\n\n        return 1000-(now%1000);\n    }\n\n\n    /**\n     * Increases the number of bytes read this second to the given number.\n     *\n     * @param nbRead number of bytes that have been read or skipped from the underlying stream.\n     */\n    private void addToLimitCounter(long nbRead) {\n        updateLimitCounter();\n\n        this.nbBytesReadThisSecond += nbRead;\n    }\n\n\n    ////////////////////////////////\n    // InputStream implementation //\n    ////////////////////////////////\n\n    @Override\n    public int read() throws IOException {\n        // Wait until at least 1 byte is available if a limit is set\n        if(bpsLimit>=0)\n            getNbAllowedBytes();\n\n        // Read the byte from the underlying stream\n        int i = in.read();\n\n        // Increase read counter by 1\n        if(i>0)\n            addToLimitCounter(1);\n\n        return i;\n    }\n\n    @Override\n    public int read(byte[] bytes) throws IOException {\n        return this.read(bytes, 0, bytes.length);\n    }\n\n    @Override\n    public int read(byte[] bytes, int off, int len) throws IOException {\n        int nbRead;\n\n        // Wait until at least 1 byte is available if a limit is set and try to read as many bytes are available\n        // without exceeding the throughput limit or the number specified\n        if(bpsLimit>=0)\n            nbRead = in.read(bytes, off, Math.min(getNbAllowedBytes(),len));\n        else\n            nbRead = in.read(bytes, off, len);\n\n        // Increase read counter by the number of bytes that have actually been read by the underlying stream\n        if(nbRead>0)\n            addToLimitCounter(nbRead);\n\n        return nbRead;\n    }\n\n    @Override\n    public long skip(long l) throws IOException {\n        long nbSkipped = in.skip(bpsLimit>=0?Math.min(getNbAllowedBytes(),l):l);\n\n        // Increase read counter by the number of bytes that have actually been skipped by the underlying stream\n        if(nbSkipped>0)\n            addToLimitCounter(nbSkipped);\n\n        return nbSkipped;\n    }\n\n    @Override\n    public int available() throws IOException {\n        return in.available();\n    }\n\n    @Override\n    public void close() throws IOException {\n        in.close();\n    }\n\n    @Override\n    public synchronized void mark(int i) {\n        in.mark(i);\n    }\n\n    @Override\n    public synchronized void reset() throws IOException {\n        in.reset();\n    }            \n\n    @Override\n    public boolean markSupported() {\n        return in.markSupported();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/base64/Base64Decoder.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io.base64;\n\nimport java.io.*;\n\n/**\n * <code>Base64Decoder</code> provides methods to ease the decoding of strings and byte arrays in base64.\n * The {@link Base64InputStream} class is used under the hood to perform the actual base64 decoding.\n *\n * @see Base64InputStream\n * @author Maxence Bernard\n */\npublic abstract class Base64Decoder {\n\n    /**\n     * Shorthand for {@link #decodeAsBytes(String, Base64Table)} invoked with {@link Base64Table#STANDARD_TABLE}.\n     *\n     * @param s a Base64-encoded String\n     * @return the decoded string as a byte array\n     * @throws java.io.IOException if the given String isn't properly Base64-encoded\n     */\n    public static byte[] decodeAsBytes(String s) throws IOException {\n        return decodeAsBytes(s, Base64Table.STANDARD_TABLE);\n    }\n\n    /**\n     * Decodes the given Base64-encoded string and returns the result as a byte array.\n     * Throws an <code>IOException</code> if the String isn't properly Base64-encoded.\n     *\n     * @param s a Base64-encoded String\n     * @param table the table to use to decode data\n     * @return the decoded string as a byte array\n     * @throws java.io.IOException if the given String isn't properly Base64-encoded\n     */\n    public static byte[] decodeAsBytes(String s, Base64Table table) throws IOException {\n        byte[] b = s.getBytes();\n\n        if (b.length % 4 != 0) {\n            // Base64 encoded data must come in a multiple of 4 bytes, throw an IOException if it's not the case\n            throw new IOException(\"Byte array length is not a multiple of 4\");\n        }\n\n        ByteArrayOutputStream bout = new ByteArrayOutputStream();\n        try (Base64InputStream bin = new Base64InputStream(new ByteArrayInputStream(b), table)) {\n            int i;\n            while ((i = bin.read()) != -1) {\n                bout.write(i);\n            }\n            return bout.toByteArray();\n        }\n    }\n\n    /**\n     * Decodes the given Base64-encoded string and returns the result as a String. The specified encoding is used for\n     * transforming the decoded bytes into a String. Throws an <code>IOException</code> if the String isn't properly\n     * Base64-encoded, or if the encoding is not supported by the Java runtime.\n     *\n     * @param s a Base64-encoded String\n     * @param encoding the character encoding to use for transforming the decoded bytes into a String\n     * @param table the table to use to decode data\n     * @return the decoded String\n     * @throws UnsupportedEncodingException if the specified encoding is not supported by the Java runtime\n     * @throws java.io.IOException if the given String isn't properly Base64-encoded\n     */\n    public static String decode(String s, String encoding, Base64Table table) throws IOException {\n        StringBuilder sb = new StringBuilder();\n\n        try (InputStreamReader isr = new InputStreamReader(new ByteArrayInputStream(decodeAsBytes(s, table)), encoding)) {\n            int i;\n            while ((i = isr.read()) != -1) {\n                sb.append((char) i);\n            }\n            return sb.toString();\n        }\n    }\n\n    /**\n     * Shorthand for {@link #decode(String, String, Base64Table)} invoked with <code>UTF-8</code> encoding and\n     * {@link Base64Table#STANDARD_TABLE}.\n     *\n     * @param s a Base64-encoded String\n     * @return the decoded String\n     * @throws java.io.IOException if the given String isn't properly Base64-encoded\n     */\n    public static String decode(String s) throws IOException {\n        return decode(s, \"UTF-8\", Base64Table.STANDARD_TABLE);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/base64/Base64Encoder.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io.base64;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.UnsupportedEncodingException;\n\n/**\n * <code>Base64Encoder</code> provides methods to ease the encoding of strings and byte arrays in base64.\n * The {@link Base64OutputStream} class is used under the hood to perform the actual base64 encoding.\n *\n * @see Base64OutputStream\n * @author Maxence Bernard\n */\npublic abstract class Base64Encoder {\n\n    /**\n     * Shorthand for {@link #encode(byte[], Base64Table)} invoked with {@link Base64Table#STANDARD_TABLE}.\n     *\n     * @param b bytes to base64-encode\n     * @return the base64-encoded String\n     */\n    public static String encode(byte[] b) {\n        return encode(b, 0, b.length, Base64Table.STANDARD_TABLE);\n    }\n\n    /**\n     * Base64-encodes the given byte array using {@link Base64Table#STANDARD_TABLE} using the given Base64 table\n     * and returns the result.\n     *\n     * @param b bytes to base64-encode\n     * @param table the table to use to encode data\n     * @return the base64-encoded String\n     */\n    public static String encode(byte[] b, Base64Table table) {\n        return encode(b, 0, b.length, table);\n    }\n\n    /**\n     * Shorthand for {@link #encode(byte[], int, int, Base64Table)} invoked with {@link Base64Table#STANDARD_TABLE}.\n     *\n     * @param b bytes to base64-encode\n     * @param off position to the first byte in the array to be encoded\n     * @param len number of bytes in the array to encode\n     * @return the base64-encoded String\n     */\n    public static String encode(byte[] b, int off, int len) {\n        return encode(b, off, len, Base64Table.STANDARD_TABLE);\n    }\n\n    /**\n     * Base64-encodes the given byte array, from off to len, and returns the result.\n     *\n     * @param b bytes to base64-encode\n     * @param off position to the first byte in the array to be encoded\n     * @param len number of bytes in the array to encode\n     * @param table the table to use to encode data\n     * @return the base64-encoded String\n     */\n    public static String encode(byte[] b, int off, int len, Base64Table table) {\n        ByteArrayOutputStream bout = new ByteArrayOutputStream();\n\n        try (Base64OutputStream out64 = new Base64OutputStream(bout, false, table)) {\n            out64.write(b, off, len);\n            out64.writePadding();\n\n            return bout.toString();\n        } catch (IOException e) {\n            // Should never happen\n            return null;\n        }\n    }\n\n    /**\n     * Shorthand for {@link #encode(String, String, Base64Table)} invoked with <code>UTF-8</code> encoding and\n     * {@link Base64Table#STANDARD_TABLE}.\n     *\n     * @param s the String to base64-encode\n     * @return the base64-encoded String\n     */\n    public static String encode(String s) {\n        try {\n            return encode(s, \"UTF-8\", Base64Table.STANDARD_TABLE);\n        } catch(UnsupportedEncodingException e) {\n            // Should never happen, UTF-8 is necessarily supported by the Java runtime\n            return null;\n        }\n    }\n\n    /**\n     * Base64-encodes the given String and returns result. The specified encoding is used for tranforming\n     * the string into bytes.\n     *\n     * @param s the String to base64-encode\n     * @param encoding the character encoding to use for transforming the string into bytes\n     * @param table the table to use to encode data\n     * @return the base64-encoded String\n     * @throws UnsupportedEncodingException if the specified encoding is not supported by the Java runtime\n     */\n    public static String encode(String s, String encoding, Base64Table table) throws UnsupportedEncodingException {\n        return encode(s.getBytes(encoding), table);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/base64/Base64InputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io.base64;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * <code>Base64InputStream</code> is an <code>InputStream</code> that decodes Base64-encoded data provided by\n * an underlying <code>InputStream</code>. The underlying data must be valid base64-encoded data with respect to\n * the character table in use. If not, an <code>IOException</code> will be thrown when illegal data is encountered.\n *\n * @see Base64Decoder\n * @author Maxence Bernard\n */\npublic class Base64InputStream extends InputStream {\n\n    /** Underlying stream data is read from */\n    private final InputStream in;\n\n    /** The Base64 decoding table */\n    private final int[] decodingTable;\n\n    /** The character used for padding */\n    private final byte paddingChar;\n\n    /** Decoded bytes available for reading */\n    private final int[] readBuffer = new int[3];\n\n    /** Index of the next byte available for reading in the buffer */\n    private int readOffset;\n\n    /** Number of bytes left for reading in the buffer */\n    private int bytesLeft;\n\n    /** Buffer used temporarily for decoding */\n    private final int[] decodeBuffer = new int[4];\n\n\n    /**\n     * Equivalent to calling {@link #Base64InputStream(java.io.InputStream, Base64Table)} with\n     * a {@link Base64Table#STANDARD_TABLE} table.\n     *\n     * @param in underlying InputStream the Base64-encoded data is read from\n     */\n    public Base64InputStream(InputStream in) {\n        this(in, Base64Table.STANDARD_TABLE);\n    }\n\n    /**\n     * Creates a new <code>Base64InputStream</code> that allows to decode data that has been Base64-encoded using the\n     * given table, from the provided <code>InputStream</code>.\n     *\n     * @param in underlying InputStream the Base64-encoded data is read from\n     * @param table the table to use for Base64 decoding\n     */\n    public Base64InputStream(InputStream in, Base64Table table) {\n        this.in = in;\n        this.decodingTable = table.getDecodingTable();\n        this.paddingChar = table.getPaddingChar();\n    }\n\n\n    @Override\n    public int read() throws IOException {\n        // Read buffer empty: read and decode a new base64-encoded 4-byte group\n        if (bytesLeft == 0) {\n            int read;\n            int nbRead = 0;\n\n            while (nbRead<4) {\n                read = in.read();\n                // EOF reached\n                if(read==-1) {\n                    if(nbRead%4 != 0) {\n                        // Base64 encoded data must come in a multiple of 4 bytes, throw an IOException if the underlying stream ended prematurely\n                        throw new IOException(\"InputStream did not end on a multiple of 4 bytes\");\n                    }\n\n                    if(nbRead==0)\n                        return -1;\n                    else    // nbRead==4\n                        break;\n                }\n\n                decodeBuffer[nbRead] = decodingTable[read];\n\n                // Discard any character that's not a base64 character, without throwing an IOException.\n                // In particular, '\\r' and '\\n' characters that are usually found in email attachments are simply ignored.\n                if(decodeBuffer[nbRead]==-1 && read!=paddingChar) {\n                    continue;\n                }\n\n                nbRead++;\n            }\n\n            // Decode byte 0\n            readBuffer[bytesLeft++] = ((decodeBuffer[0]<<2)&0xFC | ((decodeBuffer[1]>>4)&0x03));\n\n            // Test if the character is not a padding character\n            if (decodeBuffer[2]!=-1) {\n                // Decode byte 1\n                readBuffer[bytesLeft++] = (decodeBuffer[1]<<4)&0xF0 | ((decodeBuffer[2]>>2)&0x0F);\n\n                // Test if the character is a padding character\n                if (decodeBuffer[3]!=-1)\n                    // Decode byte 2\n                    readBuffer[bytesLeft++] = ((decodeBuffer[2]<<6)&0xC0) | (decodeBuffer[3]&0x3F);\n            }\n\n            readOffset = 0;\n        }\n\n        bytesLeft--;\n\n        return readBuffer[readOffset++];\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/base64/Base64OutputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io.base64;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n\n/**\n * This <code>OutputStream</code> encodes supplied data to Base64 encoding and writes it to the underlying\n * <code>OutputStream</code>.\n *\n * @see Base64Encoder\n * @author Maxence Bernard, with the exception of the algorithm description which was found on the Web.\n */\npublic class Base64OutputStream extends OutputStream {\n    /*\n      Base64 uses a 65 character subset of US-ASCII,\n      allowing 6 bits for each character so the character\n      \"m\" with a Base64 value of 38, when represented\n      in binary form, is 100110.\n\n      With a text string, let's say \"men\" is encoded this\n      is what happens :\n\n      The text string is converted into its US-ASCII value.\n\n      The character \"m\" has the decimal value of 109\n      The character \"e\" has the decimal value of 101\n      The character \"n\" has the decimal value of 110\n\n      When converted to binary the string looks like this :\n\n      m   01101101\n      e   01100101\n      n   01101110\n\n      These three \"8-bits\" are concatenated to make a\n      24 bit stream\n      011011010110010101101110\n\n      This 24 bit stream is then split up into 4 6-bit\n      sections\n      011011 010110 010101 101110\n\n      We now have 4 values. These binary values are\n      converted to decimal form\n      27     22     21     46\n\n      And the corresponding Base64 character are :\n      b      W       V     u\n\n      The encoding is always on a three characters basis\n      (to have a set of 4 Base64 characters). To encode one\n      or two then, we use the special character \"=\" to pad\n      until 4 base64 characters is reached.\n\n      ex. encode \"me\"\n\n      01101101  01100101\n      0110110101100101\n      011011 010110 0101\n      111111    (AND to fill the missing bits)\n      011011 010110 010100\n      b     W      U\n      b     W      U     =  (\"=\" is the padding character)\n\n      so \"bWU=\"  is the base64 equivalent.\n\n      encode \"m\"\n\n      01101101\n      011011 01\n      111111         (AND to fill the missing bits)\n      011011 010000\n      b     Q     =  =   (two paddings are added)\n\n      Finally, MIME specifies that lines are 76 characters wide maximum.\n\n    */\n\n    /** Underlying OutputStream encoded data is sent to */\n    private final OutputStream out;\n\n    /** The Base64 encoding table */\n    private final byte[] encodingTable;\n\n    /** The character used for padding */\n    private final byte paddingChar;\n\n    /** Array used to accumulate the first 2 bytes of a 3-byte group */\n    private final byte[] byteAcc = new byte[2];\n\t\n    /** Number of bytes accumulated to form a 3-byte group */\n    private int nbBytesWaiting;\n\n    /** Specifies whether line breaks should be inserted after 80 chars */\n    private final boolean insertLineBreaks;\n\n    /** Current line length (to insert line return character after 80 chars)*/\n    private int lineLength;\n\n\n    /**\n     * Equivalent to calling {@link #Base64OutputStream(java.io.OutputStream, boolean, Base64Table)} with\n     * a {@link Base64Table#STANDARD_TABLE} table.\n     *\n     * @param out the underlying OutputStream to write the base64-encoded data to\n     * @param insertLineBreaks if <code>true</code>, line breaks will be inserted after every 80 characters written\n     */\n    public Base64OutputStream(OutputStream out, boolean insertLineBreaks) {\n        this(out, insertLineBreaks, Base64Table.STANDARD_TABLE);\n    }\n\n    /**\n     * Creates a new <code>Base64OutputStream</code> using the underlying OutputStream and table to write the\n     * base64-encoded data.\n     *\n     * @param out the underlying OutputStream to write the base64-encoded data to\n     * @param insertLineBreaks if <code>true</code>, line breaks will be inserted after every 80 characters written\n     * @param table the table to use to encode data\n     */\n    public Base64OutputStream(OutputStream out, boolean insertLineBreaks, Base64Table table) {\n        this.out = out;\n        this.insertLineBreaks = insertLineBreaks;\n        this.encodingTable = table.getEncodingTable();\n        this.paddingChar = table.getPaddingChar();\n    }\n\n    /**\n     * Writes padding '=' characters to the underlying <code>OutputStream</code> if there currently is an\n     * unfinished 3-byte group. If it's not the case, then this method is a no-op.\n     *\n     * @throws IOException if the padding characters could not be written to the underlying OutputStream.\n     */\n    public void writePadding() throws IOException {\n        // No padding needed\n        if(nbBytesWaiting==0)\n            return;\n\n        // 1 padding character\n        if (nbBytesWaiting==2) {\n            // 2 bytes left\n            out.write(encodingTable[(byte)((byteAcc[0] & 0xFC) >> 2)]);\n            out.write(encodingTable[(byte)(((byteAcc[0] & 0x03) << 4) | ((byteAcc[1] & 0xF0) >> 4))]);\n            out.write(encodingTable[(byte)((byteAcc[1] & 0x0F) << 2)]);\n            out.write(paddingChar);\n        }\n        // 2 padding characters\n        else if (nbBytesWaiting==1) {\n            // 1 byte left\n            out.write(encodingTable[(byte)((byteAcc[0] & 0xFC) >> 2)]);\n            out.write(encodingTable[(byte)((byteAcc[0] & 0x03) << 4)]);\n            out.write(paddingChar);\n            out.write(paddingChar);\n        }\n\n        // Just in case this method is called again\n        nbBytesWaiting = 0;\n    }\n\n\n    /////////////////////////////////\n    // OutputStream implementation //\n    /////////////////////////////////\n\n    @Override\n    public void write(int i) throws IOException {\n        // We have a 3-byte group\n        if(nbBytesWaiting==2) {\n            // Write 3 bytes as 4 base64 characters\n            out.write(encodingTable[(byte)((byteAcc[0] & 0xFC) >> 2)]);\n            out.write(encodingTable[(byte)(((byteAcc[0] & 0x03) << 4) | ((byteAcc[1] & 0xF0) >> 4))]);\n            out.write(encodingTable[(byte)(((byteAcc[1] & 0x0F) << 2) | ((i & 0xC0) >> 6))]);\n            out.write(encodingTable[(byte)(i & 0x3F)]);\n\n            nbBytesWaiting = 0;\n\n            // Insert a line break after every 80 characters written\n            if (insertLineBreaks && (lineLength += 4) >= 76) {\n                out.write('\\r');\n                out.write('\\n');\n                lineLength = 0;\n            }\n        }\n        // Waiting for more bytes...\n        else {\n            byteAcc[nbBytesWaiting++] = (byte)i;\n        }\n    }\n\n    /**\n     * Writes padding if necessary and closes the underlying stream.\n     */\n    @Override\n    public void close() throws IOException {\n        writePadding();\n        out.close();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/base64/Base64Table.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io.base64;\n\n/**\n * This class represents an immutable Base64 encoding/decoding table. It provides the correspondance between encoded\n * and decoded characters (and vice-versa), and the character to use for padding.\n * <p>\n * A number of common Base64 tables are provided as public static final fields of this class:\n * <ul>\n *   <li>{@link #STANDARD_TABLE}</li>\n *   <li>{@link #URL_SAFE_TABLE}</li>\n *   <li>{@link #FILENAME_SAFE_TABLE}</li>\n *   <li>{@link #REGEXP_SAFE_TABLE}</li>\n * </ul>\n *\n * @author Maxence Bernard\n */\nclass Base64Table {\n\n    /** Encoding table, 64 bytes long */\n    protected byte[] encodingTable;\n\n    /** Decoding table, 256 bytes long */\n    protected int[] decodingTable;\n\n    /** Padding character used */\n    protected byte paddingChar;\n\n    /** The standard Base64 table, using '+' and '/' for non-alphanumerical characters, and '=' for padding */\n    public final static Base64Table STANDARD_TABLE = createTable((byte)'+', (byte)'/', (byte)'=');\n\n    /** An URL-safe Base64 table, using '-' and '_' for non-alphanumerical characters, and '.' for padding */\n    public final static Base64Table URL_SAFE_TABLE = createTable((byte)'-', (byte)'_', (byte)'.');\n\n    /** A filename-safe Base64 table, using '+' and '-' for non-alphanumerical characters, and '=' for padding */\n    public final static Base64Table FILENAME_SAFE_TABLE = createTable((byte)'+', (byte)'-', (byte)'=');\n\n    /** A regexp-safe Base64 table, using '!' and '-' for non-alphanumerical characters, and '=' for padding */\n    public final static Base64Table REGEXP_SAFE_TABLE = createTable((byte)'!', (byte)'-', (byte)'=');\n\n    /**\n     * Creates a base64 table using A–Z, a–z, and 0–9 for the first 62 values, the two specified characters at\n     * position 62 and 63 in the table, and the specified padding character.\n     *\n     * @param char62 ASCII character to use at position 62 of the table\n     * @param char63 ASCII character to use at position 63 of the table\n     * @param paddingChar ASCII character to use for padding\n     * @throws IllegalArgumentException if one specified characters are the same or alphanumerical characters\n     * @return a base64 table\n     */\n    public static Base64Table createTable(byte char62, byte char63, byte paddingChar) throws IllegalArgumentException {\n        byte[] table = new byte[] {\n            'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',\n            'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',\n            'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',\n            'w','x','y','z','0','1','2','3','4','5','6','7','8','9',char62,char63\n        };\n\n        return new Base64Table(table, paddingChar);\n    }\n\n\n    /**\n     * Creates a new <code>Base64Table</code> using the specified character table and padding character.\n     *\n     * <p>An <code>IllegalArgumentException</code> if the specified table is not 64 bytes long, contains duplicate\n     * values, or if the specified padding character is present in the table.\n     *\n     * <p>The given byte array is cloned before being stored, to avoid any side effect that could be caused by the\n     * byte array being modified inadvertently after this constructor is called.\n     *\n     * @param table the base64 character table. The array must be 64 bytes long and must not contain any duplicate values.\n     * @param paddingChar the ASCII character used for padding. This character must not already be used in the table.\n     * @throws IllegalArgumentException if the specified table is not 64 bytes long, contains duplicate values, or\n     * if the specified padding character is present in the table.\n     */\n    public Base64Table(byte[] table, byte paddingChar) throws IllegalArgumentException {\n        // Basic length check\n        if(table==null || table.length!=64)\n            throw new IllegalArgumentException(\"Base64 table is not 64 bytes long\");\n\n        // create the decoding table and initialize all values to -1\n        this.decodingTable = new int[256];\n        char c;\n        for(c=0; c<256; c++)\n            decodingTable[c] = -1;\n\n        // Fill the decoding table and ensure that characters are used only once\n        for (int i = 0; i < 64; i++) {\n            byte val = table[i];\n            if (decodingTable[val] != -1) {\n                throw new IllegalArgumentException(\"Base64 table contains duplicate values\");\n            }\n\n            decodingTable[val] = i;\n        }\n\n        // Ensure that the padding character is not already used in the table\n        if (decodingTable[paddingChar] != -1) {\n            throw new IllegalArgumentException(\"Padding char is already used in Base64 table\");\n        }\n\n        this.paddingChar = paddingChar;\n\n        // Clone the byte array so that it cannot be altered externally\n        this.encodingTable = new byte[64];\n        System.arraycopy(table, 0, this.encodingTable, 0, 64);\n    }\n\n\n    /**\n     * Returns the base64 encoding table. The return array should not be modified.\n     *\n     * @return the base64 encoding table.\n     */\n    byte[] getEncodingTable() {\n        return encodingTable;\n    }\n\n    /**\n     * Returns the base64 decoding table, containing byte values with the exception of <code>-1</code> which indicates\n     * the character is not used. The return array should not be modified.\n     *\n     * @return the base64 decoding table.\n     */\n    int[] getDecodingTable() {\n        return decodingTable;\n    }\n\n    /**\n     * Returns the ASCII character used for padding.\n     *\n     * @return the ASCII character used for padding.\n     */\n    byte getPaddingChar() {\n        return paddingChar;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/base64/package.html",
    "content": "<body>\n  Provides classes to deal with base-64 encoded streams.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/bom/BOM.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io.bom;\n\nimport java.nio.charset.Charset;\n\n/**\n * BOM represents a Byte-Order Mark, a byte sequence that can be found at the beginning of a Unicode text stream\n * which indicates the encoding of the text that follows.\n *\n * @see BOMInputStream\n * @author Maxence Bernard\n */\npublic class BOM {\n\n    /** the byte sequence that identifies this BOM */\n    private final byte[] sig;\n\n    /** the character encoding denoted by this BOM */\n    private final String encoding;\n\n    /** character encoding aliases that map onto this BOM */\n    private final String[] aliases;\n\n    /**\n     * Creates a new <code>BOM</code> instance identified by the given signature and denoting the specified\n     * character encoding.\n     *\n     * @param signature the byte sequence that identifies this BOM\n     * @param encoding the character encoding denoted by this BOM\n     * @param aliases character encoding aliases\n     */\n    BOM(byte[] signature, String encoding, String[] aliases) {\n        this.sig = signature;\n        this.encoding = encoding;\n        this.aliases = aliases;\n    }\n\n    /**\n     * Returns the byte sequence that identifies this BOM at the beginning of a byte stream.\n     *\n     * @return the byte sequence that identifies this BOM at the beginning of a byte stream\n     */\n    public byte[] getSignature() {\n        return sig;\n    }\n\n    /**\n     * Returns the character encoding that this BOM denotes.\n     *\n     * @return the character encoding that this BOM denotes\n     */\n    public String getEncoding() {\n        return encoding;\n    }\n\n    /**\n     * Returns a set of character encoding aliases that map onto this BOM.\n     *\n     * @return a set of character encoding aliases that map onto this BOM\n     */\n    public String[] getAliases() {\n        return aliases;\n    }\n\n    /**\n     * Returns <code>true</code> if this BOM's signature starts with the given byte sequence.\n     *\n     * @param bytes the byte sequence to compare against this BOM's signature\n     * @return true if this BOM's signature starts with the given byte sequence\n     */\n    public boolean sigStartsWith(byte[] bytes) {\n        int bytesLen = bytes.length;\n        if (bytesLen > sig.length) {\n            return false;\n        }\n\n        for (int i = 0; i < bytesLen; i++) {\n            if (bytes[i] != sig[i]) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * Returns <code>true</code> if this BOM's signature matches the given byte sequence.\n     *\n     * @param bytes the byte sequence to compare against this BOM's signature\n     * @return true if this BOM's signature matches the given byte sequence\n     */\n    public boolean sigEquals(byte[] bytes) {\n        return bytes.length==sig.length && sigStartsWith(bytes);\n    }\n\n\n    /**\n     * Returns a {@link BOM} instance for the specified encoding, <code>null</code> if the encoding doesn't\n     * have a corresponding BOM (non-Unicode encoding). The search is case-insensitive.\n     *\n     * <p>All UTF encoding aliases are supported, in a BOM-neutral way: a BOM is always returned, regardless of\n     * whether the particular encoding requires a BOM to be used or not. For instance,\n     * <code>UTF-16LE</code> and <code>UnicodeLittleUnmarked</code> will both return the {@link BOMConstants#UTF16_LE_BOM}\n     * BOM, even though by specification <code>UTF-16LE</code> and <code>UnicodeLittleUnmarked</code> should not\n     * include a BOM in the data stream. Furthermore, when called with <code>UTF-16</code> and <code>UTF-32</code>,\n     * the returned BOM will arbitrarily default to big endian and return {@link BOMConstants#UTF16_BE_BOM} and\n     * {@link BOMConstants#UTF32_BE_BOM} respectively.\n     *\n     * @param encoding name of a character encoding\n     * @return a {@link BOM} instance for the specified encoding, <code>null</code> if the encoding doesn't\n     * have a corresponding BOM (non-Unicode encoding).\n     */\n    public static BOM getInstance(String encoding) {\n        if (!Charset.isSupported(encoding)) {\n            return null;\n        }\n\n        Charset charset = Charset.forName(encoding);\n        // Retrieve the charset's canonical name for aliases we may not know about\n        encoding = charset.name();\n\n        for (int i = 0; i<BOMConstants.SUPPORTED_BOMS.length; i++) {\n            if (BOMConstants.SUPPORTED_BOMS[i].getEncoding().equalsIgnoreCase(encoding)) {\n                return BOMConstants.SUPPORTED_BOMS[i];\n            }\n            String[] aliases = BOMConstants.SUPPORTED_BOMS[i].getAliases();\n            for (String alias : aliases) {\n                if (alias.equalsIgnoreCase(encoding)) {\n                    return BOMConstants.SUPPORTED_BOMS[i];\n                }\n            }\n        }\n\n        return null;\n    }\n\n\n    /**\n     * Returns <code>true</code> if and only if the given Object is a <code>BOM</code> instance with the same\n     * signature as this instance.\n     *\n     * @param o the Object to test for equality\n     * @return true if the specified Object is a BOM instance with the same signature as this instance\n     */\n    @Override\n    public boolean equals(Object o) {\n        return (o instanceof BOM) && ((BOM)o).sigEquals(sig);\n    }\n\n    /**\n     * Returns a String representation of this <code>BOM</code>.\n     *\n     * @return returns a String representation of this <code>BOM</code>.\n     */\n    @Override\n    public String toString() {\n        StringBuilder out = new StringBuilder(super.toString());\n        out.append(\", signature=\");\n        for(int i = 0; i < sig.length; i++) {\n            out.append(0xFF&sig[i]);\n            out.append((i == sig.length-1 ? \"}\" : \", \"));\n        }\n        out.append(\", encoding=\");\n        out.append(encoding);\n        return out.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/bom/BOMConstants.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io.bom;\n\n/**\n * This interface contains constants used by several classes of the BOM package.\n *\n * @author Maxence Bernard\n */\npublic interface BOMConstants {\n\n    /** UTF-8 BOM: EF BB BF */\n    BOM UTF8_BOM = new BOM(\n            new byte[]{(byte)0xEF, (byte)0xBB, (byte)0xBF},\n            \"UTF-8\",\n            new String[]{}\n    );\n\n    /** UTF-16 Big Endian BOM: FE FF */\n    BOM UTF16_BE_BOM = new BOM(\n            new byte[]{(byte)0xFE, (byte)0xFF},\n            \"UTF-16BE\",\n            new String[]{\"UTF-16\", \"x-UTF-16BE-BOM\" ,\"UnicodeBig\", \"UnicodeBigUnmarked\"}\n    );\n\n    /** UTF-16 Little Endian BOM: FF FE */\n    BOM UTF16_LE_BOM = new BOM(\n            new byte[]{(byte)0xFF, (byte)0xFE},\n            \"UTF-16LE\",\n            new String[]{\"x-UTF-16LE-BOM\", \"UnicodeLittle\", \"UnicodeLittleUnmarked\"}\n    );\n\n    /** UTF-32 Big Endian BOM: 00 00 FE FF. */\n    BOM UTF32_BE_BOM = new BOM(\n            new byte[]{(byte)0x00, (byte)0x00, (byte)0xFE, (byte)0xFF},\n            \"UTF-32BE\",\n            new String[]{\"UTF-32\", \"x-UTF-32BE-BOM\"}\n    );\n\n    /** UTF-32 Little Endian BOM: FF FE 00 00 */\n    BOM UTF32_LE_BOM = new BOM(\n            new byte[]{(byte)0xFF, (byte)0xFE, (byte)0x00, (byte)0x00},\n            \"UTF-32LE\",\n            new String[]{\"x-UTF-32LE-BOM\"}\n    );\n\n    /** List of supported BOMs */\n    BOM[] SUPPORTED_BOMS = new BOM[] {\n        UTF8_BOM,\n        UTF16_BE_BOM,\n        UTF16_LE_BOM,\n        UTF32_BE_BOM,\n        UTF32_LE_BOM\n    };\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/bom/BOMInputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io.bom;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * <code>BOMInputStream</code> is an <code>InputStream</code> which provides support for Byte-Order Marks (BOM).\n * A BOM is a byte sequence found at the beginning of a Unicode text stream which indicates the encoding of the text\n * that follows.\n *\n * <p>\n * This class serves a dual purpose:<br>\n * 1) it allows to detect a BOM in the underlying stream and determine the encoding used by the stream:\n * the {@link BOM} instance returned by {@link #getBOM()} provides that information.<br>\n * 2) it allows to discard the BOM from a Unicode stream: the leading bytes corresponding to the BOM are swallowed by\n * the stream and never returned by the <code>read</code> methods.\n *\n *<p>\n * The following BOMs are supported by this class:\n * <ul>\n *  <li>{@link #UTF8_BOM UTF-8}</li>\n *  <li>{@link #UTF16_BE_BOM UTF-16 Big Endian}</li>\n *  <li>{@link #UTF16_LE_BOM UTF-16 Little Endian}</li>\n *  <li>{@link #UTF32_BE_BOM UTF-32 Big Endian}.</li>\n *  <li>{@link #UTF32_LE_BOM UTF-32 Little Endian}</li>\n * </ul>\n * Note that UTF-32 encodings (both Little and Big Endians) are usually <b>not</b> supported by Java runtimes\n * out of the box.\n * <p>\n *\n * @see BOMReader\n * @author Maxence Bernard\n */\npublic class BOMInputStream extends InputStream implements BOMConstants {\n\n    /** The underlying InputStream that feeds bytes to this stream */\n    private final InputStream in;\n\n    /** Contains the BOM that was detected in the stream, null if none was found */\n    private BOM bom;\n\n    /** Bytes that were swallowed by this stream when searching for a BOM, null if a BOM was found */\n    private byte[] leadingBytes;\n\n    /** Current offset within the {@link #leadingBytes} array */\n    private int leadingBytesOff;\n\n    private byte[] oneByteBuf;\n\n    /** Contains the max signature length of supported BOMs */\n    private final static int MAX_BOM_LENGTH;\n\n    static {\n        // Calculates MAX_BOM_LENGTH\n        int maxLen = SUPPORTED_BOMS[0].getSignature().length;\n        int len;\n        for(int i=1; i<SUPPORTED_BOMS.length; i++) {\n            len = SUPPORTED_BOMS[i].getSignature().length;\n            if(len>maxLen)\n                maxLen = len;\n        }\n\n        MAX_BOM_LENGTH = maxLen;\n    }\n\n\n    /**\n     * Creates a new <code>BOMInputStream</code> and looks for a BOM at the beginning of the stream.\n     *\n     * @param in the underlying stream\n     * @throws IOException if an error occurred while reading the given InputStream\n     */\n    public BOMInputStream(InputStream in) throws IOException {\n        this.in = in;\n\n        // Read up to MAX_BOM_LENGTH bytes\n        byte[] bytes = new byte[MAX_BOM_LENGTH];\n        int nbRead;\n        int totalRead = 0;\n        while((nbRead=in.read(bytes, totalRead, MAX_BOM_LENGTH-totalRead))!=-1 && (totalRead+=nbRead)<MAX_BOM_LENGTH);\n\n        // Truncate the byte array if the stream ended before MAX_BOM_LENGTH\n        if(totalRead<MAX_BOM_LENGTH) {\n            byte[] tempBytes = new byte[totalRead];\n            System.arraycopy(bytes, 0, tempBytes, 0, totalRead);\n            bytes = tempBytes;\n        }\n\n        int bestMatchLength = 0;\n        int bestMatchIndex = -1;\n        BOM tempBom;\n        byte[] tempBomSig;\n\n        // Looks for the best (longest) signature match\n        for (int i = 0; i<SUPPORTED_BOMS.length; i++) {\n            tempBom = SUPPORTED_BOMS[i];\n            tempBomSig = tempBom.getSignature();\n            if (tempBomSig.length>bestMatchLength && startsWith(bytes, tempBomSig)) {\n                bestMatchIndex = i;\n                bestMatchLength = tempBomSig.length;\n            }\n        }\n\n        // Keep the bytes that do not correspond to a BOM to have the read methods return them\n        if (bestMatchIndex!=-1) {\n            bom = SUPPORTED_BOMS[bestMatchIndex];\n            if(bestMatchLength<MAX_BOM_LENGTH) {\n                leadingBytes = bytes;\n                leadingBytesOff = bestMatchLength;\n            }\n        } else {\n            leadingBytes = bytes;\n            leadingBytesOff = 0;\n        }\n    }\n\n    /**\n     * Returns <code>true</code> if the first byte sequence starts with the second byte sequence.\n     *\n     * @param b1 first byte array to test\n     * @param b2 second byte array to test\n     * @return true if the first byte sequence starts with the second byte sequence.\n     */\n    private static boolean startsWith(byte[] b1, byte[] b2) {\n        int b1Len = b1.length;\n        int b2Len = b2.length;\n        if (b1Len < b2Len) {\n            return false;\n        }\n\n        for(int i = 0; i < b2Len; i++) {\n            if (b2[i] != b1[i]) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * Returns the {@link BOM} that was found at the beginning of the stream if there was one,\n     * <code>null</code> otherwise.\n     *\n     * @return the BOM that was found at the beginning of the stream\n     */\n    public BOM getBOM() {\n        return bom;\n    }\n\n\n    @Override\n    public int read() throws IOException {\n        if (oneByteBuf == null) {\n            oneByteBuf = new byte[1];\n        }\n        int ret = read(oneByteBuf, 0, 1);\n        return ret == -1 ? -1 : oneByteBuf[0];\n    }\n\n    @Override\n    public int read(byte[] b) throws IOException {\n        return read(b, 0, b.length);\n    }\n\n    @Override\n    public int read(byte[] b, int off, int len) throws IOException {\n        if (leadingBytes == null || leadingBytesOff >= leadingBytes.length) {\n            return in.read(b, off, len);\n        }\n\n        int nbBytes = Math.min(leadingBytes.length-leadingBytesOff, len);\n        System.arraycopy(leadingBytes, leadingBytesOff, b, off, nbBytes);\n\n        leadingBytesOff += nbBytes;\n\n        return nbBytes;\n    }\n\n    @Override\n    public void close() throws IOException {\n        in.close();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/bom/BOMReader.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io.bom;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.UnsupportedEncodingException;\n\n/**\n * <code>BOMReader</code> is a <code>Reader</code> which provides support for Byte-Order Marks (BOM).\n * A BOM is a byte sequence found at the beginning of a Unicode text stream which indicates the encoding of the text\n * that follows.\n *\n * <p>\n * This class uses a {@link BOMInputStream} for BOM handling and serves a dual purpose:<br>\n * 1) it allows to auto-detect the encoding when a BOM is present in the underlying stream and use it.<br>\n * 2) it allows to discard the BOM from a Unicode stream: the leading bytes corresponding to the BOM are swallowed by\n * the stream and never returned by the <code>read</code> methods.\n *\n * @see BOMInputStream\n * @author Maxence Bernard\n */\npublic class BOMReader extends InputStreamReader {\n\n    /**\n     * Creates a new <code>BOMReader</code>. A {@link BOMInputStream} is created on top of the specified\n     * <code>InputStream</code> to auto-detect a potential {@link BOM} and use the associated encoding.\n     * If no BOM is found at the beginning of the stream, <code>UTF-8</code> encoding is assumed.\n     *\n     * @param in the underlying InputStream\n     * @throws IOException if an error occurred while detecting the BOM or initializing this reader.\n     */\n    public BOMReader(InputStream in) throws IOException {\n        this(new BOMInputStream(in), \"UTF-8\");\n    }\n\n    /**\n     * Creates a new <code>BOMReader</code>. A {@link BOMInputStream} is created on top of the specified\n     * <code>InputStream</code> to auto-detect a potential {@link BOM} and use the associated encoding.\n     * If no BOM is found at the beginning of the stream, the specified default encoding is assumed.\n     *\n     * @param in the underlying InputStream\n     * @param defaultEncoding the encoding used if the stream doesn't contain a BOM\n     * @throws IOException if an error occurred while detecting the BOM or initializing this reader.\n     */\n    public BOMReader(InputStream in, String defaultEncoding) throws IOException {\n        this(new BOMInputStream(in), defaultEncoding);\n    }\n\n    /**\n     * Creates a new <code>BOMReader</code> using the given {@link BOMInputStream}. If the <code>BOMInputStream</code>\n     * does not contain a {@link BOM}, the specified default encoding is assumed.\n     *\n     * @param bomIn the underlying BOMInputStream\n     * @param defaultEncoding the encoding used if the stream doesn't contain a BOM\n     * @throws UnsupportedEncodingException if the encoding associated with the BOM or the default encoding is not\n     * supported by the Java runtime \n     */\n    public BOMReader(BOMInputStream bomIn, String defaultEncoding) throws UnsupportedEncodingException {\n        super(bomIn, bomIn.getBOM()==null?defaultEncoding:bomIn.getBOM().getEncoding());\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/bom/BOMWriter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io.bom;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.io.OutputStreamWriter;\nimport java.io.UnsupportedEncodingException;\n\n/**\n * <code>BOMWriter</code> is a <code>Writer</code> that writes a Byte-Order Mark (BOM) at the beginning of a Unicode\n * stream and subsequently encodes characters in the requested encoding.\n *\n * @author Maxence Bernard\n */\npublic class BOMWriter extends OutputStreamWriter {\n\n    /** The underlying InputStream. */\n    protected OutputStream out;\n\n    /** The BOM to write at the beginning of the stream, <code>null</code> for none. */\n    protected BOM bom;\n\n    /** True if the BOM has already been written if there was one */\n    protected boolean bomWriteChecked;\n\n\n    /**\n     * Creates a new <code>BOMWriter</code> that will write the specified BOM at the beginning of the stream and\n     * subsequently encode characters in the encoding returned by {@link BOM#getEncoding()}.\n     *\n     * @param out the <code>OutputStream</code> to write the encoded data to\n     * @param bom the byte-order mark to write at the beginning of the stream.\n     * @throws UnsupportedEncodingException if the BOM's encoding is not a character encoding supported by the Java runtime.\n     */\n    public BOMWriter(OutputStream out, BOM bom) throws UnsupportedEncodingException {\n        this(out, bom.getEncoding(), bom);\n    }\n\n    /**\n     * Creates a new <code>BOMWriter</code> that will encode characters in the specified encoding and, if the\n     * encoding has a corresponding {@link BOM}, write at the beginning of the stream. If a Non-Unicode encoding\n     * is passed, no BOM will be written to the stream and this <code>Writer</code> will act as a regular\n     * {@link OutputStreamWriter}.\n     *\n     * <p>It is important to note that a BOM will always be written for Unicode encodings,\n     * even if the particular encoding specifies that no BOM should be written (<code>UnicodeLittleUnmarked</code> for\n     * instance). See {@link BOM#getInstance(String)} for more information about this.\n     *\n     * @param out the <code>OutputStream</code> to write the encoded data to\n     * @param encoding character encoding to use for encoding characters.\n     * @throws UnsupportedEncodingException if the specified encoding is not a character encoding supported by the Java runtime.\n     * @see BOM#getInstance(String)\n     */\n    public BOMWriter(OutputStream out, String encoding) throws UnsupportedEncodingException {\n        this(out, encoding, BOM.getInstance(encoding));\n    }\n\n    /**\n     * Creates a new <code>BOMWriter</code> that will write the specified BOM at the beginning of the stream and\n     * subsequently encode characters in the specified encoding.\n     *\n     * @param out the <code>OutputStream</code> to write the encoded data to\n     * @param bom the byte-order mark to write at the beginning of the stream.\n     * @param encoding character encoding to use for encoding characters.\n     * @throws UnsupportedEncodingException if the specified encoding is not a character encoding supported by the Java runtime.\n     */\n    protected BOMWriter(OutputStream out, String encoding, BOM bom) throws UnsupportedEncodingException {\n        super(out, encoding);\n\n        this.out = out;\n        this.bom = bom;\n    }\n\n    /**\n     * Checks if a BOM is waiting for being written, and if there is, writes it to the underlying output stream.\n     *\n     * @throws IOException if an error occurred while writing the BOM\n     */\n    protected void checkWriteBOM() throws IOException {\n        if (!bomWriteChecked) {\n            if (bom != null) {\n                out.write(bom.getSignature());\n            }\n\n            bomWriteChecked = true;\n        }\n    }\n\n\n    @Override\n    public void write(int c) throws IOException {\n        checkWriteBOM();\n        super.write(c);\n    }\n\n    @Override\n    public void write(char[] cbuf, int off, int len) throws IOException {\n        checkWriteBOM();\n        super.write(cbuf, off, len);\n    }\n\n    @Override\n    public void write(String str, int off, int len) throws IOException {\n        checkWriteBOM();\n        super.write(str, off, len);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/bom/package.html",
    "content": "<body>\n  Provides support for Byte-Order Marks (BOM) found in Unicode text streams.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/compound/CompoundInputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io.compound;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * <code>CompoundInputStream</code> concatenates several input streams into one. It can operate in two modes:\n * <dl>\n *   <dt>Merged</dt>\n *   <dd>the compound stream acts as a single, global input stream, merging the contents of underlying streams. The\n * compound stream can be read just like a regular <code>InputStream</code> -- streams are advanced automatically as EOF\n * of individual streams are reached.</dd>\n *\n *   <dt>Unmerged</dt>\n *   <dd>the compound stream has be advanced manually. EOF are signaled individually for each underlying stream.\n * After EOF is reached, the current stream has to be advanced to the next one using {@link #advanceInputStream()}.</dd>\n * </dl>\n *\n * <p>\n * This class is abstract, with a single method to implement: {@link #getNextInputStream()}.\n * See {@link IteratorCompoundInputStream} for an <code>Iterator</code>-backed implementation.\n *\n * @see IteratorCompoundInputStream\n * @see CompoundReader\n * @author Maxence Bernard\n */\npublic abstract class CompoundInputStream extends InputStream {\n\n    /** True if this CompoundInputStream operates in 'merged' mode */\n    private boolean merged;\n\n    /** The InputStream that's currently being processed */\n    private InputStream currentIn;\n\n    /** Used by {@link #read()} */\n    private byte oneByteBuf[];\n\n    /** <code>true</code> if the global EOF has been reached */\n    private boolean globalEOFReached;\n\n\n    /**\n     * Creates a new <code>CompoundInputStream</code> operating in the specified mode.\n     *\n     * @param merged <code>true</code> if the streams should be merged, acting as a single stream, or considered\n     * as separate streams that have to be {@link #advanceInputStream() advanced manually}.\n     */\n    public CompoundInputStream(boolean merged) {\n        this.merged = merged;\n    }\n\n    /**\n     * Returns:\n     * <ul>\n     *   <li><code>true</code> if this stream acts as a single, global input stream, merging the contents of underlying\n     * streams. In this mode, the compound stream can be read just like a regular InputStream -- streams are advanced\n     * automatically as EOF of individual streams are reached.</li>\n     *   <li><code>false</code> if this stream has be advanced manually. In this mode, EOF are signaled individually\n     * for each underlying stream. After EOF has been reached, the current stream has to be advanced to the next one\n     * using {@link #advanceInputStream()}.</li>\n     * </ul>\n     *\n     * @return <code>true</code> if this stream acts as a global input stream, <code>false</code> if this stream has\n     * to be advanced manually.\n     */\n    public boolean isMerged() {\n        return merged;\n    }\n\n    /**\n     * Returns the <code>InputStream</code> this compound stream is currently reading, <code>null</code> if this stream\n     * hasn't read anything yet, or if it has no underlying stream to read.\n     *\n     * @return <code>InputStream</code> this compound stream is currently reading\n     */\n    public InputStream getCurrentInputStream() {\n        return currentIn;\n    }\n\n    /**\n     * Closes the current input stream, if any. This method has no effect if there is no current input stream.\n     *\n     * @throws IOException if an error occurred while closing the current input stream.\n     */\n    public void closeCurrentInputStream() throws IOException {\n        if(currentIn!=null)\n            currentIn.close();\n    }\n\n    /**\n     * Tries to advances the current stream to the next one, causing subsequent calls to <code>InputStream</code>\n     * methods to operate on the new stream. Returns <code>true</code> if there was a next stream, <code>false</code>\n     * otherwise.\n     * <p>\n     * Note: the current stream (if any) will be closed by this method.\n     *\n     * @return <code>true</code> if there was a next stream, <code>false</code> otherwise\n     * @throws IOException if an error occurred while trying to advancing the current stream. This\n     * <code>CompoundInputStream</code> can't be used after that and must be closed.\n     */\n    public boolean advanceInputStream() throws IOException {\n        // Return immediately (don't close the stream) if this method is global EOF has already been reached\n        if(globalEOFReached)\n            return false;\n\n        // Close the current stream\n        if(currentIn!=null) {\n            try {\n                closeCurrentInputStream();\n            }\n            catch(IOException e) {\n                // Fail silently\n            }\n        }\n\n        // Try to advance the current InputStream to the next\n        try {\n            currentIn = getNextInputStream();\n        }\n        catch(IOException e) {\n            // Can't recover from this, this is the end of this stream\n            globalEOFReached = true;\n            throw e;\n        }\n\n        if(currentIn==null) {\n            // Global EOF reached\n            globalEOFReached = true;\n            return false;\n        }\n        return true;\n    }\n\n\n    /**\n     * Checks the current stream and returns <code>true</code> if the current stream is in a state where it can be\n     * accessed, <code>false</code> if global EOF has been reached.\n     *\n     * @return <code>true</code> if the current stream is in a state where it can be accessed, <code>false</code> if\n     * global EOF has been reached.\n     * @throws IOException if an error occurred while trying to advancing the current stream.\n     */\n    private boolean checkStream() throws IOException {\n        if(globalEOFReached)\n            return true;\n\n        if(currentIn==null)\n            return !advanceInputStream();\n\n        return false;\n    }\n\n\n    //////////////////////\n    // Abstract methods //\n    //////////////////////\n\n    /**\n     * Returns the next <code>InputStream</code>, <code>null</code> if there is none.\n     * <p>\n     * Before calling this method, {@link #advanceInputStream()} closes the current stream (if any). In other words,\n     * implementations do not have to worry about closing previously-returned streams.\n     *\n     * @return the next <code>InputStream</code>, <code>null</code> if there is none.\n     * @throws IOException if an error occurred while retrieving the next input stream \n     */\n    public abstract InputStream getNextInputStream() throws IOException;\n\n\n    ////////////////////////////////\n    // InputStream implementation //\n    ////////////////////////////////\n\n    /**\n     * Delegates to {@link #read(byte[], int, int)} with a 1-byte buffer.\n     */\n    @Override\n    public int read() throws IOException {\n        if(oneByteBuf==null)\n            oneByteBuf = new byte[1];\n\n        int ret = read(oneByteBuf, 0, 1);\n\n        return ret<=0?ret:oneByteBuf[0];\n    }\n\n    /**\n     * Delegates to {@link #read(byte[], int, int)} with a <code>0</code> offset and the whole buffer's length.\n     */\n    @Override\n    public int read(byte[] b) throws IOException {\n        return read(b, 0, b.length);\n    }\n\n    /**\n     * Reads up to <code>len-off</code> bytes and stores them in the specified byte buffer, starting at <code>off</code>.\n     * Returns the number of bytes that were actually read, or <code>-1</code> to signal:\n     * <ul>\n     *   <li>if {@link #isMerged()} is <code>true</code>, the end of the compound stream as a whole</li>\n     *   <li>if {@link #isMerged ()} is <code>false</code>, the end of the current stream, which may or may not coincide\n     * with the end of the stream as a whole.</li>\n     * </ul>\n     */\n    @Override\n    public int read(byte[] b, int off, int len) throws IOException {\n        if(checkStream())\n            return -1;\n\n        int ret = currentIn.read(b, off, len);\n\n        if(ret==-1) {\n            // read the next stream\n            if(merged) {\n                if(!advanceInputStream())\n                    return -1;      // Global EOF reached\n\n                // Recurse\n                return read(b, off, len);\n            }\n\n            return -1;\n        }\n\n        return ret;\n    }\n\n    /**\n     * Skips up to <code>n</code> bytes and returns the number of bytes that were actually skipped, or <code>-1</code>\n     * to signal:\n     * <ul>\n     *   <li>if {@link #isMerged()} is enabled, the end of the compound stream as a whole</li>\n     *   <li>if {@link #isMerged ()} is disabled, the end of the current stream, which may or may not coincide\n     * with the end of the stream as a whole.</li>\n     * </ul>\n     */\n    @Override\n    public long skip(long n) throws IOException {\n        if(checkStream())\n            return -1;\n\n        long ret = currentIn.skip(n);\n\n        if(ret==-1) {\n            // read the next stream\n            if(merged) {\n                if(!advanceInputStream())\n                    return -1;      // Global EOF reac  hed\n\n                return currentIn.skip(n);\n            }\n\n            return -1;\n        }\n\n        return ret;\n    }\n\n    /**\n     * Closes the current <code>InputStream</code> and this <code>CompoundInputStream</code> a whole.\n     * The current stream can no longer be advanced after this method has been called.\n     *\n     * @throws IOException if the current stream could not be closed.  \n     */\n    @Override\n    public void close() throws IOException {\n        try {\n            if(currentIn!=null)\n                closeCurrentInputStream();\n        }\n        finally {\n            globalEOFReached = true;\n        }\n    }\n\n    /**\n     * Delegates to the current <code>InputStream</code>.\n     */\n    @Override\n    public int available() throws IOException {\n        if(checkStream())\n            return 0;\n\n        return currentIn.available();\n    }\n\n    /**\n     * Delegates to the current <code>InputStream</code>.\n     */\n    @Override\n    public void mark(int readlimit) {\n        try {\n            if(!checkStream())\n                currentIn.mark(readlimit);\n        }\n        catch(IOException e) {\n            // Can't throw an IOException here unfortunately, fail silently\n        }\n    }\n\n    /**\n     * Delegates to the current <code>InputStream</code>.\n     */\n    @Override\n    public void reset() throws IOException {\n        if(!checkStream())\n            currentIn.reset();\n    }\n\n    /**\n     * Delegates to the current <code>InputStream</code>.\n     */\n    @Override\n    public boolean markSupported() {\n        try {\n            return !checkStream() && currentIn.markSupported();\n        }\n        catch(IOException e) {\n            return false;\n        }\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/compound/CompoundReader.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io.compound;\n\nimport java.io.IOException;\nimport java.io.Reader;\n\n/**\n * <code>CompoundReader</code> concatenates several readers into one. It can operate in two modes:\n * <dl>\n *   <dt>Merged</dt>\n *   <dd>the compound reader acts as a single, global reader, merging the contents of underlying readers. The\n * compound reader can be read just like a regular <code>Reader</code> -- readers are advanced automatically as EOF\n * of individual readers are reached.</dd>\n *\n *   <dt>Unmerged</dt>\n *   <dd>the compound reader has be advanced manually. EOF are signaled individually for each underlying reader.\n * After EOF is reached, the current reader has to be advanced to the next one using {@link #advanceReader()}.</dd>\n * </dl>\n *\n * <p>\n * This class is abstract, with a single method to implement: {@link #getNextReader()}.\n * See {@link IteratorCompoundReader} for an <code>Iterator</code>-backed implementation.\n *\n * @see IteratorCompoundReader\n * @see CompoundInputStream\n * @author Maxence Bernard\n */\npublic abstract class CompoundReader extends Reader {\n\n    /** True if this CompoundReader operates in 'merged' mode */\n    private boolean merged;\n\n    /** The Reader that's currently being processed */\n    private Reader currentReader;\n\n    /** Used by {@link #read()} */\n    private char oneCharBuf[];\n\n    /** <code>true</code> if the global EOF has been reached */\n    private boolean globalEOFReached;\n\n\n    /**\n     * Creates a new <code>CompoundReader</code> operating in the specified mode.\n     *\n     * @param merged <code>true</code> if the readers should be merged, acting as a single reader, or considered\n     * as separate readers that have to be {@link #advanceReader() advanced manually}.\n     */\n    public CompoundReader(boolean merged) {\n        this.merged = merged;\n    }\n\n    /**\n     * Returns:\n     * <ul>\n     *   <li><code>true</code> if this reader acts as a single, global input reader, merging the contents of underlying\n     * readers. In this mode, the compound reader can be read just like a regular Reader -- readers are advanced\n     * automatically as EOF of individual readers are reached.</li>\n     *   <li><code>false</code> if this reader has be advanced manually. In this mode, EOF are signaled individually\n     * for each underlying reader. After EOF has been reached, the current reader has to be advanced to the next one\n     * using {@link #advanceReader()}.</li>\n     * </ul>\n     *\n     * @return <code>true</code> if this reader acts as a global reader, <code>false</code> if this reader has\n     * to be advanced manually.\n     */\n    public boolean isMerged() {\n        return merged;\n    }\n\n    /**\n     * Returns the <code>Reader</code> this compound reader is currently reading, <code>null</code> if this reader\n     * hasn't read anything yet, or if it has no underlying reader to read.\n     *\n     * @return <code>Reader</code> this compound reader is currently reading\n     */\n    public Reader getCurrentReader() {\n        return currentReader;\n    }\n\n    /**\n     * Closes the current reader, if any. This method has no effect if there is no current reader.\n     *\n     * @throws IOException if an error occurred while closing the current reader.\n     */\n    public void closeCurrentReader() throws IOException {\n        if(currentReader!=null)\n            currentReader.close();\n    }\n\n    /**\n     * Advances the current reader to the next one, causing subsequent calls to <code>Reader</code>\n     * methods to operate on the new reader. Returns <code>true</code> if there was a next reader, <code>false</code>\n     * otherwise.\n     * <p>\n     * Note: the current reader (if any) will be closed by this method.\n     *\n     * @return <code>true</code> if there was a next reader, <code>false</code> otherwise\n     * @throws IOException if an error occurred while trying to advancing the current reader. This\n     * <code>CompoundReader</code> can't be used after that and must be closed.\n     */\n    public boolean advanceReader() throws IOException {\n        // Return immediately (don't close the reader) if this method is global EOF has already been reached\n        if(globalEOFReached)\n            return false;\n\n        // Close the current reader\n        if(currentReader !=null) {\n            try {\n                closeCurrentReader();\n            }\n            catch(IOException e) {\n                // Fail silently\n            }\n        }\n\n        // Try to advance the current Reader to the next\n        try {\n            currentReader = getNextReader();\n        }\n        catch(IOException e) {\n            // Can't recover from this, this is the end of this stream\n            globalEOFReached = true;\n            throw e;\n        }\n\n        if(currentReader==null) {\n            // Global EOF reached\n            globalEOFReached = true;\n            return false;\n        }\n        return true;\n    }\n\n\n    /**\n     * Checks the current reader and returns <code>true</code> if the current reader is in a state where it can be\n     * accessed, <code>false</code> if global EOF has been reached.\n     *\n     * @return <code>true</code> if the current reader is in a state where it can be accessed, <code>false</code> if\n     * global EOF has been reached.\n     * @throws IOException if an error occurred while trying to advancing the current reader.\n     */\n    private boolean checkReader() throws IOException {\n        if(globalEOFReached)\n            return true;\n\n        if(currentReader ==null)\n            return !advanceReader();\n\n        return false;\n    }\n\n\n    //////////////////////\n    // Abstract methods //\n    //////////////////////\n\n    /**\n     * Returns the next <code>Reader</code>, <code>null</code> if there is none.\n     * <p>\n     * Before calling this method, {@link #advanceReader()} closes the current reader (if any). In other words,\n     * implementations do not have to worry about closing previously-returned readers.\n     *\n     * @return the next <code>Reader</code>, <code>null</code> if there is none.\n     * @throws IOException if an error occurred while retrieving the next reader \n     */\n    public abstract Reader getNextReader() throws IOException;\n\n\n    ///////////////////////////\n    // Reader implementation //\n    ///////////////////////////\n\n    /**\n     * Delegates to {@link #read(char[], int, int)} with a 1-char buffer.\n     */\n    @Override\n    public int read() throws IOException {\n        if(oneCharBuf ==null)\n            oneCharBuf = new char[1];\n\n        int ret = read(oneCharBuf, 0, 1);\n\n        return ret<=0?ret:oneCharBuf[0];\n    }\n\n    /**\n     * Delegates to {@link #read(char[], int, int)} with a <code>0</code> offset and the whole buffer's length.\n     */\n    @Override\n    public int read(char[] c) throws IOException {\n        return read(c, 0, c.length);\n    }\n\n    /**\n     * Reads up to <code>len-off</code> characters and stores them in the specified buffer, starting at <code>off</code>.\n     * Returns the number of characters that were actually read, or <code>-1</code> to signal:\n     * <ul>\n     *   <li>if {@link #isMerged()} is enabled, the end of the compound reader as a whole</li>\n     *   <li>if {@link #isMerged ()} is disabled, the end of the current reader, which may or may not coincide\n     * with the end of the reader as a whole.</li>\n     * </ul>\n     */\n    @Override\n    public int read(char[] c, int off, int len) throws IOException {\n        if(checkReader())\n            return -1;\n\n        int ret = currentReader.read(c, off, len);\n\n        if(ret==-1) {\n            // read the next reader\n            if(merged) {\n                if(!advanceReader())\n                    return -1;      // Global EOF reached\n\n                // Recurse\n                return read(c, off, len);\n            }\n\n            return -1;\n        }\n\n        return ret;\n    }\n\n    /**\n     * Skips up to <code>n</code> characters and returns the number of characters that were actually skipped, or\n     * <code>-1</code> to signal:\n     * <ul>\n     *   <li>if {@link #isMerged()} is enabled, the end of the compound reader as a whole</li>\n     *   <li>if {@link #isMerged ()} is disabled, the end of the current reader, which may or may not coincide\n     * with the end of the reader as a whole.</li>\n     * </ul>\n     */\n    @Override\n    public long skip(long n) throws IOException {\n        if(checkReader())\n            return -1;\n\n        long ret = currentReader.skip(n);\n\n        if(ret==-1) {\n            // read the next reader\n            if(merged) {\n                if(!advanceReader())\n                    return -1;      // Global EOF reached\n\n                return currentReader.skip(n);\n            }\n\n            return -1;\n        }\n\n        return ret;\n    }\n\n    /**\n     * Closes the current <code>Reader</code> and this <code>CompoundReader</code> a whole.\n     * The current reader can no longer be advanced after this method has been called.\n     *\n     * @throws IOException if the current reader could not be closed.\n     */\n    @Override\n    public void close() throws IOException {\n        try {\n            if(currentReader!=null)\n                closeCurrentReader();\n        }\n        finally {\n            globalEOFReached = true;\n        }\n    }\n\n    /**\n     * Delegates to the current <code>Reader</code>.\n     */\n    @Override\n    public boolean ready() throws IOException {\n        return !checkReader() && currentReader.ready();\n    }\n\n    /**\n     * Delegates to the current <code>Reader</code>.\n     */\n    @Override\n    public void mark(int readlimit) throws IOException {\n        if(!checkReader())\n            currentReader.mark(readlimit);\n    }\n\n    /**\n     * Delegates to the current <code>Reader</code>.\n     */\n    @Override\n    public void reset() throws IOException {\n        if(!checkReader())\n            currentReader.reset();\n    }\n\n    /**\n     * Delegates to the current <code>Reader</code>.\n     */\n    @Override\n    public boolean markSupported() {\n        try {\n            return !checkReader() && currentReader.markSupported();\n        }\n        catch(IOException e) {\n            return false;\n        }\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/compound/IteratorCompoundInputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io.compound;\n\nimport java.io.InputStream;\nimport java.util.Iterator;\n\n/**\n * A <code>CompoundInputStream</code> implementation using an {@link Iterator} to implement {@link #getNextInputStream()}.\n *\n * @author Maxence Bernard\n */\npublic class IteratorCompoundInputStream extends CompoundInputStream {\n\n    /** Iterator containing the InputStreams to be concatenated */\n    private Iterator<? extends InputStream> inputStreamIterator;\n\n    /**\n     * Creates a new compound input stream using the {@link InputStream} instances contained by the given\n     * {@link Iterator} and the specified mode.\n     *\n     * @param inputStreamIterator an Iterator that contains the {@link InputStream} instances to be used\n     * by this <code>CompoundInputStream</code>.\n     * @param merged <code>true</code> if the streams should be merged, acting as a single stream, or considered\n     * as separate streams that have to be {@link #advanceInputStream() advanced manually}.\n     */\n    public IteratorCompoundInputStream(Iterator<? extends InputStream> inputStreamIterator, boolean merged) {\n        super(merged);\n\n        this.inputStreamIterator = inputStreamIterator;\n    }\n\n\n    ////////////////////////////////////////\n    // CompoundInputStream implementation //\n    ////////////////////////////////////////\n\n    @Override\n    public InputStream getNextInputStream() {\n        return inputStreamIterator.hasNext()?inputStreamIterator.next():null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/compound/IteratorCompoundReader.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io.compound;\n\nimport java.io.Reader;\nimport java.util.Iterator;\n\n/**\n * A <code>CompoundReader</code> implementation using an {@link Iterator} to implement {@link #getNextReader()}.\n *\n * @author Maxence Bernard\n */\npublic class IteratorCompoundReader extends CompoundReader {\n\n    /** Iterator containing the readers to be concatenated */\n    private Iterator<? extends Reader> readerIterator;\n\n    /**\n     * Creates a new compound reader using the {@link Reader} instances contained by the given\n     * {@link Iterator} and the specified mode.\n     *\n     * @param readerIterator an Iterator that contains the {@link Reader} instances to be used\n     * by this <code>CompoundReader</code>.\n     * @param merged <code>true</code> if the reader should be merged, acting as a single reader, or considered\n     * as separate readers that have to be {@link #advanceReader() advanced manually}.\n     */\n    public IteratorCompoundReader(Iterator<? extends Reader> readerIterator, boolean merged) {\n        super(merged);\n\n        this.readerIterator = readerIterator;\n    }\n\n\n    ///////////////////////////////////\n    // CompoundReader implementation //\n    ///////////////////////////////////\n\n    @Override\n    public Reader getNextReader() {\n        return readerIterator.hasNext()?readerIterator.next():null;\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/package.html",
    "content": "<body>\n  Provides various I/O related classes.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/security/Adler32MessageDigest.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io.security;\n\nimport java.util.zip.Adler32;\n\n/**\n * Provides a <code>ChecksumMessageDigest</code> implementation of the <i>Adler32</i> algorithm, using the\n * <code>java.util.zip.Adler32</code> class.\n *\n * @author Maxence Bernard\n */\npublic class Adler32MessageDigest extends ChecksumMessageDigest {\n\n    public Adler32MessageDigest() {\n        super(new Adler32(), getAlgorithmName());\n    }\n\n    /**\n     * Returns the name of the algorithm implemented by this MessageDigest.\n     *\n     * @return the name of the algorithm implemented by this MessageDigest\n     */\n    protected static String getAlgorithmName() {\n        return \"Adler32\";\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/security/CRC32MessageDigest.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io.security;\n\nimport java.util.zip.CRC32;\n\n/**\n * Provides a <code>ChecksumMessageDigest</code> implementation of the <i>CRC32</i> algorithm, using the\n * <code>java.util.zip.CRC32</code> class.\n *\n * @author Maxence Bernard\n */\npublic class CRC32MessageDigest extends ChecksumMessageDigest {\n\n    public CRC32MessageDigest() {\n        super(new CRC32(), getAlgorithmName());\n    }\n\n    /**\n     * Returns the name of the algorithm implemented by this MessageDigest.\n     *\n     * @return the name of the algorithm implemented by this MessageDigest\n     */\n    protected static String getAlgorithmName() {\n        return \"CRC32\";\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/security/ChecksumMessageDigest.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io.security;\n\nimport java.security.MessageDigest;\nimport java.util.zip.Checksum;\n\n/**\n * This class turns a <code>java.util.zip.Checksum</code> into a <code>java.security.MessageDigest</code>, allowing\n * <code>Checksum</code> implementations to be used with the Java Cryptography Extension.\n *\n * @author Maxence Bernard\n */\npublic class ChecksumMessageDigest extends MessageDigest {\n\n    /** The Checksum instance that performs all of the checksumming work */\n    private Checksum checksum;\n\n    /**\n     * Creates a new <code>ChecksumMessageDigest</code> that delegates all the checksumming work to the given\n     * <code>Checksum</code> instance. \n     *\n     * @param checksum the Checksum responsible for calculating the checksum\n     * @param algorithm the name of the checksum algorithm implemented by the Checksum\n     */\n    public ChecksumMessageDigest(Checksum checksum, String algorithm) {\n        super(algorithm);\n\n        this.checksum = checksum;\n    }\n\n\n    //////////////////////////////////\n    // MessageDigest implementation //\n    //////////////////////////////////\n\n    /**\n     * This method delegates to the underlying <code>java.util.zip.Checksum</code> instance.\n     */\n    @Override\n    protected void engineReset() {\n        checksum.reset();\n    }\n\n    /**\n     * This method delegates to the underlying <code>java.util.zip.Checksum</code> instance.\n     */\n    @Override\n    protected void engineUpdate(byte input) {\n        checksum.update(input);\n    }\n\n    /**\n     * This method delegates to the underlying <code>java.util.zip.Checksum</code> instance.\n     */\n    @Override\n    protected void engineUpdate(byte[] input, int offset, int len) {\n        checksum.update(input, offset, len);\n    }\n\n    /**\n     * This method delegates to the underlying <code>java.util.zip.Checksum</code> instance.\n     */\n    @Override\n    protected byte[] engineDigest() {\n        long crcLong = checksum.getValue();\n\n        byte[] crcBytes = new byte[4];\n        crcBytes[0] = (byte)((crcLong>>24) & 0xFF);\n        crcBytes[1] = (byte)((crcLong>>16) & 0xFF);\n        crcBytes[2] = (byte)((crcLong>>8) & 0xFF);\n        crcBytes[3] = (byte)(crcLong & 0xFF);\n\n        return crcBytes;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/security/MuProvider.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io.security;\n\nimport java.security.Provider;\nimport java.security.Security;\n\n/**\n * This custom <code>java.security.Provider</code> exposes trolCommander's own <code>MessageDigest</code> implementations,\n * and the ones used aggregated from third party libraries.\n *\n * <p>The {@link #registerProvider()} method should be called once to register this <code>Provider</code> with the\n * Java Cryptography Extension and advertise the additional <code>MessageDigest</code> implementations.\n *\n * @author Maxence Bernard\n */\npublic class MuProvider extends Provider {\n\n    /** True if an instance of this Provider has already been registered with java.security.Security */\n    private static boolean initialized;\n\n    private MuProvider() {\n        super(\"trolCommander\", \"1.0\", \"trolCommander's additional MessageDigest implementations.\");\n    }\n\n    /**\n     * Registers an instance of this Provider with the <code>java.security.Security</code> class, to expose the\n     * additional <code>MessageDigest</code> implementations and have them returned by\n     * <code>java.security.Security.getAlgorithms(\"MessageDigest\")</code>.\n     * This method should be called once\n     */\n    public static void registerProvider() {\n        // A Provider must be registered only once\n        if (initialized)\n            return;\n\n        MuProvider provider = new MuProvider();\n\n        // Add our own MessageDigest implementations\n        provider.put(\"MessageDigest.\"+Adler32MessageDigest.getAlgorithmName(), Adler32MessageDigest.class.getName());\n        provider.put(\"MessageDigest.\"+CRC32MessageDigest.getAlgorithmName(), CRC32MessageDigest.class.getName());\n\n        // Register the provider with java.security.Security\n        Security.addProvider(provider);\n\n        // A Provider must be registered only once\n        initialized = true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/io/security/package.html",
    "content": "<body>\n  This package contains add-ons to the Java Security API that can be registered at runtime using {@link com.mucommander.commons.io.security.MuProvider}. \n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/runtime/ComparableRuntimeProperty.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.runtime;\n\n/**\n * Utility methods for comparing the current runtime's value of this property to this instance\n *\n * @author Arik Hadas, Maxence Bernard\n*/\npublic interface ComparableRuntimeProperty {\n\n    /**\n     * Returns <code>true</code> if the current runtime's value of this property is equal or lower to this instance,\n     * according to {@link Enum#compareTo(Object)}.\n     *\n     * @return {@code true} if the current runtime's value of this property is equal or lower to this instance\n     */\n    boolean isCurrentOrLower();\n\n    /**\n     * Returns <code>true</code> if the current runtime's value of this property is lower than this instance,\n     * according to {@link Enum#compareTo(Object)}.\n     *\n     * @return {@code true} if the current runtime's value of this property is lower than this instance\n     */\n    boolean isCurrentLower();\n\n    /**\n     * Returns {@code true} if the current runtime's value of this property is equal or higher to this instance,\n     * according to {@link Enum#compareTo(Object)}.\n     *\n     * @return {@code true} if the current runtime's value of this property is equal or higher to this instance\n     */\n    boolean isCurrentOrHigher();\n\n    /**\n     * Returns {@code true} if the current runtime's value of this property is higher than this instance,\n     * according to {@link Enum#compareTo(Object)}.\n     *\n     * @return {@code true} if the current runtime's value of this property is higher than this instance\n     */\n    boolean isCurrentHigher();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/runtime/JavaVersion.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.runtime;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * This class represents a major version of Java, like <code>Java 1.5</code> for instance. The current runtime instance\n * is determined using the value of the <code>java.version</code> system property.\n * Being a {@link com.mucommander.commons.runtime.ComparableRuntimeProperty}, versions of Java are ordered and can be compared\n * against each other.\n *\n * @author Maxence Bernard, Arik Hadas\n*/\npublic enum JavaVersion implements ComparableRuntimeProperty {\n    JAVA_11(\"11\"),\n    /** Java 12.x */\n    JAVA_12(\"12\"),\n    /** Java 13.x */\n    JAVA_13(\"13\"),\n    /** Java 14.x */\n    JAVA_14(\"14\"),\n    /** Java 15.x */\n    JAVA_15(\"15\"),\n    /** Java 16.x */\n    JAVA_16(\"16\"),\n    /** Java 17.x */\n    JAVA_17(\"17\"),\n    /** Java 18.x */\n    JAVA_18(\"18\"),\n    /** Java 19.x */\n    JAVA_19(\"19\"),\n    /** Java 20.x */\n    JAVA_20(\"20\"),\n    /** Java 21.x */\n    JAVA_21(\"21\"),\n    /** Java 22.x */\n    JAVA_22(\"22\"),\n    /** Java 23.x */\n    JAVA_23(\"23\");\n\n    /** Logger used by this class. */\n    private static final Logger LOGGER = LoggerFactory.getLogger(JavaVersion.class);\n\n    /** Holds the JavaVersion of the current runtime environment  */\n    private static final JavaVersion CURRENT_VALUE;\n\n    /** Holds the String representation of the current JVM architecture  */\n    private static final String CURRENT_ARCHITECTURE;\n\n    /** The String representation of this RuntimeProperty, set at creation time */\n    private final String stringRepresentation;\n\n    static {\n    \tCURRENT_VALUE = parseSystemProperty(getRawSystemProperty());\n    \tCURRENT_ARCHITECTURE = System.getProperty(\"os.arch\");\n    \tLOGGER.info(\"Current Java version: {}\", CURRENT_VALUE);\n    \tLOGGER.info(\"Current JVM architecture: {}\", CURRENT_ARCHITECTURE);\n    }\n\n\n    JavaVersion(String stringRepresentation) {\n    \tthis.stringRepresentation = stringRepresentation;\n    }\n\n\n    /**\n     * Returns <code>true</code> if the JVM architecture is amd64\n     *\n     * @return <code>true</code> if the JVM architecture is amd64, and <code>false</code> otherwise.\n     */\n    public static boolean isAmd64Architecture() {\n    \treturn \"amd64\".equals(CURRENT_ARCHITECTURE);\n    }\n\n    /**\n     * Returns the Java version of the current runtime environment.\n     *\n     * @return the Java version of the current runtime environment\n     */\n    public static JavaVersion getCurrent() {\n        return CURRENT_VALUE;\n    }\n\n    /**\n     * Returns the value of the system property which serves to detect the Java version at runtime.\n     *\n     * @return the value of the system property which serves to detect the Java version at runtime.\n     */\n    public static String getRawSystemProperty() {\n        return System.getProperty(\"java.version\");\n    }\n\n    /**\n     * Returns a <code>JavaVersion</code> instance corresponding to the specified system property's value.\n     *\n     * @param s the value of the \"java.version\" system property\n     * @return a JavaVersion instance corresponding to the specified system property's value\n     */\n    static JavaVersion parseSystemProperty(String s) {\n        // Java version property should never be null or empty, but better be safe than sorry ...\n        if (s == null || (s = s.trim()).isEmpty())\n            // Assume java 11 (first supported Java version)\n            return JavaVersion.JAVA_11;\n        if (s.startsWith(\"22\")) {\n            return JavaVersion.JAVA_22;\n        } else if (s.startsWith(\"21\")) {\n            return JavaVersion.JAVA_21;\n        } else if (s.startsWith(\"20\")) {\n            return JavaVersion.JAVA_20;\n        } else if (s.startsWith(\"19\")) {\n            return JavaVersion.JAVA_19;\n        } else if (s.startsWith(\"18\")) {\n            return JavaVersion.JAVA_18;\n        } else if (s.startsWith(\"17\")) {\n            return JavaVersion.JAVA_17;\n        } else if (s.startsWith(\"16\")) {\n            return JavaVersion.JAVA_16;\n        } else if (s.startsWith(\"15\")) {\n            return JavaVersion.JAVA_15;\n        } else if (s.startsWith(\"14\")) {\n            return JavaVersion.JAVA_14;\n        } else if (s.startsWith(\"13\")) {\n            return JavaVersion.JAVA_13;\n        } else if (s.startsWith(\"12\")) {\n            return JavaVersion.JAVA_12;\n        } else if (s.startsWith(\"11\")) {\n            return JavaVersion.JAVA_11;\n        }\n        // Newer version we don't know of yet, assume latest supported Java version\n        return JavaVersion.JAVA_23;\n    }\n\n    /**\n     * Returns <code>true</code> if this instance is the same instance as the one returned by {@link #getCurrent()}.\n     *\n     * @return true if this instance is the same as the current runtime value\n     */\n    public boolean isCurrent() {\n        return this == CURRENT_VALUE;\n    }\n\n\n    @Override\n\tpublic boolean isCurrentOrLower() {\n\t\treturn CURRENT_VALUE.compareTo(this) <= 0;\n\t}\n\n\t@Override\n\tpublic boolean isCurrentLower() {\n\t\treturn CURRENT_VALUE.compareTo(this) < 0;\n\t}\n\n\t@Override\n\tpublic boolean isCurrentOrHigher() {\n\t\treturn CURRENT_VALUE.compareTo(this) >= 0;\n\t}\n\n\t@Override\n\tpublic boolean isCurrentHigher() {\n\t\treturn CURRENT_VALUE.compareTo(this) > 0;\n\t}\n\n    @Override\n    public String toString() {\n        return stringRepresentation;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/runtime/OsFamily.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.runtime;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * This class represents a non-versioned family of operating system, like <code>Windows</code> or <code>Linux</code>. \n * The current runtime instance is determined using the value of the <code>os.name</code> system property.\n *\n * @see OsVersion\n * @author Maxence Bernard, Arik Hadas\n */\npublic enum OsFamily {\n\t/** Windows */\n    WINDOWS(\"Windows\",false),\n    /** Mac OS X */\n    MAC_OS_X(\"Mac OS X\"),\n    /** Linux */\n    LINUX(\"Linux\"),\n    /** Solaris */\n    SOLARIS(\"Solaris\"),\n    /** OS/2 */\n    OS_2(\"OS/2\"),\n    /** FreeBSD */\n    FREEBSD(\"FreeBSD\"),\n    /** AIX */\n    AIX(\"AIX\"),\n    /** HP-UX */\n    HP_UX(\"HP-UX\"),\n    /** OpenVMS */\n    OPENVMS(\"OpenVMS\"),\n    /** Other OS */\n    UNKNOWN_OS_FAMILY(\"Unknown\");\n\n    /** Logger used by this class. */\n    private static Logger logger;\n\n    /** The String representation of this RuntimeProperty, set at creation time */\n    private final String stringRepresentation;\n    \n    private final boolean isCaseSensitiveFilesystem;\n\n    /** Holds the OsFamily of the current runtime environment  */\n    private static OsFamily currentValue;\n\n    \n    OsFamily(String stringRepresentation) {\n    \tthis(stringRepresentation,true);\n    }\n    \n    OsFamily(String stringRepresentation, boolean isCaseSensitiveFilesystem) {\n    \tthis.stringRepresentation = stringRepresentation;\n    \tthis.isCaseSensitiveFilesystem = isCaseSensitiveFilesystem;\n    }\n\n\n    /**\n     * Returns the OS family of the current runtime environment.\n     *\n     * @return the OS family of the current runtime environment\n     */\n    public static OsFamily getCurrent() {\n        if (currentValue == null) {\n            currentValue = parseSystemProperty(getRawSystemProperty());\n        }\n        return currentValue;\n    }\n\n    /**\n     * Returns <code>true</code> if this OS family is UNIX-based. The following OS families are considered UNIX-based:\n     * <ul>\n     *  <li>{@link #LINUX}</li>\n     *  <li>{@link #MAC_OS_X}</li>\n     *  <li>{@link #SOLARIS}</li>\n     *  <li>{@link #FREEBSD}</li>\n     *  <li>{@link #AIX}</li>\n     *  <li>{@link #HP_UX}</li>\n     *  <li>{@link #UNKNOWN_OS_FAMILY}: the reason for this being that most alternative OSes are Unix-based.</li>\n     * </ul>\n     *\n     * @return <code>true</code> if the current OS is UNIX-based\n     */\n    public boolean isUnixBased() {\n        return this == MAC_OS_X\n                || this == LINUX\n                || this == SOLARIS\n                || this == FREEBSD\n                || this == AIX\n                || this == HP_UX\n                || this == UNKNOWN_OS_FAMILY;\n\n        // Not UNIX-based: WINDOWS, OS/2 and OpenVMS\n    }\n\n    /**\n     * Returns the value of the system property which serves to detect the OS family at runtime.\n     *\n     * @return the value of the system property which serves to detect the OS family at runtime.\n     */\n    public static String getRawSystemProperty() {\n        return System.getProperty(\"os.name\");\n    }\n\n    public static String getRawOsArch() {\n        return System.getProperty(\"os.arch\");\n    }\n\n    public static boolean isAarch64() {\n        return \"aarch64\".equals(getRawOsArch());\n    }\n\n    public static boolean isAmd64() {\n        return \"amd64\".equals(getRawOsArch());\n    }\n\n    /**\n     * Returns an <code>OsFamily</code> instance corresponding to the specified system property's value.\n     *\n     * @param osNameProp the value of the \"os.name\" system property\n     * @return an OsFamily instance corresponding to the specified system property's value\n     */\n    static OsFamily parseSystemProperty(String osNameProp) {\n        // This website holds a collection of system property values under many OSes:\n        // http://lopica.sourceforge.net/os.html\n\n        // Windows family\n        if (osNameProp.startsWith(\"Windows\")) {\n            return WINDOWS;\n        }\n        // Mac OS X family\n        if (osNameProp.startsWith(\"Mac OS X\")) {\n            return MAC_OS_X;\n        }\n        // OS/2 family\n        if (osNameProp.startsWith(\"OS/2\")) {\n            return OS_2;\n        }\n        // Linux family\n        if (osNameProp.startsWith(\"Linux\")) {\n            return LINUX;\n        }\n        // Solaris family\n        if (osNameProp.startsWith(\"Solaris\") || osNameProp.startsWith(\"SunOS\")) {\n            return SOLARIS;\n        }\n        if (osNameProp.startsWith(\"FreeBSD\")) {\n            return FREEBSD;\n        }\n        if (osNameProp.startsWith(\"AIX\")) {\n            return AIX;\n        }\n        if (osNameProp.startsWith(\"HP-UX\")) {\n            return HP_UX;\n        }\n        if (osNameProp.startsWith(\"OpenVMS\")) {\n            return OPENVMS;\n        }\n\n        // Any other OS\n        return UNKNOWN_OS_FAMILY;\n    }\n\n    /**\n     * Returns <code>true</code> if this instance is the same instance as the one returned by {@link #getCurrent()}.\n     *\n     * @return true if this instance is the same as the current runtime's value\n     */\n    public boolean isCurrent() {\n        return this == currentValue;\n    }\n\n\n    @Override\n    public String toString() {\n        return stringRepresentation;\n    }\n\n\n\tpublic boolean isCaseSensitiveFilesystem() {\n\t\treturn isCaseSensitiveFilesystem;\n\t}\n\n    private static Logger getLogger() {\n        if (logger == null) {\n            logger = LoggerFactory.getLogger(OsFamily.class);\n        }\n        return logger;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/runtime/OsVersion.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.runtime;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * This class represents a major version of an operating system, like <code>Mac OS X 10.5</code> or\n * <code>Windows XP</code>. The current runtime value is determined using the value of the <code>os.version</code>\n * system property and the current {@link OsFamily} instance.\n * Being a {@link com.mucommander.commons.runtime.ComparableRuntimeProperty}, OS versions are ordered and can be compared\n * against each other.\n *\n * @see OsFamily\n * @author Maxence Bernard, Arik Hadas\n */\npublic enum OsVersion implements ComparableRuntimeProperty {\n\t/** Unknown OS version */\n\tUNKNOWN_VERSION(\"Unknown\"),\n\n\n\t/** Windows 95 */\n\tWINDOWS_95(\"Windows 95\"),\n\t/** Windows 98 */\n\tWINDOWS_98(\"Windows 98\"),\n\t/** Windows Me */\n\tWINDOWS_ME(\"Windows Me\"),\n\n\t// Windows NT subfamily\n\n\t/** Windows NT */\n\tWINDOWS_NT(\"Windows NT\"),\n\t/** Windows 2000 */\n\tWINDOWS_2000(\"Windows 2000\"),\n\t/** Windows XP */\n\tWINDOWS_XP(\"Windows XP\"),\n\t/** Windows 2003 */\n\tWINDOWS_2003(\"Windows 2003\"),\n\t/** Windows Vista */\n\tWINDOWS_VISTA(\"Windows Vista\"),\n\t/** Windows 7 */\n\tWINDOWS_7(\"Windows 7\"),\n\t/** Windows 8 */\n\tWINDOWS_8(\"Windows 8\"),\n    /** Windows 8 */\n    WINDOWS_8_1(\"Windows 8.1\"),\n    /** Windows 10 */\n    WINDOWS_10(\"Windows 10\"),\n    /** Windows 11 */\n    WINDOWS_11(\"Windows 11\"),\n\n\n\t/** Mac OS X 10.0 (Cheetah) */\n\tMAC_OS_X_10_0(\"10.0\"),\n\t/** Mac OS X 10.1 (Puma) */\n\tMAC_OS_X_10_1(\"10.1\"),\n\t/** Mac OS X 10.2 (Jaguar) */\n\tMAC_OS_X_10_2(\"10.2\"),\n\t/** Mac OS X 10.3 (Panther) */\n\tMAC_OS_X_10_3(\"10.3\"),\n\t/** Mac OS X 10.4 (Tiger) */\n\tMAC_OS_X_10_4(\"10.4\"),\n\t/** Mac OS X 10.5 (Leopard) */\n\tMAC_OS_X_10_5(\"10.5\"),\n\t/** Mac OS X 10.6 (Snow Leopard) */\n\tMAC_OS_X_10_6(\"10.6\"),\n\t/** Mac OS X 10.7 (Lion) */\n\tMAC_OS_X_10_7(\"10.7\"),\n\t/** Mac OS X 10.8 (Mountain Lion) */\n\tMAC_OS_X_10_8(\"10.8\"),\n    /** Mac OS X 10.9 (Mavericks) */\n    MAC_OS_X_10_9(\"10.9\"),\n    /** Mac OS X 10.10 (Yosemite) */\n    MAC_OS_X_10_10(\"10.10\"),\n    /** Mac OS X 10.11 (El Capitan) */\n    MAC_OS_X_10_11(\"10.11\"),\n    /** Mac OS X 10.12 (Sierra) */\n    MAC_OS_X_10_12(\"10.12\"),\n    /** Mac OS X 10.13 (High Sierra) */\n    MAC_OS_X_10_13(\"10.13\"),\n    /** Mac OS X 10.14 (Mojave) */\n    MAC_OS_X_10_14(\"10.14\"),\n    /** Mac OS X 10.15 (Catalina) */\n    MAC_OS_X_10_15(\"10.15\"),\n    /** Big Sur */\n    MAC_OS_11(\"11\"),\n    /** Monterey */\n    MAC_OS_12(\"12\"),\n    /** Ventura */\n    MAC_OS_13(\"13\"),\n    /** Sonoma */\n    MAC_OS_14(\"14\");\n\n\n\n    /** Logger used by this class. */\n    private static final Logger LOGGER = LoggerFactory.getLogger(OsVersion.class);\n\n    /** The String representation of this RuntimeProperty, set at creation time */\n    private final String stringRepresentation;\n\n    /** Holds the OsVersion of the current runtime environment  */\n    private static final OsVersion currentValue;\n\n    static {\n    \tcurrentValue = parseSystemProperty(getRawSystemProperty(), OsFamily.getRawSystemProperty(), OsFamily.getCurrent());\n    \tLOGGER.info(\"Current OS version: {}\", currentValue);\n    }\n\n\n    OsVersion(String stringRepresentation) {\n    \tthis.stringRepresentation = stringRepresentation;\n    }\n\n\n    /**\n     * Returns the OS version of the current runtime environment.\n     *\n     * @return the OS version of the current runtime environment\n     */\n    public static OsVersion getCurrent() {\n        return currentValue;\n    }\n\n    /**\n     * Returns the value of the system property which serves to detect the OS version at runtime.\n     *\n     * @return the value of the system property which serves to detect the OS version at runtime.\n     */\n    public static String getRawSystemProperty() {\n        return System.getProperty(\"os.version\");\n    }\n\n    /**\n     * Returns an <code>OsVersion</code> instance corresponding to the specified system property's value.\n     *\n     * @param osVersionProp the value of the \"os.version\" system property\n     * @param osNameProp the value of the \"os.name\" system property\n     * @param osFamily the current OS family\n     * @return an OsVersion instance corresponding to the specified system property's value\n     */\n    static OsVersion parseSystemProperty(String osVersionProp, String osNameProp, OsFamily osFamily) {\n        // This website holds a collection of system property values under many OSes:\n        // http://lopica.sourceforge.net/os.html\n\n        if (osFamily == OsFamily.WINDOWS) {\n            return switch (osNameProp) {\n                case \"Windows 95\" -> WINDOWS_95;\n                case \"Windows 98\" -> WINDOWS_98;\n                case \"Windows Me\" -> WINDOWS_ME;\n                case \"Windows NT\" -> WINDOWS_NT;\n                case \"Windows 2000\" -> WINDOWS_2000;\n                case \"Windows XP\" -> WINDOWS_XP;\n                case \"Windows 2003\" -> WINDOWS_2003;\n                case \"Windows Vista\" -> WINDOWS_VISTA;\n                case \"Windows 7\" -> WINDOWS_7;\n                case \"Windows 8\" -> WINDOWS_8;\n                case \"Windows 8.1\" -> WINDOWS_8_1;\n                case \"Windows 10\" -> WINDOWS_10;\n                case \"Windows 11\" -> WINDOWS_11;\n                default -> WINDOWS_11;  // Newer version we don't know of yet, assume latest supported OS version\n            };\n        }\n        // Mac OS X versions\n        if (osFamily == OsFamily.MAC_OS_X) {\n            if (osVersionProp.startsWith(\"14\")) {\n                return MAC_OS_14;\n            } else if (osVersionProp.startsWith(\"13\")) {\n                return MAC_OS_13;\n            } else if (osVersionProp.startsWith(\"12\")) {\n                return MAC_OS_12;\n            } if (osVersionProp.startsWith(\"11\")) {\n                return MAC_OS_11;\n            } else if (osVersionProp.startsWith(\"10.15\")) {\n                return MAC_OS_X_10_15;\n            } else if (osVersionProp.startsWith(\"10.14\")) {\n                return MAC_OS_X_10_14;\n            } else if (osVersionProp.startsWith(\"10.13\")) {\n                return MAC_OS_X_10_13;\n            } else if (osVersionProp.startsWith(\"10.12\")) {\n                return MAC_OS_X_10_12;\n            } else if (osVersionProp.startsWith(\"10.11\")) {\n                return MAC_OS_X_10_11;\n            } else if (osVersionProp.startsWith(\"10.10\")) {\n                return MAC_OS_X_10_10;\n            } else if (osVersionProp.startsWith(\"10.9\")) {\n                return MAC_OS_X_10_9;\n            } else if (osVersionProp.startsWith(\"10.8\")) {\n                return MAC_OS_X_10_8;\n            } else if (osVersionProp.startsWith(\"10.7\")) {\n                return MAC_OS_X_10_7;\n            } else if (osVersionProp.startsWith(\"10.6\")) {\n                return MAC_OS_X_10_6;\n            } else if (osVersionProp.startsWith(\"10.5\")) {\n                return MAC_OS_X_10_5;\n            } else if (osVersionProp.startsWith(\"10.4\")) {\n                return MAC_OS_X_10_4;\n            } else if (osVersionProp.startsWith(\"10.3\")) {\n                return MAC_OS_X_10_3;\n            } else if (osVersionProp.startsWith(\"10.2\")) {\n                return MAC_OS_X_10_2;\n            } else if (osVersionProp.startsWith(\"10.1\")) {\n                return MAC_OS_X_10_1;\n            } else if (osVersionProp.startsWith(\"10.0\")) {\n                return MAC_OS_X_10_0;\n            }\n            return MAC_OS_14; // Newer version we don't know of yet, assume latest supported OS version\n        }\n\n        return OsVersion.UNKNOWN_VERSION;\n    }\n\n    /**\n     * Returns <code>true</code> if this instance is the same instance as the one returned by {@link #getCurrent()}.\n     *\n     * @return true if this instance is the same as the current runtime's value\n     */\n    public boolean isCurrent() {\n        return this == currentValue;\n    }\n\n    @Override\n    public boolean isCurrentOrLower() {\n\t\treturn currentValue.compareTo(this) <= 0;\n\t}\n\n\t@Override\n\tpublic boolean isCurrentLower() {\n\t\treturn currentValue.compareTo(this) < 0;\n\t}\n\n\t@Override\n\tpublic boolean isCurrentOrHigher() {\n\t\treturn currentValue.compareTo(this) >= 0;\n\t}\n\n\t@Override\n\tpublic boolean isCurrentHigher() {\n\t\treturn currentValue.compareTo(this) > 0;\n\t}\n\n    @Override\n    public String toString() {\n        return stringRepresentation;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/runtime/package.html",
    "content": "<body>\n  This package provides automatic detection and easy access to certain properties of the runtime environment such as the\n  Java version and the OS family and version.\n</body>"
  },
  {
    "path": "src/main/java/com/mucommander/commons/util/BufferOverflowException.java",
    "content": "/*\n * Buffer Overflow Exception\n * Copyright (C) 2002-2010 Stephen Ostermiller\n * http://ostermiller.org/contact.pl?regarding=Java+Utilities\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * See LICENSE.txt for details.\n */\n\npackage com.mucommander.commons.util;\n\nimport java.io.IOException;\n\n/**\n * An indication that there was a buffer overflow.\n *\n * @author Stephen Ostermiller http://ostermiller.org/contact.pl?regarding=Java+Utilities\n * @since ostermillerutils 1.00.00\n */\npublic class BufferOverflowException extends IOException {\n\n\t/**\n\t * Serial version ID\n\t */\n\tprivate static final long serialVersionUID = -322401823167626048L;\n\n\t/**\n\t * create a new Exception\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tpublic BufferOverflowException(){\n\t\tsuper();\n\t}\n\n\t/**\n\t * create a new Exception with the given message.\n\t *\n\t * @param msg Error message.\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tpublic BufferOverflowException(String msg){\n\t\tsuper(msg);\n\t}\n}\n\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/util/CircularByteBuffer.java",
    "content": "/*\n * Circular Byte Buffer\n * Copyright (C) 2002-2010 Stephen Ostermiller\n * http://ostermiller.org/contact.pl?regarding=Java+Utilities\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * See LICENSE.txt for details.\n */\n\npackage com.mucommander.commons.util;\n\nimport java.io.*;\n\n/**\n * Implements the Circular Buffer producer/consumer model for bytes.\n * More information about this class is available from <a target=\"_top\" href=\n * \"http://ostermiller.org/utils/CircularByteBuffer.html\">ostermiller.org</a>.\n * <p>\n * Using this class is a simpler alternative to using a PipedInputStream\n * and a PipedOutputStream. PipedInputStreams and PipedOutputStreams don't support the\n * mark operation, don't allow you to control buffer sizes that they use,\n * and have a more complicated API that requires instantiating two\n * classes and connecting them.\n * <p>\n * This class is thread safe.\n *\n * @author Stephen Ostermiller http://ostermiller.org/contact.pl?regarding=Java+Utilities\n * @since ostermillerutils 1.00.00\n */\npublic class CircularByteBuffer {\n\n\t/**\n\t * The default size for a circular byte buffer.\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tprivate final static int DEFAULT_SIZE = 1024;\n\n\t/**\n\t * A buffer that will grow as things are added.\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tpublic final static int INFINITE_SIZE = -1;\n\n\t/**\n\t * The circular buffer.\n\t * <p>\n\t * The actual capacity of the buffer is one less than the actual length\n\t * of the buffer so that an empty and a full buffer can be\n\t * distinguished.  An empty buffer will have the markPostion and the\n\t * writePosition equal to each other.  A full buffer will have\n\t * the writePosition one less than the markPostion.\n\t * <p>\n\t * There are three important indexes into the buffer:\n\t * The readPosition, the writePosition, and the markPosition.\n\t * If the InputStream has never been marked, the readPosition and\n\t * the markPosition should always be the same.  The bytes\n\t * available to be read go from the readPosition to the writePosition,\n\t * wrapping around the end of the buffer.  The space available for writing\n\t * goes from the write position to one less than the markPosition,\n\t * wrapping around the end of the buffer.  The bytes that have\n\t * been saved to support a reset() of the InputStream go from markPosition\n\t * to readPosition, wrapping around the end of the buffer.\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tprotected byte[] buffer;\n\t/**\n\t * Index of the first byte available to be read.\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tprotected volatile int readPosition = 0;\n\t/**\n\t * Index of the first byte available to be written.\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tprotected volatile int writePosition = 0;\n\t/**\n\t * Index of the first saved byte. (To support stream marking.)\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tprotected volatile int markPosition = 0;\n\t/**\n\t * Number of bytes that have to be saved\n\t * to support mark() and reset() on the InputStream.\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tprotected volatile int markSize = 0;\n\t/**\n\t * If this buffer is infinite (should resize itself when full)\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tprotected volatile boolean infinite = false;\n\t/**\n\t * True if a write to a full buffer should block until the buffer\n\t * has room, false if the write method should throw an IOException\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tprotected boolean blockingWrite = true;\n\t/**\n\t * The InputStream that can empty this buffer.\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tprotected InputStream in = new CircularByteBufferInputStream();\n\t/**\n\t * true if the close() method has been called on the InputStream\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tprotected boolean inputStreamClosed = false;\n\t/**\n\t * The OutputStream that can fill this buffer.\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tprotected OutputStream out = new CircularByteBufferOutputStream();\n\t/**\n\t * true if the close() method has been called on the OutputStream\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tprotected boolean outputStreamClosed = false;\n\n\t/**\n\t * Make this buffer ready for reuse.  The contents of the buffer\n\t * will be cleared and the streams associated with this buffer\n\t * will be reopened if they had been closed.\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tpublic void clear(){\n\t\tsynchronized (this){\n\t\t\treadPosition = 0;\n\t\t\twritePosition = 0;\n\t\t\tmarkPosition = 0;\n\t\t\toutputStreamClosed = false;\n\t\t\tinputStreamClosed = false;\n\t\t}\n\t}\n\n\t/**\n\t * Retrieve a OutputStream that can be used to fill\n\t * this buffer.\n\t * <p>\n\t * Write methods may throw a BufferOverflowException if\n\t * the buffer is not large enough.  A large enough buffer\n\t * size must be chosen so that this does not happen or\n\t * the caller must be prepared to catch the exception and\n\t * try again once part of the buffer has been consumed.\n\t *\n\t *\n\t * @return the producer for this buffer.\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tpublic OutputStream getOutputStream(){\n\t\treturn out;\n\t}\n\n\t/**\n\t * Retrieve a InputStream that can be used to empty\n\t * this buffer.\n\t * <p>\n\t * This InputStream supports marks at the expense\n\t * of the buffer size.\n\t *\n\t * @return the consumer for this buffer.\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tpublic InputStream getInputStream(){\n\t\treturn in;\n\t}\n\n\t/**\n\t * Get number of bytes that are available to be read.\n\t * <p>\n\t * Note that the number of bytes available plus\n\t * the number of bytes free may not add up to the\n\t * capacity of this buffer, as the buffer may reserve some\n\t * space for other purposes.\n\t *\n\t * @return the size in bytes of this buffer\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tpublic int getAvailable(){\n\t\tsynchronized (this){\n\t\t\treturn available();\n\t\t}\n\t}\n\n\t/**\n\t * Get the number of bytes this buffer has free for\n\t * writing.\n\t * <p>\n\t * Note that the number of bytes available plus\n\t * the number of bytes free may not add up to the\n\t * capacity of this buffer, as the buffer may reserve some\n\t * space for other purposes.\n\t *\n\t * @return the available space in bytes of this buffer\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tpublic int getSpaceLeft(){\n\t\tsynchronized (this){\n\t\t\treturn spaceLeft();\n\t\t}\n\t}\n\n\t/**\n\t * Get the capacity of this buffer.\n\t * <p>\n\t * Note that the number of bytes available plus\n\t * the number of bytes free may not add up to the\n\t * capacity of this buffer, as the buffer may reserve some\n\t * space for other purposes.\n\t *\n\t * @return the size in bytes of this buffer\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tpublic int getSize(){\n\t\tsynchronized (this) {\n\t\t\treturn buffer.length;\n\t\t}\n\t}\n\n\t/**\n\t * double the size of the buffer\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tprivate void resize() {\n\t\tbyte[] newBuffer = new byte[buffer.length * 2];\n\t\tint marked = marked();\n\t\tint available = available();\n\t\tif (markPosition <= writePosition){\n\t\t\t// any space between the mark and\n\t\t\t// the first write needs to be saved.\n\t\t\t// In this case it is all in one piece.\n\t\t\tint length = writePosition - markPosition;\n\t\t\tSystem.arraycopy(buffer, markPosition, newBuffer, 0, length);\n\t\t} else {\n\t\t\tint length1 = buffer.length - markPosition;\n\t\t\tSystem.arraycopy(buffer, markPosition, newBuffer, 0, length1);\n\t\t\tint length2 = writePosition;\n\t\t\tSystem.arraycopy(buffer, 0, newBuffer, length1, length2);\n\t\t}\n\t\tbuffer = newBuffer;\n\t\tmarkPosition = 0;\n\t\treadPosition = marked;\n\t\twritePosition = marked + available;\n\t}\n\n\t/**\n\t * Space available in the buffer which can be written.\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tprivate int spaceLeft() {\n\t\tif (writePosition < markPosition){\n\t\t\t// any space between the first write and\n\t\t\t// the mark except one byte is available.\n\t\t\t// In this case it is all in one piece.\n\t\t\treturn (markPosition - writePosition - 1);\n\t\t}\n\t\t// space at the beginning and end.\n\t\treturn ((buffer.length - 1) - (writePosition - markPosition));\n\t}\n\n\t/**\n\t * Bytes available for reading.\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tprivate int available() {\n\t\tif (readPosition <= writePosition){\n\t\t\t// any space between the first read and\n\t\t\t// the first write is available.  In this case i\n\t\t\t// is all in one piece.\n\t\t\treturn (writePosition - readPosition);\n\t\t}\n\t\t// space at the beginning and end.\n\t\treturn (buffer.length - (readPosition - writePosition));\n\t}\n\n\t/**\n\t * Bytes saved for supporting marks.\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tprivate int marked() {\n\t\tif (markPosition <= readPosition){\n\t\t\t// any space between the markPosition and\n\t\t\t// the first write is marked.  In this case i\n\t\t\t// is all in one piece.\n\t\t\treturn (readPosition - markPosition);\n\t\t}\n\t\t// space at the beginning and end.\n\t\treturn (buffer.length - (markPosition - readPosition));\n\t}\n\n\t/**\n\t * If we have passed the markSize reset the\n\t * mark so that the space can be used.\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tprivate void ensureMark() {\n\t\tif (marked() > markSize){\n\t\t\tmarkPosition = readPosition;\n\t\t\tmarkSize = 0;\n\t\t}\n\t}\n\n\t/**\n\t * create a new buffer with a default capacity.\n\t * Writing to a full buffer will block until space\n\t * is available rather than throw an exception.\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tpublic CircularByteBuffer(){\n\t\tthis (DEFAULT_SIZE, true);\n\t}\n\n\t/**\n\t * create a new buffer with given capacity.\n\t * Writing to a full buffer will block until space\n\t * is available rather than throw an exception.\n\t * <p>\n\t * Note that the buffer may reserve some bytes for\n\t * special purposes and capacity number of bytes may\n\t * not be able to be written to the buffer.\n\t * <p>\n\t * Note that if the buffer is of INFINITE_SIZE it will\n\t * neither block or throw exceptions, but rather grow\n\t * without bound.\n\t *\n\t * @param size desired capacity of the buffer in bytes or CircularByteBuffer.INFINITE_SIZE.\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tpublic CircularByteBuffer(int size){\n\t\tthis (size, true);\n\t}\n\n\t/**\n\t * create a new buffer with a default capacity and\n\t * given blocking behavior.\n\t *\n\t * @param blockingWrite true writing to a full buffer should block\n\t *        until space is available, false if an exception should\n\t *        be thrown instead.\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tpublic CircularByteBuffer(boolean blockingWrite){\n\t\tthis (DEFAULT_SIZE, blockingWrite);\n\t}\n\n\t/**\n\t * create a new buffer with the given capacity and\n\t * blocking behavior.\n\t * <p>\n\t * Note that the buffer may reserve some bytes for\n\t * special purposes and capacity number of bytes may\n\t * not be able to be written to the buffer.\n\t * <p>\n\t * Note that if the buffer is of INFINITE_SIZE it will\n\t * neither block or throw exceptions, but rather grow\n\t * without bound.\n\t *\n\t * @param size desired capacity of the buffer in bytes or CircularByteBuffer.INFINITE_SIZE.\n\t * @param blockingWrite true writing to a full buffer should block\n\t *        until space is available, false if an exception should\n\t *        be thrown instead.\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tpublic CircularByteBuffer(int size, boolean blockingWrite) {\n\t\tif (size == INFINITE_SIZE) {\n\t\t\tbuffer = new byte[DEFAULT_SIZE];\n\t\t\tinfinite = true;\n\t\t} else {\n\t\t\tbuffer = new byte[size];\n\t\t\tinfinite = false;\n\t\t}\n\t\tthis.blockingWrite = blockingWrite;\n\t}\n\n\t/**\n\t * Class for reading from a circular byte buffer.\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tprotected class CircularByteBufferInputStream extends InputStream {\n\n\t\t/**\n\t\t * Returns the number of bytes that can be read (or skipped over) from this\n\t\t * input stream without blocking by the next caller of a method for this input\n\t\t * stream. The next caller might be the same thread or or another thread.\n\t\t *\n\t\t * @return the number of bytes that can be read from this input stream without blocking.\n\t\t * @throws IOException if the stream is closed.\n\t\t *\n\t\t * @since ostermillerutils 1.00.00\n\t\t */\n\t\t@Override public int available() throws IOException {\n\t\t\tsynchronized (CircularByteBuffer.this) {\n\t\t\t\tif (inputStreamClosed) {\n                    throw new IOException(\"InputStream has been closed, it is not ready.\");\n                }\n\t\t\t\treturn (CircularByteBuffer.this.available());\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Close the stream. Once a stream has been closed, further read(), available(),\n\t\t * mark(), or reset() invocations will throw an IOException. Closing a\n\t\t * previously-closed stream, however, has no effect.\n\t\t *\n\t\t * @throws IOException never.\n\t\t *\n\t\t * @since ostermillerutils 1.00.00\n\t\t */\n\t\t@Override public void close() throws IOException {\n\t\t\tsynchronized (CircularByteBuffer.this) {\n\t\t\t\tinputStreamClosed = true;\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Mark the present position in the stream. Subsequent calls to reset() will\n\t\t * attempt to reposition the stream to this point.\n\t\t * <p>\n\t\t * The readAheadLimit must be less than the size of circular buffer, otherwise\n\t\t * this method has no effect.\n\t\t *\n\t\t * @param readAheadLimit Limit on the number of bytes that may be read while\n\t\t *    still preserving the mark. After reading this many bytes, attempting to\n\t\t *    reset the stream will fail.\n\t\t *\n\t\t * @since ostermillerutils 1.00.00\n\t\t */\n\t\t@Override public void mark(int readAheadLimit) {\n\t\t\tsynchronized (CircularByteBuffer.this) {\n\t\t\t\t//if (inputStreamClosed) throw new IOException(\"InputStream has been closed; cannot mark a closed InputStream.\");\n\t\t\t\tif (buffer.length - 1 > readAheadLimit) {\n\t\t\t\t\tmarkSize = readAheadLimit;\n\t\t\t\t\tmarkPosition = readPosition;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Tell whether this stream supports the mark() operation.\n\t\t *\n\t\t * @return true, mark is supported.\n\t\t *\n\t\t * @since ostermillerutils 1.00.00\n\t\t */\n\t\t@Override public boolean markSupported() {\n\t\t\treturn true;\n\t\t}\n\n\t\t/**\n\t\t * Read a single byte.\n\t\t * This method will block until a byte is available, an I/O error occurs,\n\t\t * or the end of the stream is reached.\n\t\t *\n\t\t * @return The byte read, as an integer in the range 0 to 255 (0x00-0xff),\n\t\t *     or -1 if the end of the stream has been reached\n\t\t * @throws IOException if the stream is closed.\n\t\t *\n\t\t * @since ostermillerutils 1.00.00\n\t\t */\n\t\t@Override public int read() throws IOException {\n\t\t\twhile (true) {\n\t\t\t\tsynchronized (CircularByteBuffer.this){\n\t\t\t\t\tif (inputStreamClosed) throw new IOException(\"InputStream has been closed; cannot read from a closed InputStream.\");\n\t\t\t\t\tint available = CircularByteBuffer.this.available();\n\t\t\t\t\tif (available > 0) {\n\t\t\t\t\t\tint result = buffer[readPosition] & 0xff;\n\t\t\t\t\t\treadPosition++;\n\t\t\t\t\t\tif (readPosition == buffer.length){\n\t\t\t\t\t\t\treadPosition = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tensureMark();\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t} else if (outputStreamClosed) {\n\t\t\t\t\t\treturn -1;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tThread.sleep(100);\n\t\t\t\t} catch(Exception x){\n\t\t\t\t\tthrow new IOException(\"Blocking read operation interrupted.\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Read bytes into an array.\n\t\t * This method will block until some input is available,\n\t\t * an I/O error occurs, or the end of the stream is reached.\n\t\t *\n\t\t * @param cbuf Destination buffer.\n\t\t * @return The number of bytes read, or -1 if the end of\n\t\t *   the stream has been reached\n\t\t * @throws IOException if the stream is closed.\n\t\t *\n\t\t * @since ostermillerutils 1.00.00\n\t\t */\n\t\t@Override public int read(byte[] cbuf) throws IOException {\n\t\t\treturn read(cbuf, 0, cbuf.length);\n\t\t}\n\n\t\t/**\n\t\t * Read bytes into a portion of an array.\n\t\t * This method will block until some input is available,\n\t\t * an I/O error occurs, or the end of the stream is reached.\n\t\t *\n\t\t * @param cbuf Destination buffer.\n\t\t * @param off Offset at which to start storing bytes.\n\t\t * @param len Maximum number of bytes to read.\n\t\t * @return The number of bytes read, or -1 if the end of\n\t\t *   the stream has been reached\n\t\t * @throws IOException if the stream is closed.\n\t\t *\n\t\t * @since ostermillerutils 1.00.00\n\t\t */\n\t\t@Override public int read(byte[] cbuf, int off, int len) throws IOException {\n\t\t\twhile (true) {\n\t\t\t\tsynchronized (CircularByteBuffer.this) {\n\t\t\t\t\tif (inputStreamClosed) {\n                        throw new IOException(\"InputStream has been closed; cannot read from a closed InputStream.\");\n                    }\n\t\t\t\t\tint available = CircularByteBuffer.this.available();\n\t\t\t\t\tif (available > 0) {\n\t\t\t\t\t\tint length = Math.min(len, available);\n\t\t\t\t\t\tint firstLen = Math.min(length, buffer.length - readPosition);\n\t\t\t\t\t\tint secondLen = length - firstLen;\n\t\t\t\t\t\tSystem.arraycopy(buffer, readPosition, cbuf, off, firstLen);\n\t\t\t\t\t\tif (secondLen > 0){\n\t\t\t\t\t\t\tSystem.arraycopy(buffer, 0, cbuf, off+firstLen,  secondLen);\n\t\t\t\t\t\t\treadPosition = secondLen;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treadPosition += length;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (readPosition == buffer.length) {\n\t\t\t\t\t\t\treadPosition = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tensureMark();\n\t\t\t\t\t\treturn length;\n\t\t\t\t\t} else if (outputStreamClosed){\n\t\t\t\t\t\treturn -1;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tThread.sleep(100);\n\t\t\t\t} catch(Exception x){\n\t\t\t\t\tthrow new IOException(\"Blocking read operation interrupted.\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Reset the stream.\n\t\t * If the stream has been marked, then attempt to reposition i\n\t\t * at the mark. If the stream has not been marked, or more bytes\n\t\t * than the readAheadLimit have been read, this method has no effect.\n\t\t *\n\t\t * @throws IOException if the stream is closed.\n\t\t *\n\t\t * @since ostermillerutils 1.00.00\n\t\t */\n\t\t@Override public void reset() throws IOException {\n\t\t\tsynchronized (CircularByteBuffer.this){\n\t\t\t\tif (inputStreamClosed) {\n                    throw new IOException(\"InputStream has been closed; cannot reset a closed InputStream.\");\n                }\n\t\t\t\treadPosition = markPosition;\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Skip bytes.\n\t\t * This method will block until some bytes are available,\n\t\t * an I/O error occurs, or the end of the stream is reached.\n\t\t *\n\t\t * @param n The number of bytes to skip\n\t\t * @return The number of bytes actually skipped\n\t\t * @throws IllegalArgumentException if n is negative.\n\t\t * @throws IOException if the stream is closed.\n\t\t *\n\t\t * @since ostermillerutils 1.00.00\n\t\t */\n\t\t@Override public long skip(long n) throws IOException, IllegalArgumentException {\n\t\t\twhile (true) {\n\t\t\t\tsynchronized (CircularByteBuffer.this){\n\t\t\t\t\tif (inputStreamClosed) throw new IOException(\"InputStream has been closed; cannot skip bytes on a closed InputStream.\");\n\t\t\t\t\tint available = CircularByteBuffer.this.available();\n\t\t\t\t\tif (available > 0){\n\t\t\t\t\t\tint length = Math.min((int)n, available);\n\t\t\t\t\t\tint firstLen = Math.min(length, buffer.length - readPosition);\n\t\t\t\t\t\tint secondLen = length - firstLen;\n\t\t\t\t\t\tif (secondLen > 0){\n\t\t\t\t\t\t\treadPosition = secondLen;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treadPosition += length;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (readPosition == buffer.length) {\n\t\t\t\t\t\t\treadPosition = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tensureMark();\n\t\t\t\t\t\treturn length;\n\t\t\t\t\t} else if (outputStreamClosed){\n\t\t\t\t\t\treturn 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tThread.sleep(100);\n\t\t\t\t} catch(Exception x){\n\t\t\t\t\tthrow new IOException(\"Blocking read operation interrupted.\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Class for writing to a circular byte buffer.\n\t * If the buffer is full, the writes will either block\n\t * until there is some space available or throw an IOException\n\t * based on the CircularByteBuffer's preference.\n\t *\n\t * @since ostermillerutils 1.00.00\n\t */\n\tprotected class CircularByteBufferOutputStream extends OutputStream {\n\n\t\t/**\n\t\t * Close the stream, flushing it first.\n\t\t * This will cause the InputStream associated with this circular buffer\n\t\t * to read its last bytes once it empties the buffer.\n\t\t * Once a stream has been closed, further write() or flush() invocations\n\t\t * will cause an IOException to be thrown. Closing a previously-closed stream,\n\t\t * however, has no effect.\n\t\t *\n\t\t * @throws IOException never.\n\t\t *\n\t\t * @since ostermillerutils 1.00.00\n\t\t */\n\t\t@Override public void close() throws IOException {\n\t\t\tsynchronized (CircularByteBuffer.this){\n\t\t\t\tif (!outputStreamClosed && !inputStreamClosed){\n\t\t\t\t\tflush();\n\t\t\t\t}\n\t\t\t\toutputStreamClosed = true;\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Flush the stream.\n\t\t *\n\t\t * @throws IOException if the stream is closed.\n\t\t *\n\t\t * @since ostermillerutils 1.00.00\n\t\t */\n\t\t@Override public void flush() throws IOException {\n\t\t\tsynchronized (CircularByteBuffer.this){\n\t\t\t\tif (outputStreamClosed) throw new IOException(\"OutputStream has been closed; cannot flush a closed OutputStream.\");\n\t\t\t\tif (inputStreamClosed) throw new IOException(\"Buffer closed by inputStream; cannot flush.\");\n\t\t\t}\n\t\t\t// this method needs to do nothing\n\t\t}\n\n\t\t/**\n\t\t * Write an array of bytes.\n\t\t * If the buffer allows blocking writes, this method will block until\n\t\t * all the data has been written rather than throw an IOException.\n\t\t *\n\t\t * @param cbuf Array of bytes to be written\n\t\t * @throws BufferOverflowException if buffer does not allow blocking writes\n\t\t *   and the buffer is full.  If the exception is thrown, no data\n\t\t *   will have been written since the buffer was set to be non-blocking.\n\t\t * @throws IOException if the stream is closed, or the write is interrupted.\n\t\t *\n\t\t * @since ostermillerutils 1.00.00\n\t\t */\n\t\t@Override public void write(byte[] cbuf) throws IOException {\n\t\t\twrite(cbuf, 0, cbuf.length);\n\t\t}\n\n\t\t/**\n\t\t * Write a portion of an array of bytes.\n\t\t * If the buffer allows blocking writes, this method will block until\n\t\t * all the data has been written rather than throw an IOException.\n\t\t *\n\t\t * @param cbuf Array of bytes\n\t\t * @param off Offset from which to start writing bytes\n\t\t * @param len - Number of bytes to write\n\t\t * @throws BufferOverflowException if buffer does not allow blocking writes\n\t\t *   and the buffer is full.  If the exception is thrown, no data\n\t\t *   will have been written since the buffer was set to be non-blocking.\n\t\t * @throws IOException if the stream is closed, or the write is interrupted.\n\t\t *\n\t\t * @since ostermillerutils 1.00.00\n\t\t */\n\t\t@Override public void write(byte[] cbuf, int off, int len) throws IOException {\n\t\t\twhile (len > 0) {\n\t\t\t\tsynchronized (CircularByteBuffer.this) {\n\t\t\t\t\tif (outputStreamClosed) {\n                        throw new IOException(\"OutputStream has been closed; cannot write to a closed OutputStream.\");\n                    }\n\t\t\t\t\tif (inputStreamClosed) {\n                        throw new IOException(\"Buffer closed by InputStream; cannot write to a closed buffer.\");\n                    }\n\t\t\t\t\tint spaceLeft = spaceLeft();\n\t\t\t\t\twhile (infinite && spaceLeft < len){\n\t\t\t\t\t\tresize();\n\t\t\t\t\t\tspaceLeft = spaceLeft();\n\t\t\t\t\t}\n\t\t\t\t\tif (!blockingWrite && spaceLeft < len) {\n                        throw new BufferOverflowException(\"CircularByteBuffer is full; cannot write \" + len + \" bytes\");\n                    }\n\t\t\t\t\tint realLen = Math.min(len, spaceLeft);\n\t\t\t\t\tint firstLen = Math.min(realLen, buffer.length - writePosition);\n\t\t\t\t\tint secondLen = Math.min(realLen - firstLen, buffer.length - markPosition - 1);\n\t\t\t\t\tint written = firstLen + secondLen;\n\t\t\t\t\tif (firstLen > 0){\n\t\t\t\t\t\tSystem.arraycopy(cbuf, off, buffer, writePosition, firstLen);\n\t\t\t\t\t}\n\t\t\t\t\tif (secondLen > 0){\n\t\t\t\t\t\tSystem.arraycopy(cbuf, off+firstLen, buffer, 0, secondLen);\n\t\t\t\t\t\twritePosition = secondLen;\n\t\t\t\t\t} else {\n\t\t\t\t\t\twritePosition += written;\n\t\t\t\t\t}\n\t\t\t\t\tif (writePosition == buffer.length) {\n\t\t\t\t\t\twritePosition = 0;\n\t\t\t\t\t}\n\t\t\t\t\toff += written;\n\t\t\t\t\tlen -= written;\n\t\t\t\t}\n\t\t\t\tif (len > 0){\n\t\t\t\t\ttry {\n\t\t\t\t\t\tThread.sleep(100);\n\t\t\t\t\t} catch(Exception x){\n\t\t\t\t\t\tthrow new IOException(\"Waiting for available space in buffer interrupted.\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Write a single byte.\n\t\t * The byte to be written is contained in the 8 low-order bits of the\n\t\t * given integer value; the 24 high-order bits are ignored.\n\t\t * If the buffer allows blocking writes, this method will block until\n\t\t * all the data has been written rather than throw an IOException.\n\t\t *\n\t\t * @param c number of bytes to be written\n\t\t * @throws BufferOverflowException if buffer does not allow blocking writes\n\t\t *   and the buffer is full.\n\t\t * @throws IOException if the stream is closed, or the write is interrupted.\n\t\t *\n\t\t * @since ostermillerutils 1.00.00\n\t\t */\n\t\t@Override public void write(int c) throws IOException {\n\t\t\tboolean written = false;\n\t\t\twhile (!written) {\n\t\t\t\tsynchronized (CircularByteBuffer.this){\n\t\t\t\t\tif (outputStreamClosed) {\n                        throw new IOException(\"OutputStream has been closed; cannot write to a closed OutputStream.\");\n                    }\n\t\t\t\t\tif (inputStreamClosed) {\n                        throw new IOException(\"Buffer closed by InputStream; cannot write to a closed buffer.\");\n                    }\n\t\t\t\t\tint spaceLeft = spaceLeft();\n\t\t\t\t\twhile (infinite && spaceLeft < 1){\n\t\t\t\t\t\tresize();\n\t\t\t\t\t\tspaceLeft = spaceLeft();\n\t\t\t\t\t}\n\t\t\t\t\tif (!blockingWrite && spaceLeft < 1) {\n                        throw new BufferOverflowException(\"CircularByteBuffer is full; cannot write 1 byte\");\n                    }\n\t\t\t\t\tif (spaceLeft > 0){\n\t\t\t\t\t\tbuffer[writePosition] = (byte)(c & 0xff);\n\t\t\t\t\t\twritePosition++;\n\t\t\t\t\t\tif (writePosition == buffer.length) {\n\t\t\t\t\t\t\twritePosition = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t\twritten = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!written){\n\t\t\t\t\ttry {\n\t\t\t\t\t\tThread.sleep(100);\n\t\t\t\t\t} catch(Exception x){\n\t\t\t\t\t\tthrow new IOException(\"Waiting for available space in buffer interrupted.\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/util/Pair.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.util;\n\n/**\n * Simple structure of two elements.\n * \n * Note that this class does not support concurrent usage, and does not exposes setters and \n * getters methods, on purpose in order to expose simple API similar to C++ pair structure.\n * \n * @author Arik Hadas\n */\npublic class Pair<FIRST, SECOND> {\n\n\t/* first element */\n\tpublic FIRST first;\n\t/* second element */\n\tpublic SECOND second;\n\t\n\t/**\n\t * Empty Constructor\n\t * The elements should be assigned after the class instantiation\n\t */\n\tpublic Pair() { }\n\t\n\t/**\n\t * Constructor that take the two elements as parameters\n\t * \n\t * @param first - first element\n\t * @param second - second element\n\t */\n\tpublic Pair(FIRST first, SECOND second) {\n\t\tthis.first = first;\n\t\tthis.second = second;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/util/StringUtils.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.util;\n\nimport java.text.Collator;\nimport java.util.Locale;\n\n/**\n * This class contains convenience methods for working with strings.\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic final class StringUtils {\n\n\tpublic static final String EMPTY = \"\";\n\n    /**\n     * Prevents instantiation of this class.\n     */\n    private StringUtils() {\n    }\n\n    /**\n     * Returns <code>true</code> if <code>a</code> ends with <code>b</code> regardless of the case.\n     * <p>\n     * This method has a known bug under some alphabets with peculiar capitalization rules such as the Georgian one,\n     * where <code>Character.toUpperCase(a) == Character.toUpperCase(b)</code> doesn't necessarily imply that\n     * <code>Character.toLowerCase(a) == Character.toLowerCase(b)</code>.\n     * The performance hit of testing for these exceptions is so huge that it was deemed an acceptable issue.\n     * <p>\n     * Note that this method will return <code>true</code> if <code>b</code> is an empty string.\n     *\n     * @param a string to test.\n     * @param b suffix to test for.\n     * @return <code>true</code> if <code>a</code> ends with <code>b</code> regardless of the case, <code>false</code> otherwise.\n     */\n    public static boolean endsWithIgnoreCase(String a, String b) {\n        return matchesIgnoreCase(a, b, a.length());\n    }\n\n    /**\n     * Returns <code>true</code> if the substring of <code>a</code> starting at <code>posA</code> matches <code>b</code> regardless of the case.\n     * <p>\n     * This method has a known bug under some alphabets with peculiar capitalization rules such as the Georgian one,\n     * where <code>Character.toUpperCase(a) == Character.toUpperCase(b)</code> doesn't necessarily imply that\n     * <code>Character.toLowerCase(a) == Character.toLowerCase(b)</code>. The performance hit of testing for these\n     * exceptions is so huge that it was deemed an acceptable issue.\n     * <p>\n     * Note that this method will return <code>true</code> if <code>b</code> is an empty string.\n     *\n     * @param  a                              string to test.\n     * @param  b                              suffix to test for.\n     * @param  posA                           position in <code>a</code> at which to look for <code>b</code>\n     * @return                                <code>true</code> if <code>a</code> ends with <code>b</code> regardless of the case, <code>false</code> otherwise.\n     * @throws ArrayIndexOutOfBoundsException if <code>a.length</code> is smaller than <code>posA</code>.\n     */\n    public static boolean matchesIgnoreCase(String a, String b, int posA) {\n        int posB = b.length(); // Position in b.\n\n        // Checks whether there's any point in testing the strings.\n        if (posA < posB)\n            return false;\n\n        // Loops until we've tested the whole of b.\n        while (posB > 0) {\n            char cA = a.charAt(--posA);   // Current character in a.\n            // Works on lower-case characters only.\n            if (!Character.isLowerCase(cA)) {\n                cA = Character.toLowerCase(cA);\n            }\n            char cB = b.charAt(--posB);   // Current character in b.\n            if (!Character.isLowerCase(cB)) {\n                cB = Character.toLowerCase(cB);\n            }\n            if (cA != cB) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * <p>\n     * Checks if <code>str</code> contains a search string <code>searchStr</code> regardless of case, handling {@code null}.\n     * <p>\n     * A {@code null} string will return {@code false}.\n     *\n     * <pre>\n     * StringUtils.contains(null, *) = false\n     * StringUtils.contains(*, null) = false\n     * StringUtils.contains(\"\", \"\") = true\n     * StringUtils.contains(\"abc\", \"\") = true\n     * StringUtils.contains(\"abc\", \"a\") = true\n     * StringUtils.contains(\"abc\", \"z\") = false\n     * StringUtils.contains(\"abc\", \"A\") = true\n     * StringUtils.contains(\"abc\", \"Z\") = false\n     * </pre>\n     *\n     * @param str string to test, may be null.\n     * @param searchStr the string to find, may be null.\n     * @return <code>true</code> if <code>str</code> contains <code>b</code> regardless of the case, <code>false</code> otherwise.\n     */\n    public static boolean containsIgnoreCase(String str, String searchStr, Locale locale) {\n        if (str == null || searchStr == null) {\n            return false;\n        }\n        if (searchStr.isEmpty()) {\n            return true;\n        }\n\n        return str.toLowerCase(locale).contains(searchStr.toLowerCase(locale));\n    }\n\n    /**\n     * Returns <code>true</code> if <code>a</code> ends with <code>b</code> regardless of the case.\n     * <p>\n     * This method has a known bug under some alphabets with peculiar capitalization rules such as the Georgian one,\n     * where <code>Character.toUpperCase(a) == Character.toUpperCase(b)</code> doesn't necessarily imply that\n     * <code>Character.toLowerCase(a) == Character.toLowerCase(b)</code>. The performance hit of testing for these\n     * exceptions is so huge that it was deemed an acceptable issue.\n     * <p>\n     * Note that this method will return <code>true</code> if <code>b</code> is an empty string.\n     *\n     * @param a string to test.\n     * @param b suffix to test for.\n     * @return <code>true</code> if <code>a</code> ends with <code>b</code> regardless of the case, <code>false</code> otherwise.\n     */\n    public static boolean endsWithIgnoreCase(String a, char[] b) {\n        return matchesIgnoreCase(a, b, a.length());\n    }\n\n    /**\n     * Returns <code>true</code> if the substring of <code>a</code> starting at <code>posA</code> matches <code>b</code> regardless of the case.\n     * <p>\n     * This method has a known bug under some alphabets with peculiar capitalisation rules such as the Georgian one,\n     * where <code>Character.toUpperCase(a) == Character.toUpperCase(b)</code> doesn't necessarily imply that\n     * <code>Character.toLowerCase(a) == Character.toLowerCase(b)</code>. The performance hit of testing for this\n     * exceptions is so huge that it was deemed an acceptable issue.\n     * <p>\n     * Note that this method will return <code>true</code> if <code>b</code> is an empty string.\n     *\n     * @param  a                              string to test.\n     * @param  b                              suffix to test for.\n     * @param  posA                           position in <code>a</code> at which to look for <code>b</code>\n     * @return                                <code>true</code> if <code>a</code> ends with <code>b</code> regardless of the case, <code>false</code> otherwise.\n     * @throws ArrayIndexOutOfBoundsException if <code>a.length</code> is smaller than <code>posA</code>.\n     */\n    public static boolean matchesIgnoreCase(String a, char[] b, int posA) {\n        int posB = b.length; // Position in b.\n\n        // Checks whether there's any point in testing the strings.\n        if (posA < posB)\n            return false;\n\n        while (posB > 0) {\n            char cA = a.charAt(--posA);   // Current character in a.\n            if (!Character.isLowerCase(cA)) {\n                cA = Character.toLowerCase(cA);\n            }\n            char cB = b[--posB];          // Current character in b.\n            if (!Character.isLowerCase(cB)) {\n                cB = Character.toLowerCase(cB);\n            }\n            if (cA != cB) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Equivalent of <code>String.endsWith(String)</code> using a <code>char[]</code>.\n     * @param  a String to test.\n     * @param  b suffix to test.\n     * @return   <code>true</code> if <code>a</code> ends with <code>b</code>.\n     */\n    public static boolean endsWith(String a, char[] b) {\n        return matches(a, b, a.length());\n    }\n\n    /**\n     * Returns <code>true</code> if the substring of <code>a</code> ending at <code>posA</code> matches <code>b</code>.\n     * @param  a    String to test.\n     * @param  b    substring to look for.\n     * @param  posA position in <code>a</code> at which to look for <code>b</code>\n     * @return      <code>true</code> if <code>a</code> contains <code>b</code> at position <code>posA - b.length()</code>, <code>false</code> otherwise..\n     */\n    public static boolean matches(String a, char[] b, int posA) {\n        int posB = b.length;\n\n        if (posA < posB) {\n            return false;\n        }\n        while (posB > 0) {\n            if (a.charAt(--posA) != b[--posB]) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Returns <code>true</code> if <code>a</code> starts with <code>b</code> regardless of the case.\n     * <p>\n     * Note that this method will return <code>true</code> if <code>b</code> is an empty string.\n     *\n     * @param a string to test.\n     * @param b prefix to test for.\n     * @return <code>true</code> if <code>a</code> starts with <code>b</code> regardless of the case, <code>false</code> otherwise..\n     */\n    public static boolean startsWithIgnoreCase(String a, String b) {\n        return a.regionMatches(true, 0, b, 0, b.length());\n    }\n\n    /**\n     * This method is a locale-aware version of <code>java.lang.String#equals(Object)</code>. It returns\n     * <code>true</code> if the two given <code>String</code> are equal in the specified <code>Locale</code>.\n     *\n     * <p>This method is useful for testing text expressed in a language where two strings with an identical\n     * written representation can have a different <code>String</code> representation according to\n     * <code>java.lang.String#equals(Object)</code>. Japanese is such a language for instance.\n     * This method uses the <code>java.text.Collator</code> class under the hood.\n     *\n     * @param s1 a String to compare\n     * @param s2 a String to compare\n     * @param locale the Locale to consider for testing the String\n     * @return true if the two given String are equal in the specified Locale\n     */\n    public static boolean equals(String s1, String s2, Locale locale) {\n        return Collator.getInstance(locale).equals(s1, s2);\n    }\n    \n    /**\n     * Compares the two specified strings and returns <code>true</code> if both strings are equal. This method handles\n     * <code>null</code> values with no risk of a <code>NullPointerException</code>. The comparison is case-sensitive\n     * only if requested.\n     *\n     * <p>In other words, this method returns <code>true</code> if strings are either both <code>null</code>\n     * or equal according to <code>String#equals(String)</code> for case-sensitive comparison, or\n     * <code>String#equalsIgnoreCase(String)</code> for case-insensitive comparison.\n     *\n     * @param s1 string to compare, potentially <code>null</code>\n     * @param s2 string to compare, potentially <code>null</code>\n     * @param caseSensitive <code>true</code> for case-sensitive comparison, <code>false</code> for case-insensitive comparison\n     * @return <code>true</code> if strings are equal or both null\n     */\n    public static boolean equals(String s1, String s2, boolean caseSensitive) {\n        if (s1 == null && s2 == null)\n            return true;\n\n        if (caseSensitive)\n            return s1 != null && s1.equals(s2);\n        return s1 != null && s1.equalsIgnoreCase(s2);\n    }\n\n    /**\n     * Parses the string argument as a signed decimal integer. If the string cannot be\n     * parsed a default value is returned.\n     * @param s a String containing the int representation to be parsed\n     * @param def a default value if string cannot be parsed\n     * @return the integer value represented by the argument or default value if it cannot be parsed \n     */\n    public static int parseIntDef(String s, int def) {\n        try {\n            return Integer.parseInt(s);\n        } catch (NumberFormatException e) {\n            return def;\n        }\n    }\n\n    /**\n     * Capitalizes the given string, making its first character upper case, and the rest of them lower case.\n     * This method returns an empty string if <code>null</code> or an empty string is passed.\n     *\n     * @param s the string to capitalize\n     * @return the capitalized string\n     */\n    public static String capitalize(String s) {\n        if (isNullOrEmpty(s)) {\n            return EMPTY;\n        }\n\n        StringBuilder out = new StringBuilder(s.length());\n        out.append(Character.toUpperCase(s.charAt(0)));\n        if (s.length() > 1) {\n            out.append(s.substring(1).toLowerCase());\n        }\n        return out.toString();\n    }\n\n\n    /**\n     * Shorthand for {@link #flatten(String[], String)} invoked with a <code>\" \"</code> separator.\n     *\n     * @param s the string array to flatten\n     * @return the flattened string array\n     */\n    public static String flatten(String[] s) {\n        return flatten(s, \" \");\n    }\n\n\n    /**\n     * Concatenates all the given string array's elements into a single string, separating each element\n     * by the specified separator string. <code>null</code> and empty string values are simply skipped and not\n     * reflected in the returned string. If the string array is <code>null</code>, the returned string will also be\n     * <code>null</code>.\n     *\n     * @param s the string array to flatten\n     * @param separator the String that separates each\n     * @return the flattened string array\n     */\n    public static String flatten(String[] s, String separator) {\n        if (s == null) {\n            return null;\n        }\n\n        StringBuilder sb = new StringBuilder();\n        boolean first = true;\n\n        for (String el : s) {\n            if (isNullOrEmpty(el)) {\n                continue;\n            }\n\n            if (first) {\n                first = false;\n            } else {\n                sb.append(separator);\n            }\n\n            sb.append(el);\n        }\n\n        return sb.toString();\n    }\n\n    /**\n     * Returns true if the given string is null or empty (i.e. it's length is 0)\n     *\n     * @param string - the given String to check\n     * @return true if the given string is null or empty, false otherwise\n     */\n    public static boolean isNullOrEmpty(String string) {\n    \treturn string == null || string.isEmpty();\n    }\n\n\n    /**\n     * Returns true if the given string is null or empty, or it's trimmed value is empty (i.e. it's length is 0)\n     *\n     * @param string - the given String to check\n     * @return true if the given string is null or empty or whitespace only, false otherwise\n     *\n     * @see String#trim()\n     */\n    public static boolean isNullOrBlank(String string) {\n        return isNullOrEmpty(string) || string.trim().isEmpty();\n    }\n\n    public static boolean isNumber(String string) {\n        try {\n            Integer.parseInt(string);\n            return true;\n        } catch (NumberFormatException e) {\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/commons/util/package-info.java",
    "content": "/**\n * Contains various utility classes.\n * @author Nicolas Rinaudo, Maxence Bernard\n */\npackage com.mucommander.commons.util;"
  },
  {
    "path": "src/main/java/com/mucommander/conf/TcConfigurationFile.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.conf;\n\nimport com.mucommander.PlatformManager;\nimport com.mucommander.commons.conf.ConfigurationSource;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.io.backup.BackupInputStream;\nimport com.mucommander.io.backup.BackupOutputStream;\n\nimport java.io.*;\nimport java.nio.charset.StandardCharsets;\n\n/**\n * This abstract package-protected class represents configuration file of muCommander as configuration source \n * for the mucommander.commons.conf package.\n * It can point to a file in a given path or to the default file located in the preferences folder if no path was given.\n * \n * @author Nicolas Rinaudo, Arik Hadas\n */\nabstract class TcConfigurationFile implements ConfigurationSource {\n\n    /** Path to the configuration file. */\n    private AbstractFile configurationFile;\n    /** Default configuration file name. */\n    private final String DEFAULT_CONFIGURATION_FILE_NAME;\n\n\n\n    /**\n     * Creates a new <code>MuConfigurationSource</code> on the specified file.\n     * @param path path to the configuration file.\n     * @throws FileNotFoundException if <code>path</code> is not accessible.\n     */\n    TcConfigurationFile(String path, String defaultFilename) throws FileNotFoundException {\n    \tDEFAULT_CONFIGURATION_FILE_NAME = defaultFilename;\n    \tif (path != null) {\n            setConfigurationFile(path);\n        }\n    }\n\n\n    // - Configuration file handling ------------------------------------------------\n    // ------------------------------------------------------------------------------\n    /**\n     * Returns the path to the configuration file.\n     * @return             the path to the configuration file.\n     * @throws IOException if an error occurred while locating the default configuration file.\n     */\n    private synchronized AbstractFile getConfigurationFile() throws IOException {\n        if (configurationFile == null) {\n            AbstractFile folder = PlatformManager.getPreferencesFolder();\n            return folder.getChild(DEFAULT_CONFIGURATION_FILE_NAME);\n        }\n        return configurationFile;\n    }\n\n    /**\n     * Sets the path to the configuration file.\n     * @param  path path to the file that should be used for configuration storage.\n     * @throws FileNotFoundException if the specified file is not a valid file.\n     */\n    private synchronized void setConfigurationFile(String path) throws FileNotFoundException {\n        AbstractFile file = FileFactory.getFile(path);\n        if (file == null) {\n            setConfigurationFile(new File(path));\n        } else {\n            setConfigurationFile(file);\n        }\n    }\n\n    /**\n     * Sets the path to the configuration file.\n     * @param  file path to the file that should be used for configuration storage.\n     * @throws FileNotFoundException if the specified file is not a valid file.\n     */\n    private synchronized void setConfigurationFile(File file) throws FileNotFoundException {\n        setConfigurationFile(FileFactory.getFile(file.getAbsolutePath()));\n    }\n\n    /**\n     * Sets the path to the configuration file.\n     * @param  file                  path to the file that should be used for configuration storage.\n     * @throws FileNotFoundException if the specified file is not a valid file.\n     */\n    private synchronized void setConfigurationFile(AbstractFile file) throws FileNotFoundException {\n        // Makes sure file can be used as a configuration.\n        if (file.isBrowsable()) {\n            throw new FileNotFoundException(\"Not a valid file: \" + file.getAbsolutePath());\n        }\n\n        configurationFile = file;\n    }\n\n\n\n    /**\n     * Returns an input stream on the configuration file.\n     * @return an input stream on the configuration file.\n     */\n    @Override\n    public synchronized Reader getReader() throws IOException {\n        InputStream is = new BackupInputStream(getConfigurationFile());\n        Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8);\n        return new BufferedReader(reader);\n    }\n\n    /**\n     * Returns an output stream on the configuration file.\n     * @return an output stream on the configuration file.\n     */\n    @Override\n    public synchronized Writer getWriter() throws IOException {\n        OutputStream os = new BackupOutputStream(getConfigurationFile());\n        Writer writer = new OutputStreamWriter(os, StandardCharsets.UTF_8);\n        return new BufferedWriter(writer);\n    }\n\n    @Override\n\tpublic boolean isExists() throws IOException {\n\t\treturn getConfigurationFile().exists();\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/conf/TcConfigurations.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.conf;\n\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\n\nimport com.mucommander.commons.conf.Configuration;\nimport com.mucommander.commons.conf.ConfigurationException;\nimport com.mucommander.commons.conf.ConfigurationListener;\n\n/**\n * This class contains the configurations of trolCommander and exposes their API methods.\n * It provides global access to the configurations without using singletons.\n * \n * @author Arik Hadas\n */\npublic class TcConfigurations {\n\n\t/** Static configurations of trolCommander */\n\tprivate static final TcPreferences preferences = new TcPreferences();\n\t/** Dynamic configurations of trolCommander */\n\tprivate static final TcSnapshot snapshot = new TcSnapshot();\n\t\n\tpublic static TcPreferencesAPI getPreferences() {\n\t\treturn preferences;\n\t}\n\t\n\tpublic static void loadPreferences() throws IOException, ConfigurationException {\n\t\tpreferences.read();\n\t}\n\t\n\tpublic static void savePreferences() throws IOException, ConfigurationException {\n\t\tpreferences.write();\n\t}\n\t\n\tpublic static void setPreferencesFile(String path) throws FileNotFoundException {\n\t\tpreferences.setConfigurationFile(path);\n\t}\n\t\n\tpublic static boolean isPreferencesFileExists() throws IOException {\n\t\treturn preferences.isFileExists();\n\t}\n\t\n    public static void addPreferencesListener(ConfigurationListener listener) {\n    \tpreferences.addConfigurationListener(listener);\n    }\n\n    public static void removePreferencesListener(ConfigurationListener listener) {\n    \tpreferences.removeConfigurationListener(listener);\n    }\n\n    public static Configuration getSnapshot() {\n    \treturn snapshot.getConfiguration();\n    }\n    \n    public static void loadSnapshot() throws IOException, ConfigurationException {\n    \tsnapshot.read();\n    }\n    \n    public static void saveSnapshot() throws IOException, ConfigurationException {\n    \tsnapshot.write();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/conf/TcPreference.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.conf;\n\n/**\n * \n * @author Arik Hadas\n */\npublic enum TcPreference {\n\tCHECK_FOR_UPDATE(TcPreferences.CHECK_FOR_UPDATE),\n\tDATE_FORMAT(TcPreferences.DATE_FORMAT),\n\tDATE_SEPARATOR(TcPreferences.DATE_SEPARATOR),\n\tTIME_FORMAT(TcPreferences.TIME_FORMAT),\n\tLANGUAGE(TcPreferences.LANGUAGE),\n\tDISPLAY_COMPACT_FILE_SIZE(TcPreferences.DISPLAY_COMPACT_FILE_SIZE),\n\tCONFIRM_ON_QUIT(TcPreferences.CONFIRM_ON_QUIT),\n\tSHOW_SPLASH_SCREEN(TcPreferences.SHOW_SPLASH_SCREEN),\n\tLOOK_AND_FEEL(TcPreferences.LOOK_AND_FEEL),\n\tCUSTOM_LOOK_AND_FEELS(TcPreferences.CUSTOM_LOOK_AND_FEELS),\n\tENABLE_SYSTEM_NOTIFICATIONS(TcPreferences.ENABLE_SYSTEM_NOTIFICATIONS),\n\tPREFERRED_ENCODINGS(TcPreferences.PREFERRED_ENCODINGS),\n\tLOG_LEVEL(TcPreferences.LOG_LEVEL),\n\tLOG_BUFFER_SIZE(TcPreferences.LOG_BUFFER_SIZE),\n\tCUSTOM_SHELL(TcPreferences.CUSTOM_SHELL),\n\tUSE_CUSTOM_SHELL(TcPreferences.USE_CUSTOM_SHELL),\n\tSHELL_HISTORY_SIZE(TcPreferences.SHELL_HISTORY_SIZE),\n\tSHELL_ENCODING(TcPreferences.SHELL_ENCODING),\n\tAUTODETECT_SHELL_ENCODING(TcPreferences.AUTODETECT_SHELL_ENCODING),\n\tSMTP_SERVER(TcPreferences.SMTP_SERVER),\n\tSMTP_PORT(TcPreferences.SMTP_PORT),\n\tMAIL_SENDER_NAME(TcPreferences.MAIL_SENDER_NAME),\n\tMAIL_SENDER_ADDRESS(TcPreferences.MAIL_SENDER_ADDRESS),\n\tCOMMAND_BAR_VISIBLE(TcPreferences.COMMAND_BAR_VISIBLE),\n\tCOMMAND_BAR_ICON_SCALE(TcPreferences.COMMAND_BAR_ICON_SCALE),\n\tSTATUS_BAR_VISIBLE(TcPreferences.STATUS_BAR_VISIBLE),\n\tTOOLBAR_VISIBLE(TcPreferences.TOOLBAR_VISIBLE),\n\tTOOLBAR_ICON_SCALE(TcPreferences.TOOLBAR_ICON_SCALE),\n\tVOLUME_EXCLUDE_REGEXP(TcPreferences.VOLUME_EXCLUDE_REGEXP),\n\tSHOW_HIDDEN_FILES(TcPreferences.SHOW_HIDDEN_FILES),\n\tSHOW_DS_STORE_FILES(TcPreferences.SHOW_DS_STORE_FILES),\n\tSHOW_SYSTEM_FOLDERS(TcPreferences.SHOW_SYSTEM_FOLDERS),\n\tTABLE_ICON_SCALE(TcPreferences.TABLE_ICON_SCALE),\n\tAUTO_SIZE_COLUMNS(TcPreferences.AUTO_SIZE_COLUMNS),\n\tUSE_SYSTEM_FILE_ICONS(TcPreferences.USE_SYSTEM_FILE_ICONS),\n\tSHOW_FOLDERS_FIRST(TcPreferences.SHOW_FOLDERS_FIRST),\n\tFOLDERS_ALWAYS_ALPHABETICAL(TcPreferences.FOLDERS_ALWAYS_ALPHABETICAL),\n\tSHOW_QUICK_SEARCH_MATCHES_FIRST(TcPreferences.SHOW_QUICK_SEARCH_MATCHES_FIRST),\n\tQUICK_SEARCH_TIMEOUT(TcPreferences.QUICK_SEARCH_TIMEOUT),\n\tCD_FOLLOWS_SYMLINKS(TcPreferences.CD_FOLLOWS_SYMLINKS),\n\tUSE_BRUSHED_METAL(TcPreferences.USE_BRUSHED_METAL),\n\tUSE_SCREEN_MENU_BAR(TcPreferences.USE_SCREEN_MENU_BAR),\n\tSTARTUP_FOLDERS(TcPreferences.STARTUP_FOLDERS),\n\tLEFT_CUSTOM_FOLDER(TcPreferences.LEFT_CUSTOM_FOLDER),\n\tRIGHT_CUSTOM_FOLDER(TcPreferences.RIGHT_CUSTOM_FOLDER),\n\tREFRESH_CHECK_PERIOD(TcPreferences.REFRESH_CHECK_PERIOD),\n\tWAIT_AFTER_REFRESH(TcPreferences.WAIT_AFTER_REFRESH),\n\tPROGRESS_DIALOG_EXPANDED(TcPreferences.PROGRESS_DIALOG_EXPANDED),\n\tPROGRESS_DIALOG_CLOSE_WHEN_FINISHED(TcPreferences.PROGRESS_DIALOG_CLOSE_WHEN_FINISHED),\n\tTHEME_TYPE(TcPreferences.THEME_TYPE),\n\tTHEME_NAME(TcPreferences.THEME_NAME),\n    SYNTAX_THEME_NAME(TcPreferences.SYNTAX_THEME_NAME),\n\tENABLE_BONJOUR_DISCOVERY(TcPreferences.ENABLE_BONJOUR_DISCOVERY),\n\tLIST_HIDDEN_FILES(TcPreferences.LIST_HIDDEN_FILES),\n\tSMB_LM_COMPATIBILITY(TcPreferences.SMB_LM_COMPATIBILITY),\n\tSMB_USE_EXTENDED_SECURITY(TcPreferences.SMB_USE_EXTENDED_SECURITY),\n\tSHOW_TAB_HEADER(TcPreferences.SHOW_SINGLE_TAB_HEADER),\n\tCALCULATE_FOLDER_SIZE_ON_MARK(TcPreferences.CALCULATE_FOLDER_SIZE_ON_MARK),\n    FILE_GROUP_1_MASK(TcPreferences.FILE_GROUP_1_MASK),\n    FILE_GROUP_2_MASK(TcPreferences.FILE_GROUP_2_MASK),\n    FILE_GROUP_3_MASK(TcPreferences.FILE_GROUP_3_MASK),\n    FILE_GROUP_4_MASK(TcPreferences.FILE_GROUP_4_MASK),\n    FILE_GROUP_5_MASK(TcPreferences.FILE_GROUP_5_MASK),\n    FILE_GROUP_6_MASK(TcPreferences.FILE_GROUP_6_MASK),\n    FILE_GROUP_7_MASK(TcPreferences.FILE_GROUP_7_MASK),\n    FILE_GROUP_8_MASK(TcPreferences.FILE_GROUP_8_MASK),\n    FILE_GROUP_9_MASK(TcPreferences.FILE_GROUP_9_MASK),\n    FILE_GROUP_10_MASK(TcPreferences.FILE_GROUP_10_MASK),\n    CUSTOM_EXTERNAL_TERMINAL(TcPreferences.CUSTOM_EXTERNAL_TERMINAL),\n    EXTERNAL_TERMINAL_TYPE(TcPreferences.EXTERNAL_TERMINAL_TYPE),\n    TERMINAL_SHELL(TcPreferences.TERMINAL_SHELL),\n    TERMINAL_USE_CUSTOM_SHELL(TcPreferences.TERMINAL_USE_CUSTOM_SHELL),\n\tFIND_FILE_ENCODING(TcPreferences.FIND_FILE_ENCODING),\n\tFIND_FILE_SUBDIRECTORIES(TcPreferences.FIND_FILE_SUBDIRECTORIES),\n\tFIND_FILE_ARCHIVES(TcPreferences.FIND_FILE_ARCHIVES),\n\tFIND_FILE_IGNORE_HIDDEN(TcPreferences.FIND_FILE_IGNORE_HIDDEN),\n\tFIND_FILE_CASE_SENSITIVE(TcPreferences.FIND_FILE_CASE_SENSITIVE),\n\tFIND_FILE_SEARCH_HEX(TcPreferences.FIND_FILE_SEARCH_HEX);\n\n\n\n    private final String label;\n\t\n\tTcPreference(String label) {\n\t\tthis.label = label;\n\t}\n\t\n\t@Override\n\tpublic String toString() {\n\t\treturn label;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/conf/TcPreferences.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.conf;\n\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.util.List;\n\nimport com.mucommander.RuntimeConstants;\nimport com.mucommander.commons.conf.Configuration;\nimport com.mucommander.commons.conf.ConfigurationException;\nimport com.mucommander.commons.conf.ConfigurationListener;\nimport com.mucommander.commons.conf.ValueList;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.ui.icon.FileIcons;\n\n/**\n * trolCommander specific wrapper for the <code>com.mucommander.conf</code> API which is used to save 'static' configurations.\n * 'static' configurations refer to properties that can be changed from the preference's dialog only.\n * those properties do not change often.\n * \n * @author Nicolas Rinaudo, Maxence Bernard, Arik Hadas\n */\npublic class TcPreferences implements TcPreferencesAPI {\n\n\t// - Misc. variables -----------------------------------------------------\n\t// -----------------------------------------------------------------------\n\t/** Whether to automatically check for updates on startup. */\n\tpublic static final String  CHECK_FOR_UPDATE                  = \"check_for_updates_on_startup\";\n\t/** Default automated update behavior. */\n\tpublic static final boolean DEFAULT_CHECK_FOR_UPDATE          = true;\n\t/** Description of the format dates should be displayed with. */\n\tpublic static final String  DATE_FORMAT                       = \"date_format\";\n\t/** Default date format. */\n\tpublic static final String  DEFAULT_DATE_FORMAT               = \"MM/dd/yy\";\n\t/** Character used to separate years, months and days in a date. */\n\tpublic static final String  DATE_SEPARATOR                    = \"date_separator\";\n\t/** Default date separator. */\n\tpublic static final String  DEFAULT_DATE_SEPARATOR            = \"/\";\n\t/** Description of the format timestamps should be displayed with.*/\n\tpublic static final String  TIME_FORMAT                       = \"time_format\";\n\t/** Default time format. */\n\tpublic static final String  DEFAULT_TIME_FORMAT               = \"hh:mm a\";\n\t/** Language trolCommander should use when looking for text. */\n\tpublic static final String  LANGUAGE                          = \"language\";\n\t/** Whether to display compact file sizes. */\n\tpublic static final String  DISPLAY_COMPACT_FILE_SIZE         = \"display_compact_file_size\";\n\t/** Default file size display behavior. */\n\tpublic static final boolean DEFAULT_DISPLAY_COMPACT_FILE_SIZE = true;\n\t/** Whether to ask the user for confirmation before quitting trolCommander. */\n\tpublic static final String  CONFIRM_ON_QUIT                   = \"quit_confirmation\";\n\t/** Default quitting behavior. */\n\tpublic static final boolean DEFAULT_CONFIRM_ON_QUIT           = true;\n\t/** Whether to display splash screen when starting trolCommander. */\n\tpublic static final String  SHOW_SPLASH_SCREEN                = \"show_splash_screen\";\n\t/** Default splash screen behavior. */\n\tpublic static final boolean DEFAULT_SHOW_SPLASH_SCREEN        = true;\n\t/** Look and feel used by trolCommander. */\n\tpublic static final String  LOOK_AND_FEEL                     = \"lookAndFeel\";\n\t/** All registered custom Look and feels. */\n\tpublic static final String  CUSTOM_LOOK_AND_FEELS             = \"custom_look_and_feels\";\n\t/** Separator used to tokenize the custom look and feels variable. */\n\tpublic static final String  CUSTOM_LOOK_AND_FEELS_SEPARATOR   = \";\";\n\t/** Controls whether system notifications are enabled. */\n\tpublic static final String  ENABLE_SYSTEM_NOTIFICATIONS       = \"enable_system_notifications\";\n\t/** System notifications are enabled by default on platforms where a notifier is available and works well enough.\n\t * In particular, the system tray notifier is available under Linux+Java 1.6, but it doesn't work well, so it is not\n\t * enabled by default. */\n\tpublic static final boolean DEFAULT_ENABLE_SYSTEM_NOTIFICATIONS = OsFamily.MAC_OS_X.isCurrent() ||\n\t\t\tOsFamily.WINDOWS.isCurrent();\n\t/** List of encodings that are displayed in encoding selection components. */\n\tpublic static final String  PREFERRED_ENCODINGS               = \"preferred_encodings\";\n\n\n\t// - Log variables -------------------------------------------------------\n\t// -----------------------------------------------------------------------\n\t/** Section describing the log CONFIGURATION. */\n\tpublic static final String  LOG_SECTION                       = \"log\";\n\t/** Log level. */\n\tpublic static final String  LOG_LEVEL                         = LOG_SECTION + '.' + \"level\";\n\t/** Default log level. */\n\tpublic static final String  DEFAULT_LOG_LEVEL                 = \"WARNING\";\n\t/** Log buffer size, in number of messages. */\n\tpublic static final String  LOG_BUFFER_SIZE                   = LOG_SECTION + '.' + \"buffer_size\";\n\t/** Default log buffer size. Should be set to a low value to minimize memory usage, yet high enough to have most of\n\t * the recent log messages. */\n\tpublic static final int     DEFAULT_LOG_BUFFER_SIZE           = 200;\n\n\n\t// - Shell variables -----------------------------------------------------\n\t// -----------------------------------------------------------------------\n\t/** Section describing the shell CONFIGURATION. */\n\tpublic static final String  SHELL_SECTION                     = \"shell\";\n\t/** Shell invocation command (in case trolCommander is not using the default one). */\n\tpublic static final String  CUSTOM_SHELL                      = SHELL_SECTION + '.' + \"custom_command\";\n\t/** Whether to use a custom shell invocation command. */\n\tpublic static final String  USE_CUSTOM_SHELL                  = SHELL_SECTION + '.' + \"use_custom\";\n\t/** Default custom shell behavior. */\n\tpublic static final boolean DEFAULT_USE_CUSTOM_SHELL          = false;\n\t/** Maximum number of items that should be present in the shell history. */\n\tpublic static final String  SHELL_HISTORY_SIZE                = SHELL_SECTION + '.' + \"history_size\";\n\t/** Default maximum shell history size. */\n\tpublic static final int     DEFAULT_SHELL_HISTORY_SIZE        = 100;\n\t/** Encoding used to read the shell output. */\n\tpublic static final String  SHELL_ENCODING                    = SHELL_SECTION + '.' + \"encoding\";\n\t/** Whether shell encoding should be auto-detected. */\n\tpublic static final String  AUTODETECT_SHELL_ENCODING         = SHELL_SECTION + '.' + \"autodetect_encoding\";\n\t/** Default shell encoding auto-detection behavior. */\n\tpublic static final boolean DEFAULT_AUTODETECT_SHELL_ENCODING = true;\n\n\n    // - Terminal variables -----------------------------------------------------\n\tpublic static final int TERMINAL_DEFAULT = 0;\n\tpublic static final int TERMINAL_CUSTOM = 1;\n\tpublic static final int TERMINAL_ITERM = 2;\t// MacOS only\n    /** Section describing the terminal (external and built in) CONFIGURATION. */\n    public static final String TERMINAL_SECTION = \"terminal\";\n    /** Terminal invocation command. */\n    public static final String CUSTOM_EXTERNAL_TERMINAL = TERMINAL_SECTION + '.' + \"custom_external_command\";\n    /** Whether to use a custom shell invocation Terminal command. */\n    public static final String EXTERNAL_TERMINAL_TYPE = TERMINAL_SECTION + '.' + \"external_type\";\n    /** Default custom terminal behavior. */\n    public static final int DEFAULT_TERMINAL_TYPE = TERMINAL_DEFAULT;\n\n    /** Terminal shell command. */\n    public static final String TERMINAL_SHELL = TERMINAL_SECTION + '.' + \"shell\";\n    public static final String TERMINAL_USE_CUSTOM_SHELL = TERMINAL_SECTION + '.' + \"use_custom_shell\";\n\n    public static final boolean DEFAULT_TERMINAL_USE_CUSTOM_SHELL = false;\n\n\n    // - Mail variables ------------------------------------------------------\n\t// -----------------------------------------------------------------------\n\t/** Section describing mail CONFIGURATION. */\n\tpublic static final String MAIL_SECTION                       = \"mail\";\n\t/** Address of the SMTP server that should be used when sending mails. */\n\tpublic static final String SMTP_SERVER                        = MAIL_SECTION + '.' + \"smtp_server\";\n\t/** Outgoing TCP port to the SMTP server. */\n\tpublic static final String SMTP_PORT                          = MAIL_SECTION + '.' + \"smtp_port\";\n\t/** Default outgoing TCP port to the SMTP server. */\n\tpublic static final int    DEFAULT_SMTP_PORT                   = 25;\n\t/** Name under which mails sent by trolCommander should appear. */\n\tpublic static final String MAIL_SENDER_NAME                   = MAIL_SECTION + '.' + \"sender_name\";\n\t/** Address which mails sent by trolCommander should be replied to. */\n\tpublic static final String MAIL_SENDER_ADDRESS                = MAIL_SECTION + '.' + \"sender_address\";\n\n\n\n\t// - Command bar variables -----------------------------------------------\n\t// -----------------------------------------------------------------------\n\t/** Section describing the command bar CONFIGURATION. */\n\tpublic static final String  COMMAND_BAR_SECTION               = \"command_bar\";\n\t/** Whether the command bar is visible. */\n\tpublic static final String  COMMAND_BAR_VISIBLE               = COMMAND_BAR_SECTION + '.' + \"visible\";\n\t/** Default command bar visibility. */\n\tpublic static final boolean DEFAULT_COMMAND_BAR_VISIBLE       = true;\n\t/** Scale factor of commandbar icons. */\n\tpublic static final String  COMMAND_BAR_ICON_SCALE            = COMMAND_BAR_SECTION + '.' + \"icon_scale\";\n\t/** Default scale factor of commandbar icons. */\n\tpublic static final float   DEFAULT_COMMAND_BAR_ICON_SCALE    = 1.0f;\n\n\n\n\t// - Status bar variables ------------------------------------------------\n\t// -----------------------------------------------------------------------\n\t/** Section describing the status bar CONFIGURATION. */\n\tpublic static final String STATUS_BAR_SECTION                 = \"status_bar\";\n\t/** Whether the status bar is visible. */\n\tpublic static final String STATUS_BAR_VISIBLE                 = STATUS_BAR_SECTION + '.' + \"visible\";\n\t/** Default status bar visibility. */\n\tpublic static final boolean DEFAULT_STATUS_BAR_VISIBLE        = true;\n\n\n\n\t// - Toolbar variables ---------------------------------------------------\n\t// -----------------------------------------------------------------------\n\t/** Section describing the toolbar CONFIGURATION. */\n\tpublic static final String TOOLBAR_SECTION                    = \"toolbar\";\n\t/** Whether the toolbar is visible. */\n\tpublic static final String TOOLBAR_VISIBLE                    = TOOLBAR_SECTION + '.' + \"visible\";\n\t/** Default toolbar visibility. */\n\tpublic static final boolean DEFAULT_TOOLBAR_VISIBLE           = true;\n\t/** Scale factor of toolbar icons. */\n\tpublic static final String  TOOLBAR_ICON_SCALE                = TOOLBAR_SECTION + '.' + \"icon_scale\";\n\t/** Default scale factor of toolbar icons. */\n\tpublic static final float   DEFAULT_TOOLBAR_ICON_SCALE        = 1.0f;\n\n\n\t// - Volume list ---------------------------------------------------------\n\t// -----------------------------------------------------------------------\n\t/** Section describing the volume list CONFIGURATION. */\n\tpublic static final String VOLUME_LIST_SECTION                 = \"volume_list\";\n\t/** Regexp that allows volumes to be excluded from the list. */\n\tpublic static final String VOLUME_EXCLUDE_REGEXP               = VOLUME_LIST_SECTION + '.' + \"exclude_regexp\";\n\n\n\t// - FileTable variables ---------------------------------------------------\n\t// -----------------------------------------------------------------------\n\t/** Section describing the folders view CONFIGURATION. */\n\tpublic static final String  FILE_TABLE_SECTION                 = \"file_table\";\n\t/** Identifier of the left file table. */\n\tpublic static final String  LEFT                               = \"left\";\n\t/** Identifier of the right file table. */\n\tpublic static final String  RIGHT                              = \"right\";\n\t/** Whether to display hidden files. */\n\tpublic static final String  SHOW_HIDDEN_FILES                  = FILE_TABLE_SECTION + '.' + \"show_hidden_files\";\n\t/** Default hidden files visibility. */\n\tpublic static final boolean DEFAULT_SHOW_HIDDEN_FILES          = true;\n\t/** Whether to display OS X .DS_Store files. */\n\tpublic static final String  SHOW_DS_STORE_FILES                = FILE_TABLE_SECTION + '.' + \"show_ds_store_files\";\n\t/** Default .DS_Store files visibility. */\n\tpublic static final boolean DEFAULT_SHOW_DS_STORE_FILES        = true;\n\t/** Whether to display system folders. */\n\tpublic static final String  SHOW_SYSTEM_FOLDERS                = FILE_TABLE_SECTION + '.' + \"show_system_folders\";\n\t/** Default system folders visibility. */\n\tpublic static final boolean DEFAULT_SHOW_SYSTEM_FOLDERS        = true;\n\t/** Scale factor of file table icons. */\n\tpublic static final String  TABLE_ICON_SCALE                   = FILE_TABLE_SECTION + '.' + \"icon_scale\";\n\t/** Default scale factor of file table icons. */\n\tpublic static final float   DEFAULT_TABLE_ICON_SCALE           = 1.0f;\n\t/** Whether columns should resize themselves automatically. */\n\tpublic static final String  AUTO_SIZE_COLUMNS                  = FILE_TABLE_SECTION + '.' + \"auto_size_columns\";\n\t/** Default columns auto-resizing behavior. */\n\tpublic static final boolean DEFAULT_AUTO_SIZE_COLUMNS          = true;\n\t/** Controls if and when system file icons should be used instead of custom file icons. */\n\tpublic static final String  USE_SYSTEM_FILE_ICONS              = FILE_TABLE_SECTION + '.' + \"use_system_file_icons\";\n\t/** Default system file icons policy. */\n\tpublic static final String  DEFAULT_USE_SYSTEM_FILE_ICONS      = FileIcons.USE_SYSTEM_ICONS_APPLICATIONS;\n\t/** Controls whether folders are displayed first in the FileTable or mixed with regular files. */\n\tpublic static final String  SHOW_FOLDERS_FIRST                 = FILE_TABLE_SECTION + '.' + \"show_folders_first\";\n\t/** Controls whether folders are always sorted alphabetical, doesn't matter which sort is set for the files. */\n\tpublic static final String  FOLDERS_ALWAYS_ALPHABETICAL        = FILE_TABLE_SECTION + '.' + \"folders_always_alphabetical\";\n\t/** Default value for 'Show folders first' option. */\n\tpublic static final boolean DEFAULT_SHOW_FOLDERS_FIRST         = true;\n\t/** Default value for 'Folders always alphabetical' option. */\n\tpublic static final boolean DEFAULT_FOLDERS_ALWAYS_ALPHABETICAL = false;\n\t/** Controls whether symlinks should be followed when changing directory. */\n\tpublic static final String  CD_FOLLOWS_SYMLINKS                = FILE_TABLE_SECTION + '.' + \"cd_follows_symlinks\";\n\t/** Default value for 'Follow symlinks when changing directory' option. */\n\tpublic static final boolean DEFAULT_CD_FOLLOWS_SYMLINKS        = false;\n\t/** Whether to always show the header of a single tab or not */\n\tpublic static final String SHOW_SINGLE_TAB_HEADER\t\t\t   = FILE_TABLE_SECTION + '.' + \"show_single_tab_header\";\n\t/** Default value for 'Always show single tab header' */\n\tpublic static final boolean DEFAULT_SHOW_TAB_HEADER\t   = false;\n\t/** Whether to calculate folder size when marking that folder */\n\tpublic static final String CALCULATE_FOLDER_SIZE_ON_MARK       = FILE_TABLE_SECTION + '.' + \"calculate_folder_size_on_mark\";\n\t/** Default value for 'Calculate folder size on mark' */\n\tpublic static final boolean DEFAULT_CALCULATE_FOLDER_SIZE_ON_MARK = false;\n\n\t/** Name of the root element's attribute that contains the version of trolCommander used to write the CONFIGURATION file. */\n\tstatic final String VERSION_ATTRIBUTE = \"version\";\n\n\n\n\t// - Mac OS X variables --------------------------------------------------\n\t// -----------------------------------------------------------------------\n\t/** Section describing trolCommander's Mac OS X integration. */\n\tpublic static final String  MAC_OSX_SECTION                   = \"macosx\";\n\t/** Whether to use the brushed metal look. */\n\tpublic static final String  USE_BRUSHED_METAL                 = MAC_OSX_SECTION + '.' + \"brushed_metal_look\";\n\t/** Default brushed metal look behavior. */\n\t// At the time of writing, the 'brushed metal' look causes the JVM to crash randomly under Leopard (10.5)\n\t// so we disable brushed metal on that OS version but leave it for earlier versions where it works fine.\n\tpublic static final boolean DEFAULT_USE_BRUSHED_METAL         = false;\n\t/** Whether to use a Mac OS X style menu bar. */\n\tpublic static final String  USE_SCREEN_MENU_BAR               = MAC_OSX_SECTION + '.' + \"screen_menu_bar\";\n\t/** Default menu bar type. */\n\tpublic static final boolean DEFAULT_USE_SCREEN_MENU_BAR       = true;\n\n\n\n\t// - Startup folder variables --------------------------------------------\n\t// -----------------------------------------------------------------------\n\t/** Section describing trolCommander's startup folders. */\n\tpublic static final String  STARTUP_FOLDER_SECTION            = \"startup_folder\";\n\t/** Startup folder type (for the two panels) */\n\tpublic static final String  STARTUP_FOLDERS                   = STARTUP_FOLDER_SECTION + '.' + \"on_startup\";\n\t/** The custom folder should be used on startup. */\n\tpublic static final String  STARTUP_FOLDERS_CUSTOM            = \"customFolders\";\n\t/** The last visited folder should be used on startup. */\n\tpublic static final String  STARTUP_FOLDERS_LAST              = \"lastFolders\";\n\t/** Default startup folder type. */\n\tpublic static final String  DEFAULT_STARTUP_FOLDERS           = STARTUP_FOLDERS_LAST;\n\t/** Section describing custom folders that were set for the two panel. */\n\tpublic static final String  CUSTOM_FOLDERS_SECTION            = STARTUP_FOLDER_SECTION + '.' + \"custom_folders\";\n\t/** Path to a custom startup folders Section describing the right panel's startup folder. */\n\tpublic static final String  RIGHT_CUSTOM_FOLDER      \t\t  = CUSTOM_FOLDERS_SECTION + '.' + RIGHT;\n\t/** Section describing the left panel's startup folder. */\n\tpublic static final String  LEFT_CUSTOM_FOLDER\t\t\t      = CUSTOM_FOLDERS_SECTION + '.' + LEFT;\n\t\n\n\n\t// - Folder monitoring variables -----------------------------------------\n\t// -----------------------------------------------------------------------\n\t/** Section describing the automatic folder refresh behavior. */\n\tstatic final String REFRESH_SECTION                    = \"auto_refresh\";\n\t/** Frequency at which the current folder is checked for updates, -1 to disable auto refresh. */\n\tpublic static final String REFRESH_CHECK_PERIOD               = REFRESH_SECTION + '.' + \"check_period\";\n\t/** Default folder refresh frequency. */\n\tpublic static final long   DEFAULT_REFRESH_CHECK_PERIOD       = 3000;\n\t/** Minimum amount of time a folder should be checked for updates after it's been refreshed. */\n\tpublic static final String WAIT_AFTER_REFRESH                 = REFRESH_SECTION + '.' + \"wait_after_refresh\";\n\t/** Default minimum amount of time between two refreshes. */\n\tpublic static final long   DEFAULT_WAIT_AFTER_REFRESH         = 10000;\n\n\n\t// - Quick search variables -----------------------------------------\n\t// -----------------------------------------------------------------------\n\n\tstatic final String QUICK_SEARCH_SECTION = \"quick_search\";\n\t/** Controls whether matched files are displayed first in the FileTable or mixed with other files on quick search operation. */\n\tstatic final String SHOW_QUICK_SEARCH_MATCHES_FIRST     = QUICK_SEARCH_SECTION + '.' + \"show_quick_search_matches_first\";\n\t/** Default value for 'Show matches first' option. */\n\tpublic static final boolean DEFAULT_SHOW_QUICK_SEARCH_MATCHES_FIRST = true;\n\n\tstatic final String QUICK_SEARCH_TIMEOUT\t\t\t    = QUICK_SEARCH_SECTION + '.' + \"timeout\";\n\t/** Quick search timeout in ms. No timeout if &lt;= 0 */\n\tpublic static final int DEFAULT_QUICK_SEARCH_TIMEOUT    = 5000;\n\n\n\n\n\t// - Progress dialog variables -------------------------------------------\n\t// -----------------------------------------------------------------------\n\t/** Section describing the behavior of the progress dialog. */\n\tpublic static final String  PROGRESS_DIALOG_SECTION           = \"progress_dialog\";\n\t/** Whether the progress dialog is expanded or not. */\n\tpublic static final String  PROGRESS_DIALOG_EXPANDED          = PROGRESS_DIALOG_SECTION + '.' + \"expanded\";\n\t/** Default progress dialog expanded state. */\n\tpublic static final boolean DEFAULT_PROGRESS_DIALOG_EXPANDED  = true;\n\t/** Controls whether the progress dialog should be closed when the job is finished. */\n\tpublic static final String PROGRESS_DIALOG_CLOSE_WHEN_FINISHED = PROGRESS_DIALOG_SECTION + '.' + \"close_when_finished\";\n\t/** Default progress dialog behavior when the job is finished. */\n\tpublic static final boolean DEFAULT_PROGRESS_DIALOG_CLOSE_WHEN_FINISHED  = true;\n\n\n\n\t// - Variables used for themes -------------------------------------------\n\t// -----------------------------------------------------------------------\n\t/** Section controlling which theme should be applied to trolCommander. */\n\tpublic static final String THEME_SECTION                      = \"theme\";\n\t/** Current theme type (custom, predefined or user defined). */\n\tpublic static final String THEME_TYPE                         = THEME_SECTION + '.' + \"type\";\n\t/** Describes predefined themes. */\n\tpublic static final String THEME_PREDEFINED                   = \"predefined\";\n\t/** Describes custom themes. */\n\tpublic static final String THEME_CUSTOM                       = \"custom\";\n\t/** Describes the user theme. */\n\tpublic static final String THEME_USER                         = \"user\";\n\t/** Default theme type. */\n\tpublic static final String DEFAULT_THEME_TYPE                 = THEME_PREDEFINED;\n\t/** Name of the current theme. */\n\tpublic static final String THEME_NAME                         = THEME_SECTION + '.' + \"path\";\n    /** Name of the current theme. */\n    public static final String SYNTAX_THEME_NAME                  = \"editor.syntax.path\";\n\t/** Default current theme name. */\n\tpublic static final String DEFAULT_THEME_NAME                 = RuntimeConstants.DEFAULT_THEME;\n    /** Default current editor syntax theme name. */\n    public static final String DEFAULT_SYNTAX_THEME_NAME          = \"Default\";\n\n\n\n\t// - Variables used by Bonjour/Zeroconf support --------------------------\n\t// -----------------------------------------------------------------------\n\t/** Section controlling parameters related to Bonjour/Zeroconf support. */\n\tpublic static final String  BONJOUR_SECTION                   = \"bonjour\";\n\t/** Used do determine whether discovery of Bonjour services should be activated or not. */\n\tpublic static final String  ENABLE_BONJOUR_DISCOVERY          = BONJOUR_SECTION + '.' + \"discovery_enabled\";\n\t/** Default Bonjour discovery activation used on startup. */\n\tpublic static final boolean DEFAULT_ENABLE_BONJOUR_DISCOVERY  = !OsFamily.MAC_OS_X.isCurrent();\n\n\n\n\t// - Variables used for FTP ----------------------------------------------\n\t// -----------------------------------------------------------------------\n\t/** Section containing all FTP variables. */\n\tpublic static final String FTP_SECTION                        = \"ftp\";\n\t/** Controls whether hidden files should be listed by the client (LIST -al instead of LIST -l). */\n\tpublic static final String LIST_HIDDEN_FILES                  = FTP_SECTION + '.' + \"list_hidden_files\";\n\t/** Default value for {@link #LIST_HIDDEN_FILES}. */\n\tpublic static final boolean DEFAULT_LIST_HIDDEN_FILES         = false;\n\n\n\t// - Variables used for SMB ----------------------------------------------\n\t// -----------------------------------------------------------------------\n\t/** Section containing all SMB variables. */\n\tpublic static final String SMB_SECTION                        = \"smb\";\n\t/** Controls the authentication protocol to use when connecting to SMB servers. */\n\tpublic static final String SMB_LM_COMPATIBILITY               = SMB_SECTION + '.' + \"lm_compatibility\";\n\t/** Default value for {@link #SMB_LM_COMPATIBILITY}. */\n\tpublic static final int DEFAULT_SMB_LM_COMPATIBILITY          = 0;\n\t/** Controls the authentication protocol to use when connecting to SMB servers. */\n\tpublic static final String SMB_USE_EXTENDED_SECURITY          = SMB_SECTION + '.' + \"use_extended_security\";\n\t/** Default value for {@link #SMB_USE_EXTENDED_SECURITY}. */\n\tpublic static final boolean DEFAULT_SMB_USE_EXTENDED_SECURITY = false;\n\n    // - File group masks ----------------------------------------------------\n    // -----------------------------------------------------------------------\n    static final String FILE_GROUP_SECTION                 = \"file_groups\";\n    static final String FILE_GROUP_1_MASK                  = FILE_GROUP_SECTION + \".files1\";\n    static final String FILE_GROUP_2_MASK                  = FILE_GROUP_SECTION + \".files2\";\n    static final String FILE_GROUP_3_MASK                  = FILE_GROUP_SECTION + \".files3\";\n    static final String FILE_GROUP_4_MASK                  = FILE_GROUP_SECTION + \".files4\";\n    static final String FILE_GROUP_5_MASK                  = FILE_GROUP_SECTION + \".files5\";\n    static final String FILE_GROUP_6_MASK                  = FILE_GROUP_SECTION + \".files6\";\n    static final String FILE_GROUP_7_MASK                  = FILE_GROUP_SECTION + \".files7\";\n    static final String FILE_GROUP_8_MASK                  = FILE_GROUP_SECTION + \".files8\";\n    static final String FILE_GROUP_9_MASK                  = FILE_GROUP_SECTION + \".files9\";\n    static final String FILE_GROUP_10_MASK                 = FILE_GROUP_SECTION + \".files10\";\n\n\t// - Find file dialog ----------------------------------------------------\n\t// -----------------------------------------------------------------------\n\tstatic final String FIND_FILE_SECTION\t\t\t\t  = \"find_file\";\n\tstatic final String FIND_FILE_ENCODING                 = FIND_FILE_SECTION + \".encoding\";\n\tstatic final String FIND_FILE_SUBDIRECTORIES           = FIND_FILE_SECTION + \".subdirectories\";\n\tstatic final String FIND_FILE_ARCHIVES                 = FIND_FILE_SECTION + \".archives\";\n\tstatic final String FIND_FILE_IGNORE_HIDDEN            = FIND_FILE_SECTION + \".ignore_hidden\";\n\tstatic final String FIND_FILE_CASE_SENSITIVE           = FIND_FILE_SECTION + \".case_sensitive\";\n\tstatic final String FIND_FILE_SEARCH_HEX               = FIND_FILE_SECTION + \".search_hex\";\n\t\n\tprivate static final String ROOT_ELEMENT = \"preferences\";\n\n\t// - Instance fields -----------------------------------------------------\n\t// -----------------------------------------------------------------------\n\tprivate Configuration configuration;\n\t\n\tprivate String configurationVersion;\n\n\t/**\n\t * Prevents instantiation of this class from outside this package.\n\t */\n\tTcPreferences() {\n\t\tTcPreferencesFile muPreferencesFile = TcPreferencesFile.getPreferencesFile();\n\n        configuration = new Configuration(muPreferencesFile, new VersionedXmlConfigurationReaderFactory(),\n\t\t\t\tnew VersionedXmlConfigurationWriterFactory(ROOT_ELEMENT));\n\t}\n\n\t// - Configuration reading / writing -------------------------------------\n\t// -----------------------------------------------------------------------\n\t/**\n\t * Loads the trolCommander CONFIGURATION.\n\t * @throws IOException            if an I/O error occurs.\n\t * @throws ConfigurationException if a CONFIGURATION related error occurs.\n\t */\n\tvoid read() throws IOException, ConfigurationException {\n\t\tVersionedXmlConfigurationReader reader = new VersionedXmlConfigurationReader();\n\t\tconfiguration.read(reader);\n\n\t\t// Ensure backward compatibility\n\t\tconfigurationVersion = reader.getVersion();\n\t\tif (configurationVersion == null || !configurationVersion.equals(RuntimeConstants.VERSION)) {\n\t\t\t// Rename preferences that have changed (from v0.8.5)\n\t\t\tconfiguration.renameVariable(\"show_hidden_files\", SHOW_HIDDEN_FILES);\n\t\t\tconfiguration.renameVariable(\"auto_size_columns\", AUTO_SIZE_COLUMNS);\n\t\t\tconfiguration.renameVariable(\"show_toolbar\",      TOOLBAR_VISIBLE);\n\t\t\tconfiguration.renameVariable(\"show_status_bar\",   STATUS_BAR_VISIBLE);\n\t\t\tconfiguration.renameVariable(\"show_command_bar\",  COMMAND_BAR_VISIBLE);\n\t\t}\n\n\t\t// Initializes MAC OS X specific values\n\t\tif (OsFamily.MAC_OS_X.isCurrent()) {\n\t\t\tif (configuration.getVariable(SHELL_ENCODING) == null) {\n\t\t\t\tconfiguration.setVariable(SHELL_ENCODING, \"UTF-8\");\n\t\t\t\tconfiguration.setVariable(AUTODETECT_SHELL_ENCODING, false);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Saves the trolCommander CONFIGURATION.\n\t * @throws IOException            if an I/O error occurs.\n\t * @throws ConfigurationException if a CONFIGURATION related error occurs.\n\t */\n\tvoid write() throws IOException, ConfigurationException {\n\t\tif (configurationVersion != null && !configurationVersion.equals(RuntimeConstants.VERSION)) {\n\t\t\t// Clear the configuration before saving to drop preferences which are unused anymore\n\t\t\tConfiguration conf = new Configuration(TcPreferencesFile.getPreferencesFile(), new VersionedXmlConfigurationReaderFactory(),\n\t\t\t\t\tnew VersionedXmlConfigurationWriterFactory(ROOT_ELEMENT));\n\n\t\t\tfor (TcPreference preference : TcPreference.values())\n\t\t\t\tconf.setVariable(preference.toString(), configuration.getVariable(preference.toString()));\n\t\t\t\n\t\t\t// Remove preferences which are not relevant if we're not using MAC\n\t\t\tif (!OsFamily.MAC_OS_X.isCurrent()) {\n\t\t\t\tconf.removeVariable(USE_BRUSHED_METAL);\n\t\t\t\tconf.removeVariable(USE_SCREEN_MENU_BAR);\n\t\t\t}\n\n\t\t\tconfiguration = conf;\n\t\t}\n\n\t\tconfiguration.write();\n\t}\n\n\t// - Configuration listening -----------------------------------------------\n\t// -------------------------------------------------------------------------\n\t/**\n\t * Adds the specified object to the list of registered CONFIGURATION listeners.\n\t * @param listener object to register as a CONFIGURATION listener.\n\t * @see            #removeConfigurationListener(ConfigurationListener)\n\t */\n\tvoid addConfigurationListener(ConfigurationListener listener) {configuration.addConfigurationListener(listener);}\n\n\t/**\n\t * Removes the specified object from the list of registered CONFIGURATION listeners.\n\t * @param listener object to remove from the list of registered CONFIGURATION listeners.\n\t * @see            #addConfigurationListener(ConfigurationListener)\n\t */\n\tvoid removeConfigurationListener(ConfigurationListener listener) {configuration.removeConfigurationListener(listener);}\n\n\n\t// - Configuration source --------------------------------------------------\n\t// -------------------------------------------------------------------------\n\t/**\n\t * Sets the path to the CONFIGURATION file.\n\t * @param  file                  path to the file that should be used for CONFIGURATION storage.\n\t * @throws FileNotFoundException if the specified file is not a valid file.\n\t */\n\tvoid setConfigurationFile(String file) throws FileNotFoundException {\n\t\tconfiguration.setSource(TcPreferencesFile.getPreferencesFile(file));\n\t}\n\n\t/**\n\t * Check whether the preferences file exists\n\t * @return             true if the preferences file exits, false otherwise.\n\t * @throws IOException if an error occurred.\n\t */\n\tboolean isFileExists() throws IOException {\n\t\treturn configuration.getSource().isExists();\n\t}\n\t\n\t/////////////////////////////////////\n\t// MuPreferencesAPI implementation //\n\t/////////////////////////////////////\n\t@Override\n\tpublic boolean setVariable(TcPreference preference, String value) {\n\t\treturn configuration.setVariable(preference.toString(), value);\n\t}\n\n\t@Override\n\tpublic boolean setVariable(TcPreference preference, int value) {\n\t\treturn configuration.setVariable(preference.toString(), value);\n\t}\n\n\t@Override\n\tpublic boolean setVariable(TcPreference preference, List<String> value, String separator) {\n\t\treturn configuration.setVariable(preference.toString(), value, separator);\n\t}\n\n\t@Override\n\tpublic boolean setVariable(TcPreference preference, float value) {\n\t\treturn configuration.setVariable(preference.toString(), value);\n\t}\n\n\t@Override\n\tpublic boolean setVariable(TcPreference preference, boolean value) {\n\t\treturn configuration.setVariable(preference.toString(), value);\n\t}\n\n\t@Override\n\tpublic boolean setVariable(TcPreference preference, long value) {\n\t\treturn configuration.setVariable(preference.toString(), value);\n\t}\n\n\t@Override\n\tpublic boolean setVariable(TcPreference preference, double value) {\n\t\treturn configuration.setVariable(preference.toString(), value);\n\t}\n\n\t@Override\n\tpublic String getVariable(TcPreference preference) {\n\t\treturn configuration.getVariable(preference.toString());\n\t}\n\n\t@Override\n\tpublic String getVariable(TcPreference preference, String value) {\n\t\treturn configuration.getVariable(preference.toString(), value);\n\t}\n\n\t@Override\n\tpublic int getVariable(TcPreference preference, int value) {\n\t\treturn configuration.getVariable(preference.toString(), value);\n\t}\n\n\t@Override\n\tpublic List<String> getVariable(TcPreference preference, List<String> value, String separator) {\n\t\treturn configuration.getVariable(preference.toString(), value, separator);\n\t}\n\n\t@Override\n\tpublic float getVariable(TcPreference preference, float value) {\n\t\treturn configuration.getVariable(preference.toString(), value);\n\t}\n\n\t@Override\n\tpublic boolean getVariable(TcPreference preference, boolean value) {\n\t\treturn configuration.getVariable(preference.toString(), value);\n\t}\n\n\t@Override\n\tpublic long getVariable(TcPreference preference, long value) {\n\t\treturn configuration.getVariable(preference.toString(), value);\n\t}\n\n\t@Override\n\tpublic double getVariable(TcPreference preference, double value) {\n\t\treturn configuration.getVariable(preference.toString(), value);\n\t}\n\n\t@Override\n\tpublic ValueList getListVariable(TcPreference preference, String separator) {\n\t\treturn configuration.getListVariable(preference.toString(), separator);\n\t}\n\n\t@Override\n\tpublic boolean getBooleanVariable(String name) {\n\t\treturn configuration.getBooleanVariable(name);\n\t}\n\n\t@Override\n\tpublic String  getVariable(String name) {\n\t\treturn configuration.getVariable(name);\n\t}\n\n\t@Override\n\tpublic boolean isVariableSet(TcPreference preference) {\n\t\treturn configuration.isVariableSet(preference.toString());\n\t}\n\n\t@Override\n\tpublic String removeVariable(String name) {\n\t\treturn configuration.removeVariable(name);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/conf/TcPreferencesAPI.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.conf;\n\nimport java.util.List;\n\nimport com.mucommander.commons.conf.ValueList;\n\n/**\n * \n * @author Arik Hadas\n */\npublic interface TcPreferencesAPI {\n\tboolean setVariable(TcPreference preference, String value);\n\tboolean setVariable(TcPreference preference, int value);\n\tboolean setVariable(TcPreference preference, List<String> value, String separator);\n\tboolean setVariable(TcPreference preference, float value);\n\tboolean setVariable(TcPreference preference, boolean value);\n\tboolean setVariable(TcPreference preference, long value);\n\tboolean setVariable(TcPreference preference, double value);\n\t\n\tString getVariable(TcPreference preference);\n\tString getVariable(TcPreference preference, String value);\n\tint getVariable(TcPreference preference, int value);\n\tList<String> getVariable(TcPreference preference, List<String> value, String separator);\n\tfloat getVariable(TcPreference preference, float value);\n\tboolean getVariable(TcPreference preference, boolean value);\n\tlong getVariable(TcPreference preference, long value);\n\tdouble getVariable(TcPreference preference, double value);\n\tValueList getListVariable(TcPreference preference, String separator);\n\t\n\t// TODO: remove those methods\n\tboolean getBooleanVariable(String name);\n\tString  getVariable(String name);\n\t\n\tboolean isVariableSet(TcPreference preference);\n\t\n\tString removeVariable(String name);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/conf/TcPreferencesFile.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.conf;\n\nimport java.io.FileNotFoundException;\n\n/**\n * \n * @author Arik Hadas\n */\nclass TcPreferencesFile extends TcConfigurationFile {\n\t\n\tprivate static final String DEFAULT_PREFERENCES_FILE_NAME = \"preferences.xml\";\n\t\n\tstatic TcPreferencesFile getPreferencesFile(String path) throws FileNotFoundException {\n\t\treturn new TcPreferencesFile(path);\n\t}\n\t\n\tstatic TcPreferencesFile getPreferencesFile() {\n\t\ttry {\n\t\t\treturn new TcPreferencesFile(null);\n\t\t} catch (FileNotFoundException e) {\n\t\t\t// Not possible exception\n\t\t\treturn null;\n\t\t}\n\t}\n\t\n\tprivate TcPreferencesFile(String path) throws FileNotFoundException {\n\t\tsuper(path, DEFAULT_PREFERENCES_FILE_NAME);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/conf/TcSnapshot.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.conf;\n\nimport java.awt.Dimension;\nimport java.awt.HeadlessException;\nimport java.awt.Rectangle;\nimport java.awt.Toolkit;\nimport java.io.IOException;\nimport java.util.Iterator;\nimport java.util.List;\n\nimport javax.swing.JSplitPane;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.conf.Configuration;\nimport com.mucommander.commons.conf.ConfigurationException;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.core.GlobalLocationHistory;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.WindowManager;\nimport com.mucommander.ui.main.table.Column;\nimport com.mucommander.ui.main.table.FileTable;\nimport com.mucommander.ui.main.tabs.FileTableTab;\nimport com.mucommander.ui.main.tabs.FileTableTabs;\nimport com.mucommander.ui.viewer.text.TextViewer;\n\n/**\n * muCommander specific wrapper for the <code>com.mucommander.conf</code> API which is used to save 'dynamic' configurations.\n * 'dynamic' configurations refer to properties that represent the state of the last running instance which is not set from the \n * preferences dialog. those properties are changed often.\n * \n * @author Arik Hadas\n */\npublic class TcSnapshot {\n\tprivate static Logger logger;\n\t\n\t// - Last screen variables -----------------------------------------------\n    // -----------------------------------------------------------------------\n    /** Section describing last known screen properties. */\n    private static final String SCREEN_SECTION                     = \"screen\";\n    /** Last known screen width. */\n    public static final String  SCREEN_WIDTH                       = SCREEN_SECTION + \".\" + \"width\";\n    /** Last known screen height. */\n    public static final String  SCREEN_HEIGHT                      = SCREEN_SECTION + \".\" + \"height\";\n\n    // - Text file presenter (viewer/editor) variables -----------------------\n \t// -----------------------------------------------------------------------\n \t/** Section describing information about features used by the last file presenter instance. */\n \tprivate static final String  FILE_PRESENTER_SECTION            = \"file_presenter\";\n \t/** Section describing information specific to text file presenter. */\n \tprivate static final String TEXT_FILE_PRESENTER_SECTION        = FILE_PRESENTER_SECTION + \".\" + \"text\";\n \t/** Whether to wrap long lines. */\n \tpublic static final String  TEXT_FILE_PRESENTER_LINE_WRAP      = TEXT_FILE_PRESENTER_SECTION + \".\" + \"line_wrap\";\n \t/** Default wrap value. */\n \tpublic static final boolean DEFAULT_LINE_WRAP                  = false;\n \t/** Whether to show line numbers. */\n \tpublic static final String  TEXT_FILE_PRESENTER_LINE_NUMBERS   = TEXT_FILE_PRESENTER_SECTION + \".\" + \"line_numbers\";\n \t/** Default line numbers value. */\n \tpublic static final boolean DEFAULT_LINE_NUMBERS               = true;\n    /** Last known file presenter full screen mode. */\n    public static final String  TEXT_FILE_PRESENTER_FULL_SCREEN    = TEXT_FILE_PRESENTER_SECTION + \".\" + \"full_screen\";\n\n    // - Location history ---- -----------------------------------------------\n    // -----------------------------------------------------------------------\n    /** Section containing the visited location in each folder panel. */\n    private static final String RECENT_LOCATIONS_SECTION           = \"recent_locations\";\n    /** A location that was visited within the folder panel. */\n    private static final String  LOCATION                           = \"location\";\n    /** Describes the number of visited locations saved for the folder panel */\n    private static final String LOCATIONS_COUNT                    = \"count\";\n    \n\t// - Last windows variables ----------------------------------------------\n    // -----------------------------------------------------------------------\n    /** Section describing known information about last muCommander windows. */\n    private static final String WINDOWS_SECTION                    = \"windows\";\n    /** Describes the number of windows that were open. */\n    private static final String  WINDOWS_COUNT                      = WINDOWS_SECTION + '.' + \"count\";\n    /** Describes the index of the selected window. */\n    private static final String  WINDOWS_SELECTION                  = WINDOWS_SECTION + '.' + \"selection\";\n    /** Subsection describing information which is specific to a particular window. */\n    private static final String WINDOW                             = \"window\";\n    \n    // - Window variables ----------------------------------------------------\n    // -----------------------------------------------------------------------\n    /** Section describing known general information of muCommander window. */\n    private static final String WINDOW_PROPERTIES_SECTION          = \"window_properties\";\n    /** Describes the window's horizontal position. */\n    private static final String X                                  = \"x\";\n    /** Describes the window's vertical position. */\n    private static final String Y                                  = \"y\";\n    /** Describes the window's width. */\n    private static final String WIDTH                              = \"width\";\n    /** Describes the window's height. */\n    private static final String HEIGHT                             = \"height\";\n    /** Describes the orientation used to split folder panels. */\n    private static final String SPLIT_ORIENTATION                  = \"split_orientation\";\n    /** Vertical split pane orientation. */\n    public static final String  VERTICAL_SPLIT_ORIENTATION         = \"vertical\";\n    /** Horizontal split pane orientation. */\n    public static final String  HORIZONTAL_SPLIT_ORIENTATION       = \"horizontal\";\n    /** Default split pane orientation. */\n    public static final String  DEFAULT_SPLIT_ORIENTATION          = VERTICAL_SPLIT_ORIENTATION;\n    \n    // - Panels variables ----------------------------------------------------\n    // -----------------------------------------------------------------------\n    /** Section describing the dynamic information contained in the folder panels */\n    private static final String  PANELS_SECTION                    = \"panels\";\n    /** Identifier of the left panel. */\n    private static final String  LEFT                              = \"left\";\n    /** Identifier of the right panel. */\n    private static final String  RIGHT                             = \"right\";\n    \n    // - Tree variables ------------------------------------------------------\n    // -----------------------------------------------------------------------\n    /** Subsection describing the tree view CONFIGURATION. */\n    private static final String  TREE_SECTION                      = \"tree\";\n    /** Describes whether the tree is visible */\n    private static final String  TREE_VISIBLE                      = \"visible\";\n    /** Describes the tree's width */\n    private static final String  TREE_WIDTH                        = \"width\";\n    \n    // - File Table variables ------------------------------------------------\n    // -----------------------------------------------------------------------\n    /** Subsection describing the folders view CONFIGURATION. */\n    private static final String  FILE_TABLE_SECTION                = \"file_table\";\n    /** Describes an ascending sort order. */\n    private static final String  SORT_ORDER_ASCENDING               = \"asc\";\n    /** Describes a descending sort order. */\n    public static final String  SORT_ORDER_DESCENDING              = \"desc\";\n    /** Default 'sort order' column for the file table. */\n    public static final String  DEFAULT_SORT_ORDER                 = SORT_ORDER_ASCENDING;\n    /** Name of the 'show column' variable. */\n    private static final String SHOW_COLUMN                        = \"show\";\n    /** Name of the 'column position' variable. */\n    private static final String COLUMN_POSITION                    = \"position\";\n    /** Name of the 'column width' variable. */\n    private static final String COLUMN_WIDTH                       = \"width\";\n    /** Default 'sort by' column for the file table. */\n    public static final String  DEFAULT_SORT_BY                    = \"name\";\n    /** Identifier of the sort section in a file table's CONFIGURATION. */\n    private static final String SORT                               = \"sort\";\n    /** Identifier of the sort criteria in a file table's CONFIGURATION. */\n    private static final String SORT_BY                            = \"by\";\n    /** Identifier of the sort order in a file table's CONFIGURATION. */\n    private static final String SORT_ORDER                         = \"order\";\n    \n    // - Tabs variables ------------------------------------------------------\n    // -----------------------------------------------------------------------\n    /** Subsection describing the tabs CONFIGURATION. */\n    private static final String TABS_SECTION                       = \"tabs\";\n    /** Describes the number of tabs presented in the panel */\n    private static final String TABS_COUNT                         = \"count\";\n    /** Subsection describing information which is specific to a particular tab */\n    private static final String TAB                                = \"tab\";\n    /** Describes the location presented in a tab */\n    private static final String TAB_LOCATION                       = \"location\";\n    /** Describes whether a tab is locked */\n    private static final String TAB_LOCKED                         = \"locked\";\n    /** The index of the selected tab within the folder panel */\n    private static final String SELECTED_TAB                       = \"selection\";\n    /** Describes the title that was set for the tab */\n    private static final String TAB_TITLE                          = \"title\";\n    \n\t/** Cache the screen's size. this value isn't computed during the shutdown process since it cause a deadlock then */\n\tprivate static Dimension screenSize;\n\t\n\tpublic static String getSelectedWindow() {\n\t\treturn WINDOWS_SELECTION;\n\t}\n\n\tpublic static String getWindowsCount() {\n\t\treturn WINDOWS_COUNT;\n\t}\n\n\t/**\n\t * Returns the section of the {@link com.mucommander.ui.main.MainFrame} at a given index\n\t * \n\t * @param index index of MainFrame\n\t * @return the section of the MainFrame at the given index\n\t */\n\tprivate static String getWindowSection(int index) {\n        return WINDOWS_SECTION + \".\" + WINDOW + \"-\" + index; \n    }\n\t\n\t/**\n\t * Returns the section of the properties for the {@link com.mucommander.ui.main.MainFrame} at a given index\n\t * \n\t * @param window index of MainFrame\n\t * @return the section of the properties for the MainFrame at the given index\n\t */\n\tprivate static String getWindowPropertiesSection(int window) {\n\t\treturn getWindowSection(window) + \".\" + WINDOW_PROPERTIES_SECTION;\n\t}\n\n\t/**\n\t * Returns the horizontal position on the screen of the {@link com.mucommander.ui.main.MainFrame} at a given index\n\t * \n\t * @param window index of MainFrame\n\t * @return the horizontal position of the MainFrame at the given index\n\t */\n\tpublic static String getX(int window) {\n    \treturn getWindowPropertiesSection(window) + \".\" + X;\n    }\n    \n\t/**\n\t * Return the vertical position on the screen of the {@link com.mucommander.ui.main.MainFrame} at a given index\n\t * \n\t * @param window index of MainFrame\n\t * @return the vertical position of the MainFrame at the given index\n\t */\n    public static String getY(int window) {\n    \treturn getWindowPropertiesSection(window) + \".\" + Y;\n    }\n    \n    /**\n     * Returns the width of the {@link com.mucommander.ui.main.MainFrame} at a given index\n     * \n     * @param window index of MainFrame\n     * @return the width of the MainFrame at the given index\n     */\n    public static String getWidth(int window) {\n    \treturn getWindowPropertiesSection(window) + \".\" + WIDTH;\n    }\n    \n    /**\n     * Return the height of the {@link com.mucommander.ui.main.MainFrame} in a given index\n     * \n     * @param window index of MainFrame\n     * @return the height of the MainFrame at the given index\n     */\n    public static String getHeight(int window) {\n    \treturn getWindowPropertiesSection(window) + \".\" + HEIGHT;\n    }\n    \n    /**\n     * Returns the orientation used to split folder panels in the {@link com.mucommander.ui.main.MainFrame} at a given index\n     * \n     * @param window index of MainFrame\n     * @return the orientation used to split folder panel in the MainFrame at the given index\n     */\n    public static String getSplitOrientation(int window) {\n    \treturn getWindowPropertiesSection(window) + \".\" + SPLIT_ORIENTATION;\n    }\n\n\t/**\n     * Returns the CONFIGURATION section corresponding to the specified {@link com.mucommander.ui.main.FolderPanel},\n     * left or right one in the {@link com.mucommander.ui.main.MainFrame} at the given index.\n     *\n     * @param window index of MainFrame\n     * @param left true for the left FolderPanel, false for the right one\n     * @return the CONFIGURATION section corresponding to the specified FolderPanel\n     */\n\tprivate static String getFolderPanelSection(int window, boolean left) {\n        return getWindowSection(window) + \".\" + PANELS_SECTION + \".\" + (left?LEFT:RIGHT);\n    }\n\t\n\t/**\n     * Returns the CONFIGURATION section corresponding to the specified {@link com.mucommander.ui.main.tree.FoldersTreePanel},\n     * left or right one in the {@link com.mucommander.ui.main.MainFrame} at the given index.\n     *\n     * @param window index of MainFrame\n     * @param left true for the left FoldersTreePanel, false for the right one\n     * @return the CONFIGURATION section corresponding to the specified FoldersTreePanel\n     */\n\tprivate static String getTreeSection(int window, boolean left) {\n    \treturn getFolderPanelSection(window, left) + \".\" + TREE_SECTION;\n    }\n    \n\t/**\n     * Returns the variable that controls the visibility of the tree view, in the left or right\n     * {@link com.mucommander.ui.main.FolderPanel} at the {@link com.mucommander.ui.main.MainFrame} in the given index.\n     *\n     * @param window index of MainFrame\n     * @param left true for the left FolderPanel, false for the right one\n     * @return the variable that controls the visibility of the tree view in the specified FolderPanel\n     */\n    public static String getTreeVisiblityVariable(int window, boolean left) {\n    \treturn getTreeSection(window, left) + \".\" + TREE_VISIBLE;\n    }\n    \n    /**\n     * Returns the variable that holds the width of the tree view, in the left or right\n     * {@link com.mucommander.ui.main.FolderPanel} at the {@link com.mucommander.ui.main.MainFrame} in the given index.\n     *\n     * @param window index of MainFrame\n     * @param left true for the left FolderPanel, false for the right one\n     * @return the variable that holds the width of the tree view in the specified FolderPanel\n     */\n    public static String getTreeWidthVariable(int window, boolean left) {\n    \treturn getTreeSection(window, left) + \".\" + TREE_WIDTH;\n    }\n\t\n    /**\n     * Returns the CONFIGURATION section corresponding to the specified {@link com.mucommander.ui.main.table.FileTable},\n     * left or right one in the {@link com.mucommander.ui.main.MainFrame} at the given index.\n     *\n     * @param window index of MainFrame\n     * @param left true for the left FileTable, false for the right one\n     * @return the CONFIGURATION section corresponding to the specified FileTable\n     */\n    private static String getFileTableSection(int window, boolean left) {\n        return getFolderPanelSection(window, left) + \".\" + FILE_TABLE_SECTION;\n    }\n    \n    /**\n     * Returns the CONFIGURATION section that describes the sorting of the specified {@link com.mucommander.ui.main.table.FileTable},\n     * left or right one in the {@link com.mucommander.ui.main.MainFrame} at the given index.\n     * \n     * @param window index of MainFrame\n     * @param left true for the left FileTable, false for the right one\n     * @return the CONFIGURATION section that describes the sorting of the specified FileTable\n     */\n    private static String getFileTableSortSection(int window, boolean left) {\n    \treturn getFileTableSection(window, left) + \".\" + SORT;\n    }\n    \n    /**\n     * Returns the variable that controls the sort criteria of the file table, in the left or right\n     * {@link com.mucommander.ui.main.FolderPanel} at the {@link com.mucommander.ui.main.MainFrame} in the given index.\n     *\n     * @param window index of MainFrame\n     * @param left true for the left FolderPanel, false for the right one\n     * @return the variable that controls the sort criteria of the file table in the specified FolderPanel\n     */\n    public static String getFileTableSortByVariable(int window, boolean left) {\n    \treturn getFileTableSortSection(window, left) + \".\" + SORT_BY;\n    }\n    \n    /**\n     * Returns the variable that controls the sort order (ascending\\descending) of the file table, \n     * in the left or right {@link com.mucommander.ui.main.FolderPanel} at the {@link com.mucommander.ui.main.MainFrame} in the given index.\n     *\n     * @param window index of MainFrame\n     * @param left true for the left FolderPanel, false for the right one\n     * @return the variable that controls the sort order of the file table in the specified FolderPanel\n     */\n    public static String getFileTableSortOrderVariable(int window, boolean left) {\n    \treturn getFileTableSortSection(window, left) + \".\" + SORT_ORDER;\n    }\n\n    /**\n     * Returns the CONFIGURATION section corresponding to the specified column in the left or right\n     * {@link com.mucommander.ui.main.table.FileTable} at the {@link com.mucommander.ui.main.MainFrame} in the given index.\n     *\n     * @param window index of MainFrame\n     * @param column column, see {@link Column} for possible values\n     * @param left true for the left FileTable, false for the right one\n     * @return the CONFIGURATION section corresponding to the specified FileTable\n     */\n    private static String getColumnSection(int window, Column column, boolean left) {\n        return getFileTableSection(window, left) + \".\" + column.toString().toLowerCase();\n    }\n\n    /**\n     * Returns the variable that controls the visibility of the specified column, in the left or right\n     * {@link com.mucommander.ui.main.table.FileTable} at the {@link com.mucommander.ui.main.MainFrame} in the given index.\n     *\n     * @param window index of MainFrame\n     * @param column column, see {@link Column} for possible values\n     * @param left true for the left FileTable, false for the right one\n     * @return the variable that controls the visibility of the specified column\n     */\n    public static String getShowColumnVariable(int window, Column column, boolean left) {\n        return getColumnSection(window, column, left) + \".\" + SHOW_COLUMN;\n    }\n\n    /**\n     * Returns the variable that holds the width of the specified column, in the left or right\n     * {@link com.mucommander.ui.main.table.FileTable} at the {@link com.mucommander.ui.main.MainFrame} in the given index.\n     *\n     * @param window index of MainFrame\n     * @param column column, see {@link Column} for possible values\n     * @param left true for the left FileTable, false for the right one\n     * @return the variable that holds the width of the specified column\n     */\n    public static String getColumnWidthVariable(int window, Column column, boolean left) {\n        return getColumnSection(window, column, left) + \".\" + COLUMN_WIDTH;\n    }\n    \n    /**\n     * Returns the variable that holds the position of the specified column, in the left or right\n     * {@link com.mucommander.ui.main.table.FileTable} at the {@link com.mucommander.ui.main.MainFrame} in the given index.\n     *\n     * @param window index of MainFrame\n     * @param column column, see {@link Column} for possible values\n     * @param left true for the left FileTable, false for the right one\n     * @return the variable that holds the position of the specified column\n     */\n    public static String getColumnPositionVariable(int window, Column column, boolean left) {\n        return getColumnSection(window, column, left) + \".\" + COLUMN_POSITION;\n    }\n    \n\n    /**\n     * Returns the CONFIGURATION section corresponding to the specified {@link com.mucommander.ui.main.tabs.FileTableTabs},\n     * left or right one in the {@link com.mucommander.ui.main.MainFrame} at the given index.\n     *\n     * @param window index of MainFrame\n     * @param left true for the left FileTableTabs, false for the right one\n     * @return the CONFIGURATION section corresponding to the specified FileTableTabs\n     */    \n    private static String getTabsSection(int window, boolean left) {\n    \treturn getFolderPanelSection(window, left) + \".\" + TABS_SECTION;\n    }\n    \n    /**\n     * Returns the variable that holds the number of presented tabs, in the left or right\n     * {@link com.mucommander.ui.main.FolderPanel} at the {@link com.mucommander.ui.main.MainFrame} in the given index.\n     *\n     * @param window index of MainFrame\n     * @param left true for the left FolderPanel, false for the right one\n     * @return the variable that holds the number of presented tabs in the specified FolderPanel\n     */\n    public static String getTabsCountVariable(int window, boolean left) {\n    \treturn getTabsSection(window, left) + \".\" + TABS_COUNT;\n    }\n    \n    /**\n     * Returns the variable that holds the index of the selected tab, in the left or right\n     * {@link com.mucommander.ui.main.FolderPanel} at the {@link com.mucommander.ui.main.MainFrame} in the given index.\n     * \n     * @param window index of MainFrame\n     * @param left true for the left FileTableTab, false for the right one\n     * @return the variable that holds the index of the selected tab in the specified FolderPanel\n     */\n    public static String getTabsSelectionVariable(int window, boolean left) {\n    \treturn getTabsSection(window, left) + \".\" + SELECTED_TAB;\n    }\n    \n    /**\n     * Returns the CONFIGURATION section corresponding to the specified {@link com.mucommander.ui.main.tabs.FileTableTab},\n     * left or right one in the {@link com.mucommander.ui.main.MainFrame} at the given index.\n     *\n     * @param window index of MainFrame\n     * @param left true for the left FileTableTab, false for the right one\n     * @return the CONFIGURATION section corresponding to the specified FileTableTab\n     */    \n    private static String getTabSection(int window, boolean left, int index) {\n    \treturn getTabsSection(window, left) + \".\" + TAB + \"-\" + index; \n    }\n    \n    /**\n     * Returns the variable that holds the location presented at the tab in the given index,\n     * in the left or right {@link com.mucommander.ui.main.FolderPanel} at the {@link com.mucommander.ui.main.MainFrame} in the given index.\n     *\n     * @param window index of MainFrame\n     * @param left true for the left FolderPanel, false for the right one\n     * @param index the index of tab at the FolderPanel's tabs \n     * @return the variable that holds the location presented at the tab in the given index in the specified FolderPanel\n     */\n    public static String getTabLocationVariable(int window, boolean left, int index) {\n    \treturn getTabSection(window, left, index) + \".\" + TAB_LOCATION;\n    }\n\n    /**\n     * Returns the variable that holds the title of the tab in the given index,\n     * in the left or right {@link com.mucommander.ui.main.FolderPanel} at the {@link com.mucommander.ui.main.MainFrame} in the given index.\n     *\n     * @param window index of MainFrame\n     * @param left true for the left FolderPanel, false for the right one\n     * @param index the index of tab at the FolderPanel's tabs \n     * @return the variable that holds the title of the tab in the given index in the specified FolderPanel\n     */\n    public static String getTabTitleVariable(int window, boolean left, int index) {\n    \treturn getTabSection(window, left, index) + \".\" + TAB_TITLE;\n    }\n\n    /**\n     * Returns the variable that indicates whether the tab in the given index,\n     * in the left or right {@link com.mucommander.ui.main.FolderPanel} at the {@link com.mucommander.ui.main.MainFrame} in the given index,\n     * is locked\n     *\n     * @param window index of MainFrame\n     * @param left true for the left FolderPanel, false for the right one\n     * @param index the index of tab at the FolderPanel's tabs \n     * @return the variable that indicates whether the tab in the given index in the specified FolderPanel is locked\n     */\n    public static String getTabLockedVariable(int window, boolean left, int index) {\n    \treturn getTabSection(window, left, index) + \".\" + TAB_LOCKED;\n    }\n    \n    /**\n     * Returns the CONFIGURATION section corresponding to the specified {@link com.mucommander.core.GlobalLocationHistory}\n\n     * @return the CONFIGURATION section corresponding to the specified {@link com.mucommander.core.GlobalLocationHistory}\n     */    \n    private static String getRecentLocationsSection() {\n    \treturn RECENT_LOCATIONS_SECTION;\n    }\n    \n    /**\n     * Returns the variable that holds number of the saved visited locations\n     *\n     * @return the variable that holds the number of saved visited locations\n     */\n    public static String getRecentLocationsCountVariable() {\n    \treturn getRecentLocationsSection()  + \".\" + LOCATIONS_COUNT; \n    }\n    \n    /**\n     * Returns the variable that holds a location contained in the global locations history at the given index\n     *\n     * @param index the index of location in the visited location history \n     * @return the variable that holds a location contained in the global locations history at the given index\n     */\n    public static String getRecentLocationVariable(int index) {\n    \treturn getRecentLocationsSection() + \".\" + LOCATION + \"-\" + index;\n    }\n    \n    private static final String ROOT_ELEMENT = \"snapshot\";\n    \n\t// - Instance fields -----------------------------------------------------\n    // -----------------------------------------------------------------------\n    private final Configuration configuration;\n\n\n    /**\n     * Prevents instantiation of this class from outside of this package.\n     */\n    TcSnapshot() {\n    \tconfiguration = new Configuration(TcSnapshotFile.getSnapshotFile(), new VersionedXmlConfigurationReaderFactory(),\n    \t\t\tnew VersionedXmlConfigurationWriterFactory(ROOT_ELEMENT));\n    }\n\n    /**\n     *\n     * @return size of screen\n     */\n    public static Dimension getScreenSize() {\n        if (screenSize != null) {\n            return screenSize;\n        }\n        synchronized (TcSnapshot.class) {\n            if (screenSize == null) {\n                // Getting DefaultToolkit and screen sizes\n                try {\n                    screenSize = Toolkit.getDefaultToolkit().getScreenSize();\n                } catch (HeadlessException e) {\n                    getLogger().debug(\"Could not fetch screen size: \" + e.getMessage());\n                }\n            }\n        }\n        return screenSize;\n    }\n\n\n    /**\n     * TODO: change this method such that it will return a more specific API\n     */\n    Configuration getConfiguration() {\n    \treturn configuration;\n    }\n    \n // - Configuration reading / writing -------------------------------------\n    // -----------------------------------------------------------------------\n    /**\n     * Loads the muCommander CONFIGURATION.\n     * Here, we don't try to convert preferences that were changed between muCommander versions,\n     * as those are 'dynamic' preferences and as such can be ignored when the user installs new version.\n     * \n     * @throws IOException            if an I/O error occurs.\n     * @throws ConfigurationException if a CONFIGURATION related error occurs.\n     */\n    void read() throws IOException, ConfigurationException {\n        VersionedXmlConfigurationReader reader = new VersionedXmlConfigurationReader();\n        configuration.read(reader);\n    }\n\n    /**\n     * Saves the muCommander CONFIGURATION.\n     * @throws IOException            if an I/O error occurs.\n     * @throws ConfigurationException if a CONFIGURATION related error occurs.\n     */\n    void write() throws IOException, ConfigurationException {\n    \t//Clear the configuration before saving to drop preferences which are unused anymore\n    \tconfiguration.clear();\n    \t\n    \t// Get opened main frames list\n    \tList<MainFrame> mainFrames = WindowManager.getMainFrames();    \t\n    \t\n    \t// Save windows count\n    \tint nbMainFrames = mainFrames.size();\n    \tconfiguration.setVariable(WINDOWS_COUNT, nbMainFrames);\n    \t\n    \t// Save the index of the selected window\n    \tint indexOfSelectedWindow = WindowManager.getCurrentWindowIndex();\n    \tconfiguration.setVariable(WINDOWS_SELECTION, indexOfSelectedWindow);\n    \t\n    \t// Save attributes for each window\n    \tfor (int i=0; i < nbMainFrames; ++i)\n    \t\tsetFrameAttributes(mainFrames.get(i), i);\n    \t\n    \tif (getScreenSize() != null) {\n        \tconfiguration.setVariable(TcSnapshot.SCREEN_WIDTH, screenSize.width);\n        \tconfiguration.setVariable(TcSnapshot.SCREEN_HEIGHT, screenSize.height);\n        }\n    \t\n    \tsetGlobalHistory();\n\n    \tsetTextPresenterProperties();\n\n        configuration.write();\n    }\n\n    private void setTextPresenterProperties() {\n//    \tconfiguration.setVariable(MuSnapshot.TEXT_FILE_PRESENTER_FULL_SCREEN, TextViewer.isFullScreen());\n    \tconfiguration.setVariable(TcSnapshot.TEXT_FILE_PRESENTER_LINE_WRAP, TextViewer.isLineWrap());\n    \tconfiguration.setVariable(TcSnapshot.TEXT_FILE_PRESENTER_LINE_NUMBERS, TextViewer.isLineNumbers());\n    }\n\n    private void setFrameAttributes(MainFrame mainFrame, int index) {\n    \t// Save window position, size and screen resolution\n        setWindowAttributes(index, mainFrame);\n        \n        // Save left panel dynamic properties\n        setPanelAttributes(index, true, mainFrame.getLeftPanel());\n\n        // Save right panel dynamic properties\n        setPanelAttributes(index, false, mainFrame.getRightPanel());\n\n    }\n    \n    private void setPanelAttributes(int index, boolean isLeft, FolderPanel panel) {\n    \t// Save tree folders preferences\n        setTreeAttributes(index, isLeft, panel);\n    \t\n        setTableAttributes(index, isLeft, panel.getFileTable());\n        \n        setTabsAttributes(index, isLeft, panel.getTabs());\n    }\n    \n    private void setGlobalHistory() {\n    \tList<FileURL> locations = GlobalLocationHistory.getInstance().getHistory();\n\n    \tconfiguration.setVariable(getRecentLocationsCountVariable(), locations.size());\n\n        int i = 0;\n        for (FileURL url : locations) {\n            configuration.setVariable(getRecentLocationVariable(i++), url.toString());\n        }\n    }\n\n    private void setTabsAttributes(int index, boolean isLeft, FileTableTabs tabs) {\n    \tint tabsCounter = 0;\n    \tIterator<FileTableTab> tabsIterator = tabs.iterator();\n    \t\n    \t// Save tabs locations\n    \twhile (tabsIterator.hasNext()) {\n    \t\tFileTableTab tab = tabsIterator.next();\n    \t\tconfiguration.setVariable(getTabLocationVariable(index, isLeft, tabsCounter), tab.getLocation().toString());\n    \t\tconfiguration.setVariable(getTabLockedVariable(index, isLeft, tabsCounter), tab.isLocked());\n    \t\tconfiguration.setVariable(getTabTitleVariable(index, isLeft, tabsCounter), tab.getTitle());\n    \t\t++tabsCounter;\n    \t}\n\n    \tif (tabsCounter > 0) {\n    \t\t// Save tabs count\n    \t\tconfiguration.setVariable(getTabsCountVariable(index, isLeft), tabsCounter);\n\n    \t\t// Save the index of the selected tab\n    \t\tconfiguration.setVariable(getTabsSelectionVariable(index, isLeft), tabs.getSelectedIndex());\n    \t}\n    }\n    \n    private void setTableAttributes(int index, boolean isLeft, FileTable table) {\n    \t// Saves table sort order.\n    \tconfiguration.setVariable(TcSnapshot.getFileTableSortByVariable(index, isLeft), table.getSortInfo().getCriterion().toString().toLowerCase());\n    \tconfiguration.setVariable(TcSnapshot.getFileTableSortOrderVariable(index, isLeft), table.getSortInfo().getAscendingOrder() ? TcSnapshot.SORT_ORDER_ASCENDING : TcSnapshot.SORT_ORDER_DESCENDING);\n    \t\n    \t// Loop on columns\n\t\tfor (Column c : Column.values()) {\n\t\t\tif (c != Column.NAME) {       // Skip the special name column (always enabled, width automatically calculated)\n\t\t\t\tTcConfigurations.getSnapshot().setVariable(\n\t\t\t\t\t\tTcSnapshot.getShowColumnVariable(index, c, isLeft),\n\t\t\t\t\t\ttable.isColumnEnabled(c)\n\t\t\t\t\t\t);\n\n\t\t\t\tTcConfigurations.getSnapshot().setVariable(\n\t\t\t\t\t\tTcSnapshot.getColumnWidthVariable(index, c, isLeft),\n\t\t\t\t\t\ttable.getColumnWidth(c)\n\t\t\t\t\t\t);\n\t\t\t}\n\n\t\t\tTcConfigurations.getSnapshot().setVariable(\n\t\t\t\t\tTcSnapshot.getColumnPositionVariable(index, c, isLeft),\n\t\t\t\t\ttable.getColumnPosition(c)\n\t\t\t\t\t);\n\t\t}\n    }\n    \n    private void setTreeAttributes(int index, boolean isLeft, FolderPanel panel) {\n    \tconfiguration.setVariable(TcSnapshot.getTreeVisiblityVariable(index, isLeft), panel.isTreeVisible());\n        configuration.setVariable(TcSnapshot.getTreeWidthVariable(index, isLeft), panel.getTreeWidth());\n    }\n    \n    private void setWindowAttributes(int index, MainFrame currentMainFrame) {\n        Rectangle bounds = currentMainFrame.getJFrame().getBounds();\n        configuration.setVariable(getX(index), (int)bounds.getX());\n        configuration.setVariable(getY(index), (int)bounds.getY());\n        configuration.setVariable(getWidth(index), (int)bounds.getWidth());\n        configuration.setVariable(getHeight(index), (int)bounds.getHeight());\n        \n        // Save split pane orientation\n        // Note: the vertical/horizontal terminology used in muCommander is just the opposite of the one used\n        // in JSplitPane which is anti-natural / confusing\n    \tconfiguration.setVariable(getSplitOrientation(index), currentMainFrame.getSplitPane().getOrientation()==JSplitPane.HORIZONTAL_SPLIT? TcSnapshot.VERTICAL_SPLIT_ORIENTATION: TcSnapshot.HORIZONTAL_SPLIT_ORIENTATION);\n    }\n\n    private static Logger getLogger() {\n        if (logger == null) {\n            logger = LoggerFactory.getLogger(TcSnapshot.class);\n        }\n        return logger;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/conf/TcSnapshotFile.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.conf;\n\nimport java.io.FileNotFoundException;\n\n/**\n * \n * @author Arik Hadas\n */\nclass TcSnapshotFile extends TcConfigurationFile {\n\t\n\tprivate static final String DEFAULT_SNAPSHOT_FILE_NAME = \"snapshot.xml\";\n\t\n\tstatic TcSnapshotFile getSnapshotFile() {\n\t\ttry {\n\t\t\treturn new TcSnapshotFile();\n\t\t} catch (FileNotFoundException e) {\n\t\t\t// Not possible exception??\n\t\t\treturn null;\n\t\t}\n\t}\n\t\n\tprivate TcSnapshotFile() throws FileNotFoundException {\n\t\tsuper(null, DEFAULT_SNAPSHOT_FILE_NAME);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/conf/VersionedXmlConfigurationReader.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.conf;\n\nimport com.mucommander.commons.conf.XmlConfigurationReader;\nimport org.xml.sax.Attributes;\nimport org.xml.sax.SAXException;\n\n/**\n * @author Maxence Bernard\n */\npublic class VersionedXmlConfigurationReader extends XmlConfigurationReader {\n\n    /** True until the root element has been parsed */\n    private boolean isRootElement = true;\n\n    /** the version that was used to write the configuration file */\n    private String version;\n\n    \n    /**\n     * Returns the muCommander version that was used to write the configuration file, <code>null</code> if it is unknown.\n     * <p>\n     * Note: the version attribute was introduced in muCommander 0.8.4.\n     *\n     * @return the muCommander version that was used to write the configuration file, <code>null</code> if it is unknown.\n     */\n    public String getVersion() {\n        return version;\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    @Override\n    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {\n        super.startElement(uri, localName, qName, attributes);\n\n        if (isRootElement) {\n            version = attributes.getValue(TcPreferences.VERSION_ATTRIBUTE);\n            isRootElement = false;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/conf/VersionedXmlConfigurationReaderFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.conf;\n\nimport com.mucommander.commons.conf.ConfigurationReader;\nimport com.mucommander.commons.conf.ConfigurationReaderFactory;\n\n/**\n * @author Maxence Bernard\n */\npublic class VersionedXmlConfigurationReaderFactory implements ConfigurationReaderFactory {\n    @Override\n    public ConfigurationReader getReaderInstance() {\n        return new VersionedXmlConfigurationReader();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/conf/VersionedXmlConfigurationWriter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.conf;\n\nimport com.mucommander.RuntimeConstants;\nimport com.mucommander.commons.conf.ConfigurationException;\nimport com.mucommander.commons.conf.XmlConfigurationWriter;\nimport org.xml.sax.SAXException;\nimport org.xml.sax.helpers.AttributesImpl;\n\nimport java.io.Writer;\n\n/**\n * @author Maxence Bernard\n */\nclass VersionedXmlConfigurationWriter extends XmlConfigurationWriter {\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    public VersionedXmlConfigurationWriter(Writer out, String rootElementName) {\n        super(out, rootElementName);\n    }\n\n    @Override\n    public void startConfiguration() throws ConfigurationException {\n        // Version the file.\n        // Note: the version attribute was introduced in muCommander 0.8.4.\n        AttributesImpl attributes;\n\n        attributes = new AttributesImpl();\n\n        attributes.addAttribute(\"\", TcPreferences.VERSION_ATTRIBUTE, TcPreferences.VERSION_ATTRIBUTE, \"string\", RuntimeConstants.VERSION);\n\n        try {out.startElement(\"\", rootElementName, rootElementName, attributes);}\n        catch(SAXException e) {throw new ConfigurationException(e);}\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/conf/VersionedXmlConfigurationWriterFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.conf;\n\nimport com.mucommander.commons.conf.ConfigurationBuilder;\nimport com.mucommander.commons.conf.ConfigurationWriterFactory;\n\nimport java.io.Writer;\n\n/**\n* @author Maxence Bernard\n*/\nclass VersionedXmlConfigurationWriterFactory extends ConfigurationWriterFactory<ConfigurationBuilder> {\n    \n\t/**\n\t * Constructor\n\t * \n\t * @param rootElementName the name of the root element in the XML file\n\t */\n\tpublic VersionedXmlConfigurationWriterFactory(String rootElementName) {\n\t\tsuper(rootElementName);\n\t}\n\t\n\t@Override\n\tpublic ConfigurationBuilder getWriterInstance(Writer out) {\n        return new VersionedXmlConfigurationWriter(out, getRootElementName());\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/conf/package.html",
    "content": "<body>\n  Application-specific implementation of the <code>com.mucommander.commons.conf</code> API.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/core/FolderChangeMonitor.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.core;\n\nimport java.awt.event.WindowEvent;\nimport java.awt.event.WindowListener;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileProtocols;\nimport com.mucommander.commons.file.filter.AbstractFileFilter;\nimport com.mucommander.commons.file.filter.FileFilter;\nimport com.mucommander.commons.file.filter.OrFileFilter;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.ui.event.LocationEvent;\nimport com.mucommander.ui.event.LocationListener;\nimport com.mucommander.ui.main.FolderPanel;\n\n\n/**\n * This file monitors changes in the current folder of a FolderPanel, checking periodically if the current folder's\n * date has changed. If a change has been detected, the FolderPanel will be asked to refresh its current folder.\n * \n * <p>If the MainFrame which contains the monitored FolderPanel becomes inactive (lies in the background), monitoring\n * on will be not happen until the MainFrame becomes active again.\n *\n * <p>Implementation note: the monitoring is done in one single thread for all folders, each folder being monitored\n * one after another. Current folder refreshes are performed in a separate thread.\n *\n * @author Maxence Bernard\n * @see <a href=\"http://trac.mucommander.com/wiki/FolderAutoRefresh\">FolderAutoRefresh wiki entry</a>\n */\npublic class FolderChangeMonitor implements Runnable, WindowListener, LocationListener {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(FolderChangeMonitor.class);\n\n    /**\n     * If not null then refresh folder that contains this files\n     */\n    private static final List<String> forceRefreshFilePath = new ArrayList<>();\n\n    /** Thread in which the actual monitoring is performed */\n    private static Thread monitorThread;\n\n    /** FolderChangeMonitor instances */\n    private static final List<FolderChangeMonitor> instances;\n\n    private static final OrFileFilter disableAutoRefreshFilter = new OrFileFilter();\n\n    /** Milliseconds period between checks to current folder's date */\n    private static final long checkPeriod;\n\n    /** Delay in milliseconds before folder date check after a folder has been refreshed */\n    private static final long waitAfterRefresh;\n\n    /** If folder change check took an average of N milliseconds, thread will wait at least N*WAIT_MULTIPLIER before next check */\n    private final static int WAIT_MULTIPLIER = 50;\n\n    /** Granularity of the thread check (number of milliseconds to sleep before next loop) */\n    private final static int TICK = 300;\n\n    /** Folder panel we are monitoring */\n    private final FolderPanel folderPanel;\n\n    /** Current file table's folder */\n    private AbstractFile currentFolder;\n\n    /** True when the current folder is currently being changed */\n    private boolean folderChanging;\n\n    /** Current folder's date */\n    private long currentFolderDate;\n\n    /** Folder check/refresh while be skipped while this field is set to <code>true</code> */ \n    private boolean paused;\n\n    /** Number of milliseconds to wait before next folder check */\n    private long waitBeforeCheckTime;\n\t\n    /** Timestamp of the last folder change check */\n    private long lastCheckTimestamp;\n\n    /** Total time spent checking for folder changes in current folder */\n    private long totalCheckTime = 0;\n\t\n    /** Number of checks in current folder */\n    private int nbSamples = 0;\n\n\n    static {\n        instances = Collections.synchronizedList(new ArrayList<>());\n\n        // Retrieve configuration values\n        checkPeriod = TcConfigurations.getPreferences().getVariable(TcPreference.REFRESH_CHECK_PERIOD, TcPreferences.DEFAULT_REFRESH_CHECK_PERIOD);\n        waitAfterRefresh = TcConfigurations.getPreferences().getVariable(TcPreference.WAIT_AFTER_REFRESH, TcPreferences.DEFAULT_WAIT_AFTER_REFRESH);\n\n        disableAutoRefreshFilter.addFileFilter(new AbstractFileFilter() {\n            public boolean accept(AbstractFile file) {\n                return file.getURL().getScheme().equals(FileProtocols.S3);\n            }\n        });\n    }\n\n\n    /**\n     * Adds the given {@link FileFilter} to the list of filters that match folders for which auto-refresh is disabled.\n     * One use case for disabling auto-refresh is for protocols that involve a cost ($$$) when looking for changes\n     * or refreshing the folder. This is the case for Amazon S3 for which auto-refresh is disabled by default.\n     *\n     * @param filter matches folders for which auto-refresh will be disabled\n     */\n    public static void addDisableAutoRefreshFilter(FileFilter filter) {\n        disableAutoRefreshFilter.addFileFilter(filter);\n    }\n\n    public FolderChangeMonitor(FolderPanel folderPanel) {\n        this.folderPanel = folderPanel;\n\n        // Listen to folder changes to know when a folder is being / has been changed\n        folderPanel.getLocationManager().addLocationListener(this);\n\n        this.currentFolder = folderPanel.getCurrentFolder();\n        this.currentFolderDate = currentFolder.getLastModifiedDate();\n\n        // Folder contents is up-to-date let's wait before checking it for changes\n        this.lastCheckTimestamp = System.currentTimeMillis();\n        this.waitBeforeCheckTime = waitAfterRefresh;\n\t\t\n        folderPanel.getMainFrame().getJFrame().addWindowListener(this);\n\n        instances.add(this);\n\t\t\n        // create and start the monitor thread on first FolderChangeMonitor instance\n        if (monitorThread == null && checkPeriod >= 0) {\n            monitorThread = new Thread(this, getClass().getName());\n            monitorThread.setDaemon(true);\n            monitorThread.start();\n        }\n    }\n\n\n    public void run() {\n        // TODO: it would be more efficient to use a wait/notify scheme rather than sleeping. \n        // It would also allow folders to be checked immediately upon certain conditions such as a window becoming activated.\n\n        int needToClearRefreshQueueCounter = 0;\n        while (monitorThread != null) {\n            // Sleep for a while\n            try {\n                Thread.sleep(TICK);\n            } catch(InterruptedException ignore) {}\n\t\t\t\n            // Loop on instances\n            try {\n                for (FolderChangeMonitor instance : instances) {\n                    checkForMonitor(instance);\n                }\n                // clean up the refresh queue if it doesn't empty a \"long\" time\n                if (needToClearRefreshQueueCounter > 10) {\n                    needToClearRefreshQueueCounter = 0;\n                    synchronized (forceRefreshFilePath) {\n                        forceRefreshFilePath.clear();\n                    }\n                } else if (!forceRefreshFilePath.isEmpty()) {\n                    needToClearRefreshQueueCounter++;\n                }\n            } catch (Throwable t) {\n                LOGGER.error(\"Execution error\", t);\n            }\n        }\n    }\n\n\n    private void checkForMonitor(FolderChangeMonitor monitor) {\n        // Check for changes in current folder and refresh it only if :\n        // - MainFrame is in the foreground\n        // - monitor is not paused\n        // - current folder is not being changed\n        if (!monitor.folderPanel.getMainFrame().isForegroundActive() || monitor.folderChanging || monitor.paused) {\n            return;\n        }\n        if (disableAutoRefreshFilter.match(monitor.currentFolder)) {\n            monitor.lastCheckTimestamp = System.currentTimeMillis();\n            monitor.waitBeforeCheckTime = checkPeriod;\n            return;\n        }\n        // By checking FolderPanel.getLastFolderChangeTime(), we ensure that we don't check right after\n        // the folder has been refreshed.\n        if (System.currentTimeMillis() - Math.max(monitor.lastCheckTimestamp, monitor.folderPanel.getLastFolderChangeTime()) > monitor.waitBeforeCheckTime) {\n            // Checks folder contents and refreshes view if necessary\n            boolean folderRefreshed = monitor.checkAndRefresh();\n            monitor.lastCheckTimestamp = System.currentTimeMillis();\n\n            // If folder change check took an average of N milliseconds, we will wait at least N*WAIT_MULTIPLIER before next check\n            monitor.waitBeforeCheckTime = calcWaitBeforeCheckTime(monitor, folderRefreshed);\n        }\n    }\n\n    private long calcWaitBeforeCheckTime(FolderChangeMonitor monitor, boolean folderRefreshed) {\n        if (monitor.nbSamples == 0) {\n            return checkPeriod;\n        } else {\n            long refreshPeriod = folderRefreshed ? waitAfterRefresh : checkPeriod;\n            long perSamplePeriod = (long) (WAIT_MULTIPLIER * (monitor.totalCheckTime / (float) monitor.nbSamples));\n            return Math.max(refreshPeriod, perSamplePeriod);\n        }\n    }\n\n\n    /**\n     * Stops monitoring (stops monitoring thread).\n     */\n    public void stop() {\n        monitorThread = null;\n    }\n\n\n    /**\n     * Suspends or resumes this monitor.\n     *\n     * @param paused true to suspend, false to resume\n     */\n    public void setPaused(boolean paused) {\n        // Note: this method should *not* be synchronized as it would potentially lock while the folder is being\n        // checked/refreshed\n        this.paused = paused;\n\n        // Check folder for changes immediately as setPaused(false) is often called after a FileJob\n        if (!paused) {\n            this.waitBeforeCheckTime = 0;\n        }\n    }\n\t\n\t\n    /**\n     * Forces this monitor to update current folder information. This method should be called when a folder has been\n     * manually refreshed, so that this monitor doesn't detect changes and try to refresh the table again.\n     *\n     * @param folder the new current folder\n     */\n    private void updateFolderInfo(AbstractFile folder) {\n        this.currentFolder = folder;\n        this.currentFolderDate = currentFolder.getLastModifiedDate();\n\n        // Reset time average\n        totalCheckTime = 0;\n        nbSamples = 0;\n    }\n\t\n\t\n    /**\n     * Checks if current file table's folder has changed and if it hasn't, checks if current folder's date has changed\n     * and if it has, refresh the file table.\n     *\n     * @return <code>true</code> if the folder was refreshed.\n     */\n    private synchronized boolean checkAndRefresh() {\n//        if (disableAutoRefreshFilter.match(currentFolder)) {\n//            return false;\n//        }\n\n        // Update time average next loop\n        long timeStamp = System.currentTimeMillis();\n\t\t\n        // Check folder's date\n        long date = currentFolder.getLastModifiedDate();\n\n        totalCheckTime += System.currentTimeMillis() - timeStamp;\n        nbSamples++;\n\t\t\n        // Has date changed ?\n        // Note that date will be 0 if the folder is no longer available, and thus yield a refresh: this is exactly\n        // what we want (the folder will be changed to a 'workable' folder).\n        boolean result = false;\n        if (date != currentFolderDate) {\n            LOGGER.debug(\"{} ({}) Detected changes in current folder, refreshing table!\", this, currentFolder.getName());\n\t\t\t\n            // Try and refresh current folder in a separate thread as to not lock monitor thread\n            folderPanel.tryRefreshCurrentFolder();\n            result = true;\n        }\n\n        if (!forceRefreshFilePath.isEmpty()) {\n            synchronized (forceRefreshFilePath) {\n                String folderPath = currentFolder.getAbsolutePath();\n                for (String path : forceRefreshFilePath) {\n                    if (path.startsWith(folderPath)) {\n                        forceRefreshFilePath.remove(path);\n                        folderPanel.tryRefreshCurrentFolder();\n                        result = true;\n                        break;\n                    }\n                }\n            }\n        }\n\t\t\n        return result;\n    }\n\n\n    @Override\n    public void locationChanging(LocationEvent locationEvent) {\n        folderChanging = true;\n    }\n\n    @Override\n    public void locationChanged(LocationEvent locationEvent) {\n        // Update new current folder info\n        updateFolderInfo(locationEvent.getFolderPanel().getCurrentFolder());\n\n        folderChanging = false;\n    }\n\n    @Override\n    public void locationCancelled(LocationEvent locationEvent) {\n        folderChanging = false;\n    }\n\n    @Override\n    public void locationFailed(LocationEvent locationEvent) {\n        folderChanging = false;\n    }\n\n\n    @Override\n    public void windowActivated(WindowEvent e) {}\n\n    @Override\n    public void windowDeactivated(WindowEvent e) {}\n\n    @Override\n    public void windowIconified(WindowEvent e) {}\n\n    @Override\n    public void windowDeiconified(WindowEvent e) {}\n\n    @Override\n    public void windowOpened(WindowEvent e) {}\n\n    @Override\n    public void windowClosing(WindowEvent e) {}\n\n    @Override\n    public void windowClosed(WindowEvent e) {\n        // Remove the MainFrame from the list of monitored instances\n        instances.remove(this);\n        LOGGER.debug(\"nbInstances= {}\", instances.size());\n    }\n\n    /**\n     * Force to refresh folder that contains this file\n     * @param path path to file\n     */\n    public static void addFileToRefresh(String path) {\n        synchronized (forceRefreshFilePath) {\n            forceRefreshFilePath.add(path);\n        }\n    }\n\t\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/core/GlobalLocationHistory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.core;\n\nimport java.net.MalformedURLException;\nimport java.util.ArrayList;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.conf.Configuration;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcSnapshot;\nimport com.mucommander.ui.event.LocationAdapter;\nimport com.mucommander.ui.event.LocationEvent;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.main.MainFrame;\n\n/**\n * This class tracks location changing events, in every {@link FolderPanel} or {@link MainFrame},\n * and saves those locations, thus creating a global location history tracking.\n * \n * <p>FolderHistory also keeps track of the last visited location so that it can be saved and recalled the next time the\n * application is started.\n * \n * @author Arik Hadas\n */\npublic class GlobalLocationHistory extends LocationAdapter {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(GlobalLocationHistory.class);\n\t\n\t/** Singleton instance */\n\tprivate static GlobalLocationHistory instance;\n\t\n\t/** Locations that were accessed */\n\tprivate final Set<FileURL> history = new LinkedHashSet<>();\n\t\n\t/** Maximum number of location that would be saved */\n\tprivate static final int MAX_CAPACITY = 100;\n\t\n\t/**\n\t * Private Constructor\n\t */\n\tprivate GlobalLocationHistory() {\n\t\tConfiguration snapshot = TcConfigurations.getSnapshot();\n\n\t\t// Restore the global history from last init\n\t\tint nbLocations = snapshot.getIntegerVariable(TcSnapshot.getRecentLocationsCountVariable());\n    \tfor (int i = 0; i < nbLocations; ++i) {\n    \t\tString filePath = snapshot.getVariable(TcSnapshot.getRecentLocationVariable(i));\n\t\t\ttry {\n\t\t\t\thistory.add(FileURL.getFileURL(filePath));\n\t\t\t} catch (MalformedURLException e) {\n\t\t\t\tLOGGER.debug(\"Got invalid URL from the snapshot file: {}\", filePath, e);\n\t\t\t} catch (Throwable t) {\n\t\t\t\tLOGGER.debug(\"Can't process URL from the snapshot file: {}\", filePath, t);\n\t\t\t}\n    \t}\n\t}\n\n\t/**\n\t * Returns Singleton instance of this class\n\t * \n\t * @return Singleton instance of this class\n\t */\n\tpublic static GlobalLocationHistory getInstance() {\n\t\tif (instance == null) {\n\t\t\tinstance = new GlobalLocationHistory();\n\t\t}\n\t\treturn instance;\n\t}\n\n\t/**\n\t * Returns all the tracked locations as a {@link Set} of {@link AbstractFile}\n\t * The locations are turned in a reverse insertion-order, that means that the last accessed location would be the first  \n\t * \n\t * @return all the tracked locations\n\t */\n\tpublic List<FileURL> getHistory() {\n\t\treturn new ArrayList<>(history);\n\t}\n\t\n\t/**\n\t * Returns true if the global history contains the given FileURL\n\t */\n\tboolean historyContains(FileURL folderURL) {\n\t\treturn history.contains(folderURL);\n\t}\n\t\n\t///////////////////////\n\t/// LocationAdapter ///\n\t///////////////////////\n\t\n\tpublic void locationChanged(LocationEvent locationEvent) {\n\t\tFileURL file = locationEvent.getFolderURL();\n\t\t\n\t\t// remove the new location from the history as it should be put \n\t\t// at the end of the list to preserve the insertion order of the history\n\t\tboolean alreadyExists = history.remove(file);\n\t\t\n\t\t// ensure that we won't cross the maximum number of saved locations\n\t\tif (!alreadyExists && MAX_CAPACITY == history.size()) {\n\t\t\thistory.remove(history.iterator().next());\n\t\t}\n\t\t\n\t\t// add the location as last one in the history\n\t\thistory.add(locationEvent.getFolderURL());\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/core/LocalLocationHistory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.core;\n\nimport java.util.List;\nimport java.util.Vector;\n\nimport lombok.Getter;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.FileProtocols;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport com.mucommander.ui.main.FolderPanel;\n\n/**\n * This class maintains a history of visited locations for a given tab, and provides methods to go back and go forward\n * in the history.\n *\n * <p>There is a limit to the number of locations the history can contain, defined by {@link #HISTORY_CAPACITY}.\n *\n * @author Maxence Bernard, Arik Hadas\n */\npublic class LocalLocationHistory {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(LocalLocationHistory.class);\n\n\t/** Maximum number of elements the folder history can contain */\n\tprivate final static int HISTORY_CAPACITY = 100;\n\n\t/** List of visited locations, ordered by last visit date */\n\tprivate final List<FileURL> history = new Vector<>(HISTORY_CAPACITY+1);\n\n\t/** Index of current folder in history */\n\tprivate int historyIndex = -1;\n\n\t/** FolderPanel which is being monitored */\n\tprivate final FolderPanel folderPanel;\n\n\t/** Last folder which can be recalled on next startup\n     * -- GETTER --\n     *  Returns the last visited folder that can be saved when the application terminates, and recalled next time\n     *  the application is started.\n     *  <p>The returned folder will NOT be a folder on a remote filesystem which would be likely not to be reachable next\n\t *  time the app is started, or a removable media drive (cd/dvd/floppy) under Windows, which would trigger a nasty\n\t *  'drive not ready' popup dialog if the drive is not available or the media has changed.\n     */\n\t@Getter\n    private String lastRecallableFolder;\n\n\n\t/**\n\t * Creates a new FolderHistory instance which will keep track of visited folders in the given FolderPanel.\n\t *\n\t * @param folderPanel folder panel\n\t */\n\tpublic LocalLocationHistory(FolderPanel folderPanel) {\n\t\tthis.folderPanel = folderPanel;\n\t}\n\n\t/**\n\t * Adds the specified folder to history. The folder won't be added if the previous folder is the same.\n\t *\n\t * <p>This method is called by FolderPanel each time a folder is changed.\n\t *\n\t * @param folderURL url to add\n\t */\n\tpublic void tryToAddToHistory(FileURL folderURL) {\n\t\t// Do not add folder to history if new current folder is the same as previous folder\n\t\tif (historyIndex < 0 || !folderURL.equals(history.get(historyIndex), false, false)) {\n\t\t\taddToHistory(folderURL);\n\t\t}\n\n\t\t// Save last recallable folder on startup, only if :\n\t\t//  - it is a directory on a local filesytem\n\t\t//  - it doesn't look like a removable media drive (cd/dvd/floppy), especially in order to prevent\n\t\t// Java from triggering that dreaded 'Drive not ready' popup.\n\t\tLOGGER.trace(\"folder=\"+folderURL);\n\t\tif (folderURL.getScheme().equals(FileProtocols.FILE)) {\n\t\t\tAbstractFile folder = FileFactory.getFile(folderURL);\n\t\t\tif (folder instanceof LocalFile && folder.isDirectory() && !((LocalFile)folder.getRoot()).guessRemovableDrive()) {\n\t\t\t\tthis.lastRecallableFolder = folder.getAbsolutePath();\n\t\t\t\tLOGGER.trace(\"lastRecallableFolder= \"+lastRecallableFolder);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void addToHistory(FileURL folderURL) {\n\t\tint historySize = history.size();\n\n\t\thistoryIndex++;\n\n\t\t// Delete 'forward' history items if any\n\t\tif (historySize > historyIndex) {\n\t\t\thistory.subList(historyIndex, historySize).clear();\n\t\t}\n\n\t\t// If capacity is reached, remove first folder\n\t\tif (history.size() >= HISTORY_CAPACITY) {\n\t\t\thistory.removeFirst();\n\t\t\thistoryIndex--;\n\t\t}\n\n\t\t// Add previous folder to history\n\t\thistory.add(folderURL);\n\t}\n\n\t/**\n\t * Changes current folder to be the previous one in folder history.\n\t * Does nothing if there is no previous folder in history. \n\t */\n\tpublic synchronized void goBack() {\n\t\tif (historyIndex == 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tfolderPanel.tryChangeCurrentFolder(history.get(--historyIndex));\n\t}\n\n\t/**\n\t * Changes current folder to be the next one in folder history.\n\t * Does nothing if there is no next folder in history. \n\t */\n\tpublic synchronized void goForward() {\n\t\tif (historyIndex == history.size() - 1) {\n\t\t\treturn;\n\t\t}\n\n\t\tfolderPanel.tryChangeCurrentFolder(history.get(++historyIndex));\n\t}\n\n\n\t/**\n\t * Returns <code>true</code> if there is at least one folder 'back' in the history.\n\t * @return  <code>true</code> if there is at least one folder 'back' in the history.\n\t */\n\tpublic boolean hasBackFolder() {\n\t\treturn historyIndex>0;\n\t}\n\n\t/**\n\t * Returns <code>true</code> if there is at least one folder 'forward' in the history.\n\t * @return <code>true</code> if there is at least one folder 'forward' in the history.\n\t */\n\tpublic boolean hasForwardFolder() {\n\t\treturn historyIndex!=history.size()-1;\n\t}\n\n\n\t/**\n\t * Returns a list of 'back' folders, most recently visited folder first. The returned array may be empty if there\n\t * currently isn't any 'back' folder in history, but may never be null.\n\t *\n\t * @return a array of 'back' folders, most recently visited folder first\n\t */\n\tpublic FileURL[] getBackFolders() {\n\t\tif (!hasBackFolder()) {\n\t\t\treturn new FileURL[0];\n\t\t}\n\n\t\tint backLen = historyIndex;\n\t\tFileURL[] urls = new FileURL[backLen];\n\n\t\tint cur = 0;\n\t\tfor (int i = historyIndex-1; i >= 0; i--) {\n\t\t\turls[cur++] = history.get(i);\n\t\t}\n\n\t\treturn urls;\n\t}\n\n\n\t/**\n\t * Returns a list of 'forward' folders, most recently visited folder first. The returned array may be empty if there\n\t * currently isn't any 'forward' folder in history, but may never be null.\n\t *\n\t * @return a array of 'forward' folders, most recently visited folder first\n\t */\n\tpublic FileURL[] getForwardFolders() {\n\t\tif (!hasForwardFolder()) {\n\t\t\treturn new FileURL[0];\n\t\t}\n\n\t\tint historySize = history.size();\n\t\tFileURL[] urls = new FileURL[historySize-historyIndex-1];\n\n\t\tint cur = 0;\n\t\tfor(int i=historyIndex+1; i<historySize; i++) {\n\t\t\turls[cur++] = history.get(i);\n\t\t}\n\n\t\treturn urls;\n\t}\n\n\t/**\n\t * Returns true if the folder history contains the given FileURL, either as a back or forward folder, or as the\n\t * current folder.\n\t *\n\t * @return true if history contains this url\n\t */\n\tpublic boolean historyContains(FileURL folderURL) {\n\t\treturn history.contains(folderURL);\n\t}\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/core/LocationChanger.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.core;\n\nimport com.mucommander.auth.CredentialsManager;\nimport com.mucommander.auth.CredentialsMapping;\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.file.impl.CachedFile;\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.ui.dialog.InformationDialog;\nimport com.mucommander.ui.dialog.QuestionDialog;\nimport com.mucommander.ui.dialog.auth.AuthDialog;\nimport com.mucommander.ui.dialog.file.DownloadDialog;\nimport com.mucommander.ui.event.LocationManager;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.utils.text.Translator;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.awt.*;\nimport java.net.MalformedURLException;\n\n/**\n * \n * @author Arik Hadas, Maxence Bernard\n */\npublic class LocationChanger {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(LocationChanger.class);\n\n    /** Last time folder has changed */\n    private long lastFolderChangeTime;\n\n\tprivate ChangeFolderThread changeFolderThread;\n\n\tprivate final GlobalLocationHistory globalHistory = GlobalLocationHistory.getInstance();\n\n\t/** The lock object used to prevent simultaneous folder change operations */\n\tprivate final Object FOLDER_CHANGE_LOCK = new Object();\n\n\tprivate final static int CANCEL_ACTION = 0;\n\tprivate final static int BROWSE_ACTION = 1;\n\tprivate final static int DOWNLOAD_ACTION = 2;\n\n\tprivate final static String CANCEL_TEXT = Translator.get(\"cancel\");\n\tprivate final static String BROWSE_TEXT = Translator.get(\"browse\");\n\tprivate final static String DOWNLOAD_TEXT = Translator.get(\"download\");\n\t\n\tprivate final MainFrame mainFrame;\n\tprivate final FolderPanel folderPanel;\n\tprivate final LocationManager locationManager;\n\t\n\tpublic LocationChanger(MainFrame mainFrame, FolderPanel folderPanel, LocationManager locationManager) {\n\t\tthis.mainFrame = mainFrame;\n\t\tthis.folderPanel = folderPanel;\n\t\tthis.locationManager = locationManager;\n\t}\n\n\t/**\n\t * This method is triggered internally (i.e not by user request) to change the current\n\t * folder to the given folder\n\t *\n\t * @param folderURL the URL of the folder to switch to\n\t * @param callback the {@link Runnable#run()} method will be called when folder has changed\n\t */\n\tpublic void tryChangeCurrentFolderInternal(final FileURL folderURL, Runnable callback) {\n\t\tmainFrame.setNoEventsMode(true);\n        showWaitCursor();\n\n        Thread setLocationThread = new Thread(() -> {\n            AbstractFile folder = getWorkableLocation(folderURL);\n            try {\n                locationManager.setCurrentFolder(folder, null, true);\n            } finally {\n                mainFrame.setNoEventsMode(false);\n                restoreDefaultCursor();\n                // Notify callback that the folder has been set\n                callback.run();\n            }\n        });\n\n    \tif (EventQueue.isDispatchThread()) {\n\t\t\tsetLocationThread.start();\n\t\t} else {\n\t\t\tsetLocationThread.run();\n\t\t}\n\t}\n\n    private void restoreDefaultCursor() {\n        mainFrame.getJFrame().setCursor(Cursor.getDefaultCursor());\n    }\n\n    private void showWaitCursor() {\n        mainFrame.getJFrame().setCursor(new Cursor(Cursor.WAIT_CURSOR));\n    }\n\n    /**\n\t * Return a workable location according the following logic:\n\t * - If the given folder exists, return it\n\t * - if the given folder is local file, find workable location\n\t *   according to the logic used for inaccessible local files\n\t * - Otherwise, return the non-exist remote location\n\t */\n\tprivate AbstractFile getWorkableLocation(FileURL folderURL) {\n\t\tAbstractFile folder = FileFactory.getFile(folderURL);\n\t\tif (folder != null && folder.exists()) {\n\t\t\treturn folder;\n\t\t}\n\t\t\n\t\tif (folder == null) {\n\t\t\tfolder = new NullableFile(folderURL);\n\t\t}\n\t\t\n\t\treturn FileProtocols.FILE.equals(folderURL.getScheme()) ? getWorkableFolder(folder) : folder;\n\t}\n\n\t/**\n\t * Tries to change the current folder to the new specified one and notifies the user in case of a problem.\n\t *\n\t * <p>This method spawns a separate thread that takes care of the actual folder change and returns it.\n\t * It does nothing and returns <code>null</code> if another folder change is already underway.\n\t *\n\t * <p>\n\t * This method is <b>not</b> I/O-bound and returns immediately, without any chance of locking the calling thread.\n\t *\n\t * @param folder the folder to be made current folder\n\t * @param changeLockedTab - flag that indicates whether to change the presented folder in the currently selected tab although it's locked\n\t * @return the thread that performs the actual folder change, null if another folder change is already underway\n\t */\n\tpublic ChangeFolderThread tryChangeCurrentFolder(AbstractFile folder, boolean changeLockedTab) {\n\t\t// TODO branch setBranchView(false);\n\t\treturn tryChangeCurrentFolder(folder, null, false, changeLockedTab);\n\t}\n\n\t/**\n\t * Tries to change current folder to the new specified one, and selects the given file after the folder has been\n\t * changed. The user is notified by a dialog if the folder could not be changed.\n\t *\n\t * <p>If the current folder could not be changed to the requested folder and <code>findWorkableFolder</code> is\n\t * <code>true</code>, the current folder will be changed to the first existing parent of the request folder if there\n\t * is one, to the first existing local volume otherwise. In the unlikely event that no local volume is workable,\n\t * the user will be notified that the folder could not be changed.\n\t *\n\t * <p>This method spawns a separate thread that takes care of the actual folder change and returns it.\n\t * It does nothing and returns <code>null</code> if another folder change is already underway.\n\t *\n\t * <p>\n\t * This method is <b>not</b> I/O-bound and returns immediately, without any chance of locking the calling thread.\n\t *\n\t * @param folder the folder to be made current folder\n\t * @param selectThisFileAfter the file to be selected after the folder has been changed (if it exists in the folder), can be null in which case FileTable rules will be used to select current file\n\t * @param changeLockedTab - flag that indicates whether to change the presented folder in the currently selected tab although it's locked\n\t * @return the thread that performs the actual folder change, null if another folder change is already underway  \n\t */\n\tpublic ChangeFolderThread tryChangeCurrentFolder(AbstractFile folder, AbstractFile selectThisFileAfter, boolean findWorkableFolder, boolean changeLockedTab) {\n\t\tLOGGER.debug(\"folder= {} selectThisFileAfter={}\", folder, selectThisFileAfter);\n\n\t\tsynchronized(FOLDER_CHANGE_LOCK) {\n\t\t\t// Make sure a folder change is not already taking place. This can happen under rare but normal\n\t\t\t// circumstances, if this method is called before the folder change thread has had the time to call\n\t\t\t// MainFrame#setNoEventsMode.\n\t\t\tif (isChangeFolderThreadAlreadyTakingPlace()) return null;\n\n\t\t\t// Important: the ChangeFolderThread instance must be kept in a local variable (as opposed to the\n\t\t\t// changeFolderThread field only) before being returned. The reason for this is that ChangeFolderThread\n\t\t\t// changes the changeFolderThread field to null when finished, and it may do so before this method has\n\t\t\t// returned (I've seen this happening). Relying solely on the changeFolderThread field could thus cause\n\t\t\t// a null value to be returned, which is particularly problematic during startup (would cause an NPE).\n\t\t\tChangeFolderThread thread = new ChangeFolderThread(folder, findWorkableFolder, changeLockedTab);\n\n\t\t\tif (selectThisFileAfter != null) {\n\t\t\t\tthread.selectThisFileAfter(selectThisFileAfter);\n\t\t\t}\n\t\t\tthread.start();\n\n\t\t\tchangeFolderThread = thread;\n\t\t\treturn thread;\n\t\t}\n\t}\n\n\t/**\n\t * Tries to change the current folder to the specified path and notifies the user in case of a problem.\n\t *\n\t * <p>This method spawns a separate thread that takes care of the actual folder change and returns it.\n\t * It does nothing and returns <code>null</code> if another folder change is already underway or if the given\n\t * path could not be resolved.\n\t *\n\t * <p>\n\t * This method is <b>not</b> I/O-bound and returns immediately, without any chance of locking the calling thread.\n\t *\n\t * @param folderPath path to the new current folder. If this path does not resolve into a file, an error message will be displayed.\n\t * @return the thread that performs the actual folder change, null if another folder change is already underway or if the given path could not be resolved\n\t */\n\tpublic ChangeFolderThread tryChangeCurrentFolder(String folderPath) {\n\t\ttry {\n\t\t\treturn tryChangeCurrentFolder(FileURL.getFileURL(folderPath), null, false);\n\t\t} catch (MalformedURLException e) {\n\t\t\t// FileURL could not be resolved, notify the user that the folder doesn't exist\n\t\t\tshowFolderDoesNotExistDialog();\n\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Tries to change current folder to the new specified URL and notifies the user in case of a problem.\n\t *\n\t * <p>This method spawns a separate thread that takes care of the actual folder change and returns it.\n\t * It does nothing and returns <code>null</code> if another folder change is already underway.\n\t *\n\t * <p>\n\t * This method is <b>not</b> I/O-bound and returns immediately, without any chance of locking the calling thread.\n\t *\n\t * @param folderURL location to the new current folder. If this URL does not resolve into a file, an error message will be displayed.\n\t * @return the thread that performs the actual folder change, null if another folder change is already underway\n\t */\n\tpublic ChangeFolderThread tryChangeCurrentFolder(FileURL folderURL) {\n\t\treturn tryChangeCurrentFolder(folderURL, null, false);\n\t}\n\n\tpublic ChangeFolderThread tryChangeCurrentFolder(FileURL folderURL, boolean changeLockedTab) {\n\t\treturn tryChangeCurrentFolder(folderURL, null, changeLockedTab);\n\t}\n\n\t/**\n\t * Tries to change current folder to the new specified path and notifies the user in case of a problem.\n\t * If not <code>null</code>, the specified {@link com.mucommander.auth.CredentialsMapping} is used to authenticate\n\t * the folder, and added to {@link CredentialsManager} if the folder has been successfully changed.\n\t *\n\t * <p>This method spawns a separate thread that takes care of the actual folder change and returns it.\n\t * It does nothing and returns <code>null</code> if another folder change is already underway.\n\t *\n\t * <p>\n\t * This method is <b>not</b> I/O-bound and returns immediately, without any chance of locking the calling thread.\n\t *\n\t * @param folderURL folder's URL to be made current folder. If this URL does not resolve into an existing file, an error message will be displayed.\n\t * @param credentialsMapping the CredentialsMapping to use for authentication, can be null\n\t * @param changeLockedTab flag that indicates whether to change the presented folder in the currently selected tab although it's locked\n\t * @return the thread that performs the actual folder change, null if another folder change is already underway\n\t */\n\tpublic ChangeFolderThread tryChangeCurrentFolder(FileURL folderURL, CredentialsMapping credentialsMapping, boolean changeLockedTab) {\n\t\tLOGGER.debug(\"folderURL=\" + folderURL);\n\n\t\tsynchronized(FOLDER_CHANGE_LOCK) {\n\t\t\t// Make sure a folder change is not already taking place. This can happen under rare but normal\n\t\t\t// circumstances, if this method is called before the folder change thread has had the time to call\n\t\t\t// MainFrame#setNoEventsMode.\n\t\t\tif (isChangeFolderThreadAlreadyTakingPlace()) return null;\n\n\t\t\t// Important: the ChangeFolderThread instance must be kept in a local variable (as opposed to the\n\t\t\t// changeFolderThread field only) before being returned. The reason for this is that ChangeFolderThread\n\t\t\t// changes the changeFolderThread field to null when finished, and it may do so before this method has\n\t\t\t// returned (I've seen this happening). Relying solely on the changeFolderThread field could thus cause\n\t\t\t// a null value to be returned, which is particularly problematic during startup (would cause an NPE).\n\t\t\tChangeFolderThread thread = new ChangeFolderThread(folderURL, credentialsMapping, changeLockedTab);\n\t\t\tthread.start();\n\n\t\t\tchangeFolderThread = thread;\n\t\t\treturn thread;\n\t\t}\n\t}\n\n\tprivate boolean isChangeFolderThreadAlreadyTakingPlace() {\n\t\tif (changeFolderThread != null) {\n\t\t\tLOGGER.debug(\"A folder change is already taking place ({}), returning null\", changeFolderThread);\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Shorthand for {@link #tryRefreshCurrentFolder(AbstractFile)} called with no specific file (<code>null</code>)\n\t * to select after the folder has been changed.\n\t *\n\t * @return the thread that performs the actual folder change, null if another folder change is already underway\n\t */\n\tpublic ChangeFolderThread tryRefreshCurrentFolder() {\n\t\treturn tryRefreshCurrentFolder(null);\n\t}\n\n\t/**\n\t * Refreshes the current folder's contents. If the folder is no longer available, the folder will be changed to a\n\t * 'workable' folder (see {@link #tryChangeCurrentFolder(AbstractFile, AbstractFile, boolean, boolean)}.\n\t *\n\t * <p>This method spawns a separate thread that takes care of the actual folder change and returns it.\n\t * It does nothing and returns <code>null</code> if another folder change is already underway.\n\t *\n\t * <p>This method is <b>not</b> I/O-bound and returns immediately, without any chance of locking the calling thread.\n\t *\n\t * @param selectThisFileAfter file to be selected after the folder has been refreshed (if it exists in the folder),\n\t * can be null in which case FileTable rules will be used to select current file\n\t * @return the thread that performs the actual folder change, null if another folder change is already underway\n\t * @see #tryChangeCurrentFolder(AbstractFile, AbstractFile, boolean, boolean)\n\t */\n\tpublic ChangeFolderThread tryRefreshCurrentFolder(AbstractFile selectThisFileAfter) {\n\t\tfolderPanel.getFoldersTreePanel().refreshFolder(locationManager.getCurrentFolder());\n\t\treturn tryChangeCurrentFolder(locationManager.getCurrentFolder(), selectThisFileAfter, true, true);\n\t}\n\t\n\t /**\n     * Changes current folder using the given folder and children files.\n     *\n     * <p>\n     * This method <b>is</b> I/O-bound and locks the calling thread until the folder has been changed. It may under\n     * certain circumstances lock indefinitely, for example when accessing network-based filesystems.\n     *\n     * @param folder folder to be made current folder\n     * @param fileToSelect file to be selected after the folder has been refreshed (if it exists in the folder), can be null in which case FileTable rules will be used to select current file\n     * @param changeLockedTab - flag that indicates whether to change the presented folder in the currently selected tab although it's locked\n     */\n    private void setCurrentFolder(AbstractFile folder, AbstractFile fileToSelect, boolean changeLockedTab) {\n    \t// Update the timestamp right before the folder is set in case FolderChangeMonitor checks the timestamp\n        // while FileTable#setCurrentFolder is being called. \n        lastFolderChangeTime = System.currentTimeMillis();\n        \n    \tlocationManager.setCurrentFolder(folder, fileToSelect, changeLockedTab);\n    }\n\n    /**\n     * Returns the time at which the last folder change completed successfully.\n     *\n     * @return the time at which the last folder change completed successfully.\n     */\n    public long getLastFolderChangeTime() {\n        return lastFolderChangeTime;\n    }\n\n    /**\n     * Returns the thread that is currently changing the current folder, <code>null</code> is the folder is not being\n     * changed.\n     *\n     * @return the thread that is currently changing the current folder, <code>null</code> is the folder is not being\n     * changed\n     */\n    public ChangeFolderThread getChangeFolderThread() {\n        return changeFolderThread;\n    }\n\n\n    /**\n     * Returns <code>true</code> ´if the current folder is currently being changed, <code>false</code> otherwise.\n     *\n     * @return <code>true</code> ´if the current folder is currently being changed, <code>false</code> otherwise\n     */\n    public boolean isFolderChanging() {\n        return changeFolderThread != null;\n    }\n\n\tprivate void showFailedToReadFolderDialog() {\n\t\tInformationDialog.showErrorDialog(mainFrame.getJFrame(),\n                Translator.get(\"table.folder_access_error_title\"),\n                Translator.get(\"failed_to_read_folder\"));\n\t}\n    \n\t/**\n     * Displays a popup dialog informing the user that the requested folder doesn't exist or isn't available.\n     */\n    private void showFolderDoesNotExistDialog() {\n        InformationDialog.showErrorDialog(mainFrame.getJFrame(),\n                Translator.get(\"table.folder_access_error_title\"),\n                Translator.get(\"folder_does_not_exist\"));\n    }\n\n\n    /**\n     * Displays a popup dialog informing the user that the requested folder couldn't be opened.\n     *\n     * @param e the Exception that was caught while changing the folder\n     */\n    private void showAccessErrorDialog(Exception e) {\n        InformationDialog.showErrorDialog(mainFrame.getJFrame(),\n                Translator.get(\"table.folder_access_error_title\"),\n                Translator.get(\"table.folder_access_error\"),\n                e == null ? null : e.getMessage(), e);\n    }\n\n\n    /**\n     * Pops up an {@link AuthDialog authentication dialog} prompting the user to select or enter credentials in order to\n     * be granted the access to the file or folder represented by the given {@link FileURL}.\n     * The <code>AuthDialog</code> instance is returned, allowing to retrieve the credentials that were selected\n     * by the user (if any).\n     *\n     * @param fileURL the file or folder to ask credentials for\n     * @param errorMessage optional (can be null), an error message describing a prior authentication failure\n     * @return the AuthDialog that contains the credentials selected by the user (if any)\n     */\n    private AuthDialog popAuthDialog(FileURL fileURL, boolean authFailed, String errorMessage) {\n        AuthDialog authDialog = new AuthDialog(mainFrame, fileURL, authFailed, errorMessage);\n        authDialog.showDialog();\n        return authDialog;\n    }\n\n\n    /**\n     * Displays a download dialog box where the user can choose where to download the given file or cancel\n     * the operation.\n     *\n     * @param file the file to download\n     */\n    private void showDownloadDialog(AbstractFile file) {\n        FileSet fileSet = new FileSet(locationManager.getCurrentFolder());\n        fileSet.add(file);\n\t\t\n        // Show confirmation/path modification dialog\n        new DownloadDialog(mainFrame, fileSet).showDialog();\n    }\n\n\t/**\n\t * Returns a 'workable' folder as a substitute for the given non-existing folder. This method will return the\n\t * first existing parent if there is one, to the first existing local volume otherwise. In the unlikely event\n\t * that no local volume exists, <code>null</code> will be returned.\n\t *\n\t * @param folder folder for which to find a workable folder\n\t * @return a 'workable' folder for the given non-existing folder, <code>null</code> if there is none.\n\t */\n\tprivate AbstractFile getWorkableFolder(AbstractFile folder) {\n\t\t// Look for an existing parent\n\t\tAbstractFile newFolder = folder;\n\t\tdo {\n\t\t\tnewFolder = newFolder.getParent();\n\t\t\tif (newFolder != null && newFolder.exists()) {\n                return newFolder;\n            }\n\t\t} while (newFolder != null);\n\n\t\t// Fall back to the first existing volume\n\t\tAbstractFile[] localVolumes = LocalFile.getVolumes();\n\t\tfor (AbstractFile volume : localVolumes) {\n\t\t\tif (volume.exists()) {\n                return volume;\n            }\n\t\t}\n\n\t\t// No volume could be found, return null\n\t\treturn null;\n\t}\n\t\n\t/**\n\t * This thread takes care of changing current folder without locking the main\n\t * thread. The folder change can be cancelled.\n\t *\n\t * <p>A little note out of nowhere: never ever call JComponent.paintImmediately() from a thread\n\t * other than the Event Dispatcher Thread, as will create nasty repaint glitches that\n\t * then become very hard to track. Sun's Javadoc doesn't make it clear enough... just don't!\n\t *\n\t * @author Maxence Bernard\n\t */\n\tpublic class ChangeFolderThread extends Thread {\n\n\t\tprivate AbstractFile folder;\n\t\tprivate boolean findWorkableFolder;\n\t\tprivate final boolean changeLockedTab;\n\t\tprivate FileURL folderURL;\n\t\tprivate AbstractFile fileToSelect;\n\t\tprivate CredentialsMapping credentialsMapping;\n\n\t\t/** True if this thread has been interrupted by the user using #tryKill */\n\t\tprivate boolean killed;\n\t\t/** True if an attempt to kill this thread using Thread#interrupt() has already been made */\n\t\tprivate boolean killedByInterrupt;\n\t\t/** True if an attempt to kill this thread using Thread#stop() has already been made */\n\t\tprivate boolean killedByStop;\n\t\t/** True if it is unsafe to kill this thread */\n\t\tprivate boolean doNotKill;\n\n\t\tprivate boolean disposed;\n\n\t\t/** Lock object used to ensure consistency and thread safeness when killing the thread */\n\t\tprivate final Object KILL_LOCK = new Object();\n\n\t\t/* TODO branch private ArrayList childrenList; */\n\n\n\t\tChangeFolderThread(AbstractFile folder, boolean findWorkableFolder, boolean changeLockedTab) {\n\t\t\t// Ensure that we work on a raw file instance and not a cached one\n\t\t\tthis.folder = (folder instanceof CachedFile)?((CachedFile)folder).getProxiedFile():folder;\n\t\t\tthis.folderURL = folder.getURL();\n\t\t\tthis.findWorkableFolder = findWorkableFolder;\n\t\t\tthis.changeLockedTab = changeLockedTab;\n\n\t\t\tsetPriority(Thread.MAX_PRIORITY);\n\t\t}\n\n\t\t/**\n\t\t * \n\t\t * @param folderURL folder's URL to be made current folder. If this URL does not resolve into an existing file, an error message will be displayed.\n\t\t * @param credentialsMapping the CredentialsMapping to use for accessing the folder, <code>null</code> for none\n\t\t * @param changeLockedTab flag that indicates whether to change the presented folder in the currently selected tab although it's locked\n\t\t */\n\t\tChangeFolderThread(FileURL folderURL, CredentialsMapping credentialsMapping, boolean changeLockedTab) {\n\t\t\tthis.folderURL = folderURL;\n\t\t\tthis.changeLockedTab = changeLockedTab;\n\t\t\tthis.credentialsMapping = credentialsMapping;\n\n\t\t\tsetPriority(Thread.MAX_PRIORITY);\n\t\t}\n\n\t\t/**\n\t\t * Sets the file to be selected after the folder has been changed, <code>null</code> for none.\n\t\t *\n\t\t * @param fileToSelect the file to be selected after the folder has been changed\n\t\t */\n\t\tvoid selectThisFileAfter(AbstractFile fileToSelect) {\n\t\t\tthis.fileToSelect = fileToSelect;\n\t\t}\n\n\t\t/**\n\t\t * Returns <code>true</code> if the given file should have its canonical path followed. In that case, the\n\t\t * AbstractFile instance must be resolved again.\n\t\t *\n\t\t * <p>HTTP files MUST have their canonical path followed. For all other file protocols, this is an option in\n\t\t * the preferences.\n\t\t *\n\t\t * @param file the file to test\n\t\t * @return <code>true</code> if the given file should have its canonical path followed\n\t\t */\n\t\tprivate boolean followCanonicalPath(AbstractFile file) {\n\t\t\treturn (followsSymlinkEnabled() || file.getURL().getScheme().equals(FileProtocols.HTTP)) &&\n                    !file.getAbsolutePath(false).equals(file.getCanonicalPath(false));\n\t\t}\n\n\t\t/**\n\t\t * Attempts to stop this thread and returns <code>true</code> if an attempt was made.\n\t\t * An attempt to stop this thread will be made using one of the methods detailed hereunder, only if\n\t\t * it is still safe to do so: if the thread is too far into the process of changing the current folder,\n\t\t * this method will have no effect and return <code>false</code>.\n\t\t *\n\t\t * <p>The first time this method is called, {@link #interrupt()} is called, giving the thread a chance to stop\n\t\t * gracefully should it be waiting for a thread or blocked in an interruptible operation such as an\n\t\t * InterruptibleChannel. This may have no immediate effect if the thread is blocked in a non-interruptible\n\t\t * operation. This thread will however be marked as 'killed' which will sooner or later cause {@link #run()}\n\t\t * to stop the thread by simply returning.\n\t\t *\n\t\t * <p>The second time this method is called, the deprecated (and unsafe) {@link #stop()} method is called,\n\t\t * forcing the thread to abort.\n\t\t *\n\t\t * <p>Any subsequent calls to this method will have no effect and return <code>false</code>.\n\t\t *\n\t\t * @return true if an attempt was made to stop this thread.\n\t\t */\n\t\tpublic boolean tryKill() {\n\t\t\tsynchronized(KILL_LOCK) {\n\t\t\t\tif (killedByStop) {\n\t\t\t\t\tLOGGER.debug(\"Thread already killed by #interrupt() and #stop(), there's nothing we can do, returning\");\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tif (doNotKill) {\n\t\t\t\t\tLOGGER.debug(\"Can't kill thread now, it's too late, returning\");\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t// This field needs to be set before actually killing the thread, #init() relies on it\n\t\t\t\tkilled = true;\n\n\t\t\t\t// Call Thread#interrupt() the first time this method is called to give the thread a chance to stop\n\t\t\t\t// gracefully if it is waiting in Thread#sleep() or Thread#wait() or Thread#join() or in an\n\t\t\t\t// interruptible operation such as java.nio.channel.InterruptibleChannel. If this is the case,\n\t\t\t\t// InterruptedException or ClosedByInterruptException will be thrown and thus need to be catched by\n\t\t\t\t// #init().\n\t\t\t\tif (!killedByInterrupt) {\n\t\t\t\t\tLOGGER.debug(\"Killing thread using #interrupt()\");\n\n\t\t\t\t\t// This field needs to be set before actually interrupting the thread, #init() relies on it\n\t\t\t\t\tkilledByInterrupt = true;\n\t\t\t\t\tinterrupt();\n\t\t\t\t} else {\n\t\t\t\t\t// Call Thread#stop() the first time this method is called\n\t\t\t\t\tLOGGER.debug(\"Killing thread using #stop()\");\n\t\t\t\t\tkilledByStop = true;\n\n\t\t\t\t\t// Execute #cleanup() as it would have been done by #init() had the thread not been stopped.\n\t\t\t\t\t// Note that #init() may end pseudo-gracefully and catch the underlying Exception. In this case\n\t\t\t\t\t// it will also call #cleanup() but the (2nd) call to #cleanup() will be ignored.\n\t\t\t\t\tcleanup(false);\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\n\t\t@Override\n\t\tpublic void start() {\n\t\t\t// Notify listeners that location is changing\n\t\t\tlocationManager.fireLocationChanging(folder == null ? folderURL : folder.getURL());\n\n\t\t\tsuper.start();\n\t\t}\n\n\n\t\t@Override\n\t\tpublic void run() {\n\t\t\tLOGGER.debug(\"starting folder change...\");\n\t\t\tboolean folderChangedSuccessfully = false;\n\n\t\t\t// Show some progress in the progress bar to give hope\n\t\t\tfolderPanel.setProgressValue(10);\n\n\t\t\tCredentialsMapping newCredentialsMapping = null;\n\t\t\t// True if Guest authentication was selected in the authentication dialog (guest credentials must not be\n\t\t\t// added to CredentialsManager)\n\t\t\tboolean guestCredentialsSelected = false;\n\n            boolean userCancelled = false;\n\t\t\tif (credentialsMapping != null) {\n\t\t\t\tnewCredentialsMapping = credentialsMapping;\n\t\t\t\tCredentialsManager.authenticate(folderURL, newCredentialsMapping);\n\t\t\t} else if (shouldDisplayAuthDialog()) {\n\t\t\t\tAuthDialog authDialog = popAuthDialog(folderURL, false, null);\n\t\t\t\tnewCredentialsMapping = authDialog.getCredentialsMapping();\n\t\t\t\tguestCredentialsSelected = authDialog.guestCredentialsSelected();\n\n\t\t\t\t// User cancelled the authentication dialog, stop\n\t\t\t\tif (newCredentialsMapping == null) {\n                    userCancelled = true;\n                } else {\n                    // Use the provided credentials and invalidate the folder AbstractFile instance (if any) so that\n                    // it gets recreated with the new credentials\n\t\t\t\t\tCredentialsManager.authenticate(folderURL, newCredentialsMapping);\n\t\t\t\t\tfolder = null;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!userCancelled) {\n\t\t\t\tboolean canonicalPathFollowed = false;\n\n\t\t\t\tdo {\n                    showWaitCursor();\n\n                    // Render all actions inactive while changing folder\n\t\t\t\t\tmainFrame.setNoEventsMode(true);\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// 2 cases here :\n\t\t\t\t\t\t// - Thread was created using an AbstractFile instance\n\t\t\t\t\t\t// - Thread was created using a FileURL, corresponding AbstractFile needs to be resolved\n\t\t\t\t\t\tif (folder == null) {\n                            // Thread was created using a FileURL\n                            if (processFile(FileFactory.getFile(folderURL, true))) {\n                                break;\n                            }\n                        } else if (!folder.exists()) {\n                            // Thread was created using an AbstractFile instance, check file existence\n\t\t\t\t\t\t\t// Find a 'workable' folder if the requested folder doesn't exist anymore\n\t\t\t\t\t\t\tif (findWorkableFolder) {\n\t\t\t\t\t\t\t\tAbstractFile newFolder = getWorkableFolder(folder);\n\t\t\t\t\t\t\t\tif (folder.equals(newFolder)) {\n\t\t\t\t\t\t\t\t\t// If we've already tried the returned folder, give up (avoids a potentially endless loop)\n\t\t\t\t\t\t\t\t\tshowFolderDoesNotExistDialog();\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Try again with the new folder\n\t\t\t\t\t\t\t\tfolder = newFolder;\n\t\t\t\t\t\t\t\tfolderURL = folder != null ? folder.getURL() : null;\n\t\t\t\t\t\t\t\t// Discard the file to select, if any\n\t\t\t\t\t\t\t\tfileToSelect = null;\n\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tshowFolderDoesNotExistDialog();\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (!folder.canRead()) {\n\t\t\t\t\t\t\tshowFailedToReadFolderDialog();\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Checks if canonical should be followed. If that is the case, the file is invalidated\n\t\t\t\t\t\t// and resolved again. This happens only once at most, to avoid a potential infinite loop\n\t\t\t\t\t\t// in the event that the absolute path still didn't match canonical one after the file is\n\t\t\t\t\t\t// resolved again.\n\t\t\t\t\t\tif (!canonicalPathFollowed && followCanonicalPath(folder)) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t// Recreate the FileURL using the file's canonical path\n\t\t\t\t\t\t\t\tFileURL newURL = FileURL.getFileURL(folder.getCanonicalPath());\n\t\t\t\t\t\t\t\t// Keep the credentials and properties (if any)\n\t\t\t\t\t\t\t\tnewURL.setCredentials(folderURL.getCredentials());\n\t\t\t\t\t\t\t\tnewURL.importProperties(folderURL);\n\t\t\t\t\t\t\t\tthis.folderURL = newURL;\n\t\t\t\t\t\t\t\t// Invalidate the AbstractFile instance\n\t\t\t\t\t\t\t\tthis.folder = null;\n\t\t\t\t\t\t\t\t// There won't be any further attempts after this one\n\t\t\t\t\t\t\t\tcanonicalPathFollowed = true;\n\n\t\t\t\t\t\t\t\t// Loop the resolve the file\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t} catch (MalformedURLException e) {\n\t\t\t\t\t\t\t\t// In the unlikely event of the canonical path being malformed, the AbstractFile\n\t\t\t\t\t\t\t\t// and FileURL instances are left untouched\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tsynchronized(KILL_LOCK) {\n\t\t\t\t\t\t\tif (killed) {\n\t\t\t\t\t\t\t\tLOGGER.debug(\"this thread has been killed, returning\");\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// File tested -> 50% complete\n\t\t\t\t\t\tfolderPanel.setProgressValue(50);\n\n\t\t\t\t\t\t/* TODO branch \n\t\t\t\t\t\tAbstractFile children[] = new AbstractFile[0];\n\t\t\t\t\t\tif (branchView) {\n\t\t\t\t\t\t\tchildrenList = new ArrayList();\n\t\t\t\t\t\t\treadBranch(folder);\n\t\t\t\t\t\t\tchildren = (AbstractFile[]) childrenList.toArray(children);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tchildren = folder.ls(chainedFileFilter);                            \n\t\t\t\t\t\t}*/ \n\n\t\t\t\t\t\tsynchronized(KILL_LOCK) {\n\t\t\t\t\t\t\tif (killed) {\n\t\t\t\t\t\t\t\tLOGGER.debug(\"this thread has been killed, returning\");\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// From now on, thread cannot be killed (would comprise table integrity)\n\t\t\t\t\t\t\tdoNotKill = true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// files listed -> 75% complete\n\t\t\t\t\t\tfolderPanel.setProgressValue(75);\n\n\t\t\t\t\t\tLOGGER.trace(\"calling setCurrentFolder\");\n\n\t\t\t\t\t\t// Change the file table's current folder and select the specified file (if any)\n\t\t\t\t\t\tsetCurrentFolder(folder, fileToSelect, changeLockedTab);\n\n\t\t\t\t\t\t// folder set -> 95% complete\n\t\t\t\t\t\tfolderPanel.setProgressValue(95);\n\n\t\t\t\t\t\t// If new credentials were entered by the user, these can now be considered valid\n\t\t\t\t\t\t// (folder was changed successfully), so we add them to the CredentialsManager.\n\t\t\t\t\t\t// Do not add the credentials if guest credentials were selected by the user.\n\t\t\t\t\t\tif (newCredentialsMapping != null && !guestCredentialsSelected) {\n                            CredentialsManager.addCredentials(newCredentialsMapping);\n                        }\n\n\t\t\t\t\t\t// All good !\n\t\t\t\t\t\tfolderChangedSuccessfully = true;\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t} catch(Exception e) {\n\t\t\t\t\t\tLOGGER.debug(\"Caught exception\", e);\n\n\t\t\t\t\t\tif (killed) {\n\t\t\t\t\t\t\t// If #tryKill() called #interrupt(), the exception we just caught was most likely\n\t\t\t\t\t\t\t// thrown as a result of the thread being interrupted.\n\t\t\t\t\t\t\t//\n\t\t\t\t\t\t\t// The exception can be a java.lang.InterruptedException (Thread throws those),\n\t\t\t\t\t\t\t// a java.nio.channels.ClosedByInterruptException (InterruptibleChannel throws those)\n\t\t\t\t\t\t\t// or any other exception thrown by some code that swallowed the original exception\n\t\t\t\t\t\t\t// and threw a new one.\n\n\t\t\t\t\t\t\tLOGGER.debug(\"Thread was interrupted, ignoring exception\");\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Restore default cursor\n\t\t\t\t\t\trestoreDefaultCursor();\n\n\t\t\t\t\t\tif (e instanceof AuthException authException) {\n                            // Retry (loop) if user provided new credentials, if not stop\n\t\t\t\t\t\t\tAuthDialog authDialog = popAuthDialog(authException.getURL(), true, authException.getMessage());\n\t\t\t\t\t\t\tnewCredentialsMapping = authDialog.getCredentialsMapping();\n\t\t\t\t\t\t\tguestCredentialsSelected = authDialog.guestCredentialsSelected();\n\n\t\t\t\t\t\t\tif (newCredentialsMapping != null) {\n\t\t\t\t\t\t\t\t// Invalidate the existing AbstractFile instance\n\t\t\t\t\t\t\t\tfolder = null;\n\t\t\t\t\t\t\t\t// Use the provided credentials\n\t\t\t\t\t\t\t\tCredentialsManager.authenticate(folderURL, newCredentialsMapping);\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Find a 'workable' folder if the requested folder doesn't exist anymore\n\t\t\t\t\t\t\tif (findWorkableFolder) {\n\t\t\t\t\t\t\t\tAbstractFile newFolder = getWorkableFolder(folder);\n\t\t\t\t\t\t\t\tif (folder.equals(newFolder)) {\n\t\t\t\t\t\t\t\t\t// If we've already tried the returned folder, give up (avoids a potentially endless loop)\n\t\t\t\t\t\t\t\t\tshowFolderDoesNotExistDialog();\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Try again with the new folder\n\t\t\t\t\t\t\t\tfolder = newFolder;\n\t\t\t\t\t\t\t\tfolderURL = folder != null ? folder.getURL() : null;\n\t\t\t\t\t\t\t\t// Discard the file to select, if any\n\t\t\t\t\t\t\t\tfileToSelect = null;\n\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tshowAccessErrorDialog(e);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Stop looping!\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\twhile(true);\n\t\t\t}\n\n\t\t\tsynchronized(KILL_LOCK) {\n\t\t\t\tcleanup(folderChangedSuccessfully);\n\t\t\t}\n\t\t}\n\n        private boolean processFile(AbstractFile file) {\n            synchronized(KILL_LOCK) {\n                if (killed) {\n                    LOGGER.debug(\"this thread has been killed, returning\");\n                    return true;\n                }\n            }\n\n            // File resolved -> 25% complete\n            folderPanel.setProgressValue(25);\n\n            // Popup an error dialog and abort folder change if the file could not be resolved\n            // or doesn't exist\n            if (file == null || !file.exists()) {\n                // Restore default cursor\n                restoreDefaultCursor();\n\n                showFolderDoesNotExistDialog();\n                return true;\n            }\n            if (!file.canRead()) {\n                // Restore default cursor\n                restoreDefaultCursor();\n                showFailedToReadFolderDialog();\n                return true;\n            }\n\n            // File is a regular directory, all good\n            if (file.isDirectory()) {\n                // Just continue\n            } else if (file.isBrowsable()) {\n                // File is a browsable file (Zip archive for instance) but not a directory : Browse or Download ? => ask the user\n\n                // If history already contains this file, do not ask the question again and assume\n                // the user wants to 'browse' the file. In particular, this prevent the 'Download or browse'\n                // dialog from popping up when going back or forward in history.\n                // The dialog is also not displayed if the file corresponds to the currently selected file,\n                // which is a weak (and not so accurate) way to know if the folder change is the result\n                // of the OpenAction (enter pressed on the file). This works well enough in practice.\n                if (!globalHistory.historyContains(folderURL) && !file.equals(folderPanel.getFileTable().getSelectedFile())) {\n                    restoreDefaultCursor();\n\n                    // Download or browse file ?\n                    QuestionDialog dialog = new QuestionDialog(mainFrame.getJFrame(),\n                            null,\n                            Translator.get(\"table.download_or_browse\"),\n                            mainFrame.getJFrame(),\n                            new String[] {BROWSE_TEXT, DOWNLOAD_TEXT, CANCEL_TEXT},\n                            new int[] {BROWSE_ACTION, DOWNLOAD_ACTION, CANCEL_ACTION},\n                            0);\n\n                    int ret = dialog.getActionValue();\n\n                    if (ret == -1 || ret == CANCEL_ACTION) {\n                        return true;\n                    }\n\n                    // Download file\n                    if (ret == DOWNLOAD_ACTION) {\n                        showDownloadDialog(file);\n                        return true;\n                    }\n                    // Continue if BROWSE_ACTION\n                    // Set cursor to hourglass/wait\n                    showWaitCursor();\n                }\n                // else just continue and browse file's contents\n            } else {\n                // File is a regular file: show download dialog which allows to download (copy) the file\n                // to a directory specified by the user\n                showDownloadDialog(file);\n                return true;\n            }\n\n            this.folder = file;\n            return false;\n        }\n\n        private boolean shouldDisplayAuthDialog() {\n            // If the URL doesn't contain any credentials and authentication for this file protocol is required, or\n            // optional and CredentialsManager has credentials for this location, popup the authentication dialog to\n            // avoid waiting for an AuthException to be thrown.\n            if (folderURL.containsCredentials()) {\n\t\t        return false;\n            }\n            AuthenticationType authenticationType = folderURL.getAuthenticationType();\n\t\t    if (authenticationType == AuthenticationType.AUTHENTICATION_REQUIRED) {\n\t\t        return true;\n            }\n            return authenticationType == AuthenticationType.AUTHENTICATION_OPTIONAL && CredentialsManager.getMatchingCredentials(folderURL).length > 0;\n        }\n\n        void cleanup(boolean folderChangedSuccessfully) {\n\t\t\t// Ensures that this method is called only once\n\t\t\tsynchronized(KILL_LOCK) {\n\t\t\t\tif (disposed) {\n\t\t\t\t\tLOGGER.debug(\"already called, returning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tdisposed = true;\n\t\t\t}\n\n\t\t\tLOGGER.trace(\"cleaning up, folderChangedSuccessfully=\"+folderChangedSuccessfully);\n\n\t\t\t// Clear the interrupted flag in case this thread has been killed using #interrupt().\n\t\t\t// Not doing this could cause some of the code called by this method to be interrupted (because this thread\n\t\t\t// is interrupted) and throw an exception\n\t\t\tinterrupted();\n\n\t\t\t// Reset location field's progress bar\n\t\t\tfolderPanel.setProgressValue(0);\n\n\t\t\t// Restore normal mouse cursor\n\t\t\trestoreDefaultCursor();\n\n\t\t\tsynchronized(FOLDER_CHANGE_LOCK) {\n\t\t\t\tchangeFolderThread = null;\n\t\t\t}\n\n\t\t\t// Make all actions active again\n\t\t\tmainFrame.setNoEventsMode(false);\n\n\t\t\tif (!folderChangedSuccessfully) {\n\t\t\t\tFileURL failedURL = folder == null ? folderURL : folder.getURL();\n\t\t\t\t// Notifies listeners that location change has been cancelled by the user or has failed\n\t\t\t\tif (killed) {\n\t\t\t\t\tlocationManager.fireLocationCancelled(failedURL);\n\t\t\t\t} else {\n\t\t\t\t\tlocationManager.fireLocationFailed(failedURL);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// For debugging purposes\n\t\tpublic String toString() {\n\t\t\treturn super.toString() + \" folderURL=\" + folderURL + \" folder=\" + folder;\n\t\t}\n\t}\n\n    private boolean followsSymlinkEnabled() {\n        return TcConfigurations.getPreferences().getVariable(TcPreference.CD_FOLLOWS_SYMLINKS, TcPreferences.DEFAULT_CD_FOLLOWS_SYMLINKS);\n    }\n\n    /* TODO branch\n\t*//*\n\tprivate void readBranch(AbstractFile parent) {\n\t\tAbstractFile[] children;\n\t\ttry {\n\t\t\tchildren = parent.ls(chainedFileFilter);\n\t\t\tfor (int i=0; i<children.length; i++) {\n\t\t\t\tif (children[i].isDirectory()) {\n\t\t\t\t\treadBranch(children[i]);\n\t\t\t\t} else {\n\t\t\t\t\tchildrenList.add(children[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (IOException e) {\n\t\t\tAppLogger.fine(\"Caught exception\", e);\n\t\t}\n\t}*/\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/core/NullableFile.java",
    "content": "package com.mucommander.core;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.io.RandomAccessInputStream;\nimport com.mucommander.commons.io.RandomAccessOutputStream;\n\n/**\n * This class represents an {@link AbstractFile} that doesn't exist for UI purposes.\n * External libraries generally return null for path to non-existing file, so in order\n * to be able to present such non-existing file, we use this class.\n *\n * @author Arik Hadas\n */\nclass NullableFile extends AbstractFile {\n\n\tNullableFile(FileURL url) {\n\t\tsuper(url);\n\t}\n\n\t@Override\n\tpublic boolean canGetGroup() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic boolean canGetOwner() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic void setLastModifiedDate(long arg0) throws IOException {\n\t\tthrow new UnsupportedFileOperationException(FileOperation.CHANGE_DATE);\n\t}\n\n\t@Override\n\tpublic void changePermission(int arg0, int arg1, boolean arg2) throws IOException {\n\t\tthrow new UnsupportedFileOperationException(FileOperation.CHANGE_PERMISSION);\n\t}\n\n\t@Override\n\tpublic void copyRemotelyTo(AbstractFile arg0) throws IOException {\n\t\tthrow new UnsupportedFileOperationException(FileOperation.COPY_REMOTELY);\n\t}\n\n\t@Override\n\tpublic void delete() throws IOException {\n\t\tthrow new UnsupportedFileOperationException(FileOperation.DELETE);\n\t}\n\n\t@Override\n\tpublic boolean exists() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic OutputStream getAppendOutputStream() throws IOException {\n\t\tthrow new UnsupportedFileOperationException(FileOperation.APPEND_FILE);\n\t}\n\n\t@Override\n\tpublic PermissionBits getChangeablePermissions() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic long getLastModifiedDate() {\n\t\treturn 0;\n\t}\n\n\t@Override\n\tpublic long getFreeSpace() throws IOException {\n\t\tthrow new UnsupportedFileOperationException(FileOperation.GET_FREE_SPACE);\n\t}\n\n\t@Override\n\tpublic String getGroup() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic InputStream getInputStream() throws IOException {\n\t\tthrow new UnsupportedFileOperationException(FileOperation.WRITE_FILE);\n\t}\n\n\t@Override\n\tpublic OutputStream getOutputStream() throws IOException {\n\t\tthrow new UnsupportedFileOperationException(FileOperation.READ_FILE);\n\t}\n\n\t@Override\n\tpublic String getOwner() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic AbstractFile getParent() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic FilePermissions getPermissions() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic RandomAccessInputStream getRandomAccessInputStream()\n\t\t\tthrows IOException {\n\t\tthrow new UnsupportedFileOperationException(FileOperation.RANDOM_WRITE_FILE);\n\t}\n\n\t@Override\n\tpublic RandomAccessOutputStream getRandomAccessOutputStream() throws IOException {\n\t\tthrow new UnsupportedFileOperationException(FileOperation.RANDOM_READ_FILE);\n\t}\n\n\t@Override\n\tpublic long getSize() {\n\t\treturn -1;\n\t}\n\n\t@Override\n\tpublic long getTotalSpace() throws IOException {\n\t\tthrow new UnsupportedFileOperationException(FileOperation.GET_TOTAL_SPACE);\n\t}\n\n\t@Override\n\tpublic Object getUnderlyingFileObject() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic boolean isArchive() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic boolean isDirectory() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic boolean isSymlink() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic boolean isSystem() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic AbstractFile[] ls() throws IOException {\n\t\tthrow new UnsupportedFileOperationException(FileOperation.LIST_CHILDREN);\n\t}\n\n\t@Override\n\tpublic void mkdir() throws IOException {\n\t}\n\n\t@Override\n\tpublic void renameTo(AbstractFile arg0) throws IOException {\n\t\tthrow new UnsupportedFileOperationException(FileOperation.RENAME);\n\t}\n\n\t@Override\n\tpublic void setParent(AbstractFile arg0) {\n\t}\n\t@Override\n\t@UnsupportedFileOperation\n\tpublic short getReplication() throws UnsupportedFileOperationException {\n\t\tthrow new UnsupportedFileOperationException(FileOperation.GET_REPLICATION);\n\t}\n\n\t@Override\n\t@UnsupportedFileOperation\n\tpublic long getBlocksize() throws UnsupportedFileOperationException {\n\t\tthrow new UnsupportedFileOperationException(FileOperation.GET_BLOCKSIZE);\n\t}\n\n\t@Override\n\t@UnsupportedFileOperation\n\tpublic void changeReplication(short replication) throws IOException {\n\t\tthrow new UnsupportedFileOperationException(FileOperation.CHANGE_REPLICATION);\n\t}\n\n}"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/AbstractTrash.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop;\n\nimport com.mucommander.commons.file.AbstractFile;\n\n/**\n * AbstractTrash is an abstract representation of a file trash, i.e. a temporary place where deleted files are stored\n * before the trash is emptied. A trash implementation provides methods to access basic trash operations: move files\n * to the trash, empty the trash, ...\n *\n * <p>Since AbstractTrash implementations are system-dependent, they should not be instantiated directly.\n * Use {@link DesktopManager#getTrash()} to retrieve an instance of a trash implementation that can\n * be used on the current platform.<br>\n * Also, some AbstractTrash subclasses may not be able to provide working implementations for all trash operations;\n * probe methods are provided to find out if a particular operation is available.\n *\n * @see TrashProvider\n * @see DesktopManager#getTrash()\n * @author Maxence Bernard\n */\npublic abstract class AbstractTrash {\n\n    /**\n     * Returns <code>true</code> if the specified file is eligible for being moved to the trash. This doesn't mean that\n     * a call to {@link #moveToTrash(com.mucommander.commons.file.AbstractFile)} will necessarily succeed, but it should at least ensure that basic\n     * prerequisites are met. \n     *\n     * @param file the file to test\n     * @return true if the given file can be moved to the trash\n     */\n    public abstract boolean canMoveToTrash(AbstractFile file);\n\n    /**\n     * Attempts to move the given file to the trash and returns <code>true</code> if the file could be moved successfully.\n     *\n     * @param file the file to move to the trash\n     * @return true if the file could successfully be moved to the trash\n     */\n    public abstract boolean moveToTrash(AbstractFile file);\n\n    /**\n     * Returns <code>true</code> if this trash can be emptied.\n     *\n     * @return true if the trash can be emptied.\n     */\n    public abstract boolean canEmpty();\n\n    /**\n     * Attempts to empty this trash and returns <code>true</code> if it was successfully emptied.\n     *\n     * @return true if the trash was successfully emptied\n     */\n    public abstract boolean empty();\n\n    /**\n     * Returns <code>true</code> if the given file is a trash folder, or one of its children.\n     * For example, if <code>/home/someuser/.Trash</code> is a trash folder, calling this method with:\n     * <ul>\n     *  <li><code>/home/someuser/.Trash</code> will return <code>true</code>\n     *  <li><code>/home/someuser/.Trash/somefolder/somefile</code> will return <code>true</code>\n     *  <li><code>/home/someuser/Desktop</code> will return <code>false</code>\n     * </ul>\n     *\n     * <p>Note that this method does not check the existence of the given file, the test is solely based on the\n     * file's path.\n     *\n     * @param file the file to test\n     * @return true if the given file is a trash folder, or one of its children.\n     */\n    public abstract boolean isTrashFile(AbstractFile file);\n\n    /**\n     * Returns the number of items that currently are in this trash, <code>-1</code> if this information is not available.\n     *\n     * @return the number of items that currently are in this trash, <code>-1</code> if this information is not available\n     */\n    public abstract int getItemCount();\n\n    /**\n     * Opens the trash in the default file manager of the current OS/Desktop manager.\n     */\n    public abstract void open();\n\n    /**\n     * Returns <code>true</code> if this trash can be opened in the default file manager of the current OS/Desktop manager\n     * by calling {@link #open()}.\n     *\n     * @return true if this trash can be opened in the default file manager of the current OS/Desktop manager.\n     */\n    public abstract boolean canOpen();\n\n    /**\n     * Waits (locks the caller thread) until all pending trash operations are completed.\n     * This method can be useful since some <code>AbstractTrash</code> implementations are asynchroneous, i.e. perform\n     * operations in separate threads.   \n     */\n    public abstract void waitForPendingOperations();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/CommandBrowse.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop;\n\nimport com.mucommander.command.Command;\nimport com.mucommander.command.CommandManager;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.process.ProcessRunner;\n\nimport java.io.IOException;\nimport java.net.URL;\n\n/**\n * @author Nicolas Rinaudo\n */\nclass CommandBrowse extends UrlOperation {\n    // - Desktop operation implementation --------------------------------\n    // -------------------------------------------------------------------\n    @Override\n    public boolean isAvailable() {\n        return CommandManager.getCommandForAlias(CommandManager.URL_OPENER_ALIAS, null) != null;\n    }\n\n    @Override\n    public void execute(URL url) throws IOException {\n        AbstractFile target = FileFactory.getFile(url.toString());\n        Command command = CommandManager.getCommandForAlias(CommandManager.URL_OPENER_ALIAS, target);\n        if (command == null) {\n            throw new UnsupportedOperationException();\n        }\n        ProcessRunner.execute(command.getTokens(target), target);\n    }\n\n    /**\n     * Returns the operation's name.\n     * @return the operation's name.\n     */\n    @Override\n    public String getName() {return \"openURL bridge\";}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/CommandOpen.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop;\n\nimport com.mucommander.command.Command;\nimport com.mucommander.command.CommandManager;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.process.ProcessRunner;\n\nimport java.io.IOException;\n\n/**\n * @author Nicolas Rinaudo\n */\nclass CommandOpen extends LocalFileOperation {\n    /** Whether the 'init as executable' command can be used if no better alternative is found. */\n    private final boolean allowDefault;\n\n\n    CommandOpen(boolean allowDefault) {\n        this.allowDefault = allowDefault;\n    }\n\n    @Override\n    public boolean isAvailable() {\n        if (allowDefault) {\n            return true;\n        }\n        return CommandManager.getCommandForAlias(CommandManager.FILE_OPENER_ALIAS, null) != null;\n    }\n\n    @Override\n    public boolean canExecute(AbstractFile file) {\n        return allowDefault || CommandManager.getCommandForFile(file, false) != null;\n\n    }\n\n    @Override\n    public void execute(AbstractFile file) throws IOException {\n        Command command = CommandManager.getCommandForFile(file, allowDefault);\n\n        // Attempts to find a command that matches the specified target.\n        if (command == null) {\n            throw new UnsupportedOperationException();\n        }\n\n        // If found, executes it.\n        ProcessRunner.execute(command.getTokens(file), file);\n    }\n\n    /**\n     * Returns the operation's name.\n     * @return the operation's name.\n     */\n    @Override\n    public String getName() {\n        return \"open bridge\";\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/CommandOpenInFileManager.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop;\n\nimport com.mucommander.command.Command;\nimport com.mucommander.command.CommandManager;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.process.ProcessRunner;\n\nimport java.io.IOException;\n\n/**\n * @author Nicolas Rinaudo\n */\nclass CommandOpenInFileManager extends LocalFileOperation {\n    @Override\n    public boolean isAvailable() {\n        return CommandManager.getCommandForAlias(CommandManager.FILE_MANAGER_ALIAS, null) != null;\n    }\n\n    @Override\n    public void execute(AbstractFile file) throws IOException {\n        Command command = CommandManager.getCommandForAlias(CommandManager.FILE_MANAGER_ALIAS, file);\n\n        if (command == null) {\n            throw new UnsupportedOperationException();\n        }\n        ProcessRunner.execute(command.getTokens(file), file);\n    }\n\n    @Override\n    public String getName() {\n        Command command = CommandManager.getCommandForAlias(CommandManager.FILE_MANAGER_ALIAS, null);\n        return command == null ? null : command.getDisplayName();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/DefaultDesktopAdapter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop;\n\nimport java.awt.Toolkit;\nimport java.awt.event.MouseEvent;\nimport java.io.File;\n\nimport com.mucommander.commons.runtime.OsFamily;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.AbstractFile;\n\n/**\n * Provides a default implementation of the {@link DesktopAdapter} interface.\n * <p>\n * This implementation is meant to help application developers by providing standard\n * implementations of all {@link DesktopAdapter} methods, letting subclasses concentrate\n * on what's important rather than mundane.\n *\n * <p>\n * Moreover, an instance of <code>DefaultDesktopAdapter</code> will be used by the\n * {@link DesktopManager} if no valid desktop could be identifier.\n *\n * @author Nicolas Rinaudo, Maxence Bernard\n */\npublic class DefaultDesktopAdapter implements DesktopAdapter {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(DefaultDesktopAdapter.class);\n\t\n    /** Default multi-click interval when the desktop property cannot be retrieved. */\n    public final static int DEFAULT_MULTICLICK_INTERVAL = 500;\n\n    /** Multi-click interval, cached to avoid polling the value every time {@link #getMultiClickInterval()} is called */\n    private static int multiClickInterval;\n\n    private String defaultShell;\n\n    static {\n        try {\n            Integer value = ((Integer)Toolkit.getDefaultToolkit().getDesktopProperty(\"awt.multiClickInterval\"));\n            multiClickInterval = value == null ? DEFAULT_MULTICLICK_INTERVAL : value;\n        } catch (Exception e) {\n            LOGGER.debug(\"Error while retrieving multi-click interval value desktop property\", e);\n\n            multiClickInterval = DEFAULT_MULTICLICK_INTERVAL;\n        }\n    }\n\n    public String toString() {\n        return \"Default Desktop\";\n    }\n\n    /**\n     * Returns <code>true</code>.\n     * @return <code>true</code>.\n     */\n    public boolean isAvailable() {\n        return true;\n    }\n\n    /**\n     * Initializes this desktop.\n     * <p>\n     * This method is empty. See {@link DesktopAdapter#init(boolean)} for information on\n     * how to override it.\n     *\n     * @param  install                        <code>true</code> if this is the application's first boot, <code>false</code> otherwise.\n     * @throws DesktopInitializationException if any error occurs.\n     */\n    public void init(boolean install) throws DesktopInitializationException {\n    }\n\n    /**\n     * Returns <code>true</code> if the specified mouse event describes a left click.\n     * <p>\n     * This method will return <code>true</code> if <code>(e.getModifiers() &amp; MouseEvent.BUTTON1_MASK)</code>\n     * doesn't equal 0.\n     *\n     * @param  e event to check.\n     * @return   <code>true</code> if the specified event is a left-click, <code>false</code> otherwise.\n     * @see      #isRightMouseButton(MouseEvent)\n     * @see      #isMiddleMouseButton(MouseEvent)\n     */\n    public boolean isLeftMouseButton(MouseEvent e) {\n        return (e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0;\n    }\n\n    /**\n     * Returns <code>true</code> if the specified mouse event describes a middle click.\n     * <p>\n     * This method will return <code>true</code> if <code>(e.getModifiers() &amp; MouseEvent.BUTTON3_MASK)</code>\n     * doesn't equal 0.\n     *\n     * @param  e event to check.\n     * @return   <code>true</code> if the specified event is a middle-click, <code>false</code> otherwise.\n     * @see      #isRightMouseButton(MouseEvent)\n     * @see      #isLeftMouseButton(MouseEvent)\n     */\n    public boolean isRightMouseButton(MouseEvent e) {\n        return (e.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) !=0;\n    }\n\n    /**\n     * Returns <code>true</code> if the specified mouse event describes a right click.\n     * <p>\n     * This method will return <code>true</code> if <code>(e.getModifiers() &amp; MouseEvent.BUTTON2_MASK)</code>\n     * doesn't equal 0.\n     *\n     * @param  e event to check.\n     * @return   <code>true</code> if the specified event is a right-click, <code>false</code> otherwise.\n     * @see      #isLeftMouseButton(MouseEvent)\n     * @see      #isMiddleMouseButton(MouseEvent)\n     */\n    public boolean isMiddleMouseButton(MouseEvent e) {\n        return (e.getModifiersEx() & MouseEvent.BUTTON2_DOWN_MASK) != 0;\n    }\n\n    /**\n     * Returns the value of the <code>\"awt.multiClickInterval\"</code> desktop property that AWT/Swing uses internally\n     * for generating the {@link MouseEvent#getClickCount() click count} returned by <code>MouseListener</code>\n     * mouse events. If the property is not set, {@link #DEFAULT_MULTICLICK_INTERVAL} is returned.\n     * @see    MouseEvent#getClickCount()\n     * @see    java.awt.Toolkit#getDesktopProperty(String) \n     * @return the value of the <code>\"awt.multiClickInterval\"</code> desktop property that AWT/Swing uses internally\n     * for generating the {@link MouseEvent#getClickCount() click count} returned by <code>MouseListener</code>\n     * mouse events\n     */\n    public int getMultiClickInterval() {\n        return multiClickInterval;\n    }\n\n    /**\n     * Returns <code>/bin/sh -l -c\"</code>.\n     * @return <code>/bin/sh -l -c\"</code>.\n     */\n    public String getDefaultShell() {\n        return getDefaultShellPath() + \" -l -c\";\n    }\n\n\n    private String getDefaultShellPath() {\n        if (OsFamily.WINDOWS.isCurrent()) {\n            return \"cmd.exe\";\n        }\n        if (defaultShell == null) {\n            if (new File(\"/bin/zsh\").exists()) {\n                defaultShell = \"/bin/zsh\";\n            } else if (new File(\"/bin/sh\").exists()) {\n                defaultShell = \"/bin/sh\";\n            } else if (new File(\"/bin/bash\").exists()) {\n                defaultShell = \"/bin/bash\";\n            } else {\n                defaultShell = \"/bin/sh\";\n            }\n        }\n        return defaultShell;\n    }\n\n    public String getDefaultTerminalShellCommand() {\n        String path = getDefaultShellPath();\n        if (!OsFamily.WINDOWS.isCurrent()) {\n            return path + \" --login\";\n        }\n        return path;\n    }\n\n    /**\n     * Always returns <code>false</code>.\n     * @return <code>false</code>, always.\n     */\n    public boolean isApplication(AbstractFile file) {\n        return false;\n    }\n\n    @Override\n    public String getDefaultTerminalAppCommand() {\n        return switch (OsFamily.getCurrent()) {\n            case WINDOWS -> \"cmd /c start cmd.exe /K \\\"cd /d $p\\\"\";\n            case LINUX -> \"gnome-terminal --working-directory=$p\";\n            case MAC_OS_X -> \"open -a Terminal .\";\n            default -> \"\";\n        };\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/DesktopAdapter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.filter.FileFilter;\nimport com.mucommander.commons.util.Pair;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.notifier.AbstractNotifier;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.MouseEvent;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.function.Consumer;\n\n/**\n * Contract for classes that provide desktop integration features.\n * <p>\n * There are two main steps to writing a desktop adapter:\n * <ul>\n *   <li>Desktop detection</li>\n *   <li>Desktop initialization</li>\n * </ul>\n *\n * <h3>Desktop detection</h3>\n * <p>\n * This is achieved through the {@link #isAvailable()} method. While it has a fairly\n * simple contract, this method can prove quite difficult to implement properly.<br>\n * The <code>com.mucommander.commons.runtime</code> package provides helpfull classes for this,\n * but application developers might end up having to try to init commands to see if they work\n * (this can be done through the {@link com.mucommander.process.ProcessRunner} class), query\n * environment variables, ...\n *\n * <h3>Desktop initialization</h3>\n * <p>\n * This is achieved through the {@link #init(boolean)} method. Application developers are\n * expected to register all of their desktop specific tools there: {@link DesktopOperation desktop operations},\n * {@link com.mucommander.command.Command commands},\n * {@link com.mucommander.command.CommandManager#registerDefaultAssociation(String, FileFilter) associations}...<br>\n *\n * @author Nicolas Rinaudo, Maxence Bernard\n */\npublic interface DesktopAdapter {\n\n    /**\n     * Checks whether the desktop is available on the current platform.\n     * @return <code>true</code> if the desktop is available on the current platform, <code>false</code> otherwise.\n     */\n    boolean isAvailable();\n\n    /**\n     * Initializes this desktop.\n     * <p>\n     * This method is called when an instance of <code>DesktopAdapter</code> has been chosen as the\n     * best fit for the current system.<br>\n     * This gives the instance an opportunity to set itself up -\n     * default {@link com.mucommander.command.Command} and {@link TcAction} registration,\n     * trash management...\n     * <p>\n     * If the <code>install</code> parameter is set to <code>true</code>, this is the first time the\n     * application has been started. The desktop instance should use this opportunity to install platform\n     * dependant things such as {@link com.mucommander.bookmark.Bookmark} or {@link com.mucommander.ui.action.ActionKeymap}.\n     *\n     * @param  install                        <code>true</code> if this is the application's first boot, <code>false</code> otherwise.\n     * @throws DesktopInitializationException if any error occurs.\n     */\n    void init(boolean install) throws DesktopInitializationException;\n\n\n\n    // - Mouse management ------------------------------------------------\n    // -------------------------------------------------------------------\n    /**\n     * Checks whether the specified <code>MouseEvent</code> is a left-click for this desktop.\n     * <p>\n     * There are some cases where Java doesn't detect mouse events properly - for example,\n     * <i>CONTROL + LEFT CLICK</i> is a <i>RIGHT CLICK</i> under Mac OS X.<br>\n     * The goal of this method is to allow desktop to check for such non-standard behaviors.\n     *\n     * @param  e event to check.\n     * @return   <code>true</code> if the specified event is a left-click for this desktop, <code>false</code> otherwise.\n     * @see      #isRightMouseButton(MouseEvent)\n     * @see      #isMiddleMouseButton(MouseEvent)\n     */\n    boolean isLeftMouseButton(MouseEvent e);\n\n    /**\n     * Checks whether the specified <code>MouseEvent</code> is a left-click for this desktop.\n     * <p>\n     * There are some cases where Java doesn't detect mouse events properly - for example,\n     * <i>CONTROL + LEFT CLICK</i> is a <i>RIGHT CLICK</i> under Mac OS X.<br>\n     * The goal of this method is to allow desktop to check for such non-standard behaviors.\n     *\n     * @param  e event to check.\n     * @return   <code>true</code> if the specified event is a left-click for this desktop, <code>false</code> otherwise.\n     * @see      #isMiddleMouseButton(MouseEvent)\n     * @see      #isLeftMouseButton(MouseEvent)\n     */\n    boolean isRightMouseButton(MouseEvent e);\n\n    /**\n     * Checks whether the specified <code>MouseEvent</code> is a left-click for this desktop.\n     * <p>\n     * There are some cases where Java doesn't detect mouse events properly - for example,\n     * <i>CONTROL + LEFT CLICK</i> is a <i>RIGHT CLICK</i> under Mac OS X.<br>\n     * The goal of this method is to allow desktop to check for such non-standard behaviors.\n     *\n     * @param  e event to check.\n     * @return   <code>true</code> if the specified event is a left-click for this desktop, <code>false</code> otherwise.\n     * @see      #isRightMouseButton(MouseEvent)\n     * @see      #isLeftMouseButton(MouseEvent)\n     */\n    boolean isMiddleMouseButton(MouseEvent e);\n\n    /**\n     * Returns the maximum interval in milliseconds between mouse clicks for them to be considered as 'multi-clicks'\n     * (e.g. double-clicks). The returned value should reflects the desktop's multi-click (or double-click) interval,\n     * which may or may not correspond to the one Java uses for double-clicks.\n     * @return the maximum interval in milliseconds between mouse clicks for them to be considered as 'multi-clicks'.\n     */\n    int getMultiClickInterval();\n\n\n    // - Misc. -----------------------------------------------------------\n    // -------------------------------------------------------------------\n    /**\n     * Returns the command used to start shell processes.\n     * <p>\n     * The returned command must set the shell in its 'init script' mode.\n     * For example, for bash, the returned command should be <code>/bin/bash -l -c\"</code>.\n     * @return the command used to start shell processes.\n     */\n    String getDefaultShell();\n\n    String getDefaultTerminalShellCommand();\n\n    /**\n     * Returns <code>true</code> if the given file is an application file. What an application file actually is\n     * is system-dependent and can take various forms.\n     * It can be a simple executable file, as in the case of Windows <code>.exe</code> files, or a directory \n     * containing an executable and various meta-information files, like Mac OS X's <code>.app</code> files.\n     *\n     * @param file the file to test\n     * @return <code>true</code> if the given file is an application file\n     */\n    boolean isApplication(AbstractFile file);\n\n    String getDefaultTerminalAppCommand();\n\n    default TrashProvider getTrash() {\n        return null;\n    }\n\n    default AbstractNotifier getNotifier() {\n        return null;\n    }\n\n    default Consumer<JTabbedPane> getTabbedPaneCustomizer() {\n        return null;\n    }\n\n    default void postCopy(AbstractFile source, AbstractFile target) {\n\n    }\n\n    default void customizeMainFrame(Window window) {\n\n    }\n\n    default List<Pair<JLabel, JComponent>> getExtendedFileProperties(AbstractFile file) {\n        return Collections.emptyList();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/DesktopInitializationException.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop;\n\n/**\n * Encapsulates errors that occur at {@link DesktopAdapter} initialization time.\n * <p>\n * This class can contain basic error information from either the <code>com.mucommander.desktop</code> API\n * or the application. Application writers can subclass it to provide additional functionality.\n * <p>\n * If the application needs to pass through other types of exceptions, it must wrap them in a\n * <code>DesktopInitializationException</code> or an exception derived from it.\n *\n * @author Nicolas Rinaudo\n */\npublic class DesktopInitializationException extends Exception {\n    /**\n     * Creates a new desktop initialization exception.\n     * @param message the error message.\n     */\n    public DesktopInitializationException(String message) {super(message);}\n\n\n    /**\n     * Creates a new desktop initialization exception wrapping an existing exception.\n     * <p>\n     * The existing exception will be embedded in the new one, and its message will\n     * become the default message for the <code>DesktopInitializationException</code>.\n     *\n     * @param cause the exception to be wrapped in a <code>DesktopInitializationException</code>.\n     */\n    public DesktopInitializationException(Throwable cause) {super(cause);}\n\n    /**\n     * Creates a new desktop initialization exception from an existing exception.\n     * <p>\n     * The existing exception will be embedded in the new one, but the new exception will have its own message.\n     *\n     * @param message the detail message.\n     * @param cause   the exception to be wrapped in a <code>DesktopInitializationException</code>.\n     */\n    public DesktopInitializationException(String message, Throwable cause) {super(message, cause);}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/DesktopManager.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop;\n\nimport java.awt.*;\nimport java.awt.event.MouseEvent;\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.URL;\nimport java.util.*;\nimport java.util.List;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.function.Consumer;\n\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.commons.util.Pair;\nimport com.mucommander.ui.notifier.AbstractNotifier;\nimport lombok.extern.slf4j.Slf4j;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.desktop.gnome.ConfiguredGnomeDesktopAdapter;\nimport com.mucommander.desktop.gnome.GuessedGnomeDesktopAdapter;\nimport com.mucommander.desktop.kde.ConfiguredKde3DesktopAdapter;\nimport com.mucommander.desktop.kde.ConfiguredKde4DesktopAdapter;\nimport com.mucommander.desktop.kde.GuessedKde3DesktopAdapter;\nimport com.mucommander.desktop.kde.GuessedKde4DesktopAdapter;\nimport com.mucommander.desktop.openvms.OpenVMSDesktopAdapter;\nimport com.mucommander.desktop.macos.OSXDesktopAdapter;\nimport com.mucommander.desktop.windows.Win9xDesktopAdapter;\nimport com.mucommander.desktop.windows.WinNtDesktopAdapter;\nimport com.mucommander.desktop.xfce.GuessedXfceDesktopAdapter;\n\nimport javax.swing.*;\n\n/**\n * @author Nicolas Rinaudo\n */\n@Slf4j\npublic class DesktopManager {\n    /**\n     * Represents \"browse\" operations.\n     * <p>\n     * These operations are used to open URL in a browser.\n     *\n     * @see #canBrowse()\n     * @see #browse(URL)\n     */\n    public static final String BROWSE = \"browse\";\n\n    /**\n     * Represents \"file manager\" operations.\n     * <p>\n     * These operations are used to reveal local files in a file manager.\n     *\n     * @see #canOpenInFileManager()\n     * @see #openInFileManager(File)\n     */\n    private static final String OPEN_IN_FILE_MANAGER = \"openFM\";\n\n    /**\n     * Represents \"open\" operations.\n     * <p>\n     * These operations are used to open local files.\n     *\n     * @see #canOpen()\n     * @see #open(File)\n     */\n    static final String OPEN = \"open\";\n\n    /**\n     * Represents system operations.\n     * <p>\n     * These operations are for internal use only, and cannot be registered through the\n     * {@link #registerOperation(String,int,DesktopOperation)} method.\n     *\n     */\n    private static final int SYSTEM_OPERATION = 0;\n\n    /**\n     * Non-system operations.\n     * <p>\n     * These operations are treated with a lower priority than {@link #SYSTEM_OPERATION} ones, but higher\n     * than {@link #FALLBACK_OPERATION} ones. They are meant for plugin specific operations and are expected\n     * to be fairly reliable.\n     *\n     */\n    private static final int CUSTOM_OPERATION = 1;\n\n    /**\n     * Last resort operations.\n     * <p>\n     * These operations will only ever be used if nothing else is available. They need only be a best-effort solution.\n     *\n     */\n    private static final int FALLBACK_OPERATION = 2;\n\n\n\n\n    /** All available desktop operations. */\n    private static final List<Map<String, List<DesktopOperation>>> operations = new ArrayList<>();\n    /** All known desktops. */\n    private static final List<DesktopAdapter> desktops = new CopyOnWriteArrayList<>();\n    /** Current desktop. */\n    private static DesktopAdapter desktop;\n    /** Object used to create instances of {@link AbstractTrash}. */\n    private static TrashProvider trashProvider;\n    /** AbstractNotifier instance, null if none is available on the current platform */\n    private static AbstractNotifier notifier;\n    private static Consumer<JTabbedPane> tabbedPaneCustomizer;\n\n\n    /**\n     * Prevents instantiation of the class.\n     */\n    private DesktopManager() {}\n\n    /*\n     * Static initialization.\n     * Bear in mind that adapters and operations are registered the 'wrong' way around:\n     * the earlier they are registered, the lower their priority.\n     */\n    static {\n        for (int i = 0; i < 3; i++) {\n            operations.add(new ConcurrentHashMap<>());\n        }\n        // The default desktop adapter must be registered first, as we only want to use it if nothing else worked.\n        registerAdapter(new DefaultDesktopAdapter());\n\n        // Unix desktops:\n        // - check for Gnome before KDE, as it seems to be more popular.\n        // - check for 'configured' before 'guessed', as guesses are less reliable and more expensive.\n        if (OsFamily.getCurrent() == OsFamily.LINUX) {\n            registerAdapter(new GuessedXfceDesktopAdapter());\n            registerAdapter(new GuessedKde3DesktopAdapter());\n            registerAdapter(new GuessedKde4DesktopAdapter());\n            registerAdapter(new GuessedGnomeDesktopAdapter());\n            registerAdapter(new ConfiguredKde3DesktopAdapter());\n            registerAdapter(new ConfiguredKde4DesktopAdapter());\n            registerAdapter(new ConfiguredGnomeDesktopAdapter());\n        }\n        // Known OS adapters.\n        registerAdapter(new OpenVMSDesktopAdapter());\n        if (OsFamily.getCurrent() == OsFamily.WINDOWS) {\n            registerAdapter(new OSXDesktopAdapter());\n            registerAdapter(new Win9xDesktopAdapter());\n            registerAdapter(new WinNtDesktopAdapter());\n        }\n\n        // Having 1.6 specific operations registered as the lowest priority system\n        // ones ensures that:\n        // - they are executed after CommandXXX operations (important, since CommandXXX\n        //   operations are user configurable).\n        // - they are executed before any other operations (if available, they will\n        //   provide safer and better integration than any other operation).\n        innerRegisterOperation(OPEN,   SYSTEM_OPERATION,  new InternalOpen());\n        innerRegisterOperation(BROWSE, SYSTEM_OPERATION,  new InternalBrowse());\n\n        // Registers CommandXXX operations.\n        innerRegisterOperation(BROWSE,               SYSTEM_OPERATION,    new CommandBrowse());\n        innerRegisterOperation(OPEN_IN_FILE_MANAGER, SYSTEM_OPERATION,    new CommandOpenInFileManager());\n        innerRegisterOperation(OPEN,                 SYSTEM_OPERATION,    new CommandOpen(false));\n\n        // The only FALLBACK operation we have at the time of writing is for OPEN,\n        // where we can try to init the file as if it was an executable.\n        innerRegisterOperation(OPEN, FALLBACK_OPERATION,  new CommandOpen(true));\n    }\n\n    /**\n     * Initializes desktop management.\n     * <p>\n     * If <code>install</code> is set to <code>true</code>, this method might result in installing desktop specific\n     * data such as bookmarks, keyboard shortcuts...\n     *\n     * @param install                         whether to install desktop specific information.\n     * @throws DesktopInitializationException if an error occurred while initializing desktops.\n     */\n    public static void init(boolean install) throws DesktopInitializationException {\n        // Browses desktop from the last registered to the first, to make sure that\n        // custom desktop adapters are used before the default ones.\n        for (int i = desktops.size() - 1; i >= 0; i--) {\n            DesktopAdapter current = desktops.get(i);\n            if (current.isAvailable()) {\n                desktop = current;\n                log.debug(\"Using desktop: {}\", desktop);\n                desktop.init(install);\n                setTrashProvider(desktop.getTrash());\n                setNotifier(desktop.getNotifier());\n                setTabbedPaneCustomizer(desktop.getTabbedPaneCustomizer());\n                return;\n            }\n        }\n    }\n\n    /**\n     * Makes sure that we have a {@link DesktopAdapter} to work with.\n     * <p>\n     * If the {@link #init(boolean)} method wasn't called, the <code>DesktopManager</code>\n     * will find itself in a situation where it doesn't know which desktop it's running on.\n     * Calling this method ensures that, if a {@link DesktopAdapter} instance is required,\n     * we have at least the {@link DefaultDesktopAdapter} to work with.\n     */\n    private static void checkInit() {\n        if (desktop == null) {\n            desktop = new DefaultDesktopAdapter();\n        }\n    }\n\n\n    /**\n     * Registers the specified {@link DesktopAdapter}.\n     * <p>\n     * Note that the later an adapter is registered, the higher its priority. Since all\n     * default adapters are registered at initialization time, any call to this method\n     * will result in the new adapter to be checked before them.\n     *\n     * @param adapter desktop adapter to register.\n     */\n    public static void registerAdapter(DesktopAdapter adapter) {\n        desktops.add(adapter);\n    }\n\n\n    /**\n     * Registers the specified operation for the specified type and priority.\n     */\n    private static void innerRegisterOperation(String type, int priority, DesktopOperation operation) {\n        // Makes sure we have a container for operations of the specified priority.\n        if (operations.get(priority) == null) {\n            operations.set(priority, new ConcurrentHashMap<>());\n        }\n\n        // Makes sure we have a container for operations of the specified type.\n        List<DesktopOperation> container = operations.get(priority).computeIfAbsent(type, k -> new CopyOnWriteArrayList<>());\n\n        // Creates the requested entry.\n        container.add(operation);\n    }\n\n    public static void registerOperation(String type, int priority, DesktopOperation operation) {\n        if (priority != FALLBACK_OPERATION && priority != CUSTOM_OPERATION) {\n            throw new IllegalArgumentException();\n        }\n        innerRegisterOperation(type, priority, operation);\n    }\n\n\n    private static List<DesktopOperation> getOperations(String type, int priority) {\n        if (operations.get(priority) == null) {\n            return null;\n        }\n        return operations.get(priority).get(type);\n    }\n\n    private static DesktopOperation getAvailableOperation(String type, int priority) {\n        List<DesktopOperation> container = getOperations(type, priority);\n\n        // If the operation vector is null, no need to look further.\n        if (container != null) {\n            for (int i = container.size() - 1; i >= 0; i--) {\n                DesktopOperation operation = container.get(i);\n                if (operation.isAvailable()) {\n                    return operation;\n                }\n            }\n        }\n        return null;\n    }\n\n    private static DesktopOperation getSupportedOperation(String type, int priority, Object[] target) {\n        List<DesktopOperation> container = getOperations(type, priority);\n\n        // If the operation vector is null, no need to look further.\n        if (container != null)\n            for (int i = container.size() - 1; i >= 0; i--) {\n                DesktopOperation operation = container.get(i);\n                if (operation.canExecute(target)) {\n                    return operation;\n                }\n            }\n        return null;\n    }\n\n    private static DesktopOperation getSupportedOperation(String type, Object[] target) {\n        DesktopOperation operation = getSupportedOperation(type, SYSTEM_OPERATION, target);\n        if (operation != null) {\n            return operation;\n        }\n        operation = getSupportedOperation(type, CUSTOM_OPERATION, target);\n        if (operation != null) {\n            return operation;\n        }\n        operation = getSupportedOperation(type, FALLBACK_OPERATION, target);\n        return operation;\n    }\n\n    private static DesktopOperation getAvailableOperation(String type) {\n        DesktopOperation systemOperation = getAvailableOperation(type, SYSTEM_OPERATION);\n        if (systemOperation != null) {\n            return systemOperation;\n        }\n        DesktopOperation customOperation = getAvailableOperation(type, CUSTOM_OPERATION);\n        if (customOperation != null) {\n            return customOperation;\n        }\n        return getAvailableOperation(type, FALLBACK_OPERATION);\n    }\n\n    public static boolean isOperationAvailable(String type) {\n        return getAvailableOperation(type) != null;\n    }\n\n    public static boolean isOperationSupported(String type, Object[] target) {\n        return getSupportedOperation(type, target) != null;\n    }\n\n    public static void executeOperation(String type, Object[] target) throws IOException, UnsupportedOperationException {\n        DesktopOperation operation = getSupportedOperation(type, target);\n        if (operation == null) {\n            log.info(\"Unsupported operation {} {}\", type, target);\n            throw new UnsupportedOperationException();\n        }\n        operation.execute(target);\n    }\n\n    public static String getOperationName(String type) throws UnsupportedOperationException {\n        DesktopOperation operation = getAvailableOperation(type);\n        if (operation == null) {\n            throw new UnsupportedOperationException();\n        }\n        return operation.getName();\n    }\n\n    public static String getOperationName(String type, Object[] target) throws UnsupportedOperationException {\n        DesktopOperation operation = getSupportedOperation(type, target);\n        if (operation == null) {\n            throw new UnsupportedOperationException();\n        }\n        return operation.getName();\n    }\n\n    public static boolean canBrowse() {\n        return isOperationAvailable(BROWSE);\n    }\n\n    public static boolean canBrowse(URL url) {\n        return isOperationSupported(BROWSE, new Object[] {url});\n    }\n\n    public static boolean canBrowse(String url) {\n        return isOperationSupported(BROWSE, new Object[] {url});\n    }\n\n    public static boolean canBrowse(AbstractFile url) {\n        return isOperationSupported(BROWSE, new Object[] {url});\n    }\n\n    public static void browse(URL url) throws IOException, UnsupportedOperationException {\n        executeOperation(BROWSE, new Object[] {url});\n    }\n\n    public static void browse(String url) throws IOException, UnsupportedOperationException {\n        executeOperation(BROWSE, new Object[] {url});\n    }\n\n    public static void browse(AbstractFile url) throws IOException, UnsupportedOperationException {\n        executeOperation(BROWSE, new Object[] {url});\n    }\n\n    public static boolean canOpen() {\n        return isOperationAvailable(OPEN);\n    }\n\n    public static boolean canOpen(File file) {\n        return isOperationSupported(OPEN, new Object[] {file});\n    }\n\n    public static boolean canOpen(String file) {\n        return isOperationSupported(OPEN, new Object[] {file});\n    }\n\n    public static boolean canOpen(AbstractFile file) {\n        return isOperationSupported(OPEN, new Object[] {file});\n    }\n\n    public static void open(File file) throws IOException, UnsupportedOperationException {\n        executeOperation(OPEN, new Object[] {file});\n    }\n\n    public static void open(String file) throws IOException, UnsupportedOperationException {\n        executeOperation(OPEN, new Object[] {file});\n    }\n\n    public static void open(AbstractFile file) throws IOException, UnsupportedOperationException {\n        if (file.isDirectory()) {\n            executeOperation(OPEN_IN_FILE_MANAGER, new Object[] {file});\n        } else {\n            executeOperation(OPEN, new Object[]{file});\n        }\n    }\n\n    public static boolean canOpenInFileManager() {\n        return isOperationAvailable(OPEN_IN_FILE_MANAGER);\n    }\n\n    public static boolean canOpenInFileManager(File file) {\n        return isOperationSupported(OPEN_IN_FILE_MANAGER, new Object[] {file});\n    }\n\n    public static boolean canOpenInFileManager(String file) {\n        return isOperationSupported(OPEN_IN_FILE_MANAGER, new Object[] {file});\n    }\n\n    public static boolean canOpenInFileManager(AbstractFile file) {\n        return isOperationSupported(OPEN_IN_FILE_MANAGER, new Object[] {file});\n    }\n\n    public static void openInFileManager(File file) throws IOException, UnsupportedOperationException {\n        executeOperation(OPEN_IN_FILE_MANAGER, new Object[] {file});\n    }\n\n    public static void openInFileManager(String file) throws IOException, UnsupportedOperationException {\n        executeOperation(OPEN_IN_FILE_MANAGER, new Object[] {file});\n    }\n\n    public static void openInFileManager(AbstractFile file) throws IOException, UnsupportedOperationException {\n        executeOperation(OPEN_IN_FILE_MANAGER, new Object[] {file});\n    }\n\n    private static String getFileManagerName(DesktopOperation operation) throws UnsupportedOperationException {\n        if (operation == null) {\n            throw new UnsupportedOperationException();\n        }\n        return operation.getName();\n    }\n\n    public static String getFileManagerName() throws UnsupportedOperationException {\n        return getFileManagerName(getAvailableOperation(OPEN_IN_FILE_MANAGER));\n    }\n\n    public static String getFileManagerName(File file) throws UnsupportedOperationException {\n        return getFileManagerName(getSupportedOperation(OPEN_IN_FILE_MANAGER, new Object[] {file}));\n    }\n\n    public static String getFileManagerName(String file) throws UnsupportedOperationException {\n        return getFileManagerName(getSupportedOperation(OPEN_IN_FILE_MANAGER, new Object[] {file}));\n    }\n\n    public static String getFileManagerName(AbstractFile file) throws UnsupportedOperationException {\n        return getFileManagerName(getSupportedOperation(OPEN_IN_FILE_MANAGER, new Object[] {file}));\n    }\n\n    /**\n     * Returns an instance of the {@link com.mucommander.desktop.AbstractTrash} implementation that can be used on the current platform.\n     * @return an instance of the AbstractTrash implementation that can be used on the current platform, or <code>null</code> if none is available.\n     */\n    public static AbstractTrash getTrash() {\n        TrashProvider provider = getTrashProvider();\n        return provider == null ? null : provider.getTrash();\n    }\n\n    /**\n     * Returns the object used to create instances of {@link com.mucommander.desktop.AbstractTrash}.\n     * @return the object used to create instances of {@link AbstractTrash} if any, <code>null</code> otherwise.\n     */\n    public static TrashProvider getTrashProvider() {\n        return trashProvider;\n    }\n\n    /**\n     * Sets the object that is used to create instances of {@link com.mucommander.desktop.AbstractTrash}.\n     * @param provider object that will be used to create instances of {@link com.mucommander.desktop.AbstractTrash}.\n     */\n    public static void setTrashProvider(TrashProvider provider) {\n        trashProvider = provider;\n    }\n\n    private static void setTabbedPaneCustomizer(Consumer<JTabbedPane> tabbedPaneCustomizer) {\n        DesktopManager.tabbedPaneCustomizer = tabbedPaneCustomizer;\n    }\n\n    private static void setNotifier(AbstractNotifier notifier) {\n        DesktopManager.notifier = notifier;\n    }\n\n    public static AbstractNotifier getNotifier() {\n        return notifier;\n    }\n\n    /**\n     * Checks whether the specified <code>MouseEvent</code> is a left-click for this desktop.\n     * <p>\n     * There are some cases where Java doesn't detect mouse events properly - for example,\n     * <i>CONTROL + LEFT CLICK</i> is a <i>RIGHT CLICK</i> under Mac OS X.<br>\n     * The goal of this method is to allow desktop to check for such non-standard behaviors.\n     *\n     * @param  e event to check.\n     * @return   <code>true</code> if the specified event is a left-click for this desktop, <code>false</code> otherwise.\n     * @see      #isRightMouseButton(MouseEvent)\n     * @see      #isMiddleMouseButton(MouseEvent)\n     */\n    public static boolean isLeftMouseButton(MouseEvent e) {\n        checkInit();\n        return desktop.isLeftMouseButton(e);\n    }\n\n    /**\n     * Checks whether the specified <code>MouseEvent</code> is a left-click for this desktop.\n     * <p>\n     * There are some cases where Java doesn't detect mouse events properly - for example,\n     * <i>CONTROL + LEFT CLICK</i> is a <i>RIGHT CLICK</i> under Mac OS X.<br>\n     * The goal of this method is to allow desktop to check for such non-standard behaviors.\n     *\n     * @param  e event to check.\n     * @return   <code>true</code> if the specified event is a left-click for this desktop, <code>false</code> otherwise.\n     * @see      #isMiddleMouseButton(MouseEvent)\n     * @see      #isLeftMouseButton(MouseEvent)\n     */\n    public static boolean isRightMouseButton(MouseEvent e) {\n        checkInit();\n        return desktop.isRightMouseButton(e);\n    }\n\n    /**\n     * Checks whether the specified <code>MouseEvent</code> is a left-click for this desktop.\n     * <p>\n     * There are some cases where Java doesn't detect mouse events properly - for example,\n     * <i>CONTROL + LEFT CLICK</i> is a <i>RIGHT CLICK</i> under Mac OS X.<br>\n     * The goal of this method is to allow desktop to check for such non-standard behaviors.\n     *\n     * @param  e event to check.\n     * @return   <code>true</code> if the specified event is a left-click for this desktop, <code>false</code> otherwise.\n     * @see      #isRightMouseButton(MouseEvent)\n     * @see      #isLeftMouseButton(MouseEvent)\n     */\n    public static boolean isMiddleMouseButton(MouseEvent e) {\n        checkInit();\n        return desktop.isMiddleMouseButton(e);\n    }\n\n    /**\n     * Returns the maximum interval in milliseconds between mouse clicks for them to be considered as 'multi-clicks'\n     * (e.g. double-clicks). The returned value should reflects the desktop's multi-click (or double-click) interval,\n     * which may or may not correspond to the one Java uses for double-clicks.\n     * @return the maximum interval in milliseconds between mouse clicks for them to be considered as 'multi-clicks'.\n     */\n    public static int getMultiClickInterval() {\n        checkInit();\n        return desktop.getMultiClickInterval();\n    }\n\n    /**\n     * Returns the command used to start shell processes.\n     * <p>\n     * The returned command must set the shell in its 'init script' mode.\n     * For example, for bash, the returned command should be <code>/bin/bash -l -c\"</code>.\n     *\n     * @return the command used to start shell processes.\n     */\n    public static String getDefaultShell() {\n        checkInit();\n        return desktop.getDefaultShell();\n    }\n\n    public static String getDefaultTerminalShellCommand() {\n        checkInit();\n        return desktop.getDefaultTerminalShellCommand();\n    }\n\n    public static String getDefaultTerminalAppCommand() {\n        checkInit();\n        return desktop.getDefaultTerminalAppCommand();\n    }\n\n\n    /**\n     * Returns <code>true</code> if the given file is an application file. What an application file actually is\n     * is system-dependent and can take various forms.\n     * It can be a simple executable file, as in the case of Windows <code>.exe</code> files, or a directory\n     * containing an executable and various meta-information files, like Mac OS X's <code>.app</code> files.\n     *\n     * @param file the file to test\n     * @return <code>true</code> if the given file is an application file\n     */\n    public static boolean isApplication(AbstractFile file) {\n        return desktop.isApplication(file);\n    }\n\n    public static void customizeTabbedPaneUI(JTabbedPane tabbedPane) {\n        if (tabbedPaneCustomizer != null)\n            tabbedPaneCustomizer.accept(tabbedPane);\n    }\n\n    public static void postCopy(AbstractFile source, AbstractFile target) {\n        desktop.postCopy(source, target);\n    }\n\n    public static void customizeMainFrame(Window window) {\n        desktop.customizeMainFrame(window);\n    }\n\n    public static List<Pair<JLabel, JComponent>> getExtendedFileProperties(AbstractFile file) {\n        return desktop.getExtendedFileProperties(file);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/DesktopOperation.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop;\n\nimport java.io.IOException;\n\n/**\n * Contract for basic desktop operations.\n * <p>\n * A desktop operation is an system dependent operation tied to the desktop, such as opening a file\n * or launching a web browser on a specified URL.\n * <p>\n * They are meant to be as extensible as possible. This, however, comes with a cost:\n * they can prove to be quite complex to understand and use. Before writing an implementation of\n * this interface, application developers should make sure they understand the following points:\n * <ul>\n *   <li>Generic execution</li>\n *   <li><i>Available</i> Vs <i>Supported</i></li>\n * </ul>\n * <h3>Generic execution</h3>\n * <p>\n * Desktop operations receive an <code>Object[]</code> as parameter to their {@link #execute(Object[])} method.\n * This allows the API to be rather flexible, if a bit more obscure.<br>\n * The basic unwritten contract that all operations must respect is that, for a given operation type, the same\n * parameters classes must be accepted. There is no way to enforce that and keep the flexibility, which means that the\n * responsibility for this lies on the application developer.\n *\n * <p>\n * At the time of writing, the API uses two different types of operations: {@link DesktopManager#BROWSE URL browsing}\n *  and {@link DesktopManager#OPEN local file opening}.\n * Adapters have been provided for these: {@link UrlOperation} and {@link LocalFileOperation}.\n *\n * <h3><i>Available</i> Vs <i>Supported</i></h3>\n * <p>\n * An operation is said to available if it will accept any parameter that matches its contract. For example,\n * an operation that works on local files will be available if it accepts any <code>java.io.File</code>, <code>String</code>\n * or {@link com.mucommander.commons.file.impl.local.LocalFile} parameter.\n *\n * <p>\n * An operation is said to be supported for a specific parameter subset it will accept any parameter that\n * match that subset.\n * <p>\n * An <i>available</i> operation is always supported. However, it's entirely possible for an operation to be supported\n * for some parameters but not others, and thus not to be available.<br>\n * For example, an operation that deals with XML files only will be supported for any parameter that describes a local XML file,\n * but won't be available as it will refuse plain text files.\n *\n * @author Nicolas Rinaudo\n */\npublic interface DesktopOperation {\n    /**\n     * Returns the operation's name.\n     * <p>\n     * The returned value might be displayed to the user. It should thus be made as human-readable as possible\n     * and, if possible, localised.\n     *\n     * @return the operation's name.\n     */\n    String getName();\n\n    /**\n     * Checks whether the operation is available.\n     * <p>\n     * An operation is said to be available if and only if any call to\n     * {@link #canExecute(Object[])} with parameters that match its constraints\n     * will return <code>true</code>.\n     * <p>\n     * For example, an operation of type {@link DesktopManager#BROWSE} that accepts\n     * any and all HTTP URLs is available. However, an operation of type\n     * {@link DesktopManager#OPEN} that only accepts XML files isn't.\n     *\n     * @return <code>true</code> if the operation is available, <code>false</code> otherwise.\n     * @see    #canExecute(Object[])\n     */\n    boolean isAvailable();\n\n    /**\n     * Checks whether an operation is supported for the specified parameters.\n     * <p>\n     * If the operation is {@link #isAvailable() available}, then this method must always\n     * return <code>true</code>.\n     * <p>\n     * If the operation isn't available, but the specified parameters match a subset of legal\n     * parameters that it knows how to deal with, this method must return <code>true</code>.\n     * <p>\n     * In any other case, this method must return <code>false</code>.\n     * <p>\n     * For example, a {@link DesktopManager#OPEN} operation that only accept XML files will return:\n     * <ul>\n     *   <li>\n     *     <code>true</code> if the parameter array contains a single instance of either <code>java.io.File</code>,\n     *     <code>String</code> or {@link com.mucommander.commons.file.impl.local.LocalFile} and that instance describes the\n     *     path to a valid XML file.\n     *   </li>\n     *   <li>\n     *     <code>false</code> if the specified parameters are not valid or if they do not describe the path to\n     *     a valid XML file.\n     *   </li>\n     * </ul>\n     * @param  target parameters to check.\n     * @return        <code>true</code> if the operation can be executed with the specified parameters, <code>false</code> otherwise.\n     */\n    boolean canExecute(Object[] target);\n\n    /**\n     * Executes the operation on the specified parameters.\n     * <p>\n     * There is no guarantee that this method is available for the specified parameters. This must be checked\n     * through {@link #canExecute(Object[])}.\n     *\n     * @param  target                        parameters on which to execute the operation.\n     * @throws IOException                   if an error occurs.\n     * @throws UnsupportedOperationException if the operation is not supported for the specified parameters.\n     */\n    void execute(Object[] target) throws IOException, UnsupportedOperationException;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/InternalBrowse.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop;\n\nimport java.awt.*;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.net.URL;\n\n/**\n * @author Nicolas Rinaudo\n */\nclass InternalBrowse extends UrlOperation {\n    /** Underlying desktop instance. */\n    private Desktop desktop;\n    private boolean initialized = false;\n\n    /**\n     * Creates a new <code>InternalOpenUrl</code> instance.\n     */\n    InternalBrowse() {\n    }\n\n    private Desktop getDesktop() {\n        if (!initialized) {\n            if (Desktop.isDesktopSupported()) {\n                desktop = Desktop.getDesktop();\n            }\n            initialized = true;\n        }\n        return desktop;\n    }\n\n    /**\n     * Returns <code>true</code> if this operation is available.\n     * <p>\n     * This operation is available if:\n     * <ul>\n     *   <li>Desktops are supported by the current system (<code>Desktop.isDesktopSupported()</code> returns <code>true</code>).</li>\n     *   <li>Browsing is supported by the desktop (<code>Desktop.isSupported(Desktop.Action.BROWSE)</code> returns <code>true</code>).</li>\n     * </ul>\n     *\n     * @return <code>true</code> if this operations is available, <code>false</code> otherwise.\n     */\n    @Override\n    public boolean isAvailable() {\n        return getDesktop() != null && getDesktop().isSupported(Desktop.Action.BROWSE);\n    }\n\n    /**\n     * Opens the specified URL in the system's default browser.\n     * @param  url         URL to browse.\n     * @throws IOException if an error occured.\n     */\n    @Override\n    public void execute(URL url) throws IOException {\n        // If java.awt.Desktop browsing is available, use it.\n        if (isAvailable()) {\n            try {\n                getDesktop().browse(url.toURI());\n            } catch(URISyntaxException e) {\n                throw new IOException(e.getMessage());\n            }\n        }\n\n        throw new UnsupportedOperationException();\n    }\n\n    /**\n     * Returns the action's label.\n     * @return the action's label.\n     */\n    @Override\n    public String getName() {\n        return \"java.awt.Desktop open URL\";\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/InternalOpen.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop;\n\nimport com.mucommander.commons.file.AbstractFile;\n\nimport java.awt.*;\nimport java.io.File;\nimport java.io.IOException;\n\n/**\n * @author Nicolas Rinaudo\n */\nclass InternalOpen extends LocalFileOperation {\n    /** Underlying desktop instance. */\n    private Desktop desktop;\n    private boolean initialized = false;\n\n    /**\n     * Creates a new <code>InternalOpen</code> instance.\n     */\n    InternalOpen() {\n    }\n\n    private Desktop getDesktop() {\n        if (!initialized) {\n            if (Desktop.isDesktopSupported()) {\n                desktop = Desktop.getDesktop();\n            }\n            initialized = true;\n        }\n        return desktop;\n    }\n\n    /**\n     * Returns <code>true</code> if this operation is available.\n     * <p>\n     * This operation is available if:\n     * <ul>\n     *   <li>Desktops are supported by the current system (<code>Desktop.isDesktopSupported()</code> returns <code>true</code>).</li>\n     *   <li>File opening is supported by the desktop (<code>Desktop.isSupported(Desktop.Action.OPEN)</code> returns <code>true</code>).</li>\n     * </ul>\n     * @return <code>true</code> if this operations is available, <code>false</code> otherwise.\n     */\n    @Override\n    public boolean isAvailable() {return getDesktop() != null && getDesktop().isSupported(Desktop.Action.OPEN);}\n\n    @Override\n    public void execute(AbstractFile file) throws IOException {\n        if (isAvailable()) {\n            getDesktop().open(new File(file.getAbsolutePath()));\n        } else {\n            throw new UnsupportedOperationException();\n        }\n    }\n\n    /**\n     * @return the action's label.\n     */\n    @Override\n    public String getName() {\n        return \"java.awt.Desktop open file\";\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/LocalFileOperation.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport com.mucommander.commons.file.impl.local.SpecialWindowsLocation;\n\nimport java.io.File;\nimport java.io.IOException;\n\n/**\n * {@link DesktopOperation} implementation meant for actions that involve local files.\n * <p>\n * Instead of having to deal with the {@link DesktopOperation#canExecute(Object[])}\n * and {@link DesktopOperation#execute(Object[])}, instances of <code>LocalFileOperation</code>\n * can use {@link #canExecute(AbstractFile)} and {@link #execute(AbstractFile)} and ignore the complexity of\n * the desktop API's genericity.\n *\n * @author Nicolas Rinaudo\n */\npublic abstract class LocalFileOperation implements DesktopOperation {\n    public abstract String getName();\n    public abstract boolean isAvailable();\n\n    /**\n     * Executes the operation on the specified file.\n     * @param  file                          file on which to execute the operation.\n     * @throws IOException                   if an error occurs.\n     * @throws UnsupportedOperationException if the operation is not supported.\n     */\n    public abstract void execute(AbstractFile file) throws IOException, UnsupportedOperationException;\n\n    /**\n     * Checks whether the operation knows how to deal with the specified file.\n     * <p>\n     * By default, this method returns {@link #isAvailable()}. However, some implementations\n     * might want to overwrite it. For example, a <code>LocalFileOperation</code> that only works\n     * on XML files would override this method to only return <code>true</code> if the specified\n     * file is an XML one.\n     *\n     * @param  file file to check against.\n     * @return      <code>true</code> if the operation is supported for the specified file, <code>false</code> otherwise.\n     */\n    public boolean canExecute(AbstractFile file) {\n        return isAvailable();\n    }\n\n    /**\n     * Returns <code>true</code> if the operation is supported for the specified parameters.\n     * <p>\n     * By default, this method will call {@link #extractTarget(Object[])} on the specified parameters\n     * and pass the resulting {@link AbstractFile} instance to {@link #canExecute(AbstractFile)}.\n     * <p>\n     * This behavior can be overridden by implementations, although most cases can be handled through\n     * {@link #canExecute(AbstractFile)} instead.\n     * \n     * @param  target operation parameters.\n     * @return        <code>true</code> if the operation is supported for the specified parameters, <code>false</code> otherwise.\n     * @see           #canExecute(AbstractFile)\n     * @see           #extractTarget(Object[])\n     */\n    public boolean canExecute(Object[] target) {\n        AbstractFile file = extractTarget(target);\n        return file != null && canExecute(file);\n    }\n\n    /**\n     * Analyses the specified parameters and delegates the operation execution to {@link #execute(AbstractFile)}.\n     * <p>\n     * This method is a wrapper for {@link #extractTarget(Object[])} and {@link #execute(AbstractFile)}. Most\n     * implementations should ignore it.\n     *\n     * @param  target                        parameters of the operation.\n     * @throws IOException                   if an error occurs.\n     * @throws UnsupportedOperationException if the operation is not supported.\n     * @see                                  #execute(AbstractFile)\n     * @see                                  #extractTarget(Object[])\n     */\n    public void execute(Object[] target) throws IOException, UnsupportedOperationException {\n        AbstractFile file  = extractTarget(target);\n\n        // Makes sure we received the right kind of parameters.\n        if (file == null) {\n            throw new UnsupportedOperationException();\n        }\n\n        // Execute the operation.\n        execute(file);\n    }\n\n    /**\n     * Analyses the specified parameters and returns them in a form that can be used.\n     * <p>\n     * By default, this method will return <code>null</code> unless <code>target</code>:\n     * <ul>\n     *   <li>has a length of 1.</li>\n     *   <li>\n     *     contains an instance of either <code>java.io.File</code>, {@link com.mucommander.commons.file.impl.local.LocalFile}, <code>String</code>\n     *     or {@link com.mucommander.commons.file.impl.local.SpecialWindowsLocation}.\n     *   </li>\n     * </ul>\n     * <p>\n     * This behavior can be overridden by implementations to fit their own needs, although it's probably not a great idea.\n     *\n     * @param  target operation parameters.\n     * @return        <code>null</code> if the parameters are not legal, a {@link com.mucommander.commons.file.AbstractFile} instance instead.\n     */\n    protected AbstractFile extractTarget(Object[] target) {\n        // We only deal with arrays containing 1 element.\n        if (target.length != 1) {\n            return null;\n        }\n\n        // If we find an instance of java.io.File, we can stop here.\n        if (target[0] instanceof File) {\n            return FileFactory.getFile(((File) target[0]).getAbsolutePath());\n        }\n\n        if (target[0] instanceof SpecialWindowsLocation) {\n            return (AbstractFile) target[0];\n        }\n\n        // Deals with instances of LocalFile: raw instances or wrapped in another AbstractFile container (e.g. archive files)\n        if (target[0] instanceof AbstractFile && ((AbstractFile)target[0]).hasAncestor(LocalFile.class)) {\n            return (AbstractFile) target[0];\n        }\n\n        // Deals with instances of String.\n        if (target[0] instanceof String) {\n            return FileFactory.getFile((String) target[0]);\n        }\n\n        // Illegal parameters.\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/QueuedTrash.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.InformationDialog;\nimport com.mucommander.ui.main.WindowManager;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * QueuedTrash is an {@link AbstractTrash} which moves files to the trash asynchroneously.\n *\n * <p>\n * When {@link #moveToTrash(com.mucommander.commons.file.AbstractFile)} is called, the file is added to a queue.\n * The file is not moved to the trash immediately: the trash will wait a period of {@link #QUEUE_PERIOD} milliseconds\n * for additional files to be added. If files were added during that period, the trash will wait another period and\n * so on. When no more files are added were added during the period, {@link #moveToTrash(java.util.List)} is called\n * with the list of queued files to move to the trash.\n *\n * <p>\n * This mechanism allows to group calls to the underlying trash. It is effective when the atomic operation\n * of moving a file to the trash has a high cost and {@link #moveToTrash(com.mucommander.commons.file.AbstractFile)} is called\n * repeatedly. One thing to note is since the move is performed asynchroneously,\n * {@link #moveToTrash(com.mucommander.commons.file.AbstractFile)} returns immediately without waiting for the file to be moved,\n * {@link #waitForPendingOperations()} can be used to wait for the files to have effectively been moved.\n *\n * @author Maxence Bernard\n */\npublic abstract class QueuedTrash extends AbstractTrash {\n\n    /** Contains the files that are waiting to be moved to the trash */\n    private final static List<AbstractFile> queuedFiles = new ArrayList<>();\n\n    /** Use to synchronize access to the trash */\n    private final static Object moveToTrashLock = new Object();\n\n    /** Thread that performs the actual job of moving files to the trash */\n    private static Thread moveToTrashThread;\n\n    /** Amount of time in milliseconds to wait for additional files before moving them to the trash */\n    protected final static int QUEUE_PERIOD = 1000;\n\n\n    /**\n     * Moves the {@link AbstractFile} instances contained in the given <code>Vector</code> to the trash.\n     * Returns <code>true</code> if all files were moved successfully.\n     *\n     * @param queuedFiles a Vector of AbstractFile to move to the trash\n     * @return true if all files were moved successfully\n     */\n    protected abstract boolean moveToTrash(List<AbstractFile> queuedFiles);\n\n\n    //////////////////////////////////\n    // AbstractTrash implementation //\n    //////////////////////////////////\n\n    /**\n     * Implementation notes: this method adds the given file to the queue of files to be moved to the trash and returns\n     * immediately, i.e. without waiting for the file to be moved. The specified file will only be added to the queue if\n     * {@link #canMoveToTrash(com.mucommander.commons.file.AbstractFile)} returned <code>true</code> for it.\n     * Since the actual move is performed asynchroneously, this method has no way of\n     * knowing if the file was successfully moved to the trash. So this method will return <code>true</code> if the\n     * given file has been scheduled to be moved to the trash, but it may end up failing to be moved for whatever reason.\n     */\n    @Override\n    public boolean moveToTrash(AbstractFile file) {\n        if (!canMoveToTrash(file)) {\n            return false;\n        }\n\n        synchronized(moveToTrashLock) {\n            // Queue the given file\n            queuedFiles.add(file);\n\n            // create a new thread and start it if one isn't already running\n            if (moveToTrashThread == null) {\n                moveToTrashThread = new MoveToTrashThread();\n                moveToTrashThread.start();\n            }\n        }\n\n        return true;\n    }\n\n    @Override\n    public void waitForPendingOperations() {\n        synchronized(moveToTrashLock) {\n            if (moveToTrashThread != null) {\n                try {\n                    // Wait until moveToTrashThread wakes this thread up\n                    moveToTrashLock.wait();\n                } catch(InterruptedException ignore) {}\n            }\n        }\n    }\n\n    ///////////////////\n    // Inner classes //\n    ///////////////////\n\n    /**\n     * Performs the actual job of moving files to the trash.\n     *\n     * <p>The thread starts by waiting {@link com.mucommander.desktop.macos.OSXTrash#QUEUE_PERIOD} milliseconds before moving them to give additional\n     * files a chance to be queued and regrouped as a single call to {@link QueuedTrash#moveToTrash(java.util.List)}.\n     * If more files were queued during that period, the thread will wait an additional {@link com.mucommander.desktop.macos.OSXTrash# QUEUE_PERIOD},\n     * and so on.\n     */\n    private class MoveToTrashThread extends Thread {\n\n        @Override\n        public void run() {\n            // Loops until no files were added during the sleep period\n            int queueSize;\n            do {\n                queueSize = queuedFiles.size();\n\n                try {\n                    Thread.sleep(QUEUE_PERIOD);\n                } catch(InterruptedException ignore) {}\n            } while (queueSize != queuedFiles.size());\n\n            synchronized(moveToTrashLock) {     // Files can't be added to queue while files are moved to trash\n                if (!moveToTrash(queuedFiles)) {\n                    InformationDialog.showErrorDialog(WindowManager.getCurrentMainFrame().getJFrame(), Translator.get(\"delete_dialog.move_to_trash.option\"), Translator.get(\"delete_dialog.move_to_trash.failed\"));\n                }\n\n                queuedFiles.clear();\n                // Wake up any thread waiting for this thread to be finished\n                moveToTrashLock.notify();\n                moveToTrashThread = null;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/TrashProvider.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop;\n\n/**\n * TrashProvider provides a way to instantiate {@link AbstractTrash} implementations.\n *\n * <p>Trash providers can be registered with {@link DesktopManager#setTrashProvider(TrashProvider)}\n * for them to become the default trash one.\n *\n * @see com.mucommander.desktop.AbstractTrash\n * @see DesktopManager#setTrashProvider(TrashProvider)\n * @author Nicolas Rinaudo\n */\npublic interface TrashProvider {\n\n    /**\n     * Returns a trash instance.\n     * \n     * @return a trash instance\n     */\n    AbstractTrash getTrash();\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/UrlOperation.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.impl.http.HTTPFile;\n\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.URL;\n\n/**\n * {@link DesktopOperation} implementation meant for actions that involve <code>java.net.URL</code>.\n * <p>\n * Instead of having to deal with the {@link DesktopOperation#canExecute(Object[])}\n * and {@link DesktopOperation#execute(Object[])}, instances of <code>LocalFileOperation</code>\n * can use {@link #canExecute(URL)} and {@link #execute(URL)} and ignore the complexity of\n * the desktop API's genericity.\n *\n * @author Nicolas Rinaudo\n */\npublic abstract class UrlOperation implements DesktopOperation {\n    // - DesktopOperation methods ----------------------------------------\n    // -------------------------------------------------------------------\n    public abstract String getName();\n    public abstract boolean isAvailable();\n\n\n\n    // - Wrappers --------------------------------------------------------\n    // -------------------------------------------------------------------\n    /**\n     * Executes the operation on the specified URL.\n     * @param  url                           URL on which to execute the operation.\n     * @throws IOException                   if an error occurs.\n     * @throws UnsupportedOperationException if the operation is not supported.\n     */\n    public abstract void execute(URL url) throws IOException;\n\n    /**\n     * Checks whether the operation knows how to deal with the specified URL.\n     * <p>\n     * By default, this method returns {@link #isAvailable()}. However, some implementations\n     * might want to overwrite it. For example, a <code>UrlOperation</code> that only works\n     * on HTTPS URLs would override this method to only return <code>true</code> if the specified\n     * URL is an HTTPS one.\n     *\n     * @param  url url to check against.\n     * @return     <code>true</code> if the operation is supported for the specified file, <code>false</code> otherwise.\n     */\n    public boolean canExecute(URL url) {\n        return isAvailable();\n    }\n\n\n\n    // - DesktopOperation implementation ---------------------------------\n    // -------------------------------------------------------------------\n    /**\n     * Returns <code>true</code> if the operation is supported for the specified parameters.\n     * <p>\n     * By default, this method will call {@link #extractTarget(Object[])} on the specified parameters\n     * and pass the resulting <code>java.net.URL</code> instance to {@link #canExecute(URL)}.\n     * <p>\n     * This behavior can be overridden by implementations, although most cases can be handled through\n     * {@link #canExecute(URL)} instead.\n     *\n     * @param  target operation parameters.\n     * @return        <code>true</code> if the operation is supported for the specified parameters, <code>false</code> otherwise.\n     * @see           #canExecute(URL)\n     * @see           #extractTarget(Object[])\n     */\n    public boolean canExecute(Object[] target) {\n        URL url = extractTarget(target);\n        return url != null && canExecute(url);\n    }\n\n    /**\n     * Analyses the specified parameters and delegates the operation execution to {@link #execute(URL)}.\n     * <p>\n     * This method is a wrapper for {@link #extractTarget(Object[])} and {@link #execute(URL)}. Most\n     * implementations should ignore it.\n     *\n     * @param  target                        parameters of the operation.\n     * @throws IOException                   if an error occurs.\n     * @throws UnsupportedOperationException if the operation is not supported.\n     * @see                                  #execute(URL)\n     * @see                                  #extractTarget(Object[])\n     */\n    public void execute(Object[] target) throws IOException, UnsupportedOperationException {\n        URL url = extractTarget(target);\n        if (url == null) {\n            throw new UnsupportedOperationException();\n        }\n        execute(url);\n    }\n\n\n\n    // - Parameter analysis ----------------------------------------------\n    // -------------------------------------------------------------------\n    /**\n     * Analyses the specified parameters and returns them in a form that can be used.\n     * <p>\n     * By default, this method will return <code>null</code> unless <code>target</code>:\n     * <ul>\n     *   <li>has a length of 1.</li>\n     *   <li>contains an instance of either <code>java.io.File</code>,{@link com.mucommander.commons.file.impl.local.LocalFile} or <code>String</code>.</li>\n     * </ul>\n     * <p>\n     * This behavior can be overridden by implementations to fit their own needs, although it's probably not a great idea.\n     *\n     * @param  target operation parameters.\n     * @return        <code>null</code> if the parameters are not legal, a <code>java.io.File</code> instance instead.\n     */\n    protected URL extractTarget(Object[] target) {\n        // We only deal with arrays containing 1 element.\n        if (target.length != 1) {\n            return null;\n        }\n        Object obj = target[0];\n\n        // If we find an instance of java.net.URL, we can stop here.\n        if (obj instanceof URL) {\n            return (URL)obj;\n        }\n\n        // Deals with instances of HTTPFile.\n        if (obj instanceof HTTPFile) {\n            return (URL)((AbstractFile)obj).getUnderlyingFileObject();\n        }\n\n        // Deals with instances of String.\n        if (obj instanceof String) {\n            try {\n                return new URI((String)obj).toURL();\n            } catch(URISyntaxException | MalformedURLException e) {\n                return null;\n            }\n        }\n\n        // Illegal parameters.\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/gnome/ConfiguredGnomeDesktopAdapter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.gnome;\n\nimport com.mucommander.process.ProcessRunner;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * @author Nicolas Rinaudo\n */\n@Slf4j\npublic class ConfiguredGnomeDesktopAdapter extends GnomeDesktopAdapter {\n\n    public String toString() {\n        return \"Gnome Desktop\";\n    }\n\n    @Override\n    public boolean isAvailable() {\n        String desktopSession = System.getenv(\"DESKTOP_SESSION\");\n        if (\"gnome\".equalsIgnoreCase(desktopSession)) {\n            return true;\n        }\n\n        desktopSession = System.getenv(\"XDG_CURRENT_DESKTOP\");\n        if (desktopSession != null) {\n            desktopSession = desktopSession.toLowerCase();\n            if (desktopSession.contains(\"gnome\"))\n                return true;\n            if (desktopSession.contains(\"unity\"))\n                return true;\n        }\n\n        desktopSession = System.getenv(\"GNOME_DESKTOP_SESSION_ID\");\n        return desktopSession != null && !desktopSession.trim().isEmpty();\n    }\n\n    @Override\n    protected String getFileOpenerCommand() {\n        try {\n            ProcessRunner.execute(GVFS_OPEN);\n            return GVFS_OPEN;\n        } catch(Exception ignore) {\n            log.debug(GVFS_OPEN + \" not found\");\n        }\n        try {\n            ProcessRunner.execute(GNOME_OPEN);\n            return GNOME_OPEN;\n        } catch(Exception ignore) {\n            log.debug(GNOME_OPEN + \" not found\");\n        }\n        try {\n            ProcessRunner.execute(XDG_OPEN);\n            return XDG_OPEN;\n        } catch(Exception ignore) {\n            log.debug(XDG_OPEN + \" not found\");\n        }\n\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/gnome/GnomeConfig.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.gnome;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.util.concurrent.TimeUnit;\n\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * Provides access to the GNOME configuration, using the <code>gconftool</code> command.\n *\n * @author Maxence Bernard\n */\n@Slf4j\npublic class GnomeConfig {\n    /** Name of the command to invoke for retrieving configuration values */\n    private static final String CONFIG_COMMAND = \"gconftool\";\n    /** Timeout for the configuration command execution in seconds */\n    private static final long COMMAND_TIMEOUT = 5;\n\n    /**\n     * Returns the GNOME configuration value corresponding to the given key, <code>null</code> if this key has no value.\n     *\n     * @param key key to the configuration value to retrieve.\n     * @return the configuration value corresponding to the given key, <code>null</code> if this key has no value.\n     * @throws IOException if an error occurred while invoking the <code>gconftool</code> command, for instance if the\n     * command isn't available in the path.\n     */\n    public static String getValue(String key) throws IOException {\n        ProcessBuilder processBuilder = new ProcessBuilder(CONFIG_COMMAND, \"-g\", key);\n        processBuilder.redirectErrorStream(true);\n        try {\n            Process process = processBuilder.start();\n\n            try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) {\n                String line = br.readLine();\n\n                // Wait for process completion with timeout\n                if (!process.waitFor(COMMAND_TIMEOUT, TimeUnit.SECONDS)) {\n                    process.destroyForcibly();\n                    log.warn(\"Command timed out for key: {}\", key);\n                    throw new IOException(\"Command timed out: \" + CONFIG_COMMAND);\n                }\n\n                int exitCode = process.exitValue();\n                if (exitCode != 0) {\n                    log.debug(\"Command returned exit code {} for key: {}\", exitCode, key);\n                    return null;\n                }\n\n                log.debug(CONFIG_COMMAND + \" returned '{}' for {}\", line, key);\n                if (line == null || (line=line.trim()).isEmpty() || line.startsWith(\"No value set for\")) {\n                    return null;\n                }\n                return line;\n            }\n        } catch (InterruptedException e) {\n            Thread.currentThread().interrupt();\n            log.debug(\"Interrupted while retrieving value for {}\", key, e);\n            throw new IOException(\"Command interrupted\", e);\n        } catch(IOException e) {\n            log.debug(\"Error while retrieving value for {}\", key, e);\n            throw e;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/gnome/GnomeDesktopAdapter.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.desktop.gnome;\r\n\r\nimport java.awt.Toolkit;\r\nimport java.lang.reflect.Field;\r\n\r\nimport com.mucommander.desktop.DesktopInitializationException;\r\nimport lombok.extern.slf4j.Slf4j;\r\n\r\nimport com.mucommander.command.Command;\r\nimport com.mucommander.command.CommandException;\r\nimport com.mucommander.command.CommandManager;\r\nimport com.mucommander.command.CommandType;\r\nimport com.mucommander.commons.file.filter.FileFilter;\r\nimport com.mucommander.commons.file.filter.RegexpFilenameFilter;\r\nimport com.mucommander.desktop.DefaultDesktopAdapter;\r\nimport com.mucommander.desktop.DesktopManager;\r\n\r\n/**\r\n * @author Nicolas Rinaudo, Maxence Bernard\r\n */\r\n@Slf4j\r\nabstract class GnomeDesktopAdapter extends DefaultDesktopAdapter {\r\n\tprivate static final String FILE_MANAGER_NAME = \"Nautilus\";\r\n    private static final String EXE_OPENER   = \"$f\";\r\n\tprotected static final String GVFS_OPEN  = \"gvfs-open\";\r\n\tprotected static final String GNOME_OPEN = \"gnome-open\";\r\n\tprotected static final String XDG_OPEN   = \"xdg-open\";\r\n\tprotected static final String CMD_OPENER_COMMAND = \"gnome-terminal --working-directory $f\";\r\n\r\n\t/** Key to the double-click interval value in the GNOME configuration. */\r\n\tprivate static final String DOUBLE_CLICK_CONFIG_KEY = \"/desktop/gnome/peripherals/mouse/double_click\";\r\n\t/**\r\n\t * Multi-click interval, cached to avoid polling the value every time\r\n\t * {@link #getMultiClickInterval()} is called.\r\n\t */\r\n    private int multiClickInterval;\r\n\r\n    @Override\r\n    public abstract boolean isAvailable();\r\n\r\n\tprotected abstract String getFileOpenerCommand();\r\n\r\n    @Override\r\n\tpublic void init(final boolean install) throws DesktopInitializationException {\r\n\t\tString fileOpener = String.format(\"%s $f\", getFileOpenerCommand());\r\nSystem.out.println(\"fileOpener: \" + fileOpener);\r\n\t\tsetWMClass();\r\n        // Workaround for JDK issue\r\n        try {\r\n\t\t\tToolkit xToolkit = Toolkit.getDefaultToolkit();\r\n\t\t\tField awtAppClassNameField = xToolkit.getClass().getDeclaredField(\"awtAppClassName\");\r\n\t\t\tawtAppClassNameField.setAccessible(true);\r\n\t\t\tawtAppClassNameField.set(xToolkit, \"trolCommander\");\r\n        } catch (Exception ignore) { }\r\n        // Initializes trash management.\r\n        DesktopManager.setTrashProvider(new GnomeTrashProvider());\r\n        try {\r\n\t\t\tCommandManager.registerDefaultCommand(new Command(CommandManager.FILE_OPENER_ALIAS, fileOpener, CommandType.SYSTEM_COMMAND, null, null));\r\n\t\t\tCommandManager.registerDefaultCommand(new Command(CommandManager.URL_OPENER_ALIAS, fileOpener, CommandType.SYSTEM_COMMAND, null, null));\r\n\t\t\tCommandManager.registerDefaultCommand(new Command(CommandManager.EXE_OPENER_ALIAS, EXE_OPENER, CommandType.SYSTEM_COMMAND, null, null));\r\n\t\t\tCommandManager.registerDefaultCommand(new Command(CommandManager.FILE_MANAGER_ALIAS, fileOpener, CommandType.SYSTEM_COMMAND, FILE_MANAGER_NAME, null));\r\n\t\t\tCommandManager.registerDefaultCommand(new Command(CommandManager.CMD_OPENER_ALIAS, CMD_OPENER_COMMAND, CommandType.SYSTEM_COMMAND, null, null));\r\n\r\n            FileFilter filter = new RegexpFilenameFilter(\"[^.]+\", true);\r\n            // Disabled actual permissions checking as this will break normal +x files.\r\n            // With this, a +x PDF file will not be opened.\r\n            /*\r\n\t\t\t * // Identifies which kind of IMAGE_FILTER should be used to match executable files.\r\n\t\t\t * if(JavaVersion.JAVA_1_6.isCurrentOrHigher()) IMAGE_FILTER = new\r\n\t\t\t * PermissionsFileFilter(PermissionTypes.EXECUTE_PERMISSION, true); else\r\n            */\r\n            CommandManager.registerDefaultAssociation(CommandManager.EXE_OPENER_ALIAS, filter);\r\n            // Multi-click interval retrieval\r\n\t\t\tmultiClickInterval = determineMultiClickInterval();\r\n\t\t} catch (CommandException e) {\r\n            throw new DesktopInitializationException(e);\r\n        }\r\n    }\r\n\r\n\tprivate int determineMultiClickInterval() {\r\n\t\ttry {\r\n\t\t\tString value = GnomeConfig.getValue(DOUBLE_CLICK_CONFIG_KEY);\r\n\t\t\tif (value != null) {\r\n\t\t\t\treturn Integer.parseInt(value);\r\n\t\t\t}\r\n\t\t} catch (Exception e) {\r\n\t\t\tlog.debug(\"Error while retrieving double-click interval from gconftool\", e);\r\n\t\t}\r\n\t\treturn super.getMultiClickInterval();\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the <code>/desktop/gnome/peripherals/mouse/double_click</code> GNOME configuration\r\n\t * value. If the returned value is not defined or could not be retrieved, the value of\r\n     * {@link DefaultDesktopAdapter#getMultiClickInterval()} is returned.<br>\r\n     * The value is retrieved on initialization and never updated thereafter.\r\n     * <p>\r\n\t * Note under Java 1.6 or below, the returned value does not match the one used by Java for\r\n\t * generating multi-clicks (see {@link DefaultDesktopAdapter#getMultiClickInterval()}, as\r\n\t * Java uses the multi-click speed declared in X Window's configuration, not in GNOME's. See\r\n\t * <a href=\"http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5076635\"> Java Bug 5076635</a>\r\n\t * for more information.\r\n\t *\r\n\t * @return the <code>/desktop/gnome/peripherals/mouse/double_click</code> GNOME configuration value.\r\n     */\r\n    @Override\r\n    public int getMultiClickInterval() {\r\n        return multiClickInterval;\r\n    }\r\n\r\n\t@Override\r\n\tpublic String getDefaultTerminalAppCommand() {\r\n\t\treturn \"gnome-terminal --working-directory=$p\";\r\n\t}\r\n\r\n\t@Override\r\n\tpublic String getDefaultTerminalShellCommand() {\r\n\t\treturn \"/bin/bash --login\";\r\n\t}\r\n\r\n\t/**\r\n\t * Sets the WM_CLASS for Linux window managers.\r\n\t */\r\n\tprivate static void setWMClass() {\r\n\t\ttry {\r\n\t\t\tjava.awt.Toolkit toolkit = java.awt.Toolkit.getDefaultToolkit();\r\n\t\t\tjava.lang.reflect.Field awtAppClassNameField = toolkit.getClass().getDeclaredField(\"awtAppClassName\");\r\n\t\t\tawtAppClassNameField.setAccessible(true);\r\n\t\t\tawtAppClassNameField.set(null, \"trolcommander-trolCommander\");\r\n\t\t} catch (NoSuchFieldException e) {\r\n\t\t\t// Not running on X11/Linux, or field doesn't exist in this JDK version\r\n\t\t\tlog.info(\"DEBUG: Could not set WM_CLASS - field not found (probably not Linux/X11)\");\r\n\t\t} catch (IllegalAccessException e) {\r\n\t\t\tlog.warn(\"Warning: Could not set WM_CLASS due to access restrictions: {}\", e.getMessage());\r\n\t\t} catch (Exception e) {\r\n\t\t\tlog.error(\"Warning: Unexpected error setting WM_CLASS: {}\", e.getMessage());\r\n\t\t}\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/gnome/GnomeTrash.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\npackage com.mucommander.desktop.gnome;\r\n\r\nimport java.io.IOException;\r\nimport java.io.OutputStreamWriter;\r\nimport java.text.SimpleDateFormat;\r\nimport java.util.Date;\r\nimport java.util.List;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.impl.local.LocalFile;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.desktop.QueuedTrash;\r\nimport com.mucommander.job.DeleteJob;\r\nimport com.mucommander.process.ProcessRunner;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.dialog.file.ProgressDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.WindowManager;\r\n\r\n/**\r\n * This class handles with GNOME Trash (deleting to trash, empty the trash, go to trash etc.)\r\n * <p>\r\n * <b>Implementation notes:</b><br>\r\n * <br>\r\n * This Trash class has the same possibilities as <code>KDETrash</code>, but is adapted to a\r\n * GNOME environment, where the trash is simple directory ~/.Trash. So working with trash means\r\n * working with this directory.\r\n *\r\n * @see GnomeTrashProvider\r\n * @author David Kovar (kowy), Maxence Bernard\r\n */\r\npublic class GnomeTrash extends QueuedTrash {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(GnomeTrash.class);\r\n\t/** Open trash folder default file manager in Gnome. */\r\n\tprivate static final String REVEAL_TRASH_COMMAND = \"xdg-open trash:///\";\r\n\t/**\r\n\t * User trash folder, as defined by the freedesktop specification (see\r\n\t * http://freedesktop.org/wiki/Specifications/trash-spec) <code>null</code> if there is no\r\n\t * usable trash folder.\r\n\t */\r\n\tprivate static final AbstractFile TRASH_FOLDER;\r\n\t/** \"info\" subfolder of the user trash folder. */\r\n\tprivate static final AbstractFile TRASH_INFO_SUBFOLDER;\r\n\t/** \"files\" subfolder of the user trash folder. */\r\n\tprivate static final AbstractFile TRASH_FILES_SUBFOLDER;\r\n    /**\r\n\t * Volume on which the trash folder resides, used for checking whether a file can be moved to\r\n\t * the trash or not.\r\n     */\r\n\tprivate static final AbstractFile TRASH_VOLUME;\r\n\t/** Formats dates in trash info files. */\r\n\tprivate static final SimpleDateFormat INFO_DATE_FORMAT = new SimpleDateFormat(\"yyyy-MM-dd'T'HH:mm:ss\");\r\n    static {\r\n        TRASH_FOLDER = getTrashFolder();\r\n\t\tif (TRASH_FOLDER != null) {\r\n            TRASH_INFO_SUBFOLDER = TRASH_FOLDER.getChildSilently(\"info\");\r\n            TRASH_FILES_SUBFOLDER = TRASH_FOLDER.getChildSilently(\"files\");\r\n            TRASH_VOLUME = TRASH_FOLDER.getVolume();\r\n\t\t} else {\r\n            TRASH_INFO_SUBFOLDER = null;\r\n            TRASH_FILES_SUBFOLDER = null;\r\n            TRASH_VOLUME = null;\r\n        }\r\n    }\r\n\r\n    /**\r\n\t * Tries to find an existing user Trash folder in one of the two common locations and returns\r\n\t * it. If no existing Trash folder was found, creates the standard GNOME user Trash folder\r\n\t * and returns it.\r\n     *\r\n\t * @return the user Trash folder, <code>null</code> if no user trash folder could be found or\r\n\t *         created\r\n     */\r\n     private static AbstractFile getTrashFolder() {\r\n        AbstractFile userHome = LocalFile.getUserHome();\r\n\t\t// Primary trash location: new distro's trash path\r\n\t\tAbstractFile primaryTrashDir = userHome.getChildSilently(\".local/share/Trash/\");\r\n\t\tif (isTrashFolder(primaryTrashDir)) {\r\n            return primaryTrashDir;\r\n\t\t} else {\r\n\t\t\t// Secondary trash location: old/standard path defined in specification\r\n\t\t\tAbstractFile secondaryTrashDir = userHome.getChildSilently(\"Trash/\");\r\n\t\t\tif (isTrashFolder(secondaryTrashDir)) {\r\n\t\t\t\treturn secondaryTrashDir;\r\n            }\r\n        }\r\n        // No existing user trash was found: create the folder, only if it doesn't already exist.\r\n\t\tif (!primaryTrashDir.exists()) {\r\n            try {\r\n                primaryTrashDir.mkdirs();\r\n                primaryTrashDir.getChild(\"info\").mkdir();\r\n                primaryTrashDir.getChild(\"files\").mkdir();\r\n                return primaryTrashDir;\r\n\t\t\t} catch (IOException e) {\r\n                // Will return null\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n    /**\r\n\t * Return <code>true</code> if the specified file is a GNOME Trash folder, i.e. is a\r\n\t * directory and has two subdirectories named \"info\" and \"files\".\r\n     *\r\n\t * @param file\r\n\t *          the file to test\r\n     * @return <code>true</code> if the specified file is a GNOME Trash folder\r\n     */\r\n\tprivate static boolean isTrashFolder(final AbstractFile file) {\r\n        try {\r\n            return file != null && file.isDirectory() && file.getChild(\"info\").isDirectory() && file.getChild(\"files\").isDirectory();\r\n\t\t} catch (IOException e) {\r\n            return false;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * <b>Implementation notes:</b> always returns <code>true</code>.\r\n     * \r\n     * @return True if trash can be emptied, otherwise false\r\n     */\r\n    @Override\r\n\tpublic final boolean canEmpty() {\r\n\t\treturn TRASH_FOLDER != null;\r\n    }\r\n            \r\n    /**\r\n     * Return trash files count\r\n     * <p>\r\n     * We assume the count of items in trash equals the count of files in \r\n     * <code>TRASH_PATH + \"/info\"</code> folder.\r\n     * \r\n     * @return Count of files in trash\r\n     */\r\n    @Override\r\n\tpublic final int getItemCount() {\r\n        // Abort if there is no usable trash folder\r\n\t\tif (TRASH_FOLDER == null) {\r\n            return -1;\r\n\t\t}\r\n        try {\r\n            return TRASH_INFO_SUBFOLDER.ls().length;\r\n        } catch (java.io.IOException ex) {\r\n            // can't access trash folder\r\n            return -1;\r\n        }\r\n    }\r\n    \r\n    /**\r\n\t * Empty the trash.\r\n     * <p>\r\n     * <b>Implementation notes:</b><br>\r\n     * Simply free the <code>TRASH_PATH</code> directory\r\n     * \r\n     * @return True if everything went well\r\n     */\r\n    @Override\r\n\tpublic final boolean empty() {\r\n        // Abort if there is no usable trash folder\r\n\t\tif (TRASH_FOLDER == null) {\r\n            return false;\r\n\t\t}\r\n        FileSet filesToDelete = new FileSet(TRASH_FOLDER);\r\n        try {\r\n            // delete real files\r\n            filesToDelete.addAll(TRASH_FILES_SUBFOLDER.ls());\r\n            // delete spec files\r\n            filesToDelete.addAll(TRASH_INFO_SUBFOLDER.ls());\r\n        } catch (java.io.IOException ex) {\r\n        \tLOGGER.debug(\"Failed to list files\", ex);\r\n            return false;\r\n        }\r\n        if (filesToDelete.size() > 0) {\r\n            // Starts deleting files\r\n            MainFrame mainFrame = WindowManager.getCurrentMainFrame();\r\n            ProgressDialog progressDialog = new ProgressDialog(mainFrame, Translator.get(\"delete_dialog.deleting\"));\r\n            DeleteJob deleteJob = new DeleteJob(progressDialog, mainFrame, filesToDelete, false);\r\n            progressDialog.start(deleteJob);\r\n        }\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n\tpublic final boolean canOpen() {\r\n\t\treturn TRASH_FOLDER != null;\r\n    }\r\n\r\n    /**\r\n\t * Opens the trash in default environment filemanager.\r\n     */\r\n    @Override\r\n\tpublic final void open() {\r\n        try {\r\n            ProcessRunner.execute(REVEAL_TRASH_COMMAND).waitFor();\r\n\t\t} catch (Exception e) { // IOException, InterruptedException\r\n        \tLOGGER.debug(\"Caught an exception running command \\\"\" + REVEAL_TRASH_COMMAND + \"\\\"\", e);\r\n        }\r\n    }\r\n\r\n    @Override\r\n\tpublic final boolean isTrashFile(final AbstractFile file) {\r\n\t\treturn TRASH_FOLDER != null && (file.getTopAncestor() instanceof LocalFile) && TRASH_FOLDER.isParentOf(file);\r\n    }\r\n\r\n    /**\r\n\t * Implementation notes: returns <code>true</code> only for local files that are not archive\r\n\t * entries and that reside on the same volume as the trash folder.\r\n     */\r\n    @Override\r\n\tpublic final boolean canMoveToTrash(final AbstractFile file) {\r\n\t\treturn TRASH_FOLDER != null && file.getTopAncestor() instanceof LocalFile && file.getVolume().equals(TRASH_VOLUME);\r\n    }\r\n\r\n    /**\r\n     * Implementation of {@link com.mucommander.desktop.QueuedTrash} moveToTrash method.\r\n     * <p>\r\n     * Try to copy a collection of files to the GNOME's Trash.\r\n\t * \r\n\t * @param queuedFiles\r\n\t *          Collection of files to the trash\r\n     * @return <code>true</code> if movement has been successful or <code>false</code> otherwise\r\n     */\r\n    @Override\r\n\tprotected final boolean moveToTrash(final List<AbstractFile> queuedFiles) {\r\n        String fileInfoContent;\r\n        String trashFileName;\r\n\t\tboolean retVal = true; // overall return value (if everything went OK or at least one file\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t// wasn't moved properly\r\n        for (AbstractFile fileToDelete : queuedFiles) {\r\n            // generate content of info file and new filename\r\n            try {\r\n                fileInfoContent = getFileInfoContent(fileToDelete);\r\n                trashFileName = getUniqueFilename(fileToDelete);\r\n            } catch (IOException ex) {\r\n            \tLOGGER.debug(\"Failed to create filename for new trash item: \" + fileToDelete.getName(), ex);\r\n\t\t\t\t// continue with other file (do not move file, because info file cannot be properly\r\n\t\t\t\t// created\r\n                continue;\r\n            }\r\n            AbstractFile infoFile = null;\r\n            OutputStreamWriter infoWriter = null;\r\n            try {\r\n                // create info file\r\n                infoFile = TRASH_INFO_SUBFOLDER.getChild(trashFileName + \".trashinfo\");\r\n                infoWriter = new OutputStreamWriter(infoFile.getOutputStream());\r\n                infoWriter.write(fileInfoContent);\r\n            } catch (IOException ex) {\r\n                retVal = false;\r\n                LOGGER.debug(\"Failed to create trash info file: \" + trashFileName, ex);\r\n\t\t\t\t// continue with other file (do not move file, because info file wasn't properly\r\n\t\t\t\t// created)\r\n                continue;\r\n            } finally {\r\n                if (infoWriter != null) {\r\n                    try {\r\n                        infoWriter.close();\r\n                    } catch (IOException e) {\r\n                        // Not much else to do\r\n                    }\r\n                }\r\n            }\r\n            try {\r\n                // rename original file\r\n                fileToDelete.renameTo(TRASH_FILES_SUBFOLDER.getChild(trashFileName));\r\n            } catch (IOException ex) {\r\n                try {\r\n                    // remove info file\r\n                    infoFile.delete();\r\n                } catch (IOException ex1) {\r\n                    // simply ignore\r\n                }\r\n                retVal = false;\r\n                LOGGER.debug(\"Failed to move file to trash: \" + trashFileName, ex);\r\n            }\r\n        }\r\n        return retVal;\r\n    }\r\n    \r\n    /**\r\n\t * Make a content of .trashinfo file.\r\n\t * \r\n\t * @param file\r\n\t *          File for which the content is built\r\n     * @return Final content\r\n     */\r\n\tprivate String getFileInfoContent(final AbstractFile file) {\r\n\t\tsynchronized (INFO_DATE_FORMAT) { // SimpleDateFormat is not thread safe\r\n\t\t\treturn \"[Trash Info]\\n\" + \"Path=\" + file.getAbsolutePath() + \"\\n\" + \"DeletionDate=\"\r\n\t\t\t\t\t+ INFO_DATE_FORMAT.format(new Date());\r\n        }\r\n    }\r\n    \r\n    /**\r\n\t * It is possible to add several files with same name to the Trash. These files are\r\n\t * distinguished by _N appended to the name, where _N is rising int number. <br/>\r\n     * This method tries to find first empty <code>filename_N.ext</code>.\r\n     *\r\n\t * @param file\r\n\t *          File to be deleted\r\n     * @return Suitable filename in trash (without .trashinfo extension)\r\n\t * @throws IOException\r\n\t *           If trash file cannot be accessed\r\n     */\r\n\tprivate String getUniqueFilename(final AbstractFile file) throws IOException {\r\n        // try if no previous file in trash exists\r\n\t\tif (!TRASH_FILES_SUBFOLDER.getChild(file.getName()).exists()) {\r\n            return file.getName();\r\n\t\t}\r\n        String rawName = file.getNameWithoutExtension();\r\n        String extension = file.getExtension();\r\n        // find first empty filename in format filename_N.ext\r\n        String filename;\r\n        int count = 1;\r\n\t\twhile (true) {\r\n            filename = rawName + \"_\" + count++;\r\n\t\t\tif (extension != null) {\r\n                filename += \".\" + extension;\r\n\t\t\t}\r\n\t\t\tif (!TRASH_FILES_SUBFOLDER.getChild(filename).exists()) {\r\n                return filename;\r\n        }\r\n    }\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/gnome/GnomeTrashProvider.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.gnome;\n\nimport com.mucommander.desktop.AbstractTrash;\nimport com.mucommander.desktop.TrashProvider;\n\n/**\n * Provider for the Trash in GNOME Desktop Environment\n * \n * @see GnomeTrash\n * @author David Kovar (kowy)\n */\npublic class GnomeTrashProvider implements TrashProvider {\n    public AbstractTrash getTrash() {\n        return new GnomeTrash();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/gnome/GuessedGnomeDesktopAdapter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.gnome;\n\nimport com.mucommander.process.ProcessRunner;\n\n/**\n * 'Guessed' desktop adapter for GNOME. The availability of this desktop depends on the presence of the\n * <code>gnome-open</code> command.\n * \n * @author Nicolas Rinaudo\n */\npublic class GuessedGnomeDesktopAdapter extends GnomeDesktopAdapter {\n    private String fileOpenerCommand;\n\n    public String toString() {\n        return \"Gnome Desktop (guessed)\";\n    }\n\n    @Override\n    public boolean isAvailable() {\n        try {\n            ProcessRunner.execute(GVFS_OPEN);\n            fileOpenerCommand = GVFS_OPEN;\n            return true;\n        } catch(Exception ignore) {}\n        try {\n            ProcessRunner.execute(GNOME_OPEN);\n            fileOpenerCommand = GNOME_OPEN;\n            return true;\n        } catch(Exception ignore) {}\n        return false;\n    }\n\n    @Override\n    protected String getFileOpenerCommand() {\n        return fileOpenerCommand;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/kde/ConfiguredKde3DesktopAdapter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.kde;\n\n/**\n * 'Configured' desktop adapter for KDE 3. The availability of this desktop depends on the presence of the\n * <code>KDE_FULL_SESSION</code> environment variable.\n *\n * @author Nicolas Rinaudo\n */\npublic class ConfiguredKde3DesktopAdapter extends Kde3DesktopAdapter {\n\n    private static final String KDE_FULL_SESSION_VAR = \"KDE_FULL_SESSION\";\n\n    @Override\n    protected String getConfiguredEnvVariable(String name) {\n        return System.getenv(name);\n    }\n\n    public String toString() {\n        return \"KDE 3 Desktop\";\n    }\n\n    @Override\n    public boolean isAvailable() {\n        String var = getConfiguredEnvVariable(KDE_FULL_SESSION_VAR);\n        return var != null && !var.trim().isEmpty();\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/kde/ConfiguredKde4DesktopAdapter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.kde;\n\n/**\n * 'Configured' desktop adapter for KDE 4. The availability of this desktop depends on the presence of the\n * <code>KDE_SESSION_VERSION</code> environment variable that was introduced in KDE 4.\n *\n * @author Maxence Bernard\n */\npublic class ConfiguredKde4DesktopAdapter extends Kde4DesktopAdapter {\n\n    private static final String KDE_SESSION_VERSION_VAR = \"KDE_SESSION_VERSION\";\n\n    public String toString() {\n        return \"KDE 4 Desktop\";\n    }\n\n    @Override\n    public boolean isAvailable() {\n        String var = getConfiguredEnvVariable(KDE_SESSION_VERSION_VAR);\n        return var != null && !var.trim().isEmpty();\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/kde/GuessedKde3DesktopAdapter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.kde;\n\nimport com.mucommander.process.ProcessRunner;\n\n/**\n * 'Guessed' desktop adapter for KDE 3. The availability of this desktop depends on the presence of the\n * <code>kfmclient</code> command.\n *\n * @author Nicolas Rinaudo\n */\npublic class GuessedKde3DesktopAdapter extends Kde3DesktopAdapter {\n\n    public String toString() {\n        return \"KDE 3 Desktop (guessed)\";\n    }\n\n    @Override\n    public boolean isAvailable() {\n        try {\n            ProcessRunner.execute(BASE_COMMAND);\n            return true;\n        }\n        catch(Exception e) {\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/kde/GuessedKde4DesktopAdapter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.kde;\n\nimport com.mucommander.process.ProcessRunner;\n\n/**\n * 'Guessed' desktop adapter for KDE 4. The availability of this desktop depends on the presence of the\n * <code>kioclient</code> command.\n *\n * @author Maxence Bernard\n */\npublic class GuessedKde4DesktopAdapter extends Kde4DesktopAdapter {\n\n    public String toString() {\n        return \"KDE 4 Desktop (guessed)\";\n    }\n\n    @Override\n    public boolean isAvailable() {\n        try {\n            ProcessRunner.execute(BASE_COMMAND);\n            return true;\n        } catch(Exception e) {\n            return false;\n        }\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/kde/Kde3DesktopAdapter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.kde;\n\nimport com.mucommander.desktop.TrashProvider;\n\n/**\n * @author Maxence Bernard\n */\nabstract class Kde3DesktopAdapter extends KdeDesktopAdapter {\n\n    static String BASE_COMMAND = \"kfmclient\";\n\n    @Override\n    protected String getFileManagerName() {\n        return \"Konqueror\";\n    }\n\n    @Override\n    protected String getBaseCommand() {\n        return BASE_COMMAND;\n    }\n\n    @Override\n    protected TrashProvider getTrashProvider() {\n        return new Kde3TrashProvider();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/kde/Kde3TrashProvider.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.kde;\n\nimport com.mucommander.desktop.AbstractTrash;\nimport com.mucommander.desktop.TrashProvider;\n\n/**\n * This class is a trash provider for the {@link KdeTrash KDE 3 trash}.\n *\n * @see KdeTrash\n * @author Maxence Bernard\n */\nclass Kde3TrashProvider implements TrashProvider {\n\n    public AbstractTrash getTrash() {\n        return new KdeTrash(Kde3DesktopAdapter.BASE_COMMAND);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/kde/Kde4DesktopAdapter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.kde;\n\nimport com.mucommander.desktop.TrashProvider;\n\n/**\n * @author Maxence Bernard\n */\nabstract class Kde4DesktopAdapter extends KdeDesktopAdapter {\n\n    static String BASE_COMMAND = \"kioclient\";\n\n    @Override\n    protected String getFileManagerName() {\n        return \"Dolphin\";\n    }\n\n    @Override\n    protected String getBaseCommand() {\n        return BASE_COMMAND;\n    }\n\n    @Override\n    protected TrashProvider getTrashProvider() {\n        return new Kde4TrashProvider();\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/kde/Kde4TrashProvider.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.kde;\n\nimport com.mucommander.desktop.AbstractTrash;\nimport com.mucommander.desktop.TrashProvider;\n\n/**\n * This class is a trash provider for the {@link KdeTrash KDE 3 trash}.\n *\n * @see KdeTrash\n * @author Maxence Bernard\n */\nclass Kde4TrashProvider implements TrashProvider {\n\n    public AbstractTrash getTrash() {\n        return new KdeTrash(Kde4DesktopAdapter.BASE_COMMAND);\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/kde/KdeConfig.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.kde;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.util.concurrent.TimeUnit;\n\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * Provides access to the KDE configuration, using the <code>kreadconfig</code> command.\n *\n * @author Maxence Bernard\n */\n@Slf4j\npublic class KdeConfig {\n    /** Name of the command to invoke for retrieving configuration values */\n    private static final String CONFIG_COMMAND = \"kreadconfig\";\n\n    /** Timeout for the configuration command execution in seconds */\n    private static final long COMMAND_TIMEOUT = 5;\n\n\n    /**\n     * Returns the KDE configuration value corresponding to the given key, <code>null</code> if this key has no value.\n     *\n     * @param key key to the configuration value to retrieve.\n     * @return the configuration value corresponding to the given key, <code>null</code> if this key has no value.\n     * @throws IOException if an error occurred while invoking the <code>kreadconfig</code> command, for instance if the\n     * command isn't available in the path.\n     */\n    public static String getValue(String key) throws IOException {\n        ProcessBuilder processBuilder = new ProcessBuilder(CONFIG_COMMAND, \"--key\", key);\n        processBuilder.redirectErrorStream(true);\n\n        try {\n            Process process = processBuilder.start();\n            try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) {\n                String line = br.readLine();\n\n                // Wait for process completion with timeout\n                if (!process.waitFor(COMMAND_TIMEOUT, TimeUnit.SECONDS)) {\n                    process.destroyForcibly();\n                    log.warn(\"Command timed out for key: {}\", key);\n                    throw new IOException(\"Command timed out: \" + CONFIG_COMMAND);\n                }\n\n                int exitCode = process.exitValue();\n                if (exitCode != 0) {\n                    log.debug(\"Command returned exit code {} for key: {}\", exitCode, key);\n                    return null;\n                }\n\n                log.debug(CONFIG_COMMAND + \" returned '{}' for {}\", line, key);\n\n                if (line == null || (line = line.trim()).isEmpty())\n                    return null;\n\n                return line;\n            }\n        } catch(IOException e) {\n            log.debug(\"Error while retrieving value for {}\", key, e);\n            throw e;\n        } catch (InterruptedException e) {\n            Thread.currentThread().interrupt();\n            log.debug(\"Interrupted while retrieving value for {}\", key, e);\n            throw new IOException(\"Command interrupted\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/kde/KdeDesktopAdapter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.kde;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.command.Command;\nimport com.mucommander.command.CommandException;\nimport com.mucommander.command.CommandManager;\nimport com.mucommander.command.CommandType;\nimport com.mucommander.desktop.DefaultDesktopAdapter;\nimport com.mucommander.desktop.DesktopInitializationException;\nimport com.mucommander.desktop.DesktopManager;\nimport com.mucommander.desktop.TrashProvider;\n\n/**\n * @author Nicolas Rinaudo, Maxence Bernard\n */\nabstract class KdeDesktopAdapter extends DefaultDesktopAdapter {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(KdeDesktopAdapter.class);\n\t\n    /** Multi-click interval, cached to avoid polling the value every time {@link #getMultiClickInterval()} is called */\n    private int multiClickInterval;\n\n    /** Key to the double-click interval value in the KDE configuration */\n    private final String DOUBLE_CLICK_CONFIG_KEY = \"DoubleClickInterval\";\n\n    @Override\n    public void init(boolean install) throws DesktopInitializationException {\n        // Initialises trash management.\n        DesktopManager.setTrashProvider(getTrashProvider());\n\n        // Registers KDE specific commands.\n        try {\n            String execCommand = getBaseCommand()+\" exec $f\";\n            CommandManager.registerDefaultCommand(new Command(CommandManager.FILE_OPENER_ALIAS,  execCommand, CommandType.SYSTEM_COMMAND, null, null));\n            CommandManager.registerDefaultCommand(new Command(CommandManager.URL_OPENER_ALIAS,   execCommand, CommandType.SYSTEM_COMMAND, null, null));\n            CommandManager.registerDefaultCommand(new Command(CommandManager.FILE_MANAGER_ALIAS, execCommand, CommandType.SYSTEM_COMMAND, getFileManagerName(), null));\n        } catch(CommandException e) {\n            throw new DesktopInitializationException(e);\n        }\n\n        // Multi-click interval retrieval\n        try {\n            String value = KdeConfig.getValue(DOUBLE_CLICK_CONFIG_KEY);\n            if(value==null)\n                multiClickInterval = super.getMultiClickInterval();\n\n            multiClickInterval = Integer.parseInt(value);\n        }\n        catch(Exception e) {\n            LOGGER.debug(\"Error while retrieving double-click interval from gconftool\", e);\n            multiClickInterval = super.getMultiClickInterval();\n        }\n    }\n\n    /**\n     * Returns the <code>DoubleClickInterval</code> KDE configuration value.\n     * If the returned value is not defined or could not be retrieved, the value of\n     * {@link DefaultDesktopAdapter#getMultiClickInterval()} is returned.<br>\n     * The value is retrieved on initialization and never updated thereafter.\n     * <p>\n     * Note under Java 1.6 or below, the returned value does not match the one used by Java for generating multi-clicks\n     * (see {@link DefaultDesktopAdapter#getMultiClickInterval()}, as Java uses the multi-click speed declared in\n     * X Window's configuration, not in KDE's. See <a href=\"http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5076635\">\n     * Java Bug 5076635</a> for more information.\n     *\n     * @return the <code>DoubleClickInterval</code> KDE configuration value.\n     */\n    @Override\n    public int getMultiClickInterval() {\n        return multiClickInterval;\n    }\n\n\n    ////////////////////\n    // Helper methods //\n    ////////////////////\n\n    /**\n     * Returns the 'configured' value of the given environment variable, <code>null</code> if the variable has no value.\n     *\n     * @param name name of the environment variable to retrieve\n     * @return the 'configured' value of the given environment variable, <code>null</code> if the variable has no value.\n     */\n    protected String getConfiguredEnvVariable(String name) {\n        return System.getenv(name);\n    }\n\n\n    /////////////////////////////////\n    // KDE version-specific values //\n    /////////////////////////////////\n\n    /**\n     * Returns the name of KDE's file manager.\n     *\n     * @return the name of KDE's file manager.\n     */\n    protected abstract String getFileManagerName();\n\n    /**\n     * Returns the base command that is used for interacting with KDE.\n     *\n     * @return the base command that is used for interacting with KDE.\n     */\n    protected abstract String getBaseCommand();\n\n    /**\n     * Returns an instance of {@link TrashProvider} giving access to the KDE trash.\n     *\n     * @return an instance of {@link TrashProvider} giving access to the KDE trash.\n     */\n    protected abstract TrashProvider getTrashProvider();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/kde/KdeTrash.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.kde;\n\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport com.mucommander.desktop.QueuedTrash;\nimport com.mucommander.process.ProcessRunner;\n\n/**\n * This class provides access to the KDE trash. Only local files (or locally mounted files) can be moved to the trash.\n *\n * <p>\n * <b>Implementation notes:</b><br>\n * <br>\n * This trash is implemented as a {@link com.mucommander.desktop.QueuedTrash} as it spawns a process to move a file to\n * the trash and it is thus more effective to group files to be moved instead of spawning multiple processes.<br>\n *\n * @see Kde3TrashProvider\n * @author Maxence Bernard\n */\nclass KdeTrash extends QueuedTrash {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(KdeTrash.class);\n\t\n    /** Command that empties the trash */\n    private final static String EMPTY_TRASH_COMMAND = \"ktrash --empty\";\n\n    /** Command that allows to interact with the trash */\n    private String baseCommand;\n\n    /**\n     * Creates a new <code>KDETrash</code> instance using the specified command for interacting with the trash.\n     *\n     * @param baseCommand command that allows to interact with the trash.\n     */\n    KdeTrash(String baseCommand) {\n        this.baseCommand = baseCommand;\n    }\n\n    /**\n     * Executes the given command and waits for the process termination.\n     * Returns <code>true</code> if the command was executed without any error.\n     *\n     * @param command the command to execute\n     * @return true if the command was executed without any error\n     */\n    private static boolean executeAndWait(String command) {\n        try {\n            ProcessRunner.execute(command).waitFor();\n            return true;\n        }\n        catch(Exception e) {    // IOException, InterruptedException\n            LOGGER.debug(\"Caught exception\", e);\n            return false;\n        }\n    }\n\n    /**\n     * Executes the given command and waits for the process termination.\n     * Returns <code>true</code> if the command was executed without any error.\n     *\n     * @param command the command tokens\n     * @return true if the command was executed without any error\n     */\n    private static boolean executeAndWait(String command[]) {\n        try {\n            ProcessRunner.execute(command).waitFor();\n            return true;\n        }\n        catch(Exception e) {    // IOException, InterruptedException\n            LOGGER.debug(\"Caught exception\", e);\n            return false;\n        }\n    }\n\n    //////////////////////////////////\n    // AbstractTrash implementation //\n    //////////////////////////////////\n\n    /**\n     * Implementation notes: returns <code>true</code> only for local files that are not archive entries.\n     */\n    @Override\n    public boolean canMoveToTrash(AbstractFile file) {\n        return file.getTopAncestor() instanceof LocalFile;\n    }\n\n    /**\n     * Implementation notes: always returns <code>true</code>.\n     */\n    @Override\n    public boolean canEmpty() {\n        return true;\n    }\n\n    @Override\n    public boolean empty() {\n        return executeAndWait(EMPTY_TRASH_COMMAND);\n    }\n\n    @Override\n    public boolean isTrashFile(AbstractFile file) {\n        return (file.getTopAncestor() instanceof LocalFile)\n            && (file.getAbsolutePath(true).contains(\"/.local/share/Trash/\"));\n    }\n\n    /**\n     * Implementation notes: always returns <code>-1</code> (information not available).\n     */\n    @Override\n    public int getItemCount() {\n        return -1;\n    }\n\n    @Override\n    public void open() {\n        executeAndWait(baseCommand+\" exec trash:/\");\n    }\n\n    /**\n     * Implementation notes: always returns <code>true</code>.\n     */\n    @Override\n    public boolean canOpen() {\n        return true;\n    }\n\n\n    ////////////////////////////////\n    // QueuedTrash implementation //\n    ////////////////////////////////\n\n    @Override\n    protected boolean moveToTrash(List<AbstractFile> queuedFiles) {\n        int nbFiles = queuedFiles.size();\n        String tokens[] = new String[nbFiles+3];\n\n        tokens[0] = baseCommand;\n        tokens[1] = \"move\";\n\n        for(int i=0; i<nbFiles; i++) {\n            tokens[i+2] = queuedFiles.get(i).getAbsolutePath();\n        }\n\n        tokens[nbFiles+2] = \"trash:/\";\n\n        return executeAndWait(tokens);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/macos/OSXApplications.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2026 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.desktop.macos;\n\nimport com.mucommander.commons.runtime.OsFamily;\n\nimport java.io.File;\n\npublic class OSXApplications {\n    public static boolean iTermInstalled() {\n        return OsFamily.getCurrent() == OsFamily.MAC_OS_X && new File(\"/Applications/iTerm.app\").exists();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/macos/OSXDesktopAdapter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.macos;\n\nimport com.apple.eawt.FullScreenUtilities;\nimport com.apple.eio.FileManager;\n\nimport com.dd.plist.BinaryPropertyListParser;\nimport com.dd.plist.NSString;\nimport com.dd.plist.PropertyListFormatException;\n\nimport com.mucommander.ui.macosx.OSXIntegration;\nimport com.mucommander.ui.text.MultiLineLabel;\nimport com.sun.jna.platform.mac.XAttrUtils;\n\nimport com.mucommander.command.Command;\nimport com.mucommander.command.CommandException;\nimport com.mucommander.command.CommandManager;\nimport com.mucommander.command.CommandType;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport com.mucommander.commons.file.util.OSXFileUtils;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.commons.runtime.OsVersion;\nimport com.mucommander.commons.util.Pair;\nimport com.mucommander.desktop.DefaultDesktopAdapter;\nimport com.mucommander.desktop.DesktopInitializationException;\nimport com.mucommander.desktop.DesktopManager;\nimport com.mucommander.desktop.TrashProvider;\nimport com.mucommander.ui.macosx.AppleScript;\nimport com.mucommander.ui.macosx.TabbedPaneUICustomizer;\nimport com.mucommander.ui.notifier.AbstractNotifier;\nimport com.mucommander.ui.notifier.GrowlNotifier;\nimport com.mucommander.utils.text.Translator;\nimport org.slf4j.LoggerFactory;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.MouseEvent;\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.function.Consumer;\n\nimport static com.mucommander.command.CommandManager.registerDefaultCommand;\n\n/**\n * @author Nicolas Rinaudo\n */\npublic class OSXDesktopAdapter extends DefaultDesktopAdapter {\n    private static final String OPENER_COMMAND = \"open $f\";\n//    private static final String FINDER_COMMAND = \"open $f -R\";\n    private static final String FINDER_COMMAND = \"open -a Finder $f\";\n    private static final String FINDER_NAME    = \"Finder\";\n\n    /** The key of the comment attribute in file metadata */\n    public static final String COMMENT_PROPERTY_NAME = \"com.apple.metadata:kMDItemFinderComment\";\n    public static final String TAGS_PROPERTY_NAME = \"com.apple.metadata:_kMDItemUserTags\";\n\n    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(OSXDesktopAdapter.class);\n\n    public String toString() {\n        return \"Mac OS X Desktop\";\n    }\n\n    @Override\n    public boolean isAvailable() {\n        return OsFamily.MAC_OS_X.isCurrent();\n    }\n\n    @Override\n    public void init(boolean install) throws DesktopInitializationException {\n        // Initializes trash management.\n        DesktopManager.setTrashProvider(new OSXTrashProvider());\n\n        // Registers OS X specific commands.\n        try {\n            registerDefaultCommand(new Command(CommandManager.FILE_OPENER_ALIAS,  OPENER_COMMAND, CommandType.SYSTEM_COMMAND, null, null));\n            registerDefaultCommand(new Command(CommandManager.URL_OPENER_ALIAS,   OPENER_COMMAND, CommandType.SYSTEM_COMMAND, null, null));\n            registerDefaultCommand(new Command(CommandManager.FILE_MANAGER_ALIAS, FINDER_COMMAND, CommandType.SYSTEM_COMMAND, FINDER_NAME, null));\n\n            new OSXIntegration();\n        } catch(CommandException e) {\n            throw new DesktopInitializationException(e);\n        }\n    }\n\n    @Override\n    public boolean isLeftMouseButton(MouseEvent e) {\n        int modifiers = e.getModifiersEx();\n        return (modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0 && !e.isControlDown();\n    }\n\n    @Override\n    public boolean isRightMouseButton(MouseEvent e) {\n        int modifiers = e.getModifiersEx();\n        return (modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0 || ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0 && e.isControlDown());\n    }\n\n    /**\n     * Returns <code>true</code> for directories with an <code>app</code> extension (case-insensitive comparison).\n     *\n     * @param file the file to test\n     * @return <code>true</code> for directories with an <code>app</code> extension (case-insensitive comparison).\n     */\n    @Override\n    public boolean isApplication(AbstractFile file) {\n        String extension = file.getExtension();\n\n        // the isDirectory() test comes last as it is I/O bound\n        return \"app\".equalsIgnoreCase(extension) && file.isDirectory();\n    }\n\n    @Override\n    public TrashProvider getTrash() {\n        return new OSXTrashProvider();\n    }\n\n    @Override\n    public AbstractNotifier getNotifier() {\n        return GrowlNotifier.isGrowlRunning() ? new GrowlNotifier() : null;\n    }\n\n    @Override\n    public Consumer<JTabbedPane> getTabbedPaneCustomizer() {\n        return TabbedPaneUICustomizer::customizeTabbedPaneUI;\n    }\n\n    @Override\n    public void postCopy(AbstractFile sourceFile, AbstractFile destFile) {\n        if (sourceFile.hasAncestor(LocalFile.class) && destFile.hasAncestor(LocalFile.class)) {\n            String sourcePath = sourceFile.getAbsolutePath();\n            String destPath = destFile.getAbsolutePath();\n            copyFileUserTags(sourcePath, destPath);\n            copyFileTypeAndCreator(sourcePath, destPath);\n            copyFileComment(sourcePath, destPath);\n        }\n    }\n\n    private void copyFileUserTags(String sourcePath, String destPath) {\n        byte[] bytes = XAttrUtils.read(sourcePath, TAGS_PROPERTY_NAME);\n        if (bytes != null) {\n            XAttrUtils.write(destPath, TAGS_PROPERTY_NAME, bytes);\n        }\n    }\n\n    private void copyFileTypeAndCreator(String sourcePath, String destPath) {\n        try {\n            FileManager.setFileTypeAndCreator(destPath, FileManager.getFileType(sourcePath), FileManager.getFileCreator(sourcePath));\n        } catch(IOException e) {\n            // Swallow the exception and do not interrupt the transfer\n            LOGGER.debug(\"Error while setting macOS file type and creator on destination\", e);\n        }\n    }\n\n    private void copyFileComment(String sourcePath, String destPath) {\n        byte[] bytes = XAttrUtils.read(sourcePath, COMMENT_PROPERTY_NAME);\n        if (bytes == null) {\n            return;\n        }\n\n        String comment = null;\n        try {\n            NSString value = (NSString) BinaryPropertyListParser.parse(bytes);\n            if (value != null) {\n                comment = value.getContent();\n            }\n        } catch (IOException | PropertyListFormatException e) {\n            // Swallow the exception and do not interrupt the transfer\n            LOGGER.debug(\"Error while parsing macOS file comment of source\", e);\n        }\n        if (comment != null && !(comment = comment.trim()).isEmpty() && !setFileComment(destPath, comment)) {\n            LOGGER.error(\"Error while copying macOS file comment to %s\", destPath);\n        }\n    }\n\n    private boolean setFileComment(String path, String comment) {\n        String script = String.format(OSXFileUtils.SET_COMMENT_APPLESCRIPT, path, comment);\n        return AppleScript.execute(script, null);\n    }\n\n    public void customizeMainFrame(Window window) {\n        FullScreenUtilities.setWindowCanFullScreen(window, true);\n    }\n\n    @Override\n    public List<Pair<JLabel, JComponent>> getExtendedFileProperties(AbstractFile file) {\n        if (OsVersion.MAC_OS_X_10_4.isCurrentOrHigher()) {\n            String comment = OSXFileUtils.getSpotlightComment(file);\n            JLabel commentLabel = new JLabel(Translator.get(\"comment\")+\":\");\n            commentLabel.setAlignmentY(JLabel.TOP_ALIGNMENT);\n            commentLabel.setVerticalAlignment(SwingConstants.TOP);\n            return Collections.singletonList(new Pair<>(commentLabel, new MultiLineLabel(comment)));\n        }\n        return Collections.emptyList();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/macos/OSXTerminal.kt",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2026 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.desktop.macos\n\nimport com.mucommander.commons.file.AbstractFile\nimport com.mucommander.conf.TcConfigurations\nimport com.mucommander.conf.TcPreference\nimport com.mucommander.conf.TcPreferences\nimport com.mucommander.ui.macosx.AppleScript\n\nobject OSXTerminal {\n\n    @JvmStatic\n    fun addNewTabWithCommands(currentFolder: AbstractFile, vararg commands: String): Boolean =\n        if (getTerminalType() == TerminalApp.ITERM2) {\n            addNewITermTabWithCommands(currentFolder, *commands)\n        } else {\n            addNewTerminalTabWithCommands(currentFolder, *commands)\n        }\n\n    @JvmStatic\n    fun openNewWindowAndRun(currentFolder: AbstractFile, vararg commands: String): Boolean =\n        if (getTerminalType() == TerminalApp.ITERM2) {\n            openNewITermWindowAndRun(currentFolder, *commands)\n        } else {\n            openNewTerminalWindowAndRun(currentFolder, *commands)\n        }\n\n\n    private fun addNewTerminalTabWithCommands(currentFolder: AbstractFile, vararg commands: String): Boolean {\n        val dir = currentFolder.absolutePath.replace(\"'\", \"'\\\\''\")\n\n        val script = StringBuilder().apply {\n            append(\"on run argv\\n\")\n            append(\"tell application \\\"Terminal\\\"\\n\")\n            append(\"if not (exists window 1) then reopen\\n\")\n            append(\"activate\\n\")\n            append(\"do script \\\"cd '\").append(dir).append(\"' && clear\\\" in front window\\n\")\n            append(\"delay 0.3\\n\")\n            for (cmd in commands) {\n                val escapedCmd = cmd.replace(\"\\\"\", \"\\\\\\\"\")\n                append(\"do script \\\"\").append(escapedCmd).append(\"\\\" in front window\\n\")\n                append(\"delay 0.3\\n\")\n            }\n            append(\"end tell\\n\")\n            append(\"end run\\n\")\n        }.toString()\n        return AppleScript.execute(script, null, currentFolder)\n    }\n\n    private fun addNewITermTabWithCommands(currentFolder: AbstractFile, vararg commands: String): Boolean {\n        val dir = currentFolder.absolutePath.replace(\"'\", \"'\\\\''\")\n\n        val script = StringBuilder().apply {\n            append(\"on run argv\\n\")\n            append(\"tell application \\\"iTerm2\\\"\\n\")\n            append(\"activate\\n\")\n\n            // Create tab or new window\n            append(\"if (count of windows) > 0 then\\n\")\n            append(\"    tell current window\\n\")\n            append(\"        create tab with default profile\\n\")\n            append(\"        tell current session\\n\")\n\n            append(\"            write text \\\"cd '\").append(dir).append(\"'\\\"\\n\")\n\n            //        script.append(\"            write text \\\"clear\\\"\\n\");\n            for (cmd in commands) {\n                val escapedCmd = cmd.replace(\"\\\"\", \"\\\\\\\"\").replace(\"\\\\\", \"\\\\\\\\\")\n                append(\"            write text \\\"\").append(escapedCmd).append(\"\\\"\\n\")\n                append(\"            delay 0.15\\n\")\n            }\n\n            append(\"        end tell\\n\")\n            append(\"    end tell\\n\")\n            append(\"else\\n\")\n            // If window doesn't exist\n            append(\"    create window with default profile\\n\")\n            append(\"    tell current session of current window\\n\")\n            append(\"        write text \\\"cd '\").append(dir).append(\"'\\\"\\n\")\n            //        script.append(\"        write text \\\"clear\\\"\\n\");\n            for (cmd in commands) {\n                val escapedCmd = cmd.replace(\"\\\"\", \"\\\\\\\"\").replace(\"\\\\\", \"\\\\\\\\\")\n                append(\"        write text \\\"\").append(escapedCmd).append(\"\\\"\\n\")\n                append(\"        delay 0.15\\n\")\n            }\n            append(\"    end tell\\n\")\n            append(\"end if\\n\")\n\n            append(\"end tell\\n\")\n            append(\"end run\\n\")\n        }.toString()\n\n        return AppleScript.execute(script, null, currentFolder)\n    }\n\n\n    private fun openNewTerminalWindowAndRun(currentFolder: AbstractFile, vararg commands: String): Boolean {\n        val escapedPath = currentFolder.absolutePath\n            .replace(\"\\\\\", \"\\\\\\\\\")\n            .replace(\"'\", \"'\\\\\\\\''\")\n\n        val script = StringBuilder().apply {\n            append(\"tell application \\\"Terminal\\\"\\n\")\n            append(\"activate\\n\")\n\n            val cmdBuilder = StringBuilder()\n            cmdBuilder.append(\"cd '\").append(escapedPath).append(\"'\")\n            for (cmd in commands) {\n                val escapedCmd = cmd.replace(\"\\\\\", \"\\\\\\\\\").replace(\"'\", \"'\\\\\\\\''\")\n                cmdBuilder.append(\"; \").append(escapedCmd)\n            }\n\n            append(\"do script \\\"\").append(cmdBuilder).append(\"\\\"\\n\")\n            append(\"end tell\\n\")\n        }.toString()\n\n        return AppleScript.execute(script, null, currentFolder)\n    }\n\n    private fun openNewITermWindowAndRun(currentFolder: AbstractFile, vararg commands: String): Boolean {\n        val dir = currentFolder.absolutePath.replace(\"'\", \"'\\\\''\")\n\n        val script = StringBuilder().apply {\n            append(\"tell application \\\"iTerm2\\\"\\n\")\n            append(\"activate\\n\")\n\n            append(\"create window with default profile\\n\")\n            append(\"tell current session of current window\\n\")\n\n            append(\"    write text \\\"cd '\").append(dir).append(\"'\\\"\\n\")\n\n            //        script.append(\"    write text \\\"clear\\\"\\n\");\n            for (cmd in commands) {\n                val escapedCmd = cmd.replace(\"\\\"\", \"\\\\\\\"\").replace(\"\\\\\", \"\\\\\\\\\")\n                append(\"    write text \\\"\").append(escapedCmd).append(\"\\\"\\n\")\n                append(\"    delay 0.15\\n\")\n            }\n\n            append(\"end tell\\n\")\n            append(\"end tell\\n\")\n        }.toString()\n        return AppleScript.execute(script, null, currentFolder)\n    }\n\n    private fun getTerminalType(): TerminalApp {\n        val term = TcConfigurations.getPreferences().getVariable(TcPreference.EXTERNAL_TERMINAL_TYPE, TcPreferences.DEFAULT_TERMINAL_TYPE)\n        return if (term == TcPreferences.TERMINAL_ITERM && OSXApplications.iTermInstalled())\n            TerminalApp.ITERM2 else TerminalApp.TERMINAL\n    }\n\n    internal enum class TerminalApp {\n        TERMINAL, ITERM2\n    }\n}\n\n\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/macos/OSXTrash.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.macos;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport com.mucommander.desktop.QueuedTrash;\nimport com.mucommander.ui.macosx.AppleScript;\nimport com.sun.jna.platform.mac.MacFileUtils;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.OutputStreamWriter;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * OSXTrash provides access to the Mac OS X Finder's trash. Only local files (or locally mounted files) can be moved\n * to the trash.\n *\n * <p>\n *   <b>Implementation notes:</b><br>\n *   This trash is implemented as a {@link com.mucommander.desktop.QueuedTrash} for several reasons:\n *   <ul>\n *    <li>the Finder plays a sound when it has been told to move a file to the trash and is done with it.\n *        Moving files to the trash repeatedly would play the sound as many times as the Finder has been told to move a\n *        file, which is obviously ugly.</li>\n *    <li>executing an AppleScript has a cost as it has to be compiled first. When files are moved repeatedly, it is\n *        more efficient to group files and execute only one AppleScript.</li>\n *   </ul>\n *   <br>\n *   This class uses {@link com.mucommander.ui.macosx.AppleScript} to interact with the trash.\n *\n * @see OSXTrashProvider\n * @author Maxence Bernard\n */\n@Slf4j\npublic class OSXTrash extends QueuedTrash {\n    /** AppleScript that reveals the trash in Finder */\n    private final static String REVEAL_TRASH_APPLESCRIPT =\n        \"tell application \\\"Finder\\\" to open trash\\nactivate application \\\"Finder\\\"\\n\";\n\n    /** AppleScript that counts and returns the number of items in Trash */\n    private final static String COUNT_TRASH_ITEMS_APPLESCRIPT = \"tell application \\\"Finder\\\" to return count of items in trash\";\n\n    /** AppleScript that empties the trash */\n    private final static String EMPTY_TRASH_APPLESCRIPT = \"tell application \\\"Finder\\\" to empty trash\";\n\n    private static final MacFileUtils macFileUtils = new MacFileUtils();\n\n    /**\n     * AppleScript that moves files to the trash, for versions of AppleScript (1.10 or lower )that do not allow Unicode\n     * in the script itself (only MacRoman). As a result, this script is more complicated as the only way to deal with\n     * Unicode text is to read them from a file. See http://www.satimage.fr/software/en/unicode_and_applescript.html\n     * for more info about this workaround.\n     */\n    private final static String MOVE_TO_TRASH_APPLESCRIPT_NO_UNICODE =\n            // Loads the contents of the UTF8-encoded file which path is contained in the 'tmpFilePath' variable.\n            // This variable must be set before the beginning of the script. This file contains the list of files to move\n            // to the trash, separated by EOL characters. The file must NOT end with a trailing EOL.\n            \"set tmpFile to (open for access (POSIX file tmpFilePath))\\n\" +\n                    \"set tmpFileContents to (read tmpFile for (get eof tmpFile) as «class utf8»)\\n\" +\n                    \"close access tmpFile\\n\" +\n                    // Split the file contents into a list of lines, each line representing a POSIX file path to delete\n                    \"set posixFileList to every paragraph of tmpFileContents\\n\" +\n                    // Convert the list of POSIX paths into a list of file objects. Note that internally AppleScript uses\n                    // a Mac-specific colon-separated path notation rather than the POSIX one.\n                    \"set fileCount to the number of items in posixFileList\\n\" +\n                    \"set fileList to {}\\n\" +\n                    \"repeat with i from 1 to the fileCount\\n\" +\n                    \"set posixFile to item i of posixFileList\\n\" +\n                    \"copy POSIX file posixFile to the end of fileList\\n\" +\n                    \"end repeat\\n\" +\n                    // Tell the Finder to move those files to the trash. Note that the file list must contain file objects and not\n                    // POSIX paths, hence the previous step.\n                    \"tell application \\\"Finder\\\" to move fileList to the trash\";\n\n\n    /**\n     * Implementation notes: returns <code>true</code> only for local files that are not archive entries.\n     */\n    @Override\n    public boolean canMoveToTrash(AbstractFile file) {\n        return file.getTopAncestor() instanceof LocalFile;\n    }\n\n    /**\n     * Implementation notes: always returns <code>true</code>.\n     */\n    @Override\n    public boolean canEmpty() {\n        return true;\n    }\n\n    @Override\n    public boolean empty() {\n        return AppleScript.execute(EMPTY_TRASH_APPLESCRIPT, null);\n    }\n\n    @Override\n    public boolean isTrashFile(AbstractFile file) {\n        return (file.getTopAncestor() instanceof LocalFile) && (file.getAbsolutePath(true).contains(\"/.Trash/\"));\n    }\n\n    /**\n     * Implementation notes: this method is implemented and returns <code>-1</code> only if an error ocurred while\n     * retrieving the trash item count.\n     */\n    @Override\n    public int getItemCount() {\n        StringBuilder output = new StringBuilder();\n        if (!AppleScript.execute(COUNT_TRASH_ITEMS_APPLESCRIPT, output)) {\n            return -1;\n        }\n        try {\n            return Integer.parseInt(output.toString().trim());\n        } catch(NumberFormatException e) {\n            log.debug(\"Caught an exception\", e);\n            return -1;\n        }\n    }\n\n    @Override\n    public void open() {\n        AppleScript.execute(REVEAL_TRASH_APPLESCRIPT, null);\n    }\n\n    /**\n     * Implementation notes: always returns <code>true</code>.\n     */\n    @Override\n    public boolean canOpen() {\n        return true;\n    }\n\n\n    /**\n     * Performs the actual job of moving files to the trash using JNA.\n     */\n    @Override\n    protected boolean moveToTrash(List<AbstractFile> queuedFiles) {\n        if (queuedFiles.isEmpty())\n            return true;\n\n        var partitionedByIsSmb = queuedFiles.stream().collect(Collectors.partitioningBy(this::isSmb));\n        var nonSmbFiles = partitionedByIsSmb.get(false);\n        if (moveToTrashViaJna(nonSmbFiles)) {\n            var smbFiles = partitionedByIsSmb.get(true);\n            return moveToTrashViaAppleScript(smbFiles);\n        } else {\n            log.error(\"failed to move files to trash using JNA, fall back to AppleScript\");\n            return moveToTrashViaAppleScript(queuedFiles);\n        }\n    }\n    private boolean isSmb(AbstractFile file) {\n        try {\n            File underlyingFile = (File) file.getUnderlyingFileObject();\n            java.nio.file.Path path = underlyingFile.toPath();\n            java.nio.file.FileStore fs = java.nio.file.Files.getFileStore(path);\n            return \"smbfs\".equals(fs.type());\n        } catch (IOException e) {\n            log.warn(\"failed to retrieve FileStore of {}\", file, e);\n            return false;\n        }\n    }\n\n    private boolean moveToTrashViaJna(List<AbstractFile> queuedFiles) {\n        File[] files = queuedFiles.stream().map(AbstractFile::getAbsolutePath).map(File::new).toArray(File[]::new);\n        try {\n            macFileUtils.moveToTrash(files);\n        } catch (IOException e) {\n            log.error(\"failed to move files to trash\", e);\n            return false;\n        }\n        return true;\n    }\n\n    private boolean moveToTrashViaAppleScript(List<AbstractFile> queuedFiles) {\n        if (queuedFiles.isEmpty()) {\n            return true;\n        }\n        // Simple script for AppleScript versions with Unicode support, i.e. that allows Unicode characters in the\n        // script (AppleScript 2.0 / Mac OS X 10.5 or higher).\n        if (AppleScript.getScriptEncoding().equals(AppleScript.UTF8)) {\n            String appleScript = queuedFiles.stream()\n                    .map(AbstractFile::getAbsolutePath)\n                    .map(path -> String.format(\"posix file \\\"%s\\\"\", path))\n                    .collect(Collectors.joining(\", \", \"tell application \\\"Finder\\\" to move {\", \"} to the trash\"));\n\n            return AppleScript.execute(appleScript, null);\n        }\n        // Script for AppleScript versions without Unicode support (AppleScript 1.10 / Mac OS X 10.4 or lower)\n        else {\n            AbstractFile tmpFile = null;\n            OutputStreamWriter tmpOut = null;\n\n            try {\n                // Create the temporary file that contains the list of files to move, encoded as UTF-8 and separated by\n                // EOL characters. The file must NOT end with a trailing EOL.\n                int nbFiles = queuedFiles.size();\n                tmpFile = FileFactory.getTemporaryFile(\"trash_files.tc\", false);\n                tmpOut = new OutputStreamWriter(tmpFile.getOutputStream(), StandardCharsets.UTF_8);\n\n                for (int i = 0; i < nbFiles; i++) {\n                    tmpOut.write(queuedFiles.get(i).getAbsolutePath());\n                    if (i < nbFiles-1) {\n                        tmpOut.write(\"\\n\");\n                    }\n                }\n\n                tmpOut.close();\n\n                // Set the 'tmpFilePath' variable to the path of the temporary file we just created\n                String appleScript = \"set tmpFilePath to \\\"\" + tmpFile.getAbsolutePath() + \"\\\"\\n\";\n                appleScript += MOVE_TO_TRASH_APPLESCRIPT_NO_UNICODE;\n\n                boolean success = AppleScript.execute(appleScript, null);\n\n                // AppleScript has been executed, we can now safely close and delete the temporary file\n                tmpFile.delete();\n\n                return success;\n            } catch(IOException e) {\n                log.debug(\"Caught IOException\", e);\n\n                if (tmpOut != null) {\n                    try {\n                        tmpOut.close();\n                    } catch(IOException ignore) {\n                    }\n                }\n\n                if (tmpFile != null) {\n                    try {\n                        tmpFile.delete();\n                    } catch(IOException ignore) {\n                    }\n                }\n\n                return false;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/macos/OSXTrashProvider.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.macos;\n\nimport com.mucommander.desktop.AbstractTrash;\nimport com.mucommander.desktop.TrashProvider;\n\n/**\n * This class is a trash provider for the {@link OSXTrash Mac OS X trash}.\n *\n * @see OSXTrash\n * @author Maxence Bernard\n */\npublic class OSXTrashProvider implements TrashProvider {\n\n    public AbstractTrash getTrash() {\n        return new OSXTrash();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/openvms/OpenVMSDesktopAdapter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.openvms;\n\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.desktop.DefaultDesktopAdapter;\n\n/**\n * A desktop adapter for OpenVMS.\n *\n * <p>This adapter currently doesn't bring any improvement over {@link com.mucommander.desktop.DefaultDesktopAdapter} \n * -- its purpose is simply to bypass other desktop adapters tests, some of which are costly.\n *\n * @author Maxence Bernard\n */\npublic class OpenVMSDesktopAdapter extends DefaultDesktopAdapter {\n\n    @Override\n    public boolean isAvailable() {\n        return OsFamily.OPENVMS.isCurrent();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/windows/Win9xDesktopAdapter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.windows;\n\nimport com.mucommander.command.Command;\nimport com.mucommander.command.CommandException;\nimport com.mucommander.command.CommandManager;\nimport com.mucommander.command.CommandType;\nimport com.mucommander.commons.runtime.OsVersion;\nimport com.mucommander.desktop.DesktopInitializationException;\n\n/**\n * @author Nicolas Rinaudo\n */\npublic class Win9xDesktopAdapter extends WindowsDesktopAdapter {\n    private static final String OPENER_COMMAND = \"start \\\"$f\\\"\";\n\n    public String toString() {return \"Windows 9x Desktop\";}\n\n    @Override\n    public boolean isAvailable() {return super.isAvailable() && OsVersion.getCurrent().compareTo(OsVersion.WINDOWS_NT) < 0;}\n\n    @Override\n    public void init(boolean install) throws DesktopInitializationException {\n        super.init(install);\n        try {\n            CommandManager.registerDefaultCommand(new Command(CommandManager.FILE_OPENER_ALIAS,  OPENER_COMMAND, CommandType.SYSTEM_COMMAND, null, null));\n            CommandManager.registerDefaultCommand(new Command(CommandManager.URL_OPENER_ALIAS,   OPENER_COMMAND, CommandType.SYSTEM_COMMAND, null, null));\n            CommandManager.registerDefaultCommand(new Command(CommandManager.FILE_MANAGER_ALIAS, OPENER_COMMAND, CommandType.SYSTEM_COMMAND, EXPLORER_NAME, null));\n        } catch(CommandException e) {\n            throw new DesktopInitializationException(e);\n        }\n    }\n\n    @Override\n    public String getDefaultShell() {return \"command.com /c\";}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/windows/WinNtDesktopAdapter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.windows;\n\nimport com.mucommander.command.Command;\nimport com.mucommander.command.CommandException;\nimport com.mucommander.command.CommandManager;\nimport com.mucommander.command.CommandType;\nimport com.mucommander.commons.file.filter.RegexpFilenameFilter;\nimport com.mucommander.commons.runtime.OsVersion;\nimport com.mucommander.desktop.DesktopInitializationException;\n\n/**\n * @author Nicolas Rinaudo\n */\npublic class WinNtDesktopAdapter extends WindowsDesktopAdapter {\n    //private static final String FILE_OPENER_COMMAND = \"cmd /c start \\\"\\\" \\\"$f\\\"\";\n    private static final String FILE_OPENER_COMMAND = \"cmd /c cd \\\"$f\\\" && start .\";\n    private static final String EXE_OPENER_COMMAND  = \"cmd /c $f\";\n    private static final String EXE_REGEXP          = \".*\\\\.exe\";\n\n    public String toString() {\n        return \"Windows NT+ Desktop\";\n    }\n\n    @Override\n    public boolean isAvailable() {return super.isAvailable() && OsVersion.getCurrent().compareTo(OsVersion.WINDOWS_NT) >= 0;}\n\n    @Override\n    public void init(boolean install) throws DesktopInitializationException {\n        super.init(install);\n        try {\n            CommandManager.registerDefaultCommand(new Command(CommandManager.FILE_OPENER_ALIAS,  FILE_OPENER_COMMAND, CommandType.SYSTEM_COMMAND, null, null));\n            CommandManager.registerDefaultCommand(new Command(CommandManager.URL_OPENER_ALIAS,   FILE_OPENER_COMMAND, CommandType.SYSTEM_COMMAND, null, null));\n            CommandManager.registerDefaultCommand(new Command(CommandManager.FILE_MANAGER_ALIAS, FILE_OPENER_COMMAND, CommandType.SYSTEM_COMMAND, EXPLORER_NAME, null));\n            CommandManager.registerDefaultCommand(new Command(CommandManager.EXE_OPENER_ALIAS,   EXE_OPENER_COMMAND,  CommandType.SYSTEM_COMMAND, null, null));\n\n            CommandManager.registerDefaultAssociation(CommandManager.EXE_OPENER_ALIAS, new RegexpFilenameFilter(EXE_REGEXP, false));\n        } catch(CommandException e) {\n            throw new DesktopInitializationException(e);\n        }\n    }\n\n    @Override\n    public String getDefaultShell() {\n        return \"cmd /c\";\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/windows/WindowsDesktopAdapter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.windows;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.desktop.DefaultDesktopAdapter;\nimport com.mucommander.desktop.DesktopInitializationException;\nimport com.mucommander.desktop.DesktopManager;\n\n/**\n * @author Nicolas Rinaudo\n */\nclass WindowsDesktopAdapter extends DefaultDesktopAdapter {\n    static final String EXPLORER_NAME = \"Explorer\";\n\n    public String toString() {return \"Windows Desktop\";}\n\n    @Override\n    public void init(boolean install) throws DesktopInitializationException {\n        // The Windows trash requires access to the Shell32 DLL, register the provider only if the Shell32 DLL\n        // is available on the current runtime environment.\n        if (WindowsTrashProvider.isAvailable()) {\n            DesktopManager.setTrashProvider(new WindowsTrashProvider());\n        }\n    }\n\n    @Override\n    public boolean isAvailable() {\n        return OsFamily.WINDOWS.isCurrent();\n    }\n\n    /**\n     * Returns <code>true</code> for regular files (not directories) with an <code>exe</code> extension\n     * (case-insensitive comparison).\n     *\n     * @param file the file to test\n     * @return <code>true</code> for regular files (not directories) with an <code>exe</code> extension\n     * (case-insensitive comparison).\n     */\n    @Override\n    public boolean isApplication(AbstractFile file) {\n        String extension = file.getExtension();\n\n        // the isDirectory() test comes last as it is I/O bound\n        return extension != null && extension.equalsIgnoreCase(\"exe\") && !file.isDirectory();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/windows/WindowsTrash.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.windows;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport com.mucommander.commons.file.impl.local.SpecialWindowsLocation;\nimport com.mucommander.commons.file.util.Shell32;\nimport com.mucommander.commons.file.util.Shell32API;\nimport com.mucommander.desktop.DesktopManager;\nimport com.mucommander.desktop.QueuedTrash;\n\nimport java.io.IOException;\nimport java.util.List;\n\n/**\n * WindowsTrash is an <code>AbstractTrash</code> implementation for the <i>Microsoft Windows' Recycle Bin</i>.\n *\n * <p>Native methods in the Shell32 Windows API are used to access the Recycle Bin. There is an overhead associated with\n * invoking those methods (via JNA), so for performance reasons, this trash is implemented as a {@link com.mucommander.desktop.QueuedTrash}\n * in order to group calls to {@link #moveToTrash(com.mucommander.commons.file.AbstractFile)}.\n *\n * @see WindowsTrashProvider\n * @author Maxence Bernard\n */\npublic class WindowsTrash extends QueuedTrash {\n\n    /**\n     * Implementation notes: returns <code>true</code> only for local files that are not archive entries.\n     */\n    @Override\n    public boolean canMoveToTrash(AbstractFile file) {\n        return file.getTopAncestor() instanceof LocalFile;\n    }\n\n    /**\n     * Implementation notes: always returns <code>true</code>: {@link #empty()} is implemented.\n     */\n    @Override\n    public boolean canEmpty() {\n        return true;\n    }\n\n    @Override\n    public boolean empty() {\n        return Shell32.isAvailable() && Shell32.getInstance().SHEmptyRecycleBin(null, null, Shell32API.SHERB_NOCONFIRMATION) == 0;\n    }\n\n    /**\n     * Implementation notes: always returns <code>false</code>.\n     */\n    @Override\n    public boolean isTrashFile(AbstractFile file) {\n        // Quote from http://en.wikipedia.org/wiki/Recycle_Bin_(Windows):\n        // \"The actual location of the Recycle Bin varies depending on the operating system and filesystem. On the older\n        // FAT filesystems (typically Windows 98 and prior), it is located in Drive:\\RECYCLED. In the NTFS filesystem\n        // (Windows 2000, XP, NT) it can be found in Drive:\\RECYCLER, with the exception of Windows Vista which stores\n        // it in the Drive:\\$Recycle.Bin folder.\"\n\n        // => for the test to be accurate, we'd have to go thru the trouble of testing the kind of filesystem\n        // (FAT or NTFS) and the Windows version. It's a lot of work for little added value.\n\n        return false;\n    }\n\n    /**\n     * Implementation notes: returns the number of items for all Recycle Bins on all drives. This information is not\n     * available on certain versions of Windows such as <i>Windows 2000</i>.\n     */\n    @Override\n    public int getItemCount() {\n        if (!Shell32.isAvailable()) {\n            return -1;\n        }\n\n        Shell32API.SHQUERYRBINFO queryRbInfo = new Shell32API.SHQUERYRBINFO();\n\n        // pszRootPath is null to retrieve the information for all Recycle Bins on all drives. Microsoft's documentation\n        // states that this fails on certain versions of Windows such as Windows 2000. If it does, we simply return -1.\n\n        int ret = Shell32.getInstance().SHQueryRecycleBin(\n            null,\n            queryRbInfo\n        );\n\n        return ret == 0 ? (int)queryRbInfo.i64NumItems : -1;\n    }\n\n    /**\n     * Implementation notes: always returns <code>true</code>: {@link #open()} is implemented.\n     */\n    @Override\n    public boolean canOpen() {\n        return true;\n    }\n\n    @Override\n    public void open() {\n        try {\n            DesktopManager.openInFileManager(SpecialWindowsLocation.RECYCLE_BIN);\n        } catch(IOException e) {\n            // TODO: report error.\n        }\n    }\n\n\n    ////////////////////////////////\n    // QueuedTrash implementation //\n    ////////////////////////////////\n\n    @Override\n    protected boolean moveToTrash(List<AbstractFile> queuedFiles) {\n        if (!Shell32.isAvailable()) {\n            return false;\n        }\n\n        Shell32API.SHFILEOPSTRUCT fileop = new Shell32API.SHFILEOPSTRUCT();\n\n        fileop.wFunc = Shell32API.FO_DELETE;\n        fileop.fFlags = Shell32API.FOF_ALLOWUNDO| Shell32API.FOF_NOCONFIRMATION| Shell32API.FOF_SILENT;\n\n        int nbFiles = queuedFiles.size();\n\n        String[] paths = new String[nbFiles];\n        for (int i = 0; i<nbFiles; i++) {\n            // Directories (and regular files) must not end with a trailing slash or the operation will fail.\n            paths[i] = queuedFiles.get(i).getAbsolutePath(false);\n        }\n\n        // The encodePaths method takes care of encoding the paths in a special way.\n        fileop.pFrom = fileop.encodePaths(paths);\n\n        return Shell32.getInstance().SHFileOperation(fileop) == 0;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/windows/WindowsTrashProvider.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.windows;\n\nimport com.mucommander.commons.file.util.Shell32;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.desktop.AbstractTrash;\nimport com.mucommander.desktop.TrashProvider;\n\n/**\n * This class is a trash provider for the {@link WindowsTrash Windows trash}.\n *\n * @see WindowsTrash\n * @author Maxence Bernard\n */\npublic class WindowsTrashProvider implements TrashProvider {\n\n    public AbstractTrash getTrash() {\n        return new WindowsTrash();\n    }\n\n    /**\n     * Returns <code>true</code> if the Windows Trash can be used on the current runtime environment.\n     *\n     * @return <code>true</code> if the Windows Trash can be used on the current runtime environment.\n     */\n    public static boolean isAvailable() {\n        return OsFamily.WINDOWS.isCurrent() && Shell32.isAvailable();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/xfce/GuessedXfceDesktopAdapter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.xfce;\n\nimport com.mucommander.process.ProcessRunner;\n\n/**\n * 'Guessed' desktop adapter for Xfce. The availability of this desktop depends on the presence of the\n * <code>exo-open</code> command.\n * \n * @author Arik Hadas\n */\npublic class GuessedXfceDesktopAdapter extends XfceDesktopAdapter {\n\tpublic String toString() {return \"Xfce Desktop (guessed)\";}\n\n    @Override\n    public boolean isAvailable() {\n        try {\n            ProcessRunner.execute(\"exo-open\");\n            return true;\n        } catch(Exception e) {\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/xfce/XfceDesktopAdapter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.xfce;\n\nimport com.mucommander.command.Command;\nimport com.mucommander.command.CommandException;\nimport com.mucommander.command.CommandManager;\nimport com.mucommander.command.CommandType;\nimport com.mucommander.desktop.DefaultDesktopAdapter;\nimport com.mucommander.desktop.DesktopInitializationException;\nimport com.mucommander.desktop.DesktopManager;\n\n/**\n * @author Arik Hadas\n */\nabstract class XfceDesktopAdapter extends DefaultDesktopAdapter {\n\tprivate static final String FILE_MANAGER_NAME = \"Thunar\";\n    private static final String FILE_OPENER       = \"exo-open $f\";\n    private static final String EXE_OPENER        = \"$f\";\n    \n\t@Override\n    public void init(boolean install) throws DesktopInitializationException {\n        // Initialises trash management.\n        DesktopManager.setTrashProvider(new XfceTrashProvider());\n\n        // Registers KDE specific commands.\n        try {\n        \tCommandManager.registerDefaultCommand(new Command(CommandManager.FILE_OPENER_ALIAS,  FILE_OPENER, CommandType.SYSTEM_COMMAND, null, null));\n            CommandManager.registerDefaultCommand(new Command(CommandManager.URL_OPENER_ALIAS,   FILE_OPENER, CommandType.SYSTEM_COMMAND, null, null));\n            CommandManager.registerDefaultCommand(new Command(CommandManager.EXE_OPENER_ALIAS,   EXE_OPENER,  CommandType.SYSTEM_COMMAND, null, null));\n            CommandManager.registerDefaultCommand(new Command(CommandManager.FILE_MANAGER_ALIAS, FILE_OPENER, CommandType.SYSTEM_COMMAND, FILE_MANAGER_NAME, null));\n        }\n        catch(CommandException e) {throw new DesktopInitializationException(e);}\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/xfce/XfceTrash.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.xfce;\n\nimport java.io.IOException;\nimport java.io.OutputStreamWriter;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.desktop.QueuedTrash;\nimport com.mucommander.job.DeleteJob;\nimport com.mucommander.process.ProcessRunner;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.file.ProgressDialog;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.WindowManager;\n\n/**\n * This class provides access to the Xfce trash.\n *\n * <p>\n * <b>Implementation notes:</b><br>\n * <br>\n * This trash is implemented as a {@link com.mucommander.desktop.QueuedTrash} as it spawns a process to move a file to\n * the trash and it is thus more effective to group files to be moved instead of spawning multiple processes.<br>\n * \n * TODO: combine this trash and gnome trash to \"freedesktop\" trash\n * \n * @see XfceTrashProvider\n * @author Arik Hadas\n */\npublic class XfceTrash extends QueuedTrash {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(XfceTrash.class);\n\t\n\t/** Open trash folder in Thunar */ \n\tprivate final static String REVEAL_TRASH_COMMAND = \"thunar trash:///\";\n\n\t/**\n\t * User trash folder, as defined by the freedesktop specification (see http://freedesktop.org/wiki/Specifications/trash-spec)\n\t * <code>null</code> if there is no usable trash folder.\n\t */\n\tprivate final static AbstractFile TRASH_FOLDER;\n\n\t/** \"info\" subfolder of the user trash folder */\n\tprivate final static AbstractFile TRASH_INFO_SUBFOLDER;\n\n\t/** \"files\" subfolder of the user trash folder */\n\tprivate final static AbstractFile TRASH_FILES_SUBFOLDER;\n\n\t/** Volume on which the trash folder resides, used for checking whether a file can be moved to the trash or not */\n\tprivate final static AbstractFile TRASH_VOLUME;\n\n\t/** Formats dates in trash info files */\n\tprivate final static SimpleDateFormat INFO_DATE_FORMAT = new SimpleDateFormat(\"yyyy-MM-dd'T'HH:mm:ss\");\n\n\tstatic {\n\t\tTRASH_FOLDER = getTrashFolder();\n\t\tif(TRASH_FOLDER!=null) {\n\t\t\tTRASH_INFO_SUBFOLDER = TRASH_FOLDER.getChildSilently(\"info\");\n\t\t\tTRASH_FILES_SUBFOLDER = TRASH_FOLDER.getChildSilently(\"files\");\n\t\t\tTRASH_VOLUME = TRASH_FOLDER.getVolume();\n\t\t}\n\t\telse {\n\t\t\tTRASH_INFO_SUBFOLDER = null;\n\t\t\tTRASH_FILES_SUBFOLDER = null;\n\t\t\tTRASH_VOLUME = null;\n\t\t}\n\t}\n\n\t/**\n\t * Tries to find an existing user Trash folder and returns it. If no existing\n\t * Trash folder was found, creates the standard Xfce user Trash folder and returns it.\n\t *\n\t * @return the user Trash folder, <code>null</code> if no user trash folder could be found or created\n\t */\n\tprivate static AbstractFile getTrashFolder() {\n\t\tAbstractFile userHome = LocalFile.getUserHome();\n\n\t\tAbstractFile trashDir = userHome.getChildSilently(\".local/share/Trash/\");\n\t\tif (isTrashFolder(trashDir)) {\n\t\t\treturn trashDir;\n\t\t}\n\n\t\t// No existing user trash was found: create the folder, only if it doesn't already exist.\n\t\tif (!trashDir.exists()) {\n\t\t\ttry {\n\t\t\t\ttrashDir.mkdirs();\n\t\t\t\ttrashDir.getChild(\"info\").mkdir();\n\t\t\t\ttrashDir.getChild(\"files\").mkdir();\n\n\t\t\t\treturn trashDir;\n\t\t\t} catch(IOException e) {\n\t\t\t\t// Will return null\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * Return <code>true</code> if the specified file is a Xfce Trash folder, i.e. is a directory and has two\n\t * subdirectories named \"info\" and \"files\".\n\t *\n\t * @param file the file to test\n\t * @return <code>true</code> if the specified file is a Xfce Trash folder\n\t */\n\tprivate static boolean isTrashFolder(AbstractFile file) {\n\t\ttry {\n\t\t\treturn file.isDirectory() && file.getChild(\"info\").isDirectory() && file.getChild(\"files\").isDirectory();\n\t\t} catch(IOException e) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n     * Implementation of {@link com.mucommander.desktop.QueuedTrash} moveToTrash method.\n     * <p>\n     * Try to copy a collection of files to the Xfce's Trash.\n     *\n     * @param queuedFiles Collection of files to the trash\n     * @return <code>true</code> if movement has been successful or <code>false</code> otherwise\n     */\n    @Override\n    protected boolean moveToTrash(List<AbstractFile> queuedFiles) {\n        boolean retVal = true;     // overall return value (if everything went OK or at least one file wasn't moved properly\n\n        for (AbstractFile fileToDelete : queuedFiles) {\n            String fileInfoContent;\n            String trashFileName;\n\n            // generate content of info file and new filename\n            try {\n                fileInfoContent = getFileInfoContent(fileToDelete);\n                trashFileName = getUniqueFilename(fileToDelete);\n            } catch (IOException ex) {\n                LOGGER.debug(\"Failed to create filename for new trash item: \" + fileToDelete.getName(), ex);\n\n                // continue with other file (do not move file, because info file cannot be properly created\n                continue;\n            }\n\n            AbstractFile infoFile = null;\n            OutputStreamWriter infoWriter = null;\n            try {\n                // create info file\n                infoFile = TRASH_INFO_SUBFOLDER.getChild(trashFileName + \".trashinfo\");\n                infoWriter = new OutputStreamWriter(infoFile.getOutputStream());\n                infoWriter.write(fileInfoContent);\n            } catch (IOException ex) {\n                retVal = false;\n                LOGGER.debug(\"Failed to create trash info file: \" + trashFileName, ex);\n\n                // continue with other file (do not move file, because info file wasn't properly created)\n                continue;\n            } finally {\n                if (infoWriter != null) {\n                    try {\n                        infoWriter.close();\n                    } catch (IOException e) {\n                        // Not much else to do\n                    }\n                }\n            }\n\n            try {\n                // rename original file\n                fileToDelete.renameTo(TRASH_FILES_SUBFOLDER.getChild(trashFileName));\n            } catch (IOException ex) {\n                try {\n                    // remove info file\n                    infoFile.delete();\n\n                } catch (IOException ex1) {\n                    // simply ignore\n                }\n\n                retVal = false;\n                LOGGER.debug(\"Failed to move file to trash: \" + trashFileName, ex);\n            }\n        }\n\n        return retVal;\n    }\n\n\t/**\n     * Implementation notes: returns <code>true</code> only for local files that are not archive entries and that\n     * reside on the same volume as the trash folder.\n     */\n    @Override\n    public boolean canMoveToTrash(AbstractFile file) {\n        return TRASH_FOLDER != null\n            && file.getTopAncestor() instanceof LocalFile\n            && file.getVolume().equals(TRASH_VOLUME);\n    }\n\n\t/**\n     * <b>Implementation notes:</b> always returns <code>true</code>.\n     * \n     * @return True if trash can be emptied, otherwise false\n     */\n    @Override\n    public boolean canEmpty() {\n        return TRASH_FOLDER!=null;\n    }\n\n    /**\n     * Empty the trash\n     * <p>\n     * <b>Implementation notes:</b><br>\n     * Simply free the <code>TRASH_PATH</code> directory\n     * \n     * @return True if everything went well\n     */\n    @Override\n    public boolean empty() {\n        // Abort if there is no usable trash folder\n        if (TRASH_FOLDER == null) {\n            return false;\n        }\n\n        FileSet filesToDelete = new FileSet(TRASH_FOLDER);\n\n        try {\n            // delete real files\n            filesToDelete.addAll(TRASH_FILES_SUBFOLDER.ls());\n            // delete spec files\n            filesToDelete.addAll(TRASH_INFO_SUBFOLDER.ls());\n        } catch (java.io.IOException ex) {\n            LOGGER.debug(\"Failed to list files\", ex);\n            return false;\n        }\n\n        if (filesToDelete.size() > 0) {\n            // Starts deleting files\n            MainFrame mainFrame = WindowManager.getCurrentMainFrame();\n            ProgressDialog progressDialog = new ProgressDialog(mainFrame, Translator.get(\"delete_dialog.deleting\"));\n            DeleteJob deleteJob = new DeleteJob(progressDialog, mainFrame, filesToDelete, false);\n            progressDialog.start(deleteJob);\n        }\n            \n        return true;\n    }\n\n    @Override\n    public boolean isTrashFile(AbstractFile file) {\n        return TRASH_FOLDER != null\n            && (file.getTopAncestor() instanceof LocalFile)\n            && TRASH_FOLDER.isParentOf(file);\n    }\n\n\t/**\n     * Return trash files count\n     * <p>\n     * We assume the count of items in trash equals the count of files in \n     * <code>TRASH_PATH + \"/info\"</code> folder.\n     * \n     * @return Count of files in trash\n     */\n    @Override\n    public int getItemCount() {\n        // Abort if there is no usable trash folder\n        if (TRASH_FOLDER == null) {\n            return -1;\n        }\n\n        try {\n            return TRASH_INFO_SUBFOLDER.ls().length;\n        } catch (java.io.IOException ex) {\n            // can't access trash folder\n            return -1;\n        }\n    }\n\n    /**\n     * Opens the trash in Thunar.\n     */\n    @Override\n    public void open() {\n        try {\n            ProcessRunner.execute(REVEAL_TRASH_COMMAND).waitFor();\n        }\n        catch(Exception e) {    // IOException, InterruptedException\n            LOGGER.debug(\"Caught an exception running command \\\"\" + REVEAL_TRASH_COMMAND + \"\\\"\", e);\n        }\n    }\n\n\t@Override\n    public boolean canOpen() {\n        return TRASH_FOLDER!=null;\n    }\n\t\n\t/**\n     * Make a content of .trashinfo file\n     * @param file File for which the content is built\n     * @return Final content\n     */\n    private String getFileInfoContent(AbstractFile file) {\n        synchronized(INFO_DATE_FORMAT) {        // SimpleDateFormat is not thread safe\n            return \"[Trash Info]\\n\" +\n                    \"Path=\" + file.getAbsolutePath() + \"\\n\" +\n                    \"DeletionDate=\" + INFO_DATE_FORMAT.format(new Date());\n        }\n    }\n    \n    /**\n     * It is possible to add several files with same name to the Trash. These files are distinguished\n     * by _N appended to the name, where _N is rising int number. <br/>\n     * This method tries to find first empty <code>filename_N.ext</code>.\n     *\n     * @param file File to be deleted\n     * @return Suitable filename in trash (without .trashinfo extension)\n     */\n    private String getUniqueFilename(AbstractFile file) throws IOException {\n        // try if no previous file in trash exists\n        if (!TRASH_FILES_SUBFOLDER.getChild(file.getName()).exists()) {\n            return file.getName();\n        }\n\n        String rawName = file.getNameWithoutExtension();\n        String extension = file.getExtension();\n\n        // find first empty filename in format filename_N.ext\n        int count = 1;\n        while (true) {\n            String filename = rawName + \"_\" + count++;\n            if (extension != null) {\n                filename += \".\" + extension;\n            }\n\n            if (!TRASH_FILES_SUBFOLDER.getChild(filename).exists()) {\n                return filename;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/desktop/xfce/XfceTrashProvider.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.desktop.xfce;\n\nimport com.mucommander.desktop.AbstractTrash;\nimport com.mucommander.desktop.TrashProvider;\n\n/**\n * This class is a trash provider for the {@link XfceTrash Xfce trash}.\n *\n * @see XfceTrash\n * @author Arik Hadas\n */\npublic class XfceTrashProvider implements TrashProvider {\n\n\t/*******************************\n\t * TrashProvider Implementation\n\t *******************************/\n\tpublic AbstractTrash getTrash() {\n\t\treturn new XfceTrash();\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/extension/ClassFilter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.extension;\n\n/**\n * Used to IMAGE_FILTER classes.\n * <p>\n * <code>ClassFilter</code> implementations are meant to be used in conjonction with {@link ClassFinder}.\n *\n * @author Nicolas Rinaudo\n */\npublic interface ClassFilter {\n    /**\n     * Returns <code>true</code> if the specified class must be used.\n     * @param  c class that must be evaluated.\n     * @return   <code>true</code> if the specified class must be used, <code>false</code> otherwise.\n     */\n    boolean accept(Class<?> c);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/extension/ClassFinder.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.extension;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Vector;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.AbstractFileClassLoader;\nimport com.mucommander.commons.file.filter.AttributeFileFilter;\nimport com.mucommander.commons.file.filter.AttributeFileFilter.FileAttribute;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.filter.OrFileFilter;\n\n/**\n * Finds specific classes within a browsable file.\n * <p>\n * This class will explore the content of a browsable {@link com.mucommander.commons.file.AbstractFile} and match\n * all discovered classes to a {@link ClassFilter}.\n *\n * <p>\n * In order for classes to be analyzed, they need to be loaded. This can be achieved in two ways:\n * <ul>\n *   <li>By using a custom class loader through {@link #find(AbstractFile,ClassFilter,ClassLoader)}.</li>\n *   <li>\n *     By using an {@link com.mucommander.commons.file.AbstractFileClassLoader}\n * </ul>\n *\n * @author Nicolas Rinaudo\n */\npublic class ClassFinder {\n    /** ClassLoader used to load classes from explored files. */\n    private ClassLoader  loader;\n    /** Used to IMAGE_FILTER out files that are neither classes nor directories. */\n    private final OrFileFilter filter;\n    /** Used to IMAGE_FILTER out unwanted classes. */\n    private ClassFilter  classFilter;\n\n\n    /**\n     * Creates a new instance of <code>ClassFinder</code>.\n     */\n    public ClassFinder() {\n        filter = new OrFileFilter(\n            new ExtensionFilenameFilter(\".class\"),\n            new AttributeFileFilter(FileAttribute.DIRECTORY)\n        );\n    }\n\n\n\n    // - File exploring ------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    /**\n     * Explores the specified file for classes that match {@link #classFilter}.\n     * @param  currentPackage package we're currently exploring (with a trailing '.').\n     * @param  currentFile    file we're currently exploring.\n     * @return a vector containing all the classes that were found and matched <code>classFilter</code>.\n     * @throws IOException    if an error occurs while exploring <code>currentFile</code>.\n     */\n    private List<Class<?>> find(String currentPackage, AbstractFile currentFile) throws IOException {\n        AbstractFile[] files = currentFile.ls(filter);        // All subfolders or child class files of currentFile.\n        List<Class<?>> result = new ArrayList<>();\n        \n        // Analyses all subdirectories and class files.\n        for (AbstractFile file : files) {\n            // Explores subdirectories recursively.\n            if (file.isDirectory())\n                result.addAll(find(currentPackage + file.getName() + '.', file));\n\n                // Passes each class through the class IMAGE_FILTER.\n                // Errors are treated as 'this class is not wanted'.\n            else {\n                try {\n                    Class<?> currentClass; // Buffer for the current class.\n                    if (classFilter.accept(currentClass = Class.forName(currentPackage + file.getNameWithoutExtension(), false, loader)))\n                        result.add(currentClass);\n                } catch (Throwable ignore) {\n                }\n            }\n        }\n        return result;\n    }\n\n\n    /**\n     * Explores the content of the specified file and looks for classes that match the specified class IMAGE_FILTER.\n     * <p>\n     * The <code>browsable</code> argument must be browsable as defined by {@link com.mucommander.commons.file.AbstractFile#isBrowsable()}.\n     * If such is not the case, the returned vector will be empty.\n     *\n     * @param  browsable   file in which to look for classes.\n     * @param  classFilter how to decide which classes should be kept.\n     * @param  classLoader used to load each class found in <code>browsable</code>.\n     * @return             a vector containing all the classes that were found and matched <code>classFilter</code>.\n     * @throws IOException if an error occurs while exploring <code>browsable</code>.\n     * @see                #find(AbstractFile,ClassFilter)\n     */\n    public List<Class<?>> find(AbstractFile browsable, ClassFilter classFilter, ClassLoader classLoader) throws IOException {\n        // Ignore non-browsable files.\n        if (!browsable.isBrowsable()) {\n            return new Vector<>();\n        }\n\n        // Initializes exploring.\n        loader = classLoader;\n        this.classFilter = classFilter;\n\n        // Looks for all matched classes in browsable.        \n        return find(\"\", browsable);\n    }\n\n    /**\n     * Explores the content of the specified file and looks for classes that match the specified class IMAGE_FILTER.\n     * <p>\n     * This is a convenience method and is strictly equivalent to calling {@link #find(AbstractFile,ClassFilter,ClassLoader)}\n     * with a class loader argument initialized with the following code:\n     * <pre>\n     * AbstractFileClassLoader loader;\n     *\n     * loader = new AbstractFileClassLoader();\n     * loader.addFile(browsable);\n     * </pre>\n     *\n     * @param  browsable   file in which to look for classes.\n     * @param  classFilter how to decide which classes should be kept.\n     * @return             a vector containing all the classes that were found and matched <code>classFilter</code>.\n     * @throws IOException if an error occurs while exploring <code>browsable</code>.\n     */\n    public List<Class<?>> find(AbstractFile browsable, ClassFilter classFilter) throws IOException {\n        // Initializes the default class loader.\n        AbstractFileClassLoader classLoader = new AbstractFileClassLoader();\n        classLoader.addFile(browsable);\n\n        // Explores browsable.\n        return find(browsable, classFilter, classLoader);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/extension/ExtensionManager.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.extension;\n\nimport com.mucommander.PlatformManager;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.AbstractFileClassLoader;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.StringTokenizer;\n\n/**\n * Manages muCommander's extensions.\n * <p>\n * Extensions must be stored in {@link #getExtensionsFolder()} in order for this class to be aware of them.\n * Moreover, the method {@link #addExtensionsToClasspath()} must have been called before extensions can be used.\n *\n * <p>\n * Extensions are loaded through a custom <code>ClassLoader</code>. The optimal situation is for that <code>ClassLoader</code>\n * to be the system one, which can only be achieved through setting the <code>java.system.class.loader</code> system property\n * to <code>com.mucommander.commons.file.AbstractFileClassLoader</code> at boot time.<br>\n * However, if for some reason such is not the case, we'll use a separate instance of that class. This will work in most cases, but\n * might cause conflicts under rare circumstances. Extension writers are advised to load resources through the <code>ClassLoader</code>\n * returned by {@link #getClassLoader()}, as not doing so might result in using the bootstrap classloader which doesn't have access to\n * resources found in {@link #getExtensionsFolder()}.\n *\n * <p>\n * This class can also be used to load Swing look and feel from JAR files that aren't in the system's classpath. In order to achieve this,\n * application writers must:\n * <ul>\n *   <li>\n *     Call <code>UIManager.getDefaults().put(\"ClassLoader\", ExtensionManager.getClassLoader());</code> when initializing their application.\n *     This will force Swing to use our custom classloader when loading Look&amp;Feels.\n *   </li>\n *   <li>\n *     Call <code>UIManager.setLookAndFeel((LookAndFeel)Class.forName(lnfName, true, ExtensionManager.getClassLoader()).newInstance());</code>\n *     to set a new look and feel. This will ensure that all classes and resources are available when initializing the Look&amp;Feel.\n *   </li>\n * </ul>\n * <p>\n * Unfortunately, this is not always sufficient. Some Look&amp;Feels suffer from a peculiar behavior in Swing that might cause resources to be loaded\n * through the system class loader rather than the one specified at initialization time. This happens with Look&amp;Feels that extend system ones, such\n * as <code>Quaqua</code>. The only way to get these to load properly is to make sure the system classloader is an instance of\n * {@link com.mucommander.commons.file.AbstractFileClassLoader}.\n *\n * @author Nicolas Rinaudo\n */\npublic class ExtensionManager {\n    /** ClassLoader used to load all extensions. */\n    private static AbstractFileClassLoader loader;\n\n    /** Path to the extensions folder. */\n    private static AbstractFile extensionsFolder;\n    /** Default name of the extensions folder. */\n    private static final String DEFAULT_EXTENSIONS_FOLDER_NAME = \"extensions\";\n\n\n\n    public static void init() {\n        ClassLoader temp = ClassLoader.getSystemClassLoader();\n\n        // Initializes the extension class loader.\n        if (temp instanceof AbstractFileClassLoader) {\n            // If the system classloader is an instance of AbstractFileClassLoader, use it.\n            loader = (AbstractFileClassLoader) temp;\n        } else {\n            // Otherwise, use a new instance of AbstractFileClassLoader.\n            loader = new AbstractFileClassLoader(ExtensionManager.class.getClassLoader());\n        }\n//        \n//        UIManager.put(\"ClassLoader\", loader);\n    }\n\n    /**\n     * Prevents instantiations of this class.\n     */\n    private ExtensionManager() {}\n\n    /**\n     * Sets the path to the folder in which all extensions are stored.\n     * <p>\n     * If the specified path is not browsable (i.e. a folder or any file that muCommander can treat as such), its parent\n     * will be used instead.\n     *\n     * @param  folder      path to the folder in which extensions are stored.\n     * @throws IOException if the specified folder or the specified file's parent couldn't be accessed.\n     * @see                #setExtensionsFolder(AbstractFile)\n     * @see                #setExtensionsFolder(String)\n     * @see                #getExtensionsFolder()\n     */\n    private static void setExtensionsFolder(File folder) throws IOException {\n        AbstractFile file = FileFactory.getFile(folder.getAbsolutePath());\n        setExtensionsFolder(file);\n    }\n\n    /**\n     * Sets the path to the folder in which all extensions are stored.\n     * <p>\n     * If the specified path is not browsable (i.e. a folder or any file that muCommander can treat as such), its parent\n     * will be used instead.\n     *\n     * @param  folder      path to the folder in which extensions are stored.\n     * @throws IOException if the specified folder or the specified file's parent couldn't be accessed.\n     * @see                #setExtensionsFolder(File)\n     * @see                #setExtensionsFolder(String)\n     * @see                #getExtensionsFolder()\n     */\n    private static void setExtensionsFolder(AbstractFile folder) throws IOException {\n        // If the folder doesn't exist, create it.\n        if (!folder.exists()) {\n            folder.mkdir();\n        } else if (!folder.isBrowsable()) {\n            // If it's not a browsable file, use its parent.\n            folder = folder.getParent();\n        }\n\n        extensionsFolder = folder;\n    }\n\n    /**\n     * Sets the path to the folder in which all extensions are stored.\n     * <p>\n     * If the specified path is not browsable (i.e. a folder or any file that muCommander can treat as such), its parent\n     * will be used instead.\n     *\n     * @param  path        path to the folder in which extensions are stored.\n     * @throws IOException if the specified folder or the specified file's parent couldn't be accessed.\n     * @see                #setExtensionsFolder(File)\n     * @see                #setExtensionsFolder(String)\n     * @see                #getExtensionsFolder()\n     */\n    public static void setExtensionsFolder(String path) throws IOException {\n        AbstractFile folder = FileFactory.getFile(path);\n\n        if (folder == null) {\n            setExtensionsFolder(new File(path));\n        } else {\n            setExtensionsFolder(folder);\n        }\n    }\n\n    /**\n     * Returns the path to the default extensions folder.\n     * <p>\n     * The default path is:\n     * <pre>\n     * {@link PlatformManager#getPreferencesFolder()}.{@link AbstractFile#getChild(String) getChild}({@link #DEFAULT_EXTENSIONS_FOLDER_NAME});\n     * </pre>\n     *\n     * @return             the path to the default extensions folder.\n     * @throws IOException if there was an error retrieving the default extensions folder.\n     */\n    private static AbstractFile getDefaultExtensionsFolder() throws IOException {\n        AbstractFile folder = PlatformManager.getPreferencesFolder().getChild(DEFAULT_EXTENSIONS_FOLDER_NAME);\n\n        // Makes sure the folder exists.\n        if (!folder.exists()) {\n            folder.mkdir();\n        }\n\n        return folder;\n    }\n\n    /**\n     * Returns the folder in which all extensions are stored.\n     * @return             the folder in which all extensions are stored.\n     * @throws IOException if an error occured while locating the default extensions folder.\n     * @see                #setExtensionsFolder(AbstractFile)\n     */\n    private static AbstractFile getExtensionsFolder() throws IOException {\n        // If the extensions folder has been set, use it.\n        if (extensionsFolder != null) {\n            return extensionsFolder;\n        }\n\n        return getDefaultExtensionsFolder();\n    }\n\n    /**\n     * Returns an <code>AbstractFile</code> to the extension file with the specified filename and located in the\n     * {@link #getExtensionsFolder() extensions folder}. The returned file may or may not exist.\n     * @param  filename    the extension's filename \n     * @return             an AbstractFile to the extension file with the specified filename and located in the\n     * extensions folder.\n     * @throws IOException if the file could not be instantiated.\n     */\n    public static AbstractFile getExtensionsFile(String filename) throws IOException {\n        return getExtensionsFolder().getDirectChild(filename);\n    }\n\n\n    // - Classpath querying -----------------------------------------------------\n    // --------------------------------------------------------------------------\n    /**\n     * Returns <code>true</code> if the specified file is in the extension's classloader path.\n     * @param  file file whose presence in the extensions path will be checked.\n     * @return      <code>true</code> if the specified file is in the extension's classloader path, <code>false</code> otherwise.\n     */\n    private static boolean isInExtensionsPath(AbstractFile file) {return loader.contains(file);}\n\n    /**\n     * Returns <code>true</code> if the specified file is in the system classpath.\n     * @param  file file whose presence in the system classpath will be checked.\n     * @return      <code>true</code> if the specified file is in the system classpath, <code>false</code> otherwise.\n     */\n    private static boolean isInClasspath(AbstractFile file) {\n        String path = file.getAbsolutePath();\n        StringTokenizer parser = new StringTokenizer(System.getProperty(\"java.class.path\"), System.getProperty(\"path.separator\"));\n        while (parser.hasMoreTokens()) {\n            if (parser.nextToken().equals(path)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Returns <code>true</code> if the specified file is either in the extension or system classpath.\n     * <p>\n     * This is a convenience method and is equivalent to calling:\n     * <code>{@link #isInClasspath(AbstractFile) isInClasspath}(file) || {@link #isInExtensionsPath(AbstractFile) isInExtensionsPath}(file)</code>.\n     *\n     * @param file file whose availability will be checked.\n     * @return <code>true</code> if the specified file is either in the extension or system classpath, <code>false</code> otherwise.\n     */\n    public static boolean isAvailable(AbstractFile file) {return isInClasspath(file) || isInExtensionsPath(file);}\n\n\n\n    // - Classpath extension ----------------------------------------------------\n    // --------------------------------------------------------------------------\n    /**\n     * Imports the specified file in muCommander's libraries.\n     * @param file  path to the library to import.\n     * @param  force       wether to overwrite eventual existing libraries of the same name.\n     * @return             <code>true</code> if the operation was a success,\n     *                     <code>false</code> if a library of the same name already exists and\n     *                     <code>force</code> is set to <code>false</code>.\n     * @throws IOException if an I/O error occurs.\n     */\n    public static boolean importLibrary(AbstractFile file, boolean force) throws IOException {\n        // If the file is already in the extensions or classpath,\n        // there's nothing to do.\n        if (isAvailable(file)) {\n            return true;\n        }\n\n        // If the destination file already exists, either delete it\n        // if force is set to true or just return false.\n        AbstractFile dest = getExtensionsFile(file.getName());\n        if (dest.exists()) {\n            if (!force) {\n                return false;\n            }\n            dest.delete();\n        }\n\n        // Copies the library and adds it to the extensions classpath.\n        file.copyTo(dest);\n        addToClassPath(dest);\n        return true;\n    }\n\n    /**\n     * Adds the specified file to the extension's classpath.\n     * @param file file to add to the classpath.\n     */\n    private static void addToClassPath(AbstractFile file) {loader.addFile(file);}\n\n    /**\n     * Adds all known extensions to the current classpath.\n     * <p>\n     * This method will create the following new classpath entries:\n     * <ul>\n     *   <li>{@link #getExtensionsFolder()}.</li>\n     *   <li>All <code>JAR</code> files in {@link #getExtensionsFolder()}.</li>\n     * </ul>\n     *\n     * @throws IOException if the extensions folder is not accessible.\n     */\n    public static void addExtensionsToClasspath() throws IOException {\n        // Adds the extensions folder to the classpath.\n        addToClassPath(getExtensionsFolder());\n\n        // Adds all JAR files contained by the extensions folder to the classpath.\n        AbstractFile[] files = getExtensionsFolder().ls(new ExtensionFilenameFilter(\".jar\"));\n        for (AbstractFile file : files) {\n            addToClassPath(file);\n        }\n    }\n\n    /**\n     * Returns the <code>ClassLoader</code> used to load all extensions.\n     * @return the <code>ClassLoader</code> used to load all extensions.\n     */\n    public static ClassLoader getClassLoader() {\n        return loader;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/extension/LookAndFeelFilter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.extension;\n\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Modifier;\n\nimport javax.swing.LookAndFeel;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Class IMAGE_FILTER for look and feels.\n * <p>\n * This IMAGE_FILTER will only accept classes if:\n * <ul>\n *   <li>They subclass <code>javax.swing.LookAndFeel</code>.</li>\n *   <li>They are public and not abstract.</li>\n *   <li>They have a public, no-arg constructor.</li>\n *   <li>Their <code>isSupportedLookAndFeel</code> method returns <code>true</code>.</li>\n *   <li>They are not an inner class.</li>\n * </ul>\n *\n * @author Nicolas Rinaudo\n */\npublic class LookAndFeelFilter implements ClassFilter {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(LookAndFeelFilter.class);\n\t\n\t/**\n     * Creates a new instance of <code>LookAndFeelFilter</code>.\n     */\n    public LookAndFeelFilter() {}\n\n    /**\n     * Filters out everything but available look and feels.\n     * @param c class to check.\n     * @return <code>true</code> if c is an available look and feel, <code>false</code> otherwise.\n     */\n    public boolean accept(Class<?> c) {\n        // Ignores inner classes.\n        if (c.getDeclaringClass() != null) {\n            return false;\n        }\n        return isPublicAndNotAbstract(c) && hasPublicDefaultConstructor(c) && isAvailableLookAndFeel(c);\n    }\n\n    private static boolean isPublicAndNotAbstract(Class<?> c) {\n        // Makes sure the class is public and non-abstract.\n        int modifiers = c.getModifiers();\n        return Modifier.isPublic(modifiers) && !Modifier.isAbstract(modifiers);\n    }\n\n\n    private static boolean hasPublicDefaultConstructor(Class<?> c) {\n        // Makes sure the class has a public, no-arg constructor.\n        try {\n            Constructor<?> constructor = c.getDeclaredConstructor();\n            return Modifier.isPublic(constructor.getModifiers());\n        } catch(Exception e) {\n            return false;\n        }\n    }\n\n    private static boolean isAvailableLookAndFeel(Class<?> c) {\n        // Makes sure the class extends javax.swing.LookAndFeel and that if it does,\n        // it's supported by the system.\n        Class<?> buffer = c;\n        while (buffer != null) {\n            // c is a LookAndFeel, makes sure it's supported.\n            if (buffer.equals(LookAndFeel.class)) {\n                return isSupportedLookAndFeel(c);\n            }\n            buffer = buffer.getSuperclass();\n        }\n        return false;\n    }\n\n    private static boolean isSupportedLookAndFeel(Class<?> c) {\n        try {\n            return ((LookAndFeel) c.getDeclaredConstructor().newInstance()).isSupportedLookAndFeel();\n        } catch(Throwable e) {\n            LOGGER.debug(\"Class {} caught exception\", c, e);\n            return false;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/extension/package.html",
    "content": "<body>\n  Provides extension mechanisms.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/io/backup/BackupConstants.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.io.backup;\n\n/**\n * Defines various constants common to backup classes.\n * @author Nicolas Rinaudo\n */\ninterface BackupConstants {\n    /** Character to add suffix file names with in order to mark them as backup. */\n    char BACKUP_SUFFIX = '~';\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/io/backup/BackupInputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.io.backup;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.FileURL;\n\nimport java.io.*;\n\n/**\n * Opens an input stream on a file that has been saved by {@link BackupOutputStream}.\n * <p>\n * This class' role is to choose which of the original or backup file should be read in order to ensure\n * that the data is not corrupt.\n *\n * @see BackupOutputStream\n * @author Nicolas Rinaudo\n */\npublic class BackupInputStream extends FilterInputStream implements BackupConstants {\n    /**\n     * Opens a backup input stream on the specified file.\n     * @param     file        file to open for reading.\n     * @exception IOException thrown if any IO related error occurs.\n     */\n    public BackupInputStream(File file) throws IOException {\n        super(getInputStream(file));\n    }\n\n    /**\n     * Opens a backup input stream on the specified file.\n     * @param     path        path to the file to open for reading.\n     * @exception IOException thrown if any IO related error occurs.\n     */\n    public BackupInputStream(String path) throws IOException {\n        super(getInputStream((new File(path))));\n    }\n\n    /**\n     * Opens a backup input stream on the specified file.\n     * @param     file        file to open for reading.\n     * @exception IOException thrown if any IO related error occurs.\n     */\n    public BackupInputStream(AbstractFile file) throws IOException {\n        super(getInputStream(file));\n    }\n\n    /**\n     * Opens a stream on the right file.\n     * <p>\n     * If a backup file is found, and is bigger than the target file, then it will be used.\n     *\n     * @param     file        file on which to open an input stream.\n     * @return                a stream on the right file.\n     * @exception IOException thrown if any IO related error occurs.\n     */\n    private static InputStream getInputStream(AbstractFile file) throws IOException {\n        FileURL test = (FileURL)file.getURL().clone();\n        test.setPath(test.getPath() + BACKUP_SUFFIX);\n        // Checks whether the backup file is a better choice than the target one.\n        AbstractFile backup = FileFactory.getFile(test);\n\n        if (backup != null && backup.exists() && (file.getSize() < backup.getSize())) {\n            return backup.getInputStream();\n        }\n\n        // Opens a stream on the target file.\n        return file.getInputStream();\n    }\n\n\n    /**\n     * Opens a stream on the right file.\n     * <p>\n     * If a backup file is found, and is bigger than the target file, then it will be used.\n     *\n     * @param     file        file on which to open an input stream.\n     * @return                a stream on the right file.\n     * @exception IOException thrown if any IO related error occurs.\n     */\n    private static InputStream getInputStream(File file) throws IOException {\n        File backup = new File(file.getAbsolutePath() + BACKUP_SUFFIX);\n\n        if (backup.exists() && (file.length() < backup.length())) {\n            return new FileInputStream(backup);\n        }\n\n        // Opens a stream on the target file.\n        return new FileInputStream(file);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/io/backup/BackupOutputStream.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.io.backup;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * Saves file in as crash-safe a manner as possible.\n * <p>\n * In order to prevent system or muCommander failures to corrupt configuration files,\n * the BackupOutputStream implements the following algorithm:\n * <ul>\n *   <li>Write its content to a backup file instead of the requested file</li>\n *   <li>When close is called, copy the content of the backup file over the original file</li>\n * </ul>\n * This way, if a crash was to happen while configuration files are being saved, either of the\n * following will happen:\n * <ul>\n *   <li>\n *     The backup file is not properly saved, but the original configuration is left untouched.\n *     We have lost <i>some</i> information (modifications since last save) but not <i>all</i>.\n *   </li>\n *   <li>\n *     The original file is not properly saved, but the backup file is correct. This is easy to check,\n *     as the backup and original file should always have the same size. If they don't, then the backup\n *     file should be used rather than the original one.\n *   </li>\n * </ul>\n * <p>\n * Files that have been saved by this class should be read with {@link BackupInputStream}\n * in order to make sure that an uncorrupt version of them is loaded.\n * <p>\n * The <code>BackupOutputStream</code> monitors all of its own I/O operations. If an error occurs, then the backup\n * operation will not be performed when {@link #close()} is called. It's possible to force the backup operation by\n * using the {@link #close(boolean)} method.\n *\n * @see    BackupInputStream\n * @author Nicolas Rinaudo\n */\npublic class BackupOutputStream extends OutputStream implements BackupConstants {\n    /** The underlying OutputStream */\n    private final OutputStream out;\n    /** Path of the original file. */\n    private final AbstractFile     target;\n    /** Path to the backup file. */\n    private final AbstractFile     backup;\n    /** Whether, or not an error occurred while writing to the backup file. */\n    private boolean          error;\n\n\n\n    /**\n     * Opens a backup output stream on the specified file.\n     * @param     file        file on which to open a backup output stream.\n     * @exception IOException thrown if any IO error occurs.\n     */\n    public BackupOutputStream(File file) throws IOException {\n        this(FileFactory.getFile(file.getAbsolutePath()));\n    }\n\n    /**\n     * Opens a backup output stream on the specified file.\n     * @param     file        file on which to open a backup output stream.\n     * @exception IOException thrown if any IO error occurs.\n     */\n    public BackupOutputStream(String file) throws IOException {\n        this(FileFactory.getFile((new File(file)).getAbsolutePath()));\n    }\n\n    /**\n     * Opens a backup output stream on the specified file.\n     * @param     file        file on which to open a backup output stream.\n     * @exception IOException thrown if any IO error occurs.\n     */\n    public BackupOutputStream(AbstractFile file) throws IOException {\n        this(file, FileFactory.getFile(file.getAbsolutePath() + BACKUP_SUFFIX));\n    }\n\n    /**\n     * Opens an output stream on the specified file using the specified backup file.\n     * @param     file        file on which to open the backup output stream.\n     * @param     save        file that will be used for backup.\n     * @exception IOException thrown if any IO error occurs.\n     */\n    private BackupOutputStream(AbstractFile file, AbstractFile save) throws IOException {\n        out = save.getOutputStream();\n        target = file;\n        backup = save;\n    }\n\n\n\n    // - Error catching ---------------------------------------------------------\n    // --------------------------------------------------------------------------\n    /**\n     * Flushes this output stream and forces any buffered output bytes to be written out to the stream.\n     * <p>\n     * This method calls the <code>flush()</code> method of its underlying output stream.\n     * <p>\n     * If an error occurs at this point, the {@link #close()} method will not overwrite the target file. This can be\n     * forced through the {@link #close(boolean)} method.\n     *\n     * @throws IOException if an I/O error occurs.\n     */\n    @Override\n    public void flush() throws IOException {\n        if(error)\n            out.flush();\n        else {\n            try {out.flush();}\n            catch(IOException e) {\n                error = true;\n                throw e;\n            }\n        }\n    }\n\n    /**\n     * Writes b.length bytes to this output stream.\n     * <p>\n     * This method calls the <code>write(byte[] b)</code> method of its underlying output stream.\n     * <p>\n     * If an error occurs at this point, the {@link #close()} method will not overwrite the target file. This can be\n     * forced through the {@link #close(boolean)} method.\n     *\n     * @param  b           the data to be written.\n     * @throws IOException if an I/O error occurs.\n     */\n    @Override\n    public void write(@NotNull byte[] b) throws IOException {\n        if (error) {\n            out.write(b);\n        } else {\n            try {\n                out.write(b);\n            } catch(IOException e) {\n                error = true;\n                throw e;\n            }\n        }\n    }\n\n    /**\n     * Writes len bytes from the specified byte array starting at offset off to this output stream.\n     * <p>\n     * This method calls the <code>write(byte[] b, int off, int len)</code> method of its underlying output stream.\n     * <p>\n     * If an error occurs at this point, the {@link #close()} method will not overwrite the target file. This can be\n     * forced through the {@link #close(boolean)} method.\n     *\n     * @param  b           the data to be written.\n     * @param  off         the start offset in the data.\n     * @param  len         the number of bytes to write.\n     * @throws IOException if an I/O error occurs.\n     */\n    @Override\n    public void write(@NotNull byte[] b, int off, int len) throws IOException {\n        if (error) {\n            out.write(b, off, len);\n        } else {\n            try {\n                out.write(b, off, len);\n            } catch(IOException e) {\n                error = true;\n                throw e;\n            }\n        }\n    }\n\n    /**\n     * Writes the specified byte to this output stream.\n     * <p>\n     * This method calls the <code>write(byte b)</code> method of its underlying output stream.\n     * <p>\n     * If an error occurs at this point, the {@link #close()} method will not overwrite the target file. This can be\n     * forced through the {@link #close(boolean)} method.\n     *\n     * @param  b           the data to be written.\n     * @throws IOException if an I/O error occurs.\n     */\n    @Override\n    public void write(int b) throws IOException {\n        if (error) {\n            out.write(b);\n        } else {\n            try {\n                out.write(b);\n            } catch(IOException e) {\n                error = true;\n                throw e;\n            }\n        }\n    }\n\n\n\n    // - Backup -----------------------------------------------------------------\n    // --------------------------------------------------------------------------\n    /**\n     * Overwrites the target file with the backup one.\n     * @exception IOException thrown if any IO related error occurs.\n     */\n    private void backup() throws IOException {\n        // Deletes the destination file (AbstractFile.copyTo now fails when the destination exists).\n        if (target.exists()) {\n            target.delete();\n        }\n\n        // We're not using backup.moveTo(target) because we want to make absolutely sure\n        // that if an error occurs in the middle of the operation, at least one of the two files\n        // is complete.\n        backup.copyTo(target);\n        backup.delete();\n    }\n\n    /**\n     * Finishes the backup operation.\n     * @exception IOException thrown if any IO related error occurs.\n     */\n    @Override\n    public void close() throws IOException {close(!error);}\n\n    /**\n     * Closes the output stream.\n     * <p>\n     * The <code>backup</code> parameter is meant for those cases when an error happened\n     * while writing to the stream: if it did, we don't want to propagate to the target\n     * file, and thus should prevent the backup operation from being performed.\n     *\n     * @param     backup      whether to overwrite the target file by the backup one.\n     * @exception IOException thrown if any IO related error occurs.\n     */\n    public void close(boolean backup) throws IOException {\n        // Closes the underlying output stream.\n        out.flush();\n        out.close();\n\n        if (backup) {\n            backup();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/io/package.html",
    "content": "<body>\n  This package contains I/O classes that are application-specific, and therefore not in com.mucommander.commons.io.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/AbstractCopyJob.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.job;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.AbstractRWArchiveFile;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.file.FileCollisionDialog;\nimport com.mucommander.ui.dialog.file.FileCollisionRenameDialog;\nimport com.mucommander.ui.dialog.file.ProgressDialog;\nimport com.mucommander.ui.main.MainFrame;\n\nimport java.io.IOException;\n\n/**\n * This class is the parent class of {@link com.mucommander.job.CopyJob} and {@link com.mucommander.job.MoveJob} and\n * allows them to share methods and fields.\n *\n * @author Maxence Bernard, Mariusz Jakubowski\n * @see com.mucommander.job.CopyJob\n * @see com.mucommander.job.MoveJob\n */\npublic abstract class AbstractCopyJob extends TransferFileJob {\n    \n    /** Base destination folder */\n    AbstractFile baseDestFolder;\n    \n    /** New filename in destination */\n    protected String newName;\n\n    /** Default choice when encountering an existing file */\n    private int defaultFileExistsAction;//\n    \n    /** Title used for error dialogs */\n    protected String errorDialogTitle;\n    \n    protected boolean append;\n    \n    /** The archive that contains the destination files (maybe null) */\n    AbstractRWArchiveFile archiveToOptimize;\n\n    /** True when an archive is being optimized */\n    boolean isOptimizingArchive;\n\n\n    /**\n     * Creates a new <code>AbstractCopyJob</code>.\n     *\n     * @param progressDialog dialog which shows this job's progress\n     * @param mainFrame mainFrame this job has been triggered by\n     * @param files files which are going to be copied\n     * @param destFolder destination folder where the files will be copied\n     * @param newName the new filename in the destination folder, can be <code>null</code> in which case the original filename will be used.\n     * @param fileExistsAction default action to be performed when a file already exists in the destination, see {@link com.mucommander.ui.dialog.file.FileCollisionDialog} for allowed values\n     */\n    AbstractCopyJob(ProgressDialog progressDialog, MainFrame mainFrame,\n                    FileSet files, AbstractFile destFolder, String newName, int fileExistsAction) {\n        super(progressDialog, mainFrame, files);\n\n        this.baseDestFolder = destFolder;\n        this.newName = newName;        \n        this.defaultFileExistsAction = fileExistsAction;\n    }\n\n    /**\n     * Creates a destination file given a destination folder and a new file name.\n     * @param destFolder a destination folder\n     * @param destFileName a destination file name\n     * @return the destination file or null if it cannot be created\n     */\n    AbstractFile createDestinationFile(AbstractFile destFolder, String destFileName) {\n        do {    // Loop for retry\n            try {\n                return destFolder.getDirectChild(destFileName);\n            } catch (IOException e) {\n                // Destination file couldn't be instantiated\n\n                int ret = showErrorDialog(errorDialogTitle, Translator.get(\"cannot_write_file\", destFileName));\n                // Retry loops\n                if (ret == RETRY_ACTION) {\n                    continue;\n                }\n                // Cancel or close dialog return false\n                return null;\n                // Skip continues\n            }\n        } while(true);\n    }\n    \n    /**\n     * Checks if there is a file collision (file exists in the destination).\n     * If there is no collision this method returns destFile.\n     * If there is a collision this method returns: <ul>\n     *  <li>null if a user canceled the transfer\n     *  <li>null if a user skipped the file\n     *  <li>destFile if a user resumed the transfer (and sets append flag)\n     *  <li>destFile if a user has chosen to overwrite the file\n     *  <li>new file if a user renamed the file\n     *  </ul>\n     * @param file a source file\n     * @param destFolder a destination folder\n     * @param destFile a destination file\n     * @param allowCaseVariation if true,\n     * @return destFile the new destination file\n     */\n    protected AbstractFile checkForCollision(AbstractFile file, AbstractFile destFolder, AbstractFile destFile, boolean allowCaseVariation) {\n        append = false;\n        while (true) {\n            // Check for file collisions (file exists in the destination, destination subfolder of source, ...)\n            // if a default action hasn't been specified\n            int collision = FileCollisionChecker.checkForCollision(file, destFile);\n            \n            // If allowCaseVariation is true and both files are equal, test if the destination filename is a variation\n            // of the original filename with a different case. If that is the case, do not warn about the source and\n            // destination being the same.\n            if (allowCaseVariation && collision==FileCollisionChecker.SAME_SOURCE_AND_DESTINATION) {\n                String sourceFileName = file.getName();\n                String destFileName = destFile.getName();\n                if(sourceFileName.equalsIgnoreCase(destFileName) && !sourceFileName.equals(destFileName))\n                    break;\n            }\n            \n            // Handle collision, asking the user what to do or using a default action to resolve the collision \n            if (collision != FileCollisionChecker.NO_COLLISION) {\n                int choice;\n                // Use default action if one has been set, if not show up a dialog\n                if (defaultFileExistsAction==FileCollisionDialog.ASK_ACTION) {\n                    FileCollisionDialog dialog = new FileCollisionDialog(getProgressDialog(), getMainFrame().getJFrame(), collision, file, destFile, true, true);\n                    choice = waitForUserResponse(dialog);\n                    // If 'apply to all' was selected, this choice will be used for any other files (user will not be asked again)\n                    if (dialog.applyToAllSelected())\n                        defaultFileExistsAction = choice;\n                } else {\n                    choice = defaultFileExistsAction;\n                }\n    \n                // Cancel, skip or close dialog\n                if (choice == -1 || choice == FileCollisionDialog.CANCEL_ACTION) {\n                    interrupt();\n                    return null;\n                }\n                // Skip file\n                else if (choice == FileCollisionDialog.SKIP_ACTION) {\n                    return null;\n                }\n                // Append to file (resume file copy)\n                else if (choice == FileCollisionDialog.RESUME_ACTION) {\n                    append = true;\n                    break;\n                }\n                // Overwrite file\n                else if (choice== FileCollisionDialog.OVERWRITE_ACTION) {\n                    // Do nothing, simply continue\n                    break;\n                }\n                //  Overwrite file if destination is older\n                else if (choice == FileCollisionDialog.OVERWRITE_IF_OLDER_ACTION) {\n                    // Overwrite if file is newer (strictly)\n                    if (file.getLastModifiedDate() <= destFile.getLastModifiedDate())\n                        return null;\n                    break;\n                } else if (choice == FileCollisionDialog.RENAME_ACTION) {\n                    setPaused(true);\n                    FileCollisionRenameDialog dlg = new FileCollisionRenameDialog(getMainFrame(), destFile);\n                    String destFileName = (String) waitForUserResponseObject(dlg);\n                    setPaused(false);\n                    if (destFileName != null) {\n                        destFile = createDestinationFile(destFolder, destFileName);\n                    } else {\n                        // turn on FileCollisionDialog, so we don't loop indefinitely\n                        defaultFileExistsAction = FileCollisionDialog.ASK_ACTION;\n                    }\n                    // continue with collision checking\n                    continue;\n                }\n            }\n            break;    // no collision\n        }\n        return destFile;\n    }\n    \n    /**\n     * Optimizes the given writable archive file and notifies the user in case of an error.\n     *\n     * @param rwArchiveFile the writable archive file to optimize\n     */\n    void optimizeArchive(AbstractRWArchiveFile rwArchiveFile) {\n        isOptimizingArchive = true;\n\n        while (true) {\n            try {\n                archiveToOptimize = rwArchiveFile;\n                archiveToOptimize.optimizeArchive();\n                break;\n            } catch (IOException e) {\n                if(showErrorDialog(errorDialogTitle, Translator.get(\"error_while_optimizing_archive\", rwArchiveFile.getName()))==RETRY_ACTION)\n                    continue;\n\n                break;\n            }\n        }\n\n        isOptimizingArchive = false;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/ArchiveJob.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.job;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport com.mucommander.commons.file.archiver.ArchiveFormat;\nimport com.mucommander.job.utils.ScanDirectoryThread;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.archiver.Archiver;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.commons.io.StreamUtils;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.file.FileCollisionDialog;\nimport com.mucommander.ui.dialog.file.ProgressDialog;\nimport com.mucommander.ui.main.MainFrame;\n\n\n/**\n * This FileJob is responsible for compressing a set of files into an archive file.\n *\n * @author Maxence Bernard\n */\npublic class ArchiveJob extends TransferFileJob {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(ArchiveJob.class);\n\t\n    /** Destination archive file */\n    private final AbstractFile destFile;\n\n    /** Base destination folder's path */\n    private final String baseFolderPath;\n\n    /** Archiver instance that does the actual archiving */\n    private Archiver archiver;\n\n    /** Archive format */\n    private final ArchiveFormat archiveFormat;\n\t\n    /** Optional archive comment */\n    private final String archiveComment;\n\t\n    /** Lock to avoid Archiver.close() to be called while data is being written */\n    private final Object ioLock = new Object();\n\n    private final ScanDirectoryThread scanDirectoryThread;\n\n    /** Processed files counter */\n    private long processedFilesCount;\n\n\n\n    public ArchiveJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet files, AbstractFile destFile, ArchiveFormat archiveFormat, String archiveComment) {\n        super(progressDialog, mainFrame, files);\n\t\t\n        this.destFile = destFile;\n        this.archiveFormat = archiveFormat;\n        this.archiveComment = archiveComment;\n\n        this.baseFolderPath = getBaseSourceFolder().getAbsolutePath(false);\n\n        scanDirectoryThread = new ScanDirectoryThread(files);\n        scanDirectoryThread.start();\n    }\n\n    @Override\n    protected boolean processFile(AbstractFile file, Object recurseParams) {\n        if (getState() == State.INTERRUPTED) {\n            return false;\n        }\n\n        String filePath = file.getAbsolutePath(false);\n        String entryRelativePath = filePath.substring(baseFolderPath.length()+1);\n\n        // Process current file\n        do {\t\t// Loop for retry\n            try {\n                if (file.isDirectory() && !file.isSymlink()) {\n                    // create new directory entry in archive file\n                    archiver.createEntry(entryRelativePath, file);\n\n                    // Recurse on files\n                    AbstractFile[] subFiles = file.ls();\n                    boolean folderComplete = true;\n                    for (int i=0; i<subFiles.length && getState() != State.INTERRUPTED; i++) {\n                        // Notify job that we're starting to process this file (needed for recursive calls to processFile)\n                        nextFile(subFiles[i]);\n                        if (!processFile(subFiles[i], null)) {\n                            folderComplete = false;\n                        }\n                    }\n\t\t\t\t\t\n                    return folderComplete;\n                } else {\n                    InputStream in = setCurrentInputStream(file.getInputStream());\n                    // Synchronize this block to ensure that Archiver.close() is not closed while data is still being\n                    // written to the archive OutputStream, this would cause ZipOutputStream to deadlock.\n                    synchronized(ioLock) {\n                        // create a new file entry in archive and copy the current file\n                        StreamUtils.copyStream(in, archiver.createEntry(entryRelativePath, file));\n                        in.close();\n                    }\n                    return true;\n                }\n            } catch (Exception e) {  // Catch Exception rather than IOException as ZipOutputStream has been seen throwing NullPointerException\n                // If job was interrupted by the user at the time when the exception occurred,\n                // it most likely means that the exception was caused by user cancellation.\n                // In this case, the exception should not be interpreted as an error.\n                if (getState() == State.INTERRUPTED) {\n                    return false;\n                }\n\n                LOGGER.debug(\"Caught IOException\", e);\n                \n                int ret = showErrorDialog(Translator.get(\"pack_dialog.error_title\"), Translator.get(\"error_while_transferring\", file.getAbsolutePath()));\n                // Retry loops\n                if (ret == RETRY_ACTION) {\n                    // Reset processed bytes currentFileByteCounter\n                    getCurrentFileByteCounter().reset();\n\n                    continue;\n                }\n                // Cancel, skip or close dialog return false\n                return false;\n            }\n        } while(true);\n    }\n\n    @Override\n    protected boolean hasFolderChanged(AbstractFile folder) {\n        // This job modifies the folder where the archive is\n        return folder.equalsCanonical(destFile.getParent());     // Note: parent may be null\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    /**\n     * Overriden method to initialize the archiver and handle the case where the destination file already exists.\n     */\n    @Override\n    protected void jobStarted() {\n        super.jobStarted();\n\n        // Check for file collisions, i.e. if the file already exists in the destination\n        int collision = FileCollisionChecker.checkForCollision(null, destFile);\n        if (collision!=FileCollisionChecker.NO_COLLISION) {\n            // File already exists in destination, ask the user what to do (cancel, overwrite,...) but\n            // do not offer the multiple files mode options such as 'skip' and 'apply to all'.\n            int choice = waitForUserResponse(new FileCollisionDialog(getProgressDialog(), getMainFrame().getJFrame(), collision, null, destFile, false, false));\n\n            // Overwrite file\n            if (choice == FileCollisionDialog.OVERWRITE_ACTION) {\n                // Do nothing, simply continue and file will be overwritten\n            } else {\n                // 'Cancel' or close dialog interrupts the job\n                interrupt();\n                return;\n            }\n        }\n\n        // Loop for retry\n        do {\n            try {\n                // Tries to get an Archiver instance.\n                this.archiver = Archiver.getArchiver(destFile, archiveFormat);\n                this.archiver.setComment(archiveComment);\n\n                break;\n            } catch (Exception e) {\n                int choice = showErrorDialog(Translator.get(\"pack_dialog.error_title\"),\n                                             Translator.get(\"cannot_write_file\", destFile.getName()),\n                                             new String[] {CANCEL_TEXT, RETRY_TEXT},\n                                             new int[]  {CANCEL_ACTION, RETRY_ACTION}\n                                             );\n\n                // Retry loops\n                if (choice == RETRY_ACTION) {\n                    continue;\n                }\n\n                // 'Cancel' or close dialog interrupts the job\n                interrupt();\n                return;\n            }\n        } while(true);\n    }\n\n    /**\n     * Overriden method to close the archiver.\n     */\n    @Override\n    public void jobStopped() {\n\n        // TransferFileJob.jobStopped() closes the current InputStream, this will cause copyStream() to return\n        super.jobStopped();\n\n        // Synchronize this block to ensure that Archiver.close() is not closed while data is still being\n        // written to the archive OutputStream, this would cause ZipOutputStream to deadlock.\n        synchronized(ioLock) {\n            // Try to close the archiver which in turns closes the archive OutputStream and underlying file OutputStream\n            if (archiver!=null) {\n                try {\n                    archiver.close();\n                } catch(IOException ignore) {}\n            }\n        }\n    }\n\n    @Override\n    public String getStatusString() {\n        return Translator.get(\"pack_dialog.packing_file\", getCurrentFilename());\n    }\n\n\n    @Override\n    public void interrupt() {\n        if (scanDirectoryThread != null) {\n            scanDirectoryThread.interrupt();\n        }\n        super.interrupt();\n    }\n\n\n    @Override\n    public float getTotalPercentDone() {\n        if (scanDirectoryThread == null || !scanDirectoryThread.isCompleted()) {\n            float result = super.getTotalPercentDone();\n            return result > 5 ? 5 : result;\n        }\n        float progressBySize = 1.0f*(getTotalByteCounter().getByteCount() + getTotalSkippedByteCounter().getByteCount()) / scanDirectoryThread.getTotalBytes();\n        float progressByCount = 1.0f*(processedFilesCount-1) / scanDirectoryThread.getFilesCount();\n        float result = (progressBySize * 8 + progressByCount * 2) / 10;\n        if (result < 0) {\n            result = 0;\n        } else if (result > 1) {\n            result = 1;\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/BatchRenameJob.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.job;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.ui.dialog.file.FileCollisionDialog;\r\nimport com.mucommander.ui.dialog.file.ProgressDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport java.util.List;\r\n\r\n/**\r\n * This job renames a group of files to new names defined by Batch-Rename Dialog.\r\n * @author Mariusz Jakubowski\r\n */\r\npublic class BatchRenameJob extends MoveJob {\r\n    private List<String> newNames;\r\n\r\n    public BatchRenameJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet files, List<String> newNames) {\r\n        super(progressDialog, mainFrame, files, files.getBaseFolder(), null, FileCollisionDialog.ASK_ACTION, true);\r\n        this.newNames = newNames;\r\n    }\r\n\r\n\r\n    ////////////////////////////\r\n    // FileJob implementation //\r\n    ////////////////////////////\r\n\r\n    @Override\r\n    protected boolean processFile(AbstractFile file, Object recurseParams) {\r\n        this.newName = newNames.get(getCurrentFileIndex());\r\n        return super.processFile(file, recurseParams);\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/CalculateChecksumJob.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.job;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.security.MessageDigest;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.file.FileCollisionDialog;\nimport com.mucommander.ui.dialog.file.ProgressDialog;\nimport com.mucommander.ui.icon.IconManager;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.viewer.ViewerRegistrar;\n\n/**\n * This job calculates a checksum for a list of files and stores the results in a checksum file.\n *\n * <p>The format of this file is a de facto standard ; a line is created for each file and goes like this:\n * <pre>\n * e7e9576b9e55940b4b8522a65902d4cd  readme.txt\n * 119abda7c941135d5bf382c386bca2ca  i386/debian-40r1-i386-DVD-1.iso\n * 3c0d332902b9b8dfec43ba02d1618c6e  ppc/debian-40r1-ppc-DVD-1.iso\n * ...\n * </pre>\n * The path of each file is relative to the checksum file's path. In the above example, <code>readme.txt</code> and\n * the checksum file are located in the same folder. Note that 2 space characters (and not just one as anyone in his\n * right mind would think) separate the hexadecimal checksum from the file path.\n *\n * <p>The above file format is used for all checksum algorithms but one: CRC32, which uses the special SFV format where\n * the checksum for each file is written as follow:\n * <pre>\n * wne-ebai.r00 697115b2\n * wne-ebai.r01 f80a8443\n * ...\n * </pre>\n *\n * @author Maxence Bernard\n */\npublic class CalculateChecksumJob extends TransferFileJob {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(CalculateChecksumJob.class);\n\t\n    /** The checksum file where the checksum of each file is written */\n    private AbstractFile checksumFile;\n    /** The OutputStream of the checksum file */\n    private OutputStream checksumFileOut;\n\n    /** The path to the base source folder, i.e. the folder which contains all the files this job operates on */\n    private String baseSourcePath;\n\n    /** True if the SFV format is used rather than the default 'SUMS' format */\n    private boolean useSfvFormat;\n\n    /** The MessageDigest that serves to calculate the checksum */\n    private MessageDigest digest;\n\n\n    public CalculateChecksumJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet files, AbstractFile checksumFile, MessageDigest digest) {\n        super(progressDialog, mainFrame, files);\n\n        this.checksumFile = checksumFile;\n        this.digest = digest;\n        this.useSfvFormat = digest.getAlgorithm().equalsIgnoreCase(\"CRC32\");\n\n        this.baseSourcePath = getBaseSourceFolder().getAbsolutePath(true);\n    }\n\n\n    ////////////////////////////////////\n    // TransferFileJob implementation //\n    ////////////////////////////////////\n\n    @Override\n    protected boolean processFile(AbstractFile file, Object recurseParams) {\n        // Skip directories\n        if(file.isDirectory()) {\n            do {\t\t// Loop for retry\n                try {\n                    // for each file in folder...\n                    AbstractFile children[] = file.ls();\n                    for (int i=0; i<children.length && getState() != State.INTERRUPTED; i++) {\n                        // Notify job that we're starting to process this file (needed for recursive calls to processFile)\n                        nextFile(children[i]);\n                        processFile(children[i], null);\n                    }\n\n                    return true;\n                } catch(IOException e) {\n                    // file.ls() failed\n                    int ret = showErrorDialog(Translator.get(\"error\"), Translator.get(\"cannot_read_folder\", file.getName()));\n                    // Retry loops\n                    if (ret == RETRY_ACTION) {\n                        continue;\n                    }\n                    // Cancel, skip or close dialog returns false\n                    return false;\n                }\n            } while(true);\n        }\n\n        // Calculate the file's checksum\n        do {\t\t// Loop for retry\n\n            try {\n                // Determine the path relative to the base source folder\n                String relativePath = file.getAbsolutePath();\n                relativePath = relativePath.substring(baseSourcePath.length());\n\n                // Resets the digest before use\n                digest.reset();\n                String checksum;\n                try (InputStream is = setCurrentInputStream(file.getInputStream())) {\n                    checksum = AbstractFile.calculateChecksum(is, digest);\n                }\n\n                // Write a new line in the checksum file, in the appropriate format\n                String line;\n                if (useSfvFormat) {\n                    // SFV format for CRC32 checksum\n                    line = relativePath + \" \" + checksum;     // 1 space character\n                } else {\n                    // 'SUMS' format for other checksum algorithms\n                    line = checksum + \"  \" + relativePath;    // 2 space characters, that's how the format is\n                }\n\n                line += '\\n';\n\n                checksumFileOut.write(line.getBytes(StandardCharsets.UTF_8));\n\n                return true;\n            } catch (IOException e) {\n                // If the job was interrupted by the user at the time the exception occurred, it most likely means that\n                // the IOException was caused by the stream being closed as a result of the user interruption.\n                // If that is the case, the exception should not be interpreted as an error.\n                // Same goes if the current file was skipped.\n                if (getState() == State.INTERRUPTED || wasCurrentFileSkipped()) {\n                    return false;\n                }\n\n                LOGGER.debug(\"Caught IOException\", e);\n                \n                int ret = showErrorDialog(Translator.get(\"error\"), Translator.get(\"error_while_transferring\", file.getAbsolutePath()));\n                // Retry loops\n                if (ret == RETRY_ACTION) {\n                    // Reset processed bytes currentFileByteCounter\n                    getCurrentFileByteCounter().reset();\n\n                    continue;\n                }\n\n                // Cancel, skip or close dialog return false\n                return false;\n            }\n        } while(true);\n    }\n\n    @Override\n    protected boolean hasFolderChanged(AbstractFile folder) {\n        // This job modifies the folder where the checksum file is\n        return folder.equalsCanonical(checksumFile.getParent());     // Note: parent may be null\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    @Override\n    protected void jobStarted() {\n        super.jobStarted();\n\n        // Check for file collisions, i.e. if the file already exists in the destination\n        int collision = FileCollisionChecker.checkForCollision(null, checksumFile);\n        if (collision != FileCollisionChecker.NO_COLLISION) {\n            // File already exists in destination, ask the user what to do (cancel, overwrite,...) but\n            // do not offer the multiple files mode options such as 'skip' and 'apply to all'.\n            int choice = waitForUserResponse(new FileCollisionDialog(getProgressDialog(), getMainFrame().getJFrame(), collision, null, checksumFile, false, false));\n\n            // Overwrite file\n            if (choice == FileCollisionDialog.OVERWRITE_ACTION) {\n                // Do nothing, simply continue and file will be overwritten\n            }\n            // 'Cancel' or close dialog interrupts the job\n            else {\n                interrupt();\n                return;\n            }\n        }\n\n        // Loop for retry\n        do {\n            try {\n                // Tries to get an OutputStream on the destination file\n                this.checksumFileOut = checksumFile.getOutputStream();\n\n                break;\n\n            } catch (Exception e) {\n                int choice = showErrorDialog(Translator.get(\"error\"),\n                                             Translator.get(\"cannot_write_file\", checksumFile.getName()),\n                                             new String[] {CANCEL_TEXT, RETRY_TEXT},\n                                             new int[]  {CANCEL_ACTION, RETRY_ACTION}\n                                             );\n\n                // Retry loops\n                if (choice == RETRY_ACTION) {\n                    continue;\n                }\n\n                // 'Cancel' or close dialog interrupts the job\n                interrupt();\n                return;\n            }\n        } while(true);\n    }\n\n    @Override\n    protected void jobCompleted() {\n        super.jobCompleted();\n\n        // Open the checksum file in a viewer\n        ViewerRegistrar.createViewerFrame(getMainFrame(), checksumFile, IconManager.getImageIcon(checksumFile.getIcon()).getImage());\n    }\n\n    @Override\n    protected void jobStopped() {\n        super.jobStopped();\n        \n        // Close the checksum file's OutputStream\n        if (checksumFileOut != null) {\n            try {\n                checksumFileOut.close();\n            } catch (IOException ignore) {\n                // No need to inform the user\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/ChangeFileAttributesJob.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.job;\r\n\r\nimport java.io.IOException;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileOperation;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.dialog.file.ProgressDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\n/**\r\n * @author Maxence Bernard\r\n */\r\npublic class ChangeFileAttributesJob extends FileJob {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(ChangeFileAttributesJob.class);\r\n\t\r\n    private final boolean recurseOnDirectories;\r\n\r\n    private int permissions = -1;\r\n    private long date = -1;\r\n    private short replication = -1;\r\n\r\n\r\n    public ChangeFileAttributesJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet files, int permissions, boolean recurseOnDirectories) {\r\n        super(progressDialog, mainFrame, files);\r\n\r\n        this.permissions = permissions;\r\n        this.recurseOnDirectories = recurseOnDirectories;\r\n    }\r\n\r\n\r\n    public ChangeFileAttributesJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet files, long date, boolean recurseOnDirectories) {\r\n        super(progressDialog, mainFrame, files);\r\n\r\n        this.date = date;\r\n        this.recurseOnDirectories = recurseOnDirectories;\r\n    }\r\n\r\n    public ChangeFileAttributesJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet files, short replication, boolean recurseOnDirectories) {\r\n        super(progressDialog, mainFrame, files);\r\n\r\n        this.replication = replication;\r\n        this.recurseOnDirectories = recurseOnDirectories;\r\n    }\r\n\r\n\r\n    @Override\r\n    protected boolean processFile(AbstractFile file, Object recurseParams) {\r\n        // Stop if interrupted\r\n        if (getState() == State.INTERRUPTED)\r\n            return false;\r\n\r\n        if (recurseOnDirectories && file.isDirectory()) {\r\n            do {\t\t// Loop for retries\r\n                try {\r\n                    AbstractFile[] children = file.ls();\r\n                    int nbChildren = children.length;\r\n\r\n                    for (int i=0; i<nbChildren && getState() != State.INTERRUPTED; i++) {\r\n                        // Notify job that we're starting to process this file (needed for recursive calls to processFile)\r\n                        nextFile(children[i]);\r\n                        processFile(children[i], null);\r\n                    }\r\n\r\n                    break;\r\n                } catch (IOException e) {\r\n                    // Unable to open source file\r\n                    int ret = showErrorDialog(\"\", Translator.get(\"cannot_read_folder\", file.getName()));\r\n                    // Retry loops\r\n                    if (ret == RETRY_ACTION) {\r\n                        continue;\r\n                    }\r\n                    // Cancel, skip or close dialog return false\r\n                    return false;\r\n                }\r\n            } while(true);\r\n        }\r\n\r\n        if (permissions != -1) {\r\n            if (!file.isFileOperationSupported(FileOperation.CHANGE_PERMISSION)) {\r\n                return false;\r\n            }\r\n\r\n            try {\r\n                file.changePermissions(permissions);\r\n                return true;\r\n            } catch (IOException e) {\r\n                e.printStackTrace();\r\n                return false;\r\n            }\r\n        }\r\n\r\n//        if(date!=-1)\r\n            if (!file.isFileOperationSupported(FileOperation.CHANGE_DATE)) {\r\n            return false;\r\n            }\r\n\r\n        try {\r\n            file.setLastModifiedDate(date);\r\n            } catch (IOException e) {\r\n                LOGGER.debug(\"failed to change the date of \" + file, e);\r\n                return false;\r\n            }\r\n        \r\n\r\n        /*if (!file.canGetReplication()) {\r\n            return false;\r\n        }*/\r\n        if (!file.isFileOperationSupported(FileOperation.CHANGE_REPLICATION)) {\r\n            return false;\r\n        }\r\n\r\n        LOGGER.error(\"replication:\"+replication);\r\n        try {\r\n            file.changeReplication(replication);\r\n            return true;\r\n        } catch (IOException e) {\r\n            LOGGER.debug(\"failed to change the replication of \" + file, e);\r\n            return false;\r\n        }\r\n    }\r\n\r\n    // This job modifies the FileSet's base folder and potentially its subfolders\r\n    @Override\r\n    protected boolean hasFolderChanged(AbstractFile folder) {\r\n        AbstractFile baseFolder = getBaseSourceFolder();\r\n        if (baseFolder == null) {\r\n            baseFolder = files.get(0);\r\n        }\r\n        return baseFolder.isParentOf(folder);\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/CombineFilesJob.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.job;\r\n\r\nimport java.io.BufferedReader;\r\nimport java.io.IOException;\r\nimport java.io.InputStream;\r\nimport java.io.InputStreamReader;\r\nimport java.io.OutputStream;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.commons.io.StreamUtils;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.dialog.file.ProgressDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\n/**\r\n * This job combines files into one file, optionally checking the CRC of the merged file.\r\n * @author Mariusz Jakubowski\r\n */\r\npublic class CombineFilesJob extends AbstractCopyJob {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(CombineFilesJob.class);\r\n\t\r\n\tAbstractFile destFile = null;\r\n\tprivate OutputStream out;\r\n\tprivate AbstractFile crcFile;\r\n\r\n\r\n\tpublic CombineFilesJob(ProgressDialog progressDialog, MainFrame mainFrame,\r\n\t\t\tFileSet files, AbstractFile destFile,\r\n\t\t\tint fileExistsAction) {\r\n\t\tsuper(progressDialog, mainFrame, files, destFile, null, fileExistsAction);\r\n        this.errorDialogTitle = Translator.get(\"combine_files_dialog.error_title\");\r\n\t}\r\n\r\n    @Override\r\n    protected boolean hasFolderChanged(AbstractFile folder) {\r\n        return baseDestFolder.isParentOf(folder);\r\n\t}\r\n\r\n\t@Override\r\n    protected boolean processFile(AbstractFile file, Object recurseParams) {\r\n        if (destFile == null) {\r\n        \t// executed only on first part\r\n        \tcreateDestFile(file);\r\n            findCRCFile(file);\r\n        }\r\n        \r\n        if (getState() == State.INTERRUPTED)\r\n            return false;\r\n        \r\n        try {\r\n\t\t\tInputStream in = file.getInputStream();\r\n\t\t\tsetCurrentInputStream(in);\r\n\t\t\tStreamUtils.copyStream(in, out);\r\n\t\t} catch (IOException e) {\r\n            LOGGER.debug(\"Caught exception\", e);\r\n            showErrorDialog(errorDialogTitle,\r\n                    Translator.get(\"error_while_transferring\", destFile.getName()),\r\n                    new String[]{CANCEL_TEXT},\r\n                    new int[]{CANCEL_ACTION}\r\n                    );\r\n            interrupt();\r\n\t\t\treturn false;\r\n\t\t} finally {\r\n\t\t\tcloseCurrentInputStream();\r\n\t\t}\r\n        \r\n\t\treturn true;\r\n\t}\r\n\t\r\n\t/**\r\n\t * Creates the destination (merged) file.\r\n\t * @param file first part\r\n\t */\r\n\tprotected void createDestFile(AbstractFile file) {\r\n\t\tdestFile = baseDestFolder;\r\n\t\tbaseDestFolder = baseDestFolder.getParent();\r\n        destFile = checkForCollision(file, baseDestFolder, destFile, false);\r\n        if (destFile == null) {\r\n            interrupt();\r\n        \treturn;\r\n        }\r\n        \r\n        try {\r\n    \t\tout = destFile.getOutputStream();\r\n        } catch(IOException e) {\r\n        \tLOGGER.debug(\"Caught exception\", e);\r\n            showErrorDialog(errorDialogTitle,\r\n                    Translator.get(\"error_while_transferring\", destFile.getName()),\r\n                    new String[]{CANCEL_TEXT},\r\n                    new int[]{CANCEL_ACTION}\r\n                    );\r\n            interrupt();\r\n        }\r\n\t}\r\n\t\r\n\t/**\r\n\t * Checks if CRC file exists.\r\n\t * @param file firts part\r\n\t */\r\n\tprivate void findCRCFile(AbstractFile file) {\r\n\t\tAbstractFile f = file.getParent();\r\n\t\tif (f != null) {\r\n\t\t\ttry {\r\n\t\t\t\tcrcFile = f.getDirectChild(file.getNameWithoutExtension() + \".sfv\");\r\n\t\t\t} catch (IOException e) {\r\n\t\t\t\tLOGGER.debug(\"Caught exception\", e);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t@Override\r\n    protected void jobStopped() {\r\n\t\tsuper.jobStopped();\r\n\t\tcloseOutputStream();\r\n\t}\r\n\t\r\n\t@Override\r\n    protected void jobCompleted() {\r\n\t\tsuper.jobCompleted();\r\n\t\tcloseOutputStream();\r\n\t\tcheckCRC();\r\n\t}\r\n\r\n\t/**\r\n\t * Checks CRC of merged file (if CRC file exists).\r\n\t */\r\n\tprivate void checkCRC() {\r\n\t\tif (crcFile==null  || !crcFile.exists()) {\r\n            showErrorDialog(errorDialogTitle,\r\n                    Translator.get(\"combine_files_job.no_crc_file\"),\r\n                    new String[]{OK_TEXT},\r\n                    new int[]{OK_ACTION}\r\n                    );\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tInputStream crcIn = null;\r\n\t\ttry {\r\n\t\t\tcrcIn = crcFile.getInputStream();\r\n\t\t\tBufferedReader crcReader = new BufferedReader(new InputStreamReader(crcIn));\r\n\t\t\tString crcLine = crcReader.readLine();\r\n\t\t\tcrcLine = crcLine.substring(crcLine.lastIndexOf(' ')+1).trim();\r\n\t\t\tString crcDest = destFile.calculateChecksum(\"CRC32\");\r\n\t\t\tif (!crcLine.equals(crcDest)) {\r\n\t            showErrorDialog(errorDialogTitle,\r\n\t                    Translator.get(\"combine_files_job.crc_check_failed\", crcDest, crcLine),\r\n\t                    new String[]{OK_TEXT},\r\n\t                    new int[]{OK_ACTION}\r\n\t                    );\r\n\t\t\t} else {\r\n\t            showErrorDialog(Translator.get(\"combine_files_dialog.error_title\"),\r\n\t                    Translator.get(\"combine_files_job.crc_ok\"),\r\n\t                    new String[]{OK_TEXT},\r\n\t                    new int[]{OK_ACTION}\r\n\t                    );\r\n\t\t\t}\r\n\t\t} catch (Exception e) {\r\n            LOGGER.debug(\"Caught exception\", e);\r\n            showErrorDialog(errorDialogTitle,\r\n                    Translator.get(\"combine_files_job.crc_read_error\"),\r\n                    new String[]{CANCEL_TEXT},\r\n                    new int[]{CANCEL_ACTION}\r\n                    );\r\n\t\t} finally {\r\n\t\t\tif (crcIn!=null) {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tcrcIn.close();\r\n\t\t\t\t} catch (IOException e) {\r\n                    LOGGER.debug(\"Caught exception\", e);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Closes the output stream.\r\n\t */\r\n\tprivate void closeOutputStream() {\r\n\t\tif (out != null) {\r\n\t\t\ttry {\r\n\t\t\t\tout.close();\r\n\t\t\t}\r\n            catch (IOException e) {\r\n                LOGGER.debug(\"Caught exception\", e);\r\n\t            showErrorDialog(errorDialogTitle,\r\n\t                    Translator.get(\"error_while_transferring\", destFile.getName()),\r\n\t                    new String[]{CANCEL_TEXT},\r\n\t                    new int[]{CANCEL_ACTION}\r\n\t                    );\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/CopyJob.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.job;\n\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.file.impl.adb.AdbFile;\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.job.utils.ScanDirectoryThread;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.file.ProgressDialog;\nimport com.mucommander.ui.main.MainFrame;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\n\n\n/**\n * This job recursively copies a set of files. Directories are copied recursively.\n *\n * @author Maxence Bernard\n */\npublic class CopyJob extends AbstractCopyJob {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(CopyJob.class);\n\n    /** Destination file that is being copied, this value is updated every time #processFile() is called.\n     * The value can be used by subclasses that override processFile should they need to work on the destination file. */\n    AbstractFile currentDestFile;\n\n    private final ScanDirectoryThread scanDirectoryThread;\n\n    /** Processed files counter */\n    private long processedFilesCount;\n\n\n\n    /** Operating mode : COPY or DOWNLOAD */\n    public enum Mode {\n        COPY,\n        DOWNLOAD\n    }\n    private final Mode mode;\n\n\n\t\n    /**\n     * Creates a new CopyJob without starting it.\n     *\n     * @param progressDialog dialog which shows this job's progress\n     * @param mainFrame mainFrame this job has been triggered by\n     * @param files files which are going to be copied\n     * @param destFolder destination folder where the files will be copied\n     * @param newName the new filename in the destination folder, can be <code>null</code> in which case the original filename will be used.\n     * @param mode mode in which CopyJob is to operate: {@link com.mucommander.job.CopyJob.Mode#COPY} or {@link com.mucommander.job.CopyJob.Mode#DOWNLOAD}.\n     * @param fileExistsAction default action to be performed when a file already exists in the destination, see {@link com.mucommander.ui.dialog.file.FileCollisionDialog} for allowed values\n     */\n    public CopyJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet files, AbstractFile destFolder, String newName, Mode mode, int fileExistsAction) {\n        super(progressDialog, mainFrame, files, destFolder, newName, fileExistsAction);\n\n        this.mode = mode;\n        this.errorDialogTitle = Translator.get(mode==Mode.DOWNLOAD ? \"download_dialog.error_title\" : \"copy_dialog.error_title\");\n        scanDirectoryThread = new ScanDirectoryThread(files);\n        scanDirectoryThread.start();\n    }\n\n\n\n    /**\n     * Copies recursively the given file or folder. \n     *\n     * @param file the file or folder to move\n     * @param recurseParams destination folder where the given file will be copied (null for top level files)\n     * \n     * @return <code>true</code> if the file has been copied.\n     */\n    @Override\n    protected boolean processFile(AbstractFile file, Object recurseParams) {\n        // Stop if interrupted\n        if (getState() == State.INTERRUPTED) {\n            return false;\n        }\n        processedFilesCount++;\n\n//try { // delay for debug purposes\n//    Thread.sleep(1000);\n//} catch (InterruptedException e) {\n//    e.printStackTrace();\n//}\n\n        // Destination folder\n        AbstractFile destFolder = recurseParams == null ? baseDestFolder : (AbstractFile)recurseParams;\n\t\t\n        // Is current file in base folder ?\n        boolean isFileInBaseFolder = files.contains(file);\n\n        // Determine filename in destination\n        String destFileName = (isFileInBaseFolder && newName != null) ? newName : file.getName();\n//System.out.println(\"destFileName \" + destFileName);\n        // create destination AbstractFile instance\n        AbstractFile destFile = createDestinationFile(destFolder, destFileName);\n        if (destFile == null) {\n            return false;\n        }\n        currentDestFile = destFile;\n\n        AbstractFile sourceFile = file.getAncestor();\n\n        // Do nothing if file is a symlink (skip file and return)\n        if (file.isSymlink() && file instanceof LocalFile) {\n            tryCopySymlinkFile(file, destFolder);\n            return true;\n        }\n\n        // ADB files\n        if (sourceFile instanceof AdbFile && destFile instanceof LocalFile && !sourceFile.isDirectory()) {\n            return copyAdbFile(destFile, (AdbFile) sourceFile);\n        }\n\n        destFile = checkForCollision(file, destFolder, destFile, false);\n        if (destFile == null) {\n            return false;\n        }\n\n        if (!file.isDirectory()) {\n            return tryCopyFile(file, destFile, append, errorDialogTitle);\n        }\n        return copyDirectoryRecursively(file, destFileName, destFile);\n    }\n\n    private boolean copyDirectoryRecursively(AbstractFile file, String destFileName, AbstractFile destFile) {\n        return createSubFolder(destFileName, destFile) && copyChildrenRecursively(file, destFile);\n    }\n\n    private boolean createSubFolder(String destFileName, AbstractFile destFile) {\n        // create the folder in the destination folder if it doesn't exist\n        if (!(destFile.exists() && destFile.isDirectory())) {\n            // Loop for retry\n            do {\n                try {\n                    destFile.mkdir();\n                } catch (IOException e) {\n                    // Unable to create folder\n                    int ret = showErrorDialog(errorDialogTitle, Translator.get(\"cannot_create_folder\", destFileName));\n                    // Retry loops\n                    if (ret == RETRY_ACTION) {\n                        continue;\n                    }\n                    // Cancel or close dialog return false\n                    return false;\n                    // Skip continues\n                }\n                break;\n            } while(true);\n        }\n        return true;\n    }\n\n    private boolean copyChildrenRecursively(AbstractFile file, AbstractFile destFile) {\n        // and copy each file in this folder recursively\n        do {\t\t// Loop for retry\n            try {\n                // for each file in folder...\n                processChildernFiles(file, destFile);\n\n                // Set currentDestFile back to the enclosing folder in case an overridden processFile method\n                // needs to work with the folder after calling super.processFile.\n                currentDestFile = destFile;\n\n                // Only when finished with folder, set destination folder's date to match the original folder one\n                changeFolderModifiedDate(file, destFile);\n\n                return true;\n            } catch (IOException e) {\n                // file.ls() failed\n                int ret = showErrorDialog(errorDialogTitle, Translator.get(\"cannot_read_folder\", file.getName()));\n                // Retry loops\n                if (ret == RETRY_ACTION) {\n                    continue;\n                }\n                // Cancel, skip or close dialog returns false\n                return false;\n            }\n        } while(true);\n    }\n\n\n    private void changeFolderModifiedDate(AbstractFile file, AbstractFile destFile) {\n        if (destFile.isFileOperationSupported(FileOperation.CHANGE_DATE)) {\n            try {\n                destFile.setLastModifiedDate(file.getLastModifiedDate());\n            } catch (IOException e) {\n                LOGGER.debug(\"failed to change the date of \"+ destFile, e);\n            }\n        }\n    }\n\n    private void processChildernFiles(AbstractFile file, AbstractFile destFile) throws IOException {\n        AbstractFile[] subFiles = file.ls();\n        for (int i = 0; i < subFiles.length && getState() != State.INTERRUPTED; i++) {\n            // Notify job that we're starting to process this file (needed for recursive calls to processFile)\n            nextFile(subFiles[i]);\n            processFile(subFiles[i], destFile);\n        }\n    }\n\n    private boolean copyAdbFile(AbstractFile destFile, AdbFile sourceFile) {\n        try {\n            sourceFile.pushTo(destFile);\n        } catch (IOException e) {\n            e.printStackTrace();\n            return false;\n        }\n        return true;\n    }\n\n\n    // This job modifies baseDestFolder and its subfolders\n    @Override\n    protected boolean hasFolderChanged(AbstractFile folder) {\n        return baseDestFolder.isParentOf(folder);\n    }\n\n\n\n    @Override\n    protected void jobCompleted() {\n        super.jobCompleted();\n\n        // If the destination files are located inside an archive, optimize the archive file\n        AbstractArchiveFile archiveFile = baseDestFolder.getParentArchive();\n        if (archiveFile != null && archiveFile.isArchive() && archiveFile.isWritable()) {\n            optimizeArchive((AbstractRWArchiveFile)archiveFile);\n        }\n\n        // If this job corresponds to a 'local copy' of a single file and in the same directory,\n        // select the copied file in the active table after this job has finished (and hasn't been cancelled)\n        if (files.size() == 1 && newName != null && baseDestFolder.equalsCanonical(files.elementAt(0).getParent())) {\n            // Resolve new file instance now that it exists: some remote files do not immediately update file attributes\n            // after creation, we need to get an instance that reflects the newly created file attributes\n            selectFileWhenFinished(FileFactory.getFile(baseDestFolder.getAbsolutePath(true)+newName));\n        }\n    }\n\n    @Override\n    public String getStatusString() {\n        if (isCheckingIntegrity()) {\n            return super.getStatusString();\n        }\n        if (isOptimizingArchive) {\n            return Translator.get(\"optimizing_archive\", archiveToOptimize.getName());\n        }\n        return Translator.get(mode == Mode.DOWNLOAD ? \"download_dialog.downloading_file\" : \"copy_dialog.copying_file\", getCurrentFilename());\n    }\n\n    @Override\n    public void interrupt() {\n        if (scanDirectoryThread != null) {\n            scanDirectoryThread.interrupt();\n        }\n        super.interrupt();\n    }\n\n    @Override\n    public float getTotalPercentDone() {\n        if (scanDirectoryThread == null || !scanDirectoryThread.isCompleted()) {\n            float result = super.getTotalPercentDone();\n            return result > 5 ? 5 : result;\n        }\n        float progressBySize = 1.0f*(getTotalByteCounter().getByteCount() + getTotalSkippedByteCounter().getByteCount()) / scanDirectoryThread.getTotalBytes();\n        float progressByCount = 1.0f*(processedFilesCount-1) / scanDirectoryThread.getFilesCount();\n        float result = (progressBySize * 8 + progressByCount * 2) / 10;\n        if (result < 0) {\n            return 0;\n        } else if (result > 1) {\n            return 1;\n        }\n        return result;\n    }\n\n\n//    private void copySymLink(AbstractFile file, AbstractFile destFile) {\n//        String targetPath = SymLinkUtils.getTargetPath(file);\n//        SymLinkUtils.createSymlink(destFile, targetPath);\n//    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/DeleteJob.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.job;\n\nimport java.io.IOException;\n\nimport com.mucommander.job.utils.ScanDirectoryThread;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.AbstractRWArchiveFile;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.desktop.AbstractTrash;\nimport com.mucommander.desktop.DesktopManager;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.file.ProgressDialog;\nimport com.mucommander.ui.main.MainFrame;\n\n/**\n * This class is responsible for deleting a set of files. This job can operate in two modes, depending on the boolean\n * value specified in the constructor:\n * <ul>\n *  <li>moveToTrash enabled: files are moved to the trash returned by {@link DesktopManager#getTrash()}.\n *  <li>moveToTrash disabled: files are permanently deleted, i.e. deleted files cannot be recovered. In this mode,\n * folders are deleted recursively\n * </ul>\n *\n * @author Maxence Bernard\n */\npublic class DeleteJob extends FileJob {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(DeleteJob.class);\n\t\n    /** Title used for error dialogs */\n    private final String errorDialogTitle;\n\n    /** If true, files will be moved to the trash instead of being deleted */\n    private final boolean moveToTrash;\n\n    /** Trash instance, null if moveToTrash is false */\n    private AbstractTrash trash;\n\n    /** The archive that contains the deleted files (maybe null) */\n    private AbstractRWArchiveFile archiveToOptimize;\n\n    /** True when an archive is being optimized */\n    private boolean isOptimizingArchive;\n\n    protected ScanDirectoryThread scanDirectoryThread;\n\n    /** Processed files counter */\n    protected long processedFilesCount;\n\n\n    /**\n     * Creates a new DeleteJob without starting it.\n     *\n     * @param progressDialog dialog which shows this job's progress\n     * @param mainFrame mainFrame this job has been triggered by\n     * @param files files which are going to be deleted\n     * @param moveToTrash if true, files will be moved to the trash, if false they will be permanently deleted.\n     * Should be true only if a trash is available on the current platform.\n     */\n    public DeleteJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet files, boolean moveToTrash) {\n        super(progressDialog, mainFrame, files);\n\n        this.errorDialogTitle = Translator.get(\"delete_dialog.error_title\");\n\n        this.moveToTrash = moveToTrash;\n        if (moveToTrash) {\n            trash = DesktopManager.getTrash();\n        }\n\n        scanDirectoryThread = new ScanDirectoryThread(files, false);\n        scanDirectoryThread.start();\n    }\n\n    /**\n     * Deletes the given file, either by moving it to the trash (if {@link #moveToTrash} is true) or by deleting the\n     * file directly.\n     *\n     * @param file the file to delete\n     * @throws IOException if an error occurred while deleting the file\n     */\n    private void deleteFile(AbstractFile file) throws IOException {\n        if (moveToTrash) {\n            trash.moveToTrash(file);\n        } else {\n            file.delete();\n        }\n    }\n\n\n    ////////////////////////////\n    // FileJob implementation //\n    ////////////////////////////\n\n    /**\n     * Deletes recursively the given file or folder. \n     *\n     * @param file the file or folder to delete\n     * @param recurseParams not used\n     * \n     * @return <code>true</code> if the file has been completely deleted.\n     */\n    @Override\n    protected boolean processFile(AbstractFile file, Object recurseParams) {\n        if (getState() == State.INTERRUPTED) {\n            return false;\n        }\n        processedFilesCount++;\n\n        // Delete files recursively, only if trash is not used.\n        int ret;\n        if(!moveToTrash && file.isDirectory()) {\n            String filePath = file.getAbsolutePath();\n            filePath = filePath.substring(getBaseSourceFolder().getAbsolutePath(false).length()+1);\n\n            // Important: symlinks must *not* be followed -- following symlinks could have disastrous effects.\n            if(!file.isSymlink()) {\n                do {\t\t// Loop for retry\n                    // Delete each file in this folder\n                    try {\n                        AbstractFile[] subFiles = file.ls();\n                        for(int i=0; i<subFiles.length && getState() != State.INTERRUPTED; i++) {\n                            // Notify job that we're starting to process this file (needed for recursive calls to processFile)\n                            nextFile(subFiles[i]);\n                            processFile(subFiles[i], null);\n                        }\n                        break;\n                    }\n                    catch(IOException e) {\n                        LOGGER.debug(\"IOException caught\", e);\n\n                        ret = showErrorDialog(errorDialogTitle, Translator.get(\"cannot_read_file\", filePath));\n                        // Retry loops\n                        if(ret==RETRY_ACTION)\n                            continue;\n                        // Cancel, skip or close dialog returns false\n                        return false;\n                    }\n                } while(true);\n            }\n        }\n        // Return now if the job was interrupted, so that we do not attempt to delete this folder\n        if (getState() == State.INTERRUPTED)\n            return false;\n\n        do {\t\t// Loop for retry\n            try {\n                deleteFile(file);\n\n                return true;\n            } catch(IOException e) {\n                LOGGER.debug(\"IOException caught\", e);\n\n                ret = showErrorDialog(errorDialogTitle,\n                                      Translator.get(file.isDirectory()?\"cannot_delete_folder\":\"cannot_delete_file\", file.getName())\n                                      );\n                // Retry loops\n                if (ret == RETRY_ACTION) {\n                    continue;\n                }\n                // Cancel, skip or close dialog returns false\n                return false;\n            }\n        } while(true);\n    }\n\n    // This job modifies baseFolder and subfolders\n    @Override\n    protected boolean hasFolderChanged(AbstractFile folder) {\n        return getBaseSourceFolder().isParentOf(folder);\n    }\n\n    @Override\n    protected void jobStopped() {\n        super.jobStopped();\n\n        if (moveToTrash) {\n            trash.waitForPendingOperations();\n        }\n    }\n\n    @Override\n    protected void jobCompleted() {\n        super.jobCompleted();\n\n        // If the source files are located inside an archive, optimize the archive file\n        AbstractArchiveFile archiveFile = getBaseSourceFolder().getParentArchive();\n\n        if(archiveFile!=null && archiveFile.isArchive() && archiveFile.isWritable()) {\n            while(true) {\n                try {\n                    archiveToOptimize = ((AbstractRWArchiveFile)archiveFile);\n                    isOptimizingArchive = true;\n\n                    archiveToOptimize.optimizeArchive();\n\n                    break;\n                } catch(IOException e) {\n                    if (showErrorDialog(errorDialogTitle, Translator.get(\"error_while_optimizing_archive\", archiveFile.getName()))==RETRY_ACTION) {\n                        continue;\n                    }\n\n                    break;\n                }\n            }\n\n            isOptimizingArchive = true;\n        }\n    }\n\n    @Override\n    public String getStatusString() {\n        if (isOptimizingArchive) {\n            return Translator.get(\"optimizing_archive\", archiveToOptimize.getName());\n        }\n\n        return Translator.get(\"delete.deleting_file\", getCurrentFilename());\n    }\n\n    @Override\n    public void interrupt() {\n        super.interrupt();\n        if (scanDirectoryThread != null) {\n            scanDirectoryThread.interrupt();\n        }\n    }\n\n    @Override\n    public float getTotalPercentDone() {\n        if (scanDirectoryThread == null || !scanDirectoryThread.isCompleted()) {\n            float result = super.getTotalPercentDone();\n            return result > 15 ? 15 : result;\n        }\n        float result = 1.0f*(processedFilesCount-1) / scanDirectoryThread.getFilesCount();\n        if (result < 0) {\n            result = 0;\n        } else if (result > 1) {\n            result = 1;\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/FileCollisionChecker.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.job;\n\nimport com.mucommander.commons.file.AbstractFile;\n\n/**\n * The purpose of this class is to check for collisions between a source and destination file used in a file transfer.\n *\n * <p>Currently, 3 collision types are detected:\n * <ul>\n * <li>{@link #DESTINATION_FILE_ALREADY_EXISTS}: the destination file already exists\n * <li>{@link #SAME_SOURCE_AND_DESTINATION}: source and destination files are the same, according to {@link AbstractFile#equalsCanonical(Object)}\n * <li>{@link #SOURCE_PARENT_OF_DESTINATION}: source is a folder (as returned by {@link com.mucommander.commons.file.AbstractFile#isBrowsable()}\n * and a parent of destination.\n * </ul>\n *\n * <p>The value returned by {@link #checkForCollision(com.mucommander.commons.file.AbstractFile, com.mucommander.commons.file.AbstractFile)}\n * can be used to create a {@link com.mucommander.ui.dialog.file.FileCollisionDialog} in order to inform the user of the collision\n * and ask him how to resolve it.\n *\n * @see com.mucommander.ui.dialog.file.FileCollisionDialog\n * @author Maxence Bernard\n */\npublic class FileCollisionChecker {\n\n    /** No collision detected */\n    public static final int NO_COLLISION = 0;\n\n    /** The destination file already exists and is not a directory */\n    public static final int DESTINATION_FILE_ALREADY_EXISTS = 1;\n\n    /** Source and destination files are the same */\n    public static final int SAME_SOURCE_AND_DESTINATION = 2;\n\n    /** Source and destination are both folders and destination is a subfolder of source */\n    public static final int SOURCE_PARENT_OF_DESTINATION = 3;\n\n    /**\n     *\n     * @param sourceFile source file, can be null in which case the only collision checked against is {@link #DESTINATION_FILE_ALREADY_EXISTS}.\n     * @param destFile destination file, cannot be null\n     * @return an int describing the collision type, or {@link #NO_COLLISION} if no collision was detected (see constants)\n     */\n    public static int checkForCollision(AbstractFile sourceFile, AbstractFile destFile) {\n\n        if(sourceFile!=null) {\n            // Source and destination are equal\n            if(destFile.equalsCanonical(sourceFile))\n                return SAME_SOURCE_AND_DESTINATION;\n\n            // Both source and destination are folders and destination is a subfolder of source\n            if(sourceFile.isParentOf(destFile))\n                return SOURCE_PARENT_OF_DESTINATION;\n        }\n\n        // File exists in destination\n        if(destFile.exists() && !destFile.isDirectory())\n            return DESTINATION_FILE_ALREADY_EXISTS;\n\n        return NO_COLLISION;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/FileJob.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\n\r\npackage com.mucommander.job;\r\n\r\nimport java.util.WeakHashMap;\r\n\r\nimport com.mucommander.ui.dialog.PasswordDialog;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.impl.CachedFile;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.job.progress.JobProgress;\r\nimport com.mucommander.job.ui.DialogResult;\r\nimport com.mucommander.job.ui.UserInputHelper;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.dialog.QuestionDialog;\r\nimport com.mucommander.ui.dialog.file.ProgressDialog;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.FileTable;\r\nimport com.mucommander.ui.notifier.AbstractNotifier;\r\nimport com.mucommander.ui.notifier.NotificationType;\r\n\r\n\r\n/**\r\n * FileJob is a container for a 'file getTask' : basically an operation that involves files and bytes.\r\n * The class extending FileJob is required to give some information about the status of the job that\r\n * will be used to display visual indications of the job's progress.\r\n * <p>\r\n * The actual processing is performed in a separate thread. A FileJob needs to be started explicitly using\r\n * {@link #start()}. The lifecycle of a FileJob is as follows:<br>\r\n * <br>\r\n * <pre>\r\n * {@link State#NOT_STARTED} -&gt; {@link State#RUNNING} -&gt; {@link State#FINISHED}\r\n *                         ^                |\r\n *                         |                -&gt; {@link State#INTERRUPTED}\r\n *                         |                |                      \r\n *                         |                -&gt; {@link State#PAUSED} -|\r\n *                         |                                    |\r\n *                         -------------------------------------|\r\n * </pre>\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic abstract class FileJob implements Runnable {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(FileJob.class);\r\n\r\n    /** Thread in which the file job is performed */\r\n    private Thread jobThread;\r\n\r\n    /** Lock used when job is being paused */\r\n    private final Object pauseLock = new Object();\r\n\r\n    /** Timestamp in milliseconds when job started */\r\n    private long startDate;\r\n\r\n    /** Timestamp in milliseconds when job has finished */\r\n    private long endDate;\r\n\r\n    /** Number of milliseconds during which this job has been paused (been waiting for some user response).\r\n     * Used to compute stats like average speed. */\r\n    private long pausedTime;\r\n\r\n    /** Contains the timestamp when this job has been put in pause (if in pause) */\r\n    private long pauseStartDate;\r\n\r\n    /** Associated dialog showing job progression */\r\n    private ProgressDialog progressDialog;\r\n\r\n    /** Main frame on which the job is to be performed */ \r\n    private final MainFrame mainFrame;\r\n\t\r\n    /** Base source folder */\r\n    private AbstractFile baseSourceFolder;\r\n\t\r\n    /** Files which are going to be processed */\r\n    protected FileSet files;\r\n\r\n    /** Number of files that this job contains */\r\n    private int nbFiles;\r\n\r\n    /** Index of file currently being processed, see {@link #getCurrentFileIndex()} */\r\n    private int currentFileIndex = -1;\r\n\r\n    /** File currently being processed */\r\n    private AbstractFile currentFile;\r\n\r\n    /** Name of the file currently being processed */\r\n    private String currentFilename = \"\";\r\n\r\n    /** If set to true, processed files will be unmarked from current table */\r\n    private boolean autoUnmark = true;\r\n\t\r\n    /** File to be selected after job has finished (can be null if not set) */\r\n    private AbstractFile fileToSelect;\r\n\r\n    public enum State {\r\n    \r\n        /**\r\n         * Indicates that this job has not started yet, this is a temporary state\r\n         */\r\n        NOT_STARTED,\r\n\r\n        /**\r\n         * Indicates that this job is currently processing files, this is a temporary state\r\n         */\r\n        RUNNING,\r\n\r\n        /**\r\n         * Indicates that this job is currently paused, waiting for user response, this is a temporary state\r\n         */\r\n        PAUSED,\r\n\r\n        /**\r\n         * Indicates that this job has been interrupted by the end user, this is a permanent state\r\n         */\r\n        INTERRUPTED,\r\n\r\n        /** Indicates that this job has naturally finished (i.e. without being interrupted), this is a permanent state */\r\n        FINISHED\r\n    }\r\n\r\n\r\n    /** Current state of this job */\r\n    private State jobState = State.NOT_STARTED;\r\n\r\n    /** List of registered FileJobListener stored as weak references */\r\n    private final WeakHashMap<FileJobListener, ?> listeners = new WeakHashMap<>();\r\n    \r\n    /** Information about this job progress */\r\n    private final JobProgress jobProgress;\r\n\r\n    /** True if the user asked to automatically skip errors */\r\n    private boolean autoSkipErrors;\r\n\r\n//    private int nbFilesProcessed;\r\n//    private int nbFilesDiscovered;\r\n\r\n    protected final static int SKIP_ACTION = 0;\r\n    protected final static int SKIP_ALL_ACTION = 1;\r\n    protected final static int RETRY_ACTION = 2;\r\n    protected final static int CANCEL_ACTION = 3;\r\n    protected final static int APPEND_ACTION = 4;\r\n    protected final static int OK_ACTION = 5;\r\n    protected final static int OVERWRITE_READONLY_ACTION = 6;\r\n    protected final static int OVERWRITE_READONLY_ALL_ACTION = 7;\r\n    protected final static int RETRY_AS_ROOT_ACTION = 8;\r\n    protected final static int RETRY_AS_ROOT_ALWAYS_ACTION = 9;\r\n\r\n    protected final static String SKIP_TEXT = Translator.get(\"skip\");\r\n    protected final static String SKIP_ALL_TEXT = Translator.get(\"skip_all\");\r\n    protected final static String RETRY_TEXT = Translator.get(\"retry\");\r\n    protected final static String RETRY_AS_ROOT_TEXT = Translator.get(\"retry_as_root\");\r\n    protected final static String RETRY_AS_ROOT_ALWAYS_TEXT = Translator.get(\"retry_as_root_always\");\r\n    protected final static String CANCEL_TEXT = Translator.get(\"cancel\");\r\n    protected final static String APPEND_TEXT = Translator.get(\"resume\");\r\n    protected final static String OK_TEXT = Translator.get(\"ok\");\r\n    protected final static String OVERWRITE_READONLY_TEXT = Translator.get(\"overwrite\");\r\n    protected final static String OVERWRITE_READONLY_ALL_TEXT = Translator.get(\"overwrite_all\");\r\n\r\n\r\n    /**\r\n     * Creates a new FileJob without starting it.\r\n     *\r\n     * @param progressDialog dialog which shows this job's progress\r\n     * @param mainFrame mainFrame this job has been triggered by\r\n     * @param files files which are going to be processed\r\n     */\r\n    public FileJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet files) {\r\n        this(mainFrame, files);\r\n        this.progressDialog = progressDialog;\r\n    }\r\n\r\n\t\r\n    /**\r\n     * Creates a new FileJob without starting it, and with no associated ProgressDialog.\r\n     *\r\n     * @param mainFrame mainFrame this job has been triggered by\r\n     * @param files files which are going to be processed\r\n     */\r\n    public FileJob(MainFrame mainFrame, FileSet files) {\r\n        this.mainFrame = mainFrame;\r\n        setFiles(files);\r\n    \tthis.jobProgress = new JobProgress(this);\r\n    }\r\n\r\n    public FileJob(MainFrame mainFrame) {\r\n        this.mainFrame = mainFrame;\r\n        this.jobProgress = new JobProgress(this);\r\n    }\r\n\t\r\n\t\r\n    /**\r\n     * Specifies whether files that have been processed should be unmarked from current table (enabled by default).\r\n     *\r\n     * @param autoUnmark <code>true</code> to automatically unmark files after they have been processed.\r\n     */\r\n    public void setAutoUnmark(boolean autoUnmark) {\r\n        this.autoUnmark = autoUnmark;\r\n    }\r\n\r\n    /**\r\n     * Sets whether this file job should automatically skip errors when encountered (disabled by default).\r\n     *\r\n     * @param autoSkipErrors <code>true</code> to automatically skip errors, <code>false</code> to show an error dialog.\r\n     */\r\n    public void setAutoSkipErrors(boolean autoSkipErrors) {\r\n        this.autoSkipErrors = autoSkipErrors;\r\n    }\r\n\r\n\t\r\n    /**\r\n     * Sets the given file to be selected in the active table after this job has finished.\r\n     * The file will only be selected if it exists in the active table's folder and if this job hasn't\r\n     * been canceled. The selection will occur after the tables have been refreshed (if they are refreshed).\r\n     *\r\n     * @param file the file to be selected in the active table after this job has finished\r\n     */\r\n    protected void selectFileWhenFinished(AbstractFile file) {\r\n        this.fileToSelect = file;\r\n    }\r\n\t\r\n\t\r\n    /**\r\n     * Starts file job in a separate thread.\r\n     */\r\n    public void start() {\r\n        // Return if job has already been started\r\n        if (getState() != State.NOT_STARTED) {\r\n            return;\r\n        }\r\n\r\n        // Pause auto-refresh during file job as it potentially modifies the current folders contents\r\n        // and would potentially cause folder panel to auto-refresh\r\n        getMainFrame().getLeftPanel().getFolderChangeMonitor().setPaused(true);\r\n        getMainFrame().getRightPanel().getFolderChangeMonitor().setPaused(true);\r\n\r\n        setState(State.RUNNING);\r\n        startDate = System.currentTimeMillis();\r\n\r\n        jobThread = new Thread(this, getClass().getName());\r\n        jobThread.start();\r\n    }\r\n\r\n\r\n\t/**\r\n\t * Returns the dialog showing progress of this job.\r\n\t * @return the progressDialog\r\n\t */\r\n\tprotected ProgressDialog getProgressDialog() {\r\n\t\treturn progressDialog;\r\n\t}\r\n\r\n\r\n\t/**\r\n\t * Returns the main frame.\r\n\t * @return the mainFrame\r\n\t */\r\n\tprotected MainFrame getMainFrame() {\r\n\t\treturn mainFrame;\r\n\t}\r\n\r\n\r\n\t/**\r\n     * Returns the current state of this FileJob. See constant fields for possible return values.\r\n     *\r\n     * @return the current state of this FileJob. See constant fields for possible return values.\r\n     */\r\n    public State getState() {\r\n        return jobState;\r\n    }\r\n\r\n    /**\r\n     * Sets a new state for this FileJob and notifies registered FileJobListener instances of the change.\r\n     *\r\n     * @param jobState the new state\r\n     */\r\n    protected void setState(State jobState) {\r\n        State oldState = this.jobState;\r\n        this.jobState = jobState;\r\n\r\n        for (FileJobListener listener : listeners.keySet())\r\n            listener.jobStateChanged(this, oldState, jobState);\r\n    }\r\n\r\n\r\n    /**\r\n     * Registers a FileJobListener to receive notifications whenever state of this FileJob changes.\r\n     *\r\n     * <p>Listeners are stored as weak references so {@link #removeFileJobListener(FileJobListener)}\r\n     * doesn't need to be called for listeners to be garbage collected when they're not used anymore.\r\n     *\r\n     * @param listener the FileJobListener to register\r\n     */\r\n    public void addFileJobListener(FileJobListener listener) {\r\n        listeners.put(listener, null);\r\n    }\r\n\r\n    /**\r\n     * Removes the given FileJobListener from the list of listeners that receive notifications when the state of\r\n     * this FileJob has changed.\r\n     *\r\n     * @param listener the FileJobListener to remove\r\n     */\r\n    public void removeFileJobListener(FileJobListener listener) {\r\n        listeners.remove(listener);\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the timestamp in milliseconds when this job started.\r\n     *\r\n     * @return the timestamp in milliseconds when this job started\r\n     */\r\n    public long getStartDate() {\r\n        return startDate;\r\n    }\r\n\r\n    /**\r\n     * Returns the timestamp in milliseconds when this job ended, <code>0</code> if this job hasn't finished yet.\r\n     *\r\n     * @return the timestamp in milliseconds when this job ended\r\n     */\r\n    public long getEndDate() {\r\n        return endDate;\r\n    }\r\n\r\n    /**\r\n     * Returns the timestamp in milliseconds when this job was last paused, <code>0</code> if this job has not been\r\n     * paused yet.\r\n     *\r\n     * @return the timestamp in milliseconds when this job was last paused\r\n     */\r\n    public long getPauseStartDate() {\r\n        return pauseStartDate;\r\n    }\r\n    \r\n    /**\r\n     * Sets the timestamp in milliseconds when this job is paused.\r\n     */\r\n    private void setPauseStartDate() {\r\n        this.pauseStartDate = System.currentTimeMillis();\r\n    }\r\n\r\n    \r\n    /**\r\n     * Returns the number of milliseconds during which this job has been paused (been waiting for some user response).\r\n     * If this job has been paused several times, the total is returned. If this job has not been paused yet,\r\n     * <code>0</code> is returned.\r\n     *\r\n     * @return the number of milliseconds during which this job has been paused\r\n     */\r\n    public long getPausedTime() {\r\n        return pausedTime;\r\n    }\r\n\r\n    /**\r\n     * Adds a time of last pause to this job pause time counter. \r\n     */\r\n    private void calcPausedTime() {\r\n        this.pausedTime += System.currentTimeMillis() - this.getPauseStartDate();\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the number of milliseconds this job effectively spent processing files, excluding any pause time.\r\n     *\r\n     * @return the number of milliseconds this job effectively spent processing files, excluding any pause time\r\n     */\r\n    public long getEffectiveJobTime() {\r\n        // If job hasn't start yet, return 0\r\n        if (getStartDate() == 0) {\r\n            return 0;\r\n        }\r\n        \r\n        return (getEndDate() == 0 ? System.currentTimeMillis() : getEndDate())-getStartDate()-getPausedTime();\r\n    }\r\n\r\n    \r\n    /**\r\n     * Interrupts this job, changes the job state to {@link State#INTERRUPTED} and notifies listeners.\r\n     */\t\r\n    public void interrupt() {\r\n        switch (getState()) {\r\n            case INTERRUPTED:\r\n            case FINISHED:\r\n            return;\r\n            case PAUSED:\r\n            setPaused(false);\r\n                break;\r\n        }\r\n        // Set state before calling stop() so that state is INTERRUPTED when jobStopped() is called\r\n        // (some FileJob rely on that)\r\n        setState(State.INTERRUPTED);\r\n\r\n        stop();\r\n    }\r\n\r\n\r\n    /**\r\n     * Release reference to thread and store job's end date.\r\n     */\r\n    private void stop() {\r\n        // Return if job has already been stopped\r\n        if (jobThread == null) {\r\n            return;\r\n        }\r\n\r\n//        // Start by calling interrupt to have the thread return from any blocking I/O occurring in an interruptible\r\n//        // channel or selector.\r\n//        jobThread.interrupt();\r\n\r\n        jobThread = null;\r\n        endDate = System.currentTimeMillis();\r\n\r\n        // Notify that the job has been stopped\r\n        jobStopped();\r\n    }\r\n\r\n\t\r\n    /**\r\n     * Sets or unsets this job in paused mode.\r\n     * @param paused true to set in pause mode\r\n     */\r\n    public void setPaused(boolean paused) {\r\n        // Lock the pause lock while updating paused status\r\n        synchronized (pauseLock) {\r\n            // Resume job if it was paused\r\n            if (!paused && getState() == State.PAUSED) {\r\n                // Calculate pause time\r\n                calcPausedTime();                \r\n                // Call the jobResumed method to notify of the new job's state\r\n                jobResumed();\r\n\r\n                // Wake up the job's thread that is potentially waiting for pause to be over \r\n                pauseLock.notify();\r\n\r\n                // Switch to RUNNING state and notify listeners\r\n                setState(State.RUNNING);\r\n            }\r\n            // Pause job if it not paused already\r\n            else if(paused && getState() != State.PAUSED && getState() != State.INTERRUPTED && getState() != State.FINISHED) {\r\n                // Memorize pause time in order to calculate pause time when the job is resumed\r\n                setPauseStartDate();\r\n                // Call the jobPaused method to notify of the new job's state\r\n                jobPaused();\r\n\r\n                // Switch to PAUSED state and notify listeners\r\n                setState(State.PAUSED);\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Changes the current file. This method should be called by subclasses whenever the job\r\n     * starts processing a new file other than a top-level file, i.e. one that was passed\r\n     * as an argument to {@link #processFile(AbstractFile, Object) processFile()}.\r\n     * ({#nextFile(AbstractFile) nextFile()} is automatically called for files in base folder).\r\n     * @param file file to process\r\n     */\r\n    protected void nextFile(AbstractFile file) {\r\n        this.setCurrentFile(file);\r\n\r\n//        // Notify ProgressDialog (if any) that a new file is being processed\r\n//        if(progressDialog!=null)\r\n//            progressDialog.notifyCurrentFileChanged();\r\n        \r\n        // Lock the pause lock\r\n        synchronized(pauseLock) {\r\n            // Loop while job is paused, there shouldn't normally be more than one loop\r\n            while (getState() == State.PAUSED) {\r\n                try {\r\n                    // Wait for a call to notify()\r\n                    pauseLock.wait();\r\n                } catch(InterruptedException e) {\r\n                    // No more problem, loop one more time\r\n                }\r\n            }\r\n        }\r\n//        if(this.currentFile!=null)\r\n//            this.nbFilesProcessed++;\r\n    }\r\n\r\n\r\n//    protected void fileDiscovered(AbstractFile file) {\r\n//        this.nbFilesDiscovered++;\r\n//    }\r\n//\r\n//    protected void filesDiscovered(AbstractFile files[]) {\r\n//        this.nbFilesDiscovered += files.length;\r\n//    }\r\n//\r\n//    protected int getNbFilesDiscovered() {\r\n//        return this.nbFilesDiscovered;\r\n//    }\r\n//\r\n//    protected int getNbFilesProcessed() {\r\n//        return this.nbFilesProcessed;\r\n//    }\r\n//\r\n\r\n    /**\r\n     * Returns the name of the file currently being processed surrounded by simple quotes (e.g. 'test.zip'), or an empty\r\n     * string if no file is currently being processed.\r\n     *\r\n     * @return the name of the file currently being processed surrounded by simple quotes, or an empty string if no file\r\n     * is currently being processed\r\n     */\r\n    String getCurrentFilename() {\r\n        return currentFilename;\r\n    }\r\n\r\n\r\n    /**\r\n     * This method is called when this job starts, before the first call to {@link #processFile(AbstractFile,Object)} is made.\r\n     * This method implementation does nothing but it can be overriden by subclasses to perform some first-time initializations.\r\n     */\r\n    protected void jobStarted() {\r\n        LOGGER.debug(\"called\");\r\n    }\r\n\t\r\n\r\n    /**\r\n     * This method is called when this job has completed normal execution : all files have been processed without any interruption\r\n     * (without any call to {@link #interrupt()}).\r\n     *\r\n     * <p>The call happens after the last call to {@link #processFile(AbstractFile,Object)} is made.\r\n     * This method implementation does nothing but it can be overridden by subclasses to properly complete the job.\r\n\t \r\n     * <p>Note that this method will NOT be called if a call to {@link #interrupt()} was made before all files were processed.\r\n     */\r\n    protected void jobCompleted() {\r\n        LOGGER.debug(\"called\");\r\n\r\n        // Send a system notification if a notifier is available and enabled\r\n        if (AbstractNotifier.isAvailable() && AbstractNotifier.getNotifier().isEnabled()) {\r\n            AbstractNotifier.getNotifier().displayBackgroundNotification(NotificationType.JOB_COMPLETED,\r\n                    getProgressDialog() == null ? \"\" : getProgressDialog().getTitle(),\r\n                    Translator.get(\"progress_dialog.job_finished\"));\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * This method is called when this job has been paused, either by the user, or by the job when asking for user input.\r\n     * \r\n     * <p>This method implementation does nothing but it can be overridden by subclasses to do whatever is needed\r\n     * when the job has been paused.\r\n     */\r\n    protected void jobPaused() {\r\n        LOGGER.debug(\"called\");\r\n    }\r\n\r\n\r\n    /**\r\n     * This method is called when this job has been resumed after being paused.\r\n     *\r\n     * <p>This method implementation does nothing but it can be overridden by subclasses to do whatever is needed\r\n     * when the job has returned from pause.\r\n     */\r\n    protected void jobResumed() {\r\n        LOGGER.debug(\"called\");\r\n    }\r\n\r\n\r\n    /**\r\n     * This method is called when this job has been stopped. The call happens after all calls to {@link #processFile(AbstractFile,Object)} and\r\n     * {@link #jobCompleted()}.\r\n     * This method implementation does nothing but it can be overridden by subclasses to properly terminate the job.\r\n     * This is where you want to close any opened connections.\r\n     *\r\n     * <p>Note that unlike {@link #jobCompleted()} this method is always called, whether the job has been completed (all\r\n     * files were processed) or has been interrupted in the middle.\r\n     */\r\n    protected void jobStopped() {\r\n        LOGGER.debug(\"called\");\r\n    }\r\n\t\r\n\t\r\n    /**\r\n     * Displays an error dialog with the specified title and message,\r\n     * offers to skip the file, retry or cancel and waits for user choice.\r\n     * The job is stopped if 'cancel' or 'close' was chosen, and the result \r\n     * is returned.\r\n     *\r\n     * @param title error dialog title\r\n     * @param message error dialog message\r\n     */\r\n    protected int showErrorDialog(String title, String message) {\r\n        String[] actionTexts = new String[]{SKIP_TEXT, SKIP_ALL_TEXT, RETRY_TEXT, CANCEL_TEXT};\r\n        int[] actionValues = new int[]{SKIP_ACTION, SKIP_ALL_ACTION, RETRY_ACTION, CANCEL_ACTION};\r\n\r\n        return showErrorDialog(title, message, actionTexts, actionValues);\r\n    }\r\n\r\n\r\n\t\r\n    /**\r\n     * Displays an error dialog with the specified title and message and returns the selection action's value.\r\n     *\r\n     * @param title error dialog title\r\n     * @param message error dialog message\r\n     * @param actionTexts actions text to display\r\n     * @param actionValues actions return values\r\n     */\r\n    protected int showErrorDialog(String title, String message, String[] actionTexts, int[] actionValues) {\r\n        // Return SKIP_ACTION if 'skip all' has previously been selected and 'skip' is in the list of actions.\r\n        if (autoSkipErrors) {\r\n            for (int actionValue : actionValues)\r\n                if (actionValue == SKIP_ACTION) {\r\n                    return SKIP_ACTION;\r\n                }\r\n        }\r\n\r\n        // Send a system notification if a notifier is available and enabled\r\n        if (AbstractNotifier.isAvailable() && AbstractNotifier.getNotifier().isEnabled()) {\r\n            AbstractNotifier.getNotifier().displayBackgroundNotification(NotificationType.JOB_ERROR, title, message);\r\n        }\r\n\r\n        QuestionDialog dialog;\r\n        var mainFrame = getMainFrame().getJFrame();\r\n        if (getProgressDialog() == null) {\r\n            dialog = new QuestionDialog(mainFrame,\r\n                    title,\r\n                    message,\r\n                    mainFrame,\r\n                    actionTexts,\r\n                    actionValues,\r\n                    0);\r\n        } else {\r\n            dialog = new QuestionDialog(getProgressDialog(), \r\n                    title,\r\n                    message,\r\n                    mainFrame,\r\n                    actionTexts,\r\n                    actionValues,\r\n                    0);\r\n        }\r\n        // Cancel or close dialog stops this job\r\n        int userChoice = waitForUserResponse(dialog);\r\n        if (userChoice == -1 || userChoice == CANCEL_ACTION) {\r\n            interrupt();\r\n        // Keep 'skip all' choice for further error and return SKIP_ACTION\r\n        } else if (userChoice == SKIP_ALL_ACTION) {\r\n            autoSkipErrors = true;\r\n            return SKIP_ACTION;\r\n        }\r\n\r\n        return userChoice;\r\n    }\r\n\r\n\r\n    String enterRootPasswordDialog() {\r\n        return new PasswordDialog(Translator.get(\"enter_root_password\")).getPassword();\r\n    }\r\n\r\n\r\n\r\n\r\n    /**\r\n     * Waits for the user's answer to the given question dialog, putting this\r\n     * job in pause mode while waiting for the user.\r\n     *\r\n     * @param dialog object\r\n     */\r\n    int waitForUserResponse(DialogResult dialog) {\r\n        Object userInput = waitForUserResponseObject(dialog);\r\n        return (Integer) userInput;\r\n    }\r\n    \r\n    Object waitForUserResponseObject(DialogResult dialog) {\r\n        // Put this job in pause mode while waiting for user response\r\n        setPaused(true);\r\n        \r\n        UserInputHelper jobUserInput = new UserInputHelper(this, dialog);\r\n        Object userInput = jobUserInput.getUserInput();\r\n        \r\n        // Back to work\r\n        setPaused(false);\r\n        return userInput;\r\n    }\r\n    \r\n\t\r\n\t\r\n    /**\r\n     * Check and if needed, refreshes both file tables's current folders, based on the job's refresh policy.\r\n     */\r\n    private void refreshTables() {\r\n    \tFolderPanel activePanel = getMainFrame().getActivePanel();\r\n    \tFolderPanel inactivePanel = getMainFrame().getInactivePanel();\r\n\r\n        if (hasFolderChanged(inactivePanel.getCurrentFolder())) {\r\n        \tinactivePanel.tryRefreshCurrentFolder();\r\n        }\r\n\r\n        if (hasFolderChanged(activePanel.getCurrentFolder())) {\r\n            // Select file specified by selectFileWhenFinished (if any) only if the file exists in the active table's folder\r\n            if (fileToSelect != null && activePanel.getCurrentFolder().equalsCanonical(fileToSelect.getParent()) && fileToSelect.exists()) {\r\n            \tactivePanel.tryRefreshCurrentFolder(fileToSelect);\r\n            } else {\r\n            \tactivePanel.tryRefreshCurrentFolder();\r\n        }\r\n        }\r\n\r\n        // Repaint the status bar as marked files have changed\r\n        mainFrame.getStatusBar().updateSelectedFilesInfo();\r\n\r\n        // Resume current folders auto-refresh\r\n        getMainFrame().getLeftPanel().getFolderChangeMonitor().setPaused(false);\r\n        getMainFrame().getRightPanel().getFolderChangeMonitor().setPaused(false);\r\n    }\r\n\t\r\n\r\n    /**\r\n     * Returns this job's percentage of completion, as a float comprised between 0 and 1.\r\n     *\r\n     * @return this job's percentage of completion, as a float comprised between 0 and 1\r\n     */\r\n    public float getTotalPercentDone() {\r\n        return getCurrentFileIndex()/(float)getNbFiles();\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the index of the file currently being processed, {@link #getNbFiles()} if all files have been processed.\r\n     *\r\n     * @return the index of the file currently being processed, {@link #getNbFiles()} if all files have been processed\r\n     */\r\n    public int getCurrentFileIndex() {\r\n        return currentFileIndex==-1?0:currentFileIndex;\r\n    }\r\n    \r\n    /**\r\n     * Returns the file currently being processed.\r\n     * @return the file currently being processed.\r\n     */\r\n    public AbstractFile getCurrentFile() {\r\n    \treturn currentFile;\r\n    }\r\n    \r\n    /**\r\n     * Sets the file currently being processed.\r\n     * @param file the file currently being processed.\r\n     */\r\n    private void setCurrentFile(AbstractFile file) {\r\n        this.currentFile = file;\r\n        // Update current file information returned by getCurrentFilename()\r\n        this.currentFilename = \"'\" + file.getName() + \"'\";\r\n    }\r\n\r\n    /**\r\n     * Returns the number of file that this job contains.\r\n     *\r\n     * @return the number of file that this job contains\r\n     */\r\n    public int getNbFiles() {\r\n        return nbFiles;\r\n    }\r\n    \r\n    /**\r\n     * Sets the number of files that this job contains.\r\n     * \r\n     * @param nbFiles the number of files that this job contains.\r\n     */\r\n    protected void setNbFiles(int nbFiles) {\r\n    \tthis.nbFiles = nbFiles;\r\n    }\r\n\r\n    public void setFiles(FileSet files) {\r\n        this.files = files;\r\n        this.nbFiles = files.size();\r\n        this.baseSourceFolder = files.getBaseFolder();\r\n\r\n        // create CachedFile instances around the source files in order to cache the return value of frequently accessed\r\n        // methods. This eliminates some I/O, at the (small) cost of a bit more CPU and memory. Recursion is enabled\r\n        // so that children and parents of the files are also cached.\r\n        // Note: When cached methods are called, they no longer reflect changes in the underlying files. In particular,\r\n        // changes of size or date could potentially not be reflected when files are being processed but this should\r\n        // not really present a risk.\r\n        for (int i = 0; i < nbFiles; i++) {\r\n            AbstractFile tempFile = files.elementAt(i);\r\n            files.setElementAt((tempFile instanceof CachedFile)?tempFile:new CachedFile(tempFile, true), i);\r\n        }\r\n\r\n        if (this.baseSourceFolder != null) {\r\n            this.baseSourceFolder = (getBaseSourceFolder() instanceof CachedFile)?getBaseSourceFolder():new CachedFile(getBaseSourceFolder(), true);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Returns a String describing what the job is currently doing. This default implementation returns\r\n     * <i>Processing CURRENT_FILE</i> where CURRENT_FILE is the name of the file currently being processed.\r\n     * This method should be overridden to provide a more accurate description.\r\n     *\r\n     * @return a String describing what the job is currently doing\r\n     */\r\n    public String getStatusString() {\r\n        return Translator.get(\"progress_dialog.processing_file\", getCurrentFilename());\r\n    }\r\n\r\n    /**\r\n     * Returns information about the job progress.\r\n     * @return the job progress\r\n     */\r\n\tpublic JobProgress getJobProgress() {\r\n\t\treturn jobProgress;\t\t\r\n\t}\r\n\r\n    /**\r\n     * Returns the base source folder.\r\n     * @return the baseSourceFolder\r\n     */\r\n    AbstractFile getBaseSourceFolder() {\r\n        return baseSourceFolder;\r\n    }\r\n\t\r\n\r\n    /**\r\n     * This method is public as a side-effect of this class implementing <code>Runnable</code>.\r\n     */\r\n    @Override\r\n    public final void run() {\r\n        FileTable activeTable = getMainFrame().getActiveTable();\r\n\r\n        // Notify that this job has started\r\n        jobStarted();\r\n\r\n//this.nbFilesDiscovered += nbFiles;\r\n\r\n        // Loop on all source files, checking that job has not been interrupted\r\n        for (currentFileIndex = 0; currentFileIndex < nbFiles; currentFileIndex++) {\r\n            AbstractFile currentFile = files.elementAt(currentFileIndex);\r\n\r\n            // Change current file and advance file index\r\n            nextFile(currentFile);\r\n\r\n            // Process current file\r\n            boolean success = processFile(currentFile, null);\r\n\r\n            // Stop if job was interrupted\r\n            if (getState() == State.INTERRUPTED) {\r\n                break;\r\n            }\r\n\r\n            // Unmark file in active table if 'auto unmark' is enabled\r\n            // and file was processed successfully\r\n            if (autoUnmark && success) {\r\n                // Do not repaint rows individually as it would be too expensive\r\n                activeTable.setFileMarked(currentFile, false, false);\r\n            }\r\n\r\n            // If last file was reached without any user interruption, all files have been processed with or\r\n            // without errors, switch to FINISHED state and notify listeners\r\n            if (currentFileIndex >= nbFiles-1 && getState() != FileJob.State.INTERRUPTED) {\r\n                currentFileIndex++;\r\n                stop();\r\n                jobCompleted();\r\n                setState(State.FINISHED);\r\n            }\r\n        }\r\n\r\n        // Refresh tables's current folders, based on the job's refresh policy.\r\n        refreshTables();\r\n    }\r\n\r\n\r\n\r\n    /**\r\n     * Returns <code>true</code> if the given folder has or may have been modified by this job.\r\n     * This method is called after this job has finished processing files, to determine if the current MainFrame's\r\n     * file tables need to be refreshed to reveal the modified contents.\r\n     *\r\n     * @param folder the folder to test \r\n     * @return true if the given folder has or may have been modified by this job\r\n     */\r\n    protected abstract boolean hasFolderChanged(AbstractFile folder);\r\n\t\r\n\t\r\n    /**\r\n     * Automatically called by {@link #run()} for each file that needs to be processed.\r\n     *\r\n     * @param file the file or folder to process\r\n     * @param recurseParams array of parameters which can be used when calling this method recursively, contains <code>null</code> when called by {@link #run()}\r\n     *\r\n     * @return <code>true</code> if the operation was successful\r\n     */\r\n    protected abstract boolean processFile(AbstractFile file, Object recurseParams);\r\n\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/FileJobException.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.job;\n\n\n/**\n * FileJobException are exceptions that can be thrown by certain\n * FileJob methods.\n *\n * @author Maxence Bernard\n */\npublic class FileJobException extends Exception {\n\n    /** Source cannot be opened */\n    public final static int CANNOT_OPEN_SOURCE = 1;\n\n    /** Destination cannot be opened */\n    public final static int CANNOT_OPEN_DESTINATION = 2;\n\n    /** An error occurred during the file transfer */\n    public final static int ERROR_WHILE_TRANSFERRING = 3;\n\n\n    protected int reason;\n\n\t\n    public FileJobException(int reason) {\n        this.reason = reason;\n    }\n\t\n    public int getReason() {\n        return reason;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/FileJobListener.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.job;\r\n\r\n/**\r\n * Interface to be implemented by classes that wish to be notified of state changes on a particular\r\n * {@link FileJob}. Those classes need to be registered to receive those events, this can be done by calling\r\n * {@link FileJob#addFileJobListener(FileJobListener)}.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic interface FileJobListener {\r\n\r\n    /**\r\n     * Called when the state of the specified FileJob has changed.\r\n     *\r\n     * @param source the FileJob which state has changed\r\n     * @param oldState the FileJob's state prior to the change, see FileJob's constant fields for possible values\r\n     * @param newState the new FileJob's state, see FileJob's constant fields for possible values\r\n     */\r\n    void jobStateChanged(FileJob source, FileJob.State oldState, FileJob.State newState);\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/FindFileJob.java",
    "content": "/*\r\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\r\n * Copyright (C) 2014-2017 Oleg Trifonov\r\n *\r\n * trolCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * trolCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\npackage com.mucommander.job;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport org.apache.commons.io.IOCase;\r\nimport org.apache.commons.io.filefilter.*;\r\nimport org.jetbrains.annotations.NotNull;\r\nimport ru.trolsoft.utils.search.*;\r\n\r\nimport java.io.File;\r\nimport java.io.IOException;\r\nimport java.io.UnsupportedEncodingException;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\n/**\r\n * Job for directory scanning\r\n */\r\npublic class FindFileJob extends FileJob {\r\n\r\n    private AbstractFile startDirectory;\r\n    private String fileContent;\r\n    private boolean searchSubdirectories;\r\n    private boolean searchArchives;\r\n    private boolean ignoreHidden;\r\n    private SearchPattern searchPattern;\r\n\r\n    private AbstractFileFilter fileFilter;\r\n\r\n    private final List<AbstractFile> list = new ArrayList<>();\r\n\r\n    public FindFileJob(MainFrame mainFrame) {\r\n        super(mainFrame);\r\n        setAutoUnmark(false);\r\n    }\r\n\r\n\r\n    @Override\r\n    protected boolean hasFolderChanged(AbstractFile folder) {\r\n        return false;\r\n    }\r\n\r\n    @Override\r\n    protected boolean processFile(AbstractFile file, Object recurseParams) {\r\n        // Stop if interrupted\r\n        if (getState() == State.INTERRUPTED) {\r\n            return false;\r\n        }\r\n        // If file is a directory, recurs\r\n        if (file.isDirectory() && (!file.isSymlink() || file.equals(startDirectory))) {\r\n            searchInFile(file);\r\n            if (!searchSubdirectories && !file.equals(startDirectory)) {\r\n                return true;\r\n            }\r\n            try {\r\n                AbstractFile[] subFiles = file.ls();\r\n                for (int i = 0; i < subFiles.length && getState() != State.INTERRUPTED; i++) {\r\n                    if (ignoreHidden && file.isHidden()) {\r\n                        continue;\r\n                    }\r\n                    // Notify job that we're starting to process this file (needed for recursive calls to processFile)\r\n                    nextFile(subFiles[i]);\r\n                    processFile(subFiles[i], null);\r\n                }\r\n            } catch(Throwable e) {\r\n                // Should we tell the user?\r\n            }\r\n        } else { // If not, increase file counter and bytes total\r\n            if (!ignoreHidden || !file.isHidden()) {\r\n                searchInFile(file);\r\n            }\r\n        }\r\n\r\n        if (file.isArchive() && searchArchives) {\r\n            try {\r\n                AbstractFile[] subFiles = file.ls();\r\n                for (int i = 0; i < subFiles.length && getState() != State.INTERRUPTED; i++) {\r\n                    if (ignoreHidden && file.isHidden()) {\r\n                        continue;\r\n                    }\r\n                    // Notify job that we're starting to process this file (needed for recursive calls to processFile)\r\n                    nextFile(subFiles[i]);\r\n                    processFile(subFiles[i], null);\r\n                }\r\n            } catch(Throwable e) {\r\n                // Should we tell the user?\r\n            }\r\n        }\r\n\r\n        return true;\r\n    }\r\n\r\n    private void searchInFile(AbstractFile file) {\r\n        File f = new File(file.toString());\r\n        if (fileFilter.accept(f) && fileContainsString(file)) {\r\n            synchronized (this) {\r\n                list.add(file);\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n    private boolean fileContainsString(AbstractFile f) {\r\n        //Profiler.start(\"check_new\");\r\n        if (fileContent == null || fileContent.isEmpty()) {\r\n            return true;\r\n        }\r\n        if (f.isDirectory()) {\r\n            return false;\r\n        }\r\n\r\n        try (SearchSourceStream source = new InputStreamSource(f.getInputStream())) {\r\n            long pos = SearchUtils.indexOf(source, searchPattern);\r\n            //Profiler.stop(\"check_new\");\r\n            return pos >= 0;\r\n        } catch (IOException | SearchException e) {\r\n            e.printStackTrace();\r\n            return false;\r\n        }\r\n    }\r\n\r\n\r\n\r\n\r\n    public List<AbstractFile> getResults() {\r\n        return list;\r\n    }\r\n\r\n    public void setStartDirectory(AbstractFile startDirectory) {\r\n        this.startDirectory = startDirectory;\r\n        FileSet fs = new FileSet();\r\n        fs.add(startDirectory);\r\n        setFiles(fs);\r\n    }\r\n\r\n    public void setup(String fileMask, String fileContent, boolean searchSubdirs, boolean searchArchives, boolean caseSensitive, boolean ignoreHidden, String encoding, boolean hexMode, byte[] bytes) {\r\n        fileMask = fileMask.trim();\r\n        fileMask = fileMask.isEmpty() ? \"*\" : fileMask;\r\n        this.fileContent = fileContent;\r\n        this.searchSubdirectories = searchSubdirs;\r\n        this.searchArchives = searchArchives;\r\n        this.ignoreHidden = ignoreHidden;\r\n        IOCase filterCase = OsFamily.MAC_OS_X.isCurrent() || OsFamily.WINDOWS.isCurrent() ? IOCase.INSENSITIVE : IOCase.SENSITIVE;\r\n\r\n        fileFilter = buildFileFilter(fileMask, filterCase);\r\n\r\n        if (hexMode) {\r\n            searchPattern = new BytesSearchPattern(bytes);\r\n        } else {\r\n            try {\r\n                searchPattern = caseSensitive ?\r\n                        new StringCaseSensitiveSearchPattern(fileContent, encoding) :\r\n                        new StringCaseInsensitiveSearchPattern(fileContent, encoding);\r\n            } catch (UnsupportedEncodingException e) {\r\n                e.printStackTrace();\r\n            }\r\n        }\r\n\r\n    }\r\n\r\n    @NotNull\r\n    private static AbstractFileFilter buildFileFilter(String fileMask, IOCase filterCase) {\r\n        if (!fileMask.contains(\",\")) {\r\n            String mask = fileMask.startsWith(\"!\") ? fileMask.substring(1) : fileMask;\r\n            return WildcardFileFilter.builder().setWildcards(mask.trim()).setIoCase(filterCase).get();\r\n        }\r\n\r\n        return buildMultipleFileFilters(fileMask, filterCase);\r\n    }\r\n\r\n    @NotNull\r\n    private static AbstractFileFilter buildMultipleFileFilters(String fileMask, IOCase filterCase) {\r\n        String[] masks = fileMask.split(\",\");\r\n        List<IOFileFilter> fileFilters = new ArrayList<>();\r\n        boolean hasNot = false;\r\n        for (String mask : masks) {\r\n            String trimMask = mask.trim();\r\n            if (trimMask.isEmpty()) {\r\n                continue;\r\n            }\r\n            if (trimMask.startsWith(\"!\")) {\r\n                var notMask = trimMask.substring(1).trim();\r\n                if (notMask.isEmpty()) {\r\n                    continue;\r\n                }\r\n                fileFilters.add(WildcardFileFilter.builder().setWildcards(notMask).setIoCase(filterCase).get());\r\n                hasNot = true;\r\n            } else {\r\n                fileFilters.add(WildcardFileFilter.builder().setWildcards(trimMask).setIoCase(filterCase).get());\r\n            }\r\n        }\r\n        return hasNot ? new AndFileFilter(fileFilters) : new OrFileFilter(fileFilters);\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/MakeDirectoryFileJob.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.job;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.ui.macosx.AppleScript;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.commons.io.BufferPool;\nimport com.mucommander.commons.io.RandomAccessOutputStream;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.file.FileCollisionDialog;\nimport com.mucommander.ui.dialog.file.ProgressDialog;\nimport com.mucommander.ui.main.MainFrame;\n\n\n/**\n * This FileJob creates a new file or directory.\n *\n * @author Maxence Bernard\n */\npublic class MakeDirectoryFileJob extends FileJob {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(MakeDirectoryFileJob.class);\n\t\n    private final AbstractFile destFolder;\n\n    private final boolean mkfileMode;\n    private long allocateSpace;\n    private boolean executable;\n\n\n    /**\n     * Creates a new MakeDirectoryFileJob which operates in 'mkdir' mode.\n     * @param progressDialog dialog which shows this job's progress\n     * @param mainFrame mainFrame this job has been triggered by\n     * @param fileSet files which are going to be processed\n     */\n    public MakeDirectoryFileJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet fileSet) {\n        super(progressDialog, mainFrame, fileSet);\n\n        this.destFolder = fileSet.getBaseFolder();\n        this.mkfileMode = false;\n\t\t\n        setAutoUnmark(false);\n    }\n\n    /**\n     * Creates a new MakeDirectoryFileJob which operates in 'mkfile' mode.\n     *\n     * @param progressDialog dialog which shows this job's progress\n     * @param mainFrame mainFrame this job has been triggered by\n     * @param fileSet files which are going to be processed\n     * @param allocateSpace number of bytes to allocate to the file, -1 for none (use AbstractFile#mkfile())\n     * @param executable set 'executable' attribute on unix-systems\n     */\n    public MakeDirectoryFileJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet fileSet, long allocateSpace, boolean executable) {\n        super(progressDialog, mainFrame, fileSet);\n\n        this.destFolder = fileSet.getBaseFolder();\n        this.mkfileMode = true;\n        this.allocateSpace = allocateSpace;\n        this.executable = executable;\n\n        setAutoUnmark(false);\n    }\n\n\n    ////////////////////////////\n    // FileJob implementation //\n    ////////////////////////////\n\n    /**\n     * Creates the new directory in the destination folder.\n     */\n    @Override\n    protected boolean processFile(AbstractFile file, Object recurseParams) {\n        // Stop if interrupted (although there is no way to stop the job at this time)\n        if (getState() == State.INTERRUPTED) {\n            return false;\n        }\n\n        do {\n            try {\n                LOGGER.debug(\"Creating {}\", file);\n\n                // Check for file collisions, i.e. if the file already exists in the destination\n                int collision = FileCollisionChecker.checkForCollision(null, file);\n                if (collision != FileCollisionChecker.NO_COLLISION) {\n                    // File already exists in destination, ask the user what to do (cancel, overwrite,...) but\n                    // do not offer the multiple files mode options such as 'skip' and 'apply to all'.\n                    var mainFrame = getMainFrame().getJFrame();\n                    int choice = waitForUserResponse(new FileCollisionDialog(mainFrame, mainFrame, collision, null, file, false, false));\n\n                    // Overwrite file\n                    if (choice == FileCollisionDialog.OVERWRITE_ACTION) {\n                        // Delete the file\n                        file.delete();\n                    }\n                    // Cancel or dialog close (return)\n//                    else if (choice==-1 || choice==FileCollisionDialog.CANCEL_ACTION) {\n                    else {\n                        interrupt();\n                        return false;\n                    }\n                }\n\n                if (mkfileMode) {\n                    // create file\n                    mkFile(file);\n                } else {\n                    // create directory\n                    mkDir(file);\n                }\n\n                // Resolve new file instance now that it exists: remote files do not update file attributes after\n                // creation, we need to get an instance that reflects the newly created file attributes\n                file = FileFactory.getFile(file.getURL());\n\n                // Select newly created file when job is finished\n                selectFileWhenFinished(file);\n\n                return true;        // Return Success\n            } catch (IOException e) {\n                // In mkfile mode, interrupting the job will close the OutputStream and cause an IOException to be\n                // thrown, this is normal behavior\n                if (mkfileMode && getState() == State.INTERRUPTED) {\n                    return false;\n                }\n\n                LOGGER.debug(\"IOException caught\", e);\n\n                boolean needAdminPermissions = e instanceof FileAccessDeniedException;\n                int action;\n                if (needAdminPermissions && !mkfileMode) {\n                    if (OsFamily.MAC_OS_X.isCurrent()) {\n                        tryMkDirAsAdministrator(file.getAbsolutePath(), null);\n                        return true;\n                    } else {\n                        action = showErrorDialog(\n                                Translator.get(\"error\"),\n                                Translator.get(mkfileMode ? \"cannot_write_file\" : \"cannot_create_folder\", file.getAbsolutePath()),\n                                new String[]{RETRY_TEXT, RETRY_AS_ROOT_TEXT, RETRY_AS_ROOT_ALWAYS_TEXT, CANCEL_TEXT},\n                                new int[]{RETRY_ACTION, RETRY_AS_ROOT_ACTION, RETRY_AS_ROOT_ALWAYS_ACTION, CANCEL_ACTION}\n                        );\n                    }\n                } else {\n                    action = showErrorDialog(\n                            Translator.get(\"error\"),\n                            Translator.get(mkfileMode ? \"cannot_write_file\" : \"cannot_create_folder\", file.getAbsolutePath()),\n                            new String[] {RETRY_TEXT, CANCEL_TEXT},\n                            new int[] {RETRY_ACTION, CANCEL_ACTION}\n                    );\n                }\n                // Retry (loop)\n                switch (action) {\n                    case RETRY_AS_ROOT_ACTION:\n                        String password = enterRootPasswordDialog();\n                        tryMkDirAsAdministrator(file.getAbsolutePath(), password);\n                        continue;\n//                    case RETRY_AS_ROOT_ALWAYS_ACTION:\n//                        String password = enterRootPasswordDialog();\n                    case RETRY_ACTION:\n                        continue;\n                }\n\n                // Cancel action\n                return false;\t\t// Return Failure\n            }    \n        } while(true);\n    }\n\n    private void tryMkDirAsAdministrator(String path, String password) {\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            AppleScript.execute(\"do shell script \\\"mkdir -p + '\" + path + \"' \\\" with administrator privileges\", new StringBuilder());\n        }\n    }\n\n\n    private void mkFile(AbstractFile file) throws IOException {\n        // Use mkfile\n        if (allocateSpace == -1) {\n            file.mkfile();\n        }\n        // Allocate the requested number of bytes\n        else {\n            OutputStream mkfileOut = null;\n            try {\n                // using RandomAccessOutputStream if we can have one\n                if (file.isFileOperationSupported(FileOperation.RANDOM_WRITE_FILE)) {\n                    mkfileOut = file.getRandomAccessOutputStream();\n                    ((RandomAccessOutputStream)mkfileOut).setLength(allocateSpace);\n                }\n                // manually otherwise\n                else {\n                    mkfileOut = file.getOutputStream();\n\n                    // Use BufferPool to avoid excessive memory allocation and garbage collection\n                    byte[] buffer = BufferPool.getByteArray();\n                    int bufferSize = buffer.length;\n\n                    try {\n                        long remaining = allocateSpace;\n                        while (remaining > 0 && getState() != State.INTERRUPTED) {\n                            int nbWrite = (int)(remaining > bufferSize ? bufferSize : remaining);\n                            mkfileOut.write(buffer, 0, nbWrite);\n                            remaining -= nbWrite;\n                        }\n                    } finally {\n                        BufferPool.releaseByteArray(buffer);\n                    }\n                }\n            } finally {\n                if (mkfileOut != null)\n                    try {\n                        mkfileOut.close();\n                    } catch (IOException ignore) {\n                    }\n            }\n        }\n\n        // set 'executable' attribute\n        if (executable && file.isFileOperationSupported(FileOperation.CHANGE_PERMISSION)) {\n            try {\n                file.changePermissions(FilePermissions.DEFAULT_EXECUTABLE_PERMISSIONS);\n            } catch (IOException ignore) {}\n        }\n    }\n\n\n    private void mkDir(AbstractFile file) throws IOException {\n        file.mkdirs();\n    }\n\n    /**\n     * Folders only needs to be refreshed if it is the destination folder\n     */\n    @Override\n    protected boolean hasFolderChanged(AbstractFile folder) {\n        return destFolder.equalsCanonical(folder);\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    @Override\n    public String getStatusString() {\n        return Translator.get(\"creating_file\", getCurrentFilename());\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/MoveJob.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.job;\n\nimport java.io.IOException;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.AbstractRWArchiveFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.FileOperation;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.file.ProgressDialog;\nimport com.mucommander.ui.main.MainFrame;\n\n\n/**\n * This job recursively moves a group of files.\n *\n * @author Maxence Bernard\n */\npublic class MoveJob extends AbstractCopyJob {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(MoveJob.class);\n\t\n    /** True if this job corresponds to a single file renaming */\n    private final boolean renameMode;\n\n    \n    /**\n     * Creates a new MoveJob without starting it.\n     *\n     * @param progressDialog dialog which shows this job's progress\n     * @param mainFrame mainFrame this job has been triggered by\n     * @param files files which are going to be moved\n     * @param destFolder destination folder where the files will be moved\n     * @param newName the new filename in the destination folder, can be <code>null</code> in which case the original filename will be used.\n     * @param fileExistsAction default action to be performed when a file already exists in the destination, see {@link com.mucommander.ui.dialog.file.FileCollisionDialog} for allowed values\n     * @param renameMode true if this job corresponds to a single file renaming\n     */\n    public MoveJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet files, AbstractFile destFolder, String newName, int fileExistsAction, boolean renameMode) {\n        super(progressDialog, mainFrame, files, destFolder, newName, fileExistsAction);\n\n        this.errorDialogTitle = Translator.get(\"move_dialog.error_title\");\n        this.renameMode = renameMode;\n    }\n\n\n    ////////////////////////////////////\n    // TransferFileJob implementation //\n    ////////////////////////////////////\n\n    /**\n     * Moves recursively the given file or folder. \n     *\n     * @param file the file or folder to move\n     * @param recurseParams destination folder where the given file will be moved (null for top level files)\n     * \n     * @return <code>true</code> if the file has been moved completly (copied + deleted).\n     */\n    @Override\n    protected boolean processFile(AbstractFile file, Object recurseParams) {\n        // Stop if interrupted\n        if (getState() == State.INTERRUPTED) {\n            return false;\n        }\n\t\t\n        // Destination folder\n        AbstractFile destFolder = recurseParams == null ? baseDestFolder : (AbstractFile)recurseParams;\n\t\t\n        // Is current file at the base folder level ?\n        boolean isFileInBaseFolder = files.contains(file);\n\n        // Determine filename in destination\n        String originalName = file.getName();\n        String destFileName = isFileInBaseFolder && newName != null ? newName : originalName;\n\n        // create destination AbstractFile instance\n        AbstractFile destFile = createDestinationFile(destFolder, destFileName);\n        if (destFile == null) {\n            return false;\n        }\n\n        // Do not follow symlink, simply delete it and return\n//        if (file.isSymlink()) {\n//            do {\t\t// Loop for retry\n//                try  {\n//                    file.delete();\n//                    return true;\n//                } catch(IOException e) {\n//                    LOGGER.debug(\"IOException caught\", e);\n//\n//                    int ret = showErrorDialog(errorDialogTitle, Translator.get(\"cannot_delete_file\", file.getAbsolutePath()));\n//                    // Retry loops\n//                    if (ret == RETRY_ACTION) {\n//                        continue;\n//                    }\n//                    // Cancel, skip or close dialog returns false\n//                    return false;\n//                }\n//            } while(true);\n//        }\n\n        destFile = checkForCollision(file, destFolder, destFile, renameMode);\n        if (destFile == null) {\n            return false;\n        }\n\n        // Let's try to rename the file using AbstractFile#renameTo() whenever possible, as it is more efficient\n        // than moving the file manually.\n        //\n        // Do not attempt to rename the file in the following cases:\n        // - destination has to be appended\n        // - file schemes do not match (at the time of writing, no filesystem supports mixed scheme renaming)\n        // - if the 'rename' operation is not supported\n        // Note: we want to avoid calling AbstractFile#renameTo when we know it will fail, as it performs some costly\n        // I/O bound checks and ends up throwing an exception which also comes at a cost.\n        if (!append && file.getURL().schemeEquals(destFile.getURL()) && file.isFileOperationSupported(FileOperation.RENAME)) {\n            try {\n                file.renameTo(destFile);\n                return true;\n            } catch(IOException e) {\n                // Fail silently: renameTo might fail under normal conditions, for instance for local files which are\n                // not located on the same volume.\n                LOGGER.debug(\"Failed to rename \"+file+\" into \"+destFile+\" (not necessarily an error)\", e);\n            }\n        }\n        // Rename couldn't be used or didn't succeed: move the file manually\n\n        // Move the directory and all its children recursively, by copying files to the destination and then deleting them.\n        if (file.isDirectory()) {\n            // create the destination folder if it doesn't exist\n            if (!(destFile.exists() && destFile.isDirectory())) {\n                do {\t\t// Loop for retry\n                    try {\n                        destFile.mkdir();\n                    } catch(IOException e) {\n                        int ret = showErrorDialog(errorDialogTitle, Translator.get(\"cannot_create_folder\", destFile.getAbsolutePath()));\n                        // Retry loops\n                        if (ret == RETRY_ACTION) {\n                            continue;\n                        }\n                        // Cancel, skip or close dialog returns false\n                        return false;\n                    }\n                    break;\n                } while(true);\n            }\n\t\t\t\n            // move each file in this folder recursively\n            do {\t\t// Loop for retry\n                try {\n                    AbstractFile[] subFiles = file.ls();\n                    boolean isFolderEmpty = true;\n                    for (AbstractFile subFile : subFiles) {\n                        // Return now if the job was interrupted, so that we do not attempt to delete this folder\n                        if (getState() == State.INTERRUPTED)\n                            return false;\n\n                        // Notify job that we're starting to process this file (needed for recursive calls to processFile)\n                        nextFile(subFile);\n                        if (!processFile(subFile, destFile))\n                            isFolderEmpty = false;\n                    }\n\n                    // Only when finished with folder, set destination folder's date to match the original folder one\n                    if (destFile.isFileOperationSupported(FileOperation.CHANGE_DATE)) {\n                        try {\n                            destFile.setLastModifiedDate(file.getLastModifiedDate());\n                        }\n                        catch (IOException e) {\n                            LOGGER.debug(\"failed to change the date of \"+destFile, e);\n                            // Fail silently\n                        }\n                    }\n\n                    // If one file failed to be moved, return false (failure) since this folder could not be moved totally\n                    if (!isFolderEmpty) {\n                        return false;\n                    }\n                } catch (IOException e) {\n                    // file.ls() failed\n                    int ret = showErrorDialog(errorDialogTitle, Translator.get(\"cannot_read_folder\", file.getName()));\n                    // Retry loops\n                    if (ret == RETRY_ACTION)\n                        continue;\n                    // Cancel, skip or close dialog returns false\n                    return false;\n                }\n                break;\n            } while(true);\n\n            // Return now if the job was interrupted, so that we do not attempt to delete this folder\n            if (getState() == State.INTERRUPTED) {\n                return false;\n            }\n\n            // finally, delete the empty folder\n            return deleteEmptyFolder(file);\n        }\n        // File is a regular file, move it by copying it to the destination and then deleting it\n        else  {\n            // if renameTo() was not supported or failed, or if it wasn't possible because of 'append',\n            // try the hard way by copying the file first, and then deleting the source file.\n            if (tryCopyFile(file, destFile, append, errorDialogTitle) && getState() != State.INTERRUPTED) {\n                // Delete the source file\n                do {\t\t// Loop for retry\n                    try  {\n                        file.delete();\n                        // All OK\n                        return true;\n                    } catch(IOException e) {\n                        LOGGER.debug(\"IOException caught\", e);\n\n                        int ret = showErrorDialog(errorDialogTitle, Translator.get(\"cannot_delete_file\", file.getAbsolutePath()));\n                        // Retry loops\n                        if (ret == RETRY_ACTION) {\n                            continue;\n                        }\n                        // Cancel, skip or close dialog returns false\n                        return false;\n                    }\n                } while(true);\n            }\n\n            return false;\n        }\n    }\n\n    private boolean deleteEmptyFolder(AbstractFile file) {\n        do {\t\t// Loop for retry\n            try {\n                file.delete();\n                return true;\n            } catch(IOException e) {\n                int ret = showErrorDialog(errorDialogTitle, Translator.get(\"cannot_delete_folder\", file.getAbsolutePath()));\n                // Retry loops\n                if (ret == RETRY_ACTION)\n                    continue;\n                // Cancel, skip or close dialog returns false\n                return false;\n            }\n        } while(true);\n    }\n\n    // This job modifies baseDestFolder and its subfolders\n    @Override\n    protected boolean hasFolderChanged(AbstractFile folder) {\n        return (getBaseSourceFolder()!=null && getBaseSourceFolder().isParentOf(folder)) || baseDestFolder.isParentOf(folder);\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    @Override\n    protected void jobCompleted() {\n        super.jobCompleted();\n\n        // If the source files are located inside an archive, optimize the archive file\n        AbstractArchiveFile sourceArchiveFile = getBaseSourceFolder()==null?null:getBaseSourceFolder().getParentArchive();\n        if (sourceArchiveFile != null && sourceArchiveFile.isArchive() && sourceArchiveFile.isWritable()) {\n            optimizeArchive((AbstractRWArchiveFile) sourceArchiveFile);\n        }\n\n        // If the destination files are located inside an archive, optimize the archive file, only if the destination\n        // archive is different from the source one\n        AbstractArchiveFile destArchiveFile = baseDestFolder.getParentArchive();\n        if (destArchiveFile != null && destArchiveFile.isArchive() && destArchiveFile.isWritable()\n                && !(sourceArchiveFile != null && destArchiveFile.equalsCanonical(sourceArchiveFile))) {\n            optimizeArchive((AbstractRWArchiveFile) destArchiveFile);\n        }\n\n        // If this job corresponds to a file renaming in the same directory, select the renamed file\n        // in the active table after this job has finished (and hasn't been cancelled)\n        if (files.size() == 1 && newName != null && baseDestFolder.equalsCanonical(files.elementAt(0).getParent())) {\n            // Resolve new file instance now that it exists: some remote files do not immediately update file attributes\n            // after creation, we need to get an instance that reflects the newly created file attributes\n            selectFileWhenFinished(FileFactory.getFile(baseDestFolder.getAbsolutePath(true)+newName));\n        }\n    }\n\n    @Override\n    public String getStatusString() {\n        if (isCheckingIntegrity()) {\n            return super.getStatusString();\n        }\n\n        if (isOptimizingArchive) {\n            return Translator.get(\"optimizing_archive\", archiveToOptimize.getName());\n        }\n\n        return Translator.get(\"move_dialog.moving_file\", getCurrentFilename());\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/PropertiesJob.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.job;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.ui.main.MainFrame;\n\nimport java.io.IOException;\n\n\n/**\n * This FileJob calculates the number of files contained in a list of file and folders and\n * computes their size.\n *\n * @author Maxence Bernard\n */\npublic class PropertiesJob extends FileJob {\n    \n    /** Number of folders encountered so far */\n    private int nbFolders;\n\n    /** Number of regular files (not folders) encountered so far */\n    private int nbFilesRecurse;\n\t\n    /** Combined size of all files encountered so far */\n    private long totalBytes;\n\t\n\t\n    public PropertiesJob(FileSet files, MainFrame mainFrame) {\n        super(mainFrame, files);\n        setAutoUnmark(false);\n    }\n\n    /**\n     * Returns the size in bytes of all the files seen so far.\n     */\n    public long getTotalBytes() {\n        return totalBytes;\n    }\n\n    /**\n     * Returns the number of folders counted so far.\n     */\n    public int getNbFolders() {\n        return nbFolders;\n    }\n \n    /**\n     * Returns the number of files (folders excluded) counted so far.\n     */\n    public int getNbFilesRecurse() {\n        return nbFilesRecurse;\n    }\n \n\n    ////////////////////////////\n    // FileJob implementation //\n    ////////////////////////////\n\n    /**\n     * Adds the given file to the total of files or folders and the total size,\n     * and recurses if it is a folder.\n     */\n    @Override\n    protected boolean processFile(AbstractFile file, Object recurseParams) {\n        // Stop if interrupted\n        if (getState() == State.INTERRUPTED)\n            return false;\n\n        // If file is a directory, increase folder counter and recurse\n        if (file.isDirectory() && !file.isSymlink()) {\n            nbFolders++;\n\n            try {\n                AbstractFile[] subFiles = file.ls();\n                for (int i = 0; i < subFiles.length && getState() != State.INTERRUPTED; i++) {\n                    // Notify job that we're starting to process this file (needed for recursive calls to processFile)\n                    nextFile(subFiles[i]);\n                    processFile(subFiles[i], null);\n                }\n            } catch(IOException e) {\n                // Should we tell the user?\n            }\n        }\n        // If not, increase file counter and bytes total\n        else {\n            nbFilesRecurse++;\n            long fileSize = file.getSize();\n            if (fileSize > 0) {\t\t// Can be equal to -1 if size not available\n                totalBytes += fileSize;\n            }\n        }\n\t\n        return true;\n    }\n\n    // This job does not modify anything\n    @Override\n    protected boolean hasFolderChanged(AbstractFile folder) {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/SelfUpdateJob.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2021 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.job;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.commons.file.util.ResourceLoader;\nimport com.mucommander.ui.dialog.file.FileCollisionDialog;\nimport com.mucommander.ui.dialog.file.ProgressDialog;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.WindowManager;\nimport com.mucommander.updates.SelfUpdateUtils;\nimport com.mucommander.utils.text.Translator;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * This job self-updates the trolCommander with a new JAR file that is fetched from a specified remote file.\n */\npublic class SelfUpdateJob extends CopyJob {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(SelfUpdateJob.class);\n\n\tprivate static final String TEMP_JAR_FILENAME = \"trolcommander-new.jar\";\n\n\n\n    public SelfUpdateJob(ProgressDialog progressDialog, MainFrame mainFrame, AbstractFile remoteJarFile) {\n        super(progressDialog, mainFrame, new FileSet(remoteJarFile.getParent(), remoteJarFile),\n                getTempJarFolder(), TEMP_JAR_FILENAME, CopyJob.Mode.DOWNLOAD, FileCollisionDialog.OVERWRITE_ACTION);\n    }\n\n\n    private static AbstractFile getApplicationJarFile() {\n        return ResourceLoader.getRootPackageAsFile(SelfUpdateJob.class);\n    }\n\n    private static AbstractFile getTempJarFolder() {\n        return getApplicationJarFile().getParent();\n    }\n\n\n\n//    @Override\n//    protected void jobStarted() {\n//        super.jobStarted();\n//        try {\n////            // Loads all classes from the JAR file before the new JAR file is installed.\n////            // This will ensure that the shutdown sequence, which invokes so not-yet-loaded classes goes down smoothly.\n////            loadClassRecurse(destJar);\n////            loadingClasses = false;\n//        } catch(Exception e) {\n//            LOGGER.debug(\"Caught exception\", e);\n//            // TODO: display an error message\n//            interrupt();\n//        }\n//    }\n\n    @Override\n    protected void jobCompleted() {\n        System.out.println(\"JOB COMPLETED\");\n        System.out.println(SelfUpdateUtils.extractRestarter());\n        System.out.println(SelfUpdateUtils.checkRestarter());\n        System.out.println(\"SelfUpdateUtils.updateAndRestart()\");\n        SelfUpdateUtils.updateAndRestart();\n        System.out.println(\"WindowManager.quit()\");\n        WindowManager.quit();\n        System.out.println(\"System.exit(0)\");\n        System.exit(0);\n        //System.out.println(SelfUpdateUtils.getTcExecutionCommand());\n\n//        try {\n//            // Mac OS X\n//            if (OsFamily.MAC_OS_X.isCurrent() && executeOnMacOsX()) {\n//                return;\n//            } else if (executeDefault()) {\n//                return;\n//            }\n//\n//            // No platform-specific launcher found, launch the Jar directly\n//            ProcessRunner.execute(new String[]{\"java\", \"-jar\", destJar.getAbsolutePath()});\n//        } catch(IOException e) {\n//            LOGGER.debug(\"Caught exception\", e);\n//            // TODO: we might want to do something about this\n//        } finally {\n//            WindowManager.quit();\n//        }\n    }\n\n\n\n//    @Override\n//    protected boolean processFile(AbstractFile file, Object recurseParams) {\n//        if (!super.processFile(file, recurseParams)) {\n//            return false;\n//        }\n//    }\n\n    @Override\n    public String getStatusString() {\n        return Translator.get(\"version_dialog.preparing_for_update\");\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/SendMailJob.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.job;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.MimeTypes;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.commons.io.StreamUtils;\nimport com.mucommander.commons.io.base64.Base64OutputStream;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.file.ProgressDialog;\nimport com.mucommander.ui.main.MainFrame;\n\nimport java.io.*;\nimport java.net.Socket;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.StringTokenizer;\n\n\n/**\n * This job sends one or several files by email.\n *\n * @author Maxence Bernard\n */\npublic class SendMailJob extends TransferFileJob {\n\n    /** True after connection to mail server has been established */\n    private boolean connectedToMailServer;\n\n    /** Error dialog title */\n    private final String errorDialogTitle;\n\t\n\t\n    /////////////////////\n    // Mail parameters //\n    /////////////////////\n\n    /** Email recipient(s) */\n    private String recipientString;\n    /** Email subject */\n    private final String mailSubject;\n    /** Email body */\n    private final String mailBody;\n\n    /** SMTP server */\n    private final String mailServer;\n    /** From name */\n    private final String fromName;\n    /** From address */\n    private final String fromAddress;\n\t\n    /** Email boundary string, delimits the end of the body and attachments */\n    private final String boundary;\n\n    /** Connection variable */\n    private BufferedReader in;\n    /** OutputStream to the SMTP server */\n    private OutputStream out;\n    /** Base64OutputStream to the SMTP server */\n    private Base64OutputStream out64;\n    /** Socket connection to the SMTP server */\n    private Socket socket;\n\n\t\n    private final static String CLOSE_TEXT = Translator.get(\"close\");\n    private final static int CLOSE_ACTION = 11;\n\t\n\t\n    public SendMailJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet filesToSend, String recipientString, String mailSubject, String mailBody) {\n        super(progressDialog, mainFrame, filesToSend);\n\n        this.boundary = \"trolcommander\" + System.currentTimeMillis();\n        this.recipientString = recipientString;\n        this.mailSubject = mailSubject;\n        this.mailBody = mailBody+\"\\n\\n\"+\"Sent by trolCommander - http://www.trolsoft.ru\\n\";\n\n        this.mailServer = TcConfigurations.getPreferences().getVariable(TcPreference.SMTP_SERVER);\n        this.fromName = TcConfigurations.getPreferences().getVariable(TcPreference.MAIL_SENDER_NAME);\n        this.fromAddress = TcConfigurations.getPreferences().getVariable(TcPreference.MAIL_SENDER_ADDRESS);\n    \n        this.errorDialogTitle = Translator.get(\"email_dialog.error_title\");\n    }\n\n    /**\n     * Returns true if mail preferences have been set.\n     */\n    public static boolean mailPreferencesSet() {\n        return TcConfigurations.getPreferences().isVariableSet(TcPreference.SMTP_SERVER)\n            && TcConfigurations.getPreferences().isVariableSet(TcPreference.MAIL_SENDER_NAME)\n            && TcConfigurations.getPreferences().isVariableSet(TcPreference.MAIL_SENDER_ADDRESS);\n    }\n\n\n    /**\n     * Shows an error dialog with a single action : close, and stops the job.\n     */\n    private void showErrorDialog(String message) {\n        showErrorDialog(errorDialogTitle, message, new String[]{CLOSE_TEXT}, new int[]{CLOSE_ACTION});\n        interrupt();\n    }\n\t\n\t\n    /////////////////////////////////////////////\n    // Methods taking care of sending the mail //\n    /////////////////////////////////////////////\n\n    private void openConnection() throws IOException {\n        this.socket = new Socket(mailServer, TcConfigurations.getPreferences().getVariable(TcPreference.SMTP_PORT, TcPreferences.DEFAULT_SMTP_PORT));\n        this.in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));\n        this.out = socket.getOutputStream();\n        this.out64 = new Base64OutputStream(out, true);\n\t\t\n        this.connectedToMailServer = true;\n    }\n\n    private void sendBody() throws IOException {\n        // here you are supposed to send your username\n        readWriteLine(\"HELO trolCommander\");\n        // warning : some mail server validate the sender address and will fail is an invalid\n        // address is provided\n        readWriteLine(\"MAIL FROM: \"+fromAddress);\n\t\t\n        List<String> recipients = new ArrayList<>();\n        recipientString = splitRecipientString(recipientString, recipients);\n        for (String recipient : recipients)\n            readWriteLine(\"RCPT TO: <\" + recipient + \">\");\n        readWriteLine(\"DATA\");\n        writeLine(\"MIME-Version: 1.0\");\n        writeLine(\"Subject: \"+this.mailSubject);\n        writeLine(\"From: \"+this.fromName+\" <\"+this.fromAddress+\">\");\n        writeLine(\"To: \"+recipientString);\n        writeLine(\"Content-Type: multipart/mixed; boundary=\\\"\" + boundary +\"\\\"\");\n        writeLine(\"\\r\\n--\" + boundary);\n\n        // Send the body\n        //        writeLine( \"Content-Type: text/plain; charset=\\\"us-ascii\\\"\\r\\n\");\n        writeLine(\"Content-Type: text/plain; charset=\\\"utf-8\\\"\\r\\n\");\n        writeLine(this.mailBody+\"\\r\\n\\r\\n\");\n        writeLine(\"\\r\\n--\" +  boundary );        \n    }\n    \n\n    /**\n     * Parses the specified string, replaces delimiter characters if needed and adds recipients  (String instances) to the given Vector.\n     *\n     * @param recipientsStr String containing one or several recipients that need to be separated by ',' and/or ';' characters.\n     */\n    private String splitRecipientString(String recipientsStr, List<String> recipients) {\n\n        // /!\\ this piece of code is far from being bulletproof, but I'm too lazy now to rewrite it\n        StringBuilder newRecipientsSb = new StringBuilder();\n        StringTokenizer st = new StringTokenizer(recipientsStr, \",;\");\n        while(st.hasMoreTokens()) {\n            String rec = st.nextToken().trim();\n            int pos1, pos2;\n            if ((pos1 = rec.indexOf('<')) != -1 && (pos2 = rec.indexOf('>', pos1+1)) != -1) {\n                recipients.add(rec.substring(pos1+1, pos2));\n            } else {\n                recipients.add(rec);\n            }\n\n            newRecipientsSb.append(rec);\n            if (st.hasMoreTokens()) {\n                newRecipientsSb.append(\", \");\n            }\n        }\n\t\t\n        return newRecipientsSb.toString();\n    }\n\t\n\t\n    /**\n     * Send file as attachment encoded in Base64, and returns true if file was successfully\n     * and completely transferred.\n     */ \n    private void sendAttachment(AbstractFile file) throws IOException {\n        // Send MIME type of attachment file\n        String mimeType = MimeTypes.getMimeType(file);\n        // Default mime type\n        if (mimeType == null) {\n            mimeType = \"application/octet-stream\";\n        }\n        writeLine(\"Content-Type:\"+mimeType+\"; name=\"+file.getName());\n        writeLine(\"Content-Disposition: attachment;filename=\\\"\"+file.getName()+\"\\\"\");\n        writeLine(\"Content-transfer-encoding: base64\\r\\n\");\n\n        try (InputStream fileIn = setCurrentInputStream(file.getInputStream())) {\n            // Write file to socket\n            StreamUtils.copyStream(fileIn, out64);\n\n            // Writes padding bytes without closing the stream.\n            out64.writePadding();\n\n            writeLine(\"\\r\\n--\" + boundary);\n        }\n    }\n\t\n    private void sayGoodBye() throws IOException {\n        writeLine(\"\\r\\n\\r\\n--\" + boundary + \"--\\r\\n\");\n        readWriteLine(\".\");\n        readWriteLine(\"QUIT\");\n    }\n\n\n    private void closeConnection() {\n        try {\n            socket.close();\n            in.close();\n            out64.close();\n        } catch(Exception ignore) {}\n    }\n    \n    private void readWriteLine(String s) throws IOException {\n        out.write((s + \"\\r\\n\").getBytes(StandardCharsets.UTF_8));\n        in.readLine();\n    }\n\n    private void writeLine(String s) throws IOException {\n        out.write((s + \"\\r\\n\").getBytes(StandardCharsets.UTF_8));\n    }\n\n\n    ////////////////////////////////////\n    // TransferFileJob implementation //\n    ////////////////////////////////////\n\n    @Override\n    protected boolean processFile(AbstractFile file, Object recurseParams) {\n        if (getState() == State.INTERRUPTED)\n            return false;\n\n        // Send file attachment\n        try {\n            sendAttachment(file);\n        } catch(IOException e) {\n            showErrorDialog(Translator.get(\"email.send_file_error\", file.getName()));\n            return false;\n        }\n\n        // If this was the last file, notify the mail server that the mail is over\n        if(getCurrentFileIndex()==getNbFiles()-1) {\n            try {\n                // Say goodbye to the server\n                sayGoodBye();\n            }\n            catch(IOException e) {\n                showErrorDialog(Translator.get(\"email.goodbye_failed\"));\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    @Override\n    protected boolean hasFolderChanged(AbstractFile folder) {\n        // This job does not modify anything\n        return false;\n    }\n\n\n    ///////////////////////\n    // Overridden method //\n    ///////////////////////\n\n    /**\n     * This method is called when this job starts, before the first call to {@link #processFile(AbstractFile,Object) processFile()} is made.\n     * This method here does nothing, but it can be overridden by subclasses to perform some first-time initializations.\n     */\n    @Override\n    protected void jobStarted() {\n        super.jobStarted();\n\n        // Open socket connection to the mail server, and say hello\n        try {\n            openConnection();\n        } catch(IOException e) {\n            showErrorDialog(Translator.get(\"email.server_unavailable\", mailServer));\n        }\n\n        if (getState() == State.INTERRUPTED)\n            return;\n\n        // Send mail body\n        try {\n            sendBody();\n        } catch(IOException e) {\n            showErrorDialog(Translator.get(\"email.connection_closed\"));\n        }\n    }\n\n\n    /**\n     * Method overridden to close connection to the mail server.\n     */\n    @Override\n    protected void jobStopped() {\n        super.jobStopped();\n\n        // Close the connection\n        closeConnection();\n    }\n\n    @Override\n    public String getStatusString() {\n        if (connectedToMailServer) {\n            return Translator.get(\"email.sending_file\", getCurrentFilename());\n        } else {\n            return Translator.get(\"email.connecting_to_server\", mailServer);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/SplitFileJob.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.job;\r\n\r\nimport java.io.IOException;\r\nimport java.io.InputStream;\r\nimport java.io.OutputStream;\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.security.MessageDigest;\r\nimport java.security.NoSuchAlgorithmException;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.DummyFile;\r\nimport com.mucommander.commons.file.FileOperation;\r\nimport com.mucommander.commons.file.FilePermissions;\r\nimport com.mucommander.commons.file.FileURL;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.commons.io.BufferPool;\r\nimport com.mucommander.commons.io.ChecksumInputStream;\r\nimport com.mucommander.commons.io.FileTransferException;\r\nimport com.mucommander.commons.io.StreamUtils;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.action.ActionProperties;\r\nimport com.mucommander.ui.action.impl.SplitFileAction;\r\nimport com.mucommander.ui.dialog.file.FileCollisionDialog;\r\nimport com.mucommander.ui.dialog.file.ProgressDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\n/**\r\n * This job split the file into parts with given size.\r\n * @author Mariusz Jakubowski\r\n */\r\npublic class SplitFileJob extends AbstractCopyJob {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(SplitFileJob.class);\r\n\t\r\n    private final long partSize;\r\n\tprivate final AbstractFile sourceFile;\r\n\tprivate InputStream origFileStream;\r\n\tprivate final AbstractFile destFolder;\r\n\tprivate long sizeLeft;\r\n\tprivate boolean recalculateCRC = false;\r\n\r\n\r\n\t/**\r\n\t * A class for holding file name and size of one part.\r\n\t * @author Mariusz Jakubowski\r\n\t *\r\n\t */\r\n\tprivate static class DummyDestFile extends DummyFile {\r\n\t\tprivate final long size;\r\n\t\t\r\n\t\tpublic DummyDestFile(FileURL url, long size) {\r\n\t\t\tsuper(url);\r\n\t\t\tthis.size = size;\r\n\t\t}\r\n\t\t\r\n\t\t@Override\r\n        public long getSize() {\r\n\t\t\treturn size;\r\n\t\t}\r\n\t}\r\n\r\n\t\r\n\tpublic SplitFileJob(ProgressDialog progressDialog, MainFrame mainFrame, \r\n\t\t\tAbstractFile file, AbstractFile destFolder, long partSize, int parts) {\r\n        super(progressDialog, mainFrame, new FileSet(), destFolder, null, FileCollisionDialog.ASK_ACTION);\r\n        this.partSize = partSize;\r\n        this.setNbFiles(parts);\r\n        this.sourceFile = file;\r\n        this.destFolder = destFolder;\r\n        this.errorDialogTitle = Translator.get(\"split_file_dialog.error_title\");\r\n        createInputStream();\r\n        sizeLeft = sourceFile.getSize();\r\n        for (int i=1; i<=parts; i++) {\r\n        \taddDummyFile(i, Math.min(partSize, sizeLeft));\r\n        \tsizeLeft -= partSize;\r\n        }\r\n        sizeLeft = sourceFile.getSize();\r\n    }\r\n\r\n\t/**\r\n\t * Adds a dummy output file (used in progress monitoring). \r\n\t * @param i index of a file\r\n\t * @param size size of a file\r\n\t */\r\n\tprivate void addDummyFile(int i, long size) {\r\n    \tString num;\r\n    \tif (i < 10) {\r\n    \t\tnum = \"00\" + i;\r\n    \t} else if (i < 100) {\r\n    \t\tnum = \"0\" + i;\r\n    \t} else {\r\n    \t\tnum = Integer.toString(i); \r\n    \t}\r\n        FileURL childURL = (FileURL)destFolder.getURL().clone();\r\n        childURL.setPath(destFolder.addTrailingSeparator(childURL.getPath()) + sourceFile.getName() + \".\" + num);\r\n    \tDummyDestFile fileHolder = new DummyDestFile(childURL, size);\r\n    \tfiles.add(fileHolder);\r\n\t}\r\n\t\r\n\t\r\n\t@Override\r\n    protected void jobStarted() {\r\n\t\tsuper.jobStarted();\r\n\t\tcreateInputStream();\r\n\t}\r\n\r\n\t/**\r\n\t * Creates an input stream from the file. \r\n\t */\r\n\tprivate void createInputStream() {\r\n        try {\r\n        \torigFileStream = sourceFile.getInputStream();\r\n\t\t}\r\n        catch (IOException e) {\r\n            LOGGER.debug(\"Caught exception\", e);\r\n            showErrorDialog(errorDialogTitle,\r\n                    Translator.get(\"error_while_transferring\", sourceFile.getName()),\r\n                    new String[]{CANCEL_TEXT},\r\n                    new int[]{CANCEL_ACTION}\r\n                    );\r\n            setState(State.INTERRUPTED);\r\n            return;\r\n\t\t}\r\n        origFileStream = setCurrentInputStream(origFileStream);\r\n        // init checksum calculation\r\n        if (isIntegrityCheckEnabled()) {\r\n\t\t\ttry {\r\n\t\t\t\torigFileStream = new ChecksumInputStream(origFileStream, MessageDigest.getInstance(\"CRC32\"));\r\n\t\t\t} catch (NoSuchAlgorithmException e) {\r\n\t\t\t\tsetIntegrityCheckEnabled(false);\r\n                LOGGER.debug(\"Caught exception\", e);\r\n\t\t\t}\r\n        }\r\n\t}\r\n\t\r\n\r\n    @Override\r\n    protected boolean processFile(AbstractFile file, Object recurseParams) {\r\n        if (getState() == State.INTERRUPTED)\r\n            return false;\r\n        \r\n        // create destination AbstractFile instance\r\n        AbstractFile destFile = createDestinationFile(baseDestFolder, file.getName());\r\n        if (destFile == null)\r\n            return false;\r\n\r\n        destFile = checkForCollision(sourceFile, baseDestFolder, destFile, false);\r\n        if (destFile == null)\r\n            return false;\r\n        \r\n        OutputStream out = null;\r\n        try {\r\n\t\t\tout = destFile.getOutputStream();\r\n\r\n\t\t\ttry {\r\n\t\t\t\tlong written = StreamUtils.copyStream(origFileStream, out, BufferPool.getDefaultBufferSize(), partSize);\r\n\t\t\t\tsizeLeft -= written;\r\n\t\t\t} catch (FileTransferException e) {\r\n\t\t\t\tif (e.getReason() == FileTransferException.WRITING_DESTINATION) {\r\n\t\t\t\t\t// out of disk space - ask a user for a new disk\r\n\t\t\t\t\trecalculateCRC = true;\t\t// recalculate CRC (DigestInputStream doesn't support mark() and reset())\r\n\t\t\t\t\tout.close();\r\n\t\t\t\t\tout = null;\r\n\t\t\t\t\tsizeLeft -= e.getBytesWritten();\r\n\t\t\t\t\tshowErrorDialog(ActionProperties.getActionLabel(SplitFileAction.Descriptor.ACTION_ID),\r\n\t\t\t\t\t\t\tTranslator.get(\"split_file_dialog.insert_new_media\"), \r\n\t\t\t\t\t\t\tnew String[]{OK_TEXT, CANCEL_TEXT}, \r\n\t\t\t\t\t\t\tnew int[]{OK_ACTION, CANCEL_ACTION});\r\n\t\t\t\t\tif (getState() == State.INTERRUPTED) {\r\n\t\t\t\t\t\treturn false;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// create new output file if necessary\r\n\t\t\t\t\tif ((sizeLeft>0) && (getCurrentFileIndex() == getNbFiles()-1)) {\r\n\t\t\t\t\t\tsetNbFiles(getNbFiles() + 1);\r\n\t\t\t\t\t\taddDummyFile(getNbFiles(), sizeLeft);\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthrow e;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t        // Preserve source file's date\r\n            if(destFile.isFileOperationSupported(FileOperation.CHANGE_DATE)) {\r\n                try {\r\n                    destFile.setLastModifiedDate(sourceFile.getLastModifiedDate());\r\n                }\r\n                catch (IOException e) {\r\n                    LOGGER.debug(\"failed to change date of \"+destFile, e);\r\n                    // Fail silently\r\n                }\r\n            }\r\n\r\n\t        // Preserve source file's permissions: preserve only the permissions bits that are supported by the source\r\n            // file and use default permissions for the rest of them.\r\n            if(destFile.isFileOperationSupported(FileOperation.CHANGE_PERMISSION)) {\r\n                try {\r\n                    // use #importPermissions(AbstractFile, int) to avoid isDirectory test\r\n                    destFile.importPermissions(sourceFile, FilePermissions.DEFAULT_FILE_PERMISSIONS);\r\n                }\r\n                catch (IOException e) {\r\n                    LOGGER.debug(\"failed to import \"+sourceFile+\" permissions into \"+destFile, e);\r\n                    // Fail silently\r\n                }\r\n            }\r\n\t\t} catch (IOException e) {\r\n            LOGGER.debug(\"Caught exception\", e);\r\n\r\n            showErrorDialog(errorDialogTitle,\r\n                    Translator.get(\"error_while_transferring\", destFile.getName()),\r\n                    new String[]{CANCEL_TEXT},\r\n                    new int[]{CANCEL_ACTION}\r\n                    );\r\n\t\t\treturn false;\r\n\t\t\t\r\n\t\t} finally {\r\n            try {\r\n            \tif (out != null)\r\n            \t\tout.close();\r\n            } catch(IOException ignore) {\r\n            }\r\n\t\t}\r\n\r\n    \treturn true;\r\n    }\r\n\r\n\r\n    // This job modifies baseDestFolder and its subfolders\r\n    @Override\r\n    protected boolean hasFolderChanged(AbstractFile folder) {\r\n        return baseDestFolder.isParentOf(folder);\r\n    }\r\n    \r\n    @Override\r\n    protected void jobCompleted() {\r\n    \t// create checksum file\r\n    \tif (isIntegrityCheckEnabled()) {\r\n            if(origFileStream!=null && (origFileStream instanceof ChecksumInputStream)) {\r\n            \tString crcFileName = sourceFile.getName() + \".sfv\";\r\n                try {\r\n\t            \tString sourceChecksum;\r\n\t            \tif (recalculateCRC ) {\r\n\t            \t\torigFileStream = sourceFile.getInputStream();\r\n\t\t\t\t\t\tsourceChecksum = AbstractFile.calculateChecksum(origFileStream, MessageDigest.getInstance(\"CRC32\"));\r\n\t\t\t\t\t\torigFileStream.close();\r\n\t            \t} else {\r\n\t                \tsourceChecksum = ((ChecksumInputStream)origFileStream).getChecksumString();\r\n\t            \t}\r\n\t\t\t\t\tAbstractFile crcFile = baseDestFolder.getDirectChild(crcFileName);\r\n\t\t\t\t\tOutputStream crcStream = crcFile.getOutputStream();\r\n\t\t\t\t\tString line = sourceFile.getName() + \" \" + sourceChecksum;\r\n\t\t\t\t\tcrcStream.write(line.getBytes(StandardCharsets.UTF_8));\r\n\t\t\t\t\tcrcStream.close();\r\n\t\t\t\t} catch (Exception e) {\r\n                    LOGGER.debug(\"Caught exception\", e);\r\n\t\t\t\t\t\r\n\t\t            showErrorDialog(errorDialogTitle,\r\n\t\t                    Translator.get(\"error_while_transferring\", crcFileName),\r\n\t\t                    new String[]{CANCEL_TEXT},\r\n\t\t                    new int[]{CANCEL_ACTION}\r\n\t\t                    );\r\n\t\t\t\t}\r\n            }\r\n    \t}\r\n    \tsuper.jobCompleted();\r\n    }\r\n\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/TempCopyJob.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.job;\n\nimport java.io.IOException;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.ui.dialog.file.FileCollisionDialog;\nimport com.mucommander.ui.dialog.file.ProgressDialog;\nimport com.mucommander.ui.main.MainFrame;\n\n/**\n * This job copies a file or a set of files to a temporary folder and makes the temporary file(s) read-only.\n * The temporary files are deleted when the JVM terminates.\n *\n * @author Maxence Bernard\n */\npublic class TempCopyJob extends CopyJob {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(TempCopyJob.class);\n\t\n    /**\n     * Creates a new <code>TempExecJob</code> that operates on a single file.\n     *\n     * @param progressDialog the ProgressDialog that monitors this job\n     * @param mainFrame the MainFrame this job is attached to\n     * @param fileToCopy the file to copy to a temporary location\n     */\n    public TempCopyJob(ProgressDialog progressDialog, MainFrame mainFrame, AbstractFile fileToCopy) {\n        super(progressDialog, mainFrame, new FileSet(fileToCopy.getParent(), fileToCopy), FileFactory.getTemporaryFolder(), getTemporaryFileName(fileToCopy), Mode.COPY, FileCollisionDialog.OVERWRITE_ACTION);\n    }\n\n    /**\n     * Creates a new <code>TempExecJob</code> that operates on a single file.\n     *\n     * @param progressDialog the ProgressDialog that monitors this job\n     * @param mainFrame the MainFrame this job is attached to\n     * @param filesToCopy the file to copy to a temporary location\n     */\n    public TempCopyJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet filesToCopy) {\n        super(progressDialog, mainFrame, filesToCopy, getTemporaryFolder(filesToCopy), null, Mode.COPY, FileCollisionDialog.OVERWRITE_ACTION);\n    }\n\n\n    protected static AbstractFile getTemporaryFolder(FileSet files) {\n        AbstractFile tempFolder;\n        try {\n            tempFolder = FileFactory.getTemporaryFile(files.getBaseFolder().getName(), true);\n            tempFolder.mkdir();\n        }\n        catch(IOException e) {\n            tempFolder = FileFactory.getTemporaryFolder();\n        }\n\n        return tempFolder;\n    }\n\n    protected static String getTemporaryFileName(AbstractFile files) {\n        try {\n            return FileFactory.getTemporaryFile(files.getName(), true).getName();\n        }\n        catch(IOException e) {\n            // Should never happen under normal circumstances.\n            LOGGER.warn(\"Caught exception instanciating temporary file, this should not happen!\");\n            return files.getName();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/TempExecJob.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.job;\n\nimport java.io.IOException;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.PermissionAccesses;\nimport com.mucommander.commons.file.PermissionTypes;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.desktop.DesktopManager;\nimport com.mucommander.ui.dialog.file.ProgressDialog;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.quicklist.RecentExecutedFilesQL;\n\n/**\n * This job copies a file or a set of files to a temporary folder, makes the temporary file(s) read-only and\n * executes each of them with native file associations. The temporary files are deleted when the JVM terminates.\n *\n * <p>It is important to understand that when this job operates on a set of files, a process is started for each file\n * to execute, so this operation should require confirmation by the user before being attempted.\n *\n * @author Maxence Bernard\n */\npublic class TempExecJob extends TempCopyJob {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(TempExecJob.class);\n\t\n    /** Files to execute */\n    private final FileSet filesToExecute;\n\n    /**\n     * Creates a new <code>TempExecJob</code> that operates on a single file.\n     *\n     * @param progressDialog the ProgressDialog that monitors this job\n     * @param mainFrame the MainFrame this job is attached to\n     * @param fileToExecute the file to copy to a temporary location and execute\n     */\n    public TempExecJob(ProgressDialog progressDialog, MainFrame mainFrame, AbstractFile fileToExecute) {\n        this(progressDialog, mainFrame, new FileSet(fileToExecute.getParent(), fileToExecute));\n    }\n\n    /**\n     * Creates a new <code>TempExecJob</code> that operates on a set of files. Only a single command get executed, operating on\n     * all files.\n     *\n     * @param progressDialog the ProgressDialog that monitors this job\n     * @param mainFrame the MainFrame this job is attached to\n     * @param filesToExecute the set of files to copy to a temporary location and execute\n     */\n    public TempExecJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet filesToExecute) {\n        super(progressDialog, mainFrame, filesToExecute);\n\n        this.filesToExecute = filesToExecute;\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    @Override\n    protected boolean processFile(AbstractFile file, Object recurseParams) {\n        if (!super.processFile(file, recurseParams)) {\n            return false;\n        }\n\n        // TODO: temporary files seem to remain after the JVM quits under Mac OS X, even if the files permissions are unchanged\n\n        // Execute the file, only if it is one of the top-level files\n        if (!filesToExecute.contains(file)) {\n            return true;\n        }\n        if (!currentDestFile.isDirectory()) {        // Do not change directories' permissions\n            try {\n                // Make the temporary file read only\n                if (currentDestFile.getChangeablePermissions().getBitValue(PermissionAccesses.USER_ACCESS, PermissionTypes.WRITE_PERMISSION))\n                    currentDestFile.changePermission(PermissionAccesses.USER_ACCESS, PermissionTypes.WRITE_PERMISSION, false);\n            } catch(IOException e) {\n                LOGGER.debug(\"Caught exception while changing permissions of {}\", currentDestFile, e);\n                return false;\n            }\n        }\n        return openFile(file);\n    }\n\n    private boolean openFile(AbstractFile file) {\n        try {\n            DesktopManager.open(currentDestFile);\n            RecentExecutedFilesQL.addFile(file);\n            return true;\n        } catch(Exception e) {\n            LOGGER.debug(\"Caught exception while opening {}\", currentDestFile, e);\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/TempOpenWithJob.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.job;\n\nimport java.io.IOException;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.command.Command;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.PermissionAccesses;\nimport com.mucommander.commons.file.PermissionTypes;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.process.ProcessRunner;\nimport com.mucommander.ui.dialog.file.ProgressDialog;\nimport com.mucommander.ui.main.MainFrame;\n\n/**\n * This job copies a file or a set of files to a temporary folder, makes the temporary file(s) read-only and\n * executes them with a specific command. The temporary files are deleted when the JVM terminates.\n *\n * <p>It is important to understand that when this job operates on a set of files, a process is started for each file\n  * to execute, so this operation should require confirmation by the user before being attempted.\n  *\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic class TempOpenWithJob extends TempCopyJob {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(TempOpenWithJob.class);\n\t\n    /** The command to execute, appended with the temporary file path(s) */\n    private final Command command;\n\n    /** Files to execute */\n    private final FileSet filesToOpen;\n\n    /** This list is populated with temporary files, as they are created by processFile() */\n    private final FileSet tempFiles;\n\n\n    /**\n     * Creates a new <code>TempOpenWithJob</code> that operates on a single file.\n     *\n     * @param progressDialog the ProgressDialog that monitors this job\n     * @param mainFrame the MainFrame this job is attached to\n     * @param fileToOpen the file to copy to a temporary location and execute\n     * @param command the command used to execute the temporary file\n     */\n    public TempOpenWithJob(ProgressDialog progressDialog, MainFrame mainFrame, AbstractFile fileToOpen, Command command) {\n        this(progressDialog, mainFrame, new FileSet(fileToOpen.getParent(), fileToOpen), command);\n    }\n\n    /**\n     * Creates a new <code>TempOpenWithJob</code> that operates on a set of files. Only a single command get executed, operating on\n     * all files.\n     *\n     * @param progressDialog the ProgressDialog that monitors this job\n     * @param mainFrame the MainFrame this job is attached to\n     * @param filesToOpen the set of files to copy to a temporary location and execute\n     * @param command the command used to execute the temporary file\n     */\n    public TempOpenWithJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet filesToOpen, Command command) {\n        super(progressDialog, mainFrame, filesToOpen);\n        this.command  = command;\n        this.filesToOpen = filesToOpen;\n        tempFiles = new FileSet(baseDestFolder);\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    @Override\n    protected boolean processFile(AbstractFile file, Object recurseParams) {\n        if(!super.processFile(file, recurseParams))\n            return false;\n\n        // TODO: temporary files seem to be left after the JVM quits under Mac OS X, even if the files permissions are unchanged\n\n        // Add the file to the list of files to open, only if it is one of the top-level files\n        if(filesToOpen.contains(file)) {\n            if(!currentDestFile.isDirectory()) {        // Do not change directories' permissions\n                try {\n                    // Make the temporary file read only\n                    if(currentDestFile.getChangeablePermissions().getBitValue(PermissionAccesses.USER_ACCESS, PermissionTypes.WRITE_PERMISSION))\n                        currentDestFile.changePermission(PermissionAccesses.USER_ACCESS, PermissionTypes.WRITE_PERMISSION, false);\n                } catch(IOException e) {\n                    LOGGER.debug(\"Caught exception while changing permissions of {}\", currentDestFile, e);\n                    return false;\n                }\n            }\n            \n            tempFiles.add(currentDestFile);\n        }\n\n        return true;\n    }\n\n    @Override\n    protected void jobCompleted() {\n        super.jobCompleted();\n\n        try {\n            ProcessRunner.execute(command.getTokens(tempFiles), baseDestFolder);\n        } catch(Exception e) {\n            LOGGER.debug(\"Caught exception executing {} {}\", command, tempFiles, e);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/TransferFileJob.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\n\r\npackage com.mucommander.job;\r\n\r\nimport java.io.File;\r\nimport java.io.IOException;\r\nimport java.io.InputStream;\r\nimport java.nio.file.Files;\r\nimport java.nio.file.Path;\r\nimport java.security.MessageDigest;\r\nimport java.security.NoSuchAlgorithmException;\r\n\r\nimport com.mucommander.commons.file.*;\r\nimport com.mucommander.commons.file.impl.adb.AdbFile;\r\nimport com.mucommander.desktop.DesktopManager;\r\nimport lombok.Getter;\r\nimport lombok.Setter;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport com.apple.eio.FileManager;\r\nimport com.mucommander.commons.file.impl.local.LocalFile;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.commons.io.ByteCounter;\r\nimport com.mucommander.commons.io.ChecksumInputStream;\r\nimport com.mucommander.commons.io.CounterInputStream;\r\nimport com.mucommander.commons.io.FileTransferException;\r\nimport com.mucommander.commons.io.ThroughputLimitInputStream;\r\nimport com.mucommander.commons.io.security.MuProvider;\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.dialog.file.ProgressDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\n\r\n/**\r\n * TransferFileJob is a container for a file getTask : basically an operation that involves files and bytes.<br>\r\n *\r\n * <p>What makes TransferFileJob different from FileJob (and explains its very inspired name) is that a class\r\n * implementing TransferFileJob has to be able to give progress information about the file currently being processed.\r\n * \r\n * @author Maxence Bernard\r\n */\r\npublic abstract class TransferFileJob extends FileJob {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(TransferFileJob.class);\r\n\t\r\n    /** Contains the number of bytes processed in the current file so far, see {@link #getCurrentFileByteCounter()} ()}\r\n     * -- GETTER --\r\n     *  Returns the number of bytes that have been processed in the current file.\r\n     */\r\n    @Getter\r\n    private final ByteCounter currentFileByteCounter;\r\n\r\n    /** Contains the number of bytes skipped in the current file so far, see {@link #getCurrentFileSkippedByteCounter()} ()} */\r\n    private final ByteCounter currentFileSkippedByteCounter;\r\n\r\n    /** Contains the number of bytes processed so far, see {@link #getTotalByteCounter()}\r\n     * -- GETTER --\r\n     *  Returns a that holds the total number of bytes that have been processed by this job so far.\r\n     */\r\n    @Getter\r\n    private final ByteCounter totalByteCounter;\r\n\r\n    /** Contains the number of bytes skipped so far (resumed files), see {@link #getTotalSkippedByteCounter()}\r\n     * -- GETTER --\r\n     *  Returns a\r\n     *  that holds the total number of bytes that have been skipped by this job so far.\r\n     *  Bytes are skipped when file transfers are resumed.\r\n     */\r\n    @Getter\r\n    private final ByteCounter totalSkippedByteCounter;\r\n\r\n    /** InputStream currently being processed, may be null */\r\n    private ThroughputLimitInputStream tlin;\r\n\r\n    /** ThroughputLimit in bytes per second, -1 initially (no limit)\r\n     * -- GETTER --\r\n     *  Returns the current transfer throughput limit, in bytes per second. <code>0</code> or <code>-1</code> means that\r\n     *  there currently is no limit to the attainable transfer speed (full speed).\r\n     */\r\n    @Getter\r\n    private long throughputLimit = -1;\r\n\r\n    /** Has the file currently being processed been skipped ? */\r\n    private boolean currentFileSkipped;\r\n\r\n    /** If true, all transfers will be checked for integrity: the checksum of the source and destination file will\r\n     *  be calculated and compared to verify they match.\r\n     * -- SETTER --\r\n     *  Specifies if file transfers need to be checked for data integrity. If <code>true</code> is specified, the\r\n     *  checksum of the source and destination files will both be calculated and compared to verify they match.\r\n     */\r\n    @Setter\r\n    private boolean integrityCheckEnabled;\r\n\r\n    /** True when the checksum of the source or destination file is being calculated. */\r\n    private boolean isCheckingIntegrity;\r\n\r\n    /** The checksum algorithm used for checking the integrity of transferred files. The algorithm has to be the fastest\r\n     * possible (to have the minimum impact on transfer speed) and does not need to have a good resitance to collision. */\r\n    private final static String CHECKSUM_VERIFICATION_ALGORITHM = \"Adler32\";\r\n\r\n    /**\r\n     * If user changed \"Overwrite all readonly\" in the question dialog\r\n     */\r\n    private boolean overwriteAllReadonly = false;\r\n\r\n\r\n    static {\r\n        // Register additional MessageDigest implementations provided by the muCommander API\r\n        MuProvider.registerProvider();\r\n    }\r\n\r\n    /**\r\n     * Creates a new TransferFileJob.\r\n     *\r\n     * @param progressDialog dialog which shows this job's progress\r\n     * @param mainFrame mainFrame this job has been triggered by\r\n     * @param files files which are going to be processed\r\n     */\r\n    public TransferFileJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet files) {\r\n        super(progressDialog, mainFrame, files);\r\n\r\n        this.currentFileByteCounter = new ByteCounter();\r\n        this.currentFileSkippedByteCounter = new ByteCounter();\r\n\r\n        // Account the current file's byte counter in the total byte counter\r\n        this.totalByteCounter = new ByteCounter(currentFileByteCounter);\r\n        this.totalSkippedByteCounter = new ByteCounter(currentFileSkippedByteCounter);\r\n    }\r\n\r\n    void copyToReadonlyFile(AbstractFile sourceFile, AbstractFile destFile, boolean append) throws FileTransferException {\r\n        try {\r\n            destFile.changePermission(PermissionAccesses.USER_ACCESS, PermissionTypes.WRITE_PERMISSION, true);\r\n            copyFile(sourceFile, destFile, append);\r\n            destFile.changePermission(PermissionAccesses.USER_ACCESS, PermissionTypes.WRITE_PERMISSION, false);\r\n        } catch (IOException e) {\r\n            LOGGER.error(\"Copy error\", e);\r\n            throw new FileTransferException(FileTransferException.OPENING_DESTINATION);\r\n        }\r\n    }\r\n\t\r\n\t\r\n    /**\r\n     * Copies the given source file to the specified destination file, optionally resuming the operation.\r\n     * As much as the source and destination protocols allow, the source file's date and permissions will be preserved.\r\n     *\r\n     * @param sourceFile source file\r\n     * @param destFile destination file\r\n     * @param append append or overwrite\r\n     * @throws FileTransferException on transfer error\r\n     */\r\n    private void copyFile(AbstractFile sourceFile, AbstractFile destFile, boolean append) throws FileTransferException {\r\n        // Reset this field in case it was set to true for the previous file\r\n        isCheckingIntegrity = false;\r\n\r\n        // Throw a specific FileTransferException if source and destination files are identical\r\n        if (sourceFile.equalsCanonical(destFile)) {\r\n            throw new FileTransferException(FileTransferException.SOURCE_AND_DESTINATION_IDENTICAL);\r\n        }\r\n\r\n        // Determine whether AbstractFile.copyRemotelyTo() should be used to copy the file.\r\n        // Some file protocols do not provide a getOutputStream() method and require the use of copyRemotelyTo(). Some other\r\n        // may also offer server to server copy which is more efficient than stream copy.\r\n\r\n        boolean copied = false;\r\n        if (sourceFile.isFileOperationSupported(FileOperation.COPY_REMOTELY)) {\r\n            try {\r\n                sourceFile.copyRemotelyTo(destFile);\r\n                copied = true;\r\n            } catch(IOException e) {\r\n                // The file will be copied manually\r\n            }\r\n        }\r\n\r\n        // If the file wasn't copied using copyRemotelyTo(), or if copyRemotelyTo() failed\r\n        InputStream in = null;\r\n        if (!copied) {\r\n            // Copy source file stream to destination file\r\n            try {\r\n                long inLength = sourceFile.getSize();\r\n\r\n                // Try to open InputStream\r\n                try  {\r\n                    long destFileSize = destFile.getSize();\r\n                    if (append && destFileSize > 0) {\r\n                        in = sourceFile.getInputStream(destFileSize);\r\n                        // Do not calculate checksum, as it needs to be calculated on the whole file\r\n\r\n                        inLength -= destFileSize;\r\n                        // Increase current file ByteCounter by the number of bytes skipped\r\n                        currentFileByteCounter.add(destFileSize);\r\n                        // Increase skipped ByteCounter by the number of bytes skipped\r\n                        currentFileSkippedByteCounter.add(destFileSize);\r\n                    } else {\r\n                        in = sourceFile.getInputStream();\r\n                        if (integrityCheckEnabled) {\r\n                            in = new ChecksumInputStream(in, MessageDigest.getInstance(CHECKSUM_VERIFICATION_ALGORITHM));\r\n                        }\r\n                    }\r\n\r\n                    setCurrentInputStream(in);\r\n                } catch(Exception e) {\r\n                    LOGGER.debug(\"IOException caught, throwing FileTransferException\", e);\r\n                    throw new FileTransferException(FileTransferException.OPENING_SOURCE);\r\n                }\r\n                if (destFile instanceof AdbFile adbFile) {\r\n                    try {\r\n                        adbFile.pullFrom(sourceFile);\r\n                    } catch (IOException e) {\r\n                        e.printStackTrace();\r\n                        throw new FileTransferException(FileTransferException.WRITING_DESTINATION);\r\n                    }\r\n                    System.out.println(sourceFile.getClass().getName());\r\n                    System.out.println(sourceFile + \" -> \" + destFile);\r\n                    return;\r\n                }\r\n                // Copy source stream to destination file\r\n                destFile.copyStream(tlin, append, inLength);\r\n            } finally {\r\n                // This block will always be executed, even if an exception\r\n                // was thrown in the catch block\r\n\r\n                // Tries to close the streams no matter what happened before\r\n                closeCurrentInputStream();\r\n            }\r\n        }\r\n\r\n        tryCopyFileDate(sourceFile, destFile);\r\n        tryCopyFilePermissions(sourceFile, destFile);\r\n        tryCopyFileTypeAndCreator(sourceFile, destFile);\r\n\r\n        // This block is executed only if integrity check has been enabled (disabled by default)\r\n        if (integrityCheckEnabled) {\r\n            // Indicate that integrity is being checked, the value is reset when the next file starts\r\n            isCheckingIntegrity = true;\r\n\r\n            String sourceChecksum;\r\n            if (in instanceof ChecksumInputStream) {\r\n                // The file was copied with a ChecksumInputStream, the checksum is already calculated, simply\r\n                // retrieve it\r\n                sourceChecksum = ((ChecksumInputStream)in).getChecksumString();\r\n            } else {\r\n                // The file was copied using AbstractFile#copyRemotelyTo(), or the transfer was resumed:\r\n                // we have to calculate the source file's checksum from scratch.\r\n                try {\r\n                    sourceChecksum = calculateChecksum(sourceFile);\r\n                } catch (Exception e) {\r\n                    throw new FileTransferException(FileTransferException.READING_SOURCE);\r\n                }\r\n            }\r\n\r\n            LOGGER.debug(\"Source checksum= \"+sourceChecksum);\r\n\r\n            // Calculate the destination file's checksum\r\n            String destinationChecksum;\r\n            try {\r\n                destinationChecksum = calculateChecksum(destFile);\r\n            } catch(Exception e) {\r\n                throw new FileTransferException(FileTransferException.READING_DESTINATION);\r\n            }\r\n\r\n            LOGGER.debug(\"Destination checksum= \"+destinationChecksum);\r\n\r\n            // Compare both checksums and throw an exception if they don't match\r\n            if (!sourceChecksum.equals(destinationChecksum)) {\r\n                throw new FileTransferException(FileTransferException.CHECKSUM_MISMATCH);\r\n            }\r\n        }\r\n    }\r\n\r\n    private String calculateChecksum(AbstractFile file) throws IOException, NoSuchAlgorithmException {\r\n        currentFileByteCounter.reset();\r\n        InputStream in = setCurrentInputStream(file.getInputStream());\r\n        try {\r\n            return AbstractFile.calculateChecksum(in, MessageDigest.getInstance(CHECKSUM_VERIFICATION_ALGORITHM));\r\n        } finally {\r\n            closeCurrentInputStream();\r\n        }\r\n    }\r\n\r\n\r\n\r\n    /**\r\n     * Tries to copy the given source file to the specified destination file (see {@link #copyFile(AbstractFile,AbstractFile,boolean)}\r\n     * displaying a generic error dialog {@link #showErrorDialog(String, String) #showErrorDialog()} if something went wrong, \r\n     * and giving the user the choice to skip the file, retry or cancel.\r\n     *\r\n     * @return true if the file was properly copied, false if the transfer was interrupted / aborted by the user\r\n     *\r\n     */\r\n    boolean tryCopyFile(AbstractFile sourceFile, AbstractFile destFile, boolean append, String errorDialogTitle) {\r\n        boolean overwriteReadonly = false;\r\n        // Copy file to destination\r\n        do {\t\t\t\t// Loop for retry\r\n            try {\r\n                if (overwriteReadonly) {\r\n                    copyToReadonlyFile(sourceFile, destFile, append);\r\n                } else {\r\n                    copyFile(sourceFile, destFile, append);\r\n                }\r\n                return true;\r\n            } catch(FileTransferException e) {\r\n                // If the job was interrupted by the user at the time the exception occurred, it most likely means that\r\n                // the IOException was caused by the stream being closed as a result of the user interruption.\r\n                // If that is the case, the exception should not be interpreted as an error.\r\n                // Same goes if the current file was skipped.\r\n                if (getState() == State.INTERRUPTED || wasCurrentFileSkipped()) {\r\n                    return false;\r\n                }\r\n\r\n                // Print the exception's stack trace\r\n                LOGGER.debug(\"Copy failed\", e);\r\n\r\n                int reason = e.getReason();\r\n                int choice;\r\n                switch(reason) {\r\n                    // Could not open source file for read\r\n                    case FileTransferException.OPENING_SOURCE:\r\n                        choice = showErrorDialog(errorDialogTitle, Translator.get(\"cannot_read_file\", sourceFile.getName()));\r\n                        break;\r\n                    // Could not open destination file for write\r\n                    case FileTransferException.UNSUPPORTED_OPERATION:\r\n                        choice = showErrorDialog(errorDialogTitle,\r\n                                Translator.get(\"error_unsupported_operation\"),\r\n                                // from the perspective of users there is nothing to cancel but only to acknowledge\r\n                                new String[] { OK_TEXT },\r\n                                // technically we're cancelling here\r\n                                new int[] { CANCEL_ACTION });\r\n                        break;\r\n                    case FileTransferException.OPENING_DESTINATION:\r\n                        // if write to read-only file\r\n                        if (!destFile.getPermissions().getBitValue(PermissionAccesses.USER_ACCESS, PermissionTypes.WRITE_PERMISSION)) {\r\n                            if (overwriteAllReadonly) {\r\n                                choice = OVERWRITE_READONLY_ACTION;\r\n                            } else {\r\n                                String[] actionTexts = new String[]{SKIP_TEXT, SKIP_ALL_TEXT, OVERWRITE_READONLY_TEXT, OVERWRITE_READONLY_ALL_TEXT, CANCEL_TEXT};\r\n                                int[] actionValues = new int[]{SKIP_ACTION, SKIP_ALL_ACTION, OVERWRITE_READONLY_ACTION, OVERWRITE_READONLY_ALL_ACTION, CANCEL_ACTION};\r\n                                choice = showErrorDialog(errorDialogTitle, Translator.get(\"overwrite_readonly_file\", destFile.getName()), actionTexts, actionValues);\r\n                            }\r\n                        } else {\r\n                            choice = showErrorDialog(errorDialogTitle, Translator.get(\"cannot_write_file\", destFile.getName()));\r\n                        }\r\n                        break;\r\n                    // Source and destination files are identical\r\n                    case FileTransferException.SOURCE_AND_DESTINATION_IDENTICAL:\r\n                        choice = showErrorDialog(errorDialogTitle, Translator.get(\"same_source_destination\"));\r\n                        break;\r\n                    // Checksum of source and destination files don't match\r\n                    case FileTransferException.CHECKSUM_MISMATCH:\r\n                        choice = showErrorDialog(errorDialogTitle, Translator.get(\"integrity_check_error\"));\r\n                        break;\r\n                    default:\r\n                        choice = showErrorDialog(errorDialogTitle,\r\n                                                 Translator.get(\"error_while_transferring\", sourceFile.getName()),\r\n                                                 new String[]{SKIP_TEXT, SKIP_ALL_TEXT, APPEND_TEXT, RETRY_TEXT, CANCEL_TEXT},\r\n                                                 new int[]{SKIP_ACTION, SKIP_ALL_ACTION, APPEND_ACTION, RETRY_ACTION, CANCEL_ACTION}\r\n                                                 );\r\n                    break;\r\n                }\r\n                // Retry action (append or retry)\r\n                if (choice == RETRY_ACTION || choice == APPEND_ACTION || choice == OVERWRITE_READONLY_ACTION || choice == OVERWRITE_READONLY_ALL_ACTION) {\r\n                    // Reset current file byte counters\r\n                    currentFileByteCounter.reset();\r\n                    currentFileSkippedByteCounter.reset();\r\n                    // Append resumes transfer\r\n                    append = choice == APPEND_ACTION;\r\n                    overwriteReadonly = choice == OVERWRITE_READONLY_ACTION || choice == OVERWRITE_READONLY_ALL_ACTION;\r\n                    if (choice == OVERWRITE_READONLY_ALL_ACTION) {\r\n                        overwriteAllReadonly = true;\r\n                    }\r\n                    continue;\r\n                }\r\n\r\n                // Skip or Cancel action (stop() is already called by showErrorDialog)\r\n                return false;\r\n            }\r\n        } while(true);\r\n    }\r\n\r\n\r\n    /**\r\n     * Registers the given InputStream as currently in use, in order to:\r\n     * <ul>\r\n     * <li>count the number of bytes that have been read from it (see {@link #getCurrentFileByteCounter()})\r\n     * <li>block read methods calls when the job is paused\r\n     * <li>limit the throughput if a limit has been specified (see {@link #setThroughputLimit(long)})\r\n     * <li>close the InputStream when the job is stopped\r\n     * </ul>\r\n     *\r\n     * <p>This method should be called by subclasses when creating a new InputStream, before the InputStream is used.\r\n     *\r\n     * @param in the InputStream to be used\r\n     * @return the 'augmented' InputStream using the given stream as the underlying InputStream\r\n     */\r\n    synchronized InputStream setCurrentInputStream(InputStream in) {\r\n        if (tlin == null) {\r\n            tlin = new ThroughputLimitInputStream(new CounterInputStream(in, currentFileByteCounter), throughputLimit);\r\n        } else {\r\n            tlin.setUnderlyingInputStream(new CounterInputStream(in, currentFileByteCounter));\r\n        }\r\n\r\n        return tlin;\r\n    }\r\n\r\n    /**\r\n     * Closes the currently registered source InputStream.\r\n     */\r\n    synchronized void closeCurrentInputStream() {\r\n        if (tlin != null) {\r\n            try {\r\n                tlin.close();\r\n            } catch(IOException e) {\r\n                LOGGER.error(\"Close error\", e);\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns <code>true</code> if file transfers need to be checked for data integrity. In this case, the checksum of\r\n     * the source and destination files are both calculated and compared to verify they match.\r\n     *\r\n     * @return true if file transfers need to be checked for data integrity\r\n     */\r\n    boolean isIntegrityCheckEnabled() {\r\n        return integrityCheckEnabled;\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the integrity of the current file is being verified.\r\n     *\r\n     * @return true if the integrity of the current file is being verified\r\n     */\r\n    boolean isCheckingIntegrity() {\r\n        return isCheckingIntegrity;\r\n    }\r\n\r\n\r\n    /**\r\n     * Interrupts the current file transfer and advance to the next one.\r\n     */\r\n    public synchronized void skipCurrentFile() {\r\n        if (tlin != null) {\r\n            LOGGER.debug(\"skipping current file, closing \"+ tlin);\r\n\r\n            // Prevents an error from being reported when the current InputStream is closed\r\n            currentFileSkipped = true;\r\n\r\n            // Close the current input stream to interrupt the transfer\r\n            closeCurrentInputStream();\r\n        }\r\n\r\n        // Resume job if currently paused \r\n        if (getState() == State.PAUSED) {\r\n            setPaused(false);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Return <code>true</code> if the file that is currently being processed has been skipped.\r\n     *\r\n     * @return true if the file that is currently being processed has been skipped\r\n     */\r\n    synchronized boolean wasCurrentFileSkipped() {\r\n        return currentFileSkipped;\r\n    }\r\n\r\n    /**\r\n     * Returns the percentage of the current file that has been processed, <code>0</code> if the current file's size\r\n     * is not available (in this case getNbCurrentFileBytesProcessed() returns <code>-1</code>).\r\n     *\r\n     * @return the percentage of the current file that has been processed\r\n     */\r\n    public float getFilePercentDone() {\r\n        long currentFileSize = getCurrentFileSize();\r\n        return currentFileSize <= 0 ? 0 : getCurrentFileByteCounter().getByteCount()/(float)currentFileSize;\r\n    }\r\n\r\n    /**\r\n     * Returns the number of bytes that have been skipped in the current file. Bytes are skipped when file transfers\r\n     * are resumed.\r\n     *\r\n     * @return the number of bytes that have been skipped in the current file\r\n     */\r\n    private ByteCounter getCurrentFileSkippedByteCounter() {\r\n        return currentFileSkippedByteCounter;\r\n    }\r\n\r\n    /**\r\n     * Returns the size of the file currently being processed, <code>-1</code> if this information is not available.\r\n     *\r\n     * @return the size of the file currently being processed, -1 if this information is not available.\r\n     */\r\n    public long getCurrentFileSize() {\r\n        return getCurrentFile() == null ? -1 : getCurrentFile().getSize();\r\n    }\r\n\r\n\r\n    /**\r\n     * Sets a transfer throughput limit in bytes per seconds, replacing any previous limit.\r\n     * This limit corresponds to the number of bytes that can be read from a registered InputStream.\r\n     *\r\n     * <p>Specifying 0 or -1 disables any throughput limit, the transfer will be carried out at full speed.\r\n     *\r\n     * <p>If this job is paused, the new limit will be effective after the job has been resumed.\r\n     * If not, it will be effective immediately.\r\n     *\r\n     * @param bytesPerSecond new throughput limit in bytes per second, 0 or -1 to disable the limit\r\n     */\r\n    public void setThroughputLimit(long bytesPerSecond) {\r\n        // Note: ThroughputInputStream interprets 0 as a complete pause (blocks reads) which is different\r\n        // from what a user would expect when specifying 0 as a limit\r\n        this.throughputLimit = bytesPerSecond <= 0 ? -1 : bytesPerSecond;\r\n\r\n        synchronized(this) {\r\n            if (getState() != State.PAUSED && tlin != null) {\r\n                tlin.setThroughputLimit(throughputLimit);\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Overrides {@link FileJob#jobStopped()} to stop any file processing by closing the source InputStream.\r\n     */\r\n    @Override\r\n    protected void jobStopped() {\r\n        super.jobStopped();\r\n\r\n        synchronized(this) {\r\n            if (tlin != null) {\r\n                LOGGER.debug(\"closing current InputStream \"+ tlin);\r\n                closeCurrentInputStream();\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Overrides {@link FileJob#jobPaused()} to pause any file processing\r\n     * by having the source InputStream's read methods lock.\r\n     */\r\n    @Override\r\n    protected void jobPaused() {\r\n        super.jobPaused();\r\n\r\n        synchronized(this) {\r\n            if (tlin != null) {\r\n                tlin.setThroughputLimit(0);\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Overrides {@link FileJob#jobResumed()} to resume any file processing by releasing\r\n     * the lock on the source InputStream's read methods.\r\n     */\r\n    @Override\r\n    protected void jobResumed() {\r\n        super.jobResumed();\r\n\r\n        synchronized(this) {\r\n            // Restore previous throughput limit (if any, -1 by default)\r\n            if (tlin != null) {\r\n                tlin.setThroughputLimit(throughputLimit);\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Advances file index and resets current file's byte counters. This method should be called by subclasses\r\n     * whenever the job starts processing a new file.\r\n     */\r\n    @Override\r\n    protected void nextFile(AbstractFile file) {\r\n        totalByteCounter.add(currentFileByteCounter, true);\r\n        totalSkippedByteCounter.add(currentFileSkippedByteCounter, true);\r\n\r\n        // Reset some fields that need it\r\n        currentFileSkipped = false;\r\n\r\n        super.nextFile(file);\r\n    }\r\n\r\n    /**\r\n     * Method overridden to return a more accurate percentage of job processed so far by taking into account the current\r\n     * file's percentage of completion.\r\n     */\r\n    @Override\r\n    public float getTotalPercentDone() {\r\n        float nbFilesProcessed = getCurrentFileIndex();\r\n        int nbFiles = getNbFiles();\r\n\r\n        // If file is in base folder and is not a directory...\r\n        if (getCurrentFile() != null && nbFilesProcessed != nbFiles && files.contains(getCurrentFile()) && !getCurrentFile().isDirectory()) {\r\n            // Add current file's progress\r\n            long currentFileSize = getCurrentFile().getSize();\r\n            if (currentFileSize > 0) {\r\n                nbFilesProcessed += getCurrentFileByteCounter().getByteCount()/(float)currentFileSize;\r\n            }\r\n        }\r\n\r\n        return nbFilesProcessed/(float)nbFiles;\r\n    }\r\n\r\n    /**\r\n     * This method is overridden to return a custom string \"Checking integrity of CURRENT_FILE\" when the current file\r\n     * is being checked for integrity.\r\n     */\r\n    @Override\r\n    public String getStatusString() {\r\n        if (isCheckingIntegrity()) {\r\n            return Translator.get(\"progress_dialog.verifying_file\", getCurrentFilename());\r\n        }\r\n\r\n        return super.getStatusString();\r\n    }\r\n\r\n    protected boolean tryCopySymlinkFile(AbstractFile sourceFile, AbstractFile destFile) {\r\n        Path sourcePath = ((File) sourceFile.getUnderlyingFileObject()).toPath();\r\n        Path destPath = ((File) destFile.getUnderlyingFileObject()).toPath();\r\n        try {\r\n            Files.createSymbolicLink(destPath, Files.readSymbolicLink(sourcePath));\r\n        } catch (IOException e) {\r\n            LOGGER.debug(\"failed to create symbolic link \"+destFile, e);\r\n            return false;\r\n        }\r\n\r\n        // Preserve source file's date\r\n        tryCopyFileDate(sourceFile, destFile);\r\n        // Preserve source file's permissions: preserve only the permissions bits that are supported by the source file\r\n        // and use default permissions for the rest of them.\r\n        tryCopyFilePermissions(sourceFile, destFile);\r\n\r\n        // Under Mac OS X only, preserving the file type and creator\r\n        DesktopManager.postCopy(sourceFile, destFile);\r\n\r\n        // Under Mac OS X only, preserving the file type and creator\r\n        tryCopyFileTypeAndCreator(sourceFile, destFile);\r\n\r\n        return true;\r\n    }\r\n\r\n    private void tryCopyFileDate(AbstractFile sourceFile, AbstractFile destFile) {\r\n        if (destFile.isFileOperationSupported(FileOperation.CHANGE_DATE)) {\r\n            try {\r\n                destFile.setLastModifiedDate(sourceFile.getLastModifiedDate());\r\n            } catch (IOException e) {\r\n                LOGGER.debug(\"failed to change the date of \"+destFile, e);\r\n            }\r\n        }\r\n    }\r\n\r\n    private void tryCopyFilePermissions(AbstractFile sourceFile, AbstractFile destFile) {\r\n        if (destFile.isFileOperationSupported(FileOperation.CHANGE_PERMISSION)) {\r\n            try {\r\n                destFile.importPermissions(sourceFile, FilePermissions.DEFAULT_FILE_PERMISSIONS);  // use #importPermissions(AbstractFile, int) to avoid isDirectory test\r\n            } catch(IOException e) {\r\n                LOGGER.debug(\"failed to import \"+sourceFile+\" permissions into \"+destFile, e);\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n    private void tryCopyFileTypeAndCreator(AbstractFile sourceFile, AbstractFile destFile) {\r\n        if (OsFamily.MAC_OS_X.isCurrent() && sourceFile.hasAncestor(LocalFile.class) && destFile.hasAncestor(LocalFile.class)) {\r\n            String sourcePath = sourceFile.getAbsolutePath();\r\n            try {\r\n                FileManager.setFileTypeAndCreator(destFile.getAbsolutePath(), FileManager.getFileType(sourcePath), FileManager.getFileCreator(sourcePath));\r\n            } catch(IOException e) {\r\n                // Swallow the exception and do not interrupt the transfer\r\n                LOGGER.debug(\"Error while setting Mac OS X file type and creator on destination\", e);\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n//    /**\r\n//     * Method overridden to return a more accurate percentage of job processed so far by taking\r\n//     * into account the current file's processed percentage.\r\n//     */\r\n//    public float getTotalPercentDone() {\r\n//        float nbFilesProcessed = getNbFilesProcessed();\r\n//\r\n//        // If file is in base folder and is not a directory\r\n//        if(currentFile!=null && files.indexOf(currentFile)!=-1 && !currentFile.isDirectory()) {\r\n//            // Take into account current file's progress\r\n//            long currentFileSize = currentFile.getSize();\r\n//            if(currentFileSize>0)\r\n//                nbFilesProcessed += getCurrentFileByteCounter().getByteCount()/(float)currentFileSize;\r\n//        }\r\n//\r\n////AppLogger.finest(\"nbFilesProcessed=\"+(int)nbFilesProcessed+\" nbFilesDiscovered=\"+getNbFilesDiscovered()+\" %=\"+((int)100*nbFilesProcessed/getNbFilesDiscovered()));\r\n//\r\n//        return nbFilesProcessed/getNbFilesDiscovered();\r\n//    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/UnpackJob.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\n\r\npackage com.mucommander.job;\r\n\r\nimport com.mucommander.commons.file.*;\r\nimport com.mucommander.commons.file.impl.ProxyFile;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.commons.file.util.PathUtils;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.action.ActionManager;\r\nimport com.mucommander.ui.action.impl.UnmarkAllAction;\r\nimport com.mucommander.ui.dialog.file.ProgressDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport java.io.IOException;\r\nimport java.io.InputStream;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\n\r\n/**\r\n * This job unpacks a set of archive files to a base destination folder. Archive entries are extracted in their natural\r\n * order using {@link com.mucommander.commons.file.AbstractArchiveFile#getEntryIterator()}, to traverse the archive only once\r\n * and achieve optimal performance.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class UnpackJob extends AbstractCopyJob {\r\n\r\n    /** Archive entries to be unpacked */\r\n    private List<ArchiveEntry> selectedEntries;\r\n\r\n    /** Depth of the folder in which the top entries are located. 0 is the highest depth (archive's root folder) */\r\n    private final int baseArchiveDepth;\r\n\r\n    private long totalFilesSize;\r\n    private int totalFilesCount;\r\n\r\n    private int processedFilesCount;\r\n    private long processedFilesSize;\r\n    private boolean preparingFinished;\r\n\r\n    /**\r\n     * Creates a new UnpackJob without starting it.\r\n     * <p>\r\n     * The base destination folder will be created if it doesn't exist.\r\n     *\r\n     * @param progressDialog dialog which shows this job's progress\r\n     * @param mainFrame mainFrame this job has been triggered by\r\n     * @param files files which are going to be unpacked\r\n     * @param destFolder destination folder where the files will be copied\r\n     * @param fileExistsAction default action to be performed when a file already exists in the destination, see {@link com.mucommander.ui.dialog.file.FileCollisionDialog} for allowed values\r\n     */\r\n    public UnpackJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet files, AbstractFile destFolder, int fileExistsAction) {\r\n        super(progressDialog, mainFrame, files, destFolder, null, fileExistsAction);\r\n\r\n        this.errorDialogTitle = Translator.get(\"unpack_dialog.error_title\");\r\n        this.baseArchiveDepth = 0;\r\n    }\r\n\r\n    /**\r\n     * Creates a new UnpackJob without starting it.\r\n     *\r\n     * @param progressDialog dialog which shows this job's progress\r\n     * @param mainFrame mainFrame this job has been triggered by\r\n     * @param archiveFile the archive file which is going to be unpacked\r\n     * @param destFolder destination folder where the files will be copied\r\n     * @param newName the new filename in the destination folder, if <code>null</code> the original filename will be used\r\n     * @param fileExistsAction default action to be performed when a file already exists in the destination, see {@link com.mucommander.ui.dialog.file.FileCollisionDialog} for allowed values\r\n     * @param selectedEntries entries to be unpacked\r\n     * @param baseArchiveDepth depth of the folder in which the top entries are located. 0 is the highest depth (archive's root folder)\r\n     */\r\n    public UnpackJob(ProgressDialog progressDialog, MainFrame mainFrame, AbstractArchiveFile archiveFile, int baseArchiveDepth,\r\n                     AbstractFile destFolder, String newName, int fileExistsAction, List<ArchiveEntry> selectedEntries) {\r\n        super(progressDialog, mainFrame, new FileSet(archiveFile.getParent(), archiveFile), destFolder, newName, fileExistsAction);\r\n\r\n        this.errorDialogTitle = Translator.get(\"unpack_dialog.error_title\");\r\n        this.baseArchiveDepth = baseArchiveDepth;\r\n        this.selectedEntries = selectedEntries;\r\n    }\r\n\r\n\r\n    ////////////////////////////////////\r\n    // TransferFileJob implementation //\r\n    ////////////////////////////////////\r\n\r\n    @Override\r\n    protected void jobStarted() {\r\n        super.jobStarted();\r\n\r\n        // create the base destination folder if it doesn't exist yet\r\n        if (!baseDestFolder.exists()) {\r\n            // Loop for retry\r\n            do {\r\n                try {\r\n                    baseDestFolder.mkdir();\r\n                } catch(IOException e) {\r\n                    // Unable to create folder\r\n                    int ret = showErrorDialog(errorDialogTitle, Translator.get(\"cannot_create_folder\", baseDestFolder.getName()));\r\n                    // Retry loops\r\n                    if (ret == RETRY_ACTION) {\r\n                        continue;\r\n                    }\r\n                    // Cancel or close dialog interrupts the job\r\n                    interrupt();\r\n                    // Skip continues\r\n                }\r\n                break;\r\n            } while(true);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Unpacks the given archive file. If the file is a directory, its children will be processed recursively.\r\n     * If the file is not an archive file nor a directory, it is not processed and <code>false</code> is returned.\r\n     *\r\n     * @param file the file to unpack\r\n     * @param recurseParams unused\r\n     * @return <code>true</code> if the file has been processed successfully\r\n     */\r\n    @Override\r\n    protected boolean processFile(AbstractFile file, Object recurseParams) {\r\n        // Stop if interrupted\r\n        if (getState() == State.INTERRUPTED) {\r\n            return false;\r\n        }\r\n\r\n        // Destination folder\r\n        AbstractFile destFolder = baseDestFolder;\r\n\r\n        // If the file is a directory, process its children recursively\r\n        if (file.isDirectory()) {\r\n            do {    // Loop for retries\r\n                try {\r\n                    // List files inside archive file (can throw an IOException)\r\n                    AbstractFile[] archiveFiles = getCurrentFile().ls();\r\n\r\n                    // Recurse on zip's contents\r\n                    for(int j=0; j<archiveFiles.length && getState() != State.INTERRUPTED; j++) {\r\n                        // Notify job that we're starting to process this file (needed for recursive calls to processFile)\r\n                        nextFile(archiveFiles[j]);\r\n                        // Recurse\r\n                        processFile(archiveFiles[j], destFolder);\r\n                    }\r\n                    // Return true when complete\r\n                    return true;\r\n                } catch (IOException e) {\r\n                    // File could not be uncompressed properly\r\n                    int ret = showErrorDialog(errorDialogTitle, Translator.get(\"cannot_read_file\", getCurrentFilename()));\r\n                    // Retry loops\r\n                    if (ret==RETRY_ACTION) {\r\n                        continue;\r\n                    }\r\n                    // cancel, skip or close dialog will simply return false\r\n                    return false;\r\n                }\r\n            } while(true);\r\n        }\r\n\r\n        // Abort if the file is neither an archive file nor a directory\r\n        if (!file.isArchive()) {\r\n            return false;\r\n        }\r\n\r\n        // 'Cast' the file as an archive file\r\n        AbstractArchiveFile archiveFile = file.getAncestor(AbstractArchiveFile.class);\r\n        ArchiveEntryIterator iterator = null;\r\n\r\n\r\n        // calculate total size and files count\r\n        calculateTotalSize(archiveFile);\r\n\r\n        ArchiveEntry entry;\r\n        String entryPath;\r\n        AbstractFile entryFile;\r\n        AbstractFile destFile;\r\n        String destSeparator = destFolder.getSeparator();\r\n        String relDestPath;\r\n\r\n        // Unpack the archive, copying entries one by one, in the iterator's order\r\n        try {\r\n            iterator = archiveFile.getEntryIterator();\r\n            while ((entry = iterator.nextEntry()) != null && getState() != State.INTERRUPTED) {\r\n                entryPath = entry.getPath();\r\n\r\n                boolean processEntry = false;\r\n                if (selectedEntries == null) {    // Entries are processed\r\n                    processEntry = true;\r\n                } else {                          // We need to determine if the entry should be processed or not\r\n                    // Process this entry if the selectedEntries set contains this entry, or a parent of this entry\r\n                    int nbSelectedEntries = selectedEntries.size();\r\n                    for(int i=0; i<nbSelectedEntries; i++) {\r\n                        ArchiveEntry selectedEntry = selectedEntries.get(i);\r\n                        // Note: paths of directory entries must end with '/', so this compares whether\r\n                        // selectedEntry is a parent of the current entry.\r\n                        if (selectedEntry.isDirectory()) {\r\n                            if (entryPath.startsWith(selectedEntry.getPath())) {\r\n                                processEntry = true;\r\n                                break;\r\n                                // Note: we can't remove selectedEntryPath from the set, we still need it\r\n                            }\r\n                        } else if (entryPath.equals(selectedEntry.getPath())) {\r\n                            // If the (regular file) entry is in the set, remove it as we no longer need it (will speed up\r\n                            // subsequent searches)\r\n                            processEntry = true;\r\n                            selectedEntries.remove(i);\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n\r\n                if (!processEntry) {\r\n                    continue;\r\n                }\r\n\r\n                processedFilesCount++;\r\n                processedFilesSize += entry.getSize();\r\n\r\n                // Resolve the entry file\r\n                entryFile = archiveFile.getArchiveEntryFile(entryPath);\r\n\r\n                // Notify the job that we're starting to process this file\r\n                nextFile(entryFile);\r\n\r\n                // Figure out the destination file's path, relatively to the base destination folder\r\n                relDestPath = baseArchiveDepth == 0\r\n                        ?entry.getPath()\r\n                        :PathUtils.removeLeadingFragments(entry.getPath(), \"/\", baseArchiveDepth);\r\n\r\n                if (newName != null) {\r\n                    relDestPath = newName + (PathUtils.getDepth(relDestPath, \"/\") <= 1 ? \"\" : \"/\" + PathUtils.removeLeadingFragments(relDestPath, \"/\", 1));\r\n                }\r\n\r\n                if (!\"/\".equals(destSeparator)) {\r\n                    relDestPath = relDestPath.replace(\"/\", destSeparator);\r\n                }\r\n\r\n                // create destination AbstractFile instance\r\n                destFile = destFolder.getChild(relDestPath);\r\n\r\n                // Do nothing if the file is a symlink (skip file and return)\r\n                if (entryFile.isSymlink()) {\r\n                    // TODO !!! implement me\r\n//System.out.println(file.getAbsolutePath() + \" -> \" + file.getCanonicalPath());\r\n\r\n                    return true;\r\n                }\r\n\r\n                // Check if the file does not already exist in the destination\r\n                destFile = checkForCollision(entryFile, destFolder, destFile, false);\r\n                if (destFile == null) {\r\n                    // A collision occurred and either the file was skipped, or the user cancelled the job\r\n                    continue;\r\n                }\r\n\r\n                // It is noteworthy that the iterator returns entries in no particular order (consider it random).\r\n                // For that reason, we cannot assume that the parent directory of an entry will be processed\r\n                // before the entry itself.\r\n\r\n                // If the entry is a directory ...\r\n                if (entryFile.isDirectory()) {\r\n                    // create the directory in the destination, if it doesn't already exist\r\n                    if (!(destFile.exists() && destFile.isDirectory())) {\r\n                        // Loop for retry\r\n                        do {\r\n                            try {\r\n                                // Use mkdirs() instead of mkdir() to create any parent folder that doesn't exist yet\r\n                                destFile.mkdirs();\r\n                            } catch(IOException e) {\r\n                                // Unable to create folder\r\n                                int ret = showErrorDialog(errorDialogTitle, Translator.get(\"cannot_create_folder\", entryFile.getName()));\r\n                                // Retry loops\r\n                                if (ret == RETRY_ACTION) {\r\n                                    continue;\r\n                                }\r\n                                // Cancel or close dialog return false\r\n                                return false;\r\n                                // Skip continues\r\n                            }\r\n                            break;\r\n                        } while(true);\r\n                    }\r\n                }\r\n                // The entry is a regular file, copy it\r\n                else  {\r\n                    // create the file's parent directory(s) if it doesn't already exist\r\n                    AbstractFile destParentFile = destFile.getParent();\r\n                    if (!destParentFile.exists()) {\r\n                        // Use mkdirs() instead of mkdir() to create any parent folder that doesn't exist yet\r\n                        destParentFile.mkdirs();\r\n                    }\r\n\r\n                    // The entry is wrapped in a ProxyFile to override #getInputStream() and delegate it to\r\n                    // ArchiveFile#getEntryInputStream in order to take advantage of the ArchiveEntryIterator, which for\r\n                    // some archive file implementations (such as TAR) can speed things by an order of magnitude.\r\n                    if (!tryCopyFile(new ProxiedEntryFile(entryFile, entry, archiveFile, iterator), destFile, append, errorDialogTitle)) {\r\n                        // !!! we don't need to break the process in this case\r\n//                        return false;\r\n                    }\r\n                }\r\n            }\r\n\r\n            return true;\r\n        } catch (IOException e) {\r\n            showErrorDialog(errorDialogTitle, Translator.get(\"cannot_read_file\", archiveFile.getName()));\r\n        } finally {\r\n            // The ArchiveEntryIterator must be closed when finished\r\n            if (iterator != null) {\r\n                try {\r\n                    iterator.close();\r\n                } catch(IOException e) {\r\n                    // Not much we can do about it\r\n                }\r\n            }\r\n        }\r\n\r\n        return false;\r\n    }\r\n\r\n    // This job modifies the base destination folder and its subfolders\r\n    @Override\r\n    protected boolean hasFolderChanged(AbstractFile folder) {\r\n        return baseDestFolder.isParentOf(folder);\r\n    }\r\n\r\n\r\n    @Override\r\n    protected void jobCompleted() {\r\n        super.jobCompleted();\r\n\r\n        // If the destination files are located inside an archive, optimize the archive file\r\n        AbstractArchiveFile archiveFile = baseDestFolder.getParentArchive();\r\n        if(archiveFile!=null && archiveFile.isArchive() && archiveFile.isWritable())\r\n            optimizeArchive((AbstractRWArchiveFile)archiveFile);\r\n\r\n        // Unselect all files in the active table upon successful completion\r\n        if (selectedEntries != null) {\r\n            ActionManager.performAction(UnmarkAllAction.Descriptor.ACTION_ID, getMainFrame());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public String getStatusString() {\r\n        if (isCheckingIntegrity() || !preparingFinished) {\r\n            return super.getStatusString();\r\n        }\r\n\r\n        if (isOptimizingArchive) {\r\n            return Translator.get(\"optimizing_archive\", archiveToOptimize.getName());\r\n        }\r\n\r\n        return Translator.get(\"unpack_dialog.unpacking_file\", getCurrentFilename());\r\n    }\r\n\r\n\r\n    private void calculateTotalSize(AbstractArchiveFile archiveFile) {\r\n        totalFilesSize = 0;\r\n        totalFilesCount = 0;\r\n        // get all directories\r\n        List<String> selectedDirectories = new ArrayList<>();\r\n        List<ArchiveEntry> fileEntries = new ArrayList<>();\r\n\r\n        if (selectedEntries != null) {\r\n            for (ArchiveEntry entry : selectedEntries) {\r\n                if (entry.isDirectory()) {\r\n                    selectedDirectories.add(entry.getPath());\r\n                } else {\r\n                    fileEntries.add(entry);\r\n                }\r\n            }\r\n        }\r\n\r\n        try {\r\n            ArchiveEntryIterator iterator = archiveFile.getEntryIterator();\r\n            ArchiveEntry entry;\r\n            while ((entry = iterator.nextEntry()) != null && getState() != State.INTERRUPTED) {\r\n                // check in directories\r\n                boolean addThisEntry = false;\r\n                if (selectedEntries != null) {\r\n                    if (!selectedDirectories.isEmpty()) {\r\n                        String path = entry.getPath();\r\n                        for (String dir : selectedDirectories) {\r\n                            if (path.startsWith(dir)) {\r\n                                addThisEntry = true;\r\n                                break;\r\n                            }\r\n                        }\r\n                    } // directories\r\n                    if (!addThisEntry && !selectedEntries.isEmpty()) {\r\n                        for (ArchiveEntry selEntry : selectedEntries) {\r\n                            if (entry == selEntry) {\r\n                                addThisEntry = true;\r\n                                break;\r\n                            }\r\n                        }\r\n                    } // entries\r\n                } else {\r\n                    addThisEntry = true;\r\n                }\r\n\r\n                if (addThisEntry) {\r\n                    totalFilesSize += entry.getSize();\r\n                    totalFilesCount++;\r\n                }\r\n            } // while\r\n        } catch (IOException e) {\r\n            e.printStackTrace();\r\n        }\r\n        preparingFinished = true;\r\n    }\r\n\r\n\r\n    @Override\r\n    public float getTotalPercentDone() {\r\n        if (totalFilesSize == 0) {\r\n            float result = super.getTotalPercentDone();\r\n            return result > 5 ? 5 : result;\r\n        }\r\n        float progressBySize = 1.0f*processedFilesSize / totalFilesSize;\r\n        float progressByCount = 1.0f*(processedFilesCount-1) / totalFilesCount;\r\n        float result = (progressBySize * 8 + progressByCount * 2) / 10;\r\n        if (result < 0) {\r\n            result = 0;\r\n        } else if (result > 1) {\r\n            result = 1;\r\n        }\r\n        return result;\r\n    }\r\n\r\n\r\n\r\n    private static class ProxiedEntryFile extends ProxyFile {\r\n        private final ArchiveEntry entry;\r\n        private final AbstractArchiveFile archiveFile;\r\n        private final ArchiveEntryIterator iterator;\r\n\r\n        ProxiedEntryFile(AbstractFile entryFile, ArchiveEntry entry, AbstractArchiveFile archiveFile, ArchiveEntryIterator iterator) {\r\n            super(entryFile);\r\n\r\n            this.entry = entry;\r\n            this.archiveFile = archiveFile;\r\n            this.iterator = iterator;\r\n        }\r\n\r\n        @Override\r\n        public InputStream getInputStream() throws IOException {\r\n            return archiveFile.getEntryInputStream(entry, iterator);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/progress/JobProgress.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.job.progress;\r\n\r\nimport com.mucommander.job.FileJob;\r\nimport com.mucommander.job.TransferFileJob;\r\nimport com.mucommander.utils.text.DurationFormat;\r\nimport com.mucommander.utils.text.Translator;\r\n\r\n/**\r\n * Contains information about job progress.\r\n *\r\n */\r\npublic class JobProgress {\r\n\tprivate final FileJob job;\r\n\tprivate TransferFileJob transferFileJob;\r\n\r\n\tprivate long effectiveJobTime;\r\n\tprivate long lastTime;\r\n\tprivate int totalPercentInt;\r\n\tprivate String totalProgressText;\r\n\tprivate int filePercentInt;\r\n\tprivate String fileProgressText;\r\n\tprivate long currentBps;\r\n\tprivate long bytesTotal;\r\n\tprivate long totalBps;\r\n\tprivate long lastBytesTotal;\r\n\tprivate String jobStatusString;\r\n\tprivate long jobPauseStartDate;\r\n\r\n\tpublic JobProgress(FileJob job) {\r\n\t\tthis.job = job;\r\n\t\tif (job instanceof TransferFileJob) {\r\n\t\t\tthis.transferFileJob = (TransferFileJob) job;\r\n\t\t}\r\n\t\tlastBytesTotal = 0;\r\n\t\tlastTime = System.currentTimeMillis();\r\n\t}\r\n\r\n\t\r\n\t/**\r\n\t * Calculates the job progress status. This method calculates variables used\r\n\t * to show job progress information. It can update information only on a\r\n\t * processed file (when <code>labelOnly</code> is <code>true</code>). If\r\n\t * <code>labelOnly</code> is false it will try to update full information on\r\n\t * a job progress (e.g. percent completed, bytes per second, etc.).\r\n\t * \r\n\t * @param fullUpdate\r\n\t * \t\t\t <code>true</code> update all information about processed file.<br/>\r\n\t * \t\t\t <code>false</code> update only label of a processed file.<br/>\r\n\t * \t\t     Note that if a job has just finished this flag is ignored \r\n\t * \t\t\t and all variables are recalculated.\r\n\t * @return <code>true</code> if full job progress has been updated,\r\n\t *         <code>false</code> if only label has been updated.\r\n\t */\r\n\tboolean calcJobProgress(boolean fullUpdate) {\r\n\t\tFileJob.State jobState = job.getState();\r\n\t\tjobPauseStartDate = job.getPauseStartDate();\r\n\t\tif (jobState == FileJob.State.FINISHED || jobState == FileJob.State.INTERRUPTED) {\r\n\t\t\tjobStatusString = Translator.get(\"progress_dialog.job_finished\");\r\n\t\t\t// Job just finished, let's loop one more time to ensure that\r\n\t\t\t// components (progress bar in particular)\r\n\t\t\t// reflect job completion\r\n\t\t\tfullUpdate = true;\r\n\t\t} else {\r\n\t\t\tjobStatusString = job.getStatusString();\r\n\t\t}\r\n\t\tif (!fullUpdate) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\t// Do not refresh progress information is job is paused, simply sleep\r\n\t\tif (jobState == FileJob.State.PAUSED) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\t// Now is updated with current time, or job end date if job has finished\r\n\t\t// already.\r\n\t\tlong now = job.getEndDate();\r\n\t\tif (now == 0) { // job hasn't finished yet\r\n\t\t\tnow = System.currentTimeMillis();\r\n\t\t}\r\n\r\n\t\tlong currentFileRemainingTime = 0;\r\n\t\tlong totalRemainingTime;\r\n\r\n\t\teffectiveJobTime = job.getEffectiveJobTime();\r\n\t\tif (effectiveJobTime == 0) {\r\n\t\t\teffectiveJobTime = 1; // To avoid potential zero divisions\r\n\t\t}\r\n\r\n\t\tif (transferFileJob != null) {\r\n\t\t\tbytesTotal = transferFileJob.getTotalByteCounter().getByteCount() - transferFileJob.getTotalSkippedByteCounter().getByteCount();\r\n\t\t\ttotalBps = (long) (bytesTotal * 1000d / effectiveJobTime);\r\n\t\t\tif (now - lastTime > 0) { // To avoid divisions by zero \r\n\t\t\t\tcurrentBps = (long) ((bytesTotal - lastBytesTotal) * 1000d / (now - lastTime));\r\n\t\t\t} else {\r\n\t\t\t\tcurrentBps = 0;\r\n\t\t\t}\r\n\r\n\t\t\t// Update current file progress bar\r\n\t\t\tfloat filePercentFloat = transferFileJob.getFilePercentDone();\r\n\t\t\tfilePercentInt = (int) (100 * filePercentFloat);\r\n\r\n\t\t\tfileProgressText = filePercentInt + \"%\";\r\n\t\t\t// Append estimated remaining time (ETA) if current file transfer is\r\n\t\t\t// not already finished (100%)\r\n\t\t\tif (filePercentFloat < 1) {\r\n\t\t\t\tfileProgressText += \" - \";\r\n\r\n\t\t\t\tlong currentFileSize = transferFileJob.getCurrentFileSize();\r\n\t\t\t\t// If current file size is not available, ETA cannot be\r\n\t\t\t\t// calculated\r\n\t\t\t\tif (currentFileSize == -1) {\r\n\t\t\t\t\tfileProgressText += \"?\";\r\n\t\t\t\t}\r\n\t\t\t\t// Avoid potential divisions by zero\r\n\t\t\t\telse if (totalBps == 0) {\r\n\t\t\t\t\tcurrentFileRemainingTime = -1;\r\n\t\t\t\t\tfileProgressText += DurationFormat.getInfiniteSymbol();\r\n\t\t\t\t} else {\r\n\t\t\t\t\tcurrentFileRemainingTime = (long) ((1000 * (currentFileSize - \r\n\t\t\t\t\t\t\ttransferFileJob.getCurrentFileByteCounter().getByteCount())) / \r\n\t\t\t\t\t\t\t(float) totalBps);\r\n\t\t\t\t\tfileProgressText += DurationFormat.format(currentFileRemainingTime);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tlastBytesTotal = bytesTotal;\r\n\t\t\tlastTime = now;\r\n\t\t}\r\n\r\n\t\t// Update total progress bar\r\n\t\t// Total job percent is based on the *number* of files remaining, not\r\n\t\t// their actual size.\r\n\t\t// So this is very approximate.\r\n\t\tfloat totalPercentFloat = job.getTotalPercentDone();\r\n\t\ttotalPercentInt = (int) (100 * totalPercentFloat);\r\n\r\n\t\ttotalProgressText = totalPercentInt + \"%\";\r\n\r\n\t\t// Add a rough estimate of the total remaining time (ETA):\r\n\t\t// total remaining time is based on the total job percent completed\r\n\t\t// which itself is based on the *number*\r\n\t\t// of files remaining, not their actual size. So this is very\r\n\t\t// approximate.\r\n\t\t// Do not add ETA if job is already finished (100%)\r\n\t\tif (totalPercentFloat < 1) {\r\n\t\t\ttotalProgressText += \" - \";\r\n\r\n\t\t\t// Avoid potential divisions by zero\r\n\t\t\tif (totalPercentFloat == 0) {\r\n\t\t\t\ttotalProgressText += \"?\";\r\n\t\t\t} else {\r\n\t\t\t\t// Make sure that total ETA is never smaller than current file\r\n\t\t\t\t// ETA\r\n\t\t\t\ttotalRemainingTime = (long) ((1 - totalPercentFloat) * \r\n\t\t\t\t\t\t(effectiveJobTime / totalPercentFloat));\r\n\t\t\t\ttotalRemainingTime = Math.max(totalRemainingTime,\r\n\t\t\t\t\t\tcurrentFileRemainingTime);\r\n\t\t\t\ttotalProgressText += DurationFormat.format(totalRemainingTime);\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn true;\r\n\t}\r\n\r\n\tpublic String getJobStatusString() {\r\n\t\treturn jobStatusString;\r\n\t}\r\n\r\n\tpublic boolean isTransferFileJob() {\r\n\t\treturn transferFileJob != null;\r\n\t}\r\n\r\n\tpublic int getFilePercentInt() {\r\n\t\treturn filePercentInt;\r\n\t}\r\n\r\n\tpublic String getFileProgressText() {\r\n\t\treturn fileProgressText;\r\n\t}\r\n\r\n\tpublic long getBytesTotal() {\r\n\t\treturn bytesTotal;\r\n\t}\r\n\r\n\tpublic long getTotalBps() {\r\n\t\treturn totalBps;\r\n\t}\r\n\r\n\tpublic long getLastTime() {\r\n\t\treturn lastTime;\r\n\t}\r\n\r\n\tpublic long getCurrentBps() {\r\n\t\treturn currentBps;\r\n\t}\r\n\r\n\tpublic int getTotalPercentInt() {\r\n\t\treturn totalPercentInt;\r\n\t}\r\n\r\n\tpublic String getTotalProgressText() {\r\n\t\treturn totalProgressText;\r\n\t}\r\n\r\n\tpublic long getEffectiveJobTime() {\r\n\t\treturn effectiveJobTime;\r\n\t}\r\n\r\n\tpublic long getJobPauseStartDate() {\r\n\t\treturn jobPauseStartDate;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/progress/JobProgressListener.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.job.progress;\r\n\r\nimport com.mucommander.job.FileJob;\r\n\r\nimport java.util.EventListener;\r\n\r\n/**\r\n * Interface to be implemented by classes that wish to be notified of progress changes on a particular\r\n * {@link FileJob}. Those classes need to be registered to receive those events, this can be done by calling\r\n * {@link JobProgressMonitor#addJobProgressListener(JobProgressListener)}.\r\n *\r\n * @author Mariusz Jakubowski\r\n */\r\npublic interface JobProgressListener extends EventListener {\r\n\t\r\n\t/**\r\n     * Called when a new job has been initiated.\r\n\t * \r\n\t * @param source a job added\r\n\t * @param idx index of a job in a job queue\r\n\t */\r\n\tvoid jobAdded(FileJob source, int idx);\r\n\t\r\n\t/**\r\n     * Called when a new job has finished and has been removed from the queue.\r\n\t * \r\n\t * @param source a job removed\r\n\t * @param idx index of a job in a job queue\r\n\t */\r\n\tvoid jobRemoved(FileJob source, int idx);\r\n\r\n\t/**\r\n     * Called when the progress of the specified FileJob has been updated.\r\n     *\r\n     * @param source the FileJob which progress has been updated\r\n\t * @param idx index of a job in a job queue\r\n     * @param fullUpdate if false indicates that only file label has been updated\r\n     * @see JobProgress#calcJobProgress\r\n     */\r\n\tvoid jobProgress(FileJob source, int idx, boolean fullUpdate);\r\n\t\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/progress/JobProgressMonitor.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.job.progress;\r\n\r\nimport com.mucommander.job.FileJob;\r\nimport com.mucommander.job.FileJobListener;\r\n\r\nimport javax.swing.*;\r\nimport javax.swing.event.EventListenerList;\r\nimport java.awt.event.ActionEvent;\r\nimport java.awt.event.ActionListener;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\n/**\r\n * A class that monitors jobs progress.\r\n * @author Mariusz Jakubowski\r\n *\r\n */\r\npublic class JobProgressMonitor implements FileJobListener {\r\n\t\r\n    /** Controls how often should current file label be refreshed (in ms) */\r\n\tprivate final static int CURRENT_FILE_LABEL_REFRESH_RATE = 100;\r\n\t\r\n\t/** Controls how often should progress information be refreshed */\r\n    private final static int MAIN_REFRESH_RATE = 10;\r\n    \r\n    /** Time after which remove finished job from a monitor */\r\n    private final static int FINISHED_JOB_REMOVE_TIME = 1500;\r\n\r\n    /** Timer used to monitor jobs progress */\r\n    private Timer progressTimer;\r\n\t\r\n    /** List of listeners */\r\n\tprivate EventListenerList listenerList = new EventListenerList();\r\n\t\r\n\t/** A list of monitored jobs. */\r\n\tprivate List<FileJob> jobs = new ArrayList<>();\r\n\r\n\t/** An instance of this class */\r\n\tprivate static final JobProgressMonitor instance = new JobProgressMonitor();\r\n\t\t\r\n\t\r\n\t/**\r\n\t * Creates a new JobProgressMonitor instance.\r\n\t */\r\n\tprivate JobProgressMonitor() {\r\n\t\tJobProgressTimer timerListener = new JobProgressTimer(); \r\n    \tprogressTimer = new Timer(CURRENT_FILE_LABEL_REFRESH_RATE, timerListener);\r\n\t}\r\n\t\r\n\t/**\r\n\t * Returns the instance of JobProgressMonitor.\r\n\t * @return the instance of JobProgressMonitor.\r\n\t */\r\n\tpublic static JobProgressMonitor getInstance() {\r\n\t\treturn instance;\r\n\t}\r\n    \r\n\t\r\n    /**\r\n     * Adds a listener to the list that's notified each time a job \r\n     * progress is updated.\r\n     *\r\n     * @param\tl\t\tthe JobProgressListener\r\n     */\r\n    public void addJobProgressListener(JobProgressListener l) {\r\n    \tlistenerList.add(JobProgressListener.class, l);\r\n    }\r\n\r\n    /**\r\n     * Removes a listener from the list that's notified each time job\r\n     * progress is updated.\r\n     *\r\n     * @param\tl\t\tthe JobProgressListener\r\n     */\r\n    public void removeJobProgressListener(JobProgressListener l) {\r\n    \tlistenerList.remove(JobProgressListener.class, l);\r\n    }\r\n\r\n    /**\r\n     * Forwards the progress notification event to all\r\n     * <code>JobProgressListeners</code> that registered\r\n     * themselves as listeners.\r\n     * @param source a job for which the progress has been updated\r\n     * @param fullUpdate if false only file label has been updated \r\n     * \r\n     * @see #addJobProgressListener\r\n     * @see JobProgressListener#jobProgress\r\n     */\r\n    private void fireJobProgress(FileJob source, boolean fullUpdate) {\r\n\t\tint idx = jobs.indexOf(source);\r\n    \tObject[] listeners = listenerList.getListenerList();\r\n    \tfor (int i = listeners.length-2; i>=0; i-=2) {\r\n    \t\t((JobProgressListener)listeners[i+1]).jobProgress(source, idx, fullUpdate);\r\n    \t}\r\n    }\r\n    \r\n    /**\r\n     * Forwards the job added notification event to all\r\n     * <code>JobProgressListeners</code> that registered\r\n     * themselves as listeners.\r\n     * @param source an added job \r\n     * @param idx index of a job in a list \r\n     * \r\n     * @see #addJobProgressListener\r\n     * @see JobProgressListener#jobAdded(FileJob, int)\r\n     */\r\n    private void fireJobAdded(FileJob source, int idx) {\r\n    \tObject[] listeners = listenerList.getListenerList();\r\n    \tfor (int i = listeners.length-2; i>=0; i-=2) {\r\n    \t\t((JobProgressListener)listeners[i+1]).jobAdded(source, idx);\r\n    \t}    \t\r\n    }\r\n    \r\n    /**\r\n     * Forwards the job removed notification event to all\r\n     * <code>JobProgressListeners</code> that registered\r\n     * themselves as listeners.\r\n     * @param source a removed job\r\n     * @param idx index of a job in a list \r\n     * \r\n     * @see #addJobProgressListener\r\n     * @see JobProgressListener#jobRemoved(FileJob, int)\r\n     */\r\n    private void fireJobRemoved(FileJob source, int idx) {\r\n    \tObject[] listeners = listenerList.getListenerList();\r\n    \tfor (int i = listeners.length-2; i>=0; i-=2) {\r\n    \t\t((JobProgressListener)listeners[i+1]).jobRemoved(source, idx);\r\n    \t}    \t\r\n    }\r\n\r\n    /**\r\n     * Adds a new job to the list of monitored jobs. \r\n     * This method is executed in Swing Thread (EDT).\r\n     * After adding a new job a {@link JobProgressListener#jobAdded(FileJob, int)} \r\n     * event is fired.\r\n     * @param job a job to be added\r\n     */\r\n    public void addJob(final FileJob job) {\r\n    \t// ensure that this method is called in EDT\r\n    \tif (!SwingUtilities.isEventDispatchThread()) {\r\n    \t\tSwingUtilities.invokeLater(() -> addJob(job));\r\n    \t\t\t}\r\n\r\n    \tjobs.add(job);\r\n    \tint idx = jobs.size() - 1;\r\n\t\tfireJobAdded(job, idx);    \t\t\t\r\n    \tif (!progressTimer.isRunning()) {\r\n    \t\tprogressTimer.start();\r\n    \t}\r\n    \tjob.addFileJobListener(this);\r\n    }\r\n    \r\n    /**\r\n     * Removes a job from a list of monitored jobs.\r\n     * This method is executed in Swing Thread (EDT).\r\n     * After adding a new job a {@link JobProgressListener#jobRemoved(FileJob, int)} \r\n     * event is fired.\r\n     * @param job a job to be removed\r\n     */\r\n    public void removeJob(final FileJob job) {\r\n    \t// ensure that this method is called in EDT\r\n    \tif (!SwingUtilities.isEventDispatchThread()) {\r\n    \t\tSwingUtilities.invokeLater(() -> removeJob(job));\r\n    \t\t\t}\r\n\r\n    \tint idx = jobs.indexOf(job);\r\n\t\tif (idx != -1) {\r\n\t\t\tjobs.remove(idx);\r\n\t\t}\r\n\t\tif (jobs.isEmpty()) {\r\n\t\t\tprogressTimer.stop();\r\n\t\t}\r\n\t\tfireJobRemoved(job, idx);\r\n\t\tjob.removeFileJobListener(this);\r\n    }\r\n    \r\n\t/**\r\n\t * Returns number of monitored jobs.\r\n\t * @return number of monitored jobs.\r\n\t */\r\n\tpublic int getJobCount() {\r\n\t\treturn jobs.size();\r\n\t}\r\n\r\n\t\r\n\t/**\r\n\t * Returns a progress of a job with specified index.\r\n\t * @param rowIndex an index of a job\r\n\t * @return a progress information or null if job doesn't exists\r\n\t */\r\n\tpublic JobProgress getJobProgres(int rowIndex) {\r\n\t\tif (rowIndex < jobs.size()) {\r\n\t\t\tFileJob job = jobs.get(rowIndex);\r\n\t\t\treturn job.getJobProgress();\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\r\n\t/**\r\n\t * A {@link FileJobListener} implementation.\r\n\t * Removes a finished job after a small delay.\r\n\t */\r\n\tpublic void jobStateChanged(final FileJob source, FileJob.State oldState, FileJob.State newState) {\r\n\t\tif (newState==FileJob.State.FINISHED || newState==FileJob.State.INTERRUPTED) {\r\n\t\t\tTimer timer = new Timer(FINISHED_JOB_REMOVE_TIME, (e) -> removeJob(source));\r\n\t\t\ttimer.setRepeats(false);\r\n\t\t\ttimer.start();\r\n\t\t}\t\t\r\n\t}\r\n\t\r\n\t\r\n\t\r\n\t/**\r\n     * \r\n     * This class implements a listener for a job progress timer.\r\n     *\r\n     */\r\n\tprivate class JobProgressTimer implements ActionListener {\r\n\t\t\r\n\t\t/** a loop index indicating if this refresh is partial (label only) or full */\r\n\t\tprivate int loopCount = 0;\r\n\r\n\t\tpublic void actionPerformed(ActionEvent e) {\r\n\t\t\tloopCount++;\r\n\r\n\t\t\tboolean fullUpdate;\t\t\t\r\n\t\t\tif (loopCount >= MAIN_REFRESH_RATE) {\r\n\t\t\t\tfullUpdate = true;\r\n\t\t\t\tloopCount = 0;\r\n\t\t\t} else {\r\n\t\t\t\tfullUpdate = false;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// for each job calculate new progress and notify listeners\r\n\t\t\tfor(FileJob job : jobs) {\r\n\t\t\t\tboolean updateFullUI;\r\n\t\t\t\tJobProgress jobProgress = job.getJobProgress();\r\n\t\t\t\tupdateFullUI = jobProgress.calcJobProgress(fullUpdate);\r\n\t\t\t\tfireJobProgress(job, updateFullUI);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t}\r\n\r\n\t}\r\n\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/ui/DialogResult.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.job.ui;\r\n\r\n/**\r\n * An interface marking a dialog which can return a value. \r\n * @author Mariusz Jakubowski\r\n *\r\n */\r\npublic interface DialogResult {\r\n    Object getUserInput();\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/ui/UserInputHelper.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.job.ui;\r\n\r\nimport javax.swing.SwingUtilities;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport com.mucommander.job.FileJob;\r\n\r\n/**\r\n * This class is used to show a dialog for user and get a response from\r\n * this dialog. It is used by {@link FileJob} class.\r\n * \r\n * @author Mariusz Jakubowski\r\n */\r\npublic class UserInputHelper {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(UserInputHelper.class);\r\n\t\r\n    private Object userInput;\r\n    private final DialogResult dialog;\r\n\r\n    public UserInputHelper(FileJob job, DialogResult dialog) {\r\n        this.dialog = dialog;\r\n    }\r\n    \r\n    \r\n    public Object getUserInput() {\r\n        try {\r\n            SwingUtilities.invokeAndWait(() -> userInput = dialog.getUserInput());\r\n        } catch (Exception e) {\r\n            LOGGER.debug(\"Caught exception\", e);\r\n        }\r\n        return userInput;\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/job/utils/ScanDirectoryThread.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.job.utils;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.util.FileSet;\n\nimport java.io.IOException;\n\n/**\n * Thread to calculating the total size of files\n */\npublic class ScanDirectoryThread extends Thread {\n\n    private final FileSet files;\n    private long totalBytes;\n    private boolean completed;\n    private long executionTime;\n    private long filesCount;\n    private boolean interrupted;\n    private final boolean calcSize;\n\n    public ScanDirectoryThread(FileSet files) {\n        this.files = files;\n        this.calcSize = true;\n        setName(\"ScanDirectoryThread \" + files.getBaseFolder());\n    }\n\n    public ScanDirectoryThread(FileSet files, boolean calcSize) {\n        this.files = files;\n        this.calcSize = calcSize;\n        setName(\"ScanDirectoryThread \" + files.getBaseFolder());\n    }\n\n    @Override\n    public void run() {\n        executionTime = System.currentTimeMillis();\n        for (AbstractFile file : files) {\n            if (interrupted) {\n                break;\n            }\n            try {\n                processFile(file);\n            } catch (Throwable ignore) {}\n        }\n        completed = true;\n        executionTime = System.currentTimeMillis() - executionTime;\n//System.out.println(\"finished  \" + totalBytes + \" \" + filesCount + \"    time \" + executionTime);\n    }\n\n    private void processFile(AbstractFile file) {\n        if (interrupted) {\n            return;\n        }\n        filesCount++;\n        if (file.isSymlink()) {\n            return; // ignore symlinks\n        }\n        if (file.isDirectory()) {\n            try {\n                AbstractFile[] subfiles = file.ls();\n                for (AbstractFile subfile : subfiles ) {\n                    if (interrupted) {\n                        return;\n                    }\n                    processFile(subfile);\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        } else {\n            if (calcSize) {\n                totalBytes += file.getSize();\n            }\n        }\n    }\n\n    public long getTotalBytes() {\n        return totalBytes;\n    }\n\n    public boolean isCompleted() {\n        return completed;\n    }\n\n    public long getFilesCount() {\n        return filesCount;\n    }\n\n    public void interrupt() {\n        interrupted = true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/launcher/LauncherCmdHelper.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.launcher;\n\nimport com.mucommander.PlatformManager;\nimport com.mucommander.RuntimeConstants;\nimport com.mucommander.TrolCommander;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.extension.ExtensionManager;\nimport com.mucommander.shell.ShellHistoryManager;\nimport com.mucommander.ui.main.commandbar.CommandBarIO;\nimport com.mucommander.ui.main.toolbar.ToolBarIO;\n\n/**\n * Helper class for launcher command line\n *\n * @author Maxence Bernard, Nicolas Rinaudo, Oleg Trifonov\n */\npublic class LauncherCmdHelper {\n\tprivate static Logger logger;\n\n    /**\n     * Whether to display verbose error messages.\n     */\n    private boolean verbose;\n\n    /**\n     * Whether to ignore warnings when booting.\n     */\n    private boolean fatalWarnings;\n\n    /**\n     * Index in the command line arguments.\n     */\n    private int index;\n\n    private final String[] args;\n\n\n    public LauncherCmdHelper(String[] args, boolean verbose, boolean fatalWarnings) {\n        this.args = args;\n        this.verbose = verbose;\n        this.fatalWarnings = fatalWarnings;\n    }\n\n\n    // - Commandline handling methods -------------------------------------------\n    // --------------------------------------------------------------------------\n    /**\n     * Prints muCommander's command line usage and exits.\n     */\n    private static void printUsage() {\n        System.out.println(\"Usage: trolcommander [options] [folders]\");\n        System.out.println(\"Options:\");\n\n        // Allows users to tweak how file associations are loaded / saved.\n        System.out.println(\" -a FILE, --assoc FILE             Load associations from FILE.\");\n\n        // Allows users to tweak how bookmarks are loaded / saved.\n        System.out.println(\" -b FILE, --bookmarks FILE         Load bookmarks from FILE.\");\n\n        // Allows users to tweak how configuration is loaded / saved.\n        System.out.println(\" -c FILE, --configuration FILE     Load configuration from FILE\");\n\n        // Allows users to tweak how command bar configuration is loaded / saved.\n        System.out.println(\" -C FILE, --commandbar FILE        Load command bar from FILE.\");\n\n        // Allows users to change the extension's folder.\n        System.out.println(\" -e FOLDER, --extensions FOLDER    Load extensions from FOLDER.\");\n\n        // Allows users to tweak how custom commands are loaded / saved.\n        System.out.println(\" -f FILE, --commands FILE          Load custom commands from FILE.\");\n\n        // Ignore warnings.\n        System.out.println(\" -index, --ignore-warnings             Do not fail on warnings (default).\");\n\n        // Allows users to tweak how keymaps are loaded.\n        System.out.println(\" -k FILE, --keymap FILE            Load keymap from FILE\");\n\n        // Allows users to change the preference's folder.\n        System.out.println(\" -p FOLDER, --preferences FOLDER   Store configuration files in FOLDER\");\n\n        // muCommander will not print verbose error messages.\n        System.out.println(\" -S, --silent                      Do not print verbose error messages\");\n\n        // Allows users to tweak how shell history is loaded / saved.\n        System.out.println(\" -s FILE, --shell-history FILE     Load shell history from FILE\");\n\n        // Allows users to tweak how toolbar configuration are loaded.\n        System.out.println(\" -t FILE, --toolbar FILE           Load toolbar from FILE\");\n\n        // Allows users to tweak how credentials are loaded.\n        System.out.println(\" -u FILE, --credentials FILE       Load credentials from FILE\");\n\n        // Text commands.\n        System.out.println(\" -h, --help                        Print the help text and exit\");\n        System.out.println(\" -v, --version                     Print the version and exit\");\n\n        // muCommander will print verbose boot error messages.\n        System.out.println(\" -V, --verbose                     Print verbose error messages (default)\");\n\n        // Pedantic mode.\n        System.out.println(\" -w, --fail-on-warnings            Quits when a warning is encountered during\");\n        System.out.println(\"                                   the boot process.\");\n        System.exit(0);\n    }\n\n    /**\n     * Prints muCommander's version to stdout and exits.\n     */\n    private static void printVersion() {\n        System.out.println(RuntimeConstants.APP_STRING);\n        System.out.print(\"Copyright (C) \");\n        System.out.print(RuntimeConstants.COPYRIGHT);\n        System.out.println(\" Maxence Bernard\");\n        System.out.println(\"This is free software, distributed under the terms of the GNU General Public License.\");\n        System.exit(0);\n    }\n\n\n    public void parseArgs() {\n        label:\n        for (index = 0; index < args.length; index++) {\n            // Print version.\n            switch (args[index]) {\n                case \"-v\":\n                case \"--version\":\n                    printVersion();\n                    break;\n\n                // Print help.\n                case \"-h\":\n                case \"--help\":\n                    printUsage();\n                    break;\n\n                // Associations handling.\n                case \"-a\":\n                case \"--assoc\":\n                    if (index >= args.length - 1)\n                        printError(\"Missing FILE parameter to \" + args[index], null, true);\n                    try {\n                        com.mucommander.command.CommandManager.setAssociationFile(args[++index]);\n                    } catch (Exception e) {\n                        printError(\"Could not set association files\", e, fatalWarnings);\n                    }\n                    break;\n\n                // Custom commands handling.\n                case \"-f\":\n                case \"--commands\":\n                    if (index >= args.length - 1)\n                        printError(\"Missing FILE parameter to \" + args[index], null, true);\n                    try {\n                        com.mucommander.command.CommandManager.setCommandFile(args[++index]);\n                    } catch (Exception e) {\n                        printError(\"Could not set commands file\", e, fatalWarnings);\n                    }\n                    break;\n\n                // Bookmarks handling.\n                case \"-b\":\n                case \"--bookmarks\":\n                    if (index >= args.length - 1)\n                        printError(\"Missing FILE parameter to \" + args[index], null, true);\n                    try {\n                        com.mucommander.bookmark.BookmarkManager.setBookmarksFile(args[++index]);\n                    } catch (Exception e) {\n                        printError(\"Could not set bookmarks file\", e, fatalWarnings);\n                    }\n                    break;\n\n                // Configuration handling.\n                case \"-c\":\n                case \"--configuration\":\n                    if (index >= args.length - 1)\n                        printError(\"Missing FILE parameter to \" + args[index], null, true);\n                    try {\n                        TcConfigurations.setPreferencesFile(args[++index]);\n                    } catch (Exception e) {\n                        printError(\"Could not set configuration file\", e, fatalWarnings);\n                    }\n                    break;\n\n                // Shell history.\n                case \"-s\":\n                case \"--shell-history\":\n                    if (index >= args.length - 1)\n                        printError(\"Missing FILE parameter to \" + args[index], null, true);\n                    try {\n                        ShellHistoryManager.setHistoryFile(args[++index]);\n                    } catch (Exception e) {\n                        printError(\"Could not set shell history file\", e, fatalWarnings);\n                    }\n                    break;\n\n                // Keymap file.\n                case \"-k\":\n                case \"--keymap\":\n                    if (index >= args.length - 1)\n                        printError(\"Missing FILE parameter to \" + args[index], null, true);\n                    try {\n                        com.mucommander.ui.action.ActionKeymapIO.setActionsFile(args[++index]);\n                    } catch (Exception e) {\n                        printError(\"Could not set keymap file\", e, fatalWarnings);\n                    }\n                    break;\n\n                // Toolbar file.\n                case \"-t\":\n                case \"--toolbar\":\n                    if (index >= args.length - 1)\n                        printError(\"Missing FILE parameter to \" + args[index], null, true);\n                    try {\n                        ToolBarIO.setDescriptionFile(args[++index]);\n                    } catch (Exception e) {\n                        printError(\"Could not set keymap file\", e, fatalWarnings);\n                    }\n                    break;\n\n                // Commandbar file.\n                case \"-C\":\n                case \"--commandbar\":\n                    if (index >= args.length - 1)\n                        printError(\"Missing FILE parameter to \" + args[index], null, true);\n                    try {\n                        CommandBarIO.setDescriptionFile(args[++index]);\n                    } catch (Exception e) {\n                        printError(\"Could not set commandbar description file\", e, fatalWarnings);\n                    }\n                    break;\n\n                // Credentials file.\n                case \"-U\":\n                case \"--credentials\":\n                    if (index >= args.length - 1)\n                        printError(\"Missing FILE parameter to \" + args[index], null, true);\n                    try {\n                        com.mucommander.auth.CredentialsManager.setCredentialsFile(args[++index]);\n                    } catch (Exception e) {\n                        printError(\"Could not set credentials file\", e, fatalWarnings);\n                    }\n                    break;\n\n                // Preference folder.\n                case \"-p\":\n                case \"--preferences\":\n                    if (index >= args.length - 1)\n                        printError(\"Missing FOLDER parameter to \" + args[index], null, true);\n                    try {\n                        PlatformManager.setPreferencesFolder(args[++index]);\n                    } catch (Exception e) {\n                        printError(\"Could not set preferences folder\", e, fatalWarnings);\n                    }\n                    break;\n\n                // Extensions folder.\n                case \"-e\":\n                case \"--extensions\":\n                    if (index >= args.length - 1)\n                        printError(\"Missing FOLDER parameter to \" + args[index], null, true);\n                    try {\n                        ExtensionManager.setExtensionsFolder(args[++index]);\n                    } catch (Exception e) {\n                        printError(\"Could not set extensions folder\", e, fatalWarnings);\n                    }\n                    break;\n\n                // Ignore warnings.\n                case \"-index\":\n                case \"--ignore-warnings\":\n                    fatalWarnings = false;\n                    break;\n\n                // Fail on warnings.\n                case \"-w\":\n                case \"--fail-on-warnings\":\n                    fatalWarnings = true;\n                    break;\n\n                // Silent mode.\n                case \"-S\":\n                case \"--silent\":\n                    verbose = false;\n                    break;\n\n                // Verbose mode.\n                case \"-V\":\n                case \"--verbose\":\n                    verbose = true;\n                    break;\n\n                // Illegal argument.\n                default:\n                    break label;\n            }\n        }\n    }\n\n    /**\n     * Prints an error message.\n     */\n    private static void printError(String msg, boolean quit) {\n        if (quit) {\n        \tgetLogger().error(msg);\n            System.err.println(\"See trolcommander --help for more information.\");\n            System.exit(1);\n        } else{\n        \tgetLogger().warn(msg);\n        }\n    }\n\n    /**\n     * Prints a configuration file specific error message.\n     */\n    void printFileError(String msg, Throwable exception, boolean quit) {\n        StringBuilder error;\n\n        error = createErrorMessage(msg, exception, quit);\n        if (!quit) {\n            error.append(\". Using default values.\");\n        }\n\n        printError(error.toString(), quit);\n    }\n\n    public void printFileError(String msg, Throwable exception) {\n        printFileError(msg, exception, fatalWarnings);\n    }\n\n    /**\n     * Prints the specified error message to stderr.\n     * @param msg       error message to print to stderr.\n     * @param quit      whether to quit after printing the error message.\n     * @param exception exception that triggered the error (for verbose output).\n     */\n    public void printError(String msg, Exception exception, boolean quit) {\n        printError(createErrorMessage(msg, exception, quit).toString(), quit);\n    }\n\n    /**\n     * Creates an error message.\n     */\n    private StringBuilder createErrorMessage(String msg, Throwable exception, boolean quit) {\n        StringBuilder error = new StringBuilder();\n        if (quit) {\n            error.append(\"Warning: \");\n        }\n        error.append(msg);\n        if (verbose && exception != null) {\n            error.append(\": \").append(exception.getMessage());\n        }\n\n        return error;\n    }\n\n    public String[] getFolders() {\n        String[] folders = new String[args.length - index];\n        System.arraycopy(args, index, folders, 0, folders.length);\n        return folders;\n    }\n\n\n    private static Logger getLogger() {\n        if (logger == null) {\n            logger = LoggerFactory.getLogger(TrolCommander.class);\n        }\n        return logger;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/launcher/LauncherExecutor.kt",
    "content": "package com.mucommander.launcher\n\nimport java.util.concurrent.LinkedBlockingQueue\nimport java.util.concurrent.ThreadPoolExecutor\nimport java.util.concurrent.TimeUnit\n\nclass LauncherExecutor(\n    private val cores: Int\n) : ThreadPoolExecutor(\n    cores,  cores, 0L, TimeUnit.MILLISECONDS, LinkedBlockingQueue()\n) {\n    private val runningTasks: MutableSet<LauncherTask> = HashSet()\n\n    fun isFull(): Boolean {\n        if (runningTasks.size < cores) {\n            return false\n        }\n        runningTasks.removeIf { obj: LauncherTask? -> obj!!.isDone() }\n        return runningTasks.size >= cores\n    }\n\n    fun execute(task: LauncherTask, force: Boolean): Boolean {\n        if (force || (runningTasks.size < cores && task.isReadyForExecution())) {\n            super.execute(task.task)\n            runningTasks.add(task)\n            return true\n        }\n        return false\n    }\n\n    fun executeFirst(tasks: MutableCollection<LauncherTask>): Boolean {\n        val it = tasks.iterator()\n        while (it.hasNext()) {\n            val task = it.next()\n            if (execute(task, false)) {\n                it.remove()\n                return true\n            }\n        }\n        return false\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/launcher/LauncherTask.kt",
    "content": "package com.mucommander.launcher\n\nimport com.mucommander.profiler.Profiler\nimport java.util.concurrent.Callable\nimport java.util.concurrent.FutureTask\n\nclass LauncherTask (\n    name: String,\n    private val handler: Runnable,\n) : Callable<Void?> {\n    private val name = \"launcher.$name\"\n    private var depends: List<LauncherTask>? = null\n    internal val task: FutureTask<Void?> = FutureTask<Void?>(this)\n\n    fun depends(vararg tasks: LauncherTask): LauncherTask {\n        depends = tasks.toList()\n        return this\n    }\n\n    //@Throws(Exception::class)\n    override fun call(): Void? {\n        if (!depends.isNullOrEmpty()) {\n            Profiler.start(\"$name.depends\")\n            for (t in depends) {\n                t.task.get()\n            }\n            Profiler.stop(\"$name.depends\")\n        }\n        Profiler.start(name)\n        try {\n            handler.run()\n        } catch (e: Throwable) {\n            printError(\"Launcher getTask error for $name: \", e)\n        }\n        Profiler.stop(name)\n        onFinish()\n        return null\n    }\n\n    fun isReadyForExecution(): Boolean {\n        if (depends.isNullOrEmpty()) {\n            return true\n        }\n        for (dt in depends) {\n            if (!dt.isDone()) {\n                return false\n            }\n        }\n        return true\n    }\n\n    fun isDone(): Boolean = task.isDone()\n\n    fun onFinish() {\n    }\n\n    override fun toString() = name\n\n    fun printError(msg: String, e: Throwable) {\n        println(msg)\n        e.printStackTrace()\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/launcher/ShutdownHook.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.launcher;\n\nimport com.mucommander.auth.CredentialsManager;\nimport com.mucommander.bookmark.BookmarkManager;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.shell.ShellHistoryManager;\nimport com.mucommander.ui.action.ActionKeymapIO;\nimport com.mucommander.ui.main.commandbar.CommandBarIO;\nimport com.mucommander.ui.main.toolbar.ToolBarIO;\nimport com.mucommander.ui.main.tree.TreeIOThreadManager;\nimport com.mucommander.ui.notifier.AbstractNotifier;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * The init method of this thread is called when the program shuts down, either because\n * the user chose to quit the program or because the program was interrupted by a logoff.\n * @author Maxence Bernard\n */\npublic class ShutdownHook extends Thread {\n\tprivate static Logger logger;\n\t\n    /** Whether shutdown tasks have been performed already. */\n    private static boolean shutdownTasksPerformed;\n\n    /**\n     * Creates a new <code>ShutdownHook</code>.\n     */\n    ShutdownHook() {\n        super(ShutdownHook.class.getName());\n    }\n\n\n    /**\n     * Shuts down muCommander.\n     */\n    public static void initiateShutdown() {\n        getLogger().info(\"shutting down\");\n\n//            // No need to call System.exit() under Java 1.4, application will naturally exit\n//            // when no there is no more window showing and no non-daemon thread still running.\n//            // However, natural application death will not trigger ShutdownHook so we need to explicitly\n//            // perform shutdown tasks.\n//            performShutdownTasks();\n\n        // System.exit() will trigger ShutdownHook and perform shutdown tasks\n        System.exit(0);\n    }\n    \n\n    /**\n     * Called by the VM when the program shuts down, this method writes the configuration.\n     */\n    @Override\n    public void run() {\n        performShutdownTasks();\n    }\n\n\n    /**\n     * Performs tasks before shut down, such as writing the configuration file. This method can only\n     * be called once, any further call will be ignored (no-op).\n     */\n    private synchronized static void performShutdownTasks() {\n        // Return if shutdown tasks have already been performed\n        if (shutdownTasksPerformed) {\n            return;\n        }\n        \n        TreeIOThreadManager.getInstance().interrupt();\n\n        // Save snapshot\n        try {\n            TcConfigurations.saveSnapshot();\n        } catch(Exception e) {\n            getLogger().warn(\"Failed to save snapshot\", e);\n        }\n        \n        // Save preferences\n        // Don't need to save preferences on shutdown because it saves in Preferences edit dialog on Ok pressed\n        try {\n            TcConfigurations.savePreferences();\n        } catch(Exception e) {\n            getLogger().warn(\"Failed to save configuration\", e);\n        }\n\n        // Save shell history\n        try {\n            ShellHistoryManager.writeHistory();\n        } catch(Exception e) {\n            getLogger().warn(\"Failed to save shell history\", e);\n        }\n\n        // Write credentials file to disk, only if changes were made\n        try {\n            CredentialsManager.writeCredentials(false);\n        } catch(Exception e) {\n            getLogger().warn(\"Failed to save credentials\", e);\n        }\n\n        // Write bookmarks file to disk, only if changes were made\n        try {\n            BookmarkManager.writeBookmarks(false);\n        } catch(Exception e) {\n            getLogger().warn(\"Failed to save bookmarks\", e);\n        }\n\n        // Saves the current theme.\n//        try {ThemeManager.saveCurrentTheme();}\n//        catch(Exception e) {LOGGER.warn(\"Failed to save user theme\", e);}\n\n        // Saves the file associations.\n//        try {CommandManager.writeCommands();}\n//        catch(Exception e) {LOGGER.warn(\"Failed to save commands\", e);}\n//        try {CommandManager.writeAssociations();}\n//        catch(Exception e) {LOGGER.warn(\"Failed to save associations\", e);}\n        \n        // Saves the action keymap.\n        try {\n            ActionKeymapIO.saveActionKeymap();\n        } catch(Exception e) {\n            getLogger().warn(\"Failed to save action keymap\", e);\n        }\n        \n        // Saves the command bar.\n        try {\n            CommandBarIO.saveCommandBar();\n        } catch(Exception e) {\n            getLogger().warn(\"Failed to save command bar\", e);\n        }\n        \n        // Saves the toolbar.\n        try {\n            ToolBarIO.saveToolBar();\n        } catch(Exception e) {\n            getLogger().warn(\"Failed to save toolbar\", e);\n        }\n\n        var notifier = AbstractNotifier.getNotifier();\n        if (notifier.isEnabled()) {\n            notifier.setEnabled(false);\n        }\n\n        // Shutdown tasks should only be performed once\n        shutdownTasksPerformed = true;\n    }\n\n    private static Logger getLogger() {\n        if (logger == null) {\n            logger = LoggerFactory.getLogger(ShutdownHook.class);\n        }\n        return logger;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/launcher/tasks.kt",
    "content": "package com.mucommander.launcher\n\nimport com.formdev.flatlaf.FlatDarculaLaf\nimport com.formdev.flatlaf.FlatDarkLaf\nimport com.formdev.flatlaf.FlatIntelliJLaf\nimport com.formdev.flatlaf.FlatLightLaf\nimport com.formdev.flatlaf.themes.FlatMacDarkLaf\nimport com.formdev.flatlaf.themes.FlatMacLightLaf\nimport com.mucommander.RuntimeConstants\nimport com.mucommander.TrolCommander\nimport com.mucommander.auth.CredentialsManager\nimport com.mucommander.bonjour.BonjourDirectory\nimport com.mucommander.bookmark.BookmarkManager\nimport com.mucommander.bookmark.file.BookmarkProtocolProvider\nimport com.mucommander.command.CommandManager\nimport com.mucommander.commons.file.FileFactory\nimport com.mucommander.commons.file.icon.impl.SwingFileIconProvider\nimport com.mucommander.commons.file.impl.ftp.FTPProtocolProvider\nimport com.mucommander.commons.file.impl.smb.SMBProtocolProvider\nimport com.mucommander.commons.runtime.OsFamily\nimport com.mucommander.commons.runtime.OsVersion\nimport com.mucommander.conf.TcConfigurations\nimport com.mucommander.conf.TcPreference\nimport com.mucommander.conf.TcPreferences\nimport com.mucommander.desktop.DesktopManager\nimport com.mucommander.extension.ExtensionManager\nimport com.mucommander.ui.PreloadedJFrame\nimport com.mucommander.profiler.Profiler\nimport com.mucommander.shell.ShellHistoryManager\nimport com.mucommander.ui.action.ActionKeymapIO\nimport com.mucommander.ui.action.ActionManager\nimport com.mucommander.ui.dialog.about.AboutDialog\nimport com.mucommander.ui.dialog.pref.general.GeneralPreferencesDialog\nimport com.mucommander.ui.dialog.startup.InitialSetupDialog\nimport com.mucommander.ui.icon.FileIcons\nimport com.mucommander.ui.main.SplashScreen\nimport com.mucommander.ui.main.WindowManager\nimport com.mucommander.ui.main.commandbar.CommandBarIO\nimport com.mucommander.ui.main.frame.CommandLineMainFrameBuilder\nimport com.mucommander.ui.main.frame.DefaultMainFramesBuilder\nimport com.mucommander.ui.main.toolbar.ToolBarIO\nimport com.mucommander.ui.notifier.AbstractNotifier\nimport com.mucommander.ui.theme.ThemeManager\nimport com.mucommander.ui.tools.ToolsEnvironment\nimport com.mucommander.utils.TcLogging\nimport com.mucommander.utils.text.CustomDateFormat\nimport com.mucommander.utils.text.Translator\nimport org.slf4j.LoggerFactory\nimport java.awt.Desktop\nimport java.awt.GraphicsEnvironment\nimport java.awt.desktop.AboutEvent\nimport java.awt.event.KeyEvent\nimport java.lang.reflect.Constructor\nimport java.util.*\nimport javax.swing.KeyStroke\nimport javax.swing.UIManager\nimport kotlin.math.max\nimport kotlin.system.exitProcess\n\n\nprivate var splashScreen: SplashScreen? = null\n\nfun prepareLauncherTasks(helper: LauncherCmdHelper): List<LauncherTask> {\n    val prepareLoggerTask = LauncherTask(\"prepare_logger\") {\n        LoggerFactory.getLogger(TrolCommander::class.java)\n    }\n    val prepareGraphicsTask = LauncherTask(\"prepare_graphics\") {\n        GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames()\n    }\n    val loadPreferencesTask = LauncherTask(\"load_preferences\") {\n        TcConfigurations.getPreferences()\n    }\n    val installFlatLightLafTask = LauncherTask(\"install_flat_light_laf\") {\n        FlatLightLaf.installLafInfo()\n    }\n    val installFlatDarculaLafTask = LauncherTask(\"install_flat_dracula_laf\") {\n        FlatDarculaLaf.installLafInfo()\n    }\n    val installFlatDarkLafTask = LauncherTask(\"install_flat_dark_laf\") {\n        FlatDarkLaf.installLafInfo()\n    }\n    val installFlatIntelliJLafTask = LauncherTask(\"install_flat_intellij_laf\") {\n        FlatIntelliJLaf.installLafInfo()\n    }\n    val installFlatMacLightLafTask = LauncherTask(\"install_flat_maclight_laf\") {\n        FlatMacLightLaf.installLafInfo()\n    }\n    val installFlatMacDarkLafTask = LauncherTask(\"install_flat_macdark_laf\") {\n        FlatMacDarkLaf.installLafInfo()\n    }\n    val installVaquaLafTask = LauncherTask(\"install_aqua_lf\") {\n        if (OsFamily.getCurrent() == OsFamily.MAC_OS_X && OsVersion.MAC_OS_X_10_13.isCurrentLower()) {\n            val aquaLaf = org.violetlib.aqua.AquaLookAndFeel()\n            UIManager.installLookAndFeel(UIManager.LookAndFeelInfo(aquaLaf.getName(), aquaLaf.javaClass.getName()))\n        }\n    }\n    val prepareKeystrokeClassTask = LauncherTask(\"prepare_keystroke\") {\n        KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.SHIFT_DOWN_MASK)\n    }\n    val loadConfigsTask = LauncherTask(\"config\") {\n        loadConfigs(helper)\n    }\n    val loadEnvTask = LauncherTask(\"load_env\") {\n        ToolsEnvironment.load()\n    }\n    val loadBookmarksTask = LauncherTask(\"load_bookmarks\") {\n        BookmarkManager.loadBookmarks()\n    }\n    val loadIconsTask = LauncherTask(\"load_file_icons\") {\n        // Initialize the SwingFileIconProvider from the main thread, see method Javadoc for an explanation on why we do this now\n        SwingFileIconProvider.forceInit()\n    }\n    val registerArchiveProtocolsTask = LauncherTask(\"register_archive_protocols\") {\n        FileFactory.registerProtocolArchives()\n    }\n    val registerNetworkProtocolsTask = LauncherTask(\"register_network_protocols\") {\n        FileFactory.registerProtocolNetworks()\n    }\n    val registerOtherProtocolsTask = LauncherTask(\"register_other_protocols\") {\n        FileFactory.registerProtocolOthers()\n    }\n    val loadCredentialsTask = LauncherTask(\"load_credentials\") {\n        CredentialsManager.loadCredentials()\n    }\n    val loadCustomCommandsTask = LauncherTask(\"load_custom_commands\") {\n        loadCustomCommands(helper)\n    }\n    val loadShellHistoryTask = LauncherTask(\"load_shell_history\") {\n        ShellHistoryManager.loadHistory()\n    }\n    val configureFileSystemTask = LauncherTask(\"configure_files\") {\n        configureFileSystems()\n    }\n    val initMacOsSupportTask = LauncherTask(\"init_macos_support\") {\n        initMacOsSupport(helper)\n    }\n    val startBonjourTask = LauncherTask(\"start_bonjour\") {\n        BonjourDirectory.setActive(isBonjourEnabled())\n    }\n    val preloadedFramesTask = LauncherTask(\"preloaded_frames\") {\n        PreloadedJFrame.init();\n    }\n\n\n    val startTask = LauncherTask(\"start\") {\n        start(helper)\n    }.depends(initMacOsSupportTask)\n    val initCustomDateFormatTask = LauncherTask(\"init_custom_date_format\") {\n        CustomDateFormat.init()\n    }.depends(loadConfigsTask)\n    val loadDictionaryTask = LauncherTask(\"load_dictionary\") {\n        Translator.init()\n    }.depends(loadConfigsTask)\n    val showSplashTask = LauncherTask(\"show_splash\") {\n        if (isShowSplash()) {\n            splashScreen = SplashScreen(RuntimeConstants.VERSION, \"Loading preferences...\")\n        }\n    }.depends(loadPreferencesTask, loadConfigsTask)\n    val loadThemeTask = LauncherTask(\"load_theme\") {\n        ThemeManager.loadCurrentTheme()\n    }.depends(showSplashTask, prepareGraphicsTask)\n    val registerActionsTask = LauncherTask(\"register_actions\") {\n        ActionManager.registerActions()\n    }.depends(prepareKeystrokeClassTask, loadDictionaryTask)\n    val initDesktopTask = LauncherTask(\"init_desktop\") {\n        initDesktop()\n    }.depends(loadConfigsTask)\n    val initBarsTask = LauncherTask(\"init_bars\") {\n        initBarFiles()\n    }.depends(registerActionsTask)\n    val enableNotificationsTask = LauncherTask(\"enable_notifications\") {\n        enableNotifications()\n    }.depends(registerActionsTask)\n    val prepareWindowManagerTask = LauncherTask(\"prepared_window_manager\") {\n        WindowManager.setupAfterCreation()\n    }\n    val createMainWindowTask = LauncherTask(\"create_main_window\") {\n        createMainWindow(helper)\n    }.depends(loadThemeTask, showSplashTask, initDesktopTask, registerActionsTask, loadCustomCommandsTask, prepareWindowManagerTask)\n    val disposeSplashTask = LauncherTask(\"dispose_splash\") {\n        splashScreen?.dispose()\n        splashScreen = null\n    }.depends(showSplashTask, createMainWindowTask)\n    val showSetupWindowTask = LauncherTask(\"show_setup_window\") {\n        val showSetup = TcConfigurations.getPreferences().getVariable(TcPreference.THEME_TYPE) == null\n        if (showSetup) {\n            InitialSetupDialog(WindowManager.getCurrentMainFrame().jFrame).showDialog()\n        }\n    }.depends(loadConfigsTask)\n\n    return LinkedList(\n        listOf(\n            prepareLoggerTask,\n            prepareGraphicsTask,\n            loadPreferencesTask,\n            installFlatLightLafTask,\n            preloadedFramesTask,\n\n            installFlatDarculaLafTask,\n            installFlatDarkLafTask,\n            installFlatIntelliJLafTask,\n            installFlatMacLightLafTask,\n            installFlatMacDarkLafTask,\n            installVaquaLafTask,\n            prepareKeystrokeClassTask,\n            initMacOsSupportTask,\n        registerActionsTask,\n            loadConfigsTask,\n            startTask,\n            loadIconsTask,\n            showSplashTask,\n            configureFileSystemTask,\n            loadThemeTask,\n        loadDictionaryTask,\n            loadCustomCommandsTask,\n            loadBookmarksTask,\n            loadCredentialsTask,\n            loadShellHistoryTask,\n            initCustomDateFormatTask,\n            startBonjourTask,\n            initBarsTask,\n            prepareWindowManagerTask,\n        createMainWindowTask,\n            enableNotificationsTask,\n            initDesktopTask,\n\n\n            registerArchiveProtocolsTask,\n            registerNetworkProtocolsTask,\n            registerOtherProtocolsTask,\n            disposeSplashTask,\n\n            showSetupWindowTask,\n            loadEnvTask,\n        ))\n\n//        tasks.add(taskDisposeSplash);\n//        tasks.add(taskShowSetupWindow);\n//        tasks.add(taskLoadEnvironment);\n\n}\n\nprivate fun initDesktop() {\n    try {\n        val install = !TcConfigurations.isPreferencesFileExists()\n        DesktopManager.init(install)\n    } catch (e: Exception) {\n        System.err.println(\"Could not initialize desktop\")\n        e.printStackTrace()\n        exitProcess(1)\n    }\n}\n\nfun start(helper: LauncherCmdHelper) {\n    // Checks whether a graphics environment is available and exit with an error otherwise.\n    if (GraphicsEnvironment.isHeadless()) {\n        System.err.println(\"Error: no graphical environment detected.\")\n        exitProcess(1)\n    }\n    try {\n        TcLogging.configureLogging()\n    } catch (e: Exception) {\n        helper.printFileError(\"Configure logging error\", e)\n    }\n\n    // Adds all extensions to the classpath.\n    try {\n        Profiler.start(\"init-extensions-manager\")\n        ExtensionManager.init()\n        Profiler.stop(\"init-extensions-manager\")\n        ExtensionManager.addExtensionsToClasspath()\n    } catch (e: Exception) {\n        helper.printFileError(\"Failed to add extensions to the classpath\", e)\n    }\n\n    // This the property is supposed to have the java.net package use the proxy defined in the system settings\n    // to establish HTTP connections. This property is supported only under Java 1.5 and up.\n    // Note that Mac OS X already uses the system HTTP proxy, with or without this property being set.\n    System.setProperty(\"java.net.useSystemProxies\", \"true\")\n\n    //boolean showSetup = MuConfigurations.getPreferences().getVariable(MuPreference.THEME_TYPE) == null;\n    // Traps VM shutdown\n    Runtime.getRuntime().addShutdownHook(ShutdownHook())\n}\n\n\nprivate fun createMainWindow(helper: LauncherCmdHelper) {\n    println(\"Initializing window...\")\n    Profiler.start(\"launcher.create-window\")\n    WindowManager.createNewMainFrame(CommandLineMainFrameBuilder(helper.getFolders()))\n\n    // If no initial path was specified, start a default main window.\n    if (WindowManager.getCurrentMainFrame() == null) {\n        val mainFrameBuilder = DefaultMainFramesBuilder()\n        WindowManager.createNewMainFrame(mainFrameBuilder)\n    }\n\n    Profiler.stop(\"launcher.create-window\")\n    Profiler.stop(\"loading\")\n    Profiler.print()\n    Profiler.hide(\"launcher.\")\n}\n\nfun initMacOsSupport(helper: LauncherCmdHelper) {\n    // If trolCommander is running under Mac OS X (how lucky!), add some glue for the main menu bar and other OS X specifics.\n    if (OsFamily.MAC_OS_X.isCurrent) {\n        // Use reflection to create an OSXIntegration instance so that ClassLoader\n        // doesn't throw an NoClassDefFoundException under platforms other than Mac OS X\n        try {\n            val osxIntegrationClass = Class.forName(\"com.mucommander.ui.macosx.OSXIntegration\")\n            val constructor: Constructor<*> = osxIntegrationClass.getConstructor()\n            constructor.newInstance()\n        } catch (e: java.lang.Exception) {\n            helper.printFileError(\"Exception thrown while initializing Mac OS X integration\", e)\n        }\n        val desktop = Desktop.getDesktop()\n        if (desktop.isSupported(Desktop.Action.APP_ABOUT)) {\n            desktop.setAboutHandler { _: AboutEvent? -> AboutDialog((WindowManager.getCurrentMainFrame())).showDialog() }\n        }\n        if (desktop.isSupported(Desktop.Action.APP_PREFERENCES)) {\n            desktop.setAboutHandler {\n                _: AboutEvent? -> GeneralPreferencesDialog.getDialog().showDialog()\n            }\n        }\n    }\n}\n\n\nprivate fun loadConfigs(helper: LauncherCmdHelper) {\n    // Attempts to guess whether this is the first time trolCommander is booted or not.\n    //boolean isFirstBoot;\n    //try {isFirstBoot = !MuConfigurations.isPreferencesFileExists();}\n    //catch(IOException e) {isFirstBoot = true;}\n\n    // Load snapshot data before loading configuration as until version 0.9 the snapshot properties\n    // were stored as preferences so when loading such preferences they could overload snapshot properties\n    try {\n        TcConfigurations.loadSnapshot()\n    } catch (e: Exception) {\n        helper.printFileError(\"Could not load snapshot\", e)\n    }\n\n    // Configuration needs to be loaded before any sort of GUI creation is performed\n    // under Mac OS X, if we're to use the metal look, we need to know about it right about now.\n    try {\n        TcConfigurations.loadPreferences()\n    } catch (e: Exception) {\n        helper.printFileError(\"Could not load configuration\", e)\n    }\n\n    // The math.max(1.0f, ...) part is to workaround a bug which cause(d) this value to be set to 0.0 in the configuration file.\n    FileIcons.setScaleFactor(\n        max(1.0f, TcConfigurations.getPreferences().getVariable(TcPreference.TABLE_ICON_SCALE, TcPreferences.DEFAULT_TABLE_ICON_SCALE))\n    )\n    FileIcons.setSystemIconsPolicy(\n        TcConfigurations.getPreferences().getVariable(TcPreference.USE_SYSTEM_FILE_ICONS, TcPreferences.DEFAULT_USE_SYSTEM_FILE_ICONS)\n    )\n}\n\nprivate fun loadCustomCommands(helper: LauncherCmdHelper) {\n    try {\n        CommandManager.loadCommands()\n    } catch (e: java.lang.Exception) {\n        helper.printFileError(\"Could not load custom commands\", e)\n    }\n    // Migrates the custom editor and custom viewer if necessary.\n    TrolCommander.migrateCommand(\"viewer.use_custom\", \"viewer.custom_command\", CommandManager.VIEWER_ALIAS)\n    TrolCommander.migrateCommand(\"editor.use_custom\", \"editor.custom_command\", CommandManager.EDITOR_ALIAS)\n    try {\n        CommandManager.writeCommands()\n    } catch (e: java.lang.Exception) {\n        helper.printFileError(\"Caught exception\", e)\n        // There's really nothing we can do about this...\n    }\n\n    try {\n        CommandManager.loadAssociations()\n    } catch (e: java.lang.Exception) {\n        helper.printFileError(\"Could not load custom associations\", e)\n    }\n\n    ActionManager.registerCommandsActions()\n}\n\n\nprivate fun configureFileSystems() {\n    // Configure the SMB subsystem (backed by jCIFS) to maintain compatibility with SMB servers that don't support\n    // NTLM v2 authentication such as Samba 3.0.x, which still is widely used and comes pre-installed on\n    // Mac OS X Leopard.\n    // Since jCIFS 1.3.0, the default is to use NTLM v2 authentication and extended security.\n    SMBProtocolProvider.setSmbLmCompatibility(isSmbLmCompatibilityEnabled())\n    SMBProtocolProvider.setExtendedSecurity(isSmbExtendedSecurityEnabled())\n\n\n    // Use the FTP configuration option that controls whether to force the display of hidden files, or leave it for\n    // the servers to decide whether to show them.\n    FTPProtocolProvider.setForceHiddenFilesListing(isListHiddenFiles())\n\n\n    //            FileFactory.registerProtocolFile();\n    // Use CredentialsManager for file URL authentication\n    FileFactory.setDefaultAuthenticator(CredentialsManager.getAuthenticator())\n\n\n    // Register the application-specific 'bookmark' protocol.\n    FileFactory.registerProtocol(BookmarkProtocolProvider.BOOKMARK, BookmarkProtocolProvider())\n}\n\nprivate fun initBarFiles() {\n    println(\"Loading actions shortcuts...\")\n    try {\n        ActionKeymapIO.loadActionKeymap()\n    } catch (e: Exception) {\n        printError(\"Could not load actions shortcuts\", e)\n    }\n    println(\"Loading toolbar description...\")\n    try {\n        ToolBarIO.loadDescriptionFile()\n    } catch (e: Exception) {\n        printError(\"Could not load toolbar description\", e)\n    }\n    println(\"Loading command bar description...\")\n    try {\n        CommandBarIO.loadCommandBar()\n    } catch (e: Exception) {\n        printError(\"Could not load commandbar description\", e)\n    }\n}\n\nprivate fun enableNotifications() {\n    // Enable system notifications, only after MainFrame is created as SystemTrayNotifier needs to retrieve a MainFrame instance\n    if (isNotificationsEnabled()) {\n        println(\"Enabling system notifications...\")\n        if (AbstractNotifier.isAvailable()) {\n            AbstractNotifier.getNotifier().setEnabled(true)\n        }\n    }\n}\n\nprivate fun printError(msg: String, e: Throwable) {\n    println(msg)\n    e.printStackTrace()\n}\n\nprivate fun isListHiddenFiles() =\n    TcConfigurations.getPreferences().getVariable(TcPreference.LIST_HIDDEN_FILES, TcPreferences.DEFAULT_LIST_HIDDEN_FILES)\n\n\nprivate fun isSmbExtendedSecurityEnabled() =\n    TcConfigurations.getPreferences().getVariable(TcPreference.SMB_USE_EXTENDED_SECURITY, TcPreferences.DEFAULT_SMB_USE_EXTENDED_SECURITY)\n\nprivate fun isSmbLmCompatibilityEnabled() =\n    TcConfigurations.getPreferences().getVariable(TcPreference.SMB_LM_COMPATIBILITY, TcPreferences.DEFAULT_SMB_LM_COMPATIBILITY)\n\nprivate fun isBonjourEnabled() =\n    TcConfigurations.getPreferences().getVariable(TcPreference.ENABLE_BONJOUR_DISCOVERY, TcPreferences.DEFAULT_ENABLE_BONJOUR_DISCOVERY)\n\nprivate fun isShowSplash() =\n    TcConfigurations.getPreferences().getVariable(TcPreference.SHOW_SPLASH_SCREEN, TcPreferences.DEFAULT_SHOW_SPLASH_SCREEN)\n\nprivate fun isNotificationsEnabled() =\n    TcConfigurations.getPreferences().getVariable(TcPreference.ENABLE_SYSTEM_NOTIFICATIONS, TcPreferences.DEFAULT_ENABLE_SYSTEM_NOTIFICATIONS)\n"
  },
  {
    "path": "src/main/java/com/mucommander/package.html",
    "content": "<body>\n  muCommander specific wrappers for the various APIs.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/process/AbstractProcess.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.process;\n\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * muCommander specific version of a process, allowing various types of processes to be executed.\n * <p>\n * Unlike normal instances of <code>java.lang.Process</code>, abstract processes\n * will empty their own streams, preventing deadlocks from occurring on some systems.\n * <p>\n * Note that abstract processes should not be created directly. They should be\n * instantiated through {@link com.mucommander.process.ProcessRunner#execute(String[], com.mucommander.commons.file.AbstractFile,ProcessListener)}.\n *\n * @author Nicolas Rinaudo\n */\npublic abstract class AbstractProcess {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(AbstractProcess.class);\n\t\n    // - Instance fields -------------------------------------------------------\n    // -------------------------------------------------------------------------\n    /** Stdout monitor. */\n    private ProcessOutputMonitor stdoutMonitor;\n    /** Stderr monitor. */\n    private ProcessOutputMonitor stderrMonitor;\n\n\n\n    // - Process monitoring ----------------------------------------------------\n    // -------------------------------------------------------------------------\n    /**\n     * Kills the process.\n     */\n    public final void destroy() {\n        // Process destruction occurs in a separate thread, as in some (rare)\n        // cases, deadlocks will occur while trying to kill a native process.\n        // An example of that is executing <code>echo blah | ssh localhost ls -l</code>\n        // under MAC OS X.\n        // Using a separate thread allows muCommander to continue working properly even\n        // when that occurs.\n        new Thread(() -> {\n            // Closes the process' streams.\n            LOGGER.debug(\"Destroying process...\");\n            stdoutMonitor.stopMonitoring();\n            if (stderrMonitor != null) {\n                stderrMonitor.stopMonitoring();\n            }\n\n            // Destroys the process.\n            try {\n                destroyProcess();\n            } catch(IOException e) {\n                LOGGER.debug(\"IOException caught\", e);\n            }\n        }).start();\n    }\n\n    public void waitMonitoring() throws InterruptedException {\n        if (stdoutMonitor != null) {\n            stdoutMonitor.join();\n        }\n        if (stderrMonitor != null) {\n            stderrMonitor.join();\n        }\n    }\n\n    /**\n     * Starts monitoring the process.\n     * @param listener if non <code>null</code>, <code>listener</code> will receive updates about the process' event.\n     * @param encoding encoding that should be used by the process' stdout and stderr streams.\n     */\n    final void startMonitoring(ProcessListener listener, String encoding) throws IOException {\n        // Only monitors stdout if the process uses merged streams.\n        if (usesMergedStreams()) {\n        \tLOGGER.debug(\"Starting process merged output monitor...\");\n            stdoutMonitor = new ProcessOutputMonitor(getInputStream(), encoding, listener, this);\n            stdoutMonitor.setName(\"Process stdout/stderr monitor\");\n            stdoutMonitor.start();\n        }\n        // Monitors both stdout and stderr.\n        else {\n        \tLOGGER.debug(\"Starting process stdout and stderr monitors...\");\n            stdoutMonitor = new ProcessOutputMonitor(getInputStream(), encoding, listener, this);\n            stdoutMonitor.setName(\"Process stdout monitor\");\n            stdoutMonitor.start();\n            stderrMonitor = new ProcessOutputMonitor(getErrorStream(), encoding, listener);\n            stderrMonitor.setName(\"Process stderr monitor\");\n            stderrMonitor.start();\n        }\n    }\n\n\n\n    // - Abstract methods ------------------------------------------------------\n    // -------------------------------------------------------------------------\n    /**\n     * Returns <code>true</code> if this process only uses one output stream.\n     * <p>\n     * Some processes will use a single stream for their standard error and standard output streams. Such\n     * processes should return <code>true</code> here to prevent both streams from being monitored.<br>\n     * Note that if a process uses merged streams, {@link #getInputStream()} will be monitored.\n     *\n     * @return <code>true</code> if this process merges his output streams, <code>false</code> otherwise.\n     */\n    public abstract boolean usesMergedStreams();\n\n    /**\n     * Makes the current thread wait for the process to die.\n     * @return the process' exit code.\n     * @throws InterruptedException thrown if the current thread is interrupted while waiting on the process to die.\n     * @throws IOException          thrown if an error occurs while waiting for the process to die.\n     */\n    public abstract int waitFor() throws InterruptedException, IOException;\n\n    /**\n     * Destroys the process.\n     * @throws IOException thrown if an error occurs while destroying the process.\n     */\n    protected abstract void destroyProcess() throws IOException;\n\n    /**\n     * Returns this process' exit value.\n     * @return this process' exit value.\n     */\n    public abstract int exitValue();\n\n    /**\n     * Returns the stream used to send data to the process.\n     * @return             the stream used to send data to the process.\n     * @throws IOException thrown if an error occurs while retrieving the process' output stream.\n     */\n    public abstract OutputStream getOutputStream() throws IOException;\n\n    /**\n     * Returns the process' standard output stream.\n     * @return             the process' standard output stream.\n     * @throws IOException thrown if an error occurs while retrieving the process' input stream.\n     */\n    public abstract InputStream getInputStream() throws IOException;\n\n    /**\n     * Returns the process' standard error stream.\n     * @return             the process' standard error stream.\n     * @throws IOException thrown if an error occurs while retrieving the process' error stream.\n     */\n    public abstract InputStream getErrorStream() throws IOException;\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/process/DebugProcessListener.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.process;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Listener used while in debug mode.\n * <p>\n * In debug mode, instances of this listener will automatically be registered to non-monitored processes.\n * Its only goal is to output information about the process' state.\n *\n * @author Nicolas Rinaudo\n */\nclass DebugProcessListener implements ProcessListener {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(DebugProcessListener.class);\n\t\n    // - Instance fields -----------------------------------------------------\n    // -----------------------------------------------------------------------\n    /** Command that this listener is monitoring. */\n    private String command;\n\n\n\n    // - Initialisastion -----------------------------------------------------\n    // -----------------------------------------------------------------------\n    /**\n     * Creates a new process listener monitoring the specified command.\n     * @param tokens tokens that compose the command that is being ran.\n     */\n    public DebugProcessListener(String[] tokens) {\n        StringBuilder buffer;\n\n        // Rebuilds the command.\n        buffer = new StringBuilder();\n        for (String token : tokens) {\n            buffer.append(token);\n            buffer.append(' ');\n        }\n\n        command = buffer.toString();\n    }\n\n\n\n    // - Process monitoring --------------------------------------------------\n    // -----------------------------------------------------------------------\n    /**\n     * Prints out information about the way the process died.\n     * @param returnValue process' return value.\n     */\n    public void processDied(int returnValue) {\n        LOGGER.debug(command + \": died with return code \" + returnValue);\n    }\n\n    /**\n     * Ignored.\n     */\n    public void processOutput(byte[] buffer, int offset, int length) {\n        LOGGER.trace(command + \": \" + new String(buffer, offset, length));\n    }\n\n    /**\n     * Prints out the process output.\n     */\n    public void processOutput(String output) {\n        LOGGER.trace(command + \": \" + output);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/process/ExecutionFinishListener.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.process;\n\n/**\n * @author Oleg Trifonov\n * Created on 22/04/16.\n */\npublic interface ExecutionFinishListener {\n    /**\n     *\n     * @param exitCode the process' exit code.\n     * @param output contains the encoded process output.\n     */\n    void onFinish(int exitCode, String output);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/process/ExecutorUtils.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2015 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.process;\n\nimport com.mucommander.command.Command;\nimport com.mucommander.commons.file.AbstractFile;\n\nimport java.io.IOException;\n\n/**\n * @author Oleg Trifonov\n * Created on 22/04/16.\n */\npublic class ExecutorUtils {\n\n    /**\n     * Executes the specified command in the specified folder.\n     *\n     * @param command                  command to execute\n     * @param currentFolder            where to init the command from.\n     * @param onFinish  here to send information about the resulting process.\n     * @param encoding                 output encoding (system default is used if <code>null</code>).\n     * @return process exit code\n     * @throws IOException\n     * @throws InterruptedException\n     */\n    public static int executeAndGetOutput(String command, AbstractFile currentFolder, ExecutionFinishListener onFinish,\n                                           String encoding) throws IOException, InterruptedException {\n        StringBuffer out = new StringBuffer();\n        AbstractProcess process = execute(command, currentFolder, new ProcessListener() {\n                @Override\n                public void processDied(int returnValue) {}\n\n                @Override\n                public void processOutput(String output) {\n                    out.append(output);\n                }\n\n                @Override\n                public void processOutput(byte[] buffer, int offset, int length) {}\n            }, encoding);\n        int exitCode = process.waitFor();\n        process.waitMonitoring();\n        process.destroy();\n        if (onFinish != null) {\n            onFinish.onFinish(exitCode, out.toString());\n        }\n        return exitCode;\n    }\n\n    public static int executeAndGetOutput(String[] command, AbstractFile currentFolder, ExecutionFinishListener onFinish,\n                                          String encoding) throws IOException, InterruptedException {\n        StringBuffer out = new StringBuffer();\n        AbstractProcess process = execute(command, currentFolder, new ProcessListener() {\n            @Override\n            public void processDied(int returnValue) {}\n\n            @Override\n            public void processOutput(String output) {\n                out.append(output);\n            }\n\n            @Override\n            public void processOutput(byte[] buffer, int offset, int length) {}\n        }, encoding);\n        int exitCode = process.waitFor();\n        process.waitMonitoring();\n        process.destroy();\n        if (onFinish != null) {\n            onFinish.onFinish(exitCode, out.toString());\n        }\n        return exitCode;\n    }\n\n    /**\n     * Executes the specified command in the specified folder, using system default encoding.\n     *\n     * @param command                  command to execute\n     * @param currentFolder            where to init the command from.\n     * @param executionFinishListener  here to send information about the resulting process.\n     * @return exit code\n     * @throws IOException\n     * @throws InterruptedException\n     */\n    public static int executeAndGetOutput(String command, AbstractFile currentFolder, ExecutionFinishListener executionFinishListener) throws IOException, InterruptedException {\n        return executeAndGetOutput(command, currentFolder, executionFinishListener, null);\n    }\n\n    public static int executeAndGetOutput(String[] command, AbstractFile currentFolder, ExecutionFinishListener executionFinishListener) throws IOException, InterruptedException {\n        return executeAndGetOutput(command, currentFolder, executionFinishListener, null);\n    }\n\n    public static int execute(String command, AbstractFile currentFolder) throws IOException, InterruptedException {\n        return executeAndGetOutput(command, currentFolder, null);\n    }\n\n    public static int execute(String command) throws IOException, InterruptedException {\n        return executeAndGetOutput(command, null, null);\n    }\n\n    public static int execute(String[] command) throws IOException, InterruptedException {\n        return executeAndGetOutput(command, null, null);\n    }\n\n\n    private static AbstractProcess execute(String command, AbstractFile currentFolder, ProcessListener listener, String encoding) throws IOException {\n        String[] tokens = Command.getTokens(command);\n        return encoding == null ? ProcessRunner.execute(tokens, currentFolder, listener) : ProcessRunner.execute(tokens, currentFolder, listener, encoding);\n    }\n\n    private static AbstractProcess execute(String[] command, AbstractFile currentFolder, ProcessListener listener, String encoding) throws IOException {\n        return encoding == null ? ProcessRunner.execute(command, currentFolder, listener) : ProcessRunner.execute(command, currentFolder, listener, encoding);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/process/LocalProcess.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.process;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Process running on the local computer.\n * @author Nicolas Rinaudo\n */\npublic class LocalProcess extends AbstractProcess {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(LocalProcess.class);\n\n    /** Underlying system process. */\n    private final Process process;\n\n\n    /**\n     * Creates a new local process running the specified command.\n     * @param  tokens      command to init and its parameters.\n     * @param  dir         directory in which to start the command.\n     * @throws IOException if the process could not be created.\n     */\n    public LocalProcess(String[] tokens, File dir) throws IOException {\n        ProcessBuilder pb = new ProcessBuilder(removeOuterQuotationMarks(tokens));\n        // Set the process' working directory\n        pb.directory(dir);\n        // Merge the process' stdout and stderr\n        pb.redirectErrorStream(true);\n\n        process = pb.start();\n    }\n\n    private boolean isQuotedWith(String string, String quotationMark) {\n        return string.startsWith(quotationMark) && string.endsWith(quotationMark);\n    }\n\n    private String[] removeOuterQuotationMarks(String[] tokens) {\n        String[] newTokens = new String[tokens.length];\n        for(int i = 0; i < tokens.length; ++i) {\n            String token = tokens[i];\n            if (token.length() > 1 && isQuotedWith(token, \"\\\"\") || isQuotedWith(token, \"'\")) {\n                token = token.substring(1, token.length() - 1);\n            }\n            newTokens[i]= token;\n        }\n        return newTokens;\n    }\n\n    /**\n     * Returns <code>true</code> if the current JRE version supports merged <code>java.lang.Process</code> streams.\n     * @return <code>true</code> if the current JRE version supports merged <code>java.lang.Process</code> streams, <code>false</code> otherwise.\n     */\n    @Override\n    public boolean usesMergedStreams() {\n        // TODO remove it\n        return true;//JavaVersion.JAVA_1_5.isCurrentOrHigher();\n    }\n\n    /**\n     * Waits for the process to die.\n     * @return                      the process' exit code.\n     * @throws InterruptedException if the thread was interrupted while waiting on the process to die.\n     */\n    @Override\n    public int waitFor() throws InterruptedException {\n        return process.waitFor();\n    }\n\n    /**\n     * Destroys the process.\n     */\n    @Override\n    protected void destroyProcess() {\n        process.destroy();\n    }\n\n    /**\n     * Returns the process' exit value.\n     * @return the process' exit value.\n     */\n    @Override\n    public int exitValue() {\n        return process.exitValue();\n    }\n\n    /**\n     * Returns the process' output stream.\n     * @return the process' output stream.\n     */\n    @Override\n    public OutputStream getOutputStream() {\n        return process.getOutputStream();\n    }\n\n    /**\n     * Returns the process' error stream.\n     * <p>\n     * On Java 1.5 or higher, this will throw an <code>java.io.IOException</code>, as we're using\n     * merged output streams. Developers should protect themselves against this by checking\n     * {@link #usesMergedStreams()} before accessing streams.\n     *\n     * @return             the process' error stream.\n     * @throws IOException if this process is using merged streams.\n     */\n    @Override\n    public InputStream getErrorStream()  throws IOException {\n        if (usesMergedStreams()) {\n            LOGGER.debug(\"Tried to access the error stream of a merged streams process.\");\n            throw new IOException();\n        }\n        return process.getErrorStream();\n    }\n\n    /**\n     * Returns the process' input stream.\n     * @return the process' input stream.\n     */\n    @Override\n    public InputStream getInputStream() {\n        return process.getInputStream();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/process/ProcessListener.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.process;\n\n\n/**\n * Implementations of this interface can listen to a process' state and streams.\n * @see com.mucommander.process.AbstractProcess\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic interface ProcessListener {\n\t\n    /** \n     * This method is called when the process dies. No more calls to <code>processOutput</code> and\n     * <code>processError</code> will be made past this call.\n     * @param returnValue the value returned by the process (return code).\n     */\n    void processDied(int returnValue);\n\n    /**\n     * This method is called whenever the process sends data to its output streams (stdout or stderr).\n     * <p>\n     * The output passed to this method is encoded. Listener that need to work with raw bytes should\n     * use {@link #processOutput(byte[],int,int)} instead.\n     *\n     * @param output contains the encoded process output.\n     */\n    void processOutput(String output);\n\n    /**\n     * This method is called whenever the process sends data to its output streams (stdout or stderr).\n     * <p>\n     * The output passed to this method is raw and doesn't take encoding into account. Listeners that\n     * need to work with properly encoded output should use {@link #processOutput(String)} instead.\n     *\n     * @param buffer contains the process' output.\n     * @param offset offset in buffer at which the process' output starts.\n     * @param length length of the process' output in buffer.\n     */\n    void processOutput(byte[] buffer, int offset, int length);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/process/ProcessListenerList.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.process;\n\nimport java.util.List;\nimport java.util.Vector;\n\n/**\n * Convenience class used to have more than one listener on any given process.\n * @author Nicolas Rinaudo\n */\npublic class ProcessListenerList implements ProcessListener {\n    // - Instance fields -----------------------------------------------------\n    // -----------------------------------------------------------------------\n    /** All registered listeners. */\n    private final List<ProcessListener> listeners = new Vector<>();\n\n\n\n    // - Listener registration -----------------------------------------------\n    // -----------------------------------------------------------------------\n    /**\n     * Adds the specified listener to the list of listeners.\n     * @param listener process listener to add.\n     */\n    public void add(ProcessListener listener) {listeners.add(listener);}\n\n    /**\n     * Removes the specified listener from the list of listeners.\n     * @param listener process listener to remove.\n     */\n    public void remove(ProcessListener listener) {listeners.remove(listener);}\n\n\n\n    // - Listener code -------------------------------------------------------\n    // -----------------------------------------------------------------------\n    /**\n     * Propagates the <i>process died</i> event to all registered listeners.\n     */\n    public void processDied(int returnValue) {\n        for (ProcessListener listener : listeners) {\n            listener.processDied(returnValue);\n        }\n    }\n\n    /**\n     * Propagates the <i>process output</i> event to all registered listeners.\n     */\n    public void processOutput(byte[] buffer, int offset, int length) {\n        for (ProcessListener listener : listeners) {\n            listener.processOutput(buffer, offset, length);\n        }\n    }\n\n    /**\n     * Propagates the <i>process output</i> event to all registered listeners.\n     */\n    public void processOutput(String output) {\n        for (ProcessListener listener : listeners) {\n            listener.processOutput(output);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/process/ProcessOutputMonitor.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.process;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Used to monitor a process' stdout and stderr streams.\n * <p>\n * This class is used by {@link com.mucommander.process.AbstractProcess} to make sure that\n * processes do not stall because their stdout and stderr streams are not emptied.\n * <p>\n * This implementation is rather hackish, and should not be used directly: it works, but is not\n * meant to support anything but the very specific needs of {@link com.mucommander.process.AbstractProcess}.\n *\n * @author Nicolas Rinaudo\n */\nclass ProcessOutputMonitor extends Thread {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(ProcessOutputMonitor.class);\n\n    /** Stream to read from. */\n    private InputStream     in;\n    private final String          encoding;\n    /** Listener to notify of updates. */\n    private final ProcessListener listener;\n    /** Process to wait on once the stream is closed. */\n    private AbstractProcess process;\n    /** Whether the process is still being monitored. */\n    private boolean         monitor;\n\n\n    /**\n     * Creates a news ProcessOutputMonitor that will read from <code>in</code> and notify <code>listener</code>.\n     * @param in       input stream to 'empty'.\n     * @param listener where to send the content of the stream.\n     */\n    public ProcessOutputMonitor(InputStream in, String encoding, ProcessListener listener) {\n        this.listener = listener;\n        this.in = in;\n        this.monitor = true;\n        this.encoding = encoding;\n    }\n\n    /**\n     * Creates a news ProcessOutputMonitor that will read from <code>in</code> and notify <code>listener</code>.\n     * <p>\n     * A process monitor created that way will also wait on the specified <code>process</code> before quiting,\n     * and notify the listener when the process has actually died.\n     *\n     * @param in       input stream to 'empty'.\n     * @param listener where to send the content of the stream.\n     * @param process  process to wait on.\n     */\n    public ProcessOutputMonitor(InputStream in, String encoding, ProcessListener listener, AbstractProcess process) {\n        this(in, encoding, listener);\n        this.process = process;\n    }\n\n\n\n    // - Main code -------------------------------------------------------------\n    // -------------------------------------------------------------------------\n    /**\n     * Empties the content of the stream and notifies the listener.\n     */\n    public void run() {\n        int read;   // Number of bytes read in the last read operation.\n        byte[] buffer = new byte[512];  // Where to store the stream's output.\n\n        // Reads the content of the stream.\n        try {\n            while (monitor && ((read = in.read(buffer, 0, buffer.length)) != -1)) {\n                if (listener != null) {\n                    listener.processOutput(buffer, 0, read);\n                    if (encoding == null) {\n                        listener.processOutput(new String(buffer, 0, read));\n                    } else {\n                        listener.processOutput(new String(buffer, 0, read, encoding));\n                    }\n                }\n            }\n        }\n        // Ignore this exception: either there's nothing we can do about it anyway,\n        // or it's 'normal' (the process has been killed).\n        catch (IOException e) {\n            LOGGER.debug(\"IOException thrown while monitoring process\", e);\n        }\n\n        LOGGER.debug(\"Process output stream emptied, closing\");\n\n        // Closes the stream.\n        try {\n\t        if (in != null) {\n                in.close();\n            }\n\t    } catch(IOException e) {\n            LOGGER.debug(\"IOException thrown while closing process stream\", e);\n        }\n\n        // If a process was set, perform 'cleanup' tasks.\n        if (process != null) {\n            // Waits for the process to die.\n            try {\n                process.waitFor();\n            } catch(Exception e) {\n                LOGGER.debug(\"Caught Exception while waiting for process \"+process, e);\n            }\n            // If this process is still being monitored, notifies its\n            // listener that it has exited.\n            if (monitor && (listener != null)) {\n                listener.processDied(process.exitValue());\n            }\n        }\n    }\n\n    /**\n     * Notifies the monitor that it should stop reading from the stream it's been affected to.\n     * <p>\n     * Calling this method will cause the process' stream to stop being read. For this reason,\n     * it should only be called right before the process is killed, as it will otherwise stall.\n     */\n    public void stopMonitoring() {\n        // Closes the input stream.\n        try {\n            in.close();\n        } catch(Exception ignore) {}\n\n        // Notifies the main thread that it should stop monitoring the stream.\n        in = null;\n        monitor = false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/process/ProcessRunner.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.process;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.impl.local.LocalFile;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.StringTokenizer;\n\n/**\n * Used to init process in as safe a manner as possible.\n * <p>\n * The Java process API, while very simple, contains a lot of pitfalls and requires some work to use properly.\n * Typical errors are forgetting to monitor a process' output streams, which will make it deadlock more often than not.\n * <p>\n * Using the <code>ProcessRunner</code> will take care of all these tasks, while still allowing most of the flexibility\n * of the standard API.\n *\n * @author Nicolas Rinaudo\n */\npublic class ProcessRunner {\n    /**\n     * Prevents instances of ProcessRunner from being created.\n     */\n    private ProcessRunner() {\n    }\n\n    /**\n     * Executes the specified command in the specified directory.\n     * <p>\n     * Note that both <code>currentDirectory</code> and <code>listener</code> can be set to <code>null</code>.<br>\n     * If no current directory is specified, the VM's current directory will be used. Moreover, if the current directory\n     * is not on residing on local file system, the user's home directory will be used instead. Finally, if the current\n     * directory is on a local file system but is actually not a {@link AbstractFile#isDirectory() directory}\n     * (an archive entry for instance), the first file's parent that is an actual directory will be used.\n     * <br>\n     * If <code>listener</code> is set to <code>null</code>, nobody will be notified of the process' state. Its streams\n     * will still be emptied to prevent deadlocks.\n     *\n     * @param  tokens           tokens that compose the command to execute.\n     * @param  currentDirectory directory in which to execute the process (user directory if <code>null</code>).\n     * @param  listener         object that will be notified of modifications in the process' state (ignored if <code>null</code>).\n     * @param  encoding         encoding used to read from the process' stream (system default is used if <code>null</code>).\n     * @return                  the generated process.\n     * @throws IOException      thrown if any error occurs while creating the process.\n     */\n    public static AbstractProcess execute(String[] tokens, AbstractFile currentDirectory, ProcessListener listener, String encoding) throws IOException {\n        // If currentDirectory is null, use the VM's current directory.\n        if (currentDirectory == null) {\n            currentDirectory = FileFactory.getFile(System.getProperty(\"user.dir\"), true);\n        } else {\n            // If currentDirectory is not on a local filesystem, use the user's home.\n            if (!currentDirectory.hasAncestor(LocalFile.class)) {\n                currentDirectory = FileFactory.getFile(System.getProperty(\"user.home\"), true);\n            }\n            // If currentDirectory is not a directory (e.g. an archive entry)\n            else {\n                while (currentDirectory != null && !currentDirectory.isDirectory()) {\n                    currentDirectory = currentDirectory.getParent();\n                }\n\n                // This shouldn't normally happen\n                if (currentDirectory == null) {\n                    currentDirectory = FileFactory.getFile(System.getProperty(\"user.dir\"), true);\n                }\n            }\n        }\n\n//        // Register a debug process listener.\n//        if(listener == null)\n//            listener = new DebugProcessListener(tokens);\n\n        // Starts the process.\n        File dir = currentDirectory == null ? null : (File)currentDirectory.getUnderlyingFileObject();\n        AbstractProcess process = new LocalProcess(tokens, dir);\n        process.startMonitoring(listener, encoding);\n\n        return process;\n    }\n\n\n\n    // - Helper methods ------------------------------------------------------\n    // -----------------------------------------------------------------------\n    /**\n     * Executes the specified command in the specified directory.\n     * <p>\n     * This is a convenience method and behaves exactly as a call to <code>execute(command, currentDirectory, listener, null)</code>.\n     *\n     * @param  command          command to execute.\n     * @param  currentDirectory directory in which to execute the process (user directory if <code>null</code>).\n     * @param  listener         object that will be notified of modifications in the process' state (ignored if <code>null</code>).\n     * @return                  the generated process.\n     * @throws IOException      thrown if any error occurs while creating the process.\n     */\n    public static AbstractProcess execute(String command, AbstractFile currentDirectory, ProcessListener listener) throws IOException {\n        return execute(command, currentDirectory, listener, null);\n    }\n\n    /**\n     * Executes the specified command in the VM's current directory.\n     * <p>\n     * This is a convenience method and behaves exactly as a call to <code>execute(command, null, null, null)</code>.\n     *\n     * @param  command     command to execute.\n     * @return             the generated process.\n     * @see                #execute(String,AbstractFile,ProcessListener,String)\n     * @throws IOException thrown if an error happens while starting the process.\n     */\n    public static AbstractProcess execute(String command) throws IOException {\n        return execute(command, null, null, null);\n    }\n\n    /**\n     * Executes the specified command in the VM's current directory.\n     * <p>\n     * This is a convenience method and behaves exactly as a call to <code>execute(command, null, null, encoding)</code>.\n     *\n     * @param  command     command to execute.\n     * @param  encoding    encoding used to read from the process' stream (system default is used if <code>null</code>).\n     * @return             the generated process.\n     * @see                #execute(String,AbstractFile,ProcessListener,String)\n     * @throws IOException thrown if an error happens while starting the process.\n     */\n    public static AbstractProcess execute(String command, String encoding) throws IOException {\n        return execute(command, null, null, encoding);\n    }\n\n    /**\n     * Executes the specified command in the VM's current directory.\n     * <p>\n     * This is a convenience method and behaves exactly as a call to <code>execute(command, null, listener, null)</code>.\n     *\n     * @param  command     command to execute.\n     * @param  listener    object that will be notified of any modification in the process' state (ignored if <code>null</code>).\n     * @return             the generated process.\n     * @see                #execute(String,AbstractFile,ProcessListener,String)\n     * @throws IOException thrown if an error happens while starting the process.\n     */\n    public static AbstractProcess execute(String command, ProcessListener listener) throws IOException {\n        return execute(command, null, listener, null);\n    }\n\n    /**\n     * Executes the specified command in the VM's current directory.\n     * <p>\n     * This is a convenience method and behaves exactly as a call to <code>execute(command, null, listener, encoding)</code>.\n     *\n     * @param  command     command to execute.\n     * @param  listener    object that will be notified of any modification in the process' state.\n     * @param  encoding    encoding used to read from the process' stream (system default is used if <code>null</code>).\n     * @return             the generated process.\n     * @see                #execute(String,AbstractFile,ProcessListener,String)\n     * @throws IOException thrown if an error happens while starting the process.\n     */\n    public static AbstractProcess execute(String command, ProcessListener listener, String encoding) throws IOException {\n        return execute(command, null, listener, encoding);\n    }\n\n    /**\n     * Executes the specified command in the specified directory.\n     * <p>\n     * This is a convenience method and behaves exactly as a call to <code>execute(command, currentDirectory, null, null)</code>.\n     *\n     * @param  command          command to execute.\n     * @param  currentDirectory directory in which to init the command.\n     * @return                  the generated process.\n     * @see                     #execute(String,AbstractFile,ProcessListener,String)\n     * @throws IOException      thrown if an error happens while starting the process.\n     */\n    public static AbstractProcess execute(String command, AbstractFile currentDirectory) throws IOException {\n        return execute(command, currentDirectory, null, null);\n    }\n\n    /**\n     * Executes the specified command in the specified directory.\n     * <p>\n     * This is a convenience method and behaves exactly as a call to <code>execute(command, currentDirectory, null, encoding)</code>.\n     *\n     * @param  command          command to execute.\n     * @param  currentDirectory directory in which to init the command (uses the VM's current directory if <code>null</code>).\n     * @param  encoding         encoding used to read from the process' stream (system default is used if <code>null</code>).\n     * @return                  the generated process.\n     * @see                     #execute(String,AbstractFile,ProcessListener,String)\n     * @throws IOException      thrown if an error happens while starting the process.\n     */\n    public static AbstractProcess execute(String command, AbstractFile currentDirectory, String encoding) throws IOException {\n        return execute(command, currentDirectory, null, encoding);\n    }\n\n    /**\n     * Executes the specified command in the specified directory.\n     * <p>\n     * This is a convenience method and behaves exactly as a call to <code>execute(tokens, currentDirectory, null, encoding)</code> where <code>tokens</code>\n     * is an array contains all the tokens found in <code>command</code>.\n     * <p>\n     * More precisely, the <code>command</code> string is broken into tokens using a <code>StringTokenizer</code> created by the call\n     * <code>new StringTokenizer(command)</code> with no further modification of the character categories. The tokens produced by the\n     *  tokenizer are then placed in the new string array <code>tokens</code>, in the same order.\n     *\n     * @param  command          command to execute.\n     * @param  currentDirectory directory in which to init the command (uses the VM's current directory if <code>null</code>).\n     * @param  encoding         encoding used to read from the process' stream (system default is used if <code>null</code>).\n     * @param  listener         object that will be notified of modifications in the process' state (ignored if <code>null</code>).\n     * @return                  the generated process.\n     * @throws IOException      thrown if an error happens while starting the process.\n     */\n    public static AbstractProcess execute(String command, AbstractFile currentDirectory, ProcessListener listener, String encoding) throws IOException {\n        // Initialisation.\n        StringTokenizer parser = new StringTokenizer(command);      // Used to parse the command.\n        String[] tokens = new String[parser.countTokens()];         // Tokens that make up the command.\n\n        // Breaks command into tokens.\n        for(int i = 0; i < tokens.length; i++) {\n            tokens[i] = parser.nextToken();\n        }\n\n        // Starts the process.\n        return execute(tokens, currentDirectory, listener, encoding);\n    }\n\n    /**\n     * Executes the specified command in the specified directory.\n     * <p>\n     * This is a convenience method and behaves exactly as a call to <code>execute(tokens, currentDirectory, listener, null)</code>.\n     *\n     * @param  tokens           command to execute.\n     * @param  currentDirectory directory in which to init the command (uses the VM's current directory if <code>null</code>).\n     * @param  listener         object that will be notified of any modification in the process' state.\n     * @return                  the generated process.\n     * @throws IOException thrown if an error happens while starting the process.\n     */\n    public static AbstractProcess execute(String[] tokens, AbstractFile currentDirectory, ProcessListener listener) throws IOException {\n        return execute(tokens, currentDirectory, listener, null);\n    }\n\n    /**\n     * Executes the specified command in the VM's current directory.\n     * <p>\n     * This is a convenience method and behaves exactly as a call to <code>execute(tokens, null, null, null)</code>.\n     *\n     * @param  tokens      command to execute.\n     * @return             the generated process.\n     * @throws IOException thrown if an error happens while starting the process.\n     */\n    public static AbstractProcess execute(String[] tokens) throws IOException {\n        return execute(tokens, null, null, null);\n    }\n\n    /**\n     * Executes the specified command in the VM's current directory.\n     * <p>\n     * This is a convenience method and behaves exactly as a call to <code>execute(tokens, null, null, encoding)</code>.\n     *\n     * @param  tokens      command to execute.\n     * @param  encoding    encoding used to read from the process' stream (system default is used if <code>null</code>).\n     * @return             the generated process.\n     * @throws IOException thrown if an error happens while starting the process.\n     */\n    public static AbstractProcess execute(String[] tokens, String encoding) throws IOException {\n        return execute(tokens, null, null, encoding);\n    }\n\n    /**\n     * Executes the specified command in the VM's current directory.\n     * <p>\n     * This is a convenience method and behaves exactly as a call to <code>execute(tokens, null, listener, null)</code>.\n     *\n     * @param  tokens      command to execute.\n     * @param  listener    object that will be notified of any modification in the process' state (ignored if <code>null</code>).\n     * @return             the generated process.\n     * @see                #execute(String[],AbstractFile,ProcessListener,String)\n     * @throws IOException thrown if an error happens while starting the process.\n     */\n    public static AbstractProcess execute(String[] tokens, ProcessListener listener) throws IOException {\n        return execute(tokens, null, listener, null);\n    }\n\n    /**\n     * Executes the specified command in the VM's current directory.\n     * <p>\n     * This is a convenience method and behaves exactly as a call to <code>execute(tokens, null, listener, encoding)</code>.\n     *\n     * @param  tokens      command to execute.\n     * @param  listener    object that will be notified of any modification in the process' state (ignored if <code>null</code>).\n     * @param  encoding    encoding used to read from the process' stream (system default is used if <code>null</code>).\n     * @return             the generated process.\n     * @see                #execute(String[],AbstractFile,ProcessListener,String)\n     * @throws IOException thrown if an error happens while starting the process.\n     */\n    public static AbstractProcess execute(String[] tokens, ProcessListener listener, String encoding) throws IOException {\n        return execute(tokens, null, listener, encoding);\n    }\n\n    /**\n     * Executes the specified command in the specified directory.\n     * <p>\n     * This is a convenience method and behaves exactly as a call to <code>execute(tokens, currentDirectory, null, null)</code>.\n     *\n     * @param  tokens           command to execute.\n     * @param  currentDirectory directory in which to init the command.\n     * @return                  the generated process.\n     * @see                     #execute(String[],AbstractFile,ProcessListener,String)\n     * @throws IOException      thrown if an error happens while starting the process.\n     */\n    public static AbstractProcess execute(String[] tokens, AbstractFile currentDirectory) throws IOException {\n        return execute(tokens, currentDirectory, null, null);\n    }\n\n    /**\n     * Executes the specified command in the specified directory.\n     * <p>\n     * This is a convenience method and behaves exactly as a call to <code>execute(tokens, currentDirectory, null, null)</code>.\n     *\n     * @param  tokens           command to execute.\n     * @param  currentDirectory directory in which to init the command.\n     * @param  encoding         encoding used to read from the process' stream (system default is used if <code>null</code>).\n     * @return                  the generated process.\n     * @see                     #execute(String[],AbstractFile,ProcessListener,String)\n     * @throws IOException      thrown if an error happens while starting the process.\n     */\n    public static AbstractProcess execute(String[] tokens, AbstractFile currentDirectory, String encoding) throws IOException {\n        return execute(tokens, currentDirectory, null, encoding);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/process/package.html",
    "content": "<body>\n  Generic process management API.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/profiler/Profiler.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.profiler;\n\nimport java.util.*;\n\n/**\n * Created on 01/01/14.\n */\npublic class Profiler {\n    public static final boolean ENABLED = true;\n\n    private static final Map<String, Long> timesStart = new HashMap<>();\n    private static final Map<String, Long> timesDuration = new HashMap<>();\n    private static final Map<String, Integer> callCount = new HashMap<>();\n    private static final Set<String> hiddenGroups = new HashSet<>();\n\n    private static String lastSectionName;\n    private static final List<String> initThreads = new ArrayList<>();\n\n    public static long getTime() {\n//        return System.nanoTime();\n        return System.currentTimeMillis();\n    }\n\n    public static void start(String name) {\n        if (!ENABLED) {\n            return;\n        }\n        lastSectionName = name;\n        synchronized (timesStart) {\n            timesStart.put(name, getTime());\n        }\n        synchronized (callCount) {\n            Integer cnt = callCount.get(name);\n            if (cnt == null) {\n                cnt = 1;\n            } else {\n                cnt++;\n            }\n            callCount.put(name, cnt);\n        }\n    }\n\n    public static void stop(String name) {\n        if (!ENABLED) {\n            return;\n        }\n        long endTime = getTime();\n        long startTime;\n        synchronized (timesStart) {\n            startTime = timesStart.get(name);\n        }\n        long duration = endTime - startTime;\n        synchronized (timesDuration) {\n            Long sum = timesDuration.get(name);\n            if (sum == null) {\n                sum = duration;\n            } else {\n                sum += duration;\n            }\n            timesDuration.put(name, sum);\n        }\n    }\n\n    public static void stop() {\n        if (!ENABLED) {\n            return;\n        }\n        stop(lastSectionName);\n    }\n\n    public static void print() {\n        if (!ENABLED) {\n            return;\n        }\n        synchronized (timesDuration) {\n            TreeMap<String, Long> sortedMap = new TreeMap<>(new ValueComparator(timesDuration));\n            sortedMap.putAll(timesDuration);\n            System.out.println(withSpaces(\"Name\", 40) + \"\\t\" + withSpaces(\"Total\", 10) + \"\\t\" + withSpaces(\"Count\", 7) + \"\\t\" + \"Average\");\n            System.out.println(withSpaces(\"-----------\", 40) + \"\\t\" + withSpaces(\"--------\", 10) + \"\\t\" + withSpaces(\"-------\", 7) + \"\\t\" + \"----------\");\n            for (String name : sortedMap.keySet()) {\n                boolean isHidden = false;\n                for (String hiddenName : hiddenGroups) {\n                    if (name.contains(hiddenName)) {\n                        isHidden = true;\n                        break;\n                    }\n                }\n                if (isHidden) {\n                    continue;\n                }\n                long duration = timesDuration.get(name);\n                int count = callCount.get(name);\n                String avgDuration = String.valueOf(duration/count);\n                if (avgDuration.equals(\"0\") && duration != 0) {\n                    avgDuration = String.valueOf(1.0*duration/count);\n                }\n                System.out.println(withSpaces(name, 40) + \"\\t\" + withSpaces(Long.toString(duration), 10) + \"\\t\" + withSpaces(Integer.toString(count), 7) + \"\\t\" + avgDuration);\n            }\n        }\n    }\n\n    private static String withSpaces(String name, int len) {\n        StringBuilder nameBuilder = new StringBuilder(name);\n        while (nameBuilder.length() < len) {\n            nameBuilder.append(\" \");\n        }\n        name = nameBuilder.toString();\n        return name;\n    }\n\n    static class ValueComparator implements Comparator<String> {\n\n        Map<String, Long> base;\n        public ValueComparator(Map<String, Long> base) {\n            this.base = base;\n        }\n\n        // Note: this comparator imposes orderings that are inconsistent with equals.\n        @Override\n        public int compare(String a, String b) {\n            if (base.get(a) >= base.get(b)) {\n                return -1;\n            } else {\n                return 1;\n            } // returning 0 would merge keys\n        }\n    }\n\n    public static void hide(String name) {\n        hiddenGroups.add(name);\n    }\n\n\n    public static void unhide(String name) {\n        hiddenGroups.remove(name);\n    }\n\n\n/*\n    public static void printThreads() {\n        System.out.println(\"---------------------------\");\n        Set<Thread> threads = Thread.getAllStackTraces().keySet();\n        for (Thread thread : threads) {\n            String s = thread2str(thread);\n            if (initThreads.contains(s)) {\n                continue;\n            }\n            System.out.println(s);\n        }\n        System.out.println(\"---------------------------\");\n    }\n\n    public static void initThreads() {\n        if (initThreads.size() > 0) {\n            return;\n        }\n        Set<Thread> threads = Thread.getAllStackTraces().keySet();\n        for (Thread thread : threads) {\n            initThreads.add(thread2str(thread));\n        }\n    }\n\n    private static String thread2str(Thread thread) {\n        return thread.getId() + \":\" + thread.getName() + \":\" + thread.getState();\n    }\n*/\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/shell/Shell.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.shell;\n\nimport java.io.IOException;\n\nimport com.mucommander.process.*;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.command.Command;\nimport com.mucommander.commons.conf.ConfigurationEvent;\nimport com.mucommander.commons.conf.ConfigurationListener;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.desktop.DesktopManager;\n\n/**\n * Used to execute shell commands.\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic class Shell implements ConfigurationListener {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(Shell.class);\n\t\n    // - Class variables -----------------------------------------------------\n    // -----------------------------------------------------------------------\n    /** Encoding used by the shell. */\n    private static String   encoding;\n    /** Whether encoding should be auto-detected or not. */\n    private static boolean  autoDetectEncoding;\n    /** Tokens that compose the shell command. */\n    private static String[] tokens;\n    /** Tokens that compose remote shell commands. */\n    private static final String[] remoteTokens;\n    /** Instance of configuration listener. */\n    private static final Shell confListener;\n\n\n    static {\n    \tTcConfigurations.addPreferencesListener(confListener = new Shell());\n\n        // This could in theory also be written without the confListener reference.\n        // It turns out, however, that proGuard is a bit too keen when removing fields\n        // he thinks are not used. This code is written that way to make sure\n        // confListener is not taken out, and the ConfigurationListener instance removed\n        // instantly as there is only a WeakReference on it.\n        // The things we have to do...\n        Shell.setShellCommand();\n\n        remoteTokens = new String[1];\n    }\n\n    /**\n     * Prevents instances of Shell from being created.\n     */\n    private Shell() {}\n\n\n\n    // - Shell interaction ---------------------------------------------------\n    // -----------------------------------------------------------------------\n    /**\n     * Executes the specified command in the specified folder.\n     * <p>\n     * The <code>currentFolder</code> folder parameter will only be used if it's neither a\n     * remote directory nor an archive. Otherwise, the command will init from the user's\n     * home directory.\n     *\n     * @param     command       command to init.\n     * @param     currentFolder where to init the command from.\n     * @return                  the resulting process.\n     * @exception IOException   thrown if any error occurs while trying to init the command.\n     */\n    public static AbstractProcess execute(String command, AbstractFile currentFolder) throws IOException {\n        return execute(command, currentFolder, null);\n    }\n\n    /**\n     * Executes the specified command in the specified folder.\n     * <p>\n     * The <code>currentFolder</code> folder parameter will only be used if it's neither a\n     * remote directory nor an archive. Otherwise, the command will init from the user's\n     * home directory.\n     * <p>\n     * Information about the resulting process will be sent to the specified <code>listener</code>.\n     *\n     * @param     command       command to init.\n     * @param     currentFolder where to init the command from.\n     * @param     listener      where to send information about the resulting process.\n     * @return                  the resulting process.\n     * @exception IOException   thrown if any error occurs while trying to init the command.\n     */\n    public static synchronized AbstractProcess execute(String command, AbstractFile currentFolder, ProcessListener listener) throws IOException {\n        LOGGER.debug(\"Executing \" + command);\n\n        // Adds the command to history.\n        ShellHistoryManager.add(command);\n\n        // Builds the shell command.\n        // Local files use the configuration defined shell. Remote files\n        // will execute the command as-is.\n        String[] commandTokens;\n        if (currentFolder == null || currentFolder.hasAncestor(LocalFile.class)) {\n            tokens[tokens.length - 1] = command;\n            commandTokens = tokens;\n        } else {\n            remoteTokens[0] = command;\n            commandTokens  = remoteTokens;\n        }\n\n        // Starts the process.\n        if (autoDetectEncoding) {\n            if (listener == null) {\n                listener = new ShellEncodingListener();\n            } else {\n                ProcessListenerList listeners = new ProcessListenerList();\n                listeners.add(listener);\n                listeners.add(new ShellEncodingListener());\n                listener = listeners;\n            }\n        }\n        return (encoding == null) ? ProcessRunner.execute(commandTokens, currentFolder, listener) : ProcessRunner.execute(commandTokens, currentFolder, listener, encoding);\n    }\n\n\n\n    // - Configuration management --------------------------------------------\n    // -----------------------------------------------------------------------\n    /**\n     * Extracts the shell command from configuration.\n     */\n    private static synchronized void setShellCommand() {\n        String shellCommand;\n\n        // Retrieves the configuration defined shell command.\n        if (TcConfigurations.getPreferences().getVariable(TcPreference.USE_CUSTOM_SHELL, TcPreferences.DEFAULT_USE_CUSTOM_SHELL))\n            shellCommand = TcConfigurations.getPreferences().getVariable(TcPreference.CUSTOM_SHELL, DesktopManager.getDefaultShell());\n        else\n            shellCommand = DesktopManager.getDefaultShell();\n\n        // Splits the command into tokens, leaving room for the argument.\n        String[] buffer = Command.getTokens(shellCommand);\n        tokens = new String[buffer.length + 1];\n        System.arraycopy(buffer, 0, tokens, 0, buffer.length);\n\n        // Retrieves encoding configuration.\n        encoding = TcConfigurations.getPreferences().getVariable(TcPreference.SHELL_ENCODING);\n        autoDetectEncoding = TcConfigurations.getPreferences().getVariable(TcPreference.AUTODETECT_SHELL_ENCODING, TcPreferences.DEFAULT_AUTODETECT_SHELL_ENCODING);\n    }\n\n    /**\n     * Reacts to configuration changes.\n     */\n    public void configurationChanged(ConfigurationEvent event) {\n        if (event.getVariable().startsWith(TcPreferences.SHELL_SECTION)) {\n            setShellCommand();\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/shell/ShellEncodingListener.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.shell;\n\nimport java.io.ByteArrayOutputStream;\nimport java.nio.charset.Charset;\n\nimport com.mucommander.commons.io.EncodingDetector;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.process.ProcessListener;\n\n/**\n * Listens to shell output and tries to guess at its encoding.\n * @author Nicolas Rinaudo\n */\nclass ShellEncodingListener implements ProcessListener {\n    private static ByteArrayOutputStream out = new ByteArrayOutputStream();\n\n    public synchronized void processDied(int returnValue) {\n        // Abort if there is no need to identify the encoding anymore.\n        if (out == null) {\n            return;\n        }\n\n        // Attempts to guess at the encoding. If no guess can be made, ignore.\n        String encoding = EncodingDetector.detectEncoding(out.toByteArray());\n        if (encoding == null) {\n            return;\n        }\n\n        // Checks whether the detected charset is supported.\n        if (Charset.isSupported(encoding)) {\n            String oldEncoding = TcConfigurations.getPreferences().getVariable(TcPreference.SHELL_ENCODING);\n\n            // If no encoding was previously set, or we have found a new encoding, change the current shell encoding.\n            if(!encoding.equals(oldEncoding)) {\n                TcConfigurations.getPreferences().setVariable(TcPreference.SHELL_ENCODING, encoding);\n            }\n\n            // Stop listening for new byte input if we have gathered a large enough sample set.\n            if (out.size() >= EncodingDetector.MAX_RECOMMENDED_BYTE_SIZE) {\n                out = null;\n            }\n        }\n    }\n\n    /**\n     * Ignored.\n     */\n    public void processOutput(String output) {}\n\n    public synchronized void processOutput(byte[] buff, int from, int len) {\n        if (out != null && (len = Math.min(len, EncodingDetector.MAX_RECOMMENDED_BYTE_SIZE - out.size())) > 0) {\n            out.write(buff, from, len);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/shell/ShellHistoryConstants.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.shell;\n\n/**\n * Defines the structure of the shell history XML file.\n * @author Nicolas Rinaudo\n */\ninterface ShellHistoryConstants {\n    /** Name of the XML file's root element. */\n    String ROOT_ELEMENT    = \"history\";\n    /** Name of a command element in the XML file. */\n    String COMMAND_ELEMENT = \"command\";\n    /** Name of the root element's attribute containing the muCommander version that was used to create the shell history file */\n    String ATTRIBUTE_VERSION      = \"version\";\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/shell/ShellHistoryListener.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.shell;\n\n/**\n * Interface used to monitor changes to the shell history.\n * @author Nicolas Rinaudo\n */\npublic interface ShellHistoryListener {\n    /**\n     * Notifies the listener that a new element has been added to the history.\n     * @param command command that was added to the history.\n     */\n    void historyChanged(String command);\n\n    /**\n     * Notifies the listeners that the history has been cleared.\n     */\n    void historyCleared();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/shell/ShellHistoryManager.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.shell;\n\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.util.Iterator;\nimport java.util.NoSuchElementException;\nimport java.util.WeakHashMap;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.PlatformManager;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.io.backup.BackupInputStream;\nimport com.mucommander.io.backup.BackupOutputStream;\n\n/**\n * Used to manage shell HISTORY.\n * <p>\n * Using this class is fairly basic: you can add elements to the shell HISTORY through\n * {@link #add(String)} and browse it through {@link #getHistoryIterator()}.\n *\n * @author Nicolas Rinaudo\n */\npublic class ShellHistoryManager {\n\tprivate static Logger logger;\n\t\n    /** File in which to store the shell HISTORY. */\n    private static final String DEFAULT_HISTORY_FILE_NAME = \"shell_history.xml\";\n\n\n    /** List of shell HISTORY registered LISTENERS. */\n    private static final WeakHashMap<ShellHistoryListener, ?> LISTENERS;\n    /** Stores the shell HISTORY. */\n    private static final String[] HISTORY;\n    /** Index of the first element of the HISTORY. */\n    private static int historyStart;\n    /** Index of the last element of the HISTORY. */\n    private static int historyEnd;\n    /** Path to the HISTORY file. */\n    private static AbstractFile historyFile;\n\n\n\n    /**\n     * Prevents instantiations of the class.\n     */\n    private ShellHistoryManager() {}\n\n    static {\n        HISTORY = new String[TcConfigurations.getPreferences().getVariable(TcPreference.SHELL_HISTORY_SIZE, TcPreferences.DEFAULT_SHELL_HISTORY_SIZE)];\n        LISTENERS = new WeakHashMap<>();\n    }\n\n    /**\n     * Registers a listener to changes in the shell HISTORY.\n     * @param listener listener to register.\n     */\n    public static void addListener(ShellHistoryListener listener) {\n        LISTENERS.put(listener, null);}\n\n    /**\n     * Propagates shell HISTORY events to all registered LISTENERS.\n     * @param command command that was added to the shell HISTORY.\n     */\n    private static void triggerEvent(String command) {\n        for (ShellHistoryListener listener : LISTENERS.keySet()) {\n            listener.historyChanged(command);\n        }\n    }\n\n\n\n    // - History access -------------------------------------------------------------\n    // ------------------------------------------------------------------------------\n    /**\n     * Completely empties the shell HISTORY.\n     */\n    public static void clear() {\n        // Empties HISTORY.\n        historyStart = 0;\n        historyEnd   = 0;\n\n        // Notifies LISTENERS.\n        for (ShellHistoryListener listener : LISTENERS.keySet()) {\n            listener.historyCleared();\n        }\n    }\n\n    /**\n     * Returns a <b>non thread-safe</b> iterator on the HISTORY.\n     * @return an iterator on the HISTORY.\n     */\n    public static Iterator<String> getHistoryIterator() {return new HistoryIterator();}\n\n    /**\n     * Adds the specified command to shell HISTORY.\n     * @param command command to add to the shell HISTORY.\n     */\n    public static void add(String command) {\n        // Ignores empty commands.\n        if (command.trim().isEmpty()) {\n            return;\n        }\n        // Ignores the command if it's the same as the last one.\n        // There is no last command if HISTORY is empty.\n        if (historyEnd != historyStart) {\n            // Computes the index of the previous command.\n            int lastIndex = historyEnd == 0 ? HISTORY.length - 1 : historyEnd - 1;\n            if (command.equals(HISTORY[lastIndex])) {\n                return;\n            }\n        }\n\n        getLogger().debug(\"Adding  \" + command + \" to shell HISTORY.\");\n\n        // Updates the HISTORY buffer.\n        HISTORY[historyEnd] = command;\n        historyEnd++;\n\n        // Wraps around the HISTORY buffer.\n        if (historyEnd == HISTORY.length) {\n            historyEnd = 0;\n        }\n\n        // Clears items from the beginning of the buffer if necessary.\n        if (historyEnd == historyStart) {\n            if (++historyStart == HISTORY.length) {\n                historyStart = 0;\n            }\n        }\n\n        // Propagates the event.\n        triggerEvent(command);\n    }\n\n\n\n    // - History saving / loading ---------------------------------------------------\n    // ------------------------------------------------------------------------------\n    /**\n     * Sets the path of the shell HISTORY file.\n     * @param     path                  where to load the shell HISTORY from.\n     * @exception FileNotFoundException if <code>path</code> is not accessible.\n     * @see                             #getHistoryFile()\n     * @see                             #setHistoryFile(File)\n     * @see                             #setHistoryFile(AbstractFile)\n     */\n    public static void setHistoryFile(String path) throws FileNotFoundException {\n        AbstractFile file = FileFactory.getFile(path);\n        if (file == null) {\n            setHistoryFile(new File(path));\n        } else {\n            setHistoryFile(file);\n        }\n    }\n\n    /**\n     * Sets the path of the shell HISTORY file.\n     * @param     file                  where to load the shell HISTORY from.\n     * @exception FileNotFoundException if <code>path</code> is not accessible.\n     * @see                             #getHistoryFile()\n     * @see                             #setHistoryFile(AbstractFile)\n     * @see                             #setHistoryFile(String)\n     */\n    public static void setHistoryFile(File file) throws FileNotFoundException {\n        setHistoryFile(FileFactory.getFile(file != null ? file.getAbsolutePath() : null));\n    }\n\n    /**\n     * Sets the path of the shell HISTORY file.\n     * @param     file                  where to load the shell HISTORY from.\n     * @exception FileNotFoundException if <code>path</code> is not accessible.\n     * @see                             #getHistoryFile()\n     * @see                             #setHistoryFile(File)\n     * @see                             #setHistoryFile(String)\n     */\n    public static void setHistoryFile(AbstractFile file) throws FileNotFoundException {\n        // Makes sure file can be used as a shell HISTORY file.\n        if (file.isBrowsable()) {\n            throw new FileNotFoundException(\"Not a valid file: \" + file.getAbsolutePath());\n        }\n        historyFile = file;\n    }\n\n    /**\n     * Returns the path to the shell HISTORY file.\n     * <p>\n     * This method cannot guarantee the file's existence, and it's up to the caller\n     * to deal with the fact that the user might not actually have created a HISTORY file yet.\n     * <p>\n     * This method's return value can be modified through {@link #setHistoryFile(String)}.\n     * If this wasn't called, the default path will be used: {@link #DEFAULT_HISTORY_FILE_NAME}\n     * in the {@link com.mucommander.PlatformManager#getPreferencesFolder() preferences} folder.\n     *\n     * @return             the path to the shell HISTORY file.\n     * @throws IOException if an error occurred while locating the default shell HISTORY file.\n     * @see                #setHistoryFile(File)\n     * @see                #setHistoryFile(String)\n     * @see                #setHistoryFile(AbstractFile)\n     */\n    public static AbstractFile getHistoryFile() throws IOException {\n        if (historyFile == null) {\n            return PlatformManager.getPreferencesFolder().getChild(DEFAULT_HISTORY_FILE_NAME);\n        }\n        return historyFile;\n    }\n\n    /**\n     * Writes the shell HISTORY to hard drive.\n     * @throws IOException if an I/O error occurs.\n     */\n    public static void writeHistory() throws IOException {\n        try (BackupOutputStream out = new BackupOutputStream(getHistoryFile())) {\n            ShellHistoryWriter.write(out);\n        }\n    }\n\n    /**\n     * Loads the shell HISTORY.\n     * @throws Exception if an error occurs.\n     */\n    public static void loadHistory() throws Exception {\n        try (BackupInputStream in = new BackupInputStream(getHistoryFile())) {\n            ShellHistoryReader.read(in);\n        }\n    }\n\n    private static Logger getLogger() {\n        if (logger == null) {\n            logger = LoggerFactory.getLogger(ShellHistoryManager.class);\n        }\n        return logger;\n    }\n\n\n    /**\n     * Iterator used to browse HISTORY.\n     * @author Nicolas Rinaudo\n     */\n    static class HistoryIterator implements Iterator<String> {\n        /** Index in the HISTORY. */\n        private int index;\n\n        /**\n         * Creates a new HISTORY iterator.\n         */\n        HistoryIterator() {\n            index = ShellHistoryManager.historyStart;\n        }\n\n        /**\n         * Returns <code>true</code> if there are more elements to iterate through.\n         * @return <code>true</code> if there are more elements to iterate through, <code>false</code> otherwise.\n         */\n        public boolean hasNext() {\n            return index != ShellHistoryManager.historyEnd;\n        }\n\n        /**\n         * Returns the next element in the HISTORY.\n         * @return the next element in the HISTORY.\n         */\n        public String next() throws NoSuchElementException {\n            if (!hasNext()) {\n                throw new NoSuchElementException();\n            }\n\n            String value = ShellHistoryManager.HISTORY[index];\n            if (++index == ShellHistoryManager.HISTORY.length) {\n                index = 0;\n            }\n            return value;\n        }\n\n        /**\n         * Operation not supported.\n         */\n        public void remove() throws UnsupportedOperationException {\n            throw new UnsupportedOperationException();\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/shell/ShellHistoryReader.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.shell;\n\nimport java.io.InputStream;\nimport javax.xml.parsers.SAXParserFactory;\nimport org.xml.sax.Attributes;\nimport org.xml.sax.helpers.DefaultHandler;\n\n/**\n * Parses XML shell history files and populates the {@link com.mucommander.shell.ShellHistoryManager}.\n * @author Nicolas Rinaudo\n */\nclass ShellHistoryReader extends DefaultHandler implements ShellHistoryConstants {\n    /** Parsing hasn't started. */\n    private static final int STATUS_UNKNOWN = 0;\n    /** Currently parsing the root tag. */\n    private static final int STATUS_ROOT    = 1;\n    /** Currently parsing a command tag. */\n    private static final int STATUS_COMMAND = 2;\n\n\n\n    /** Reader's current status. */\n    private int          status;\n    /** Buffer for the current command. */\n    private final StringBuilder command;\n    /** muCommander version that was used to write the shell history file */\n    private String version;\n\n\n    /**\n     * Creates a new shell history reader.\n     */\n    private ShellHistoryReader() {\n        command = new StringBuilder();\n        status = STATUS_UNKNOWN;\n    }\n\n    /**\n     * Returns the muCommander version that was used to write the shell history file, <code>null</code> if it is unknown.\n     * <p>\n     * Note: the version attribute was introduced in muCommander 0.8.4.\n     *\n     * @return the muCommander version that was used to write the shell history file, <code>null</code> if it is unknown.\n     */\n    public String getVersion() {\n        return version;\n    }\n\n\n    // - XML interaction -----------------------------------------------------\n    // -----------------------------------------------------------------------\n    /**\n     * Reads shell history from the specified input stream.\n     * @param in where to read the history from.\n     */\n    public static void read(InputStream in) throws Exception {SAXParserFactory.newInstance().newSAXParser().parse(in, new ShellHistoryReader());}\n\n    /**\n     * Notifies the reader that CDATA has been encountered.\n     */\n    @Override\n    public void characters(char[] ch, int start, int length) {\n        if (status == STATUS_COMMAND) {\n            command.append(ch, start, length);\n        }\n    }\n\n    /**\n     * Notifies the reader that a new XML element is starting.\n     */\n    @Override\n    public void startElement(String uri, String localName, String qName, Attributes attributes) {\n\n        if (qName.equals(ROOT_ELEMENT) && (status == STATUS_UNKNOWN)) {     // Root element declaration.\n            status = STATUS_ROOT;\n            version = attributes.getValue(ATTRIBUTE_VERSION);\n        } else if(qName.equals(COMMAND_ELEMENT) && status == STATUS_ROOT) { // Command element declaration.\n            status = STATUS_COMMAND;\n        }\n    }\n\n    /**\n     * Notifies the reader that the current element declaration is over.\n     */\n    @Override\n    public void endElement(String uri, String localName, String qName) {\n        if (qName.equals(ROOT_ELEMENT) && (status == STATUS_ROOT)) {    // Root element finished.\n            status = STATUS_UNKNOWN;\n        } else if(qName.equals(COMMAND_ELEMENT) && (status == STATUS_COMMAND)) {    // Command element finished.\n            status = STATUS_ROOT;\n\n            // Adds the current command to shell history.\n            ShellHistoryManager.add(command.toString().trim());\n            command.setLength(0);\n\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/shell/ShellHistoryWriter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.shell;\n\nimport java.io.OutputStream;\nimport java.util.Iterator;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.RuntimeConstants;\nimport com.mucommander.utils.xml.XmlAttributes;\nimport com.mucommander.utils.xml.XmlWriter;\n\n/**\n * Used to save the content of the {@link com.mucommander.shell.ShellHistoryManager} to a file.\n * @author Nicolas Rinaudo\n */\nclass ShellHistoryWriter implements ShellHistoryConstants {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(ShellHistoryWriter.class);\n\t\n\t/**\n     * Writes the content of the {@link com.mucommander.shell.ShellHistoryManager} to the specified output stream.\n     * @param stream where to save the shell history.\n     */\n    public static void write(OutputStream stream) {\n        // Initialises writing.\n        Iterator<String>history = ShellHistoryManager.getHistoryIterator();\n\n        try {\n            // Opens the file for writing.\n            XmlWriter out = new XmlWriter(stream);\n\n            // Version the file\n            XmlAttributes attributes = new XmlAttributes();\n            attributes.add(ATTRIBUTE_VERSION, RuntimeConstants.VERSION);\n\n            out.startElement(ROOT_ELEMENT, attributes);\n            out.println();\n\n            // Writes the content of the shell history.\n            while (history.hasNext()) {\n                out.startElement(COMMAND_ELEMENT);\n                out.writeCData(history.next());\n                out.endElement(COMMAND_ELEMENT);\n            }\n            out.endElement(ROOT_ELEMENT);\n        } catch(Exception e) {\n            LOGGER.debug(\"Failed to write shell history\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/shell/package.html",
    "content": "<body>\n  Generic shell and shell history API.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/tools/AdbTool.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2015 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.tools;\n\nimport com.mucommander.process.AbstractProcess;\nimport com.mucommander.process.ProcessRunner;\n\n/**\n * @author Oleg Trifonov\n * Created on 09/09/15.\n */\npublic class AdbTool extends ExternalTool {\n\n    @Override\n    boolean detect() {\n        try {\n            AbstractProcess process = ProcessRunner.execute(\"adb devices\");\n            process.waitFor();\n            return process.exitValue() == 0;\n        } catch (Throwable t) {\n            return false;\n        }\n    }\n\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/tools/AvrAssemblerCommandsHelper.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2015 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.tools;\n\nimport com.mucommander.commons.file.util.ResourceLoader;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.lang.ref.WeakReference;\nimport java.util.Properties;\n\n/**\n * @author Oleg Trifonov\n * Created on 27/05/16.\n */\npublic class AvrAssemblerCommandsHelper {\n\n    private static final String AVR_COMMANDS_FILE = \"/avr/avr_commands.properties\";\n    private static WeakReference<Properties> propertiesRef;\n\n    public static String getCommandDescription(String mnemonic) {\n        if (mnemonic == null) {\n            return null;\n        }\n        if (propertiesRef == null || propertiesRef.get() == null) {\n            propertiesRef = new WeakReference<>(loadProperties());\n        }\n        return propertiesRef.get().getProperty(mnemonic.toUpperCase());\n    }\n\n    private static Properties loadProperties() {\n        Properties properties = new Properties();\n        try (InputStream is = ResourceLoader.getResourceAsStream(AVR_COMMANDS_FILE)) {\n            properties.load(is);\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        return properties;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/tools/ExternalTool.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.tools;\n\nimport java.io.File;\n\n/**\n * Base class for external tools.\n *\n * @author Oleg Trifonov\n * Created on 09/09/15.\n */\npublic abstract class ExternalTool {\n\n    /**\n     * Path to application / file\n     */\n    private String fullPath;\n\n    /**\n     *\n     * @return true if the tool is available on current OS\n     */\n    public boolean isActive() {\n        return true;\n    }\n\n    /**\n     * Try to find tools\n     * @return true if tool was found\n     */\n    abstract boolean detect();\n\n\n    /**\n     *\n     * @param path full file path\n     * @return true if file exists\n     */\n    protected boolean checkFileExists(String path) {\n        return new File(path).exists();\n    }\n\n    public String getFullPath() {\n        return fullPath;\n    }\n\n    public void setFullPath(String fullPath) {\n        this.fullPath = fullPath;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/tools/FileMergeTool.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.tools;\n\nimport com.mucommander.commons.runtime.OsFamily;\n\n/**\n * @author Oleg Trifonov\n * Created on 09/09/15.\n */\npublic class FileMergeTool extends ExternalTool {\n\n    @Override\n    public boolean isActive() {\n        return OsFamily.MAC_OS_X.isCurrent();\n    }\n\n    @Override\n    boolean detect() {\n        String path = \"/Applications/Xcode.app/Contents/Applications/FileMerge.app\";\n        if (checkFileExists(path)) {\n            setFullPath(path);\n            return true;\n        }\n        // opendiff <file1> <file2>\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/PreloadedJFrame.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui;\n\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.awt.LayoutManager;\nimport java.util.Queue;\nimport java.util.concurrent.ConcurrentLinkedDeque;\n\nimport javax.swing.JFrame;\nimport javax.swing.JPanel;\n\n/**\n * A class that extends JFrame to be later on used by core.\n * Since this bundle is loaded almost as the first (as it has no deps), it can be\n * used by core having JFrame \"preloaded\" aka \"cached\" in JVM.\n*/\n@Slf4j\npublic class PreloadedJFrame extends JFrame {\n    private static final int PRELOAD_FRAMES = 2;\n    private static final int PRELOAD_PANELS = 6;\n\n\n    private static final Queue<PreloadedJFrame> preloadedFrame = new ConcurrentLinkedDeque<>();\n\n    private static final Queue<JPanel> preloadedPanels = new ConcurrentLinkedDeque<>();\n\n    private Object mainFrameObject;\n\n    public static void init() {\n        new Thread(() -> {\n            log.info(\"Going to pre-create a couple of JFrames...\");\n            var start = System.currentTimeMillis();\n            for (int i = 0; i < PRELOAD_FRAMES; i++) {\n                preloadedFrame.add(new PreloadedJFrame());\n            }\n            log.info(\"JFrames pre-creation completed in {}ms\", (System.currentTimeMillis() - start));\n\n            log.info(\"Going to pre-create a couple of JPanels...\");\n            start = System.currentTimeMillis();\n            for (int i = 0; i < PRELOAD_PANELS; i++) {\n                preloadedPanels.add(new JPanel());\n            }\n            log.info(\"JPanel pre-creation completed in {}ms\", (System.currentTimeMillis() - start));\n\n        }, \"Preload-JFrame\").start();\n    }\n\n    private void setMainFrameObject(Object mainFrameObj) {\n        this.mainFrameObject = mainFrameObj;\n    }\n\n    public Object getMainFrameObject() {\n        return mainFrameObject;\n    }\n\n    public static JFrame getJFrame(Object mainFrame) {\n        var result = preloadedFrame.poll();\n        if (result == null) {\n            result = new PreloadedJFrame();\n        }\n        result.setMainFrameObject(mainFrame);\n        return result;\n    }\n\n    public static JPanel getJPanel(LayoutManager layout) {\n        var result = preloadedPanels.poll();\n        if (result == null) {\n            result = new JPanel(layout);\n        } else {\n            result.setLayout(layout);\n            // Re-apply the current L&F defaults. Preloaded panels are created eagerly\n            // in a background thread, potentially before the user's chosen L&F is installed.\n            result.updateUI();\n        }\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/AbstractActionDescriptor.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action;\r\n\r\nimport com.mucommander.commons.file.util.ResourceLoader;\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.icon.IconManager;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.KeyEvent;\r\n\r\n/**\r\n * AbstractActionDescriptor is an abstract class which implements ActionDescriptor interface.\r\n * this class implements the following methods which are common to all action descriptors:\r\n * {@link ActionDescriptor#getLabel()}\r\n * {@link ActionDescriptor#getIcon()}\r\n * {@link ActionDescriptor#getTooltip()}\r\n * \r\n * @author Arik Hadas\r\n */\r\npublic abstract class AbstractActionDescriptor implements ActionDescriptor {\r\n    protected static final int CTRL_OR_META_DOWN_MASK = OsFamily.MAC_OS_X.isCurrent() ? KeyEvent.META_DOWN_MASK : KeyEvent.CTRL_DOWN_MASK;\r\n\r\n    @Override\r\n    public String getLabel() {\r\n        String label = getStandardLabel();\r\n        return label != null ? label : getLabelKey();\r\n    }\r\n\r\n    @Override\r\n    public ImageIcon getIcon() {\r\n        return getStandardIcon(getId());\r\n    }\r\n\r\n    @Override\r\n    public String getTooltip() {\r\n        return getStandardTooltip(getId());\r\n    }\r\n\r\n    @Override\r\n    public String getDescription() {\r\n    \tString tooltip = getTooltip();\r\n    \treturn tooltip == null ? getLabel() : tooltip;\r\n    }\r\n    \r\n    /**\r\n     * Returns the dictionary key for action's label, using the following standard naming convention:\r\n     * <pre>\r\n     *      action_id.label\r\n     * </pre>\r\n     * where <code>action_id</code> is a String identification of the action, as returned by <code>getId()</code>.\r\n     *\r\n     * @return the standard dictionary key for the action's label\r\n     */\r\n    @Override\r\n    public String getLabelKey() {\r\n\t\treturn getId()+\".label\";\r\n\t}\r\n\r\n    /**\r\n     * Implements {@link ActionDescriptor#isParameterized()} by returning <code>false</code>, which suits most actions.\r\n     * This method can be overridden to change this behavior.\r\n     *\r\n     * @return <code>false</code>\r\n     */\r\n    @Override\r\n    public boolean isParameterized() {\r\n        return false;\r\n    }\r\n\r\n\r\n\r\n    /**\r\n     * Queries {@link Translator} for a label corresponding to the action using the standard naming convention.\r\n     * Returns the label or <code>null</code> if no corresponding entry was found in the dictionary.\r\n     *\r\n     * @return the standard label corresponding to the MuAction, <code>null</code> if none was found\r\n     */\r\n    private String getStandardLabel() {\r\n    \tString labelKey = getLabelKey();\r\n        if (!Translator.hasValue(labelKey, true))\r\n            return null;\r\n\r\n        return Translator.get(labelKey);\r\n    }\r\n    \r\n    /**\r\n     * Queries {@link IconManager} for an image icon corresponding to the specified action using standard icon path\r\n     * conventions. Returns the image icon, <code>null</code> if none was found.\r\n     *\r\n     * @param actionId a String identification of MuAction\r\n     * @return the standard icon image corresponding to the specified MuAction, <code>null</code> if none was found\r\n     */\r\n    protected static ImageIcon getStandardIcon(String actionId) {\r\n        // Look for an icon image file with the /action/<action id>.png path and use it if it exists\r\n        String iconPath = getStandardIconPath(actionId);\r\n        return ResourceLoader.getResourceAsURL(iconPath) == null ? null : IconManager.getIcon(iconPath);\r\n    }\r\n    \r\n    /**\r\n     * Returns the standard path to the icon image for the specified {@link TcAction} id.\r\n     * The returned path is relative to the application's JAR file.\r\n     *\r\n     * @param actionId a String identification of MuAction\r\n     * @return the standard path to the icon image corresponding to the specified MuAction\r\n     */\r\n    private static String getStandardIconPath(String actionId) {\r\n        return IconManager.IconSet.ACTION.getFolder() + actionId + \".png\";\r\n    }\r\n    \r\n    /**\r\n     * Queries {@link Translator} for a tooltip corresponding to the specified action using standard naming conventions.\r\n     * Returns the tooltip or <code>null</code> if no corresponding entry was found in the dictionary.\r\n     *\r\n     * @param actionId a String identification of MuAction\r\n     * @return the standard tooltip corresponding to the specified MuAction, <code>null</code> if none was found\r\n     */\r\n    private static String getStandardTooltip(String actionId) {\r\n        String tooltipKey = getStandardTooltipKey(actionId);\r\n        if (!Translator.hasValue(tooltipKey, true))\r\n            return null;\r\n\r\n        return Translator.get(tooltipKey);\r\n    }\r\n    \r\n    /**\r\n     * Returns the dictionary key for the specified action's tooltip, using the following standard naming convention:\r\n     * <pre>\r\n     *      action_id.tooltip\r\n     * </pre>\r\n     * where <code>action_id</code> is a String identification of the action, as returned by <code>getId()</code>.\r\n     *\r\n     * @param actionId a String identification of MuAction\r\n     * @return the standard dictionary key for the specified action's tooltip\r\n     */\r\n    private static String getStandardTooltipKey(String actionId) {\r\n        return actionId+\".tooltip\";\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/AcceleratorMap.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action;\n\nimport javax.swing.*;\n\nimport com.mucommander.commons.util.Pair;\n\nimport java.util.HashMap;\n\n/**\n * Data structure that maps KeyStroke (accelerator) to MuAction id.\n * \n * @author Arik Hadas\n */\npublic class AcceleratorMap {\n    \n\t// Accelerator types\n\tpublic final static int PRIMARY_ACCELERATOR = 1;\n    public final static int ALTERNATIVE_ACCELERATOR = 2;\n    \n    // Maps KeyStrokes to MuAction id and accelerator type (PRIMARY_ACCELERATOR/ALTERNATIVE_ACCELERATOR) pair.\n\tprivate static final HashMap<KeyStroke, Pair<String, Integer>> map = new HashMap<>();\n    \n\t/**\n\t * Register KeyStroke to MuAction as primary accelerator.\n\t * \n\t * @param ks - accelerator\n\t * @param actionId - id of MuAction to which the given accelerator would be registered.\n\t */\n\tpublic void putAccelerator(KeyStroke ks, String actionId) {\n\t\tput(ks, actionId, PRIMARY_ACCELERATOR);\n\t}\n\t\n\t/**\n\t * Register KeyStroke to MuAction as alternative accelerator.\n\t * \n\t * @param ks - alternative accelerator.\n\t * @param actionId - id of MuAction to which the given accelerator would be registered.\n\t */\n\tpublic void putAlternativeAccelerator(KeyStroke ks, String actionId) {\n\t\tput(ks, actionId, ALTERNATIVE_ACCELERATOR);\n\t}\n\t\n\t/**\n\t * Return id of MuAction that accelerator is registered to.\n\t * \n\t * @param ks - accelerator.\n\t * @return id of MuAction that the given accelerator is registered to.\n\t */\n    public String getActionId(KeyStroke ks) {\n    \tPair<String, Integer> idAndType = getActionIdAndAcceleratorTypeOfKeyStroke(ks);\n    \treturn idAndType != null ? idAndType.first : null;\n    }\n    \n    /**\n     * Return accelerator type.\n     * \n     * @param ks - accelerator.\n     * @return the type of the given accelerator (primary(1)/alternative(2)/not registered(0)).\n     */\n    public int getAcceleratorType(KeyStroke ks) {\n    \tPair<String, Integer> idAndType = getActionIdAndAcceleratorTypeOfKeyStroke(ks);\n    \treturn idAndType != null ? idAndType.second : 0;\n    }\n    \n    /**\n     * Remove accelerator.\n     * \n     * @param ks - accelerator.\n     */\n    public void remove(KeyStroke ks) {\n    \tmap.remove(ks);\n    }\n    \n    /**\n     * Remove all accelerators.\n     */\n    public void clear() {\n    \tmap.clear();\n    }\n    \n    private void put(KeyStroke ks, String actionId, int acceleratorType) {\n    \tif (ks != null)\n    \t\tmap.put(ks, new Pair<>(actionId, acceleratorType));\n    }\n    \n    private Pair<String, Integer> getActionIdAndAcceleratorTypeOfKeyStroke(KeyStroke ks) {\n    \treturn map.get(ks);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/ActionCategory.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action;\r\n\r\nimport com.mucommander.utils.text.Translator;\r\n\r\n/**\r\n * ActionCategory represent category that can be associated with MuAction action.\r\n * \r\n * @author Arik Hadas\r\n */\r\npublic enum ActionCategory {\r\n    ALL(\"all\") {\r\n        @Override\r\n        public boolean contains(String actionId) {\r\n            return true;\r\n        }\r\n    },\r\n    NAVIGATION(\"navigation\"),\r\n    SELECTION(\"selection\"),\r\n    VIEW(\"view\"),\r\n    FILES(\"file_operations\"),\r\n    WINDOW(\"windows\"),\r\n    TAB(\"tabs\"),\r\n    MISC(\"misc\"),\r\n    COMMANDS(\"commands\");\r\n\r\n\r\n    /** The category's label key in the dictionary file */\r\n    private final String descriptionKey;\r\n\r\n    ActionCategory(String descriptionKey) {\r\n        this.descriptionKey = \"action_categories.\" + descriptionKey;\r\n    }\r\n\r\n    public String getDescriptionKey() {\r\n        return descriptionKey;\r\n    }\r\n\r\n    public String getDescription() { return Translator.get(descriptionKey); }\r\n\r\n    public boolean contains(String actionId) {\r\n        ActionCategory actionCategory = ActionProperties.getActionCategory(actionId);\r\n        return actionCategory != null && descriptionKey.equals(actionCategory.getDescriptionKey());\r\n    }\r\n\r\n    @Override\r\n    public String toString() {\r\n        String description = getDescription();\r\n        if (description != null) {\r\n            return description;\r\n        }\r\n        return getDescriptionKey();\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/ActionDescriptor.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action;\r\n\r\nimport javax.swing.*;\r\n\r\n/**\r\n * Each MuAction is registered with an object of ActionDescriptor type\r\n * that provides its properties. ActionDescriptor is an interface that \r\n * defines those action's properties. \r\n * \r\n * @author Arik Hadas\r\n */\r\npublic interface ActionDescriptor extends ActionFactory {\r\n\t\r\n\tString getId();\r\n\t\r\n\tString getDescription();\r\n\t\r\n\tActionCategory getCategory();\r\n\t\r\n\tString getLabel();\r\n\t\r\n\tString getLabelKey();\r\n\t\r\n\tKeyStroke getDefaultKeyStroke();\r\n\t\r\n\tKeyStroke getDefaultAltKeyStroke();\r\n\t\r\n\tImageIcon getIcon();\r\n\t\r\n\tString getTooltip();\r\n\r\n    /**\r\n     * Returns <code>true</code> if the action requires parameters at creation time.\r\n     *\r\n     * @return <code>true</code> if the action requires parameters at creation time.\r\n     */\r\n    boolean isParameterized();\r\n\r\n\r\n//\tMuAction createAction(MainFrame mainFrame, Map<String,Object> properties);\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/ActionFactory.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action;\r\n\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport java.util.Map;\r\n\r\n/**\r\n *  Each MuAction's factory should implement this interface.\r\n *\r\n * @author Arik Hadas\r\n */\r\npublic interface ActionFactory {\r\n\t\r\n\t/**\r\n\t * This is an initiation method that returns an instance of MuAction subclass.\r\n\t * \r\n\t * @param mainFrame - MainFrame.\r\n\t * @param properties - a hashtable of arguments for the action. \r\n\t * @return an instance of MuAction subclass.\r\n\t */\r\n\tTcAction createAction(MainFrame mainFrame, Map<String,Object> properties);\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/ActionKeymap.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action;\n\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.*;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.Map;\n\n/**\n * This class manages keyboard associations with {@link TcAction} ids.\n *\n * @author Maxence Bernard, Arik Hadas\n */\npublic class ActionKeymap {\n\n    /** Maps action id onto Keystroke instances */\n    private static final Map<String, KeyStroke> customPrimaryActionKeymap = new HashMap<>();\n    /** Maps action id instances onto Keystroke instances */\n    private static final Map<String, KeyStroke> customAlternateActionKeymap = new HashMap<>();\n    /** Maps Keystroke instances onto action id */\n    private static final AcceleratorMap acceleratorMap = new AcceleratorMap();\n\n    /*=================\n     * Public Methods *\n     =================*/\n    \n    /**\n     * Register all action shortcuts to the given MainFrame's file tables.\n     * \n     * @param mainFrame - MainFrame instance to which all action shortcuts would be registered.\n     */\n    public static void registerActions(MainFrame mainFrame) {\n        Iterator<String> actionIds = ActionManager.getActionIds();\n\n        while (actionIds.hasNext()) {\n            String actionId = actionIds.next();\n            ActionDescriptor actionDescriptor = ActionProperties.getActionDescriptor(actionId);\n\n            // Instantiate the action only if it is not parameterized: parameterized actions should only be instantiated\n            // when they are needed and with the required parameters.\n            if (!actionDescriptor.isParameterized()) {\n                registerAction(mainFrame, ActionManager.getActionInstance(actionId, mainFrame));\n            }\n        }\n    }\n    \n    /**\n     * Register MuAction to JComponent with the given condition. \n     * \n     * @param action - MuAction instance.\n     * @param comp - JComponent to which the action would be registered\n     * @param condition - condition in which the action could be invoked. \n     */\n    public static void registerActionAccelerators(TcAction action, JComponent comp, int condition) {\n    \tregisterActionAccelerator(action, action.getAccelerator(), comp, condition);\n    \tregisterActionAccelerator(action, action.getAlternateAccelerator(), comp, condition);\n    }\n\n    /**\n     * Register new accelerators to the given action.\n     * \n     * Note: if accelerator is null, it would be replaced by alternateAccelerator.\n     * \n     * @param actionId - id of the MuAction.\n     * @param accelerator - KeyStroke that would be primary accelerator of the given action.\n     * @param alternateAccelerator - KeyStroke that would be alternative accelerator of the given action.\n     */\n    public static void changeActionAccelerators(String actionId, KeyStroke accelerator, KeyStroke alternateAccelerator) {\n    \t// Check whether the given actions are already assigned to the given action\n    \tif (equals(accelerator, ActionKeymap.getAccelerator(actionId)) &&\n    \t\t\tequals(alternateAccelerator, ActionKeymap.getAlternateAccelerator(actionId)))\n    \t\treturn;\n    \t\n    \t// If primary accelerator is already registered to other MuAction, unregister it.\n    \tString previousActionForAccelerator = getRegisteredActionIdForKeystroke(accelerator);\n    \tif (previousActionForAccelerator != null && !previousActionForAccelerator.equals(actionId))\n    \t\tunregisterAcceleratorFromAction(previousActionForAccelerator, accelerator);\n    \t\n    \t// If alternative accelerator is already registered to other MuAction, unregister it.\n    \tString previousActionForAlternativeAccelerator = getRegisteredActionIdForKeystroke(alternateAccelerator);\n    \tif (previousActionForAlternativeAccelerator != null && !previousActionForAlternativeAccelerator.equals(actionId))\n    \t\tunregisterAcceleratorFromAction(previousActionForAlternativeAccelerator, alternateAccelerator);\n    \t\n    \t// Remove action's previous accelerators (primary and alternate)\n    \tacceleratorMap.remove(customPrimaryActionKeymap.remove(actionId));\n    \tacceleratorMap.remove(customAlternateActionKeymap.remove(actionId));\n\n    \t// Register new accelerators\n    \tregisterActionAccelerators(actionId, accelerator, alternateAccelerator);\n    }\n    \n    private static void unregisterAcceleratorFromAction(String actionId, KeyStroke accelerator) {\n    \tswitch (getAcceleratorType(accelerator)) {\n\t\t\tcase AcceleratorMap.PRIMARY_ACCELERATOR:\n\t\t\t\tregisterActionAccelerators(actionId, null, getAlternateAccelerator(actionId));\n\t\t\t\tbreak;\n\t\t\tcase AcceleratorMap.ALTERNATIVE_ACCELERATOR:\n\t\t\t\tregisterActionAccelerators(actionId, getAccelerator(actionId), null);\n\t\t\t\tbreak;\n\t\t}\n    }\n    \n    /**\n     * Register all primary and alternative accelerator given.\n     * \n     * @param primary - HashMap that maps action id to primary accelerator.\n     * @param alternate - HashMap that maps action id to alternative accelerator.\n     */\n    public static void registerActions(Map<String, KeyStroke> primary, Map<String, KeyStroke> alternate) {\n    \tfor (String actionId : primary.keySet()) {\n    \t\t// Add the action/keystroke mapping\n    \t\tActionKeymap.registerActionAccelerators(\n    \t\t\t\tactionId,\n    \t\t\t\tprimary.get(actionId),\n    \t\t\t\talternate.get(actionId));\n    \t}\n    }\n\n    /**\n     * Check whether an accelerator is registered to MuAction.\n     * \n     * @param ks - accelerator.\n     * @return true if the given accelerator is registered to whatever MuAction, false otherwise.\n     */\n    public static boolean isKeyStrokeRegistered(KeyStroke ks) {\n    \treturn getRegisteredActionIdForKeystroke(ks)!=null;\n    }\n    \n    /**\n     * Check whether the action has accelerator assigned to it.\n     * \n     * @param actionId - id of MuAction.\n     * @return true if there is a shortcut which is assigned to the action, false otherwise.\n     */\n    public static boolean doesActionHaveShortcut(String actionId) {\n    \treturn getAccelerator(actionId) != null;\n    }\n    \n    /**\n     * Restore the default accelerators assignments (remove custom assignments).\n     */\n    public static void restoreDefault() {\n    \tcustomPrimaryActionKeymap.clear();\n    \tcustomAlternateActionKeymap.clear();\n    \tacceleratorMap.clear();\n    }\n    \n    ///////////////////\n    ///// getters /////\n    ///////////////////\n    \n    /**\n     * Return primary accelerator of MuAction.\n     * \n     * @param actionId - id of MuAction.\n     * @return primary accelerator of the given MuAction.\n     */\n    public static KeyStroke getAccelerator(String actionId) {\n    \tif (customPrimaryActionKeymap.containsKey(actionId))\n    \t\treturn customPrimaryActionKeymap.get(actionId);\n        return ActionProperties.getDefaultAccelerator(actionId);\n    }\n\n    /**\n     * Return alternative accelerator of MuAction.\n     * \n     * @param actionId - id of MuAction.\n     * @return alternative accelerator of the given MuAction.\n     */\n    public static KeyStroke getAlternateAccelerator(String actionId) {\n    \tif (customAlternateActionKeymap.containsKey(actionId))\n    \t\treturn customAlternateActionKeymap.get(actionId);\n    \treturn ActionProperties.getDefaultAlternativeAccelerator(actionId);\n    }\n\n    /**\n     * Return the id of MuAction to which accelerator is registered.\n     * \n     * @param ks - accelerator.\n     * @return id of MuAction to which the given accelerator is registered, null if the accelerator is not registered.\n     */\n    public static String getRegisteredActionIdForKeystroke(KeyStroke ks) {\n    \tString actionId = acceleratorMap.getActionId(ks);\n        return actionId != null ? actionId : ActionProperties.getDefaultActionForKeyStroke(ks);\n    }\n    \n    /**\n     * Return accelerator type: primary\\alternative\\not registered.\n     * \n     * @param ks - accelerator.\n     * @return accelerator type.\n     */\n    private static int getAcceleratorType(KeyStroke ks) {\n    \tint type = acceleratorMap.getAcceleratorType(ks);\n    \treturn type != 0 ? type : ActionProperties.getDefaultAcceleratorType(ks);\n    }\n    \n    /**\n     * Return ids of actions that their registered accelerators are different from their default accelerators.\n     * \n     * @return Iterator of actions that their accelerators were customized.\n     */\n    public static Iterator<String> getCustomizedActions() {\n    \tHashSet<String> modifiedActions = new HashSet<>();\n    \tmodifiedActions.addAll(customPrimaryActionKeymap.keySet());\n    \tmodifiedActions.addAll(customAlternateActionKeymap.keySet());\n    \treturn modifiedActions.iterator();\n    }\n    \n    \n    /*==================\n     * Private Methods *\n     ==================*/\n\n    /**\n     * Register MuAction instance to MainFrame instance.\n     */\n    private static void registerAction(MainFrame mainFrame, TcAction action) {\n        registerActionAccelerators(action, mainFrame.getLeftPanel().getFileTable(), JComponent.WHEN_FOCUSED);\n        registerActionAccelerators(action, mainFrame.getRightPanel().getFileTable(), JComponent.WHEN_FOCUSED);\n    }\n\n    /**\n     * Register accelerator of MuAction to JComponent with a condition that states when the action can be invoked.\n     */\n    private static void registerActionAccelerator(TcAction action, KeyStroke accelerator, JComponent comp, int condition) {\n    \tif (accelerator != null) {\n    \t\tInputMap inputMap = comp.getInputMap(condition);\n    \t\tActionMap actionMap = comp.getActionMap();\n    \t\tString actionId = action.getId();//action.getDescriptor().getId();\n    \t\tinputMap.put(accelerator, actionId);\n    \t\tactionMap.put(actionId, action);\n    \t}\n    }\n\n    /**\n     * Unregister MuAction instance from MainFrame instance.\n     */\n    private static void unregisterAction(MainFrame mainFrame, TcAction action) {\n        unregisterActionAccelerators(action, mainFrame.getLeftPanel().getFileTable(), JComponent.WHEN_FOCUSED);\n        unregisterActionAccelerators(action, mainFrame.getRightPanel().getFileTable(), JComponent.WHEN_FOCUSED);\n    }\n    \n    /**\n     * Unregister MuAction from JComponent.\n     */\n    private static void unregisterActionAccelerators(TcAction action, JComponent comp, int condition) {\n    \tunregisterActionAccelerator(action, action.getAccelerator(), comp, condition);\n    \tunregisterActionAccelerator(action, action.getAlternateAccelerator(), comp, condition);\n    }\n    \n    /**\n     * Unregister accelerator of MuAction from JComponent.\n     */\n    private static void unregisterActionAccelerator(TcAction action, KeyStroke accelerator, JComponent comp, int condition) {\n    \tif (accelerator != null) {\n    \t\tInputMap inputMap = comp.getInputMap(condition);\n    \t\tActionMap actionMap = comp.getActionMap();\n    \t\tinputMap.remove(accelerator);\n\t\t\tString actionId  = action.getId();\t// action.getDescriptor().getId()\n    \t\tactionMap.remove(actionId);\n    \t}\n    }\n    \n    /**\n     * Register primary and alternative accelerators to MuAction.\n     */\n    private static void registerActionAccelerators(String actionId, KeyStroke accelerator, KeyStroke alternateAccelerator) {\n    \t// If accelerator is null, replace it with alternateAccelerator\n    \tif (accelerator == null) {\n    \t\taccelerator = alternateAccelerator;\n    \t\talternateAccelerator = null;\n    \t}\n    \t\n    \t// New accelerators are identical to the default action's accelerators\n    \tif (equals(accelerator, ActionProperties.getDefaultAccelerator(actionId)) &&\n    \t\t\tequals(alternateAccelerator, ActionProperties.getDefaultAlternativeAccelerator(actionId))) {\n    \t\t// Remove all action's accelerators customization\n    \t\tcustomPrimaryActionKeymap.remove(actionId);\n    \t\tcustomAlternateActionKeymap.remove(actionId);\n    \t\tacceleratorMap.remove(accelerator);\n    \t\tacceleratorMap.remove(alternateAccelerator);\n    \t}\n    \t// New accelerators are different from the default accelerators\n    \telse {\n    \t\tcustomPrimaryActionKeymap.put(actionId, accelerator);\n    \t\tacceleratorMap.putAccelerator(accelerator, actionId);\n\n    \t\tcustomAlternateActionKeymap.put(actionId, alternateAccelerator);\n    \t\tacceleratorMap.putAlternativeAccelerator(alternateAccelerator, actionId);\n    \t}\n    \t\n    \t// Update each MainFrame's action instance and input map\n    \tfor(TcAction action : ActionManager.getActionInstances(actionId)) {\n    \t\tMainFrame mainFrame = action.getMainFrame();\n\n    \t\t// Remove action from MainFrame's action and input maps\n    \t\tunregisterAction(mainFrame, action);\n\n    \t\t// Change action's accelerators\n    \t\taction.setAccelerator(accelerator);\n    \t\taction.setAlternateAccelerator(alternateAccelerator);\n\n    \t\t// Add updated action to MainFrame's action and input maps\n    \t\tregisterAction(mainFrame, action);\n    \t}\n    }\n    \n    /**\n     * Return true if the two KeyStrokes are equal, false otherwise.\n     */\n    private static boolean equals(Object first, Object second) {\n    \tif (first == null) {\n\t\t\treturn second == null;\n\t\t}\n    \treturn first.equals(second);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/ActionKeymapIO.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action;\r\n\r\nimport java.io.File;\r\nimport java.io.FileNotFoundException;\r\nimport java.io.IOException;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.xml.sax.helpers.DefaultHandler;\r\n\r\nimport com.mucommander.PlatformManager;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileFactory;\r\n\r\n/**\r\n * This class contains the common things to the actions reading and writing.\r\n * \r\n * @author Maxence Bernard, Arik Hadas\r\n */\r\npublic abstract class ActionKeymapIO extends DefaultHandler  {\r\n\tprivate static Logger logger;\r\n\t\r\n\t/* Variables used for XML parsing */\r\n\tprotected final static String ROOT_ELEMENT = \"keymap\";\r\n    final static String ACTION_ELEMENT = \"action\";\r\n    final static String CLASS_ATTRIBUTE = \"class\";\r\n    final static String ID_ATTRIBUTE = \"id\";\r\n    final static String PRIMARY_KEYSTROKE_ATTRIBUTE = \"keystroke\";\r\n    final static String ALTERNATE_KEYSTROKE_ATTRIBUTE = \"alt_keystroke\";\r\n    /** Attribute containing the last muCommander version that was used to create the file */\r\n\tprotected static final String VERSION_ATTRIBUTE  = \"version\";\r\n    \r\n    /** Actions file used when calling {@link #loadActionKeymap()} */\r\n    private static AbstractFile actionsFile;\r\n\t\r\n    /** Default actions filename */\r\n    private final static String DEFAULT_ACTIONS_FILE_NAME = \"action_keymap.xml\";\r\n    /** Path to the actions resource file within the application JAR file */\r\n    public final static String ACTION_KEYMAP_RESOURCE_PATH = \"/\" + DEFAULT_ACTIONS_FILE_NAME;\r\n    \r\n    /** Whether the actions have been modified since the last time they were saved */\r\n    protected static boolean wereActionsModified;\r\n    \r\n    private static final ActionKeymapWriter writer = new ActionKeymapWriter();\r\n    \r\n\t/**\r\n     * Sets the path to the user actions file to be loaded when calling {@link #loadActionKeymap()}.\r\n     * By default, this file is {@link #DEFAULT_ACTIONS_FILE_NAME} within the preferences folder.\r\n     * <p>\r\n     * This is a convenience method and is strictly equivalent to calling <code>setActionsFile(FileFactory.getFile(file))</code>.\r\n     *\r\n     * @param  path                  path to the actions file\r\n     * @throws FileNotFoundException if <code>file</code> is not accessible.\r\n     */\r\n    public static void setActionsFile(String path) throws FileNotFoundException {\r\n        AbstractFile file = FileFactory.getFile(path);\r\n\r\n        if (file == null) {\r\n            setActionsFile(new File(path));\r\n        } else {\r\n            setActionsFile(file);\r\n        }\r\n    }\r\n    \r\n    /**\r\n     * Sets the path to the user actions file to be loaded when calling {@link #loadActionKeymap()}.\r\n     * By default, this file is {@link #DEFAULT_ACTIONS_FILE_NAME} within the preferences folder.\r\n     * <p>\r\n     * This is a convenience method and is strictly equivalent to calling <code>setActionsFile(FileFactory.getFile(file.getAbsolutePath()))</code>.\r\n     *\r\n     * @param  file                  path to the actions file\r\n     * @throws FileNotFoundException if <code>file</code> is not accessible.\r\n     */\r\n    private static void setActionsFile(File file) throws FileNotFoundException {\r\n        setActionsFile(FileFactory.getFile(file.getAbsolutePath()));\r\n    }\r\n\r\n    /**\r\n     * Sets the path to the user actions file to be loaded when calling {@link #loadActionKeymap()}.\r\n     * By default, this file is {@link #DEFAULT_ACTIONS_FILE_NAME} within the preferences folder.\r\n     * @param  file                  path to the actions file\r\n     * @throws FileNotFoundException if <code>file</code> is not accessible.\r\n     */\r\n    private static void setActionsFile(AbstractFile file) throws FileNotFoundException {\r\n        if (file.isBrowsable()) {\r\n            throw new FileNotFoundException(\"Not a valid file: \" + file.getAbsolutePath());\r\n        }\r\n\r\n        actionsFile = file;\r\n    }\r\n    \r\n    /**\r\n     * Returns the actions file.\r\n     * @return             the actions file.\r\n     * @throws IOException if an error occurred while locating the default actions file.\r\n     */\r\n    protected static AbstractFile getActionsFile() throws IOException {\r\n        if (actionsFile == null) {\r\n            return PlatformManager.getPreferencesFolder().getChild(DEFAULT_ACTIONS_FILE_NAME);\r\n        }\r\n        return actionsFile;\r\n    }\r\n    \r\n    /**\r\n     * Mark that actions were modified and therefore should be saved.\r\n     */\r\n    public static void setModified() { wereActionsModified = true; }\r\n    \r\n    /**\r\n     * Writes the current action keymaps to the user's actions file.\r\n     * @throws IOException\r\n     */\r\n    public static void saveActionKeymap() throws IOException {\r\n    \tif (wereActionsModified)\r\n    \t\twriter.write();\r\n    \telse\r\n            getLogger().debug(\"Action keymap not modified, not saving\");\r\n    }\r\n    \r\n    protected static void createEmptyFile() {\r\n    \twriter.create();\r\n    }\r\n    \r\n    /**\r\n     * Loads the action files: loads the one contained in the JAR file first, and then the user's one.\r\n     * This means any new action in the JAR action keymap (when a new version is released) will have the default\r\n     * keyboard mapping, but the keyboard mappings customized by the user in the user's action keymap will override\r\n     * the ones from the JAR action keymap.\r\n     *\r\n     * <p>This method must be called before requesting and registering any action.\r\n     */\r\n    public static void loadActionKeymap() throws Exception {\r\n    \t// Load user's file if exist\r\n    \tAbstractFile actionKeymapFile = getActionsFile();\r\n    \tif (actionKeymapFile != null && actionKeymapFile.exists()) {\r\n    \t\tActionKeymapReader reader = new ActionKeymapReader(actionKeymapFile);\r\n    \t\tActionKeymap.registerActions(reader.getPrimaryActionsKeymap(), reader.getAlternateActionsKeymap());\r\n    \t} else {\r\n    \t\tcreateEmptyFile();\r\n    \t\tgetLogger().debug(DEFAULT_ACTIONS_FILE_NAME + \" was not found, created empty file\");\r\n    \t}\r\n    }\r\n\r\n\r\n    private static Logger getLogger() {\r\n        if (logger == null) {\r\n            logger = LoggerFactory.getLogger(ActionKeymapIO.class);\r\n        }\r\n        return logger;\r\n    }\r\n}\r\n\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/ActionKeymapReader.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action;\r\n\r\nimport java.io.IOException;\r\nimport java.io.InputStream;\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport javax.xml.parsers.ParserConfigurationException;\r\nimport javax.xml.parsers.SAXParserFactory;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.xml.sax.Attributes;\r\nimport org.xml.sax.SAXException;\r\n\r\nimport com.mucommander.RuntimeConstants;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.io.backup.BackupInputStream;\r\n\r\n/**\r\n * This class is responsible for reading the actions.\r\n * it read and parse the two action files - the default one \r\n * (placed in the jar file) and the user's one.\r\n * \r\n * @author Maxence Bernard, Arik Hadas\r\n */\r\nclass ActionKeymapReader extends ActionKeymapIO {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(ActionKeymapReader.class);\r\n\t\r\n\t/** Maps action Class onto Keystroke instances*/\r\n    private Map<String, KeyStroke> primaryActionsReadKeymap;\r\n    /** Maps action Class instances onto Keystroke instances*/\r\n    private Map<String, KeyStroke> alternateActionsReadKeymap;\r\n\r\n    /** Parsed file */\r\n    private final AbstractFile file;\r\n    \r\n    /**\r\n     * Loads the action file: loads the one contained in the JAR file first, and then the user's one.\r\n     * This means any new action in the JAR action keymap (when a new version gets released) will have the default\r\n     * keyboard mapping, but the keyboard mappings customized by the user in the user's action keymap will override\r\n     * the ones from the JAR action keymap.\r\n     *\r\n     * Starts parsing the XML actions file.\r\n     * \r\n     * @throws ParserConfigurationException \r\n     * @throws IOException \r\n     * @throws SAXException \r\n     */\r\n    ActionKeymapReader(AbstractFile file) throws SAXException, IOException, ParserConfigurationException {\r\n    \tthis.file = file;\r\n\r\n    \ttry (InputStream in = new BackupInputStream(file)) {\r\n\t\t\tSAXParserFactory.newInstance().newSAXParser().parse(in, this);\r\n\t\t}\r\n    }\r\n\t\r\n    /**\r\n     * Parses the keystrokes defined in the given attribute map (if any) and associates them with the given action id.\r\n     * The keystroke will not be associated in any of the following cases:\r\n     * <ul>\r\n     *  <li>the keystrokes attributes do not contain any value.</li>\r\n     *  <li>the keystrokes attributes have values that do not represent a valid KeyStroke (syntax error).</li>\r\n     * </ul>\r\n     * If a given keystroke is already associated to an action, the existing association is replaced. \r\n     * If there is a valid alternative keystroke defined but there is no valid primary keystroke defined, the primary keystroke \r\n     * is replaced by the alternative keystroke.\r\n     *\r\n     * @param actionId the action id to associate the keystroke with\r\n     * @param attributes the attributes map that holds the value\r\n     */\r\n    private void processKeystrokeAttribute(String actionId, Attributes attributes) {\r\n    \t// Parse the primary keystroke and retrieve the corresponding KeyStroke instance\r\n    \tString keyStrokeString = attributes.getValue(PRIMARY_KEYSTROKE_ATTRIBUTE);\r\n\r\n        KeyStroke primaryKeyStroke = null;\r\n    \tif (keyStrokeString != null) {\r\n    \t\tprimaryKeyStroke = KeyStroke.getKeyStroke(keyStrokeString);\r\n    \t\tif (primaryKeyStroke == null) {\r\n                LOGGER.info(\"Action keymap file contains a keystroke which could not be resolved: \" + keyStrokeString);\r\n            } else {\r\n    \t\t\tString prevAssignedActionId = ActionKeymap.getRegisteredActionIdForKeystroke(primaryKeyStroke);\r\n    \t\t\tif (prevAssignedActionId != null && !prevAssignedActionId.equals(actionId)) {\r\n                    LOGGER.debug(\"Canceling previous association of keystroke \" + keyStrokeString + \", reassign it to action: \" + actionId);\r\n                }\r\n    \t\t}\r\n    \t}\r\n\r\n    \t// Parse the alternate keystroke and retrieve the corresponding KeyStroke instance\r\n    \tkeyStrokeString = attributes.getValue(ALTERNATE_KEYSTROKE_ATTRIBUTE);\r\n\r\n        KeyStroke alternateKeyStroke = null;\r\n    \tif (keyStrokeString != null) {\r\n    \t\talternateKeyStroke = KeyStroke.getKeyStroke(keyStrokeString);\r\n    \t\tif (alternateKeyStroke == null) {\r\n                LOGGER.info(\"Action keymap file contains a keystroke which could not be resolved: \" + keyStrokeString);\r\n            } else {\r\n    \t\t\tString prevAssignedActionId = ActionKeymap.getRegisteredActionIdForKeystroke(alternateKeyStroke);\r\n    \t\t\tif (prevAssignedActionId != null && !prevAssignedActionId.equals(actionId)) {\r\n                    LOGGER.debug(\"Canceling previous association of keystroke \" + keyStrokeString + \", reassign it to action: \" + actionId);\r\n                }\r\n    \t\t}\r\n    \t}\r\n\r\n   \t\t// If there is no primary shortcut defined for the action but there is an alternative shortcut defined,\r\n   \t\t// turn the alternative shortcut to the action's primary shortcut\r\n   \t\tif (primaryKeyStroke == null) {\r\n   \t\t\tLOGGER.debug(\"Action \\\"\" + actionId +\"\\\" has an alternative shortcut with no primary shortcut, so the alternative shortcut become primary\");\r\n   \t\t\tprimaryActionsReadKeymap.put(actionId, alternateKeyStroke);\r\n   \t\t\talternateActionsReadKeymap.put(actionId, null);\r\n   \t\t\t// Mark that the actions keymap file should be updated\r\n  \t\t\tsetModified();\r\n   \t\t}\r\n   \t\telse {\r\n   \t\t\tprimaryActionsReadKeymap.put(actionId, primaryKeyStroke);\r\n   \t\t\talternateActionsReadKeymap.put(actionId, alternateKeyStroke);    \t\t\r\n   \t\t}\r\n    }\r\n\r\n    ///////////////////\r\n    ///// getters /////\r\n    ///////////////////\r\n    \r\n    Map<String, KeyStroke> getPrimaryActionsKeymap() {\r\n\t\treturn primaryActionsReadKeymap;\r\n\t}\r\n    \r\n    Map<String, KeyStroke> getAlternateActionsKeymap() {\r\n\t\treturn alternateActionsReadKeymap;\r\n\t}\r\n    \r\n    ///////////////////////////////////\r\n    // ContentHandler implementation //\r\n    ///////////////////////////////////\r\n\r\n    @Override\r\n    public void startDocument() {\r\n    \tLOGGER.trace(file.getAbsolutePath()+\" parsing started\");\r\n    \t\r\n    \tprimaryActionsReadKeymap = new HashMap<>();\r\n    \talternateActionsReadKeymap = new HashMap<>();\r\n    }\r\n    \r\n    @Override\r\n    public void endDocument() {\r\n    \tLOGGER.trace(file.getAbsolutePath()+\" parsing finished\");\r\n    }\r\n    \r\n    @Override\r\n    public void startElement(String uri, String localName, String qName, Attributes attributes) {\r\n    \tif (qName.equals(ACTION_ELEMENT)) {\r\n    \t\t// Retrieve the action id\r\n    \t\tString actionId = attributes.getValue(ID_ATTRIBUTE);\r\n    \t\t// if id attribute not exits, read class attribute\r\n    \t\tif (actionId == null) {\r\n    \t\t\tString actionClassPath = attributes.getValue(CLASS_ATTRIBUTE);\r\n    \t\t\t\r\n    \t\t\tif (actionClassPath == null) {\r\n    \t\t\t\tLOGGER.warn(\"Error in action keymap file: no 'class' or 'id' attribute specified in 'action' element\");\r\n        \t\t\treturn;\r\n        \t\t}\r\n    \t\t\t// extrapolate the action id from its class path\r\n    \t\t\tactionId = ActionManager.extrapolateId(actionClassPath);\r\n    \t\t}\r\n    \t\t\r\n    \t\tif (!ActionManager.isActionExist(actionId)) {\r\n    \t\t\tLOGGER.warn(\"Error in action keymap file: could not resolve action \" + actionId);\r\n    \t\t\treturn;\r\n    \t\t}\r\n\r\n    \t\t// Load the action's accelerators (if any)\r\n    \t\tprocessKeystrokeAttribute(actionId, attributes);\r\n    \t} else if (qName.equals(ROOT_ELEMENT)) {\r\n    \t\t// Note: early 0.8 beta3 nightly builds did not have version attribute, so the attribute may be null\r\n            String fileVersion = attributes.getValue(VERSION_ATTRIBUTE);\r\n    \t\t\r\n    \t\t// if the file's version is not up-to-date, update the file to the current version at quitting.\r\n    \t\tif (!RuntimeConstants.VERSION.equals(fileVersion)) {\r\n                setModified();\r\n            }\r\n    \t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/ActionKeymapWriter.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action;\r\n\r\nimport java.io.IOException;\r\nimport java.io.OutputStream;\r\nimport java.util.Hashtable;\r\nimport java.util.Iterator;\r\nimport java.util.Map;\r\n\r\nimport javax.swing.KeyStroke;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport com.mucommander.RuntimeConstants;\r\nimport com.mucommander.io.backup.BackupOutputStream;\r\nimport com.mucommander.ui.text.KeyStrokeUtils;\r\nimport com.mucommander.utils.xml.XmlAttributes;\r\nimport com.mucommander.utils.xml.XmlWriter;\r\n\r\n/**\r\n * This class is responsible for writing the actions.\r\n * When actions are modified, they are written to the user's actions file. \r\n * \r\n * @author Maxence Bernard, Arik Hadas\r\n */\r\nclass ActionKeymapWriter extends ActionKeymapIO {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(ActionKeymapWriter.class);\r\n\t\r\n\tActionKeymapWriter() {}\r\n\t\r\n\tpublic void create() {\r\n\r\n\t\ttry (BackupOutputStream bos = new BackupOutputStream(getActionsFile())) {\r\n\t\t\tnew Writer(bos).writeKeyMap(null);\r\n\t\t} catch (Exception e) {\r\n\t\t\tLOGGER.debug(\"Caught exception\", e);\r\n\t\t}\r\n\t}\r\n\t\r\n\tvoid write() {\r\n\t\tMap<String, KeyStroke[]> combinedMapping = new Hashtable<>();\r\n\t\tIterator<String> modifiedActionsIterator = ActionKeymap.getCustomizedActions();\r\n\r\n\t\twhile(modifiedActionsIterator.hasNext()) {\r\n\t\t\tString actionId = modifiedActionsIterator.next();\r\n\t\t\tKeyStroke[] keyStrokes = new KeyStroke[2];\r\n\t\t\tkeyStrokes[0] = ActionKeymap.getAccelerator(actionId);\r\n\t\t\tkeyStrokes[1] = ActionKeymap.getAlternateAccelerator(actionId);\r\n\r\n\t\t\tcombinedMapping.put(actionId, keyStrokes);\r\n\t\t}\r\n\r\n\t\ttry (BackupOutputStream bos = new BackupOutputStream(getActionsFile())) {\r\n\t\t\tnew Writer(bos).writeKeyMap(combinedMapping);\r\n\t\t\twereActionsModified = false;\r\n\t\t} catch (Exception e) {\r\n\t\t\tLOGGER.debug(\"Caught exception\", e);\r\n\t\t}\r\n\t}\r\n\t\r\n    private static class Writer {\r\n    \tprivate final XmlWriter writer;\r\n\r\n    \tprivate Writer(OutputStream stream) throws IOException {\r\n    \t\tthis.writer = new XmlWriter(stream);\r\n    \t}\r\n    \t\r\n    \tprivate void writeKeyMap(Map<String, KeyStroke[]> actionMap) throws IOException {\r\n    \t\ttry {\r\n    \t\t\twriter.writeCommentLine(\"See http://trac.mucommander.com/wiki/ActionKeyMap for information on how to customize this file\");\r\n    \t\t\t\r\n    \t\t\tXmlAttributes rootElementAttributes = new XmlAttributes();\r\n\t\t\t\trootElementAttributes.add(VERSION_ATTRIBUTE, RuntimeConstants.VERSION);\r\n    \t\t\t\r\n    \t\t\twriter.startElement(ROOT_ELEMENT, rootElementAttributes, true);\r\n\r\n    \t\t\tif (actionMap != null) {\r\n                    for (String actionId: actionMap.keySet()) {\r\n\t\t\t\t\t\taddMapping(actionId, actionMap.get(actionId));\r\n\t\t\t\t\t}\r\n    \t\t\t}\r\n\r\n    \t\t} finally {\r\n    \t\t\twriter.endElement(ROOT_ELEMENT);\r\n    \t\t}\r\n    \t}\r\n\r\n    \tprivate void addMapping(String actionId, KeyStroke[] keyStrokes) throws IOException {\r\n    \t\tXmlAttributes attributes = new XmlAttributes();\r\n    \t\tattributes.add(ID_ATTRIBUTE, actionId);\r\n\r\n    \t    LOGGER.trace(\"     Writing mapping of \"  + actionId + \" to \" + keyStrokes[0] + \" and \" + keyStrokes[1]);\r\n\r\n    \t\tif (keyStrokes[0] != null) {\r\n\t\t\t\tattributes.add(PRIMARY_KEYSTROKE_ATTRIBUTE, KeyStrokeUtils.getKeyStrokeRepresentation(keyStrokes[0]));\r\n\t\t\t}\r\n\r\n    \t\tif (keyStrokes[1] != null) {\r\n\t\t\t\tattributes.add(ALTERNATE_KEYSTROKE_ATTRIBUTE, KeyStrokeUtils.getKeyStrokeRepresentation(keyStrokes[1]));\r\n\t\t\t}\r\n    \t\t\r\n    \t\twriter.writeStandAloneElement(ACTION_ELEMENT, attributes);\r\n    \t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/ActionManager.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action;\n\nimport com.mucommander.command.Command;\nimport com.mucommander.command.CommandManager;\nimport com.mucommander.command.CommandType;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.ui.action.impl.*;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.ImageIcon;\nimport javax.swing.KeyStroke;\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * ActionManager provides methods to retrieve {@link TcAction} instances and invoke them. It keeps track of all the\n * action instances it has created and allows them to be reused within a {@link MainFrame}.\n *\n * <p>MuAction subclasses should not be instantiated directly, <code>getActionInstance</code>\n * methods should be used instead. Using ActionManager to retrieve a MuAction ensures that only one instance\n * exists for a given {@link MainFrame}. This is particularly important because actions are stateful and can be used\n * in several components of a MainFrame at the same time; if an action's state changes, the change must be reflected\n * everywhere the action is used. It is also important for performance reasons: sharing one action throughout a\n * {@link com.mucommander.ui.main.MainFrame} saves some memory and also CPU cycles as some actions listen to particular events to change\n * their state accordingly.\n *\n * @see TcAction\n * @see ActionParameters\n * @see ActionKeymap\n * @author Maxence Bernard, Arik Hadas\n */\npublic class ActionManager {\n\t//private static final Logger LOGGER = LoggerFactory.getLogger(ActionManager.class);\n\t\n    /* MuAction id -> factory map */\n    //static final Map<String, ActionFactory> actionFactories = new HashMap<>();\n    \n    /** MainFrame -> MuAction map */\n    private static final WeakHashMap<MainFrame, Map<ActionParameters, ActionAndIdPair>> mainFrameActionsMap = new WeakHashMap<>();\n    \n    /** Pattern to resolve the action ID from action class path */\n    private final static Pattern PATTERN = Pattern.compile(\".*\\\\.(.*)?Action\");\n\n    public static void registerActions() {\n    \tregisterAction(new AddBookmarkAction.Descriptor());\n    \tregisterAction(new AddTabAction.Descriptor());\n    \tregisterAction(new BatchRenameAction.Descriptor());\n    \tregisterAction(new BringAllToFrontAction.Descriptor());\n    \tregisterAction(new CalculateChecksumAction.Descriptor());\n    \tregisterAction(new ChangeDateAction.Descriptor());\n\t\tregisterAction(new ChangeReplicationAction.Descriptor());\n    \tregisterAction(new ChangeLocationAction.Descriptor());\n    \tregisterAction(new ChangePermissionsAction.Descriptor());\n    \tregisterAction(new CheckForUpdatesAction.Descriptor());\n    \tregisterAction(new CloneTabToOtherPanelAction.Descriptor());\n    \tregisterAction(new CloseDuplicateTabsAction.Descriptor());\n    \tregisterAction(new CloseOtherTabsAction.Descriptor());\n    \tregisterAction(new CloseWindowAction.Descriptor());\n    \tregisterAction(new CloseTabAction.Descriptor());\n//    \tregisterAction(new CommandAction.Descriptor());\n    \tregisterAction(new CompareFoldersAction.Descriptor());\n\t\tregisterAction(new CompareFolderFilesAction.Descriptor());\n    \tregisterAction(new ConnectToServerAction.Descriptor());\n    \tregisterAction(new CopyAction.Descriptor());\n    \tregisterAction(new CopyFileBaseNamesAction.Descriptor());\n    \tregisterAction(new CopyFileNamesAction.Descriptor());\n    \tregisterAction(new CopyFilePathsAction.Descriptor());\n    \tregisterAction(new CopyFilesToClipboardAction.Descriptor());\n    \tregisterAction(new FocusPreviousAction.Descriptor());\n    \tregisterAction(new FocusNextAction.Descriptor());\n    \tregisterAction(new DeleteAction.Descriptor());\n    \tregisterAction(new DonateAction.Descriptor());\n    \tregisterAction(new DuplicateTabAction.Descriptor());\n    \tregisterAction(new EditAction.Descriptor());\n    \tregisterAction(new EditBookmarksAction.Descriptor());\n    \tregisterAction(new EditCredentialsAction.Descriptor());\n    \tregisterAction(new EmailAction.Descriptor());\n    \tregisterAction(new EmptyTrashAction.Descriptor());\n    \tregisterAction(new ExploreBookmarksAction.Descriptor());\n//    \tregisterAction(new GarbageCollectAction.Descriptor());\n    \tregisterAction(new GoBackAction.Descriptor());\n    \tregisterAction(new GoForwardAction.Descriptor());\n    \tregisterAction(new GoToDocumentationAction.Descriptor());\n    \tregisterAction(new GoToForumsAction.Descriptor());\n    \tregisterAction(new GoToHomeAction.Descriptor());\n    \tregisterAction(new GoToParentAction.Descriptor());\n    \tregisterAction(new GoToParentInBothPanelsAction.Descriptor());\n    \tregisterAction(new GoToParentInOtherPanelAction.Descriptor());\n    \tregisterAction(new GoToRootAction.Descriptor());\n    \tregisterAction(new GoToWebsiteAction.Descriptor());\n    \tregisterAction(new InternalEditAction.Descriptor());\n    \tregisterAction(new InternalViewAction.Descriptor());\n    \tregisterAction(new InvertSelectionAction.Descriptor());\n    \tregisterAction(new LocalCopyAction.Descriptor());\n    \tregisterAction(new MarkAllAction.Descriptor());\n    \tregisterAction(new MarkExtensionAction.Descriptor());\n    \tregisterAction(new MarkGroupAction.Descriptor());\n        registerAction(new MarkNextBlockAction.Descriptor());\n    \tregisterAction(new MarkNextPageAction.Descriptor());\n        registerAction(new MarkNextRowAction.Descriptor());\n        registerAction(new MarkPreviousBlockAction.Descriptor());\n    \tregisterAction(new MarkPreviousPageAction.Descriptor());\n        registerAction(new MarkPreviousRowAction.Descriptor());\n    \tregisterAction(new MarkSelectedFileAction.Descriptor());\n    \tregisterAction(new MarkToFirstRowAction.Descriptor());\n    \tregisterAction(new MarkToLastRowAction.Descriptor());\n    \tregisterAction(new MaximizeWindowAction.Descriptor());\n    \tregisterAction(new CombineFilesAction.Descriptor());\n    \tregisterAction(new MinimizeWindowAction.Descriptor());\n    \tregisterAction(new MkdirAction.Descriptor());\n\t\tregisterAction(new MkfileAction.Descriptor());\n    \tregisterAction(new MoveAction.Descriptor());\n    \tregisterAction(new MoveTabToOtherPanelAction.Descriptor());\n    \tregisterAction(new NewWindowAction.Descriptor());\n    \tregisterAction(new NextTabAction.Descriptor());\n    \tregisterAction(new OpenAction.Descriptor());\n\t\tregisterAction(new OpenAsAction.Descriptor());\n    \tregisterAction(new OpenInBothPanelsAction.Descriptor());\n    \tregisterAction(new OpenInNewTabAction.Descriptor());\n    \tregisterAction(new OpenInOtherPanelAction.Descriptor());\n        registerAction(new OpenLeftInRightPanelAction.Descriptor());\n        registerAction(new OpenRightInLeftPanelAction.Descriptor());\n//    \tregisterAction(new OpenLocationAction.Descriptor());\n    \tregisterAction(new OpenNativelyAction.Descriptor());\n    \tregisterAction(new OpenTrashAction.Descriptor());\n    \tregisterAction(new OpenURLInBrowserAction.Descriptor());\n    \tregisterAction(new PackAction.Descriptor());\n    \tregisterAction(new PasteClipboardFilesAction.Descriptor());\n    \tregisterAction(new PermanentDeleteAction.Descriptor());\n    \tregisterAction(new PopupLeftDriveButtonAction.Descriptor());\n    \tregisterAction(new PopupRightDriveButtonAction.Descriptor());\n    \tregisterAction(new PreviousTabAction.Descriptor());\n    \tregisterAction(new QuitAction.Descriptor());\n    \tregisterAction(new RecallNextWindowAction.Descriptor());\n    \tregisterAction(new RecallPreviousWindowAction.Descriptor());\n    \tregisterAction(new RecallWindow10Action.Descriptor());\n    \tregisterAction(new RecallWindow1Action.Descriptor());\n    \tregisterAction(new RecallWindow2Action.Descriptor());\n    \tregisterAction(new RecallWindow3Action.Descriptor());\n    \tregisterAction(new RecallWindow4Action.Descriptor());\n    \tregisterAction(new RecallWindow5Action.Descriptor());\n    \tregisterAction(new RecallWindow6Action.Descriptor());\n    \tregisterAction(new RecallWindow7Action.Descriptor());\n    \tregisterAction(new RecallWindow8Action.Descriptor());\n    \tregisterAction(new RecallWindow9Action.Descriptor());\n    \tregisterAction(new RecallWindowAction.Descriptor());\n    \tregisterAction(new RefreshAction.Descriptor());\n    \tregisterAction(new RenameAction.Descriptor());\n    \tregisterAction(new ReportBugAction.Descriptor());\n    \tregisterAction(new RevealInDesktopAction.Descriptor());\n    \tregisterAction(new ReverseSortOrderAction.Descriptor());\n    \tregisterAction(new RunCommandAction.Descriptor());\n        registerAction(new SelectPreviousBlockAction.Descriptor());\n        registerAction(new SelectPreviousPageAction.Descriptor());\n        registerAction(new SelectPreviousRowAction.Descriptor());\n        registerAction(new SelectNextBlockAction.Descriptor());\n        registerAction(new SelectNextPageAction.Descriptor());\n        registerAction(new SelectNextRowAction.Descriptor());\n    \tregisterAction(new SelectFirstRowAction.Descriptor());\n    \tregisterAction(new SelectLastRowAction.Descriptor());\n\t\tregisterAction(new LeftArrowAction.Descriptor());\n\t\tregisterAction(new RightArrowAction.Descriptor());\n    \tregisterAction(new SetSameFolderAction.Descriptor());\n    \tregisterAction(new SetTabTitleAction.Descriptor());\n    \tregisterAction(new ShowAboutAction.Descriptor());\n    \tregisterAction(new ShowBookmarksQLAction.Descriptor());\n    \tregisterAction(new CustomizeCommandBarAction.Descriptor());\n        registerAction(new ShowDebugConsoleAction.Descriptor());\n        registerAction(new ShowFilePropertiesAction.Descriptor());\n        registerAction(new ShowFilePopupMenuAction.Descriptor());\n    \tregisterAction(new ShowKeyboardShortcutsAction.Descriptor());\n    \tregisterAction(new ShowParentFoldersQLAction.Descriptor());\n    \tregisterAction(new ShowPreferencesAction.Descriptor());\n    \tregisterAction(new ShowRecentExecutedFilesQLAction.Descriptor());\n    \tregisterAction(new ShowRecentLocationsQLAction.Descriptor());\n    \tregisterAction(new ShowRootFoldersQLAction.Descriptor());\n        registerAction(new ShowRecentViewedFilesQLAction.Descriptor());\n        registerAction(new ShowRecentEditedFilesQLAction.Descriptor());\n\t\tregisterAction(new ShowEditorBookmarksQLAction.Descriptor());\n    \tregisterAction(new ShowServerConnectionsAction.Descriptor());\n    \tregisterAction(new ShowTabsQLAction.Descriptor());\n    \tregisterAction(new SortByDateAction.Descriptor());\n    \tregisterAction(new SortByExtensionAction.Descriptor());\n    \tregisterAction(new SortByGroupAction.Descriptor());\n    \tregisterAction(new SortByNameAction.Descriptor());\n    \tregisterAction(new SortByOwnerAction.Descriptor());\n    \tregisterAction(new SortByPermissionsAction.Descriptor());\n    \tregisterAction(new SortBySizeAction.Descriptor());\n    \tregisterAction(new SplitEquallyAction.Descriptor());\n    \tregisterAction(new SplitFileAction.Descriptor());\n    \tregisterAction(new SplitHorizontallyAction.Descriptor());\n    \tregisterAction(new SplitVerticallyAction.Descriptor());\n    \tregisterAction(new StopAction.Descriptor());\n\t\tregisterAction(new ToggleSinglePanelAction.Descriptor());\n    \tregisterAction(new SwapFoldersAction.Descriptor());\n    \tregisterAction(new SwitchActiveTableAction.Descriptor());\n    \tregisterAction(new ToggleAutoSizeAction.Descriptor());\n//    \tregisterAction(new ToggleColumnAction.Descriptor());\n    \tregisterAction(new ToggleCommandBarAction.Descriptor());\n    \tregisterAction(new ToggleDateColumnAction.Descriptor());\n    \tregisterAction(new ToggleExtensionColumnAction.Descriptor());\n    \tregisterAction(new ToggleGroupColumnAction.Descriptor());\n    \tregisterAction(new ToggleHiddenFilesAction.Descriptor());\n    \tregisterAction(new ToggleLockTabAction.Descriptor());\n    \tregisterAction(new ToggleOwnerColumnAction.Descriptor());\n    \tregisterAction(new TogglePermissionsColumnAction.Descriptor());\n    \tregisterAction(new ToggleShowFoldersFirstAction.Descriptor());\n    \tregisterAction(new ToggleFoldersAlwaysAlphabeticalAction.Descriptor());\n    \tregisterAction(new ToggleSizeColumnAction.Descriptor());\n    \tregisterAction(new ToggleStatusBarAction.Descriptor());\n    \tregisterAction(new ToggleToolBarAction.Descriptor());\n    \tregisterAction(new ToggleTreeAction.Descriptor());\n    \tregisterAction(new UnmarkAllAction.Descriptor());\n    \tregisterAction(new UnmarkGroupAction.Descriptor());\n\t\tregisterAction(new MarkEmptyFilesAction.Descriptor());\n    \tregisterAction(new UnpackAction.Descriptor());\n    \tregisterAction(new ViewAction.Descriptor());\n        registerAction(new ViewAsAction.Descriptor());\n\t\tregisterAction(new EditAsAction.Descriptor());\n        registerAction(new TerminalAction.Descriptor());\n\t\tif (OsFamily.MAC_OS_X.isCurrent()) {\n\t\t\tregisterAction(new TerminalAltAction.Descriptor());\n\t\t}\n        registerAction(new FindFileAction.Descriptor());\n        registerAction(new CalculatorAction.Descriptor());\n        registerAction(new CreateSymlinkAction.Descriptor());\n        registerAction(new LocateSymlinkAction.Descriptor());\n        registerAction(new EditCommandsAction.Descriptor());\n        registerAction(new TerminalPanelAction.Descriptor());\n        registerAction(new ShowFoldersSizeAction.Descriptor());\n\t\tregisterAction(new ToggleTableViewModeFullAction.Descriptor());\n\t\tregisterAction(new ToggleTableViewModeCompactAction.Descriptor());\n\t\tregisterAction(new ToggleTableViewModeShortAction.Descriptor());\n\t\tregisterAction(new EjectDriveAction.Descriptor());\n\t\tregisterAction(new CompareFilesAction.Descriptor());\n\t\tregisterAction(new TogglePanelPreviewModeAction.Descriptor());\n\t\tregisterAction(new TextEditorsListAction.Descriptor());\n\t\tregisterAction(new UserMenuAction.Descriptor());\n    }\n\n\tpublic static void registerCommandsActions() {\n\t\t// register \"open with\" commands as actions, to allow for keyboard shortcuts for them\n\t\tfor (Command command : CommandManager.commands()) {\n\t\t\tif (command.getType() == CommandType.NORMAL_COMMAND) {\n\t\t\t\tActionManager.registerAction(new CommandAction.Descriptor(command));\n\t\t\t}\n\t\t}\n\t}\n\n    /**\n     * Registration method for MuActions.\n     * \n     * @param actionDescriptor - ActionDescriptor instance of the action.\n     */\n    private static void registerAction(ActionDescriptor actionDescriptor) {\n    \tActionProperties.addActionDescriptor(actionDescriptor);\n    }\n    \n    /**\n     * Return all ids of the registered actions.\n     * \n     * @return Enumeration of all registered actions' ids.\n     */\n    public static Iterator<String> getActionIds() {\n\t\treturn ActionProperties.actionDescriptors.keySet().iterator();\n    }\n    \n    /**\n     * Return the id of MuAction in a given path.\n     * \n     * @param actionClassPath - path to MuAction class.\n     * @return String representing the id of the MuAction in the specified path. null is returned if the given path is invalid.\n     */\n    public static String extrapolateId(String actionClassPath) {\n    \tif (actionClassPath == null) {\n\t\t\treturn null;\n\t\t}\n    \t\n    \tMatcher matcher = PATTERN.matcher(actionClassPath);\n    \treturn matcher.matches() ? matcher.group(1) : actionClassPath;\n    }\n    \n    /**\n     * Checks whether an MuAction is registered.\n     * \n     * @param actionId - id of MuAction.\n     * @return true if an MuAction which is represented by the given id is registered, otherwise return false.\n     */\n    public static boolean isActionExist(String actionId) {    \t\n    \t//return actionId != null && actionFactories.containsKey(actionId);\n\t\treturn actionId != null && ActionProperties.actionDescriptors.containsKey(actionId);\n    }\n\n    /**\n     * Convenience method that returns an instance of the action corresponding to the given <code>Command</code>,\n     * and associated with the specified <code>MainFrame</code>. This method gets the ID of the relevant action,\n     * passes it to {@link #getActionInstance(String, MainFrame)} and returns the {@link TcAction} instance.\n     *\n     * @param command the command that is invoked by the returned action\n     * @param mainFrame the MainFrame instance the action belongs to\n     * @return a MuAction instance matching the given action ID and MainFrame, <code>null</code> if the\n     * @see #getActionInstance(String, MainFrame)\n     * action could not be found or could not be instantiated.\n     */\n    public static TcAction getActionInstance(Command command, MainFrame mainFrame) {\n        return getActionInstance(new CommandAction.Descriptor(command).getId(), mainFrame);\n    }\n\n    /**\n     * Convenience method that returns an instance of the action denoted by the given ID, and associated with the\n     * specified <code>MainFrame</code>. This method creates an ActionParameters with no initial property, passes it to\n     * {@link #getActionInstance(ActionParameters, MainFrame)} and returns the {@link TcAction} instance.\n     *\n     * @param actionId ID of the action to instantiate\n     * @param mainFrame the MainFrame instance the action belongs to\n     * @return a MuAction instance matching the given action ID and MainFrame, <code>null</code> if the\n     * @see #getActionInstance(ActionParameters, MainFrame)\n     * action could not be found or could not be instantiated.\n     */\n    public static TcAction getActionInstance(String actionId, MainFrame mainFrame) {\n        return getActionInstance(new ActionParameters(actionId), mainFrame);\n    }\n\n    public static Optional<TcAction> getActionInstance2(ActionParameters actionParameters, MainFrame mainFrame) {\n    \treturn Optional.ofNullable(getActionInstance(actionParameters,mainFrame));\n    }\n    /**\n     * Returns an instance of the MuAction class denoted by the given ActionParameters and for the\n     * specified MainFrame. If an existing instance corresponding to the same ActionParameters and MainFrame is found,\n     * it is simply returned.\n     * If no matching instance could be found, a new instance is created, added to the internal action instances map\n     * (for further use) and returned.\n     * If the action denoted by the specified ActionParameters cannot be found or cannot be instantiated,\n     * <code>null</code> is returned.\n     *\n     * @param actionParameters a descriptor of the action to instantiate with initial properties\n     * @param mainFrame the MainFrame instance the action belongs to\n     * @return a MuAction instance matching the given ActionParameters and MainFrame, <code>null</code> if the\n     * MuAction action denoted by the ActionParameters could not be found or could not be instantiated.\n     */\n    public static TcAction getActionInstance(ActionParameters actionParameters, MainFrame mainFrame) {\n\t\tMap<ActionParameters, ActionAndIdPair> mainFrameActions = mainFrameActionsMap.computeIfAbsent(mainFrame, k -> new Hashtable<>());\n\n\t\t// Looks for an existing MuAction instance used by the specified MainFrame\n        if (mainFrameActions.containsKey(actionParameters)) {\n        \treturn mainFrameActions.get(actionParameters).getAction();\n        } else {\n            String actionId = actionParameters.getActionId();\n\n            // Looks for the action's factory\n            //ActionFactory actionFactory = actionFactories.get(actionId);\n\t\t\tActionFactory actionFactory = ActionProperties.actionDescriptors.get(actionId);\n            if (actionFactory == null) {\n//            \tLOGGER.debug(\"couldn't initiate action: \" + actionId + \", its factory wasn't found\");\n//            \treturn null;\n            \tthrow new IllegalStateException(\"couldn't initiate action: \" + actionId + \", its factory wasn't found\");\n            }\n\n            Map<String,Object> properties = actionParameters.getInitProperties();\n            // If no properties hashtable is specified in the action descriptor\n            if (properties == null) {\n            \tproperties = Collections.emptyMap();\n            }\n            // else clone the hashtable to ensure that it doesn't get modified by action instances.\n            // Since cloning is an expensive operation, this is done only if the hashtable is not empty.\n            else if(!properties.isEmpty()) {\n                properties = new Hashtable<>(properties);\n            }\n\n            // Instantiate the MuAction class\n            TcAction action = actionFactory.createAction(mainFrame, properties);\n            mainFrameActions.put(actionParameters, new ActionAndIdPair(action, actionId));\n\n            // If the action's label has not been set yet, use the action descriptor's\n            if (action.getLabel() == null) {\n                // Retrieve the standard label entry from the dictionary and use it as this action's label\n                String label = ActionProperties.getActionLabel(actionId);\n                \n                // Append '...' to the label if this action invokes a dialog when performed\n                if (action.getClass().isAnnotationPresent(InvokesDialog.class)) {\n                    label += \"...\";\n                }\n\n                action.setLabel(label);\n\n                // Looks for a standard label entry in the dictionary and if it is defined, use it as this action's tooltip\n                String tooltip = ActionProperties.getActionTooltip(actionId);\n                if (tooltip != null)\n                    action.setToolTipText(tooltip);\n            }\n            \n            // If the action's accelerators have not been set yet, use the ones from ActionKeymap\n            if (action.getAccelerator() == null) {\n                // Retrieve the standard accelerator (if any) and use it as this action's accelerator\n                KeyStroke accelerator = ActionKeymap.getAccelerator(actionId);\n                if (accelerator != null) {\n                    action.setAccelerator(accelerator);\n                }\n\n                // Retrieve the standard alternate accelerator (if any) and use it as this action's alternate accelerator\n                accelerator = ActionKeymap.getAlternateAccelerator(actionId);\n                if (accelerator != null) {\n                    action.setAlternateAccelerator(accelerator);\n                }\n            }\n            \n            // If the action's icon has not been set yet, use the action descriptor's\n            if (action.getIcon() == null) {\n                // Retrieve the standard icon image (if any) and use it as the action's icon\n                ImageIcon icon = ActionProperties.getActionIcon(actionId);\n                if (icon != null) {\n                    action.setIcon(icon);\n                }\n            }\n            \n            return action;\n        }\n    }\n\n\n    /**\n     * Returns a ArrayList of all MuAction instances matching the specified action id.\n     *\n     * @param muActionId the MuAction id to compare instances against\n     * @return  a ArrayList of all MuAction instances matching the specified action id\n     */\n    static List<TcAction> getActionInstances(String muActionId) {\n        List<TcAction> actionInstances = new ArrayList<>();\n\n        // Iterate on all MainFrame instances\n        for (Map<ActionParameters, ActionAndIdPair> actionParametersActionAndIdPairHashtable : mainFrameActionsMap.values()) {\n            // Iterate on all the MainFrame's actions and their ids pairs\n            for (ActionAndIdPair actionAndIdPair : actionParametersActionAndIdPairHashtable.values()) {\n                if (actionAndIdPair.getId().equals(muActionId)) {\n                    // Found an action matching the specified class\n                    actionInstances.add(actionAndIdPair.getAction());\n                    // Jump to the next MainFrame\n                    break;\n                }\n            }\n        }\n\n        return actionInstances;\n    }\n\n    /**\n     * Convenience method that retrieves an instance of the action denoted by the given ID and associated\n     * with the given {@link MainFrame} and calls {@link TcAction#performAction()} on it.\n     * Returns <code>true</code> if an instance of the action could be retrieved and performed, <code>false</code>\n     * if the MuAction could not be found or could not be instantiated.\n     *\n     * @param actionId ID of the action to perform\n     * @param mainFrame the MainFrame the action belongs to\n     * @return true if the action instance could be retrieved and the action performed, false otherwise \n     */\n    public static boolean performAction(String actionId, MainFrame mainFrame) {\n        return performAction(new ActionParameters(actionId), mainFrame);\n    }\n\n    /**\n     * Convenience method that retrieves an instance of the MuAction denoted by the given {@link ActionParameters}\n     * and associated with the given {@link com.mucommander.ui.main.MainFrame} and calls {@link TcAction#performAction()} on it.\n     * Returns <code>true</code> if an instance of the action could be retrieved and performed, <code>false</code>\n     * if the MuAction could not be found or could not be instantiated.\n     *\n     * @param actionParameters the ActionParameters of the action to perform\n     * @param mainFrame the MainFrame the action belongs to\n     * @return true if the action instance could be retrieved and the action performed, false otherwise\n     */\n    public static boolean performAction(ActionParameters actionParameters, MainFrame mainFrame) {\n        TcAction action = getActionInstance(actionParameters, mainFrame);\n\n        if (action == null) {\n            return false;\n        }\n\n        action.performAction();\n\n        return true;\n    }\n    \n    /**\n     *  Helper class to represent a pair of instance and id of MuAction.\n     */\n    private static class ActionAndIdPair {\n    \tprivate final TcAction action;\n    \tprivate final String id;\n    \t\n    \tActionAndIdPair(TcAction action, String id) {\n    \t\tthis.action = action;\n    \t\tthis.id = id;\n    \t}\n    \t\n    \tpublic TcAction getAction() { return action; }\n    \t\n    \tpublic String getId() { return id; }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/ActionParameters.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action;\r\n\r\nimport java.util.Map;\r\n\r\n/**\r\n * A descriptor class for {@link TcAction} instances. An ActionParameters is the combination of a MuAction\r\n * class (a class extending MuAction and following its conventions) and a set of properties used for instantiation.\r\n * Thus, it not only identifies an action class but also the way it is instantiated.\r\n *\r\n * <p>Two ActionParameters instances are equal only if:\r\n * <ul>\r\n *  <li>they refer to the same action ID\r\n *  <li>both sets of initialization properties are equal, i.e. they contain the same key/value pairs (deep equality)\r\n * </ul>\r\n * This means that two ActionParameters instances referring to the same MuAction ID but with a different set of\r\n * initialization properties will not be equal.\r\n *\r\n * <p>This class is used by ActionManager to instance MuAction and allow several instances to live in memory only if\r\n * they have different initialization properties, which translated into action speak means a different appearance\r\n * and/or behavior.\r\n *\r\n * @see ActionManager\r\n * @see TcAction\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class ActionParameters {\r\n\r\n    /** Action ID */\r\n    private final String actionId;\r\n\r\n    /** Initialization properties, null if there are no initialization properties */\r\n    private final Map<String,Object> properties;\r\n\r\n\r\n    /**\r\n     * Convenience constructor which has the same effect as calling {@link #ActionParameters(String, Map)}\r\n     * with a null value for the <code>properties</code> parameter.\r\n     *\r\n     * @param actionId a MuAction id\r\n     */\r\n    public ActionParameters(String actionId) {\r\n        this(actionId, null);\r\n    }\r\n\r\n    /**\r\n     * Creates a new ActionParameters which identifies the specified combination of MuAction action ID and\r\n     * initialization properties. The <code>properties</code> parameter may be <code>null</code> if the action class is\r\n     * to be instantiated without any initialization properties.\r\n     *\r\n     * @param actionId a MuAction id\r\n     * @param initProperties a Hashtable containing the properties that will be used to instantiate the specified MuAction class\r\n     */\r\n    public ActionParameters(String actionId, Map<String,Object> initProperties) {\r\n        this.actionId = actionId;\r\n        this.properties = initProperties;\r\n    }\r\n\r\n    /**\r\n     * Returns the action ID that was used to create this object.\r\n     *\r\n     * @return the action ID that was used to create this object.\r\n     */\r\n    public String getActionId() {\r\n        return actionId;\r\n    }\r\n\r\n    /**\r\n     * Returns the list of properties that are to be used to instantiate the MuAction class, or <code>null</code> if\r\n     * there are none.\r\n     *\r\n     * @return the list of properties that are to be used to instantiate the MuAction class, or <code>null</code> if\r\n     * there are none\r\n     */\r\n    public Map<String,Object> getInitProperties() {\r\n        return properties;\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns <code>true</code> if the given object is an ActionParameters that is equal to this one.\r\n     * ActionParameters instances are considered equal if they refer to the same {@link TcAction} class and\r\n     * set of initialization properties.\r\n     */\r\n    @Override\r\n    public boolean equals(Object o) {\r\n        if(!(o instanceof ActionParameters))\r\n            return false;\r\n\r\n        ActionParameters ad = (ActionParameters)o;\r\n\r\n        return actionId.equals(ad.actionId)\r\n            && (((properties==null || properties.isEmpty()) && (ad.properties==null || ad.properties.isEmpty()))\r\n                || (properties!=null && ad.properties!=null && properties.equals(ad.properties)));\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns a hash code value for this ActionParameters, making this class suitable for use as a key in a Hashtable.\r\n     */\r\n    @Override\r\n    public int hashCode() {\r\n        return actionId.hashCode() + 27*(properties==null?0:properties.hashCode());\r\n    }\r\n\r\n    /**\r\n     * Returns a String representation of this ActionParameters. \r\n     */\r\n    @Override\r\n    public String toString() {\r\n        return super.toString()+\" class=\"+actionId+\" properties=\"+properties;\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/ActionProperties.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action;\r\n\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.*;\r\nimport java.util.*;\r\n\r\n/**\r\n * Class that maintains properties of the registered MuAction-s:\r\n * 1. General properties of all registered actions, such as all existing action categories.\r\n * 2. ActionDescriptors and helper methods for fetching specific property from ActionDescriptor.\r\n * 3. Default actions &lt;-&gt; shortcuts mapping.\r\n * \r\n * @author Arik Hadas\r\n */\r\npublic class ActionProperties {\r\n\t\r\n\t/* Maps action id -> action descriptor */\r\n\tstatic final Map<String, ActionDescriptor> actionDescriptors = new HashMap<>(200);\r\n\r\n\tprivate static final ActionDescriptor nullActionDescriptor = new NullActionDescriptor();\r\n\t\r\n\t/* Contains all used action categories (i.e. for each category at least one action is registered) */\r\n\tprivate static final TreeSet<ActionCategory> actionCategories = new TreeSet<>();\r\n\r\n\t/* Maps action id -> primary shortcut */\r\n\tprivate static final Map<String, KeyStroke> defaultPrimaryActionKeymap = new HashMap<>();\r\n\t/* Maps action id -> alternative shortcut */\r\n\tprivate static final Map<String, KeyStroke> defaultAlternateActionKeymap = new HashMap<>();\r\n\t/* Maps shortcut -> action id */\r\n\tprivate static final AcceleratorMap defaultAcceleratorMap = new AcceleratorMap();\r\n\r\n\t/**\r\n\t * Getter for ActionDescriptor.\r\n\t * \r\n\t * @param actionDescriptor - an ActionDescriptor instance to be registered.\r\n\t */\r\n\tpublic static void addActionDescriptor(ActionDescriptor actionDescriptor) {\r\n\t\tString actionId = actionDescriptor.getId();\r\n\r\n\t\t// Add the descriptor to the descriptors map.\r\n\t\tactionDescriptors.put(actionId, actionDescriptor);\r\n\t\t\r\n\t\t// Add the category in the descriptor to the categories pool\r\n\t\tActionCategory category = actionDescriptor.getCategory();\r\n\t\tif (category != null) {\r\n            actionCategories.add(category);\r\n        }\r\n\r\n\t\t// Add the shortcuts in the descriptor to the default keymap\r\n\t\tKeyStroke defaultActionKeyStroke = actionDescriptor.getDefaultKeyStroke();\r\n\t\tif (defaultActionKeyStroke != null) {\r\n\t\t\tdefaultPrimaryActionKeymap.put(actionId, defaultActionKeyStroke);\r\n\t\t\tdefaultAcceleratorMap.putAccelerator(defaultActionKeyStroke, actionId);\r\n\t\t}\r\n\r\n\t\tKeyStroke defaultActionAlternativeKeyStroke = actionDescriptor.getDefaultAltKeyStroke();\r\n\t\tif (defaultActionAlternativeKeyStroke != null) {\r\n\t\t\tdefaultAlternateActionKeymap.put(actionId, defaultActionAlternativeKeyStroke);\r\n\t\t\tdefaultAcceleratorMap.putAlternativeAccelerator(defaultActionAlternativeKeyStroke, actionId);\r\n\t\t}\r\n\t}\r\n\t\r\n\t/**\r\n\t * Getter for MuAction's descriptor.\r\n\t * \r\n\t * @param actionId - id of MuAction.\r\n\t * @return ActionDescriptor of the given MuAction. null is returned if ActionDescriptor doesn't exist.\r\n\t */\r\n\tpublic static ActionDescriptor getActionDescriptor(String actionId) {\r\n\t\treturn actionDescriptors.get(actionId);\r\n\t}\r\n\t\r\n\t/**\r\n\t * Getter for MuAction's description.\r\n\t * MuAction Description is:\r\n\t * 1. action's tooltip.\r\n\t * 2. if tooltip doesn't exist then action's label.\r\n\t * 3. if tooltip and label don't exist, then action's label key.\r\n\t * \r\n\t * @param actionId - id of MuAction.\r\n\t * @return Description of MuAction as described above.\r\n\t */\r\n\tpublic static String getActionDescription(String actionId) {\r\n\t\treturn getNullSafeActionDescriptor(actionId).getDescription();\r\n\t}\r\n\t\r\n\t/**\r\n\t * Getter for MuAction's category.\r\n\t * \r\n\t * @param actionId - id of MuAction.\r\n\t * @return ActionCategory of the given MuAction. null is returned if ActionCategory doesn't exist.\r\n\t */\r\n\tpublic static ActionCategory getActionCategory(String actionId) {\r\n\t\treturn getNullSafeActionDescriptor(actionId).getCategory();\r\n\t}\r\n\t\r\n\t/**\r\n\t * Getter for MuAction's default primary shortcut.\r\n\t * \r\n\t * @param actionId - id of MuAction.\r\n\t * @return default shortcut of the given MuAction. null is returned if default shortcut doesn't exist.\r\n\t */\r\n\tpublic static KeyStroke getDefaultAccelerator(String actionId) {\r\n\t\treturn defaultPrimaryActionKeymap.get(actionId);\r\n\t}\r\n\t\r\n\t/**\r\n\t * Getter for MuAction's alternative shortcut.\r\n\t * \r\n\t * @param actionId - id of MuAction.\r\n\t * @return alternative shortcut for the given MuAction. null is returned if alternative shortcut doesn't exist.\r\n\t */\r\n\tpublic static KeyStroke getDefaultAlternativeAccelerator(String actionId) {\r\n\t\treturn defaultAlternateActionKeymap.get(actionId);\r\n\t}\r\n\t\r\n\t/**\r\n\t * Getter for shortcut's default MuAction.\r\n\t * \r\n\t * @param keyStroke - shortcut.\r\n\t * @return default MuAction which the given shortcut is assigned for. null is returned if the shortcut doesn't \r\n\t * assign to any MuAction by default.\r\n\t */\r\n\tstatic String getDefaultActionForKeyStroke(KeyStroke keyStroke) {\r\n\t\treturn defaultAcceleratorMap.getActionId(keyStroke);\r\n\t}\r\n\t\r\n\t/**\r\n\t * Getter for shortcut's default type.\r\n\t * The shortcut's type can be either PRIMARY_ACCELERATOR or ALTERNATIVE_ACCELERATOR or 0 if the shortcut doesn't exist by default.\r\n\t * \r\n\t * @param keyStroke - shortcut.\r\n\t * @return default shortcut's type (PRIMARY_ACCELERATOR/ALTERNATIVE_ACCELERATOR).\r\n\t */\r\n\tstatic int getDefaultAcceleratorType(KeyStroke keyStroke) {\r\n\t\treturn defaultAcceleratorMap.getAcceleratorType(keyStroke);\r\n\t}\r\n\t\r\n\t/**\r\n\t * Getter for MuAction's label.\r\n\t * \r\n\t * @param actionId - id of MuAction.\r\n\t * @return Label of MuAction. if the label doesn't exist in the dictionary, its key is returned.\r\n\t * null is returned if label's key doesn't exist.\r\n\t */\r\n\tpublic static String getActionLabel(String actionId) {\r\n\t\treturn getNullSafeActionDescriptor(actionId).getLabel();\r\n\t}\r\n\t\r\n\t/**\r\n\t * Getter for MuAction's label key.\r\n\t * \r\n\t * @param actionId - id of MuAction.\r\n\t */\r\n\tpublic static String getActionLabelKey(String actionId) {\r\n\t\treturn getNullSafeActionDescriptor(actionId).getLabelKey();\r\n\t}\r\n\t\r\n\t/**\r\n\t * Getter for MuAction's icon.\r\n\t *  \r\n\t * @param actionId - id of MuAction.\r\n\t * @return Icon of MuAction. null is returned if there is no icon for the action.\r\n\t */\r\n\tpublic static ImageIcon getActionIcon(String actionId) {\r\n\t\treturn getNullSafeActionDescriptor(actionId).getIcon();\r\n\t}\r\n\t\r\n\t/**\r\n\t * Getter for MuAction's tooltip.\r\n\t *  \r\n\t * @param actionId - id of MuAction.\r\n\t * @return Tooltip of MuAction. null is returned if there is no tooltip for the action.\r\n\t */\r\n\tpublic static String getActionTooltip(String actionId) {\r\n\t\treturn getNullSafeActionDescriptor(actionId).getTooltip();\r\n\t}\r\n\t\r\n\t/**\r\n\t * Getter for all existed categories.\r\n\t * Existed category means an actions' category which at least one of its actions is registered.\r\n\t * \r\n\t * The categories are ordered based on the alphabet order of their descriptions (labels).\r\n\t * \r\n\t * @return Set of existed action categories.\r\n\t */\r\n\tpublic static Set<ActionCategory> getActionCategories() {\r\n\t\treturn actionCategories;\r\n\t}\r\n\t\r\n\tprivate static ActionDescriptor getNullSafeActionDescriptor(String actionId) {\r\n\t\tActionDescriptor actionDescriptor = actionDescriptors.get(actionId);\r\n\t\treturn actionDescriptor != null ? actionDescriptor : nullActionDescriptor;\r\n\t}\r\n\t\r\n\t/**\r\n\t * Helper class that represent ActionDescriptor with null values\r\n\t */\r\n\tprivate static class NullActionDescriptor implements ActionDescriptor {\r\n\r\n\t\tpublic ActionCategory getCategory() { return null; }\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() { return null; }\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() { return null; }\r\n\r\n\t\tpublic String getDescription() { return null; }\r\n\r\n\t\tpublic ImageIcon getIcon() { return null; }\r\n\r\n\t\tpublic String getId() { return null; }\r\n\r\n\t\tpublic String getLabel() { return null; }\r\n\r\n\t\tpublic String getLabelKey() { return null; }\r\n\r\n\t\tpublic String getTooltip() { return null; }\r\n\r\n\t\tpublic boolean isParameterized() { return false; }\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String, Object> properties) { return null; }\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/AwtActionProxy.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action;\n\nimport javax.swing.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\n\n/**\n * AWTActionProxy acts as a proxy between a given <code>java.awt.event.ActionListener</code> and\n * <code>java.swing.Action</code>, transferring received action events to the Action's <code>actionPerformed</code>\n * method.\n * This class provides an easy way to use <code>java.swing.Action</code> instances in AWT components.\n * <p>\n * Usage: after creating an <code>AWTActionProxy</code> instance, the <code>addActionListener</code> method must be\n * called on the AWT component which action events are to be proxied, using the <code>AWTActionProxy</code> instance as\n * a parameter.\n *\n *\n * @author Maxence Bernard\n */\npublic class AwtActionProxy implements ActionListener {\n\n    /** Proxied Action */\n    private final Action proxiedAction;\n\n    /**\n     * Creates a new AWTActionProxy instance that will transfer ActionEvents caught by {@link #actionPerformed(java.awt.event.ActionEvent)}\n     * to the specified <code>Action</code>.\n     *\n     * @param action the Action instance to transfer the ActionEvents to.\n     */\n    public AwtActionProxy(Action action) {\n        this.proxiedAction = action;\n    }\n\n    /**\n     * Returns the <code>Action</code> instance to which the ActionEvents received by {@link #actionPerformed(java.awt.event.ActionEvent)}\n     * are transferred. \n     */\n    public Action getProxiedAction() {\n        return proxiedAction;\n    }\n\n    /**\n     * Forwards the specified ActionEvent to the proxied Action.\n     */\n    public void actionPerformed(ActionEvent actionEvent) {\n        proxiedAction.actionPerformed(actionEvent);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/InvokesDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * This Annotation should mark {@link TcAction} classes that invoke a dialog when the action is performed,\n * in order to automatically append '...' to the action's label.\n *\n * @author Maxence Bernard\n */\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface InvokesDialog {\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/TcAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action;\r\n\r\nimport com.mucommander.commons.file.util.ResourceLoader;\r\nimport com.mucommander.ui.icon.IconManager;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.ActionEvent;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * MuAction extends <code>AbstractAction</code> to add more functionalities and make it easier to integrate within\r\n * muCommander. The biggest difference with <code>AbstractAction</code> is that MuAction instances are bound to a\r\n * specific {@link MainFrame}.<br>\r\n * Note that by being an Action, MuAction can be used in every Swing components that accept Action instances.\r\n *\r\n * <p>The MuAction class is abstract. MuAction subclasses must implement the {@link #performAction()} method\r\n * to provide a response to the action trigger, and must provide a constructor with the\r\n * {@link #TcAction(MainFrame, Map)} signature.\r\n *\r\n * <p>MuAction subclasses should not be instantiated directly, {@link ActionManager}'s <code>getActionInstance</code>\r\n * methods should be used instead. Using {@link ActionManager} to retrieve a MuAction ensures that only one instance\r\n * exists for a given {@link com.mucommander.ui.main.MainFrame}. This is particularly important because actions are stateful and can be used\r\n * in several components of a MainFrame at the same time; if an action's state changes, the change must be reflected\r\n * everywhere the action is used. It is also important for performance reasons: sharing one action throughout a\r\n * {@link MainFrame} saves some memory and also CPU cycles as some actions listen to particular events to change\r\n * their state accordingly.\r\n *\r\n * @see ActionManager\r\n * @see ActionKeymap\r\n * @author Maxence Bernard\r\n */\r\npublic abstract class TcAction extends AbstractAction {\r\n\r\n    TcAction() {\r\n        mainFrame = null;\r\n    }\r\n\r\n    /** The MainFrame associated with this MuAction */\r\n    protected final MainFrame mainFrame;\r\n\r\n    /** if true, action events are ignored while the MainFrame is in 'no events mode'. Enabled by default. */\r\n    private boolean honourNoEventsMode = true;\r\n\r\n    /** if true, #performAction() is called from a separate thread (and not from the event thread) when this action is\r\n     * performed. Disabled by default. */\r\n    private boolean performActionInSeparateThread = false;\r\n\r\n    /** Name of the alternate accelerator KeyStroke property */\r\n    private final static String ALTERNATE_ACCELERATOR_PROPERTY_KEY = \"alternate_accelerator\";\r\n    \r\n    /**\r\n     * Creates a new <code>MuAction</code> associated with the specified {@link MainFrame}. The properties contained by\r\n     * the given {@link Map} are used to initialize this action's property map.\r\n     *\r\n     * @param mainFrame the MainFrame to associate with this new MuAction\r\n     * @param properties the initial properties to use in this action. The Hashtable may simply be empty if no initial\r\n     * properties are specified.\r\n     */\r\n    public TcAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        this.mainFrame = mainFrame;\r\n        // Add properties to this Action.\r\n        for (String key : properties.keySet()) {\r\n            putValue(key, properties.get(key));\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Return the {@link MainFrame} this MuAction is associated.\r\n     *\r\n     * @return the MainFrame this action is associated with\r\n     */\r\n    public MainFrame getMainFrame() {\r\n        return this.mainFrame;\r\n    }\r\n\r\n    /**\r\n     * Returns the label of this action, <code>null</code> if this action has no label.\r\n     * The label value is stored in the {@link #NAME} property.\r\n     *\r\n     * @return the label of this action, <code>null</code> if this action has no label\r\n     */\r\n    public String getLabel() {\r\n        return (String)getValue(Action.NAME);\r\n    }\r\n\r\n    /**\r\n     * Sets the label for this action, <code>null</code> for no label.\r\n     * The label value is stored in the {@link #NAME} property.\r\n     *\r\n     * @param label the new text label for this action, replacing the previous one (if any)\r\n     */\r\n    public void setLabel(String label) {\r\n        putValue(Action.NAME, label);\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the tooltip text of this action, <code>null</code> if this action has no tooltip.\r\n     * The tooltip value is stored in the {@link #SHORT_DESCRIPTION} property.\r\n     *\r\n     * @return the tooltip text of this action, <code>null</code> if this action has no tooltip\r\n     */\r\n    public String getToolTipText() {\r\n        return (String)getValue(Action.SHORT_DESCRIPTION);\r\n    }\r\n\r\n    /**\r\n     * Sets the tooltip for this action, <code>null</code> for no tooltip.\r\n     * The tooltip value is stored in the {@link #SHORT_DESCRIPTION} property.\r\n     *\r\n     * @param toolTipText the new tooltip text for this action replacing the previous one (if any)\r\n     */\r\n    public void setToolTipText(String toolTipText) {\r\n        putValue(Action.SHORT_DESCRIPTION, toolTipText);\r\n    }\r\n\r\n\r\n    /**\r\n     * Return the icon of this action, <code>null</code> if this action has no icon.\r\n     * The icon value is stored in the {@link #SMALL_ICON} property.\r\n     *\r\n     * @return the icon of this action, <code>null</code> if this action has no icon\r\n     */\r\n    public ImageIcon getIcon() {\r\n        return (ImageIcon)getValue(Action.SMALL_ICON);\r\n    }\r\n\r\n    /**\r\n     * Sets the icon for this action, <code>null</code> if this action has no icon.\r\n     * The icon value is stored in the {@link #SMALL_ICON} property.\r\n     *\r\n     * @param icon the new image icon for this action, replacing the previous one (if any)\r\n     */\r\n    public void setIcon(ImageIcon icon) {\r\n        putValue(Action.SMALL_ICON, icon);\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the accelerator KeyStroke of this action, <code>null</code> if this action has no accelerator.\r\n     * The accelerator value is stored in the <code>Action.ACCELERATOR_KEY</code> property.\r\n     *\r\n     * @return the accelerator KeyStroke of this action, <code>null</code> if this action has no accelerator\r\n     */\r\n    public KeyStroke getAccelerator() {\r\n        return (KeyStroke)getValue(Action.ACCELERATOR_KEY);\r\n    }\r\n\r\n    /**\r\n     * Sets the accelerator KeyStroke for this action, <code>null</code> for no accelerator.\r\n     * The tooltip value is stored in the <code>Action.ACCELERATOR_KEY</code> property.\r\n     *\r\n     * @param keyStroke the new accelerator KeyStroke for this action, replacing the previous one (if any)\r\n     */\r\n    public void setAccelerator(KeyStroke keyStroke) {\r\n        putValue(Action.ACCELERATOR_KEY, keyStroke);\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the alternate accelerator KeyStroke of this action, <code>null</code> if it doesn't have any.\r\n     * The accelerator accelerator value is stored in the {@link #ALTERNATE_ACCELERATOR_PROPERTY_KEY} property.\r\n     *\r\n     * @return the alternate accelerator KeyStroke of this action, <code>null</code> if it doesn't have any\r\n     */\r\n    KeyStroke getAlternateAccelerator() {\r\n        return (KeyStroke)getValue(ALTERNATE_ACCELERATOR_PROPERTY_KEY);\r\n    }\r\n\r\n    /**\r\n     * Sets the alternate accelerator KeyStroke for this action, <code>null</code> for none.\r\n     * The accelerator accelerator value is stored in the {@link #ALTERNATE_ACCELERATOR_PROPERTY_KEY} property.\r\n     *\r\n     * @param keyStroke the new alternate accelerator KeyStroke for this action, replacing the previous one (if any)\r\n     */\r\n    void setAlternateAccelerator(KeyStroke keyStroke) {\r\n        putValue(ALTERNATE_ACCELERATOR_PROPERTY_KEY, keyStroke);\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if both keystrokes' {@link KeyStroke#getKeyChar() char},\r\n     * {@link KeyStroke#getKeyCode() code} and {@link KeyStroke#getModifiers() modifiers} are equal.\r\n     * Unlike {@link KeyStroke#equals(Object)}, this method does not take into account the\r\n     * {@link KeyStroke#isOnKeyRelease() onKeyRelease} flag.\r\n     *\r\n     * @param ks1 first keystroke to test\r\n     * @param ks2 second keystroke to test\r\n     * @return <code>true</code> if both keystrokes' char, code and modifiers are equal\r\n     */\r\n    private boolean acceleratorsEqual(KeyStroke ks1, KeyStroke ks2) {\r\n        return ks1.getKeyChar() == ks2.getKeyChar()\r\n            && ks1.getKeyCode() == ks2.getKeyCode()\r\n            && ks1.getModifiers() == ks2.getModifiers();\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the given KeyStroke is one of this action's accelerators. Keystrokes are compared\r\n     * using {@link #acceleratorsEqual(KeyStroke, KeyStroke)}, so that the {@link KeyStroke#isOnKeyRelease()} flag\r\n     * is not taken into account. This method always returns <code>false</code> if this method has no accelerator.\r\n     *\r\n     * @param keyStroke the KeyStroke to test against this action's accelerators\r\n     * @return true if the given KeyStroke is one of this action's accelerators\r\n     */\r\n    public boolean isAccelerator(KeyStroke keyStroke) {\r\n        KeyStroke accelerator = getAccelerator();\r\n        if (accelerator != null && acceleratorsEqual(accelerator, keyStroke))\r\n            return true;\r\n\r\n        accelerator = getAlternateAccelerator();\r\n        return accelerator != null && acceleratorsEqual(accelerator, keyStroke);\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns a displayable String representation of this action's accelerator, in the\r\n     * <code>[modifier]+[modifier]+...+key</code> format.\r\n     * This method returns <code>null</code> if this action has no accelerator.\r\n     *\r\n     * @return a String representation of the accelerator, or <code>null</code> if this action has no accelerator.\r\n     */\r\n    public String getAcceleratorText() {\r\n        KeyStroke accelerator = getAccelerator();\r\n        if (accelerator == null) {\r\n            return null;\r\n        }\r\n\r\n        String text = KeyEvent.getKeyText(accelerator.getKeyCode());\r\n        int modifiers = accelerator.getModifiers();\r\n        if (modifiers != 0) {\r\n            text = KeyEvent.getModifiersExText(modifiers) + \"+\" + text;\r\n        }\r\n        return text;\r\n    }\r\n\r\n\r\n    /**\r\n     * Return <code>true</code> if action events are ignored while the <code>MainFrame</code> associated with this\r\n     * action is in 'no events mode' (see {@link MainFrame} for an explanation about this mode).\r\n     * By default, this method returns <code>true</code>.\r\n     *\r\n     * @return <code>true</code> if action events are ignored while the <code>MainFrame</code> associated with this\r\n     * action is in 'no events' mode\r\n     */\r\n    private boolean honourNoEventsMode() {\r\n        return honourNoEventsMode;\r\n    }\r\n\r\n    /**\r\n     * Sets whether action events are to be ignored while the <code>MainFrame</code> associated with this action is in\r\n     * 'no events mode' (see {@link MainFrame} for an explanation about this mode).\r\n     * By default (unless this method has been called), 'no events mode' is honoured.\r\n     *\r\n     * @param honourNoEventsMode if true, actions events will be ignored while the <code>MainFrame</code> associated\r\n     * with this action is in 'no events mode'\r\n     */\r\n    protected void setHonourNoEventsMode(boolean honourNoEventsMode) {\r\n        this.honourNoEventsMode = honourNoEventsMode;\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns <code>true</code> if {@link #performAction()} is called from a separate thread (and not from the event\r\n     * thread) when this action is performed. By default, <code>false</code> is returned, i.e. actions are performed\r\n     * from the main event thread.\r\n     *\r\n     * <p>Actions that have the potential to hold the caller thread for a substantial amount of time should perform the\r\n     * action in a separate thread, to avoid locking the event thread.\r\n     *\r\n     * @return <code>true</code> if {@link #performAction()} is called from a separate thread (and not from the event\r\n     * thread) when this action is performed\r\n     */\r\n    private boolean performActionInSeparateThread() {\r\n        return performActionInSeparateThread;\r\n    }\r\n\r\n    /**\r\n     * Sets whether {@link #performAction()} is called from a separate thread (and not from the event thread) when this\r\n     * action is performed. By default (unless this method has been called), actions are performed from the main event\r\n     * thread.\r\n     *\r\n     * <p>Actions that have the potential to hold the caller thread for a substantial amount of time should perform the\r\n     * action in a separate thread, to avoid locking the event thread.\r\n     *\r\n     * @param performActionInSeparateThread <code>true</code> to have {@link #performAction()} called from a separate\r\n     * thread (and not from the event thread) when this action is performed\r\n     */\r\n    protected void setPerformActionInSeparateThread(boolean performActionInSeparateThread) {\r\n        this.performActionInSeparateThread = performActionInSeparateThread;\r\n    }\r\n\r\n    /**\r\n     * Shorthand for {@link #getStandardIcon(Class)} called with the Class instance returned by {@link #getClass()}.\r\n     *\r\n     * @return the standard icon corresponding to this MuAction class, <code>null</code> if none was found\r\n     */\r\n    public ImageIcon getStandardIcon() {\r\n        return getStandardIcon(getClass());\r\n    }\r\n\r\n    /**\r\n     * Shorthand for {@link #getStandardIconPath(Class)} called with the Class instance returned by {@link #getClass()}.\r\n     *\r\n     * @return the standard path for this action's image icon\r\n     */\r\n    public String getStandardIconPath() {\r\n        return getStandardIconPath(getClass());\r\n    }\r\n\r\n\r\n\r\n    /**\r\n     * Queries {@link IconManager} for an image icon corresponding to the specified action using standard icon path\r\n     * conventions. Returns the image icon, <code>null</code> if none was found.\r\n     *\r\n     * @param action a MuAction class descriptor\r\n     * @return the standard icon image corresponding to the specified MuAction class, <code>null</code> if none was found\r\n     */\r\n    public static ImageIcon getStandardIcon(Class<? extends TcAction> action) {\r\n        // Look for an icon image file with the /action/<classname>.png path and use it if it exists\r\n    \tString iconPath = getStandardIconPath(action);\r\n        if (ResourceLoader.getResourceAsURL(iconPath) == null) {\r\n            return null;\r\n        }\r\n        return IconManager.getIcon(iconPath);\r\n    }\r\n\r\n    /**\r\n     * Returns the standard path to the icon image for the specified {@link TcAction} class. The returned path is\r\n     * relative to the application's JAR file.\r\n     *\r\n     * @param action a MuAction class descriptor\r\n     * @return the standard path to the icon image corresponding to the specified MuAction class\r\n     */\r\n    private static String getStandardIconPath(Class<? extends TcAction> action) {\r\n        return IconManager.IconSet.ACTION.getFolder() + getActionName(action) + \".png\";\r\n    }\r\n\r\n    private static String getActionName(Class<? extends TcAction> action) {\r\n    \treturn action.getSimpleName().replace(\"Action\", \"\");\r\n    }\r\n\r\n    public String getId() {\r\n        return getClass().getSimpleName().replace(\"Action\", \"\");\r\n    }\r\n\r\n    ///////////////////////////////////\r\n    // AbstractAction implementation //\r\n    ///////////////////////////////////\r\n\r\n    /**\r\n     * Intercepts action events and filters them out when the {@link MainFrame} associated with this action is in\r\n     * 'no events' mode and {@link #honourNoEventsMode()} returns <code>true</code>.\r\n     * If the action event is not filtered out, {@link #performAction()} is called to provide a response to the action event.\r\n     */\r\n    public void actionPerformed(ActionEvent e) {\r\n        // Discard this event while in 'no events mode'\r\n        if (!(mainFrame.getNoEventsMode() && honourNoEventsMode())) {\r\n            if (performActionInSeparateThread()) {\r\n                new Thread(this::performAction).start();\r\n            } else {\r\n                performAction();\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n    //////////////////////\r\n    // Abstract methods //\r\n    //////////////////////\r\n\r\n    /**\r\n     * Called when this action has been triggered. This method provides a response to the action trigger.\r\n     */\r\n    public abstract void performAction();\r\n\r\n    /**\r\n     * Returns the <code>ActionDescriptor</code> of the action.\r\n     * @return the <code>ActionDescriptor</code> of the action.\r\n     */\r\n    public abstract ActionDescriptor getDescriptor();\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/AbstractViewerAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport java.util.Map;\r\n\r\nimport com.mucommander.command.Command;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileOperation;\r\nimport com.mucommander.commons.file.filter.FileOperationFilter;\r\nimport com.mucommander.commons.file.impl.local.LocalFile;\r\nimport com.mucommander.job.TempOpenWithJob;\r\nimport com.mucommander.process.ProcessRunner;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.dialog.InformationDialog;\r\nimport com.mucommander.ui.dialog.file.ProgressDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\n/**\r\n * Provides a common base for viewer and editor actions.\r\n * @author Maxence Bernard, Nicolas Rinaudo\r\n */\r\nabstract class AbstractViewerAction extends SelectedFileAction {\r\n\r\n    /**\r\n     * Creates a new instance of <code>AbstractViewerAction</code>.\r\n     * @param mainFrame  frame to which the action is attached.\r\n     * @param properties action's properties.\r\n     */\r\n    AbstractViewerAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        // Enable this action only if the currently selected file is can be read.\r\n        setSelectedFileFilter(new FileOperationFilter(FileOperation.READ_FILE));\r\n    }\r\n\r\n\r\n\r\n    /**\r\n     * Edits the currently selected file.\r\n     */\r\n    @Override\r\n    public synchronized void performAction() {\r\n        final AbstractFile file = mainFrame.getActiveTable().getSelectedFile(false, true);\r\n\r\n        // At this stage, no assumption should be made on the type of file that is allowed to be viewed/edited:\r\n        // viewer/editor implementations will decide whether they allow a particular file or not.\r\n        if (file == null) {\r\n            return;\r\n        }\r\n        Command customCommand = getCustomCommand(file);\r\n        if (preferInternalAction(file) || customCommand == null) {\r\n            // If we're not using a custom editor, this action behaves exactly like its parent.\r\n            performInternalAction(file);\r\n            return;\r\n        }\r\n        // If it's local, init the custom editor on it.\r\n        if (file.hasAncestor(LocalFile.class)) {\r\n            try {\r\n                ProcessRunner.execute(customCommand.getTokens(file), file);\r\n            } catch(Exception e) {\r\n                InformationDialog.showErrorDialog(mainFrame.getJFrame());\r\n            }\r\n        } else {\r\n            // If it's distant, copies it locally before running the custom editor on it.\r\n            ProgressDialog progressDialog = new ProgressDialog(mainFrame, Translator.get(\"copy_dialog.copying\"));\r\n            TempOpenWithJob job = new TempOpenWithJob(progressDialog, mainFrame, file, customCommand);\r\n            progressDialog.start(job);\r\n        }\r\n    }\r\n\r\n\r\n    protected boolean preferInternalAction(AbstractFile file) {\r\n        return false;\r\n    }\r\n\r\n    /**\r\n     * Opens the specified file without a custom command.\r\n     * @param file file to open.\r\n     */\r\n    protected abstract void performInternalAction(AbstractFile file);\r\n\r\n    protected abstract Command getCustomCommand(AbstractFile file);\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ActiveTabAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport java.util.Map;\n\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.event.ActivePanelListener;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.tabs.ActiveTabListener;\n\n/**\n * This class is an abstract {@link TcAction} that operates on the current tab. It monitors changes in the active\n * tab's properties and calls {@link #toggleEnabledState()} when the properties have changed, or when the active \n * tab itself has changed, in order to enable or disable this action.\n * \n * @author Arik Hadas\n */\npublic abstract class ActiveTabAction extends TcAction implements ActivePanelListener, ActiveTabListener {\n\n\tpublic ActiveTabAction(MainFrame mainFrame, Map<String,Object> properties) {\n\t\tsuper(mainFrame, properties);\n\n\t\t// Listen to active table change events\n\t\tmainFrame.addActivePanelListener(this);\n\n\t\t// Listen to active tab change events\n\t\tmainFrame.getLeftPanel().getTabs().addActiveTabListener(this);\n\t\tmainFrame.getRightPanel().getTabs().addActiveTabListener(this);\n\n\t\ttoggleEnabledState();\n\t}\n\n\n\t//////////////////////\n\t// Abstract methods //\n\t//////////////////////\n\n\t/**\n\t * Enables or disables this action based on the location of the currently active {@link FolderPanel}.\n\t * This method is called once by the constructor to set the initial state. Then it is called every time the location\n\t * of the currently active <code>FolderPanel</code> has changed, and when the currently active <code>FolderPanel</code>\n\t * has changed.\n\t */\n\tprotected abstract void toggleEnabledState();\n\n\n\t/////////////////////////////////\n\t// ActivePanelListener methods //\n\t/////////////////////////////////\n\n\tpublic void activePanelChanged(FolderPanel folderPanel) {\n\t\ttoggleEnabledState();\n\t}\n\n\t/////////////////////////////////\n\t// ActivePanelListener methods //\n\t/////////////////////////////////\n\n\tpublic void activeTabChanged() {\n\t\ttoggleEnabledState();\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/AddBookmarkAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.bookmark.AddBookmarkDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action brings up the 'Add bookmark' dialog that allows to bookmark the current folder.\r\n *\r\n * @author Maxence Bernard\r\n */\r\n@InvokesDialog\r\npublic class AddBookmarkAction extends TcAction {\r\n\r\n    private AddBookmarkAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        new AddBookmarkDialog(mainFrame);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"AddBookmark\";\r\n\r\n\t\tpublic String getId() {\treturn ACTION_ID; }\r\n\r\n\t\tpublic ActionCategory getCategory() { return ActionCategory.NAVIGATION; }\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() { return null; }\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_B, CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new AddBookmarkAction(mainFrame, properties);\r\n        }\r\n    }\r\n\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/AddTabAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\nimport javax.swing.KeyStroke;\n\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.main.MainFrame;\n\n/**\n * This action adds a new tab in the active panel with the location\n * that is currently presented in the active tab.\n *\n * @author Arik Hadas\n */\npublic class AddTabAction extends TcAction {\n\n\tprivate AddTabAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n\t@Override\n\tpublic void performAction() {\n\t\tmainFrame.getActivePanel().getTabs().add(LocalFile.getUserHome());\n\t}\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n\tpublic static final class Descriptor extends AbstractActionDescriptor {\n\t\tpublic static final String ACTION_ID = \"AddTab\";\n\t\tpublic String getId() {\n\t\t\treturn ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t\treturn ActionCategory.TAB;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t\treturn null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_T, KeyEvent.SHIFT_DOWN_MASK | CTRL_OR_META_DOWN_MASK);\n        }\n\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n\t\t\treturn new AddTabAction(mainFrame, properties);\n\t\t}\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/BatchRenameAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\nimport javax.swing.KeyStroke;\r\n\r\nimport com.mucommander.commons.file.FileOperation;\r\nimport com.mucommander.commons.file.filter.AndFileFilter;\r\nimport com.mucommander.commons.file.filter.FileOperationFilter;\r\nimport com.mucommander.commons.file.filter.OrFileFilter;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.file.BatchRenameDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\n/**\r\n * This action invokes the 'Batch-Rename' dialog which allows to\r\n * rename selected files.\r\n *\r\n * @author Mariusz Jakubowski\r\n */\r\n@InvokesDialog\r\npublic class BatchRenameAction extends SelectedFilesAction {\r\n\r\n    private BatchRenameAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        setSelectedFileFilter(new OrFileFilter(\r\n            new FileOperationFilter(FileOperation.RENAME),\r\n            new AndFileFilter(\r\n                new FileOperationFilter(FileOperation.READ_FILE),\r\n                new FileOperationFilter(FileOperation.WRITE_FILE)\r\n            )\r\n        ));\r\n    }\r\n\r\n    @Override\r\n    public void performAction(FileSet files) {\r\n        new BatchRenameDialog(mainFrame, files).showDialog();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"BatchRename\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.FILES;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_F6, KeyEvent.ALT_DOWN_MASK);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new BatchRenameAction(mainFrame, properties);\r\n        }\r\n    }\r\n\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/BringAllToFrontAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.WindowManager;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Brings all MainFrame windows to front, from the last window index to the first, except for the current\r\n * (or last active) MainFrame which is brought to the front last. .\r\n * After this action has been performed, minimized windows will return to a normal state and windows will be stacked\r\n * in the following order:\r\n * <ul>\r\n *  <li>Current MainFrame\r\n *  <li>MainFrame #1\r\n *  <li>MainFrame #2\r\n *  <li>...\r\n *  <li>MainFrame #N\r\n * </ul>\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class BringAllToFrontAction extends TcAction {\r\n\r\n    private BringAllToFrontAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        List<MainFrame> mainFrames = WindowManager.getMainFrames();\r\n        MainFrame currentMainFrame = WindowManager.getCurrentMainFrame();\r\n\r\n        int nbMainFrames = mainFrames.size();\r\n        MainFrame mainFrame;\r\n        for (int i = nbMainFrames-1; i >= 0; i--) {\r\n            mainFrame = mainFrames.get(i);\r\n            if (mainFrame != currentMainFrame) {\r\n                mainFrame.toFront();\r\n            }\r\n        }\r\n\r\n        currentMainFrame.toFront();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    \r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"BringAllToFront\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.WINDOW;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new BringAllToFrontAction(mainFrame, properties);\r\n        }\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/CalculateChecksumAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.FileOperation;\r\nimport com.mucommander.commons.file.filter.FileOperationFilter;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.file.CalculateChecksumDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action invokes the {@link com.mucommander.ui.dialog.file.CalculateChecksumDialog} which allows to calculate\r\n * the checksum of the selected files and store the results in a pseudo-standard checksum file. \r\n *\r\n * @author Maxence Bernard\r\n */\r\n@InvokesDialog\r\npublic class CalculateChecksumAction extends SelectedFilesAction  {\r\n\r\n    private CalculateChecksumAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        setSelectedFileFilter(new FileOperationFilter(FileOperation.READ_FILE));\r\n    }\r\n\r\n    @Override\r\n    public void performAction(FileSet files) {\r\n        new CalculateChecksumDialog(mainFrame, files).showDialog();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n    \r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"CalculateChecksum\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.FILES;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_K, KeyEvent.SHIFT_DOWN_MASK | KeyEvent.ALT_DOWN_MASK);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new CalculateChecksumAction(mainFrame, properties);\r\n        }\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/CalculatorAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.main.MainFrame;\nimport ru.trolsoft.calculator.CalculatorDialog;\n\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * Created on 04/06/14.\n * @author Oleg Trifonov\n */\n@InvokesDialog\npublic class CalculatorAction extends TcAction {\n\n    private CalculatorAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n\n    @Override\n    public void performAction() {\n        new CalculatorDialog(mainFrame.getJFrame()).showDialog();\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"Calculator\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.ALL;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.ALT_DOWN_MASK);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String, Object> properties) {\n            return new CalculatorAction(mainFrame, properties);\n        }\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ChangeDateAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.FileOperation;\r\nimport com.mucommander.commons.file.filter.FileOperationFilter;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.file.ChangeDateDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Brings up a dialog that allows the user to change the date of the currently selected/marked files.\r\n *\r\n * @author Maxence Bernard\r\n */\r\n@InvokesDialog\r\npublic class ChangeDateAction extends SelectedFilesAction {\r\n\r\n    private ChangeDateAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        setSelectedFileFilter(new FileOperationFilter(FileOperation.CHANGE_DATE));\r\n    }\r\n\r\n\r\n    @Override\r\n    public void performAction(FileSet files) {\r\n        new ChangeDateDialog(mainFrame, files).showDialog();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"ChangeDate\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.FILES;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_D, KeyEvent.SHIFT_DOWN_MASK | KeyEvent.ALT_DOWN_MASK);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new ChangeDateAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ChangeLocationAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action transfers focus to the location field of the currently active FolderPanel to edit or type in\r\n * a new folder location.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class ChangeLocationAction extends ActiveTabAction {\r\n\r\n    private ChangeLocationAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    /**\r\n     * Enables or disables this action based on the currently active folder's\r\n     * current tab is not locked, this action will be enabled,\r\n     * if not it will be disabled.\r\n     */\r\n    @Override\r\n    protected void toggleEnabledState() {\r\n        setEnabled(!mainFrame.getActivePanel().getTabs().getCurrentTab().isLocked());\r\n    }\r\n    \r\n    @Override\r\n    public void performAction() {\r\n        mainFrame.getActivePanel().changeCurrentLocation();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"ChangeLocation\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_G, CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new ChangeLocationAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ChangePermissionsAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.FileOperation;\r\nimport com.mucommander.commons.file.filter.FileOperationFilter;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.file.ChangePermissionsDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Brings up a dialog that allows the user to change the file permissions the currently selected/marked files.\r\n * \r\n * @author Maxence Bernard\r\n */\r\n@InvokesDialog\r\npublic class ChangePermissionsAction extends SelectedFilesAction {\r\n\r\n    private ChangePermissionsAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        setSelectedFileFilter(new FileOperationFilter(FileOperation.CHANGE_PERMISSION));\r\n    }\r\n\r\n    @Override\r\n    public void performAction(FileSet files) {\r\n        new ChangePermissionsDialog(mainFrame, files).showDialog();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"ChangePermissions\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.FILES;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_P, KeyEvent.SHIFT_DOWN_MASK | KeyEvent.ALT_DOWN_MASK);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new ChangePermissionsAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ChangeReplicationAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.FileOperation;\r\nimport com.mucommander.commons.file.filter.FileOperationFilter;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.file.ChangeReplicationDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Brings up a dialog that allows the user to change the date of the currently selected/marked files.\r\n *\r\n * @author Maxence Bernard\r\n */\r\n@InvokesDialog\r\npublic class ChangeReplicationAction extends SelectedFilesAction {\r\n\r\n    private ChangeReplicationAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        setSelectedFileFilter(new FileOperationFilter(FileOperation.CHANGE_REPLICATION));\r\n    }\r\n\r\n\r\n    @Override\r\n    public void performAction(FileSet files) {\r\n        new ChangeReplicationDialog(mainFrame, files).showDialog();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"ChangeReplication\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.FILES;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_R, KeyEvent.SHIFT_DOWN_MASK | KeyEvent.ALT_DOWN_MASK);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new ChangeReplicationAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/CheckForUpdatesAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.startup.CheckVersionDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action checks for a new version of muCommander.\r\n *\r\n * @author Maxence Bernard\r\n */\r\n@InvokesDialog\r\npublic class CheckForUpdatesAction extends TcAction {\r\n\r\n    private CheckForUpdatesAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        new CheckVersionDialog(mainFrame, null, true);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"CheckForUpdates\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.MISC;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new CheckForUpdatesAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/CloneTabToOtherPanelAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.KeyStroke;\nimport java.util.Map;\n\n/**\n * Add a new tab in the other panel with the same location as the one presented in the currently selected tab\n * \n * @author Arik Hadas\n */\npublic class CloneTabToOtherPanelAction extends TcAction {\n\n    private CloneTabToOtherPanelAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n    \tAbstractFile currentLocation = mainFrame.getActivePanel().getCurrentFolder();\n    \tmainFrame.getInactivePanel().getTabs().add(currentLocation);\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n    \tpublic static final String ACTION_ID = \"CloneTabToOtherPanel\";\n    \t\n\t\tpublic String getId() {\n\t\t\treturn ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t\treturn ActionCategory.TAB;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t\treturn null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n\t\t\treturn null;\n\t\t}\n\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n\t\t\treturn new CloneTabToOtherPanelAction(mainFrame, properties);\n\t\t}\n    }\n}\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/CloseDuplicateTabsAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.KeyStroke;\nimport java.util.Map;\n\n/**\n * Close duplicate tabs in the folder panel\n * \n * @author Arik Hadas\n */\npublic class CloseDuplicateTabsAction extends TcAction {\n\t\n\tprivate CloseDuplicateTabsAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n    \tmainFrame.getActivePanel().getTabs().closeDuplicateTabs();\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n    \tpublic static final String ACTION_ID = \"CloseDuplicateTabs\";\n    \t\n\t\tpublic String getId() {\n\t\t\treturn ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t\treturn ActionCategory.TAB;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t\treturn null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n\t\t\treturn null;\n\t\t}\n\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n\t\t\treturn new CloseDuplicateTabsAction(mainFrame, properties);\n\t\t}\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/CloseOtherTabsAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.*;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * Close all tabs in the tabbedpane except the selected tab\n * \n * @author Arik Hadas\n */\npublic class CloseOtherTabsAction extends TcAction {\n\t\n\tprivate CloseOtherTabsAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n    \tmainFrame.getActivePanel().getTabs().closeOtherTabs();\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n    \tpublic static final String ACTION_ID = \"CloseOtherTabs\";\n    \t\n\t\tpublic String getId() {\n\t\t    return ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t    return ActionCategory.TAB;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t    return null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_W, KeyEvent.SHIFT_DOWN_MASK | CTRL_OR_META_DOWN_MASK);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new CloseOtherTabsAction(mainFrame, properties);\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/CloseTabAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.tabs.FileTableTabs;\n\nimport javax.swing.*;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * Close the current selected tab\n * \n * @author Arik Hadas\n */\npublic class CloseTabAction extends ActiveTabAction {\n\n    private CloseTabAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n    \n    /**\n     * Enables or disables this action based on the currently active folder's\n     * current tab is not locked and is not the only tab in the panel,\n     * this action will be enabled, if not it will be disabled.\n     */\n    @Override\n    protected void toggleEnabledState() {\n        FileTableTabs tabs = mainFrame.getActivePanel().getTabs();\n        setEnabled(!tabs.getCurrentTab().isLocked() && tabs.getTabsCount() > 1);\n    }\n\n    @Override\n    public void performAction() {\n        // Changes the current folder to make it the user home folder\n    \tmainFrame.getActivePanel().getTabs().closeCurrentTab();\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n    \tpublic static final String ACTION_ID = \"CloseTab\";\n    \t\n\t\tpublic String getId() {\n\t\t    return ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t    return ActionCategory.TAB;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t    return null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_W, CTRL_OR_META_DOWN_MASK);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new CloseTabAction(mainFrame, properties);\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/CloseWindowAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.WindowManager;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * If there is more than one window currently open, this action disposes the currently active MainFrame\r\n * (i.e. the one this action is attached to). On the contrary, if there is only one MainFrame currently open, this\r\n * action performs {@link com.mucommander.ui.action.impl.QuitAction} to quit the application after confirmation by the user,\r\n * if the quit confirmation has not been disabled.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class CloseWindowAction extends TcAction {\r\n\r\n    private CloseWindowAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        // Closing the last window is equivalent to quitting the application: perform QuitAction in that case\r\n        if (WindowManager.getMainFrames().size() == 1) {\r\n            ActionManager.performAction(QuitAction.Descriptor.ACTION_ID, mainFrame);\r\n        } else {\r\n            mainFrame.getJFrame().dispose();\r\n        }\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"CloseWindow\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.WINDOW;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_W, KeyEvent.META_DOWN_MASK);\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_F10, 0);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new CloseWindowAction(mainFrame, properties);\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/CombineFilesAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileOperation;\r\nimport com.mucommander.commons.file.filter.AttributeFileFilter;\r\nimport com.mucommander.commons.file.filter.AttributeFileFilter.FileAttribute;\r\nimport com.mucommander.commons.file.filter.FileFilter;\r\nimport com.mucommander.commons.file.filter.FileOperationFilter;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.file.CombineFilesDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action invokes the merge file dialog which allows to combine file parts into the original file.\r\n *\r\n * @author Mariusz Jakubowski\r\n */\r\n@InvokesDialog\r\npublic class CombineFilesAction extends SelectedFilesAction {\r\n\t\r\n    private CombineFilesAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        setSelectedFileFilter(new FileOperationFilter(FileOperation.READ_FILE));\r\n    }\r\n\r\n    @Override\r\n    public void performAction(FileSet files) {\r\n        // Filter out files that are not regular files\r\n        FileFilter filter = new AttributeFileFilter(FileAttribute.FILE);\r\n        filter.filter(files);\r\n\r\n    \tif (files.isEmpty()) {\r\n            return;\r\n        }\r\n\r\n        AbstractFile destFolder = mainFrame.getInactivePanel().getCurrentFolder();\r\n        new CombineFilesDialog(mainFrame, files, destFolder).showDialog();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"CombineFiles\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.FILES;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new CombineFilesAction(mainFrame, properties);\r\n        }\r\n\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/CommandAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.command.Command;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileProtocols;\r\nimport com.mucommander.commons.file.impl.local.LocalFile;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.job.TempOpenWithJob;\r\nimport com.mucommander.process.ProcessRunner;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.dialog.InformationDialog;\r\nimport com.mucommander.ui.dialog.file.ProgressDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport lombok.Getter;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * @author Nicolas Rinaudo\r\n */\r\npublic class CommandAction extends TcAction {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(CommandAction.class);\r\n\t\r\n    /** Command to init. */\r\n    private final Command command;\r\n\r\n    /**\r\n     * Creates a new <code>CommandAction</code> initialized with the specified parameters.\r\n     * @param mainFrame  frame that will be affected by this action.\r\n     * @param properties ignored.\r\n     * @param command    command to init when this action is called.\r\n     */\r\n    private CommandAction(MainFrame mainFrame, Map<String, Object> properties, Command command) {\r\n        super(mainFrame, properties);\r\n        this.command = command;\r\n        setLabel(command.getDisplayName());\r\n    }\r\n\r\n\r\n\r\n    // - Action code -----------------------------------------------------------\r\n    // -------------------------------------------------------------------------\r\n    @Override\r\n    public void performAction() {\r\n        // Retrieves the current selection.\r\n        FileSet selectedFiles = mainFrame.getActiveTable().getSelectedFiles();\r\n\r\n        // If no files are either selected or marked, aborts.\r\n        if (selectedFiles.isEmpty() && command.hasSelectedFileKeyword()) {\r\n            return;\r\n        }\r\n\r\n        // If we're working with local files, go ahead and runs the command.\r\n        AbstractFile baseFolder = selectedFiles.getBaseFolder();\r\n        if (baseFolder.getURL().getScheme().equals(FileProtocols.FILE) && (baseFolder.hasAncestor(LocalFile.class))) {\r\n            try {\r\n                ProcessRunner.execute(command.getTokens(selectedFiles), selectedFiles.getBaseFolder());\r\n            } catch(Exception e) {\r\n                InformationDialog.showErrorDialog(mainFrame.getJFrame());\r\n\r\n                LOGGER.debug(\"Failed to execute command: {}\", command.getCommand(), e);\r\n            }\r\n        }\r\n        // Otherwise, copies the files locally before running the command.\r\n        else {\r\n            ProgressDialog progressDialog = new ProgressDialog(mainFrame, Translator.get(\"copy_dialog.copying\"));\r\n            progressDialog.start(new TempOpenWithJob(new ProgressDialog(mainFrame, Translator.get(\"copy_dialog.copying\")), mainFrame, selectedFiles, command));\r\n        }\r\n    }\r\n\r\n    @Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor(command);\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n        private final Command command;\r\n\r\n    \tprivate static final String ACTION_ID_PREFIX = \"OpenWith_\";\r\n    \tprivate final String ACTION_ID;\r\n    \t@Getter\r\n        private final String label;\r\n\r\n    \tpublic Descriptor(Command command) {\r\n            this.command = command;\r\n    \t\tACTION_ID = ACTION_ID_PREFIX + command.getAlias() + \":\" + command.getDisplayName();\r\n    \t\tlabel = String.format(\"%s %s\", \r\n    \t\t\t\tTranslator.get(\"file_menu.open_with\"),\r\n    \t\t\t\tcommand.getDisplayName());\r\n    \t}\r\n\r\n    \tpublic String getId() {\r\n    \t    return ACTION_ID;\r\n    \t}\r\n\r\n        public ActionCategory getCategory() {\r\n    \t    return ActionCategory.COMMANDS;\r\n    \t}\r\n\r\n    \tpublic KeyStroke getDefaultAltKeyStroke() {\r\n    \t    return null;\r\n    \t}\r\n\r\n    \tpublic KeyStroke getDefaultKeyStroke() {\r\n    \t    return null;\r\n    \t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new CommandAction(mainFrame, properties, command);\r\n        }\r\n\r\n\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/CompareFilesAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2020 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileOperation;\nimport com.mucommander.commons.file.filter.AbstractFileFilter;\nimport com.mucommander.commons.file.filter.AndFileFilter;\nimport com.mucommander.commons.file.filter.FileOperationFilter;\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.process.ExecutorUtils;\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.KeyStroke;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.Map;\n\n/**\n * Created on 01/02/16.\n * @author Oleg Trifonov\n */\npublic class CompareFilesAction extends SelectedFilesAction {\n    private static final String OPENDIFF_PATH = \"/usr/bin/opendiff\";\n    private static final String MELD_PATH = \"/usr/bin/meld\";\n\n    public enum DiffMethod {\n        MAC_OS_X_DIFF {\n            @Override\n            void exec(String file1, String file2) throws IOException, InterruptedException {\n                ExecutorUtils.execute(new String[]{OPENDIFF_PATH,  file1, file2});\n            }\n        },\n        LINUX_MELD {\n            @Override\n            void exec(String file1, String file2) throws IOException, InterruptedException {\n                ExecutorUtils.execute(new String[]{MELD_PATH,  file1, file2});\n            }\n        };\n\n        abstract void exec(String file1, String file2) throws IOException, InterruptedException;\n    }\n\n    private static DiffMethod diffMethod;\n\n    private CompareFilesAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n        setSelectedFileFilter(new AndFileFilter(\n            new FileOperationFilter(FileOperation.READ_FILE),\n            new AbstractFileFilter() {\n                @Override\n                public boolean accept(AbstractFile file) {\n                    if (supported()) {\n                        AbstractFile leftFile = mainFrame.getLeftPanel().getFileTable().getSelectedFile();\n                        AbstractFile rightFile = mainFrame.getRightPanel().getFileTable().getSelectedFile();\n                        return isLocalFile(leftFile) && isLocalFile(rightFile);\n                    }\n                    return false;\n                }\n            }\n        ));\n    }\n\n    private static boolean isLocalFile(AbstractFile file) {\n        return file != null && !file.isDirectory() && file instanceof LocalFile;\n    }\n\n    @Override\n    public void performAction(FileSet files) {\n        AbstractFile leftFile = mainFrame.getLeftPanel().getFileTable().getSelectedFile();\n        AbstractFile rightFile = mainFrame.getRightPanel().getFileTable().getSelectedFile();\n        if (leftFile == null || rightFile == null) {\n            return;\n        }\n        String leftFilePath = leftFile.getAbsolutePath().replace(\" \", \"\\\\ \");\n        String rightFilePath = rightFile.getAbsolutePath().replace(\" \", \"\\\\ \");\n        compareTwoFiles(leftFilePath, rightFilePath);\n    }\n\n    public static void compareTwoFiles(String file1, String file2) {\n        if (diffMethod == null || file1 == null || file2 == null) {\n            return;\n        }\n        new Thread(() -> {\n            try {\n                diffMethod.exec(file1, file2);\n            } catch (IOException | InterruptedException e) {\n                e.printStackTrace();\n            }\n        }).start();\n    }\n\n    public static boolean supported() {\n        if (diffMethod != null) {\n            return true;\n        }\n\n        switch (OsFamily.getCurrent()) {\n            case MAC_OS_X:\n                if (new File(OPENDIFF_PATH).exists()) {\n                    diffMethod = DiffMethod.MAC_OS_X_DIFF;\n                    return true;\n                }\n                break;\n            case LINUX:\n                if (new File(MELD_PATH).exists()) {\n                    diffMethod = DiffMethod.LINUX_MELD;\n                    return true;\n                }\n                break;\n        }\n        return false;\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"CompareFiles\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.FILES;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return null;\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String, Object> properties) {\n            return new CompareFilesAction(mainFrame, properties);\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/CompareFolderFilesAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2017 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.FileTable;\nimport com.mucommander.ui.main.table.views.BaseFileTableModel;\n\nimport javax.swing.*;\nimport java.awt.event.KeyEvent;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.Map;\n\n/**\n * This action compares the content of the 2 MainFrame's file tables and marks the files with different size or content.\n *\n * Created on 10/07/17.\n * @author Oleg Trifonov\n */\n\npublic class CompareFolderFilesAction extends TcAction {\n\n    private CompareFolderFilesAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n        FileTable activeTable = mainFrame.getActiveTable();\n        FileTable inactiveTableTable = mainFrame.getInactiveTable();\n\n        BaseFileTableModel activeTableModel = activeTable.getFileTableModel();\n        BaseFileTableModel inactiveTableModel = inactiveTableTable.getFileTableModel();\n\n        if (compare(activeTableModel, inactiveTableModel)) {\n            activeTable.repaint();\n        }\n\n        // Notify registered listeners that currently marked files have changed on the file tables\n        activeTable.fireMarkedFilesChangedEvent();\n    }\n\n    private boolean compare(BaseFileTableModel firstTableModel, BaseFileTableModel secondTableModel) {\n        boolean result = false;\n        int nbFilesFirst = firstTableModel.getFileCount();\n        int nbFilesSecond = secondTableModel.getFileCount();\n\n        MessageDigest digest;\n        try {\n            digest = MessageDigest.getInstance(\"MD5\");\n        } catch (NoSuchAlgorithmException e) {\n            e.printStackTrace();\n            return false;\n        }\n        for (int i = 0; i < nbFilesFirst; i++) {\n            AbstractFile tempFile = firstTableModel.getFileAt(i);\n            if (tempFile.isDirectory()) {\n                continue;\n            }\n\n            String tempFileName = tempFile.getName();\n            int fileIndex = -1;\n            for (int j = 0; j < nbFilesSecond; j++) {\n                if (secondTableModel.getFileAt(j).getName().equals(tempFileName)) {\n                    fileIndex = j;\n                    break;\n                }\n            }\n            if (fileIndex < 0 || !checkEqual(digest, secondTableModel.getFileAt(fileIndex), tempFile)) {\n                firstTableModel.setFileMarked(tempFile, true);\n                result = true;\n            }\n        }\n        return result;\n    }\n\n    private boolean checkEqual(MessageDigest digest, AbstractFile file1, AbstractFile file2) {\n        if (file1.getSize() != file2.getSize()) {\n            return false;\n        }\n        String checksum1 = getChecksum(digest, file1);\n        String checksum2 = getChecksum(digest, file2);\n        return checksum1 != null && checksum1.equals(checksum2);\n    }\n\n    private String getChecksum(MessageDigest digest, AbstractFile file) {\n        digest.reset();\n        try (InputStream is = file.getInputStream()) {\n            return AbstractFile.calculateChecksum(is, digest);\n        } catch (IOException e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"CompareFolderFiles\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.SELECTION;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_B, KeyEvent.CTRL_DOWN_MASK);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new CompareFolderFilesAction(mainFrame, properties);\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/CompareFoldersAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.FileTable;\r\nimport com.mucommander.ui.main.table.views.BaseFileTableModel;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action compares the content of the 2 MainFrame's file tables and marks the files that are different.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class CompareFoldersAction extends TcAction {\r\n\r\n    private CompareFoldersAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        FileTable leftTable = mainFrame.getLeftPanel().getFileTable();\r\n        FileTable rightTable = mainFrame.getRightPanel().getFileTable();\r\n\r\n        BaseFileTableModel leftTableModel = leftTable.getFileTableModel();\r\n        BaseFileTableModel rightTableModel = rightTable.getFileTableModel();\r\n        if (compare(leftTableModel, rightTableModel)) {\r\n            leftTable.repaint();\r\n        }\r\n        if (compare(rightTableModel, leftTableModel)) {\r\n            rightTable.repaint();\r\n        }\r\n\r\n        // Notify registered listeners that currently marked files have changed on the file tables\r\n        leftTable.fireMarkedFilesChangedEvent();\r\n        rightTable.fireMarkedFilesChangedEvent();\r\n    }\r\n\r\n    private boolean compare(BaseFileTableModel firstTableModel, BaseFileTableModel secondTableModel) {\r\n        boolean result = false;\r\n        int nbFilesFirst = firstTableModel.getFileCount();\r\n        int nbFilesSecond = secondTableModel.getFileCount();\r\n\r\n        for (int i = 0; i < nbFilesFirst; i++) {\r\n            AbstractFile tempFile = firstTableModel.getFileAt(i);\r\n            if (tempFile.isDirectory()) {\r\n                continue;\r\n            }\r\n\r\n            String tempFileName = tempFile.getName();\r\n            int fileIndex = -1;\r\n            for (int j = 0; j < nbFilesSecond; j++) {\r\n                if (secondTableModel.getFileAt(j).getName().equals(tempFileName)) {\r\n                    fileIndex = j;\r\n                    break;\r\n                }\r\n            }\r\n            if (fileIndex < 0 || secondTableModel.getFileAt(fileIndex).getLastModifiedDate() < tempFile.getLastModifiedDate()) {\r\n                firstTableModel.setFileMarked(tempFile, true);\r\n                result = true;\r\n            }\r\n        }\r\n        return result;\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"CompareFolders\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.SELECTION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n//            if (OsFamily.getCurrent() != OsFamily.MAC_OS_X) {\r\n                return KeyStroke.getKeyStroke(KeyEvent.VK_M, KeyEvent.CTRL_DOWN_MASK);\r\n//            } else {\r\n//                return KeyStroke.getKeyStroke(KeyEvent.VK_M, KeyEvent.META_DOWN_MASK);\r\n//            }\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new CompareFoldersAction(mainFrame, properties);\r\n        }\r\n\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ConnectToServerAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.server.ServerConnectDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action pops up the 'Connect to Server' dialog that assists the user in connecting to a remote server.\r\n *\r\n * @author Maxence Bernard\r\n */\r\n@InvokesDialog\r\npublic class ConnectToServerAction extends ActiveTabAction {\r\n\r\n    private ConnectToServerAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n    \r\n    /**\r\n     * Enables or disables this action based on the currently active folder's\r\n     * current tab is not locked, this action will be enabled, if not it will be disabled.\r\n     */\r\n    @Override\r\n    protected void toggleEnabledState() {\r\n        setEnabled(!mainFrame.getActivePanel().getTabs().getCurrentTab().isLocked());\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        new ServerConnectDialog(mainFrame.getActivePanel()).showDialog();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"ConnectToServer\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_K, CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new ConnectToServerAction(mainFrame, properties);\r\n        }\r\n\r\n    }\r\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/CopyAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.FileOperation;\r\nimport com.mucommander.commons.file.filter.FileOperationFilter;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.dialog.file.CopyDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action invokes the 'Copy dialog' which allows to copy the currently selected/marked files to a specified destination.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class CopyAction extends SelectedFilesAction {\r\n\r\n    private CopyAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        setSelectedFileFilter(new FileOperationFilter(FileOperation.READ_FILE));\r\n    }\r\n\r\n    @Override\r\n    public void performAction(FileSet files) {\r\n        new CopyDialog(mainFrame, files).showDialog();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"Copy\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.FILES;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new CopyAction(mainFrame, properties);\r\n        }\r\n\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/CopyFileBaseNamesAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.dnd.ClipboardSupport;\nimport com.mucommander.ui.dnd.TransferableFileSet;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * This action copies the file base name(s) (without extension) of the currently selected / marked files(s) to the system clipboard.\n *\n * @author Chen Rozenes\n */\npublic class CopyFileBaseNamesAction extends SelectedFilesAction {\n\n\tprivate CopyFileBaseNamesAction(MainFrame mainFrame, Map<String, Object> properties) {\n\t\tsuper(mainFrame, properties);\n\t}\n\n\t@Override\n\tpublic void performAction(FileSet files) {\n        // create a TransferableFileSet and make DataFlavour.stringFlavor (text) the only DataFlavour supported\n        TransferableFileSet tfs = new TransferableFileSet(files);\n\n        // Disable unwanted data flavors\n        tfs.setJavaFileListDataFlavorSupported(false);\n        tfs.setTextUriFlavorSupported(false);\n        // Note: not disabling this flavor would throw an exception because the flavor data is not serializable\n        tfs.setFileSetDataFlavorSupported(false);\n\n        // Transfer filenames, not file paths\n        tfs.setStringDataFlavourTransfersFilename(true);\n        \n        // Transfer base names (filename without its extension)\n        tfs.setStringDataFlavourTransfersFileBaseName(true);\n\n        ClipboardSupport.setClipboardContents(tfs);\n\n\t}\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n\tpublic static final class Descriptor extends AbstractActionDescriptor {\n\t\tpublic static final String ACTION_ID = \"CopyFileBaseNames\";\n\n\t\tpublic String getId() {\n\t\t\treturn ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t\treturn ActionCategory.SELECTION;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t\treturn null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.ALT_DOWN_MASK | CTRL_OR_META_DOWN_MASK);\n        }\n\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String, Object> properties) {\n\t\t\treturn new CopyFileBaseNamesAction(mainFrame, properties);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/CopyFileNamesAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.dnd.ClipboardSupport;\r\nimport com.mucommander.ui.dnd.TransferableFileSet;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n\r\n/**\r\n * This action copies the filename(s) of the currently selected / marked files(s) to the system clipboard.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class CopyFileNamesAction extends SelectedFilesAction {\r\n\r\n    private CopyFileNamesAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction(FileSet files) {\r\n        // create a TransferableFileSet and make DataFlavour.stringFlavor (text) the only DataFlavour supported\r\n        TransferableFileSet tfs = new TransferableFileSet(files);\r\n\r\n        // Disable unwanted data flavors\r\n        tfs.setJavaFileListDataFlavorSupported(false);\r\n        tfs.setTextUriFlavorSupported(false);\r\n        // Note: not disabling this flavor would throw an exception because the flavor data is not serializable\r\n        tfs.setFileSetDataFlavorSupported(false);\r\n\r\n        // Transfer filenames, not file paths\r\n        tfs.setStringDataFlavourTransfersFilename(true);\r\n\r\n        ClipboardSupport.setClipboardContents(tfs);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"CopyFileNames\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.SELECTION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    if (OsFamily.getCurrent() != OsFamily.MAC_OS_X) {\r\n                return KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.SHIFT_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK);\r\n            } else {\r\n                return KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.SHIFT_DOWN_MASK | KeyEvent.META_DOWN_MASK);\r\n            }\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new CopyFileNamesAction(mainFrame, properties);\r\n        }\r\n\r\n    }\r\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/CopyFilePathsAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.dnd.ClipboardSupport;\r\nimport com.mucommander.ui.dnd.TransferableFileSet;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action copies the path(s) of the currently selected / marked files(s) to the system clipboard.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class CopyFilePathsAction extends SelectedFilesAction {\r\n\r\n    private CopyFilePathsAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction(FileSet files) {\r\n        // create a TransferableFileSet and make DataFlavour.stringFlavor (text) the only DataFlavour supported\r\n        TransferableFileSet tfs = new TransferableFileSet(files);\r\n\r\n        // Disable unwanted data flavors\r\n        tfs.setJavaFileListDataFlavorSupported(false);\r\n        tfs.setTextUriFlavorSupported(false);\r\n        // Note: not disabling this flavor would throw an exception because the flavor data is not serializable\r\n        tfs.setFileSetDataFlavorSupported(false);\r\n\r\n        ClipboardSupport.setClipboardContents(tfs);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"CopyFilePaths\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.SELECTION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.SHIFT_DOWN_MASK | KeyEvent.ALT_DOWN_MASK);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new CopyFilePathsAction(mainFrame, properties);\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/CopyFilesToClipboardAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.dnd.ClipboardSupport;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action copies the selected / marked files to the system clipboard, allowing to paste\r\n * them to muCommander or another application.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class CopyFilesToClipboardAction extends SelectedFilesAction {\r\n\r\n    private CopyFilesToClipboardAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction(FileSet files) {\r\n        ClipboardSupport.setClipboardFiles(files);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"CopyFilesToClipboard\";\r\n\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.SELECTION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.META_DOWN_MASK);\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_C, CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new CopyFilesToClipboardAction(mainFrame, properties);\r\n        }\r\n\r\n    }\r\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/CreateSymlinkAction.java",
    "content": "package com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileOperation;\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.dialog.symlink.CreateSymLinkDialog;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n\n/**\n * This action brings up the 'create symlink' dialog.\n *\n * @author Oleg Trifonov\n */\n@InvokesDialog\npublic class CreateSymlinkAction extends ParentFolderAction {\n\n    private CreateSymlinkAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    protected void toggleEnabledState() {\n        AbstractFile targetFile = mainFrame.getActiveTable().getSelectedFile();\n        if (targetFile == null) {\n            AbstractFile f = mainFrame.getActiveTable().getFileTableModel().getFileAt(0);\n            if (f != null) {\n                targetFile = f.getParent();\n            }\n        }\n        AbstractFile linkPath = mainFrame.getInactivePanel().getCurrentFolder();\n        setEnabled(targetFile != null && linkPath != null && linkPath.isFileOperationSupported(FileOperation.CREATE_DIRECTORY));\n    }\n\n    @Override\n    public void performAction() {\n        AbstractFile targetFile = mainFrame.getActiveTable().getSelectedFile();\n        if (targetFile == null) {\n            targetFile = mainFrame.getActiveTable().getFileTableModel().getFileAt(0).getParent();\n        }\n        AbstractFile linkPath = mainFrame.getInactivePanel().getCurrentFolder();\n        new CreateSymLinkDialog(mainFrame.getJFrame(), linkPath, targetFile).showDialog();\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"CreateSymlink\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.FILES;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_F7, KeyEvent.ALT_DOWN_MASK);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new CreateSymlinkAction(mainFrame, properties);\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/CustomizeCommandBarAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.customization.CommandBarDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action opens the dialog which allows the user to customize the command-bar.\r\n * \r\n * @author Arik Hadas\r\n */\r\n@InvokesDialog\r\npublic class CustomizeCommandBarAction extends TcAction {\r\n\t\r\n\tprivate CustomizeCommandBarAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() { new CommandBarDialog(mainFrame).showDialog(); }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"CustomizeCommandBar\";\r\n\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.VIEW;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new CustomizeCommandBarAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/CutFilesToClipboardAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.dnd.ClipboardOperations;\nimport com.mucommander.ui.dnd.ClipboardSupport;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * This action cuts the selected / marked files to the system clipboard, allowing to paste\n * them to muCommander.\n *\n * @author Nicholai R. Svarre\n */\npublic class CutFilesToClipboardAction extends SelectedFilesAction {\n    \n\n    private CutFilesToClipboardAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction(FileSet files) {\n        ClipboardSupport.setClipboardFiles(files);\n        ClipboardSupport.setOperation(ClipboardOperations.CUT);\n    }\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n    \tpublic static final String ACTION_ID = \"CutFilesToClipboard\";\n\n\t\tpublic String getId() {\n\t\t    return ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t    return ActionCategory.SELECTION;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_X, KeyEvent.META_DOWN_MASK);\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_X, KeyEvent.CTRL_DOWN_MASK);\n\t\t}\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new CutFilesToClipboardAction(mainFrame, properties);\n        }\n    }\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/DeleteAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.FileOperation;\r\nimport com.mucommander.commons.file.filter.FileOperationFilter;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.dialog.file.DeleteDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action invokes a Delete confirmation dialog to delete currently the selected / marked files\r\n * in the currently active folder. Files are moved to the system trash when possible, i.e. if there is a trash available\r\n * on the current OS environment, and if the selected files are on a filesystem that allows it (usually only local files\r\n * can be moved to the trash).\r\n *\r\n * @see com.mucommander.ui.action.impl.PermanentDeleteAction\r\n * @author Maxence Bernard\r\n */\r\npublic class DeleteAction extends SelectedFilesAction {\r\n\r\n    private DeleteAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        setSelectedFileFilter(new FileOperationFilter(FileOperation.DELETE));\r\n    }\r\n\r\n    @Override\r\n    public void performAction(FileSet files) {\r\n        new DeleteDialog(mainFrame, files, false).showDialog();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"Delete\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.FILES;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0);\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_F8, 0);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new DeleteAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/DonateAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action opens the mucommander.com donation page URL in the system's default browser.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class DonateAction extends OpenURLInBrowserAction {\r\n\r\n    private DonateAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        putValue(URL_PROPERTY_KEY, com.mucommander.RuntimeConstants.DONATION_URL);\r\n    }\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"Donate\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.MISC;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new DonateAction(mainFrame, properties);\r\n        }\r\n\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/DuplicateTabAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * Open a new tab in the current folder panel with the same location as the currently selected tab\n * \n * @author Arik Hadas\n */\npublic class DuplicateTabAction extends TcAction {\n\t\n\tprivate DuplicateTabAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n    \tmainFrame.getActivePanel().getTabs().duplicate();\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n    \tpublic static final String ACTION_ID = \"DuplicateTab\";\n    \t\n\t\tpublic String getId() {\n\t\t\treturn ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t\treturn ActionCategory.TAB;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t\treturn null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_D, KeyEvent.ALT_DOWN_MASK);\n\t\t}\n\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n\t\t\treturn new DuplicateTabAction(mainFrame, properties);\n\t\t}\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/EditAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.command.Command;\r\nimport com.mucommander.command.CommandManager;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.viewer.EditorRegistrar;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * User configurable variant of {@link InternalEditAction}.\r\n * @author Maxence Bernard, Nicolas Rinaudo\r\n */\r\npublic class EditAction extends InternalEditAction {\r\n    /**\r\n     * Creates a new instance of <code>EditAction</code>.\r\n     * @param mainFrame  frame to which the action is attached.\r\n     * @param properties action's properties.\r\n     */\r\n    private EditAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n\r\n    @Override\r\n    protected boolean preferInternalAction(AbstractFile file) {\r\n        return !EditorRegistrar.getAllEditors(file).isEmpty();\r\n    }\r\n\r\n    @Override\r\n    protected Command getCustomCommand(AbstractFile file) {\r\n        return CommandManager.getCommandForAlias(CommandManager.EDITOR_ALIAS, file);\r\n    }\r\n\r\n\r\n    @Override\r\n    public ActionDescriptor getDescriptor() {\r\n        return new Descriptor();\r\n    }\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"Edit\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.FILES;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_F4, 0);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new EditAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/EditAsAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2019 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.quicklist.EditAsQL;\n\nimport javax.swing.*;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\npublic class EditAsAction extends SelectedFilesAction {\n    /**\n     * Creates a new instance of <code>EditAsAction</code>.\n     * @param mainFrame  frame to which the action is attached.\n     * @param properties action's properties.\n     */\n    private EditAsAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n\n        ImageIcon icon = getStandardIcon(EditAction.class);\n        if (icon != null) {\n            setIcon(icon);\n        }\n    }\n\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new EditAsAction.Descriptor();\n    }\n\n    @Override\n    public void performAction(FileSet files) {\n        AbstractFile file = mainFrame.getActiveTable().getSelectedFile(false, true);\n\n        // At this stage, no assumption should be made on the type of file that is allowed to be viewed/edited:\n        // viewer/editor implementations will decide whether they allow a particular file or not.\n        if (file == null || file.isDirectory()) {\n            return;\n        }\n        new EditAsQL(mainFrame, file).show();\n    }\n\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"EditAs\";\n\n        public String getId() { return ACTION_ID; }\n\n        public ActionCategory getCategory() { return ActionCategory.FILES; }\n\n        public KeyStroke getDefaultAltKeyStroke() { return null; }\n\n        public KeyStroke getDefaultKeyStroke() { return KeyStroke.getKeyStroke(KeyEvent.VK_F4, KeyEvent.SHIFT_DOWN_MASK); }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new EditAsAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/EditBookmarksAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.bookmark.EditBookmarksDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n\r\n/**\r\n * This action brings up the 'Edit bookmarks' dialog that allows to edit bookmarks.\r\n *\r\n * @author Maxence Bernard\r\n */\r\n@InvokesDialog\r\npublic class EditBookmarksAction extends TcAction {\r\n\r\n    private EditBookmarksAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        new EditBookmarksDialog(mainFrame);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"EditBookmarks\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new EditBookmarksAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/EditCommandsAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.dialog.commands.EditCommandsDialog;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.KeyStroke;\nimport java.util.Map;\n\n/**\n * @author Oleg Trifonov\n * Created on 09/10/14.\n */\n@InvokesDialog\npublic class EditCommandsAction extends TcAction {\n\n    /**\n     * Creates a new <code>MuAction</code> associated with the specified {@link com.mucommander.ui.main.MainFrame}. The properties contained by\n     * the given {@link Map} are used to initialize this action's property map.\n     *\n     * @param mainFrame  the MainFrame to associate with this new MuAction\n     * @param properties the initial properties to use in this action. The Hashtable may simply be empty if no initial\n     */\n    private EditCommandsAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n        var frame = mainFrame.getJFrame();\n        new EditCommandsDialog(frame, frame).showDialog();\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"EditCommands\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.MISC;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return null;\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String, Object> properties) {\n            return new EditCommandsAction(mainFrame, properties);\n        }\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/EditCredentialsAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.auth.EditCredentialsDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action brings up the 'Edit credentials' dialog that allows to edit persistent credentials (the ones stored\r\n * to disk).\r\n *\r\n * @author Maxence Bernard\r\n */\r\n@InvokesDialog\r\npublic class EditCredentialsAction extends TcAction {\r\n\r\n    private EditCredentialsAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        new EditCredentialsDialog(mainFrame);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"EditCredentials\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new EditCredentialsAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/EjectDriveAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.filter.MountedDriveFilter;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.macosx.AppleScript;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.statusbar.TaskWidget;\n\nimport javax.swing.*;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n *\n * Created on 26/01/16.\n * @author Oleg Trifonov\n */\npublic class EjectDriveAction extends SelectedFilesAction {\n\n    private EjectDriveAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n        setSelectedFileFilter(new MountedDriveFilter());\n    }\n\n    @Override\n    public void performAction(FileSet files) {\n        if (files.size() == 1) {\n            eject(mainFrame, files.get(0));\n            mainFrame.tryRefreshCurrentFolders();\n        }\n    }\n\n    public static void eject(MainFrame mainFrame, AbstractFile file) {\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            new EjectWorker(mainFrame, file.getName()).execute();\n        }\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"EjectDrive\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.FILES;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return null;\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String, Object> properties) {\n            return new EjectDriveAction(mainFrame, properties);\n        }\n    }\n\n\n    private static class EjectWorker extends SwingWorker<Void, Void> {\n        private final MainFrame mainFrame;\n        private final TaskWidget taskWidget;\n        private final String fileName;\n        private boolean taskWidgetAttached;\n        private int progress;\n\n        EjectWorker(MainFrame mainFrame, String fileName) {\n            this.mainFrame = mainFrame;\n            this.fileName = fileName;\n            this.taskWidget = new TaskWidget();\n            taskWidget.setText(Translator.get(\"EjectDrive.label\"));\n        }\n\n        @Override\n        protected Void doInBackground() {\n            try {\n                publish();\n                StringBuilder sb = new StringBuilder();\n                publish();\n                Thread t = new Thread(() -> {\n                    //try {Thread.sleep(5000); } catch (Throwable e) {}\n                    AppleScript.execute(\"tell application \\\"Finder\\\"\\n\" +\n                            \"   eject disk \\\"\" + fileName + \"\\\"\\n\" +\n                            \"end tell\", sb);\n                });\n                t.start();\n                while (t.isAlive() || progress < 100) {\n                    if (!t.isAlive()) {\n                        progress += 10;\n                    }\n                    Thread.sleep(50);\n                    publish();\n                }\n                progress = 100;\n                publish();\n            } catch (Throwable ignore) {}\n            return null;\n        }\n\n        @Override\n        protected void process(List<Void> chunks) {\n            if (!taskWidgetAttached) {\n                mainFrame.getStatusBar().getTaskPanel().addTask(taskWidget);\n                mainFrame.getStatusBar().revalidate();\n                mainFrame.getStatusBar().repaint();\n                taskWidgetAttached = true;\n            }\n            if (progress < 100) {\n                progress += 10;\n            }\n            taskWidget.setProgress(progress);\n        }\n\n        @Override\n        protected void done() {\n            taskWidget.removeFromPanel();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/EmailAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.FileOperation;\r\nimport com.mucommander.commons.file.filter.FileOperationFilter;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.job.SendMailJob;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.InformationDialog;\r\nimport com.mucommander.ui.dialog.file.EmailFilesDialog;\r\nimport com.mucommander.ui.dialog.pref.general.GeneralPreferencesDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action pops up the 'Email files' dialog that allows to email the currently marked files as attachment.\r\n *\r\n * @author Maxence Bernard\r\n */\r\n@InvokesDialog\r\npublic class EmailAction extends SelectedFilesAction {\r\n\r\n    private EmailAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        setSelectedFileFilter(new FileOperationFilter(FileOperation.READ_FILE));\r\n    }\r\n\r\n    @Override\r\n    public void performAction(FileSet files) {\r\n        // Notifies the user that mail preferences are not set and brings the preferences dialog\r\n        if (!SendMailJob.mailPreferencesSet()) {\r\n            InformationDialog.showErrorDialog(mainFrame.getJFrame(), Translator.get(\"email_dialog.prefs_not_set\"), Translator.get(\"email_dialog.prefs_not_set_title\"));\r\n            SwingUtilities.invokeLater(() -> {\r\n                GeneralPreferencesDialog preferencesDialog = GeneralPreferencesDialog.getDialog();\r\n                preferencesDialog.setActiveTab(GeneralPreferencesDialog.MAIL_TAB);\r\n                preferencesDialog.showDialog();\r\n            });\r\n            return;\r\n        }\r\n        new EmailFilesDialog(mainFrame, files).showDialog();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"Email\";\r\n\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.FILES;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_S, CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new EmailAction(mainFrame, properties);\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/EmptyTrashAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.desktop.AbstractTrash;\r\nimport com.mucommander.desktop.DesktopManager;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Empties the system trash. This action is enabled only if the current platform has an\r\n * {@link com.mucommander.desktop.AbstractTrash} implementation and if it is capable of emptying the trash,\r\n * as reported by {@link com.mucommander.desktop.AbstractTrash#canEmpty()}.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class EmptyTrashAction extends TcAction {\r\n\r\n    private EmptyTrashAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        AbstractTrash trash = DesktopManager.getTrash();\r\n        setEnabled(trash!=null && trash.canEmpty());\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        AbstractTrash trash = DesktopManager.getTrash();\r\n        if (trash != null) {\r\n            trash.empty();\r\n        }\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"EmptyTrash\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.FILES;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new EmptyTrashAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ExploreBookmarksAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action changes the current folder of the currently active {@link com.mucommander.ui.main.FolderPanel} to\r\n * <code>bookmark://</code> which is the root of bookmark filesystem, allowing to explore all the bookmarks the user has.\r\n *\r\n * @author Nicolas Rinaudo\r\n */\r\npublic class ExploreBookmarksAction extends ActiveTabAction {\r\n\r\n    private ExploreBookmarksAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        mainFrame.getActivePanel().tryChangeCurrentFolder(\"bookmark://\");\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n    /**\r\n     * Enables or disables this action based on the currently active folder's\r\n     * current tab is not locked, this action will be enabled,\r\n     * if not it will be disabled.\r\n     */\r\n    @Override\r\n    protected void toggleEnabledState() {\r\n        setEnabled(!mainFrame.getActivePanel().getTabs().getCurrentTab().isLocked());\r\n    }\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"ExploreBookmarks\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_B, KeyEvent.SHIFT_DOWN_MASK | CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new ExploreBookmarksAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/FileAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.filter.FileFilter;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.event.ActivePanelListener;\r\nimport com.mucommander.ui.event.TableSelectionListener;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.FileTable;\r\n\r\nimport java.util.Map;\r\n\r\n/**\r\n * FileAction is an abstract action that operates on the currently active FileTable. It is enabled only when\r\n * the table condition as tested by {@link #getFileTableCondition(FileTable) getFileTableCondition()}\r\n * method is satisfied.\r\n *\r\n * <p>Those tests are performed when:\r\n * <ul>\r\n * <li>the selected file on the currently active FileTable has changed\r\n * <li>the marked files on the currently active FileTable has changed\r\n * <li>the currently active FileTable has changed\r\n * </ul>\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic abstract class FileAction extends TcAction implements TableSelectionListener, ActivePanelListener {\r\n\r\n    /** Filter that restricts the enabled condition to files that match it (can be null) */\r\n    protected FileFilter filter;\r\n\r\n\r\n    FileAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n        init(mainFrame);\r\n    }\r\n\r\n    \r\n    private void init(MainFrame mainFrame) {\r\n        mainFrame.addActivePanelListener(this);\r\n        mainFrame.getLeftPanel().getFileTable().addTableSelectionListener(this);\r\n        mainFrame.getRightPanel().getFileTable().addTableSelectionListener(this);\r\n\r\n        // Set initial enabled state\r\n        updateEnabledState(mainFrame.getActiveTable());\r\n    }\r\n\r\n\r\n    /**\r\n     * Enables/disables this action if both of the {@link #getFileTableCondition(FileTable)} and file IMAGE_FILTER\r\n     * (if there is one) tests are satisfied.\r\n     *\r\n     * <p>This method is called each time:\r\n     * <ul>\r\n     * <li>the selected file on the currently active FileTable has changed\r\n     * <li>the marked files on the currently active FileTable has changed\r\n     * <li>the currently active FileTable has changed\r\n     * </ul>\r\n     *\r\n     * @param fileTable the currently active FileTable\r\n     */\r\n    protected void updateEnabledState(FileTable fileTable) {\r\n        // Note: AbstractAction checks if enabled value has changed before firing an event\r\n        setEnabled(getFileTableCondition(fileTable));\r\n    }\r\n\r\n\r\n    /**\r\n     * This method is called to determine if the current FileTable state allows this action to be enabled.\r\n     * If <code>false</code> is returned, the action will be disabled.\r\n     * If <code>true</code> is returned, the action will be enabled if the file IMAGE_FILTER (if there is one) matches the\r\n     * selected file.\r\n     *\r\n     * @param fileTable currently active FileTable\r\n     */\r\n    protected abstract boolean getFileTableCondition(FileTable fileTable);\r\n\r\n\r\n    ///////////////////////////////////////////\r\n    // TableSelectionListener implementation //\r\n    ///////////////////////////////////////////\r\n\r\n    /**\r\n     * Updates this action's enabled status based on the new currently selected file.\r\n     */\r\n    public void selectedFileChanged(FileTable source) {\r\n        // No need to update state if the originating FileTable is not the currently active one \r\n        if (source == mainFrame.getActiveTable()) {\r\n            updateEnabledState(source);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Updates this action's enabled status based on the new currently marked files.\r\n     */\r\n    public void markedFilesChanged(FileTable source) {\r\n        // No need to update state if the originating FileTable is not the currently active one\r\n        if (source == mainFrame.getActiveTable()) {\r\n            updateEnabledState(source);\r\n        }\r\n    }\r\n\r\n    ////////////////////////////////////////\r\n    // ActivePanelListener implementation //\r\n    ////////////////////////////////////////\r\n\r\n    public void activePanelChanged(FolderPanel folderPanel) {\r\n        updateEnabledState(folderPanel.getFileTable());\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/FindFileAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.dialog.file.FindFileDialog;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * Find File action\n * @author Oleg Trifonov\n */\n@InvokesDialog\npublic class FindFileAction extends ParentFolderAction {\n\n    private FindFileAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    protected void toggleEnabledState() {\n\n    }\n\n    @Override\n    public void performAction() {\n        AbstractFile currentFolder = mainFrame.getActiveTable().getFileTableModel().getCurrentFolder();\n        new FindFileDialog(mainFrame, currentFolder).showDialog();\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"FindFile\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.NAVIGATION;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_F, CTRL_OR_META_DOWN_MASK);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String, Object> properties) {\n            return new FindFileAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/FocusNextAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.helper.FocusRequester;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.FileTable;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action allows to cycle forward through the current FolderPanel's focusable components: file table, folder tree\r\n * and location field. The action has no effect when the focus is not in the MainFrame this action is tied to.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class FocusNextAction extends TcAction {\r\n\r\n    private FocusNextAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        // Perform the action also when in 'no events' mode\r\n        setHonourNoEventsMode(false);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        Component focusOwner = mainFrame.getJFrame().getFocusOwner();\r\n\r\n        // Abort if the focus is not in the MainFrame this action is tied to\r\n        if (focusOwner == null) {\r\n            return;\r\n        }\r\n\r\n        FolderPanel folderPanel = mainFrame.getActivePanel();\r\n        FileTable fileTable = folderPanel.getFileTable();\r\n        JTextField locationField = folderPanel.getLocationTextField();\r\n        JTree tree = folderPanel.getFoldersTreePanel().getTree();\r\n\r\n        // Request focus on the 'next' component, the cycle order being from left to right, top to bottom.\r\n        Component nextComponent;\r\n        if (focusOwner == locationField) {\r\n            nextComponent = folderPanel.isTreeVisible() ? tree : fileTable;\r\n        } else if (focusOwner == tree) {\r\n            nextComponent = fileTable;\r\n        } else if(focusOwner == fileTable) {\r\n            nextComponent = locationField;\r\n        } else {\r\n            return;\r\n        }\r\n\r\n        FocusRequester.requestFocusInWindow(nextComponent);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"FocusNext\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_TAB, CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new FocusNextAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/FocusPreviousAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.helper.FocusRequester;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.FileTable;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action allows to cycle backward through the current {@link FolderPanel}'s focusable components: file table,\r\n * folder tree and location field. The action has no effect when the focus is not in the {@link MainFrame} this action\r\n * is tied to.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class FocusPreviousAction extends TcAction {\r\n\r\n    private FocusPreviousAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        // Perform the action also when in 'no events' mode\r\n        setHonourNoEventsMode(false);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        Component focusOwner = mainFrame.getJFrame().getFocusOwner();\r\n\r\n        // Abort if the focus is not in the MainFrame this action is tied to\r\n        if (focusOwner == null) {\r\n            return;\r\n        }\r\n\r\n        FolderPanel folderPanel = mainFrame.getActivePanel();\r\n        FileTable fileTable = folderPanel.getFileTable();\r\n        JTextField locationField = folderPanel.getLocationTextField();\r\n        JTree tree = folderPanel.getFoldersTreePanel().getTree();\r\n\r\n        // Request focus on the 'previous' component, the cycle order being from right to left, bottom to top.\r\n        Component previousComponent;\r\n        if (focusOwner == fileTable) {\r\n            previousComponent = folderPanel.isTreeVisible() ? tree : locationField;\r\n        } else if (focusOwner == tree) {\r\n            previousComponent = locationField;\r\n        } else if(focusOwner == locationField) {\r\n            previousComponent = fileTable;\r\n        } else {\r\n            return;\r\n        }\r\n\r\n        FocusRequester.requestFocusInWindow(previousComponent);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"FocusPrevious\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_TAB, KeyEvent.SHIFT_DOWN_MASK| CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new FocusPreviousAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/GarbageCollectAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.FileFactory;\r\nimport com.mucommander.commons.file.icon.CacheableFileIconProvider;\r\nimport com.mucommander.commons.file.icon.impl.SwingFileIconProvider;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action invokes the garbage collector and is here for debugging purposes only.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class GarbageCollectAction extends TcAction {\r\n\r\n    private GarbageCollectAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n\t\tvar iconProvider = FileFactory.getDefaultFileIconProvider();\r\n\t\tif (iconProvider instanceof CacheableFileIconProvider cip) {\r\n\t\t\tcip.cleanCache();\r\n\t\t}\r\n        System.gc();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"GarbageCollect\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new GarbageCollectAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/GoBackAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action recalls the previous folder in the current FolderPanel's history.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class GoBackAction extends ActiveTabAction {\r\n\r\n    private GoBackAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n\r\n    @Override\r\n    public void performAction() {\r\n        mainFrame.getActivePanel().getFolderHistory().goBack();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n    /**\r\n     * Enables or disables this action based on the history of the currently active FolderPanel: if there is a previous\r\n     * folder in the history and the current tab is not locked, this action will be enabled, if not it will be disabled.\r\n     */\r\n    @Override\r\n    protected void toggleEnabledState() {\r\n        setEnabled(mainFrame.getActivePanel().getFolderHistory().hasBackFolder() &&\r\n        \t\t  !mainFrame.getActivePanel().getTabs().getCurrentTab().isLocked());\r\n    }\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"GoBack\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.NAVIGATION; }\r\n\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, KeyEvent.ALT_DOWN_MASK);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new GoBackAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/GoForwardAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action recalls the next folder in the current FolderPanel's history.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class GoForwardAction extends ActiveTabAction {\r\n\r\n    private GoForwardAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n\r\n    @Override\r\n    public void performAction() {\r\n        mainFrame.getActivePanel().getFolderHistory().goForward();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n    /**\r\n     * Enables or disables this action based on the history of the currently active FolderPanel: if there is a next\r\n     * folder in the history and the current tab is not locked, this action will be enabled, if not it will be disabled.\r\n     */\r\n    @Override\r\n    protected void toggleEnabledState() {\r\n        setEnabled(mainFrame.getActivePanel().getFolderHistory().hasForwardFolder() &&\r\n        \t\t  !mainFrame.getActivePanel().getTabs().getCurrentTab().isLocked());\r\n    }\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"GoForward\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.ALT_DOWN_MASK);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new GoForwardAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/GoToDocumentationAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.icon.IconManager;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.KeyStroke;\nimport java.beans.PropertyChangeEvent;\nimport java.beans.PropertyChangeListener;\nimport java.util.Map;\n\n/**\n * Opens the muCommander online documentation in the system's default browser. The {@link #TOPIC_PROPERTY_KEY}\n * property allows to specify a specific documentation topic which the browser will be sent to. If it is not defined,\n * the base documentation URL will be opened.\n *\n * @author Maxence Bernard\n */\npublic class GoToDocumentationAction extends OpenURLInBrowserAction implements PropertyChangeListener {\n\n    /** Key to the topic property */\n    public final static String TOPIC_PROPERTY_KEY = \"topic\";\n\n    public GoToDocumentationAction(MainFrame mainFrame, Map<String,Object> properties) {\n        super(mainFrame, properties);\n\n        setIcon(IconManager.getIcon(IconManager.IconSet.COMMON, \"help.png\"));\n\n        // Set the URL\n        updateURL();\n\n        // Listen to changes made to the topic property\n        addPropertyChangeListener(this);\n    }\n\n    /**\n     * Sets the URL to sent the browser to, using the base URL defined in the runtime constants and\n     * the optional topic defined in the {@link #TOPIC_PROPERTY_KEY}. The URL is stored in the {@link #URL_PROPERTY_KEY}\n     * property.\n     */\n    private void updateURL() {\n        String url = com.mucommander.RuntimeConstants.DOCUMENTATION_URL;\n        String topic = (String)getValue(TOPIC_PROPERTY_KEY);\n\n        // If there is a topic, append it to the URL\n        if (topic != null) {\n            if (url.endsWith(\"/\")) {\n                url += \"/\";\n            }\n\n            url += topic;\n        }\n\n        putValue(URL_PROPERTY_KEY, url);\n    }\n\n\n    ///////////////////////////////////////////\n    // PropertyChangeListener implementation //\n    ///////////////////////////////////////////\n\n    public void propertyChange(PropertyChangeEvent propertyChangeEvent) {\n\n        if (propertyChangeEvent.getPropertyName().equals(TOPIC_PROPERTY_KEY)) {\n            updateURL();\n        }\n    }\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n    \tpublic static final String ACTION_ID = \"GoToDocumentation\";\n    \t\n\t\tpublic String getId() {\n\t\t    return ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t    return ActionCategory.MISC;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t    return null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n\t\t    return null;\n\t\t}\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new GoToDocumentationAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/GoToForumsAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action opens the mucommander.com forums URL in the system's default browser.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class GoToForumsAction extends OpenURLInBrowserAction {\r\n\r\n    private GoToForumsAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        putValue(URL_PROPERTY_KEY, com.mucommander.RuntimeConstants.FORUMS_URL);\r\n    }\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"GoToForums\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.MISC;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new GoToForumsAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/GoToHomeAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.impl.local.LocalFile;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action changes the current folder of the currently active FolderPanel to the user home folder.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class GoToHomeAction extends ActiveTabAction {\r\n\r\n    private GoToHomeAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n    \r\n    /**\r\n     * Enables or disables this action based on the currently active folder's\r\n     * current tab is not locked, this action will be enabled, if not it will be disabled.\r\n     */\r\n    @Override\r\n    protected void toggleEnabledState() {\r\n        setEnabled(!mainFrame.getActivePanel().getTabs().getCurrentTab().isLocked());\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        // Changes the current folder to make it the user home folder\r\n        AbstractFile homeFolder = LocalFile.getUserHome();\r\n        if(homeFolder!=null)\r\n            mainFrame.getActivePanel().tryChangeCurrentFolder(homeFolder);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"GoToHome\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new GoToHomeAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/GoToParentAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action changes the current folder of the currently active FolderPanel to the current folder's parent.\r\n * This action only gets enabled when the current folder has a parent and current tab is not locked.\r\n *\r\n * @author Maxence Bernard, Nicolas Rinaudo\r\n */\r\npublic class GoToParentAction extends ActiveTabAction {\r\n    /**\r\n     * Creates a new <code>GoToParentAction</code> with the specified parameters.\r\n     * @param mainFrame  frame to which the action is attached.\r\n     * @param properties action's properties.\r\n     */\r\n    private GoToParentAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n\r\n    /**\r\n     * Enables or disables this action based on the currently active folder's\r\n     * has a parent and current tab is not locked, this action will be enabled,\r\n     * if not it will be disabled.\r\n     */\r\n    @Override\r\n    protected void toggleEnabledState() {\r\n        FolderPanel activePanel = mainFrame.getActivePanel();\r\n        boolean isLocked = activePanel.getTabs().getCurrentTab().isLocked();\r\n        AbstractFile currentFolder = activePanel.getCurrentFolder();\r\n        setEnabled(!isLocked && currentFolder != null && currentFolder.getParent() != null);\r\n    }\r\n\r\n\r\n    /**\r\n     * Updates <code>panel</code>'s location to its parent.\r\n     *\r\n     * @param  panel in which to change the location.\r\n     * @return <code>true</code> if <code>panel</code> has a parent, <code>false</code> otherwise.\r\n     */\r\n    private boolean goToParent(FolderPanel panel) {\r\n    \tAbstractFile parent = panel.getCurrentFolder().getParent();\r\n        if (parent != null) {\r\n        \tpanel.tryChangeCurrentFolder(parent, null, true);\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n\r\n\r\n\r\n    /**\r\n     * Goes to the current location's parent in the active panel.\r\n     */\r\n    @Override\r\n    public void performAction() {\r\n        // Changes the current folder to make it the current folder's parent.\r\n        // Does nothing if the current folder doesn't have a parent.\r\n        goToParent(mainFrame.getActivePanel());\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"GoToParent\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new GoToParentAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/GoToParentInBothPanelsAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Changes the current directory to its parent and tries to do the same in the inactive panel.\r\n * <p>\r\n * When possible, this action will open the active panel's current folder's parent. Additionally,\r\n * if the inactive panel's current folder has a parent, it will open that one as well.\r\n * <p>\r\n * Note that this action's behavior is strictly equivalent to that of {@link GoToParentAction} in the\r\n * active panel. Differences will only occur in the inactive panel, and then again only when possible.\r\n * <p>\r\n * This action opens both files synchronously: it will wait for the active panel location change confirmation\r\n * before performing the inactive one.\r\n *\r\n * @author Nicolas Rinaudo\r\n */\r\npublic class GoToParentInBothPanelsAction extends ActiveTabAction {\r\n\r\n    /**\r\n     * Creates a new <code>GoToParentInBothPanelsAction</code> instance with the specified parameters.\r\n     * @param mainFrame  frame to which the action is attached.\r\n     * @param properties action's properties.\r\n     */\r\n    private GoToParentInBothPanelsAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        // Perform this action in a separate thread, to avoid locking the event thread\r\n        setPerformActionInSeparateThread(true);\r\n    }\r\n\r\n    /**\r\n     * Enables or disables this action based on the currently active folder's\r\n     * has a parent and both tabs in the two panel are not locked,\r\n     * this action will be enabled, if not it will be disabled.\r\n     */\r\n    @Override\r\n    protected void toggleEnabledState() {\r\n        AbstractFile currentFolder = mainFrame.getActivePanel().getCurrentFolder();\r\n        setEnabled(!mainFrame.getActivePanel().getTabs().getCurrentTab().isLocked() &&\r\n        \t\t   !mainFrame.getInactivePanel().getTabs().getCurrentTab().isLocked() &&\r\n        \t\t    currentFolder != null && currentFolder.getParent() != null);\r\n    }\r\n\r\n    /**\r\n     * Opens both the active and inactive folder panel's parent directories.\r\n     */\r\n    @Override\r\n    public void performAction() {\r\n        // If the current panel has a parent file, navigate to it.\r\n        AbstractFile parent = mainFrame.getActivePanel().getCurrentFolder().getParent();\r\n        if (parent != null) {\r\n            Thread openThread = mainFrame.getActivePanel().tryChangeCurrentFolder(parent);\r\n\r\n            // If the inactive panel has a parent file, wait for the current panel change to be complete and navigate to it.\r\n            parent = mainFrame.getInactivePanel().getCurrentFolder().getParent();\r\n            if (parent != null) {\r\n                if (openThread != null) {\r\n                    while (openThread.isAlive()) {\r\n                        try {\r\n                            openThread.join();\r\n                        } catch(InterruptedException ignore) {}\r\n                    }\r\n                }\r\n                mainFrame.getInactivePanel().tryChangeCurrentFolder(parent);\r\n            }\r\n        }\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"GoToParentInBothPanels\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, KeyEvent.SHIFT_DOWN_MASK| CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new GoToParentInBothPanelsAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/GoToParentInOtherPanelAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Opens the active panel's parent in the inactive panel.\r\n * <p>\r\n * This action is only enabled when the active panel has a parent,\r\n * and the selected tab in the other panel is not locked.\r\n *\r\n * @author Nicolas Rinaudo\r\n */\r\npublic class GoToParentInOtherPanelAction extends ParentFolderAction {\r\n    /**\r\n     * Creates a new <code>GoToParentInOtherPanelAction</code> with the specified parameters.\r\n     * @param mainFrame  frame to which the action is attached.\r\n     * @param properties action's properties.\r\n     */\r\n    private GoToParentInOtherPanelAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    /**\r\n     * Goes to <code>sourcePanel</code>'s parent in <code>destPanel</code>.\r\n     * <p>\r\n     * If <code>sourcePanel</code> doesn't have a parent, nothing will happen.\r\n     *\r\n     * @param  sourcePanel panel whose parent should be used.\r\n     * @param  destPanel   panel in which to change the location.\r\n     * @return             <code>true</code> if <code>sourcePanel</code> has a parent, <code>false</code> otherwise.\r\n     */\r\n    private boolean goToParent(FolderPanel sourcePanel, FolderPanel destPanel) {\r\n        AbstractFile parent = sourcePanel.getCurrentFolder().getParent();\r\n\r\n        if (parent != null) {\r\n            destPanel.tryChangeCurrentFolder(parent, null, true);\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n    \r\n    /**\r\n     * Enables or disables this action based on the currently active folder's\r\n     * has a parent and selected tab in the other panel is not locked,\r\n     * this action will be enabled, if not it will be disabled.\r\n     */\r\n    @Override\r\n    protected void toggleEnabledState() {\r\n        AbstractFile currentFolder = mainFrame.getActivePanel().getCurrentFolder();\r\n        setEnabled(!mainFrame.getInactivePanel().getTabs().getCurrentTab().isLocked() &&\r\n        \t\t    currentFolder != null && currentFolder.getParent() != null);\r\n    }\r\n    \r\n    /**\r\n     * Opens the active panel's parent in the inactive panel.\r\n     */\r\n    @Override\r\n    public void performAction() {\r\n    \tgoToParent(mainFrame.getActivePanel(), mainFrame.getInactivePanel());\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"GoToParentInOtherPanel\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new GoToParentInOtherPanelAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/GoToRootAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action changes the current folder of the currently active FolderPanel to the current folder's root.\r\n * This action only gets enabled when the current folder has a parent.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class GoToRootAction extends ActiveTabAction {\r\n\r\n    private GoToRootAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    /**\r\n     * Enables or disables this action based on the currently active folder's\r\n     * has a parent and current tab is not locked, this action will be enabled,\r\n     * if not it will be disabled.\r\n     */\r\n    @Override\r\n    protected void toggleEnabledState() {\r\n        AbstractFile currentFolder = mainFrame.getActivePanel().getCurrentFolder();\r\n        setEnabled(!mainFrame.getActivePanel().getTabs().getCurrentTab().isLocked() &&\r\n        \t\t    currentFolder != null && currentFolder.getParent() != null);\r\n    }\r\n    \r\n    @Override\r\n    public void performAction() {\r\n        // Changes the current folder to make it the current folder's root folder.\r\n        // Does nothing if the current folder already is the root.\r\n        FolderPanel folderPanel = mainFrame.getActivePanel();\r\n        AbstractFile currentFolder = folderPanel.getCurrentFolder();\r\n        folderPanel.tryChangeCurrentFolder(currentFolder.getRoot());\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"GoToRoot\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SLASH, KeyEvent.CTRL_DOWN_MASK);\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, KeyEvent.SHIFT_DOWN_MASK);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new GoToRootAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/GoToWebsiteAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action opens the mucommander.com URL in the system's default browser.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class GoToWebsiteAction extends OpenURLInBrowserAction {\r\n\r\n    private GoToWebsiteAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        putValue(URL_PROPERTY_KEY, com.mucommander.RuntimeConstants.HOMEPAGE_URL);\r\n    }\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"GoToWebsite\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.MISC;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new GoToWebsiteAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/InternalEditAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.command.Command;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileOperation;\r\nimport com.mucommander.commons.file.FileProtocols;\r\nimport com.mucommander.commons.file.filter.AndFileFilter;\r\nimport com.mucommander.commons.file.filter.FileOperationFilter;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.dialog.file.ChangePermissionsDialog;\r\nimport com.mucommander.ui.dialog.symlink.EditSymlinkDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.viewer.EditorRegistrar;\r\n\r\nimport javax.swing.*;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Opens the current file in edit mode.\r\n * @author Maxence Bernard, Nicolas Rinaudo\r\n */\r\npublic class InternalEditAction extends AbstractViewerAction {\r\n    /**\r\n     * Creates a new instance of <code>EditAction</code>.\r\n     * @param mainFrame  frame to which the action is attached.\r\n     * @param properties action's properties.\r\n     */\r\n    InternalEditAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        // Edit requires being able to write the file (in addition to view requirements)\r\n        setSelectedFileFilter(new AndFileFilter(\r\n            new FileOperationFilter(FileOperation.WRITE_FILE),\r\n            getSelectedFileFilter()\r\n        ));\r\n\r\n        ImageIcon icon = getStandardIcon(EditAction.class);\r\n        if (icon != null) {\r\n            setIcon(icon);\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Opens the internal editor on the specified file.\r\n     * @param file file to edit.\r\n     */\r\n    @Override\r\n    protected void performInternalAction(AbstractFile file) {\r\n        if (file.isSymlink() && file.getURL().getScheme().equals(FileProtocols.FILE)) {\r\n            new EditSymlinkDialog(mainFrame.getJFrame(), file).showDialog();\r\n        } else if (file.isDirectory()) {\r\n            FileSet fileSet = new FileSet();\r\n            fileSet.add(file);\r\n            new ChangePermissionsDialog(mainFrame, fileSet).showDialog();\r\n        } else {\r\n            EditorRegistrar.createEditorFrame(mainFrame, file, getIcon().getImage());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    protected Command getCustomCommand(AbstractFile file) {\r\n        return null;\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"InternalEdit\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.FILES;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new InternalEditAction(mainFrame, properties);\r\n        }\r\n\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/InternalViewAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.command.Command;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.FileTable;\r\nimport com.mucommander.ui.main.table.views.BaseFileTableModel;\r\nimport com.mucommander.ui.viewer.ViewerRegistrar;\r\n\r\nimport javax.swing.ImageIcon;\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Opens the current file in view mode.\r\n * @author Maxence Bernard, Nicolas Rinaudo\r\n */\r\npublic class InternalViewAction extends AbstractViewerAction {\r\n    // - Initialization ------------------------------------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------------------------------------\r\n    /**\r\n     * Creates a new instance of <code>InternalViewAction</code>.\r\n     * @param mainFrame  frame to which the action is attached.\r\n     * @param properties action's properties.\r\n     */\r\n    InternalViewAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        ImageIcon icon = getStandardIcon(ViewAction.class);\r\n        if (icon != null) {\r\n            setIcon(icon);\r\n        }\r\n    }\r\n\r\n\r\n\r\n    // - AbstractViewerAction implementation ---------------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------------------------------------\r\n    @Override\r\n    protected void performInternalAction(AbstractFile file) {\r\n        if (file.isDirectory()) {\r\n            FileTable activeTable = mainFrame.getActiveTable();\r\n            BaseFileTableModel fileTableModel = (BaseFileTableModel)activeTable.getModel();\r\n            fileTableModel.startDirectorySizeCalculation(activeTable, file);\r\n        } else {\r\n            ViewerRegistrar.createViewerFrame(mainFrame, file, getIcon().getImage());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    protected Command getCustomCommand(AbstractFile file) {\r\n        return null;\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"InternalView\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.FILES;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new InternalViewAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/InvertSelectionAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.FileTable;\r\nimport com.mucommander.ui.main.table.views.BaseFileTableModel;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action .\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class InvertSelectionAction extends TcAction {\r\n\r\n    private InvertSelectionAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        FileTable fileTable = mainFrame.getActiveTable();\r\n        BaseFileTableModel tableModel = fileTable.getFileTableModel();\r\n\r\n        // Starts at 1 if current folder is not root so that '..' is not marked\r\n        int nbFiles = fileTable.getFilesCount();\r\n        for (int i = 0; i < nbFiles; i++) {\r\n            AbstractFile file = tableModel.getFileAt(i);\r\n            if (!file.isDirectory()) {\r\n                tableModel.setFileMarked(i, !tableModel.isFileMarked(i));\r\n            }\r\n        }\r\n        fileTable.repaint();\r\n\r\n        // Notify registered listeners that currently marked files have changed on the FileTable\r\n        fileTable.fireMarkedFilesChangedEvent();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"InvertSelection\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.SELECTION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_MULTIPLY, 0);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new InvertSelectionAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/LeftArrowAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.FileTable;\nimport com.mucommander.ui.main.table.views.TableViewMode;\n\nimport javax.swing.*;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * @author Oleg Trifonov\n * Created on 27/10/16.\n */\npublic class LeftArrowAction extends TcAction {\n\n    public LeftArrowAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n        FileTable table = mainFrame.getActiveTable();\n        if (table == null) {\n            return;\n        }\n        if (table.getViewMode() != TableViewMode.FULL) {\n            return;\n        }\n        if (table.getSelectedFileIndex() == 0) {\n            FolderPanel panel = mainFrame.getActivePanel();\n            AbstractFile parent = panel.getCurrentFolder().getParent();\n            if (parent != null) {\n                panel.tryChangeCurrentFolder(parent, null, true);\n            }\n        } else {\n            table.selectFile(0);\n        }\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"LeftArrowAction\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.NAVIGATION;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0);\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new LeftArrowAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/LocalCopyAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileOperation;\r\nimport com.mucommander.commons.file.filter.AndFileFilter;\r\nimport com.mucommander.commons.file.filter.FileOperationFilter;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.dialog.file.LocalCopyDialog;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action invokes the 'Copy dialog' which allows to copy the currently selected/marked files to a specified destination.\r\n * The only difference with {@link com.mucommander.ui.action.impl.CopyAction} is that if a single file is selected,\r\n * the destination will be preset to the selected file's name so that it can easily be copied to a similar filename in\r\n * the current directory.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class LocalCopyAction extends SelectedFileAction {\r\n\r\n    private LocalCopyAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        setSelectedFileFilter(new AndFileFilter(\r\n            new FileOperationFilter(FileOperation.READ_FILE),\r\n            new FileOperationFilter(FileOperation.WRITE_FILE)\r\n        ));\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        FolderPanel activePanel = mainFrame.getActivePanel();\r\n        AbstractFile selectedFile = activePanel.getFileTable().getSelectedFile(false, true);\r\n\r\n        // Display local copy dialog only if a file other than '..' is currently selected\r\n        if (selectedFile != null) {\r\n            new LocalCopyDialog(mainFrame, new FileSet(activePanel.getCurrentFolder(), selectedFile)).showDialog();\r\n        }\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"LocalCopy\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_F5, KeyEvent.SHIFT_DOWN_MASK);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new LocalCopyAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/LocateSymlinkAction.java",
    "content": "package com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.filter.AttributeFileFilter;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.KeyStroke;\nimport java.util.Map;\n\n/**\n *\n * @author Oleg Trifonov\n */\npublic class LocateSymlinkAction extends SelectedFilesAction {\n\n    private LocateSymlinkAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n\n        setSelectedFileFilter(new AttributeFileFilter(AttributeFileFilter.FileAttribute.SYMLINK));\n    }\n\n    @Override\n    public void performAction(FileSet files) {\n        AbstractFile link = mainFrame.getActiveTable().getSelectedFile();\n        AbstractFile target = link.getCanonicalFile();\n        mainFrame.getInactivePanel().tryChangeCurrentFolder(target.getParent(), target, false);\n    }\n\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"LocateSymlink\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.FILES;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return null;\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new LocateSymlinkAction(mainFrame, properties);\n        }\n    }\n}\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/MarkAllAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.FileTable;\r\nimport com.mucommander.ui.main.table.views.BaseFileTableModel;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action marks all files in the current file table.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class MarkAllAction extends TcAction {\r\n    private final boolean mark;\r\n\r\n    MarkAllAction(MainFrame mainFrame, Map<String, Object> properties, boolean mark) {\r\n        super(mainFrame, properties);\r\n        this.mark = mark;\r\n    }\r\n\r\n    private MarkAllAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        this(mainFrame, properties, true);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        FileTable fileTable = mainFrame.getActiveTable();\r\n        BaseFileTableModel tableModel = fileTable.getFileTableModel();\r\n\r\n        int nbFiles = tableModel.getFilesCount();\r\n        for (int i=tableModel.getFirstMarkableIndex(); i < nbFiles; i++) {\r\n            tableModel.setFileMarked(i, mark);\r\n        }\r\n        fileTable.repaint();\r\n\r\n        // Notify registered listeners that currently marked files have changed on the FileTable\r\n        fileTable.fireMarkedFilesChangedEvent();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"MarkAll\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.SELECTION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_A, CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new MarkAllAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/MarkBackwardAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.FileTable;\n\nimport java.util.Map;\n\n/**\n * Marks/Unmarks a range of file/rows in the active {@link FileTable}, from the currently selected row to the\n * the previous {@link #getRowDecrement()} ones.\n * The row immediately before the last marked/unmarked row will become the currently selected row.\n *\n * @author Maxence Bernard\n */\npublic abstract class MarkBackwardAction extends TcAction {\n\n    MarkBackwardAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n\n    /////////////////////////////\n    // MuAction implementation //\n    /////////////////////////////\n\n    @Override\n    public void performAction() {\n        FileTable fileTable = mainFrame.getActiveTable();\n\n        int currentFileIndex = fileTable.getSelectedFileIndex();\n        int endIndex = Math.max(0, currentFileIndex-getRowDecrement()+1);\n//        int currentRow = fileTable.getSelectedRow();\n//        int endRow = Math.max(0, currentRow-getRowDecrement()+1);\n\n        fileTable.setRangeMarked(currentFileIndex, endIndex, !fileTable.getFileTableModel().isFileMarked(currentFileIndex));\n        fileTable.selectFile(Math.max(0, endIndex - 1));\n    }\n\n\n    //////////////////////\n    // Abstract methods //\n    //////////////////////\n\n    /**\n     * Returns the number of rows to mark/unmark.\n     *\n     * @return the number of rows to mark/unmark.\n     */\n    protected abstract int getRowDecrement();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/MarkEmptyFilesAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/trolcommander\n * Copyright (C) 2014-2018 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.filter.EmptyFileFilter;\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.FileTable;\nimport com.mucommander.ui.main.table.views.BaseFileTableModel;\n\nimport javax.swing.*;\nimport java.util.Map;\n\n\npublic class MarkEmptyFilesAction extends TcAction {\n\n    private MarkEmptyFilesAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n\n    @Override\n    public void performAction() {\n        FileTable fileTable = mainFrame.getActiveTable();\n        BaseFileTableModel tableModel = fileTable.getFileTableModel();\n\n        tableModel.setFilesMarked(new EmptyFileFilter(), true);\n\n        // Notify registered listeners that currently marked files have changed on this FileTable\n        fileTable.fireMarkedFilesChangedEvent();\n\n        fileTable.repaint();\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"MarkEmpty\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.SELECTION;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return null;\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new MarkEmptyFilesAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/MarkExtensionAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.filter.AbstractFilenameFilter;\r\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\r\nimport com.mucommander.commons.file.filter.FilenameFilter;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.FileTable;\r\nimport com.mucommander.ui.main.table.views.BaseFileTableModel;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Action that marks / unmarks all files with a specific extension.\r\n * <p>\r\n * Marking behaves as follows:\r\n * <ul>\r\n *   <li>\r\n *     If the current selection is marked, all files whose extension matches that of the current selection will\r\n *     be unmarked.\r\n *   </li>\r\n *   <li>\r\n *     If the current selection isn't marked, all files whose extension matches that of the current selection will\r\n *     be marked.\r\n *   </li>\r\n * </ul>\r\n *\r\n * <p>\r\n * By default, this action will mark all files whose extension match that of the current selection in a case-insensitive fashion.\r\n * It can, however, be configured:\r\n * <ul>\r\n *   <li>\r\n *     If the <code>extension</code> property is set, its value prepended by a <code>.</code> is always going to be used regardless of the\r\n *     current selection.\r\n *   </li>\r\n *   <li>\r\n *     If the <code>case_sensitive</code> property is set to <code>true</code>, extension matching will be done in a case sensitive fashion.\r\n *   </li>\r\n * </ul>\r\n *\r\n * @author Nicolas Rinaudo\r\n */\r\npublic class MarkExtensionAction extends TcAction {\r\n    // - Property names ------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /** Key that controls which extension should be matched. */\r\n    private static final String EXTENSION_PROPERTY_KEY      = \"extension\";\r\n    /** Key that controls whether extension matching should be done in a case sensitive fashion (defaults to false). */\r\n\r\n    private static final String CASE_SENSITIVE_PROPERTY_KEY = \"case_sensitive\";\r\n\r\n\r\n\r\n    // - Initialization ------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /**\r\n     * Creates a new <code>MarkExtensionAction</code> with the specified parameters.\r\n     * @param mainFrame  frame to which the action is attached.\r\n     * @param properties action's properties.\r\n     */\r\n    private MarkExtensionAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n\r\n\r\n    // - Properties retrieval ------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /**\r\n     * Returns the extension that was configured in the action's properties.\r\n     * @return the extension that was configured in the action's properties, <code>null</code> if none.\r\n     */\r\n    private String getExtension() {\r\n        Object o = getValue(EXTENSION_PROPERTY_KEY);\r\n\r\n        // If the key wasn't set, return null.\r\n        if (o == null) {\r\n            return null;\r\n        }\r\n\r\n        // If the value is a string, return it.\r\n        if (o instanceof String) {\r\n            return (String) o;\r\n        }\r\n\r\n        // Otherwise, return null.\r\n        return null;\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the action must compare string in a case-sensitive fashion.\r\n     * @return <code>true</code> if the action must compare string in a case-sensitive fashion, <code>false</code> otherwise.\r\n     */\r\n    private boolean isCaseSensitive() {\r\n        Object o = getValue(CASE_SENSITIVE_PROPERTY_KEY);\r\n\r\n        // If the action hasn't been configured, defaults to false.\r\n        if (o == null) {\r\n            return false;\r\n        }\r\n\r\n        // Returns the configured value if it's a string, false otherwise.\r\n        return o instanceof String && o.equals(\"true\");\r\n    }\r\n\r\n\r\n\r\n    // - Action code ---------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /**\r\n     * Creates a {@link com.mucommander.commons.file.filter.FilenameFilter} that should be applied to all current files.\r\n     * <p>\r\n     * If the action has been configured using the <code>file.extension</code> property, the returned IMAGE_FILTER\r\n     * will match that extension. Otherwise, the currently selected file's extension will be used. If it doesn't\r\n     * have one, the returned IMAGE_FILTER will match all files such that\r\n     * <code>file.getExtension() == null</code>.\r\n     *\r\n     * @param  file currently selected file.\r\n     * @return      the IMAGE_FILTER that should be applied by this action.\r\n     */\r\n    private FilenameFilter getFilter(AbstractFile file) {\r\n        String ext = getExtension();\r\n\r\n        // If no extension has been configured, analyse the current selection.\r\n        if (ext == null) {\r\n\r\n            // If there is no current selection, abort.\r\n            if (file == null) {\r\n                return null;\r\n            }\r\n\r\n            // If the current file doesn't have an extension, return a filename IMAGE_FILTER that\r\n            // match null extensions.\r\n            ext = file.getExtension();\r\n            if (ext == null) {\r\n                return new AbstractFilenameFilter() {\r\n                    public boolean accept(String name) {\r\n                        return AbstractFile.getExtension(name) == null;\r\n                    }\r\n                };\r\n            }\r\n        }\r\n\r\n        // At this point, ext contains the extension that should be matched.\r\n        ExtensionFilenameFilter filter = new ExtensionFilenameFilter(\".\" + ext);\r\n\r\n        // Initializes the IMAGE_FILTER's case-sensitive depending on the action's properties.\r\n        filter.setCaseSensitive(isCaseSensitive());\r\n\r\n        return filter;\r\n    }\r\n\r\n    /**\r\n     * Marks all files whose extension matches the current selection.\r\n     */\r\n    @Override\r\n    public void performAction() {\r\n        // Initialization. Aborts if there is no selected file.\r\n        FileTable fileTable  = mainFrame.getActiveTable();\r\n        FilenameFilter filter = getFilter(fileTable.getSelectedFile(false, true));\r\n        if (filter == null) {\r\n            return;\r\n        }\r\n        BaseFileTableModel tableModel = fileTable.getFileTableModel();\r\n        int filesCount = tableModel.getFilesCount();\r\n        boolean mark = !tableModel.isFileMarked(fileTable.getSelectedFileIndex());\r\n\r\n        // Goes through all files in the active table, marking all that match 'IMAGE_FILTER'.\r\n        for (int i = tableModel.getFirstMarkableIndex(); i < filesCount; i++) {\r\n            if (filter.accept(tableModel.getCachedFileAt(i))) {\r\n                tableModel.setFileMarked(i, mark);\r\n            }\r\n        }\r\n        fileTable.repaint();\r\n\r\n        // Notify registered listeners that currently marked files have changed on the FileTable\r\n        fileTable.fireMarkedFilesChangedEvent();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"MarkExtension\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.SELECTION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_ADD, KeyEvent.SHIFT_DOWN_MASK);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n            return new MarkExtensionAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/MarkForwardAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.FileTable;\n\nimport java.util.Map;\n\n/**\n * Marks/Unmarks a range of file/rows in the active {@link FileTable}, from the currently selected row to the\n * the next {@link #getRowIncrement()} ones.\n * The row immediately after the last marked/unmarked row will become the currently selected row.\n *\n * @author Maxence Bernard\n */\npublic abstract class MarkForwardAction extends TcAction {\n\n    MarkForwardAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n        FileTable fileTable = mainFrame.getActiveTable();\n\n        int currentFileIndex = fileTable.getSelectedFileIndex();\n        int lastIndex = fileTable.getFilesCount()-1;\n//        int currentRow = fileTable.getSelectedRow();\n//        int lastRow = fileTable.getRowCount()-1;\n        int endIndex = Math.min(lastIndex, currentFileIndex + getRowIncrement() - 1);\n\n        fileTable.setRangeMarked(currentFileIndex, endIndex, !fileTable.getFileTableModel().isFileMarked(currentFileIndex));\n        fileTable.selectFile(Math.min(lastIndex, endIndex + 1));\n    }\n\n    /**\n     * Returns the number of rows to mark/unmark.\n     *\n     * @return the number of rows to mark/unmark.\n     */\n    protected abstract int getRowIncrement();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/MarkGroupAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.file.FileSelectionDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action brings up the 'File selection' dialog which allows to mark a group of files that match a specified expression.\r\n *\r\n * @author Maxence Bernard\r\n */\r\n@InvokesDialog\r\npublic class MarkGroupAction extends TcAction {\r\n\r\n    private MarkGroupAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        new FileSelectionDialog(mainFrame, true).showDialog();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"MarkGroup\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.SELECTION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0);\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new MarkGroupAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/MarkNextBlockAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.FileTable;\n\nimport javax.swing.*;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * Marks/unmarks the previous block's rows in the current {@link FileTable}, starting with the\n * current row, and moves the selected row right before the last marked/unmarked row.\n *\n * @author Maxence Bernard\n */\npublic class MarkNextBlockAction extends MarkForwardAction {\n\n    /** Number of file/rows a block represents */\n    // TODO: make this value configurable\n    private static final int BLOCK_SIZE = 5;\n\n    private MarkNextBlockAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    protected int getRowIncrement() {\n        return BLOCK_SIZE;\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n    \tpublic static final String ACTION_ID = \"MarkNextBlock\";\n\n\t\tpublic String getId() {\n\t\t    return ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t    return ActionCategory.SELECTION;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t    return null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, CTRL_OR_META_DOWN_MASK);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new MarkNextBlockAction(mainFrame, properties);\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/MarkNextPageAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.FileTable;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Marks/unmarks the next page's rows in the current {@link FileTable}, starting with the\r\n * current row, and moves the selected row right after the last marked/unmarked row.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class MarkNextPageAction extends MarkForwardAction {\r\n\r\n    private MarkNextPageAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    protected int getRowIncrement() {\r\n        // Note: the page row increment varies with the file table's height\r\n        return mainFrame.getActiveTable().getPageRowIncrement()+1;\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"MarkNextPage\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.SELECTION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, KeyEvent.SHIFT_DOWN_MASK);\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new MarkNextPageAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/MarkNextRowAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * Marks/unmarks the current row and moves the selection to the next row.\n *\n * @author Maxence Bernard\n */\npublic class MarkNextRowAction extends MarkForwardAction {\n\n    private MarkNextRowAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    protected int getRowIncrement() {\n        return 1;\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n    \tpublic static final String ACTION_ID = \"MarkNextRow\";\n\n\t\tpublic String getId() {\n\t\t\treturn ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t\treturn ActionCategory.SELECTION;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t\treturn null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.SHIFT_DOWN_MASK);\n\t\t}\n\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n\t\t\treturn new MarkNextRowAction(mainFrame, properties);\n\t\t}\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/MarkPreviousBlockAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.FileTable;\n\nimport javax.swing.*;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * Marks/unmarks the previous block's rows in the current {@link FileTable}, starting with the\n * current row, and moves the selected row right before the last marked/unmarked row.\n *\n * @author Maxence Bernard\n */\npublic class MarkPreviousBlockAction extends MarkBackwardAction {\n\n    /** Number of file/rows a block represents */\n    // TODO: make this value configurable\n    private static final int BLOCK_SIZE = 5;\n\n    private MarkPreviousBlockAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    protected int getRowDecrement() {\n        return BLOCK_SIZE;\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n    \tpublic static final String ACTION_ID = \"MarkPreviousBlock\";\n\n\t\tpublic String getId() {\n\t\t    return ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t    return ActionCategory.SELECTION;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t    return null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.SHIFT_DOWN_MASK | CTRL_OR_META_DOWN_MASK);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new MarkPreviousBlockAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/MarkPreviousPageAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.FileTable;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Marks/unmarks the previous page's rows in the current {@link FileTable}, starting with the\r\n * current row, and moves the selected row right before the last marked/unmarked row.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class MarkPreviousPageAction extends MarkBackwardAction {\r\n\r\n    private MarkPreviousPageAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    protected int getRowDecrement() {\r\n        // Note: the page row increment varies with the file table's height\r\n        return mainFrame.getActiveTable().getPageRowIncrement()+1;\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"MarkPreviousPage\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.SELECTION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, KeyEvent.SHIFT_DOWN_MASK);\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new MarkPreviousPageAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/MarkPreviousRowAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * Marks/unmarks the current row and moves the selection to the previous row.\n *\n * @author Maxence Bernard\n */\npublic class MarkPreviousRowAction extends MarkBackwardAction {\n\n    private MarkPreviousRowAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    protected int getRowDecrement() {\n        return 1;\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n    \tpublic static final String ACTION_ID = \"MarkPreviousRow\";\n\n\t\tpublic String getId() {\n\t\t\treturn ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t\treturn ActionCategory.SELECTION;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t\treturn null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.SHIFT_DOWN_MASK);\n\t\t}\n\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n\t\t\treturn new MarkPreviousRowAction(mainFrame, properties);\n\t\t}\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/MarkSelectedFileAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.conf.TcConfigurations;\r\nimport com.mucommander.conf.TcPreference;\r\nimport com.mucommander.conf.TcPreferences;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.FileTable;\r\nimport com.mucommander.ui.main.table.views.full.FileTableModel;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Marks or unmarks the current selected file (current row) and advance current row to the next one,\r\n * with the following exceptions:\r\n * <ul>\r\n * <li>if quick search is active, this method does nothing\r\n * <li>if '..' file is selected, file is not marked but current row is still advanced to the next one\r\n * <li>if the {@link com.mucommander.ui.action.impl.MarkSelectedFileAction} key event is repeated and the last file has already\r\n * been marked/unmarked since the key was last released, the file is not marked in order to avoid\r\n * marked/unmarked flaps when the mark key is kept pressed.\r\n * </ul>\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class MarkSelectedFileAction extends TcAction {\r\n\r\n    private MarkSelectedFileAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n\r\n    @Override\r\n    public void performAction() {\r\n\t\tif (TcConfigurations.getPreferences().getVariable(TcPreference.CALCULATE_FOLDER_SIZE_ON_MARK, TcPreferences.DEFAULT_CALCULATE_FOLDER_SIZE_ON_MARK))\r\n\t\t{\r\n\t\t\tcalculateFolderSize();\r\n\t\t}\r\n        mainFrame.getActiveTable().markSelectedFile();\r\n    }\r\n\r\n\tprivate void calculateFolderSize() {\r\n\t\tFileTable table = mainFrame.getActiveTable();\r\n\t\tAbstractFile file = table.getSelectedFile();\r\n\r\n\t\tif (file != null && file.isDirectory()) {\r\n\t\t\tFileTableModel model = (FileTableModel) mainFrame.getActiveTable().getModel();\r\n\t\t\tmodel.startDirectorySizeCalculation(table, table.getSelectedFile());\r\n\t\t}\r\n\t}\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"MarkSelectedFile\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.SELECTION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, 0);\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0);\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new MarkSelectedFileAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/MarkToFirstRowAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Marks/unmarks files in the active FileTable, from the currently selected row to the first row (inclusive).\r\n * The first row will also become the currently selected row.\r\n *\r\n * <p>The currently selected row's marked state determines whether the rows will be marked or unmarked : if the selected\r\n * row is marked, the rows will be unmarked and vice versa.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class MarkToFirstRowAction extends MarkBackwardAction {\r\n\r\n    private MarkToFirstRowAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    protected int getRowDecrement() {\r\n        return mainFrame.getActiveTable().getSelectedRow();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"MarkToFirstRow\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.SELECTION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_HOME, KeyEvent.SHIFT_DOWN_MASK);\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new MarkToFirstRowAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/MarkToLastRowAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.FileTable;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Marks/unmarks files in the active FileTable, from the currently selected row to the last row (inclusive).\r\n * The last row will also become the currently selected row.\r\n *\r\n * <p>The currently selected row's marked state determines whether the rows will be marked or unmarked : if the selected\r\n * row is marked, the rows will be unmarked and vice-versa.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class MarkToLastRowAction extends MarkForwardAction {\r\n\r\n    private MarkToLastRowAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    protected int getRowIncrement() {\r\n        FileTable activeTable = mainFrame.getActiveTable();\r\n\r\n        return activeTable.getRowCount()-activeTable.getSelectedRow();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"MarkToLastRow\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.SELECTION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_END, KeyEvent.SHIFT_DOWN_MASK);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new MarkToLastRowAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/MaximizeWindowAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.JFrame;\nimport javax.swing.KeyStroke;\nimport java.util.Map;\n\n/**\n * Maximizes the {@link MainFrame} this action is associated with.\n *\n * @author Maxence Bernard\n * @see com.mucommander.ui.action.impl.MinimizeWindowAction\n */\npublic class MaximizeWindowAction extends TcAction {\n\n    private MaximizeWindowAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n        mainFrame.getJFrame().setExtendedState(JFrame.MAXIMIZED_BOTH);\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n    \tpublic static final String ACTION_ID = \"MaximizeWindow\";\n    \t\n\t\tpublic String getId() {\n\t\t    return ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t    return ActionCategory.WINDOW;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t    return null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n\t\t    return null;\n\t\t}\n\n        @Override\n        public String getLabel() {\n            // Use a special label for Mac OS X, if it exists, use the standard action label otherwise\n            String macLabelKey = ActionProperties.getActionLabelKey(ACTION_ID)+\".mac_os_x\";\n            if (OsFamily.MAC_OS_X.isCurrent() && Translator.hasValue(macLabelKey, false)) {\n                return Translator.get(macLabelKey);\n            }\n\n            return super.getLabel();\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new MaximizeWindowAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/MinimizeWindowAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.JFrame;\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * Minimizes the {@link MainFrame} this action is associated with.\n *\n * @author Maxence Bernard\n * @see com.mucommander.ui.action.impl.MaximizeWindowAction\n */\npublic class MinimizeWindowAction extends TcAction {\n\n    private MinimizeWindowAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n        mainFrame.getJFrame().setExtendedState(JFrame.ICONIFIED);\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n    \tpublic static final String ACTION_ID = \"MinimizeWindow\";\n    \t\n\t\tpublic String getId() {\n\t\t    return ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t    return ActionCategory.WINDOW;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t    return null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_M, KeyEvent.META_DOWN_MASK);\n\t\t}\n\n        @Override\n        public String getLabel() {\n            // Use a special label for Mac OS X, if it exists, use the standard action label otherwise\n            String macLabelKey = ActionProperties.getActionLabelKey(ACTION_ID)+\".mac_os_x\";\n            if (OsFamily.MAC_OS_X.isCurrent() && Translator.hasValue(macLabelKey, false)) {\n                return Translator.get(macLabelKey);\n            }\n            return super.getLabel();\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new MinimizeWindowAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/MkdirAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileOperation;\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.dialog.file.MakeDirectoryFileDialog;\nimport com.mucommander.ui.main.MainFrame;\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * This action brings up the 'Make directory' dialog which allows to create a new directory in the currently active\n * folder.\n *\n * @author Maxence Bernard\n */\npublic class MkdirAction extends ParentFolderAction {\n\n\tprivate MkdirAction(MainFrame mainFrame, Map<String, Object> properties) {\n\t\tsuper(mainFrame, properties);\n\t}\n\n\t@Override\n\tprotected void toggleEnabledState() {\n\t\tAbstractFile firstFile = mainFrame.getActiveTable().getFileTableModel().getFileAt(0);\n\n\t\t// If there is no file at all, do not rely on the action being supported by the current folder as this\n\t\t// would be incorrect for some filesystems which do not support operations consistently across the\n\t\t// filesystem (e.g. S3). In that case, err on the safe side and enable the action, even if the operation\n\t\t// end up not being supported.\n\t\tsetEnabled(firstFile == null || firstFile.isFileOperationSupported(FileOperation.CREATE_DIRECTORY));\n\t}\n\n\t@Override\n\tpublic void performAction() {\n\t\tnew MakeDirectoryFileDialog(mainFrame, false).showDialog();\n\t}\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n\tpublic static final class Descriptor extends AbstractActionDescriptor {\n\t\tpublic static final String ACTION_ID = \"Mkdir\";\n\n\t\tpublic String getId() {\n\t\t\treturn ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t\treturn ActionCategory.FILES;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t\treturn null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_F7, 0);\n\t\t}\n\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String, Object> properties) {\n\t\t\treturn new MkdirAction(mainFrame, properties);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/MkfileAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileOperation;\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.dialog.file.MakeDirectoryFileDialog;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * This action brings up the 'Make file' dialog which allows to create a new file in the currently active folder.\n *\n * @author Maxence Bernard\n */\npublic class MkfileAction extends ParentFolderAction {\n\n    private MkfileAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    protected void toggleEnabledState() {\n        AbstractFile firstFile = mainFrame.getActiveTable().getFileTableModel().getFileAt(0);\n\n        // If there is no file at all, do not rely on the action being supported by the current folder as this\n        // would be incorrect for some filesystems which do not support operations consistently across the\n        // filesystem (e.g. S3). In that case, err on the safe side and enable the action, even if the operation\n        // end up not being supported.\n        setEnabled(firstFile == null || firstFile.isFileOperationSupported(FileOperation.WRITE_FILE));\n    }\n\n    @Override\n    public void performAction() {\n        new MakeDirectoryFileDialog(mainFrame, true).showDialog();\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"Mkfile\";\n\n\t\tpublic String getId() {\n\t\t    return ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t    return ActionCategory.FILES;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t    return null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_F7, KeyEvent.SHIFT_DOWN_MASK);\n\t\t}\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new MkfileAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/MoveAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.FileOperation;\r\nimport com.mucommander.commons.file.filter.AndFileFilter;\r\nimport com.mucommander.commons.file.filter.FileOperationFilter;\r\nimport com.mucommander.commons.file.filter.OrFileFilter;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.file.MoveDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action invokes the 'Move dialog' which allows to move the currently selected/marked files\r\n * in the current folder to a specified destination.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class MoveAction extends SelectedFilesAction {\r\n\r\n    private MoveAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        setSelectedFileFilter(new OrFileFilter(\r\n            new FileOperationFilter(FileOperation.RENAME),\r\n            new AndFileFilter(\r\n                new FileOperationFilter(FileOperation.READ_FILE),\r\n                new FileOperationFilter(FileOperation.WRITE_FILE)\r\n            )\r\n        ));\r\n    }\r\n\r\n    @Override\r\n    public void performAction(FileSet files) {\r\n        new MoveDialog(mainFrame, files).showDialog();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"Move\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.FILES;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_F6, 0);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new MoveAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/MoveTabToOtherPanelAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.tabs.FileTableTab;\nimport com.mucommander.ui.main.tabs.FileTableTabs;\n\nimport javax.swing.KeyStroke;\nimport java.util.Map;\n\n/**\n * Close current tab and open the same tab at the other FolderPanel\n * \n * @author Arik Hadas\n */\npublic class MoveTabToOtherPanelAction extends ActiveTabAction {\n\n    private MoveTabToOtherPanelAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n    \n    /**\n     * Enables or disables this action based on the currently active folder's\n     * current tab is not locked and is not the only tab in the panel,\n     * this action will be enabled, if not it will be disabled.\n     */\n    @Override\n    protected void toggleEnabledState() {\n        FileTableTabs tabs = mainFrame.getActivePanel().getTabs();\n        setEnabled(!tabs.getCurrentTab().isLocked() && tabs.getTabsCount() > 1);\n    }\n\n    @Override\n    public void performAction() {\n    \tFileTableTab tab = mainFrame.getActivePanel().getTabs().closeCurrentTab();\n    \tmainFrame.getInactivePanel().getTabs().add(tab);\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n    \tpublic static final String ACTION_ID = \"MoveTabToOtherPanel\";\n    \t\n\t\tpublic String getId() {\n\t\t    return ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t    return ActionCategory.TAB;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t    return null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n\t\t    return null;\n\t\t}\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new MoveTabToOtherPanelAction(mainFrame, properties);\n        }\n    }\n}\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/MuteProxyAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.ActionEvent;\r\n\r\n/**\r\n * MuteProxyAction is an implementation of {@link ProxyAction} where {@link #actionPerformed(java.awt.event.ActionEvent)}\r\n * does nothing. \r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class MuteProxyAction extends ProxyAction {\r\n\r\n    public MuteProxyAction(Action proxiedAction) {\r\n        super(proxiedAction);\r\n    }\r\n\r\n    /**\r\n     * This method is a No-op, i.e. does absolutely nothing. \r\n     */\r\n    public void actionPerformed(ActionEvent actionEvent) {\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/NewWindowAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.WindowManager;\r\nimport com.mucommander.ui.main.frame.ClonedMainFrameBuilder;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action creates a new muCommander window.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class NewWindowAction extends TcAction {\r\n\r\n    private NewWindowAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        // This action must be performed in a separate thread as it will otherwise lock the AWT event thread\r\n        setPerformActionInSeparateThread(true);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        WindowManager.createNewMainFrame(new ClonedMainFrameBuilder());\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"NewWindow\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.WINDOW;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_N, CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new NewWindowAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/NextTabAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.*;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * Change the selected tab to the tab which is located to the right of the current displayed tab.\n * If the current displayed tab is the rightmost tab, the leftmost tab will be displayed.\n * \n * @author Arik Hadas\n */\npublic class NextTabAction extends TcAction {\n\t\n\tprivate NextTabAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n    \tmainFrame.getActivePanel().getTabs().nextTab();\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n    \tpublic static final String ACTION_ID = \"NextTab\";\n    \t\n\t\tpublic String getId() {\n\t\t    return ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t    return ActionCategory.TAB;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, CTRL_OR_META_DOWN_MASK);\n        }\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, CTRL_OR_META_DOWN_MASK);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new NextTabAction(mainFrame, properties);\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/OpenAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.bookmark.Bookmark;\r\nimport com.mucommander.bookmark.BookmarkManager;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileURL;\r\nimport com.mucommander.conf.TcConfigurations;\r\nimport com.mucommander.conf.TcPreference;\r\nimport com.mucommander.conf.TcPreferences;\r\nimport com.mucommander.desktop.DesktopManager;\r\nimport com.mucommander.job.TempExecJob;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.InformationDialog;\r\nimport com.mucommander.ui.dialog.file.ProgressDialog;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.quicklist.RecentExecutedFilesQL;\r\nimport com.mucommander.ui.main.tabs.FileTableTabs;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.io.IOException;\r\nimport java.net.MalformedURLException;\r\nimport java.util.HashSet;\r\nimport java.util.Map;\r\nimport java.util.Set;\r\n\r\n/**\r\n * This action 'opens' the currently selected file or folder in the active FileTable.\r\n * This means different things depending on the kind of file that is currently selected:\r\n * <ul>\r\n * <li>For browsable files (directory, archive...): shows file contents\r\n * <li>For local file that are not an archive or archive entry: executes file with native file associations\r\n * <li>For any other file type, remote or local: copies file to a temporary local file and executes it with native file associations\r\n * </ul>\r\n *\r\n * @author Maxence Bernard, Nicolas Rinaudo\r\n */\r\npublic class OpenAction extends TcAction {\r\n    /**\r\n     * Creates a new <code>OpenAction</code> with the specified parameters.\r\n     * @param mainFrame  frame to which the action is attached.\r\n     * @param properties action's properties.\r\n     */\r\n\tOpenAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    /**\r\n     * Opens the specified file in the specified folder panel.\r\n     * <p>\r\n     * <code>file</code> will be opened using the following rules:\r\n     * <ul>\r\n     *   <li>\r\n     *     If <code>file</code> is {@link com.mucommander.commons.file.AbstractFile#isBrowsable() browsable},\r\n     *     it will be opened in <code>destination</code>.\r\n     *   </li>\r\n     *   <li>\r\n     *     If <code>file</code> is local, it will be opened using its native associations.\r\n     *   </li>\r\n     *   <li>\r\n     *     If <code>file</code> is remote, it will first be copied in a temporary local file and\r\n     *     then opened using its native association.\r\n     *   </li>\r\n     * </ul>\r\n\t *\r\n     * @param file        file to open.\r\n     * @param destination if <code>file</code> is browsable, folder panel in which to open the file.\r\n     */\r\n    protected void open(final AbstractFile file, FolderPanel destination) {\r\n    \tAbstractFile resolvedFile = resolveIfSymlink(file);\r\n    \tif (resolvedFile == null) {\r\n    \t    return;\r\n        }\r\n\r\n        if (resolvedFile.isBrowsable()) {\t\t\t\t// Opens browsable files in the destination FolderPanel.\r\n\t\t\tresolvedFile = cdFollowsSymlinks() ? resolvedFile : file;\r\n    \t\topenBrowsable(resolvedFile, destination);\r\n        } else if (resolvedFile.isLocalFile()) {\t\t// Opens local files using their native associations.\r\n\t\t\topenLocalFile(resolvedFile);\r\n\t\t} else {\t\t\t// Copies non-local file in a temporary local file and opens them using their native association.\r\n            ProgressDialog progressDialog = new ProgressDialog(mainFrame, Translator.get(\"copy_dialog.copying\"));\r\n            TempExecJob job = new TempExecJob(progressDialog, mainFrame, resolvedFile);\r\n            progressDialog.start(job);\r\n        }\r\n    }\r\n\r\n\tprivate void openLocalFile(AbstractFile file) {\r\n\t\ttry {\r\n\t\t\tDesktopManager.open(file);\r\n\t\t\tRecentExecutedFilesQL.addFile(file);\r\n\t\t} catch (IOException e) {\r\n\t\t\tInformationDialog.showErrorDialog(mainFrame.getJFrame());\r\n\t\t}\r\n\t}\r\n\r\n\tAbstractFile resolveIfSymlink(AbstractFile file) {\r\n        if (!file.isSymlink()) {\r\n            return file;\r\n        }\r\n        AbstractFile resolvedFile = resolveSymlink(file);\r\n\r\n        if (resolvedFile == null) {\r\n            InformationDialog.showErrorDialog(mainFrame.getJFrame(), Translator.get(\"cannot_open_cyclic_symlink\"));\r\n            return null;\r\n        }\r\n        return resolvedFile;\r\n    }\r\n\r\n    private void openBrowsable(AbstractFile file, FolderPanel destination) {\r\n\t\tif (BookmarkManager.isBookmark(file)) {\r\n\t\t\topenBookmark(file, destination);\r\n\t\t} else {\r\n\t\t\tFileTableTabs tabs = destination.getTabs();\r\n\t\t\tif (tabs.getCurrentTab().isLocked()) {\r\n\t\t\t\ttabs.add(file);\r\n\t\t\t} else {\r\n\t\t\t\tdestination.tryChangeCurrentFolder(file);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void openBookmark(AbstractFile file, FolderPanel destination) {\r\n\t\tBookmark bookmark = BookmarkManager.getBookmark(file.getName());\r\n\t\tif (bookmark == null) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tString bookmarkLocation = bookmark.getLocation();\r\n\t\ttry {\r\n\t\t\tFileURL bookmarkURL = FileURL.getFileURL(bookmarkLocation);\r\n\t\t\tFileTableTabs tabs = destination.getTabs();\r\n\t\t\tif (tabs.getCurrentTab().isLocked()) {\r\n\t\t\t\ttabs.add(bookmarkURL);\r\n\t\t\t} else {\r\n\t\t\t\tdestination.tryChangeCurrentFolder(bookmarkURL);\r\n\t\t\t}\r\n\t\t} catch (MalformedURLException ignore) {}\r\n\t}\r\n\r\n\tprivate static boolean cdFollowsSymlinks() {\r\n\t\treturn TcConfigurations.getPreferences().getVariable(TcPreference.CD_FOLLOWS_SYMLINKS, TcPreferences.DEFAULT_CD_FOLLOWS_SYMLINKS);\r\n\t}\r\n\r\n    private AbstractFile resolveSymlink(AbstractFile symlink) {\r\n    \treturn resolveSymlink(symlink, new HashSet<>());\r\n    }\r\n\r\n    private AbstractFile resolveSymlink(AbstractFile file, Set<AbstractFile> visitedFiles) {\r\n    \tif (visitedFiles.contains(file)) {\r\n\t\t\treturn null;\r\n\t\t}\r\n\t\tvisitedFiles.add(file);\r\n    \treturn file.isSymlink() ? resolveSymlink(file.getCanonicalFile(), visitedFiles) : file;\r\n    }\r\n\r\n    /**\r\n     * Opens the currently selected file in the active folder panel.\r\n     */\r\n    @Override\r\n    public void performAction() {\r\n\t\t// Retrieves the currently selected file, aborts if none.\r\n\t\t// Note: a CachedFile instance is retrieved to avoid blocking the event thread.\r\n\t\tAbstractFile file  = mainFrame.getActiveTable().getSelectedFile(true, true);\r\n        if (file != null) {\r\n            open(file, mainFrame.getActivePanel());\r\n\t\t}\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"Open\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new OpenAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/OpenAsAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2018 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.*;\nimport java.io.IOException;\nimport java.util.Map;\n\n/**\n * Open a file as if it has the specified file extension.\n */\npublic class OpenAsAction extends OpenAction {\n\n    private String extension;\n\n    private OpenAsAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n        extension = (String) properties.get(\"extension\");\n    }\n\n    /**\n     * Opens the currently selected file in the active folder panel.\n     */\n    @Override\n    public void performAction() {\n        // Retrieves the currently selected file,\n        // Note: a CachedFile instance is retrieved to avoid blocking the event thread.\n        AbstractFile file = mainFrame.getActiveTable().getSelectedFile(true, true);\n        if (file == null) {\n            return;\n        }\n        AbstractFile resolvedFile = resolveIfSymlink(file);\n        try {\n            resolvedFile = FileFactory.wrapArchive(resolvedFile, extension);\n            open(resolvedFile, mainFrame.getActivePanel());\n        } catch (IOException ignore) {}\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"OpenAs\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.NAVIGATION;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return null;\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new OpenAsAction(mainFrame, properties);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/OpenInBothPanelsAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.FileTable;\r\nimport com.mucommander.ui.main.table.views.BaseFileTableModel;\r\nimport org.jetbrains.annotations.Nullable;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Opens the currently selected file and its equivalent in the inactive folder panel if it exists.\r\n * <p>\r\n * This action will analyse the current selection and, if applicable, any file from the inactive\r\n * panel that bears the same name and:\r\n * <ul>\r\n *   <li>\r\n *     If both the selection and its inactive equivalent are browsable, both will be explored in their\r\n *     respective panels.\r\n *   </li>\r\n *   <li>\r\n *     If both are non-browsable, both will be opened as defined in {@link OpenAction}.\r\n *   </li>\r\n *   <li>\r\n *     If one is browsable an not the other one, only the current selection will be opened.\r\n *   </li>\r\n * </ul>\r\n *\r\n * <p>\r\n * Note that this action's behavior is strictly equivalent to that of {@link OpenAction} in the\r\n * active panel. Differences will only occur in the inactive panel, and then again only when possible.\r\n *\r\n * <p>\r\n * This action opens both files synchronously: it will wait for the active panel file to have been\r\n * opened before opening the inactive panel one.\r\n *\r\n * @author Nicolas Rinaudo\r\n */\r\npublic class OpenInBothPanelsAction extends SelectedFileAction {\r\n    /**\r\n     * Creates a new <code>OpenInBothPanelsAction</code> with the specified parameters.\r\n     * @param mainFrame  frame to which the action is attached.\r\n     * @param properties action's properties.\r\n     */\r\n    private OpenInBothPanelsAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        // Perform this action in a separate thread, to avoid locking the event thread\r\n        setPerformActionInSeparateThread(true);\r\n    }\r\n\r\n    @Override\r\n    public void activePanelChanged(FolderPanel folderPanel) {\r\n        super.activePanelChanged(folderPanel);\r\n        \r\n        if (mainFrame.getInactivePanel().getTabs().getCurrentTab().isLocked()) {\r\n            setEnabled(false);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * This method is overridden to enable this action when the parent folder is selected. \r\n     */\r\n    @Override\r\n    protected boolean getFileTableCondition(FileTable fileTable) {\r\n        AbstractFile selectedFile = fileTable.getSelectedFile(true, true);\r\n        return selectedFile != null && selectedFile.isBrowsable();\r\n    }\r\n\r\n\r\n    // - Action code ---------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /**\r\n     * Opens the current selection and its inactive equivalent.\r\n     */\r\n    @Override\r\n    public void performAction() {\r\n        // Retrieves the current selection, aborts if none (should not normally happen).\r\n        AbstractFile selectedFile = mainFrame.getActiveTable().getSelectedFile(true, true);\r\n        if (selectedFile == null || !selectedFile.isBrowsable()) {\r\n            return;\r\n        }\r\n\r\n        AbstractFile otherFile = findOtherFile(selectedFile);\r\n\r\n        // Opens 'file' in the active panel.\r\n        Thread openThread = mainFrame.getActivePanel().tryChangeCurrentFolder(selectedFile);\r\n\r\n        // Opens 'otherFile' (if any) in the inactive panel.\r\n        if (otherFile != null) {\r\n            // Waits for the previous folder change to be finished.\r\n            if (openThread != null) {\r\n                while (openThread.isAlive()) {\r\n                    try {\r\n                        openThread.join();\r\n                    } catch(InterruptedException ignore) {}\r\n                }\r\n            }\r\n            mainFrame.getInactivePanel().tryChangeCurrentFolder(otherFile);\r\n        }\r\n    }\r\n\r\n    @Nullable\r\n    private AbstractFile findOtherFile(AbstractFile selectedFile) {\r\n        try {\r\n            BaseFileTableModel otherTableModel = mainFrame.getInactiveTable().getFileTableModel();\r\n\r\n            if (mainFrame.getActiveTable().isParentFolderSelected()) {\r\n                return otherTableModel.getParentFolder();\r\n            } else {\r\n                // Look for a file in the other table with the same name as the selected one (case insensitive)\r\n                int fileCount = otherTableModel.getFileCount();\r\n                String targetFilename = selectedFile.getName();\r\n                AbstractFile otherFile = null;\r\n                for (int i = 0; i < fileCount; i++) {\r\n                    otherFile = otherTableModel.getCachedFileAt(i);\r\n                    if (otherFile.getName().equalsIgnoreCase(targetFilename)) {\r\n                        break;\r\n                    }\r\n                    if (i == fileCount-1) {\r\n                        otherFile = null;\r\n                    }\r\n                }\r\n                return otherFile;\r\n            }\r\n        } catch (Exception e) {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    @Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"OpenInBothPanels\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_O, KeyEvent.SHIFT_DOWN_MASK | CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new OpenInBothPanelsAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/OpenInNewTabAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport java.awt.event.KeyEvent;\nimport java.net.MalformedURLException;\nimport java.util.Map;\n\nimport javax.swing.KeyStroke;\n\nimport com.mucommander.bookmark.BookmarkManager;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.FileTable;\n\n/**\n * Opens browsable file in a new tab.\n * <p>\n * This action is only enabled if the current selection is browsable as defined by\n * {@link com.mucommander.commons.file.AbstractFile#isBrowsable()}.\n *\n * @author Arik Hadas\n */\npublic class OpenInNewTabAction extends SelectedFileAction {\n\n\tprivate OpenInNewTabAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\t\n\t/**\n     * This method is overridden to enable this action when the parent folder is selected.\n     */\n    @Override\n    protected boolean getFileTableCondition(FileTable fileTable) {\n        AbstractFile selectedFile = fileTable.getSelectedFile(true, true);\n\n        return selectedFile != null && selectedFile.isBrowsable();\n    }\n    \n\t@Override\n\tpublic void performAction() {\n\t\tAbstractFile file = mainFrame.getActiveTable().getSelectedFile(true, true);\n\n        // Retrieves the currently selected file, aborts if none (should not normally happen).\n        if (file == null || !file.isBrowsable()) {\n            return;\n        }\n\n        FileURL fileURL = file.getURL();\n        if (BookmarkManager.isBookmark(fileURL)) {\n            String bookmarkLocation = BookmarkManager.getBookmark(file.getName()).getLocation();\n            try {\n                fileURL = FileURL.getFileURL(bookmarkLocation);\n            } catch (MalformedURLException e) {\n                return;\n            }\n        }\n\n        // Opens the currently selected file in a new tab\n        mainFrame.getActivePanel().getTabs().add(fileURL);\n\t}\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n    \tpublic static final String ACTION_ID = \"OpenInNewTab\";\n    \t\n\t\tpublic String getId() {\n\t\t    return ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t    return ActionCategory.NAVIGATION;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t    return null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, CTRL_OR_META_DOWN_MASK);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new OpenInNewTabAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/OpenInOtherPanelAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.FileTable;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Opens browsable files in the inactive panel.\r\n * <p>\r\n * This action is only enabled if the current selection is browsable as defined by\r\n * {@link com.mucommander.commons.file.AbstractFile#isBrowsable()}.\r\n *\r\n * @author Nicolas Rinaudo\r\n */\r\npublic class OpenInOtherPanelAction extends SelectedFileAction {\r\n    /**\r\n     * Creates a new <code>OpenInOtherPanelAction</code> with the specified parameters.\r\n     * @param mainFrame  frame to which the action is attached.\r\n     * @param properties action's properties.\r\n     */\r\n    private OpenInOtherPanelAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void activePanelChanged(FolderPanel folderPanel) {\r\n        super.activePanelChanged(folderPanel);\r\n        \r\n        if (mainFrame.getInactivePanel().getTabs().getCurrentTab().isLocked())\r\n        \tsetEnabled(false);\r\n    }\r\n    \r\n    /**\r\n     * This method is overridden to enable this action when the parent folder is selected.\r\n     */\r\n    @Override\r\n    protected boolean getFileTableCondition(FileTable fileTable) {\r\n        AbstractFile selectedFile = fileTable.getSelectedFile(true, true);\r\n\r\n        return selectedFile!=null && selectedFile.isBrowsable();\r\n    }\r\n\r\n    /**\r\n     * Opens the currently selected file in the inactive folder panel.\r\n     */\r\n    @Override\r\n    public void performAction() {\r\n        AbstractFile file = mainFrame.getActiveTable().getSelectedFile(true, true);\r\n\r\n        // Retrieves the currently selected file, aborts if none (should not normally happen).\r\n        if (file == null || !file.isBrowsable()) {\r\n            return;\r\n        }\r\n\r\n        // Opens the currently selected file in the inactive panel.\r\n        mainFrame.getInactivePanel().tryChangeCurrentFolder(file);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"OpenInOtherPanel\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_O, CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new OpenInOtherPanelAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/OpenLeftInRightPanelAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport java.util.Map;\n\nimport javax.swing.KeyStroke;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.FileTable;\nimport com.mucommander.ui.main.tabs.FileTableTabs;\n\n/**\n * Opens browsable files in the right panel.\n *\n * <p>\n * If the left panel is the active panel and the selected file is browsable as defined by\n * {@link com.mucommander.commons.file.AbstractFile#isBrowsable()} then the selected file will be opened.\n *\n * <p>\n * Otherwise the left panel's location will be opened.\n *\n * <p>\n * If the right panel is locked then a new tab in the right panel will be opened first.\n *\n * @author Martin Kortkamp\n */\npublic class OpenLeftInRightPanelAction extends FileAction {\n    /**\n     * Creates a new <code>OpenLeftInRightPanelAction</code> with the specified parameters.\n     * @param mainFrame  frame to which the action is attached.\n     * @param properties action's properties.\n     */\n    OpenLeftInRightPanelAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    /**\n     * This action is always enabled.\n     */\n    @Override\n    protected boolean getFileTableCondition(FileTable fileTable) {\n\n        return true;\n    }\n\n    /**\n     * Opens the currently selected file or the current location in right folder panel.\n     */\n    @Override\n    public void performAction() {\n        AbstractFile destFile = getDestinationFile();\n\n        if (destFile.isBrowsable()) {\n\n            FileTableTabs destTabs = getDestPanel().getTabs();\n\n            if (destTabs.getCurrentTab().isLocked()) {\n                destTabs.add(destFile);\n            } else {\n                getDestPanel().tryChangeCurrentFolder(destFile);\n            }\n        }\n    }\n\n    private AbstractFile getDestinationFile() {\n        AbstractFile destFile = null;\n\n        FolderPanel srcPanel = getSrcPanel();\n\n        if (mainFrame.getActivePanel() == srcPanel) {\n            AbstractFile selectedFile = srcPanel.getFileTable().getSelectedFile();\n            if (selectedFile != null && selectedFile.isBrowsable()) {\n                destFile = selectedFile;\n            }\n        }\n\n        if (destFile == null) {\n            destFile = srcPanel.getCurrentFolder();\n        }\n        return destFile;\n    }\n\n    protected FolderPanel getDestPanel() {\n        return mainFrame.getRightPanel();\n    }\n\n    protected FolderPanel getSrcPanel() {\n        return mainFrame.getLeftPanel();\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"OpenLeftInRightPanel\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.NAVIGATION;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return null;\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new OpenLeftInRightPanelAction(mainFrame, properties);\n        }\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/OpenLocationAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport java.awt.Image;\r\nimport java.util.Map;\r\n\r\nimport com.mucommander.bonjour.BonjourService;\r\nimport com.mucommander.bookmark.Bookmark;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileFactory;\r\nimport com.mucommander.commons.file.FileProtocols;\r\nimport com.mucommander.commons.file.FileURL;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.impl.BatchRenameAction.Descriptor;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.utils.FileIconsCache;\r\n\r\nimport javax.swing.ImageIcon;\r\n\r\n/**\r\n * This action opens a specified location in the current active FileTable. The location can be designated by either a\r\n * FileURL, path, or AbstractFile.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class OpenLocationAction extends ActiveTabAction {\r\n\r\n    private FileURL url;\r\n    private AbstractFile file;\r\n    private String path;\r\n\r\n\r\n    /**\r\n     * Creates a new OpenLocationAction instance using the provided url's string representation\r\n     * (with credentials stripped out) as label.\r\n     *\r\n     * @param mainFrame main frame\r\n     * @param properties properties to use in this action\r\n     * @param url location to open\r\n     */\r\n    public OpenLocationAction(MainFrame mainFrame, Map<String, Object> properties, FileURL url) {\r\n        this(mainFrame, properties, url, url.getScheme().equals(FileProtocols.FILE) ? url.getPath() : url.toString(false));\r\n    }\r\n\r\n    /**\r\n     * Creates a new OpenLocationAction instance using the provided FileURL and label.\r\n     *\r\n     * @param mainFrame main frame\r\n     * @param properties properties to use in this action\r\n     * @param url location to open\r\n     * @param label tooltip label\r\n     */\r\n    private OpenLocationAction(MainFrame mainFrame, Map<String, Object> properties, FileURL url, String label) {\r\n        super(mainFrame, properties);\r\n\r\n        this.url = url;\r\n        setLabel(label);\r\n        boolean isLocalFile = url.getScheme().equals(FileProtocols.FILE);\r\n        if (isLocalFile) {\r\n            Image icon = FileIconsCache.getInstance().getImageIcon(FileFactory.getFile(url));\r\n            setIcon(new ImageIcon(icon));\r\n        }\r\n        setToolTipText(isLocalFile ? url.getPath() : url.toString(false));\r\n    }\r\n\r\n\r\n    /**\r\n     * Creates a new OpenLocationAction instance using the filename of the provided AbstractFile \r\n     * as label.\r\n     *\r\n     * @param mainFrame main frame\r\n     * @param properties properties to use in this action\r\n     * @param file location to open\r\n     */\r\n    public OpenLocationAction(MainFrame mainFrame, Map<String, Object> properties, AbstractFile file) {\r\n        this(mainFrame, properties, file, file.getName());\r\n    }\r\n\r\n    /**\r\n     * Creates a new OpenLocationAction instance using the provided AbstractFile and label.\r\n     *\r\n     * @param mainFrame main frame\r\n     * @param properties properties to use in this action\r\n     * @param file location to open\r\n     * @param label tooltip label\r\n     */\r\n    private OpenLocationAction(MainFrame mainFrame, Map<String, Object> properties, AbstractFile file, String label) {\r\n        super(mainFrame, properties);\r\n\r\n        this.file = file;\r\n        setLabel(label);\r\n        setToolTipText(file.getAbsolutePath());\r\n    }\r\n\r\n\r\n    /**\r\n     * Creates a new OpenLocationAction instance using the provided path as label.\r\n     *\r\n     * @param mainFrame main frame\r\n     * @param properties properties to use in this action\r\n     * @param path path to open\r\n     */\r\n    public OpenLocationAction(MainFrame mainFrame, Map<String, Object> properties, String path) {\r\n        this(mainFrame, properties, path, path);\r\n    }\r\n\r\n    /**\r\n     * Creates a new OpenLocationAction instance using the provided path and label.\r\n     *\r\n     * @param mainFrame main frame\r\n     * @param properties properties to use in this action\r\n     * @param path path to open\r\n     * @param label tooltip label\r\n     */\r\n    private OpenLocationAction(MainFrame mainFrame, Map<String, Object> properties, String path, String label) {\r\n        super(mainFrame, properties);\r\n\r\n        this.path = path;\r\n        setLabel(label);\r\n        setToolTipText(path);\r\n    }\r\n\r\n\r\n    /**\r\n     * Convenience constructor, same effect as calling {@link #OpenLocationAction(MainFrame, Map, String, String)} with\r\n     * {@link Bookmark#getLocation()} and {@link Bookmark#getName()}.\r\n     *\r\n     * @param mainFrame main frame\r\n     * @param properties properties to use in this action\r\n     * @param  bookmark location top open\r\n     */\r\n    public OpenLocationAction(MainFrame mainFrame, Map<String, Object> properties, Bookmark bookmark) {\r\n        this(mainFrame, properties, bookmark.getLocation(), bookmark.getName());\r\n    }\r\n\r\n\r\n    /**\r\n     * Convenience constructor, same effect as calling {@link #OpenLocationAction(MainFrame, Map, FileURL, String)} with\r\n     * {@link BonjourService#getURL()} and {@link BonjourService#getNameWithProtocol()} ()}.\r\n     *\r\n     * @param mainFrame main frame\r\n     * @param properties properties to use in this action\r\n     * @param  bonjourService location to open\r\n     */\r\n    public OpenLocationAction(MainFrame mainFrame, Map<String, Object> properties, BonjourService bonjourService) {\r\n        this(mainFrame, properties, bonjourService.getURL(), bonjourService.getNameWithProtocol());\r\n    }\r\n\r\n    /**\r\n     * Returns the {@link FolderPanel} on which to change the current folder. This method returns the currently active\r\n     * panel but can be overridden if another panel should be used.\r\n     *\r\n     * @return the currently active panel\r\n     */\r\n    protected FolderPanel getFolderPanel() {\r\n        return mainFrame.getActivePanel();\r\n    }\r\n\r\n    /**\r\n     * Enables or disables this action based on the current tab is not locked,\r\n     * this action will be enabled, if not it will be disabled.\r\n     */\r\n    @Override\r\n    protected void toggleEnabledState() {\r\n        setEnabled(!mainFrame.getActivePanel().getTabs().getCurrentTab().isLocked());\r\n    }\r\n\r\n    /////////////////////////////\r\n    // MuAction implementation //\r\n    /////////////////////////////\r\n\r\n    @Override\r\n    public void performAction() {\r\n        FolderPanel folderPanel = getFolderPanel();\r\n        if (url != null) {\r\n            folderPanel.tryChangeCurrentFolder(url);\r\n        } else if (file != null) {\r\n            folderPanel.tryChangeCurrentFolder(file);\r\n        } else if (path != null) {\r\n            folderPanel.tryChangeCurrentFolder(path);\r\n        }\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/OpenNativelyAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.AbstractArchiveEntryFile;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileProtocols;\r\nimport com.mucommander.desktop.DesktopManager;\r\nimport com.mucommander.job.TempExecJob;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.dialog.InformationDialog;\r\nimport com.mucommander.ui.dialog.file.ProgressDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.quicklist.RecentExecutedFilesQL;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.io.IOException;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action opens the currently selected file or folder with native file associations.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class OpenNativelyAction extends TcAction {\r\n\r\n    private OpenNativelyAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        AbstractFile selectedFile = mainFrame.getActiveTable().getSelectedFile(true, true);\r\n\r\n        if (selectedFile == null) {\r\n            return;\r\n        }\r\n\r\n        // Copy file to a temporary local file and execute it with native file associations if\r\n        // file is not on a local filesystem or file is an archive entry\r\n        if (!FileProtocols.FILE.equals(selectedFile.getURL().getScheme()) || selectedFile.hasAncestor(AbstractArchiveEntryFile.class)) {\r\n            ProgressDialog progressDialog = new ProgressDialog(mainFrame, Translator.get(\"copy_dialog.copying\"));\r\n            TempExecJob job = new TempExecJob(progressDialog, mainFrame, selectedFile);\r\n            progressDialog.start(job);\r\n        } else {\r\n            // Tries to execute file with native file associations\r\n            try {\r\n            \tDesktopManager.open(selectedFile);\r\n            \tRecentExecutedFilesQL.addFile(selectedFile);\r\n        \t} catch(IOException e) {\r\n                InformationDialog.showErrorDialog(mainFrame.getJFrame());\r\n            }\r\n        }\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"OpenNatively\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.SHIFT_DOWN_MASK);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new OpenNativelyAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/OpenRightInLeftPanelAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.KeyStroke;\nimport java.util.Map;\n\n/**\n * Opens browsable files in the left panel.\n *\n * <p>\n * If the right panel is the active panel and the selected file is browsable as defined by\n * {@link com.mucommander.commons.file.AbstractFile#isBrowsable()} then the selected file will be opened.\n *\n * <p>\n * Otherwise the right panel's location will be opened.\n *\n * <p>\n * If the left panel is locked then a new tab in the left panel will be opened first.\n *\n * @author Martin Kortkamp\n */\npublic class OpenRightInLeftPanelAction extends OpenLeftInRightPanelAction {\n    /**\n     * Creates a new <code>OpenRightInLeftPanelAction</code> with the specified parameters.\n     * @param mainFrame  frame to which the action is attached.\n     * @param properties action's properties.\n     */\n    private OpenRightInLeftPanelAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    protected FolderPanel getDestPanel() {\n        return mainFrame.getLeftPanel();\n    }\n\n    protected FolderPanel getSrcPanel() {\n        return mainFrame.getRightPanel();\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"OpenRightInLeftPanel\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.NAVIGATION;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return null;\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new OpenRightInLeftPanelAction(mainFrame, properties);\n        }\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/OpenTrashAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.desktop.AbstractTrash;\r\nimport com.mucommander.desktop.DesktopManager;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Opens the trash in the default file manager of the current OS/Desktop manager. This action is enabled only\r\n * if the current platform has an {@link com.mucommander.desktop.AbstractTrash} implementation and if it is capable\r\n * of opening the trash, as reported by {@link com.mucommander.desktop.AbstractTrash#canOpen()}.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class OpenTrashAction extends TcAction {\r\n\r\n    private OpenTrashAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        AbstractTrash trash = DesktopManager.getTrash();\r\n        setEnabled(trash != null && trash.canOpen());\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        DesktopManager.getTrash().open();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"OpenTrash\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new OpenTrashAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/OpenURLInBrowserAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport java.net.URL;\r\nimport java.util.Map;\r\n\r\nimport javax.swing.KeyStroke;\r\n\r\nimport com.mucommander.desktop.DesktopManager;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.InformationDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\n/**\r\n * This action opens a URL in the system's default browser. This action is enabled only if the OS/Window manager\r\n * is capable of doing do.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class OpenURLInBrowserAction extends TcAction {\r\n\r\n    /** Key to the URL property */\r\n    final static String URL_PROPERTY_KEY = \"url\";\r\n\r\n    OpenURLInBrowserAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        // Enable this action only if the current platform is capable of opening URLs in the default browser.\r\n        setEnabled(DesktopManager.canBrowse());\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        Object url = getValue(URL_PROPERTY_KEY);\r\n\r\n        if (url instanceof String) {\r\n            try {\r\n                DesktopManager.browse(new URL((String)url));\r\n            } catch(Exception e) {\r\n                InformationDialog.showErrorDialog(mainFrame.getJFrame());\r\n            }\r\n        }\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"OpenURLInBrowser\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\t\t\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n        @Override\r\n        public boolean isParameterized() {\r\n\t\t    return true;\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new OpenURLInBrowserAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/PackAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.FileOperation;\r\nimport com.mucommander.commons.file.filter.FileOperationFilter;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.file.PackDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action pops up the 'Pack files' dialog that allows to create an archive file with the currently marked files.\r\n *\r\n * @author Maxence Bernard\r\n */\r\n@InvokesDialog\r\npublic class PackAction extends SelectedFilesAction {\r\n\r\n    private PackAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        setSelectedFileFilter(new FileOperationFilter(FileOperation.READ_FILE));\r\n    }\r\n\r\n    @Override\r\n    public void performAction(FileSet files) {\r\n        new PackDialog(mainFrame, files).showDialog();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"Pack\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.FILES;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_I, CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new PackAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ParentFolderAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport java.util.Map;\r\n\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.event.ActivePanelListener;\r\nimport com.mucommander.ui.event.LocationEvent;\r\nimport com.mucommander.ui.event.LocationListener;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\n/**\r\n * This class is an abstract {@link TcAction} that operates on the current folder. It monitors changes in the active\r\n * panel's location and calls {@link #toggleEnabledState()} when the location has changed, or when the active panel\r\n * itself has changed, in order to enable or disable this action.\r\n *\r\n * @author Maxence Bernard, Nicolas Rinaudo\r\n */\r\npublic abstract class ParentFolderAction extends TcAction implements ActivePanelListener, LocationListener {\r\n\r\n    ParentFolderAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        // Listen to active table change events\r\n        mainFrame.addActivePanelListener(this);\r\n\r\n        // Listen to location change events\r\n        mainFrame.getLeftPanel().getLocationManager().addLocationListener(this);\r\n        mainFrame.getRightPanel().getLocationManager().addLocationListener(this);\r\n\r\n        toggleEnabledState();\r\n    }\r\n\r\n\r\n    //////////////////////\r\n    // Abstract methods //\r\n    //////////////////////\r\n\r\n    /**\r\n     * Enables or disables this action based on the location of the currently active {@link FolderPanel}.\r\n     * This method is called once by the constructor to set the initial state. Then it is called every time the location\r\n     * of the currently active <code>FolderPanel</code> has changed, and when the currently active <code>FolderPanel</code>\r\n     * has changed.\r\n     */\r\n    protected abstract void toggleEnabledState();\r\n\r\n\r\n    /////////////////////////////////\r\n    // ActivePanelListener methods //\r\n    /////////////////////////////////\r\n\r\n    public void activePanelChanged(FolderPanel folderPanel) {\r\n        toggleEnabledState();\r\n    }\r\n    \r\n    /**********************************\r\n\t * LocationListener Implementation\r\n\t **********************************/\r\n\r\n    public void locationChanged(LocationEvent e) {\r\n        toggleEnabledState();\r\n    }\r\n    \r\n\tpublic void locationChanging(LocationEvent locationEvent) { }\r\n\r\n\tpublic void locationCancelled(LocationEvent locationEvent) { }\r\n\r\n\tpublic void locationFailed(LocationEvent locationEvent) { }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/PasteClipboardFilesAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.job.CopyJob;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.file.FileCollisionDialog;\r\nimport com.mucommander.ui.dialog.file.ProgressDialog;\r\nimport com.mucommander.ui.dnd.ClipboardNotifier;\r\nimport com.mucommander.ui.dnd.ClipboardSupport;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action pastes the files contained by the system clipboard to the currently active folder.\r\n * Does nothing if the clipboard doesn't contain any file.\r\n *\r\n * <p>Under Java 1.5 and up, this action gets automatically enabled/disabled when files are present/not present\r\n * in the clipboard.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class PasteClipboardFilesAction extends TcAction {\r\n\r\n    private PasteClipboardFilesAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        // Allows this action to be dynamically enabled when the clipboard contains files, and disabled otherwise.\r\n        // ClipboardNotifier does not work under Mac OS X (tested under Tiger with Java 1.5.0_06)\r\n        if (!OsFamily.MAC_OS_X.isCurrent()) {\r\n            new ClipboardNotifier(this);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        // Retrieve clipboard files\r\n        FileSet clipboardFiles = ClipboardSupport.getClipboardFiles();\r\n        if (clipboardFiles != null && !clipboardFiles.isEmpty()) {\r\n            startCopyingFiles(clipboardFiles);\r\n        }\r\n    }\r\n\r\n    private void startCopyingFiles(FileSet clipboardFiles) {\r\n        ProgressDialog progressDialog = new ProgressDialog(mainFrame, Translator.get(\"copy_dialog.copying\"));\r\n        AbstractFile destFolder = mainFrame.getActivePanel().getCurrentFolder();\r\n        CopyJob job = new CopyJob(progressDialog, mainFrame, clipboardFiles, destFolder, null, CopyJob.Mode.COPY, FileCollisionDialog.ASK_ACTION);\r\n        progressDialog.start(job);\r\n    }\r\n\r\n    @Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"PasteClipboardFiles\";\r\n\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.SELECTION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_V, CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new PasteClipboardFilesAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/PasteFromArchiveToFilesFromClipboardAction.java",
    "content": "\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.dnd.ClipboardOperations;\nimport com.mucommander.ui.dnd.ClipboardSupport;\nimport com.mucommander.ui.main.MainFrame;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\nimport javax.swing.KeyStroke;\n\n/**\n * This action cuts the selected / marked files to the system clipboard, allowing to paste\n * them to muCommander.\n *\n * @author Nicholai R. Svarre\n */\npublic class PasteFromArchiveToFilesFromClipboardAction extends SelectedFilesAction {\n    \n\n    private PasteFromArchiveToFilesFromClipboardAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction(FileSet files) {\n        ClipboardSupport.setClipboardFiles(files);\n        ClipboardSupport.setOperation(ClipboardOperations.ARCHIVE);\n    }\n\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"PasteFromArchiveToFilesFromClipboard\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.SELECTION;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_D, KeyEvent.META_DOWN_MASK);\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_D, KeyEvent.CTRL_DOWN_MASK);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new PasteFromArchiveToFilesFromClipboardAction(mainFrame, properties);\n        }\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/PermanentDeleteAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.FileOperation;\r\nimport com.mucommander.commons.file.filter.FileOperationFilter;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.file.DeleteDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action invokes a Delete confirmation dialog to delete the currently selected / marked files in the currently\r\n * active folder. Unlike {@link com.mucommander.ui.action.impl.DeleteAction}, the system trash is not used, files are\r\n * permanently deleted.\r\n *\r\n * @see com.mucommander.ui.action.impl.DeleteAction\r\n * @author Maxence Bernard\r\n */\r\npublic class PermanentDeleteAction extends SelectedFilesAction {\r\n\r\n    private PermanentDeleteAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        setSelectedFileFilter(new FileOperationFilter(FileOperation.DELETE));\r\n    }\r\n\r\n    @Override\r\n    public void performAction(FileSet files) {\r\n        new DeleteDialog(mainFrame, files, true).showDialog();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"PermanentDelete\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.FILES;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, KeyEvent.SHIFT_DOWN_MASK);\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_F8, KeyEvent.SHIFT_DOWN_MASK);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new PermanentDeleteAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/PopupLeftDriveButtonAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.tabs.ActiveTabListener;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Pops up the DrivePopupButton (the drop down button that allows to quickly select a volume or bookmark)\r\n * of the left FolderPanel.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class PopupLeftDriveButtonAction extends TcAction implements ActiveTabListener {\r\n\r\n    private PopupLeftDriveButtonAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n        \r\n        mainFrame.getLeftPanel().getTabs().addActiveTabListener(this);\r\n        \r\n        activeTabChanged();\r\n    }\r\n\r\n    /**\r\n     * Enables or disables this action based on the current tab is not locked, \r\n     * this action will be enabled, if not it will be disabled.\r\n     */\r\n\tpublic void activeTabChanged() {\r\n\t\tsetEnabled(!mainFrame.getLeftPanel().getTabs().getCurrentTab().isLocked());\r\n\t}\r\n\r\n    @Override\r\n    public void performAction() {\r\n        mainFrame.getLeftPanel().getDriveButton().popupMenu();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"PopupLeftDriveButton\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_F1, KeyEvent.ALT_DOWN_MASK);\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new PopupLeftDriveButtonAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/PopupRightDriveButtonAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.tabs.ActiveTabListener;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Pops up the DrivePopupButton (the drop down button that allows to quickly select a volume or bookmark)\r\n * of the left FolderPanel.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class PopupRightDriveButtonAction extends TcAction implements ActiveTabListener {\r\n\r\n    private PopupRightDriveButtonAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n        \r\n        mainFrame.getRightPanel().getTabs().addActiveTabListener(this);\r\n        \r\n        activeTabChanged();\r\n    }\r\n\r\n    /**\r\n     * Enables or disables this action based on the current tab is not locked, \r\n     * this action will be enabled, if not it will be disabled.\r\n     */\r\n    public void activeTabChanged() {\r\n    \tsetEnabled(!mainFrame.getRightPanel().getTabs().getCurrentTab().isLocked());\r\n\t}\r\n\r\n    @Override\r\n    public void performAction() {\r\n        mainFrame.getRightPanel().getDriveButton().popupMenu();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"PopupRightDriveButton\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_F2, KeyEvent.ALT_DOWN_MASK);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new PopupRightDriveButtonAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/PreviousTabAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.*;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * Change the selected tab to the tab which is located to the left of the current displayed tab.\n * If the current displayed tab is the leftmost tab, the rightmost tab will be displayed.\n * \n * @author Arik Hadas\n */\npublic class PreviousTabAction extends TcAction {\n\t\n\tprivate PreviousTabAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n    \tmainFrame.getActivePanel().getTabs().previousTab();\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n    \tpublic static final String ACTION_ID = \"PreviousTab\";\n    \t\n\t\tpublic String getId() {\n\t\t    return ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t    return ActionCategory.TAB;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, CTRL_OR_META_DOWN_MASK);\n        }\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, CTRL_OR_META_DOWN_MASK);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new PreviousTabAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ProxyAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport javax.swing.*;\r\nimport java.beans.PropertyChangeListener;\r\n\r\n/**\r\n * ProxyAction is a proxy for an Action instance. All Action methods are proxied except for <code>actionPerformed()</code>.\r\n * That means all properties of the proxied Action are preserved but the proxied Action is not performed.\r\n *\r\n * <p>ProxyAction is useful to keep the visual properties of an Action instance in a component (JButton for instance)\r\n * but perform a different action.\r\n *\r\n * <p>This class is abstract, leaving <code>actionPerformed()</code> unimplemented. {@link MuteProxyAction} provides an\r\n * implementation where <code>actionPerformed()</code> does nothing.\r\n * \r\n * @author Maxence Bernard\r\n */\r\npublic abstract class ProxyAction implements Action {\r\n\r\n    /** Proxied action */\r\n    private Action proxiedAction;\r\n\r\n    \r\n    /**\r\n     * Creates a new ProxyAction that acts as a proxy to the provided Action instance.\r\n     *\r\n     * @param proxiedAction the action to proxy\r\n     */\r\n    ProxyAction(Action proxiedAction) {\r\n        this.proxiedAction = proxiedAction;\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the Action instance that this ProxyAction proxies. \r\n     */\r\n    public Action getProxiedAction() {\r\n        return proxiedAction;\r\n    }\r\n\r\n    /////////////////////\r\n    // Proxied methods //\r\n    /////////////////////\r\n\r\n    public Object getValue(String key) {\r\n        return proxiedAction.getValue(key);\r\n    }\r\n\r\n    public void putValue(String key, Object value) {\r\n        proxiedAction.putValue(key, value);\r\n    }\r\n\r\n    public void setEnabled(boolean b) {\r\n        proxiedAction.setEnabled(b);\r\n    }\r\n\r\n    public boolean isEnabled() {\r\n        return proxiedAction.isEnabled();\r\n    }\r\n\r\n    public void addPropertyChangeListener(PropertyChangeListener propertyChangeListener) {\r\n        proxiedAction.addPropertyChangeListener(propertyChangeListener);\r\n    }\r\n\r\n    public void removePropertyChangeListener(PropertyChangeListener propertyChangeListener) {\r\n        proxiedAction.removePropertyChangeListener(propertyChangeListener);\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/QuitAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.dialog.shutdown.QuitDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.WindowManager;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action pops up the Quit confirmation dialog (if it hasn't been disabled) and if quit has been confirmed,\r\n * quits the application.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class QuitAction extends TcAction {\r\n\r\n    private QuitAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        if (QuitDialog.confirmQuit()) {\r\n            WindowManager.quit();\r\n        }\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"Quit\";\r\n    \t\r\n\t\tpublic String getId() { return ACTION_ID; }\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.WINDOW;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_Q, CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new QuitAction(mainFrame, properties);\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/RecallNextWindowAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.WindowManager;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action brings the next window (next window number) to the front.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class RecallNextWindowAction extends TcAction {\r\n\r\n    private RecallNextWindowAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        WindowManager.switchToNextWindow();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"RecallNextWindow\";\r\n    \t\r\n\t\tpublic String getId() { return ACTION_ID; }\r\n\r\n\t\tpublic ActionCategory getCategory() { return ActionCategory.WINDOW; }\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() { return null; }\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.SHIFT_DOWN_MASK | CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new RecallNextWindowAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/RecallPreviousWindowAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.WindowManager;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action brings the previous window (previous window number) to the front.\r\n * \r\n * @author Maxence Bernard\r\n */\r\npublic class RecallPreviousWindowAction extends TcAction {\r\n\r\n    private RecallPreviousWindowAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        WindowManager.switchToPreviousWindow();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"RecallPreviousWindow\";\r\n\r\n\t\tpublic String getId() { return ACTION_ID; }\r\n\r\n\t\tpublic ActionCategory getCategory() { return ActionCategory.WINDOW; }\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() { return null; }\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, KeyEvent.SHIFT_DOWN_MASK | CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new RecallPreviousWindowAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/RecallWindow10Action.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport java.util.Map;\r\n\r\n/**\r\n * Recalls window number 10 (brings it to the front).\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class RecallWindow10Action extends RecallWindowAction {\r\n\r\n    private RecallWindow10Action(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, 10);\r\n    }\r\n\r\n\r\n    public static final class Descriptor extends RecallWindowAction.Descriptor {\r\n        public static final String ACTION_ID = RecallWindowAction.Descriptor.ACTION_ID+\"10\";\r\n\r\n        public Descriptor() {\r\n            super(10);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new RecallWindow10Action(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/RecallWindow1Action.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport java.util.Map;\r\n\r\n/**\r\n * Recalls window number 1 (brings it to the front). \r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class RecallWindow1Action extends RecallWindowAction {\r\n\r\n    private RecallWindow1Action(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, 1);\r\n    }\r\n\r\n\r\n    public static final class Descriptor extends RecallWindowAction.Descriptor {\r\n        public static final String ACTION_ID = RecallWindowAction.Descriptor.ACTION_ID+\"1\";\r\n\r\n        public Descriptor() {\r\n            super(1);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new RecallWindow1Action(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/RecallWindow2Action.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport java.util.Map;\r\n\r\n/**\r\n * Recalls window number 2 (brings it to the front).\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class RecallWindow2Action extends RecallWindowAction {\r\n\r\n    private RecallWindow2Action(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, 2);\r\n    }\r\n\r\n\r\n    public static final class Descriptor extends RecallWindowAction.Descriptor {\r\n        public static final String ACTION_ID = RecallWindowAction.Descriptor.ACTION_ID+\"2\";\r\n\r\n        public Descriptor() {\r\n            super(2);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new RecallWindow2Action(mainFrame, properties);\r\n        }\r\n\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/RecallWindow3Action.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport java.util.Map;\r\n\r\n/**\r\n * Recalls window number 3 (brings it to the front).\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class RecallWindow3Action extends RecallWindowAction {\r\n\r\n    private RecallWindow3Action(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, 3);\r\n    }\r\n\r\n\r\n    public static final class Descriptor extends RecallWindowAction.Descriptor {\r\n        public static final String ACTION_ID = RecallWindowAction.Descriptor.ACTION_ID+\"3\";\r\n\r\n        public Descriptor() {\r\n            super(3);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new RecallWindow3Action(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/RecallWindow4Action.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport java.util.Map;\r\n\r\n/**\r\n * Recalls window number 4 (brings it to the front).\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class RecallWindow4Action extends RecallWindowAction {\r\n\r\n    private RecallWindow4Action(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, 4);\r\n    }\r\n\r\n\r\n    public static final class Descriptor extends RecallWindowAction.Descriptor {\r\n        public static final String ACTION_ID = RecallWindowAction.Descriptor.ACTION_ID+\"4\";\r\n\r\n        public Descriptor() {\r\n            super(4);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new RecallWindow4Action(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/RecallWindow5Action.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport java.util.Map;\r\n\r\n/**\r\n * Recalls window number 5 (brings it to the front).\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class RecallWindow5Action extends RecallWindowAction {\r\n\r\n    private RecallWindow5Action(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, 5);\r\n    }\r\n\r\n\r\n    public static final class Descriptor extends RecallWindowAction.Descriptor {\r\n        public static final String ACTION_ID = RecallWindowAction.Descriptor.ACTION_ID+\"5\";\r\n\r\n        public Descriptor() {\r\n            super(5);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new RecallWindow5Action(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/RecallWindow6Action.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport java.util.Map;\r\n\r\n/**\r\n * Recalls window number 6 (brings it to the front).\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class RecallWindow6Action extends RecallWindowAction {\r\n\r\n    private RecallWindow6Action(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, 6);\r\n    }\r\n\r\n    public static final class Descriptor extends RecallWindowAction.Descriptor {\r\n        public static final String ACTION_ID = RecallWindowAction.Descriptor.ACTION_ID+\"6\";\r\n\r\n        public Descriptor() {\r\n            super(6);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new RecallWindow6Action(mainFrame, properties);\r\n        }\r\n\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/RecallWindow7Action.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport java.util.Map;\r\n\r\n/**\r\n * Recalls window number 7 (brings it to the front).\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class RecallWindow7Action extends RecallWindowAction {\r\n\r\n    private RecallWindow7Action(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, 7);\r\n    }\r\n\r\n\r\n    public static final class Descriptor extends RecallWindowAction.Descriptor {\r\n        public static final String ACTION_ID = RecallWindowAction.Descriptor.ACTION_ID+\"7\";\r\n\r\n        public Descriptor() {\r\n            super(7);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new RecallWindow7Action(mainFrame, properties);\r\n        }\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/RecallWindow8Action.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport java.util.Map;\r\n\r\n/**\r\n * Recalls window number 8 (brings it to the front).\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class RecallWindow8Action extends RecallWindowAction {\r\n\r\n    private RecallWindow8Action(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, 8);\r\n    }\r\n\r\n\r\n    public static final class Descriptor extends RecallWindowAction.Descriptor {\r\n        public static final String ACTION_ID = RecallWindowAction.Descriptor.ACTION_ID+\"8\";\r\n\r\n        public Descriptor() {\r\n            super(8);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new RecallWindow8Action(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/RecallWindow9Action.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport java.util.Map;\r\n\r\n/**\r\n * Recalls window number 9 (brings it to the front).\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class RecallWindow9Action extends RecallWindowAction {\r\n\r\n    private RecallWindow9Action(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, 9);\r\n    }\r\n\r\n\r\n    public static final class Descriptor extends RecallWindowAction.Descriptor {\r\n        public static final String ACTION_ID = RecallWindowAction.Descriptor.ACTION_ID+\"9\";\r\n\r\n        public Descriptor() {\r\n            super(9);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new RecallWindow9Action(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/RecallWindowAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.WindowManager;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Brings a {@link MainFrame} window to the front. This action operates on a specific window number specified in the\r\n * constructor, either as a constructor parameter, or in the {@link #WINDOW_NUMBER_PROPERTY_KEY} property.\r\n *\r\n * @see com.mucommander.ui.main.WindowManager\r\n * @author Maxence Bernard\r\n */\r\npublic class RecallWindowAction extends TcAction {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(RecallWindowAction.class);\r\n\t\r\n    /** Window number this action operates on */\r\n    private int windowNumber;\r\n\r\n    /** Key of the property that holds the window number */\r\n    public final static String WINDOW_NUMBER_PROPERTY_KEY = \"window_number\";\r\n\r\n\r\n    public RecallWindowAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        Object windowNumberValue = getValue(WINDOW_NUMBER_PROPERTY_KEY);\r\n        if (!(windowNumberValue instanceof String)) {\r\n            throw new IllegalArgumentException(WINDOW_NUMBER_PROPERTY_KEY + \" (\" + windowNumberValue + \")\");\r\n        }\r\n\r\n        windowNumber = Integer.parseInt((String)windowNumberValue);\r\n        if (windowNumber <= 0) {\r\n            throw new IllegalArgumentException(WINDOW_NUMBER_PROPERTY_KEY + \" (\" + windowNumberValue + \")\");\r\n        }\r\n    }\r\n\r\n    public RecallWindowAction(MainFrame mainFrame, Map<String,Object> properties, int windowNumber) {\r\n        super(mainFrame, properties);\r\n\r\n        this.windowNumber = windowNumber;\r\n        if (windowNumber <= 0) {\r\n            throw new IllegalArgumentException(\"windowNumber (\"+windowNumber+\")\");\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        List<MainFrame> mainFrames = WindowManager.getMainFrames();\r\n\r\n        // Checks that the window number currently exists\r\n        if (windowNumber <= 0 || windowNumber > mainFrames.size()) {\r\n            LOGGER.debug(\"Window number \"+windowNumber+\" does not exist\");\r\n            return;\r\n        }\r\n\r\n        // Brings the MainFrame to front\r\n        mainFrames.get(windowNumber-1).toFront();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor(windowNumber);\r\n\t}\r\n\r\n\r\n    public static class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"RecallWindow\";\r\n\r\n        private int windowNumber;\r\n\r\n        public Descriptor() {\r\n            this(-1);\r\n        }\r\n\r\n        protected Descriptor(int windowNumber) {\r\n            this.windowNumber = windowNumber;\r\n        }\r\n\r\n\t\tpublic String getId() { return windowNumber < 0 ? ACTION_ID : ACTION_ID + windowNumber; }\r\n\r\n\t\tpublic ActionCategory getCategory() { return ActionCategory.WINDOW; }\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() { return null; }\r\n\r\n        public KeyStroke getDefaultKeyStroke() {\r\n            if (windowNumber <= 0 || windowNumber > 10) {\r\n                return null;\r\n            }\r\n\r\n            return KeyStroke.getKeyStroke(Character.forDigit(windowNumber == 10 ? 0 : windowNumber, 10), KeyEvent.CTRL_DOWN_MASK);\r\n        }\r\n\r\n        @Override\r\n        public String getLabel() {\r\n            return Translator.get(getLabelKey(), windowNumber < 0 ? \"?\" : \"\"+windowNumber);\r\n        }\r\n\r\n        @Override\r\n        public boolean isParameterized() {\r\n            return windowNumber == -1;\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new RecallWindowAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/RefreshAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action refreshes the currently active FolderPanel (refreshes the content of the folder).\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class RefreshAction extends TcAction {\r\n\r\n    private RefreshAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        // Refresh current folder in a separate thread\r\n        mainFrame.getActivePanel().tryRefreshCurrentFolder();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"Refresh\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.VIEW;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_F9, 0);\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new RefreshAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/RenameAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileOperation;\r\nimport com.mucommander.commons.file.filter.AndFileFilter;\r\nimport com.mucommander.commons.file.filter.FileOperationFilter;\r\nimport com.mucommander.commons.file.filter.OrFileFilter;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.FileTable;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action triggers in-table renaming of the currently selected file, if no file is marked.\r\n * If files are marked, it simply invokes 'Move dialog' just like {@link CopyAction}.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class RenameAction extends SelectedFileAction {\r\n\r\n    private RenameAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        setSelectedFileFilter(new OrFileFilter(\r\n            new FileOperationFilter(FileOperation.RENAME),\r\n            new AndFileFilter(\r\n                new FileOperationFilter(FileOperation.READ_FILE),\r\n                new FileOperationFilter(FileOperation.WRITE_FILE)\r\n            )\r\n        ));\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        FileTable activeTable = mainFrame.getActiveTable();\r\n        AbstractFile selectedFile = activeTable.getSelectedFile(false);\r\n\r\n        // Trigger in-table editing only if a file other than parent folder '..' is selected\r\n        if (selectedFile != null) {\r\n            // Trigger in-table renaming\r\n            activeTable.editCurrentFilename();\r\n        }\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"Rename\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.FILES;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_F6, KeyEvent.SHIFT_DOWN_MASK);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new RenameAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ReportBugAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.*;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action opens the mucommander.com bug repository URL in the system's default browser.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class ReportBugAction extends OpenURLInBrowserAction {\r\n\r\n    private ReportBugAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        putValue(URL_PROPERTY_KEY, com.mucommander.RuntimeConstants.BUG_REPOSITORY_URL);\r\n    }\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"ReportBug\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.MISC;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new ReportBugAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/RevealInDesktopAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.AbstractArchiveEntryFile;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.desktop.DesktopManager;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.InformationDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.utils.text.Translator;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n\r\n/**\r\n * This action reveals the currently selected file or folder in the native Desktop's file manager\r\n * (e.g. Finder for Mac OS X, Explorer for Windows, etc...).\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class RevealInDesktopAction extends ParentFolderAction {\r\n\r\n    private RevealInDesktopAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        setEnabled(DesktopManager.canOpenInFileManager());\r\n    }\r\n\r\n    @Override\r\n    protected void toggleEnabledState() {\r\n        AbstractFile currentFolder = mainFrame.getActivePanel().getCurrentFolder();\r\n        setEnabled(isLocalRegularFolder(currentFolder));\r\n    }\r\n\r\n    private static boolean isLocalRegularFolder(AbstractFile currentFolder) {\r\n        return currentFolder != null && currentFolder.isLocalFile()\r\n               && !currentFolder.isArchive()\r\n               && !currentFolder.hasAncestor(AbstractArchiveEntryFile.class);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        try {\r\n            DesktopManager.openInFileManager(getCurrentFolder());\r\n        } catch(Exception e) {\r\n            InformationDialog.showErrorDialog(mainFrame.getJFrame());\r\n        }\r\n    }\r\n\r\n    private AbstractFile getCurrentFolder() {\r\n        if (OsFamily.MAC_OS_X.isCurrent()) {\r\n            AbstractFile currentFile = mainFrame.getActiveTable().getSelectedFile();\r\n            if (currentFile == null) {\r\n                return mainFrame.getActivePanel().getCurrentFolder();\r\n            }\r\n            return currentFile;\r\n        } else {\r\n            return mainFrame.getActivePanel().getCurrentFolder();\r\n        }\r\n\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"RevealInDesktop\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_L, CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        @Override\r\n        public String getLabel() {\r\n            return Translator.get(ActionProperties.getActionLabelKey(RevealInDesktopAction.Descriptor.ACTION_ID),\r\n                    DesktopManager.canOpenInFileManager() ? DesktopManager.getFileManagerName() : Translator.get(\"file_manager\"));\r\n        }\r\n\r\n        @Override\r\n        public ImageIcon getIcon() {\r\n\t\t    if (OsFamily.MAC_OS_X.isCurrent()) {\r\n                return getStandardIcon(\"Finder\");\r\n            }\r\n            return super.getIcon();\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new RevealInDesktopAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ReverseSortOrderAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n\r\n/**\r\n * This action reverses the sort order of the currently active FileTable.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class ReverseSortOrderAction extends TcAction {\r\n\r\n    private ReverseSortOrderAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        mainFrame.getActiveTable().reverseSortOrder();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"ReverseSortOrder\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.VIEW;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new ReverseSortOrderAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/RightArrowAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.FileTable;\nimport com.mucommander.ui.main.table.views.TableViewMode;\n\nimport javax.swing.*;\nimport java.awt.event.KeyEvent;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * @author Oleg Trifonov\n * Created on 27/10/16.\n */\npublic class RightArrowAction extends TcAction {\n\n    public RightArrowAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n        FileTable table = mainFrame.getActiveTable();\n        if (table == null) {\n            return;\n        }\n        if (table.getViewMode() != TableViewMode.FULL) {\n            return;\n        }\n        int count = table.getFilesCount();\n        AbstractFile file = table.getSelectedFile(true, true);\n        if (file != null && file.isDirectory() && table.getSelectedFileIndex() > 0) {\n            new OpenAction(mainFrame, new HashMap<>()).performAction();\n        } else {\n            table.selectFile(count-1);\n        }\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"RightArrowAction\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.NAVIGATION;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0);\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new RightArrowAction(mainFrame, properties);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/RunCommandAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.shell.RunDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action pops up the 'Run command' dialog that is used to execute a shell command.\r\n *\r\n * @author Maxence Bernard\r\n */\r\n@InvokesDialog\r\npublic class RunCommandAction extends TcAction {\r\n\r\n    private RunCommandAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        new RunDialog(mainFrame).showDialog();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"RunCommand\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.FILES;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_R, CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new RunCommandAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SelectBackwardAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.FileTable;\n\nimport java.util.Map;\n\n/**\n * Moves the current {@link FileTable}'s selection backward, {@link #getRowDecrement} rows at a time.\n *\n * @author Maxence Bernard\n */\npublic abstract class SelectBackwardAction extends TcAction {\n\n    SelectBackwardAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n\n    /////////////////////////////\n    // MuAction implementation //\n    /////////////////////////////\n\n    @Override\n    public void performAction() {\n        FileTable activeTable = mainFrame.getActiveTable();\n        int newFileIndex = Math.max(activeTable.getSelectedFileIndex() - getRowDecrement(), 0);\n        activeTable.selectFile(newFileIndex);\n    }\n\n\n    //////////////////////\n    // Abstract methods //\n    //////////////////////\n\n    /**\n     * Returns the number of rows to decrease from the current selection.\n     *\n     * @return the number of rows to decrease from the current selection.\n     */\n    protected abstract int getRowDecrement();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SelectFirstRowAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action selects the first row/file in the current FileTable.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class SelectFirstRowAction extends TcAction {\r\n\r\n    private SelectFirstRowAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        mainFrame.getActiveTable().selectFile(0);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"SelectFirstRow\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.SELECTION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0);\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new SelectFirstRowAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SelectForwardAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.FileTable;\n\nimport java.util.Map;\n\n/**\n * Moves the current {@link FileTable}'s selection forward, {@link #getRowIncrement} rows at a time.\n *\n * @author Maxence Bernard\n */\npublic abstract class SelectForwardAction extends TcAction {\n\n    SelectForwardAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n\n    /////////////////////////////\n    // MuAction implementation //\n    /////////////////////////////\n\n    @Override\n    public void performAction() {\n        FileTable activeTable = mainFrame.getActiveTable();\n        int newFileIndex = Math.min(activeTable.getSelectedFileIndex() + getRowIncrement(), activeTable.getFilesCount() - 1);\n        activeTable.selectFile(newFileIndex);\n    }\n\n\n    //////////////////////\n    // Abstract methods //\n    //////////////////////\n\n    /**\n     * Returns the number of rows to increment the current selection.\n     *\n     * @return the number of rows to increment the current selection.\n     */\n    protected abstract int getRowIncrement();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SelectLastRowAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.FileTable;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action selects the last row/file in the current FileTable.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class SelectLastRowAction extends TcAction {\r\n\r\n    private SelectLastRowAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        FileTable fileTable = mainFrame.getActiveTable();\r\n        fileTable.selectFile(fileTable.getFilesCount()-1);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"SelectLastRow\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.SELECTION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_END, 0);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new SelectLastRowAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SelectNextBlockAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.FileTable;\n\nimport javax.swing.*;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * This action moves the current {@link FileTable}'s selection to the next 'block'.\n *\n * @author Maxence Bernard\n */\npublic class SelectNextBlockAction extends SelectForwardAction {\n\n    /** Number of file/rows a block represents */\n    // TODO: make this value configurable\n    private static final int BLOCK_SIZE = 5;\n\n    private SelectNextBlockAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    protected int getRowIncrement() {\n        return BLOCK_SIZE;\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"SelectNextBlock\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.SELECTION;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, CTRL_OR_META_DOWN_MASK);\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new SelectNextBlockAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SelectNextPageAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.FileTable;\n\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * This action moves the current {@link FileTable}'s selection to the next page.\n *\n * @author Maxence Bernard\n */\npublic class SelectNextPageAction extends SelectForwardAction {\n\n    private SelectNextPageAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    protected int getRowIncrement() {\n        // Note: the page row increment varies with the file table's height\n        return mainFrame.getActiveTable().getPageRowIncrement()+1;\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"SelectNextPage\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.SELECTION;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0);\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new SelectNextPageAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SelectNextRowAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.FileTable;\n\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * This action moves the current {@link FileTable}'s selection to the next row.\n *\n * @author Maxence Bernard\n */\npublic class SelectNextRowAction extends SelectForwardAction {\n\n    private SelectNextRowAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    protected int getRowIncrement() {\n        return 1;\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"SelectNextRow\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.SELECTION;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0);\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new SelectNextRowAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SelectPreviousBlockAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.FileTable;\n\nimport javax.swing.*;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * This action moves the current {@link FileTable}'s selection to the previous 'block'.\n *\n * @author Maxence Bernard\n */\npublic class SelectPreviousBlockAction extends SelectBackwardAction {\n\n    /** Number of file/rows a block represents */\n    // TODO: make this value configurable\n    private static final int BLOCK_SIZE = 5;\n\n    private SelectPreviousBlockAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    protected int getRowDecrement() {\n        return BLOCK_SIZE;\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"SelectPreviousBlock\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.SELECTION;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_UP, CTRL_OR_META_DOWN_MASK);\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new SelectPreviousBlockAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SelectPreviousPageAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.FileTable;\n\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * This action moves the current {@link FileTable}'s selection to the previous page.\n *\n * @author Maxence Bernard\n */\npublic class SelectPreviousPageAction extends SelectBackwardAction {\n\n    private SelectPreviousPageAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    protected int getRowDecrement() {\n        // Note: the page row increment varies with the file table's height\n        return mainFrame.getActiveTable().getPageRowIncrement()+1;\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"SelectPreviousPage\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.SELECTION;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0);\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new SelectPreviousPageAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SelectPreviousRowAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.FileTable;\n\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * This action moves the current {@link FileTable}'s selection to the previous row.\n *\n * @author Maxence Bernard\n */\npublic class SelectPreviousRowAction extends SelectBackwardAction {\n\n    private SelectPreviousRowAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    protected int getRowDecrement() {\n        return 1;\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"SelectPreviousRow\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.SELECTION;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0);\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new SelectPreviousRowAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SelectedFileAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.filter.FileFilter;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.FileTable;\r\n\r\nimport java.util.Map;\r\n\r\n/**\r\n * SelectedFileAction is an abstract action that operates on the currently active FileTable,\r\n * and that is enabled only when a file other than the parent folder file '..' is selected.\r\n *\r\n * <p>Optionally, a FileFilter can be specified using {@link #setSelectedFileFilter(com.mucommander.commons.file.filter.FileFilter) setSelectedFileFilter}\r\n * to further restrict the enabled condition to files that match the IMAGE_FILTER.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic abstract class SelectedFileAction extends FileAction {\r\n\r\n    SelectedFileAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    /**\r\n     * Returns the IMAGE_FILTER that restricts the enabled condition to selected files that match the specified IMAGE_FILTER.\r\n     *\r\n     * @return the IMAGE_FILTER that restricts the enabled condition to selected files that match the specified IMAGE_FILTER.\r\n     */\r\n    FileFilter getSelectedFileFilter() {\r\n        return filter;\r\n    }\r\n\r\n    /**\r\n     * Restricts the enabled condition to selected files that match the specified IMAGE_FILTER.\r\n     *\r\n     * @param filter FileFilter instance\r\n     */\r\n    public void setSelectedFileFilter(FileFilter filter) {\r\n        this.filter = filter;\r\n    }\r\n\r\n\r\n    @Override\r\n    protected boolean getFileTableCondition(FileTable fileTable) {\r\n        AbstractFile selectedFile = fileTable.getSelectedFile(false, true);\r\n        boolean enable = selectedFile != null;\r\n\r\n        if (enable && filter != null) {\r\n            enable = filter.match(selectedFile);\r\n        }\r\n\r\n        return enable;\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SelectedFilesAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.FileTable;\r\n\r\nimport java.util.Map;\r\n\r\n/**\r\n * SelectedFilesAction is an abstract action that operates on the currently active FileTable, and is enabled only\r\n * when at least one file is marked, or when a file other than the parent folder file '..' is selected.\r\n * When none of those conditions is satisfied, this action is disabled.\r\n *\r\n * <p>Optionally, a FileFilter can be specified using {@link #setSelectedFileFilter(com.mucommander.commons.file.filter.FileFilter) setSelectedFileFilter}\r\n * to further restrict the enabled condition to files that match the IMAGE_FILTER.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic abstract class SelectedFilesAction extends SelectedFileAction {\r\n\r\n    public SelectedFilesAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    protected boolean getFileTableCondition(FileTable fileTable) {\r\n        return fileTable.getFileTableModel().getNbMarkedFiles() > 0 || super.getFileTableCondition(fileTable);\r\n    }\r\n\r\n\r\n    /////////////////////////////\r\n    // MuAction implementation //\r\n    /////////////////////////////\r\n\r\n    @Override\r\n    public final void performAction() {\r\n        FileSet files = mainFrame.getActiveTable().getSelectedFiles();\r\n        // Perform the action only if at least one file is selected/marked\r\n        if (!files.isEmpty()) {\r\n            performAction(files);\r\n        }\r\n    }\r\n\r\n\r\n    //////////////////////\r\n    // Abstract methods //\r\n    //////////////////////\r\n    \r\n    /**\r\n     * Performs the action on the files that were selected/marked by the user in the currently active table.\r\n     *\r\n     * @param files files that were selected/marked by the user in the currently active table\r\n     */\r\n    public abstract void performAction(FileSet files);\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SetSameFolderAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.event.ActivePanelListener;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action equalizes both FileTable's current folders: the 'inactive' FileTable's current folder becomes\r\n * the active FileTable's one.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class SetSameFolderAction extends TcAction implements ActivePanelListener {\r\n\r\n    private SetSameFolderAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        mainFrame.addActivePanelListener(this);\r\n        \r\n        toggleEnabledState();\r\n    }\r\n    \r\n    /**\r\n     * Enables or disables this action based on the tab in the other panel being not lock,\r\n     * this action will be enabled, if not it will be disabled.\r\n     */\r\n    private void toggleEnabledState() {\r\n        setEnabled(!mainFrame.getInactivePanel().getTabs().getCurrentTab().isLocked());\r\n    }\r\n    \r\n    public void activePanelChanged(FolderPanel folderPanel) {\r\n    \ttoggleEnabledState();\r\n\t}\r\n\r\n    @Override\r\n    public void performAction() {\r\n        mainFrame.setSameFolder();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"SetSameFolder\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.VIEW;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_E, CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new SetSameFolderAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SetTabTitleAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.dialog.tab.TabTitleDialog;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.KeyStroke;\nimport java.util.Map;\n\n/**\n * Change the title of the selected tab.\n * In case empty string is entered, the title of the tab will be based on its current location.\n * \n * @author Arik Hadas\n */\n@InvokesDialog\npublic class SetTabTitleAction extends TcAction {\n\n\tprivate SetTabTitleAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n    \tnew TabTitleDialog(mainFrame, mainFrame.getActivePanel()).showDialog();\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n    \tpublic static final String ACTION_ID = \"SetTabTitle\";\n    \t\n\t\tpublic String getId() {\n\t\t\treturn ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t\treturn ActionCategory.TAB;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t\treturn null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n\t\t\treturn null;\n\t\t}\n\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n\t\t\treturn new SetTabTitleAction(mainFrame, properties);\n\t\t}\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ShowAboutAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.about.AboutDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action displays the 'About' dialog.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class ShowAboutAction extends TcAction {\r\n\r\n    private ShowAboutAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        new AboutDialog(mainFrame).showDialog();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"ShowAbout\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.MISC;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new ShowAboutAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ShowBookmarksQLAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.QuickLists;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\npublic class ShowBookmarksQLAction extends ShowQuickListAction {\r\n\t\r\n\tprivate ShowBookmarksQLAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n\t\tsuper(mainFrame, properties);\r\n\t}\r\n\t\r\n\t@Override\r\n    public void performAction() {\r\n\t\topenQuickList(QuickLists.BOOKMARKS);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n\tpublic static final class Descriptor extends AbstractActionDescriptor {\r\n\t\tpublic static final String ACTION_ID = \"ShowBookmarksQL\";\r\n\t\t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_4, KeyEvent.ALT_DOWN_MASK);\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new ShowBookmarksQLAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ShowDebugConsoleAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.dialog.debug.DebugConsoleDialog;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.KeyStroke;\nimport java.util.Map;\n\n/**\n * @author Maxence Bernard\n */\npublic class ShowDebugConsoleAction extends TcAction {\n\n    private ShowDebugConsoleAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n        new DebugConsoleDialog(mainFrame).showDialog();\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n    \tpublic static final String ACTION_ID = \"ShowDebugConsole\";\n\n\t\tpublic String getId() {\n\t\t\treturn ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t\treturn ActionCategory.MISC;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t\treturn null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n\t\t\treturn null;\n\t\t}\n\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n\t\t\treturn new ShowDebugConsoleAction(mainFrame, properties);\n\t\t}\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ShowEditorBookmarksQLAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/trolcommander\n * Copyright (C) 2014-2018 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.QuickLists;\n\nimport javax.swing.*;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\npublic class ShowEditorBookmarksQLAction extends ShowQuickListAction {\n\n    private ShowEditorBookmarksQLAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n        openQuickList(QuickLists.EDITOR_BOOKMARKS);\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new ShowRecentEditedFilesQLAction.Descriptor();\n    }\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"ShowEditorBookmarksQL\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.NAVIGATION;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_9, KeyEvent.ALT_DOWN_MASK);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new ShowEditorBookmarksQLAction(mainFrame, properties);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ShowFilePopupMenuAction.java",
    "content": "package com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.menu.TablePopupMenu;\nimport com.mucommander.ui.main.table.FileTable;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n\n/**\n * This actions shows the popup menu for currently selected file\n *\n * @author Miroslav Oujesky\n */\npublic class ShowFilePopupMenuAction extends SelectedFilesAction {\n\n    private ShowFilePopupMenuAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction(FileSet files) {\n\n        FileTable table = mainFrame.getActiveTable();\n\n        int currentRow = table.getSelectedRow();\n\n        // Does the row correspond to the parent '..' folder ?\n        boolean parentFolderClicked = currentRow == 0 && table.getFileTableModel().hasParentFolder();\n\n        TablePopupMenu popupMenu = new TablePopupMenu(\n                mainFrame,\n                table.getFolderPanel().getCurrentFolder(),\n                parentFolderClicked ? null : table.getSelectedFile(),\n                parentFolderClicked,\n                files\n        );\n\n        // find coordinates of current row and show popup menu bellow it\n        Rectangle rect = table.getCellRect(currentRow, table.getSelectedColumn(), true);\n        popupMenu.show(table, 5, rect.y + rect.height + 5);\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"ShowFilePopupMenu\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.FILES;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.ALT_DOWN_MASK);\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_CONTEXT_MENU, 0);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new ShowFilePopupMenuAction(mainFrame, properties);\n        }\n\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ShowFilePropertiesAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.file.PropertiesDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action pops up the file Properties dialog.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class ShowFilePropertiesAction extends SelectedFilesAction {\r\n\r\n    private ShowFilePropertiesAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction(FileSet files) {\r\n        new PropertiesDialog(mainFrame, files).showDialog();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"ShowFileProperties\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.FILES;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.ALT_DOWN_MASK);\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new ShowFilePropertiesAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ShowFoldersSizeAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.FileTable;\nimport com.mucommander.ui.main.table.views.full.FileTableModel;\n\nimport javax.swing.KeyStroke;\nimport java.util.Map;\n\n/**\n * @author Oleg Trifonov\n * Created on 24/03/15.\n */\npublic class ShowFoldersSizeAction extends ParentFolderAction {\n\n    private ShowFoldersSizeAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    protected void toggleEnabledState() {\n\n    }\n\n    @Override\n    public void performAction() {\n        FileTable activeTable = mainFrame.getActiveTable();\n        FileTableModel fileTableModel = (FileTableModel)activeTable.getModel();\n        for (AbstractFile file : fileTableModel.getFiles()) {\n            if (file.isDirectory()) {\n                fileTableModel.startDirectorySizeCalculation(activeTable, file);\n            }\n        }\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"ShowFoldersSize\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.VIEW;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return null;\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String, Object> properties) {\n            return new ShowFoldersSizeAction(mainFrame, properties);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ShowKeyboardShortcutsAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.help.ShortcutsDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action displays the 'Keyboard shortcuts' dialog that lists all available keyboard shortcuts sorted by topic.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class ShowKeyboardShortcutsAction extends TcAction {\r\n\r\n    private ShowKeyboardShortcutsAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        new ShortcutsDialog(mainFrame).showDialog();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"ShowKeyboardShortcuts\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.MISC;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new ShowKeyboardShortcutsAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ShowParentFoldersQLAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.QuickLists;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action shows ParentFoldersQL on the current active FileTable.\r\n * \r\n * @author Arik Hadas\r\n */\r\n\r\npublic class ShowParentFoldersQLAction extends ShowQuickListAction {\r\n\t\r\n\tprivate ShowParentFoldersQLAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n\t\tsuper(mainFrame, properties);\r\n\t}\r\n\t\r\n\t@Override\r\n    public void performAction() {\r\n\t\topenQuickList(QuickLists.PARENT_FOLDERS);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n\tpublic static final class Descriptor extends AbstractActionDescriptor {\r\n\t\tpublic static final String ACTION_ID = \"ShowParentFoldersQL\";\r\n\t\t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_1, KeyEvent.ALT_DOWN_MASK);\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new ShowParentFoldersQLAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ShowPreferencesAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.pref.general.GeneralPreferencesDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action shows up the preferences dialog.\r\n *\r\n * @author Maxence Bernard\r\n */\r\n@InvokesDialog\r\npublic class ShowPreferencesAction extends TcAction {\r\n\r\n    private ShowPreferencesAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n    \tGeneralPreferencesDialog.getDialog().showDialog();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"ShowPreferences\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.MISC;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new ShowPreferencesAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ShowQuickListAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.QuickLists;\r\n\r\nimport java.util.Map;\r\n\r\n/**\r\n * ShowFileTablePopupAction is an abstract action that shows pop up corresponding to the given\r\n * \tindex on the currently active FileTable.\r\n *\r\n * @author Arik Hadas\r\n */\r\nabstract class ShowQuickListAction extends TcAction {\r\n\r\n\tShowQuickListAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n\t\tsuper(mainFrame, properties);\t\t\r\n\t}\r\n\t\r\n\tvoid openQuickList(QuickLists quickList) {\r\n\t\tmainFrame.getActivePanel().showQuickList(quickList.ordinal());\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ShowRecentEditedFilesQLAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/trolcommander\n * Copyright (C) 2014-2018 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.QuickLists;\n\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * This action shows RecentEditedFilesQL on the current active FileTable.\n *\n * @author Oleg Trifonov\n */\npublic class ShowRecentEditedFilesQLAction extends ShowQuickListAction {\n\n    private ShowRecentEditedFilesQLAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n        openQuickList(QuickLists.RECENT_EDITED_FILES);\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"ShowRecentEditedFilesQL\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.NAVIGATION;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_8, KeyEvent.ALT_DOWN_MASK);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new ShowRecentEditedFilesQLAction(mainFrame, properties);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ShowRecentExecutedFilesQLAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.QuickLists;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action shows RecentExecutedFilesQL on the current active FileTable.\r\n * \r\n * @author Arik Hadas\r\n */\r\n\r\npublic class ShowRecentExecutedFilesQLAction extends ShowQuickListAction {\r\n\t\r\n\tprivate ShowRecentExecutedFilesQLAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n\t\tsuper(mainFrame, properties);\r\n\t}\r\n\t\r\n\t@Override\r\n    public void performAction() {\r\n\t\topenQuickList(QuickLists.RECENT_EXECUTED_FILES);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n\tpublic static final class Descriptor extends AbstractActionDescriptor {\r\n\t\tpublic static final String ACTION_ID = \"ShowRecentExecutedFilesQL\";\r\n\t\t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_3, KeyEvent.ALT_DOWN_MASK);\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new ShowRecentExecutedFilesQLAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ShowRecentLocationsQLAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.QuickLists;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action shows RecentLocationsQL on the current active FileTable.\r\n * \r\n * @author Arik Hadas\r\n */\r\n\r\npublic class ShowRecentLocationsQLAction extends ShowQuickListAction {\r\n\t\r\n\tprivate ShowRecentLocationsQLAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n\t\tsuper(mainFrame, properties);\r\n\t}\r\n\t\r\n\t@Override\r\n    public void performAction() {\r\n\t\topenQuickList(QuickLists.RECENT_ACCESSED_LOCATIONS);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n\tpublic static final class Descriptor extends AbstractActionDescriptor {\r\n\t\tpublic static final String ACTION_ID = \"ShowRecentLocationsQL\";\r\n\t\t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_2, KeyEvent.ALT_DOWN_MASK);\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new ShowRecentLocationsQLAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ShowRecentViewedFilesQLAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/trolcommander\n * Copyright (C) 2014-2018 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.QuickLists;\n\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * This action shows RecentViewedFilesQL on the current active FileTable.\n *\n * @author Oleg Trifonov\n */\npublic class ShowRecentViewedFilesQLAction extends ShowQuickListAction {\n    private ShowRecentViewedFilesQLAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n        openQuickList(QuickLists.RECENT_VIEWED_FILES);\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"ShowRecentViewedFilesQL\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.NAVIGATION;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_7, KeyEvent.ALT_DOWN_MASK);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new ShowRecentViewedFilesQLAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ShowRootFoldersQLAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.QuickLists;\n\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * This action shows RootFoldersQL on the current active FileTable.\n * \n * @author Arik Hadas\n */\npublic class ShowRootFoldersQLAction extends ShowQuickListAction {\n\n\tprivate ShowRootFoldersQLAction(MainFrame mainFrame, Map<String, Object> properties) {\n\t\tsuper(mainFrame, properties);\n\t}\n\t\n\t@Override\n    public void performAction() {\n\t\topenQuickList(QuickLists.ROOTS);\n\t}\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n\tpublic static final class Descriptor extends AbstractActionDescriptor {\n\t\tpublic static final String ACTION_ID = \"ShowRootFoldersQL\";\n\t\t\n\t\tpublic String getId() {\n\t\t\treturn ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t\treturn ActionCategory.NAVIGATION;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t\treturn null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_5, KeyEvent.ALT_DOWN_MASK);\n\t\t}\n\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n\t\t\treturn new ShowRootFoldersQLAction(mainFrame, properties);\n\t\t}\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ShowServerConnectionsAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.server.ShowServerConnectionsDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Displays a dialog which shows a list of open server connections and allows the user to close them.\r\n *\r\n * @author Maxence Bernard\r\n */\r\n@InvokesDialog\r\npublic class ShowServerConnectionsAction extends TcAction {\r\n\r\n    private ShowServerConnectionsAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        new ShowServerConnectionsDialog(mainFrame);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"ShowServerConnections\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.FILES;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_K, KeyEvent.SHIFT_DOWN_MASK | CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new ShowServerConnectionsAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ShowTabsQLAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.QuickLists;\n\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * This action shows TabsQL on the current active FileTable.\n * \n * @author Arik Hadas\n */\npublic class ShowTabsQLAction extends ShowQuickListAction {\n\t\n\tprivate ShowTabsQLAction(MainFrame mainFrame, Map<String, Object> properties) {\n\t\tsuper(mainFrame, properties);\n\t}\n\t\n\t@Override\n    public void performAction() {\n\t\topenQuickList(QuickLists.TABS);\n\t}\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\n\tpublic static final class Descriptor extends AbstractActionDescriptor {\n\t\tpublic static final String ACTION_ID = \"ShowTabsQL\";\n\t\t\n\t\tpublic String getId() {\n\t\t\treturn ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t\treturn ActionCategory.NAVIGATION;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t\treturn null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_6, KeyEvent.ALT_DOWN_MASK);\n\t\t}\n\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n\t\t\treturn new ShowTabsQLAction(mainFrame, properties);\n\t\t}\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SortByAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.event.ActivePanelListener;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.Column;\r\nimport com.mucommander.ui.main.table.FileTable;\r\n\r\nimport javax.swing.*;\r\nimport javax.swing.event.ChangeEvent;\r\nimport javax.swing.event.ListSelectionEvent;\r\nimport javax.swing.event.TableColumnModelEvent;\r\nimport javax.swing.event.TableColumnModelListener;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action sorts the currently active {@link com.mucommander.ui.main.table.FileTable} by a specified criterion.\r\n * If the table is already sorted by this particular criterion when the action is performed, the sort order will be\r\n * reversed.\r\n *\r\n * <p>This action is enabled only if the corresponding column is currently visible. This prevents this action from being\r\n * performed when the column is not visible, which is an unsupported operation.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic abstract class SortByAction extends TcAction implements ActivePanelListener, TableColumnModelListener {\r\n\r\n    /** FileTable column this action operates on */\r\n    protected Column column;\r\n\r\n    SortByAction(MainFrame mainFrame, Map<String, Object> properties, Column column) {\r\n        super(mainFrame, properties);\r\n\r\n        this.column = column;\r\n\r\n        mainFrame.addActivePanelListener(this);\r\n        mainFrame.getLeftPanel().getFileTable().getColumnModel().addColumnModelListener(this);\r\n        mainFrame.getRightPanel().getFileTable().getColumnModel().addColumnModelListener(this);\r\n\r\n        updateState(mainFrame.getActiveTable());\r\n    }\r\n\r\n    /**\r\n     * Updates this action's enable state, enabling this action if the corresponding column is visible and vice-versa.\r\n     *\r\n     * @param fileTable the FileTable this action currently operates on\r\n     */\r\n    private void updateState(FileTable fileTable) {\r\n        setEnabled(fileTable.isColumnVisible(column));\r\n    }\r\n\r\n\r\n    /////////////////////////////\r\n    // MuAction implementation //\r\n    /////////////////////////////\r\n\r\n    @Override\r\n    public void performAction() {\r\n        mainFrame.getActiveTable().sortBy(column);\r\n    }\r\n\r\n\r\n    ////////////////////////////////////////\r\n    // ActivePanelListener implementation //\r\n    ////////////////////////////////////////\r\n\r\n    public void activePanelChanged(FolderPanel folderPanel) {\r\n        // Update this action's enabled state when the active panel has changed\r\n        updateState(folderPanel.getFileTable());\r\n    }\r\n\r\n\r\n    /////////////////////////////////////////////\r\n    // TableColumnModelListener implementation //\r\n    /////////////////////////////////////////////\r\n\r\n    public void columnAdded(TableColumnModelEvent event) {\r\n        // Enable this action when the corresponding column has been made visible\r\n        if(event.getFromIndex()==column.ordinal())\r\n            setEnabled(true);\r\n    }\r\n\r\n    public void columnRemoved(TableColumnModelEvent event) {\r\n        // Disable this action when the corresponding column has been made invisible\r\n        if(event.getFromIndex()==column.ordinal())\r\n            setEnabled(false);\r\n    }\r\n\r\n    public void columnMoved(TableColumnModelEvent event) {\r\n    }\r\n\r\n    public void columnMarginChanged(ChangeEvent event) {\r\n    }\r\n\r\n    public void columnSelectionChanged(ListSelectionEvent event) {\r\n    }\r\n\r\n\r\n    ///////////////////\r\n    // Inner classes //\r\n    ///////////////////\r\n\r\n    public abstract static class Descriptor extends AbstractActionDescriptor {\r\n\r\n        private Column column;\r\n        private KeyStroke defaultKeyStroke;\r\n\r\n        public Descriptor(Column column, KeyStroke defaultKeyStroke) {\r\n            this.column = column;\r\n            this.defaultKeyStroke = defaultKeyStroke;\r\n        }\r\n\r\n        public String getId() {\r\n            return column.getSortByColumnActionId();\r\n        }\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n            return ActionCategory.VIEW;\r\n        }\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n            return null;\r\n        }\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return defaultKeyStroke;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SortByDateAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\nimport javax.swing.KeyStroke;\r\n\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.Column;\r\n\r\n/**\r\n * This action sorts the currently active {@link com.mucommander.ui.main.table.FileTable} by date.\r\n * If the table is already sorted by date, the sort order will be reversed.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class SortByDateAction extends SortByAction {\r\n\r\n    private SortByDateAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, Column.DATE);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends SortByAction.Descriptor {\r\n\r\n        public Descriptor() {\r\n            super(Column.DATE, KeyStroke.getKeyStroke(KeyEvent.VK_F6, KeyEvent.CTRL_DOWN_MASK));\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new SortByDateAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SortByExtensionAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\nimport javax.swing.KeyStroke;\r\n\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.Column;\r\n\r\n/**\r\n * This action sorts the currently active {@link com.mucommander.ui.main.table.FileTable} by extension.\r\n * If the table is already sorted by extension, the sort order will be reversed.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class SortByExtensionAction extends SortByAction {\r\n\r\n    private SortByExtensionAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, Column.EXTENSION);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends SortByAction.Descriptor {\r\n\r\n        public Descriptor() {\r\n            super(Column.EXTENSION, KeyStroke.getKeyStroke(KeyEvent.VK_F3, KeyEvent.CTRL_DOWN_MASK));\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new SortByExtensionAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SortByGroupAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\nimport javax.swing.KeyStroke;\r\n\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.Column;\r\n\r\n/**\r\n * This action sorts the currently active {@link com.mucommander.ui.main.table.FileTable} by group.\r\n * If the table is already sorted by group, the sort order will be reversed.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class SortByGroupAction extends SortByAction {\r\n\r\n    private SortByGroupAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, Column.GROUP);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends SortByAction.Descriptor {\r\n\r\n        public Descriptor() {\r\n            super(Column.GROUP, KeyStroke.getKeyStroke(KeyEvent.VK_F9, KeyEvent.CTRL_DOWN_MASK));\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new SortByGroupAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SortByNameAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\nimport javax.swing.KeyStroke;\r\n\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.Column;\r\n\r\n/**\r\n * This action sorts the currently active {@link com.mucommander.ui.main.table.FileTable} by name.\r\n * If the table is already sorted by name, the sort order will be reversed.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class SortByNameAction extends SortByAction {\r\n\r\n    private SortByNameAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, Column.NAME);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends SortByAction.Descriptor {\r\n\r\n        public Descriptor() {\r\n            super(Column.NAME, KeyStroke.getKeyStroke(KeyEvent.VK_F4, KeyEvent.CTRL_DOWN_MASK));\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new SortByNameAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SortByOwnerAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\nimport javax.swing.KeyStroke;\r\n\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.Column;\r\n\r\n/**\r\n * This action sorts the currently active {@link com.mucommander.ui.main.table.FileTable} by owner.\r\n * If the table is already sorted by owner, the sort order will be reversed.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class SortByOwnerAction extends SortByAction {\r\n\r\n    private SortByOwnerAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, Column.OWNER);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends SortByAction.Descriptor {\r\n\r\n        public Descriptor() {\r\n            super(Column.OWNER, KeyStroke.getKeyStroke(KeyEvent.VK_F8, KeyEvent.CTRL_DOWN_MASK));\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new SortByOwnerAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SortByPermissionsAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\nimport javax.swing.KeyStroke;\r\n\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.Column;\r\n\r\n/**\r\n * This action sorts the currently active {@link com.mucommander.ui.main.table.FileTable} by permissions.\r\n * If the table is already sorted by permissions, the sort order will be reversed.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class SortByPermissionsAction extends SortByAction {\r\n\r\n    private SortByPermissionsAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, Column.PERMISSIONS);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends SortByAction.Descriptor {\r\n\r\n        public Descriptor() {\r\n            super(Column.PERMISSIONS, KeyStroke.getKeyStroke(KeyEvent.VK_F7, KeyEvent.CTRL_DOWN_MASK));\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new SortByPermissionsAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SortBySizeAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\nimport javax.swing.KeyStroke;\r\n\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.Column;\r\n\r\n/**\r\n * This action sorts the currently active {@link com.mucommander.ui.main.table.FileTable} by size.\r\n * If the table is already sorted by size, the sort order will be reversed.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class SortBySizeAction extends SortByAction {\r\n\r\n    private SortBySizeAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, Column.SIZE);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends SortByAction.Descriptor {\r\n\r\n        public Descriptor() {\r\n            super(Column.SIZE, KeyStroke.getKeyStroke(KeyEvent.VK_F5, KeyEvent.CTRL_DOWN_MASK));\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new SortBySizeAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SplitEquallyAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Positions the split pane divider in the middle so that both folder panels have the same space.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class SplitEquallyAction extends TcAction {\r\n\r\n    private SplitEquallyAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        mainFrame.getSplitPane().setSplitRatio(0.5f);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"SplitEqually\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.VIEW;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new SplitEquallyAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SplitFileAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport java.util.Map;\r\n\r\nimport javax.swing.KeyStroke;\r\n\r\nimport com.mucommander.commons.file.FileOperation;\r\nimport com.mucommander.commons.file.filter.AndFileFilter;\r\nimport com.mucommander.commons.file.filter.AttributeFileFilter;\r\nimport com.mucommander.commons.file.filter.AttributeFileFilter.FileAttribute;\r\nimport com.mucommander.commons.file.filter.FileOperationFilter;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.file.SplitFileDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\n/**\r\n * This action invokes the split file dialog which allows to split the selected file into several parts.\r\n *\r\n * @author Mariusz Jakubowski\r\n */\r\n@InvokesDialog\r\npublic class SplitFileAction extends SelectedFileAction {\r\n\r\n    private SplitFileAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        setSelectedFileFilter(new AndFileFilter(\r\n            new AttributeFileFilter(FileAttribute.DIRECTORY, true),\r\n            new FileOperationFilter(FileOperation.READ_FILE)\r\n        ));\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        new SplitFileDialog(mainFrame,\r\n                mainFrame.getActiveTable().getSelectedFile(),\r\n                mainFrame.getInactivePanel().getCurrentFolder()\r\n        ).showDialog();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"SplitFile\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new SplitFileAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SplitHorizontallyAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Splits the folder panels horizontally (left/right) within the MainFrame.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class SplitHorizontallyAction extends TcAction {\r\n\r\n    private SplitHorizontallyAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        mainFrame.setSplitPaneOrientation(false);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"SplitHorizontally\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.VIEW;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new SplitHorizontallyAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SplitVerticallyAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Splits the folder panels vertically (top/bottom) within the MainFrame.\r\n * This is the default split orientation.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class SplitVerticallyAction extends TcAction {\r\n\r\n    private SplitVerticallyAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        mainFrame.setSplitPaneOrientation(true);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"SplitVertically\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.VIEW;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n\t\t\treturn new SplitVerticallyAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/StopAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.core.LocationChanger.ChangeFolderThread;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.event.LocationEvent;\r\nimport com.mucommander.ui.event.LocationListener;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action is invoked to stop a running location change.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class StopAction extends TcAction implements LocationListener {\r\n\r\n    private StopAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        // This action is initially disabled and enabled only during a folder change\r\n        setEnabled(false);\r\n\r\n        // Listen to location change events\r\n        mainFrame.getLeftPanel().getLocationManager().addLocationListener(this);\r\n        mainFrame.getRightPanel().getLocationManager().addLocationListener(this);\r\n\r\n        // This action must be available while in 'no events mode', that's the whole point \r\n        setHonourNoEventsMode(false);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        FolderPanel folderPanel = mainFrame.getActivePanel();\r\n        ChangeFolderThread changeFolderThread = folderPanel.getChangeFolderThread();\r\n\r\n        if (changeFolderThread != null) {\r\n            changeFolderThread.tryKill();\r\n        }\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    //////////////////////////////\r\n    // LocationListener methods //\r\n    //////////////////////////////\r\n\r\n    public void locationChanged(LocationEvent e) {\r\n        setEnabled(false);\r\n    }\r\n\r\n    public void locationChanging(LocationEvent e) {\r\n        setEnabled(true);\r\n    }\r\n\r\n    public void locationCancelled(LocationEvent e) {\r\n        setEnabled(false);\r\n    }\r\n\r\n    public void locationFailed(LocationEvent e) {\r\n        setEnabled(false);\r\n    }\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"Stop\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new StopAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SwapFoldersAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action swaps both FileTable's current folders: the left table's current folder becomes the right table's one\r\n * and vice versa.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class SwapFoldersAction extends TcAction {\r\n\r\n    private SwapFoldersAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        mainFrame.swapFolders();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"SwapFolders\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.VIEW;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_U, CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new SwapFoldersAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/SwitchActiveTableAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.FileTable;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action switches the currently active FileTable, that is gives focus to the FileTable that currently doesn't\r\n * have it.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class SwitchActiveTableAction extends TcAction {\r\n\r\n    private SwitchActiveTableAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        FileTable activeTable = mainFrame.getActiveTable();\r\n        FileTable leftTable = mainFrame.getLeftPanel().getFileTable();\r\n        FileTable rightTable = mainFrame.getRightPanel().getFileTable();\r\n        if (activeTable == leftTable) {\r\n            rightTable.requestFocus();\r\n        } else if(activeTable == rightTable) {\r\n            leftTable.requestFocus();\r\n        }\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"SwitchActiveTable\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.NAVIGATION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_TAB, KeyEvent.SHIFT_DOWN_MASK);\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new SwitchActiveTableAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/TerminalAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2026 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.desktop.DesktopManager;\nimport com.mucommander.process.ProcessRunner;\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.main.MainFrame;\nimport lombok.extern.slf4j.Slf4j;\n\nimport javax.swing.*;\nimport java.awt.event.KeyEvent;\nimport java.io.IOException;\nimport java.util.Map;\n\nimport static com.mucommander.conf.TcPreferences.*;\n\n/**\n * @author Oleg Trifonov\n * Created on 17/12/13.\n */\n@Slf4j\npublic class TerminalAction extends ParentFolderAction {\n    /**\n     * Creates a new instance of <code>InternalViewAction</code>.\n     *\n     * @param mainFrame  frame to which the action is attached.\n     * @param properties action's properties.\n     */\n    private TerminalAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n        AbstractFile currentFolder = mainFrame.getActiveTable().getFileTableModel().getCurrentFolder();\n        if (OsFamily.LINUX.isCurrent()) {\n            performOnLinux(currentFolder);\n        } else {\n            String cmd = getConsoleCommand(currentFolder);\n            try {\n                ProcessRunner.execute(cmd, currentFolder);\n            } catch (Exception e) {\n                log.error(\"Execution error\", e);\n            }\n        }\n    }\n\n    private void performOnLinux(AbstractFile currentFolder) {\n        String[] tokens = getTerminalCommand().split(\" \");\n        for (int i = 0; i < tokens.length; i++) {\n            if (tokens[i].contains(\"$p\")) {\n                tokens[i] = tokens[i].replace(\"$p\", currentFolder.getAbsolutePath());\n            }\n        }\n        try {\n            ProcessRunner.execute(tokens, currentFolder);\n        } catch (IOException e) {\n            log.error(\"Execution error\", e);\n        }\n    }\n\n    private static String getConsoleCommand(AbstractFile folder) {\n        String cmd = getTerminalCommand();\n        String path = folder.getAbsolutePath();\n        return cmd.replace(\"$p\", path);\n    }\n\n\n    private static String getTerminalCommand() {\n        return switch (useCustomExternalTerminal()) {\n            case TERMINAL_DEFAULT -> DesktopManager.getDefaultTerminalAppCommand();\n            case TERMINAL_CUSTOM -> getCustomExternalTerminal();\n            case TERMINAL_ITERM -> \"open -a iTerm .\";\n            default -> {\n                log.error(\"Unknown terminal type\");\n                yield \"\";\n            }\n        };\n    }\n\n    private static String getCustomExternalTerminal() {\n        return TcConfigurations.getPreferences().getVariable(TcPreference.CUSTOM_EXTERNAL_TERMINAL);\n    }\n\n    private static int useCustomExternalTerminal() {\n        return TcConfigurations.getPreferences().getVariable(TcPreference.EXTERNAL_TERMINAL_TYPE, TcPreferences.DEFAULT_TERMINAL_TYPE);\n    }\n\n    @Override\n    protected void toggleEnabledState() {\n\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"Terminal\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.MISC;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_F2, KeyEvent.SHIFT_DOWN_MASK);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new TerminalAction(mainFrame, properties);\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/TerminalAltAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2026 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.desktop.DesktopManager;\nimport com.mucommander.desktop.macos.OSXApplications;\nimport com.mucommander.process.ProcessRunner;\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.main.MainFrame;\nimport lombok.extern.slf4j.Slf4j;\n\nimport javax.swing.*;\nimport java.awt.event.KeyEvent;\nimport java.io.IOException;\nimport java.util.Map;\n\nimport static com.mucommander.conf.TcPreferences.*;\n\n\n@Slf4j\npublic class TerminalAltAction extends ParentFolderAction {\n    /**\n     * Creates a new instance of <code>InternalViewAction</code>.\n     *\n     * @param mainFrame  frame to which the action is attached.\n     * @param properties action's properties.\n     */\n    private TerminalAltAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n        AbstractFile currentFolder = mainFrame.getActiveTable().getFileTableModel().getCurrentFolder();\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n\n        }\n        if (OsFamily.LINUX.isCurrent()) {\n            performOnLinux(currentFolder);\n        } else {\n            String cmd = getConsoleCommand(currentFolder);\n            try {\n                ProcessRunner.execute(cmd, currentFolder);\n            } catch (Exception e) {\n                log.error(\"Execution error\", e);\n            }\n        }\n    }\n\n    private void performOnLinux(AbstractFile currentFolder) {\n        String[] tokens = getTerminalCommand().split(\" \");\n        for (int i = 0; i < tokens.length; i++) {\n            if (tokens[i].contains(\"$p\")) {\n                tokens[i] = tokens[i].replace(\"$p\", currentFolder.getAbsolutePath());\n            }\n        }\n        try {\n            ProcessRunner.execute(tokens, currentFolder);\n        } catch (IOException e) {\n            log.error(\"Execution error\", e);\n        }\n    }\n\n    private static String getConsoleCommand(AbstractFile folder) {\n        String cmd = getTerminalCommand();\n        String path = folder.getAbsolutePath();\n        return cmd.replace(\"$p\", path);\n    }\n\n\n    private static String getTerminalCommand() {\n        int terminalType = useCustomExternalTerminal();\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            if (terminalType == TERMINAL_ITERM || terminalType == TERMINAL_CUSTOM) {\n                terminalType = TERMINAL_DEFAULT;\n            } else if (terminalType == TERMINAL_DEFAULT) {\n                if (OSXApplications.iTermInstalled()) {\n                    terminalType = TERMINAL_ITERM;\n                } else if (!getCustomExternalTerminal().isBlank()) {\n                    terminalType = TERMINAL_CUSTOM;\n                }\n            }\n        }\n\n        return switch (terminalType) {\n            case TERMINAL_DEFAULT -> DesktopManager.getDefaultTerminalAppCommand();\n            case TERMINAL_CUSTOM -> getCustomExternalTerminal();\n            case TERMINAL_ITERM -> \"open -a iTerm .\";\n            default -> {\n                log.error(\"Unknown terminal type\");\n                yield \"\";\n            }\n        };\n    }\n\n    private static String getCustomExternalTerminal() {\n        return TcConfigurations.getPreferences().getVariable(TcPreference.CUSTOM_EXTERNAL_TERMINAL).trim();\n    }\n\n    private static int useCustomExternalTerminal() {\n        return TcConfigurations.getPreferences().getVariable(TcPreference.EXTERNAL_TERMINAL_TYPE, TcPreferences.DEFAULT_TERMINAL_TYPE);\n    }\n\n    @Override\n    protected void toggleEnabledState() {\n\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"TerminalAlternative\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.MISC;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_F2, KeyEvent.SHIFT_DOWN_MASK|KeyEvent.ALT_DOWN_MASK);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new TerminalAltAction(mainFrame, properties);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/TerminalPanelAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * @author Oleg Trifonov\n *\n * Created on 24/10/14.\n */\npublic class TerminalPanelAction extends TcAction {\n\n    private TerminalPanelAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n\n    @Override\n    public void performAction() {\n        mainFrame.toggleTerminalPanel();\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"TerminalPanel\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.ALL;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String, Object> properties) {\n            return new TerminalPanelAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/TextEditorsListAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/trolcommander\n * Copyright (C) 2014-2017 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.quicklist.ViewedAndEditedFilesQL;\n\nimport javax.swing.*;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\npublic class TextEditorsListAction extends TcAction {\n    private TextEditorsListAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n\n    @Override\n    public void performAction() {\n        ViewedAndEditedFilesQL viewedAndEditedFilesQL = new ViewedAndEditedFilesQL(mainFrame.getActivePanel(), null);\n        if (!viewedAndEditedFilesQL.isEmpty()) {\n            viewedAndEditedFilesQL.show();\n        }\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new TextEditorsListAction.Descriptor();\n    }\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"TextEditorsList\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.WINDOW;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            if (OsFamily.MAC_OS_X.isCurrent()) {\n                return KeyStroke.getKeyStroke(KeyEvent.VK_TAB, KeyEvent.ALT_DOWN_MASK);\n            } else {\n                return KeyStroke.getKeyStroke(KeyEvent.VK_TAB, KeyEvent.CTRL_DOWN_MASK);\n            }\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new TextEditorsListAction(mainFrame, properties);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ToggleAutoSizeAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.conf.TcConfigurations;\r\nimport com.mucommander.conf.TcPreference;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action toggles the 'auto-size columns' option on the currently active FileTable, which automatically resizes\r\n * columns to fit the table's width.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class ToggleAutoSizeAction extends TcAction {\r\n\r\n    private ToggleAutoSizeAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        boolean enabled;\r\n\r\n        mainFrame.setAutoSizeColumnsEnabled(enabled = !mainFrame.isAutoSizeColumnsEnabled());\r\n        TcConfigurations.getPreferences().setVariable(TcPreference.AUTO_SIZE_COLUMNS, enabled);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"ToggleAutoSize\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.VIEW;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new ToggleAutoSizeAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ToggleColumnAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.Column;\r\n\r\nimport javax.swing.*;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Shows/hides a specified column of the currently active FileTable. If the column is currently visible, this action\r\n * will hide it and vice-versa.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic abstract class ToggleColumnAction extends TcAction {\r\n\r\n    /** Index of the FileTable column this action operates on */\r\n    protected Column column;\r\n\r\n    ToggleColumnAction(MainFrame mainFrame, Map<String, Object> properties, Column column) {\r\n        super(mainFrame, properties);\r\n\r\n        this.column = column;\r\n        updateLabel();\r\n    }\r\n\r\n    private boolean isColumnVisible() {\r\n        return mainFrame.getActiveTable().isColumnVisible(column);\r\n    }\r\n\r\n    private void updateLabel() {\r\n        setLabel(Translator.get(isColumnVisible() ? \"ToggleColumn.hide\" : \"ToggleColumn.show\", column.getLabel()));\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        boolean show = !isColumnVisible();\r\n        mainFrame.getActiveTable().setColumnEnabled(column, show);\r\n        if (show) {\r\n            mainFrame.getActivePanel().tryRefreshCurrentFolder();\r\n        }\r\n    }\r\n\r\n\r\n    public static abstract class Descriptor extends AbstractActionDescriptor {\r\n\r\n        private final Column column;\r\n\r\n        public Descriptor(Column column) {\r\n            this.column = column;\r\n        }\r\n\r\n        public String getId() {\r\n            return column.getToggleColumnActionId();\r\n        }\r\n\r\n        public ActionCategory getCategory() {\r\n            return ActionCategory.VIEW;\r\n        }\r\n\r\n        public KeyStroke getDefaultAltKeyStroke() {\r\n            return null;\r\n        }\r\n\r\n        public KeyStroke getDefaultKeyStroke() {\r\n            return null;\r\n        }\r\n\r\n        @Override\r\n        public String getLabel() {\r\n            return Translator.get(\"ToggleColumn.show\", column.getLabel());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ToggleCommandBarAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.conf.TcConfigurations;\r\nimport com.mucommander.conf.TcPreference;\r\nimport com.mucommander.conf.TcPreferences;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.commandbar.CommandBar;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action shows/hides the current MainFrame's {@link com.mucommander.ui.main.commandbar.CommandBar} depending on its\r\n * current visible state: if it is visible, hides it, if not shows it.\r\n *\r\n * <p>This action's label will be updated to reflect the current visible state.\r\n *\r\n * <p>Each time this action is executed, the new current visible state is stored in the configuration so that\r\n * new MainFrame windows will use it to determine whether the CommandBar has to be made visible or not.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class ToggleCommandBarAction extends TcAction {\r\n\r\n    private ToggleCommandBarAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n        updateLabel(TcConfigurations.getPreferences().getVariable(TcPreference.COMMAND_BAR_VISIBLE, TcPreferences.DEFAULT_COMMAND_BAR_VISIBLE));\r\n    }\r\n\r\n    private void updateLabel(boolean visible) {\r\n        setLabel(Translator.get(visible ? Descriptor.ACTION_ID + \".hide\" : Descriptor.ACTION_ID + \".show\"));\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        CommandBar commandBar = mainFrame.getCommandBar();\r\n        boolean visible = !commandBar.isVisible();\r\n        // Save the last command bar visible state in the configuration, this will become the default for new MainFrame windows.\r\n        TcConfigurations.getPreferences().setVariable(TcPreference.COMMAND_BAR_VISIBLE, visible);\r\n        // Change the label to reflect the new command bar state\r\n        updateLabel(visible);\r\n        // Show/hide the command bar\r\n        commandBar.setVisible(visible);\r\n        mainFrame.getJFrame().validate();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"ToggleCommandBar\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n        public ActionCategory getCategory() {\r\n\t\t    return ActionCategory.VIEW;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n        @Override\r\n        public String getLabelKey() {\r\n\t\t    return ACTION_ID + \".show\";\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new ToggleCommandBarAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ToggleDateColumnAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport java.util.Map;\r\n\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.Column;\r\n\r\n/**\r\n * Shows/hides the 'Date' column of the currently active FileTable. If the column is currently visible, this action\r\n * will hide it and vice versa.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class ToggleDateColumnAction extends ToggleColumnAction {\r\n\r\n    private ToggleDateColumnAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, Column.DATE);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends ToggleColumnAction.Descriptor {\r\n        public Descriptor() {\r\n            super(Column.DATE);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new ToggleDateColumnAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ToggleExtensionColumnAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport java.util.Map;\r\n\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.Column;\r\n\r\n/**\r\n * Shows/hides the 'Extension' column of the currently active FileTable. If the column is currently visible, this action\r\n * will hide it and vice-versa.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class ToggleExtensionColumnAction extends ToggleColumnAction {\r\n\r\n    private ToggleExtensionColumnAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, Column.EXTENSION);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends ToggleColumnAction.Descriptor {\r\n        public Descriptor() {\r\n            super(Column.EXTENSION);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new ToggleExtensionColumnAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ToggleFoldersAlwaysAlphabeticalAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.conf.TcConfigurations;\r\nimport com.mucommander.conf.TcPreference;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.FileTable;\r\n\r\nimport javax.swing.*;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action toggles the 'Show folders first' option, which controls whether folders are displayed first in the\r\n * FileTable or mixed with regular files.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class ToggleFoldersAlwaysAlphabeticalAction extends TcAction {\r\n\r\n    private ToggleFoldersAlwaysAlphabeticalAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        FileTable activeTable = mainFrame.getActiveTable();\r\n        boolean foldersAlwaysAlphabetical = !activeTable.getSortInfo().getFoldersAlwaysAlphabetical();\r\n        activeTable.setFoldersAlwaysAlphabetical(foldersAlwaysAlphabetical);\r\n        TcConfigurations.getPreferences().setVariable(TcPreference.FOLDERS_ALWAYS_ALPHABETICAL, foldersAlwaysAlphabetical);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"ToggleFoldersAlwaysAlphabetical\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.VIEW;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new ToggleFoldersAlwaysAlphabeticalAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ToggleGroupColumnAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.Column;\r\n\r\nimport java.util.Map;\r\n\r\n/**\r\n * Shows/hides the 'Group' column of the currently active FileTable. If the column is currently visible, this action\r\n * will hide it and vice-versa.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class ToggleGroupColumnAction extends ToggleColumnAction {\r\n\r\n    private ToggleGroupColumnAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, Column.GROUP);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends ToggleColumnAction.Descriptor {\r\n        public Descriptor() {\r\n            super(Column.GROUP);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new ToggleGroupColumnAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ToggleHiddenFilesAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.conf.TcConfigurations;\r\nimport com.mucommander.conf.TcPreference;\r\nimport com.mucommander.conf.TcPreferences;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.WindowManager;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * A simple action that toggles hidden files visibility on and off.\r\n * @author Nicolas Rinaudo\r\n */\r\npublic class ToggleHiddenFilesAction extends TcAction {\r\n    // - Initialization ------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /**\r\n     * Creates a new <code>ToggleHiddenFilesAction</code>.\r\n     */\r\n    private ToggleHiddenFilesAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n\r\n    // - Action code ---------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /**\r\n     * Toggles hidden files display on and off and requests for all file tables to be repainted.\r\n     */\r\n    @Override\r\n    public void performAction() {\r\n        boolean show = TcConfigurations.getPreferences().getVariable(TcPreference.SHOW_HIDDEN_FILES, TcPreferences.DEFAULT_SHOW_HIDDEN_FILES);\r\n    \tTcConfigurations.getPreferences().setVariable(TcPreference.SHOW_HIDDEN_FILES, !show);\r\n        WindowManager.tryRefreshCurrentFolders();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"ToggleHiddenFiles\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.VIEW;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_PERIOD, CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new ToggleHiddenFilesAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ToggleLockTabAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.tabs.FileTableTabs;\nimport com.mucommander.utils.text.Translator;\n\nimport javax.swing.*;\nimport java.util.Map;\n\n/**\n * This action locks/unlocks the currently selected {@link com.mucommander.ui.main.tabs.FileTableTab} depending on its\n * current locking state: if it is locked, unlock it, if not lock it.\n *\n * <p>This action's label will be updated to reflect the locking state of the currently selected tab.\n *\n * @author Arik Hadas\n */\npublic class ToggleLockTabAction extends ActiveTabAction {\n\n\tprivate ToggleLockTabAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    private void updateLabel(boolean locked) {\n        setLabel(Translator.get(locked ? Descriptor.ACTION_ID + \".unlock\" : Descriptor.ACTION_ID + \".lock\"));\n    }\n\n    @Override\n    public void performAction() {\n    \tboolean lock = !mainFrame.getActivePanel().getTabs().getCurrentTab().isLocked();\n\n\t\tFileTableTabs tabs = mainFrame.getActivePanel().getTabs();\n    \tif (lock) {\n\t\t\ttabs.lock();\n\t\t} else {\n\t\t\ttabs.unlock();\n\t\t}\n    \t\n        // Change the label to reflect the new tab's locking state\n        updateLabel(lock);\n    }\n\n\t@Override\n\tpublic ActionDescriptor getDescriptor() {\n\t\treturn new Descriptor();\n\t}\n\n\t@Override\n\tprotected void toggleEnabledState() {\n\t\tupdateLabel(mainFrame.getActivePanel().getTabs().getCurrentTab().isLocked());\n\t}\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n    \tpublic static final String ACTION_ID = \"ToggleLockTab\";\n    \t\n\t\tpublic String getId() {\n\t\t\treturn ACTION_ID;\n\t\t}\n\n\t\tpublic ActionCategory getCategory() {\n\t\t\treturn ActionCategory.TAB;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\n\t\t\treturn null;\n\t\t}\n\n\t\tpublic KeyStroke getDefaultKeyStroke() {\n\t\t\treturn null;\n\t\t}\n\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n\t\t\treturn new ToggleLockTabAction(mainFrame, properties);\n\t\t}\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ToggleOwnerColumnAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.Column;\r\n\r\nimport java.util.Map;\r\n\r\n/**\r\n * Shows/hides the 'Owner' column of the currently active FileTable. If the column is currently visible, this action\r\n * will hide it and vice-versa.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class ToggleOwnerColumnAction extends ToggleColumnAction {\r\n\r\n    private ToggleOwnerColumnAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, Column.OWNER);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends ToggleColumnAction.Descriptor {\r\n        public Descriptor() {\r\n            super(Column.OWNER);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new ToggleOwnerColumnAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/TogglePanelPreviewModeAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * @author Oleg Trifonov\n *\n * Created on 26/09/2016.\n */\npublic class TogglePanelPreviewModeAction extends TcAction {\n\n    /**\n     * Creates a new <code>ToggleTableViewModeShortAction</code>\n     *\n     * @param mainFrame  the MainFrame to associate with this new MuAction\n     * @param properties the initial properties to use in this action. The Hashtable may simply be empty if no initial\n     */\n    private TogglePanelPreviewModeAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n        FolderPanel panel;\n        if (getMainFrame().getActivePanel() == getMainFrame().getLeftPanel()) {\n            panel = getMainFrame().getRightPanel();\n        } else {\n            panel = getMainFrame().getLeftPanel();\n        }\n        panel.setPreviewMode(!panel.isPreviewMode());\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new TogglePanelPreviewModeAction.Descriptor();\n    }\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"TogglePanelPreviewMode\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.VIEW;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            if (OsFamily.getCurrent() == OsFamily.MAC_OS_X) {\n                return KeyStroke.getKeyStroke(KeyEvent.VK_Q, KeyEvent.CTRL_DOWN_MASK);\n            } else {\n                return KeyStroke.getKeyStroke(KeyEvent.VK_Z, KeyEvent.CTRL_DOWN_MASK);\n            }\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String, Object> properties) {\n            return new TogglePanelPreviewModeAction(mainFrame, properties);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/TogglePermissionsColumnAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.Column;\r\n\r\nimport java.util.Map;\r\n\r\n/**\r\n * Shows/hides the 'Permissions' column of the currently active FileTable. If the column is currently visible, this action\r\n * will hide it and vice-versa.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class TogglePermissionsColumnAction extends ToggleColumnAction {\r\n\r\n    private TogglePermissionsColumnAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, Column.PERMISSIONS);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends ToggleColumnAction.Descriptor {\r\n        public Descriptor() {\r\n            super(Column.PERMISSIONS);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new TogglePermissionsColumnAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ToggleShowFoldersFirstAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.conf.TcConfigurations;\r\nimport com.mucommander.conf.TcPreference;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.FileTable;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action toggles the 'Show folders first' option, which controls whether folders are displayed first in the\r\n * FileTable or mixed with regular files.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class ToggleShowFoldersFirstAction extends TcAction {\r\n\r\n    private ToggleShowFoldersFirstAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        FileTable activeTable = mainFrame.getActiveTable();\r\n        boolean showFoldersFirst = !activeTable.getSortInfo().getFoldersFirst();\r\n        activeTable.setFoldersFirst(showFoldersFirst);\r\n        TcConfigurations.getPreferences().setVariable(TcPreference.SHOW_FOLDERS_FIRST, showFoldersFirst);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"ToggleShowFoldersFirst\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.VIEW;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new ToggleShowFoldersFirstAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ToggleSinglePanelAction.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport java.util.Map;\n\nimport javax.swing.KeyStroke;\n\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\n\n/**\n * Toggles isVisible state of the right panel, imitating single/two panel view switch.\n */\npublic class ToggleSinglePanelAction extends TcAction {\n\n    private static final float TWO_PANELS_DEFAULT_RATIO = 0.5f;\n    private float previousRatio = TWO_PANELS_DEFAULT_RATIO;\n\n    private ToggleSinglePanelAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    private void hideInactivePanel() {\n        mainFrame.getInactivePanel().getPanel().setVisible(false);\n    }\n\n    private void showInactivePanel() {\n        mainFrame.getInactivePanel().getPanel().setVisible(true);\n    }\n\n\n    @Override\n    public void performAction() {\n        // we want to restore old two panel ratio\n        boolean isSinglePanelViewNow = mainFrame.toggleSinglePanel();\n\n\n        if (isSinglePanelViewNow) {\n            previousRatio = mainFrame.getSplitPane().getSplitRatio();\n            hideInactivePanel();\n\n        } else {\n            mainFrame.getSplitPane().setSplitRatio(previousRatio);\n            showInactivePanel();\n        }\n\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n\n    public static class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"ToggleSinglePanel\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.VIEW;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return null;\n        }\n\n        @Override\n        public TcAction createAction(MainFrame mainFrame, Map<String, Object> properties) {\n            return new ToggleSinglePanelAction(mainFrame, properties);\n        }\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ToggleSizeColumnAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.table.Column;\r\n\r\nimport java.util.Map;\r\n\r\n/**\r\n * Shows/hides the 'Size' column of the currently active FileTable. If the column is currently visible, this action\r\n * will hide it and vice-versa.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class ToggleSizeColumnAction extends ToggleColumnAction {\r\n\r\n    private ToggleSizeColumnAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, Column.SIZE);\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends ToggleColumnAction.Descriptor {\r\n        public Descriptor() {\r\n            super(Column.SIZE);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new ToggleSizeColumnAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ToggleStatusBarAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.conf.TcConfigurations;\r\nimport com.mucommander.conf.TcPreference;\r\nimport com.mucommander.conf.TcPreferences;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.statusbar.StatusBar;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action shows/hides the current MainFrame's {@link com.mucommander.ui.main.statusbar.StatusBar} depending on its\r\n * current visible state: if it is visible, hides it, if not shows it.\r\n *\r\n * <p>This action's label will be updated to reflect the current visible state.\r\n *\r\n * <p>Each time this action is executed, the new current visible state is stored in the configuration so that\r\n * new MainFrame windows will use it to determine whether the StatusBar has to be made visible or not.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class ToggleStatusBarAction extends TcAction {\r\n\r\n    private ToggleStatusBarAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n        updateLabel(TcConfigurations.getPreferences().getVariable(TcPreference.STATUS_BAR_VISIBLE, TcPreferences.DEFAULT_STATUS_BAR_VISIBLE));\r\n    }\r\n\r\n    private void updateLabel(boolean visible) {\r\n        setLabel(Translator.get(visible?Descriptor.ACTION_ID+\".hide\":Descriptor.ACTION_ID+\".show\"));\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        StatusBar statusBar = mainFrame.getStatusBar();\r\n        boolean visible = !statusBar.isVisible();\r\n        // Save the last status bar visible state in the configuration, this will become the default for new MainFrame windows.\r\n        TcConfigurations.getPreferences().setVariable(TcPreference.STATUS_BAR_VISIBLE, visible);\r\n        // Change the label to reflect the new status bar state\r\n        updateLabel(visible);\r\n        // Show/hide the status bar\r\n        statusBar.setVisible(visible);\r\n        mainFrame.getJFrame().validate();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"ToggleStatusBar\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.VIEW;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n        @Override\r\n        public String getLabelKey() {\r\n\t\t    return ACTION_ID+  \".show\";\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new ToggleStatusBarAction(mainFrame, properties);\r\n        }\r\n\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ToggleTableViewModeCompactAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.views.TableViewMode;\n\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * @author Oleg Trifonov\n * Created on 15/04/15.\n */\npublic class ToggleTableViewModeCompactAction extends TcAction {\n\n    /**\n     * Creates a new <code>ToggleTableViewModeCompactAction</code>\n     *\n     * @param mainFrame  the MainFrame to associate with this new MuAction\n     * @param properties the initial properties to use in this action. The Hashtable may simply be empty if no initial\n     */\n    private ToggleTableViewModeCompactAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n        getMainFrame().getActiveTable().setViewMode(TableViewMode.COMPACT);\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"ToggleTableViewModeCompact\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.VIEW;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_2, KeyEvent.CTRL_DOWN_MASK);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String, Object> properties) {\n            return new ToggleTableViewModeCompactAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ToggleTableViewModeFullAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.views.TableViewMode;\n\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * @author Oleg Trifonov\n * Created on 15/04/15.\n */\npublic class ToggleTableViewModeFullAction extends TcAction {\n\n    /**\n     * Creates a new <code>ToggleTableViewModeFullAction</code>\n     *\n     * @param mainFrame  the MainFrame to associate with this new MuAction\n     * @param properties the initial properties to use in this action. The Hashtable may simply be empty if no initial\n     */\n    private ToggleTableViewModeFullAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n        getMainFrame().getActiveTable().setViewMode(TableViewMode.FULL);\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"ToggleTableViewModeFull\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.VIEW;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_1, KeyEvent.CTRL_DOWN_MASK);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String, Object> properties) {\n            return new ToggleTableViewModeFullAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ToggleTableViewModeShortAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.views.TableViewMode;\n\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n * @author Oleg Trifonov\n * Created on 15/04/15.\n */\npublic class ToggleTableViewModeShortAction extends TcAction {\n\n    /**\n     * Creates a new <code>ToggleTableViewModeShortAction</code>\n     *\n     * @param mainFrame  the MainFrame to associate with this new MuAction\n     * @param properties the initial properties to use in this action. The Hashtable may simply be empty if no initial\n     */\n    private ToggleTableViewModeShortAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    public void performAction() {\n        getMainFrame().getActiveTable().setViewMode(TableViewMode.SHORT);\n    }\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"ToggleTableViewModeShort\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.VIEW;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_3, KeyEvent.CTRL_DOWN_MASK);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String, Object> properties) {\n            return new ToggleTableViewModeShortAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ToggleToolBarAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.conf.TcConfigurations;\r\nimport com.mucommander.conf.TcPreference;\r\nimport com.mucommander.conf.TcPreferences;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.JPanel;\r\nimport javax.swing.KeyStroke;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action shows/hides the current MainFrame's {@link com.mucommander.ui.main.toolbar.ToolBar} depending on its\r\n * current visible state: if it is visible, hides it, if not shows it.\r\n *\r\n * <p>This action's label will be updated to reflect the current visible state.\r\n *\r\n * <p>Each time this action is executed, the new current visible state is stored in the configuration so that\r\n * new MainFrame windows will use it to determine whether the ToolBar has to be made visible or not.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class ToggleToolBarAction extends TcAction {\r\n\r\n    private ToggleToolBarAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n        updateLabel(TcConfigurations.getPreferences().getVariable(TcPreference.TOOLBAR_VISIBLE, TcPreferences.DEFAULT_TOOLBAR_VISIBLE));\r\n    }\r\n\r\n    private void updateLabel(boolean visible) {\r\n        setLabel(Translator.get(visible?Descriptor.ACTION_ID+\".hide\":Descriptor.ACTION_ID+\".show\"));\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        JPanel toolBarPanel = mainFrame.getToolBarPanel();\r\n        boolean visible = !toolBarPanel.isVisible();\r\n        // Save the last toolbar visible state in the configuration, this will become the default for new MainFrame windows.\r\n        TcConfigurations.getPreferences().setVariable(TcPreference.TOOLBAR_VISIBLE, visible);\r\n        // Change the label to reflect the new toolbar state\r\n        updateLabel(visible);\r\n        // Show/hide the toolbar\r\n        toolBarPanel.setVisible(visible);\r\n        mainFrame.getJFrame().validate();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"ToggleToolBar\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.VIEW;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n        @Override\r\n        public String getLabelKey() {\r\n\t\t    return ACTION_ID + \".show\";\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new ToggleToolBarAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ToggleTreeAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.tree.FoldersTreePanel;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action toggles the visibility of a directory tree.\r\n * @see FoldersTreePanel   \r\n *\r\n * @author Mariusz Jakubowski\r\n */\r\npublic class ToggleTreeAction extends TcAction {\r\n\r\n    private ToggleTreeAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        FolderPanel folderPanel = mainFrame.getActiveTable().getFolderPanel();\r\n        folderPanel.setTreeVisible(!folderPanel.isTreeVisible());\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"ToggleTree\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.VIEW;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_J, CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new ToggleTreeAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/UnmarkAllAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action unmarks all files in the current file table.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class UnmarkAllAction extends MarkAllAction {\r\n\r\n    private UnmarkAllAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties, false);\r\n    }\r\n\r\n    @Override\r\n    public ActionDescriptor getDescriptor() {\r\n    \treturn new Descriptor();\r\n    }\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"UnmarkAll\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.SELECTION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_D, CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new UnmarkAllAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/UnmarkGroupAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.file.FileSelectionDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action brings up the 'File selection' dialog which allows to unmark a group of files that match a specified expression.\r\n *\r\n * @author Maxence Bernard\r\n */\r\n@InvokesDialog\r\npublic class UnmarkGroupAction extends TcAction {\r\n\r\n    private UnmarkGroupAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n    @Override\r\n    public void performAction() {\r\n        new FileSelectionDialog(mainFrame, false).showDialog();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"UnmarkGroup\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t\treturn ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t\treturn ActionCategory.SELECTION;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t\treturn KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0);\r\n\t\t}\r\n\r\n\t\tpublic TcAction createAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n\t\t\treturn new UnmarkGroupAction(mainFrame, properties);\r\n\t\t}\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/UnpackAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.commons.file.filter.AttributeFileFilter;\r\nimport com.mucommander.commons.file.filter.AttributeFileFilter.FileAttribute;\r\nimport com.mucommander.commons.file.filter.OrFileFilter;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.dialog.file.UnpackDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * This action pops up the 'Unpack files' dialog that allows to unpack the currently marked files.\r\n *\r\n * @author Maxence Bernard\r\n */\r\n@InvokesDialog\r\npublic class UnpackAction extends SelectedFilesAction {\r\n\r\n    private UnpackAction(MainFrame mainFrame, Map<String, Object> properties) {\r\n        super(mainFrame, properties);\r\n\r\n        // Unpack job operates on archives and directories\r\n        setSelectedFileFilter(new OrFileFilter(\r\n            new AttributeFileFilter(FileAttribute.ARCHIVE),\r\n            new AttributeFileFilter(FileAttribute.DIRECTORY)\r\n        ));\r\n    }\r\n\r\n    @Override\r\n    public void performAction(FileSet files) {\r\n        new UnpackDialog(mainFrame, files).showDialog();\r\n    }\r\n\r\n\t@Override\r\n\tpublic ActionDescriptor getDescriptor() {\r\n\t\treturn new Descriptor();\r\n\t}\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"Unpack\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.FILES;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n            return KeyStroke.getKeyStroke(KeyEvent.VK_P, CTRL_OR_META_DOWN_MASK);\r\n        }\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new UnpackAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/UserMenuAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/trolcommander\n * Copyright (C) 2014-2026 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.action.*;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.menu.UserPopupMenu;\nimport com.mucommander.ui.main.menu.usermenu.LoadUserMenuException;\nimport com.mucommander.ui.main.menu.usermenu.UserPopupMenuLoader;\nimport com.mucommander.ui.notifier.AbstractNotifier;\nimport com.mucommander.ui.notifier.NotificationType;\nimport com.mucommander.ui.viewer.EditorRegistrar;\nimport com.mucommander.ui.viewer.text.TextEditor;\nimport com.mucommander.ui.viewer.text.TextFilesHistory;\nimport lombok.extern.slf4j.Slf4j;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.KeyEvent;\nimport java.io.IOException;\nimport java.util.Map;\n\n@Slf4j\npublic class UserMenuAction extends ParentFolderAction {\n\n    private UserMenuAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n    }\n\n    @Override\n    protected void toggleEnabledState() {}\n\n    @Override\n    public void performAction() {\n        UserPopupMenu menu = createMenu(mainFrame);\n        if (menu != null) {\n            menu.show(mainFrame.getJFrame());\n        }\n    }\n\n    public static UserPopupMenu createMenu(MainFrame mainFrame) {\n        AbstractFile currentFolder = mainFrame.getActiveTable().getFileTableModel().getCurrentFolder();\n        AbstractFile localMenu = findLocalMenu(currentFolder);\n        if (localMenu != null) {\n            try {\n                return UserPopupMenuLoader.loadMenu(mainFrame, localMenu);\n            } catch (LoadUserMenuException ej) {\n                openEditorAndShowError(mainFrame, localMenu, ej);\n            } catch (IOException e) {\n                // TODO status bar\n                log.error(\"Load menu exception\", e);\n                AbstractNotifier notifier = AbstractNotifier.getNotifier();\n                if (notifier != null) {\n                    notifier.displayNotification(NotificationType.JOB_ERROR, \"Error\", e.getMessage());\n                }\n            } catch (Throwable t) {\n                log.error(\"Load menu error\", t);\n            }\n        }\n        return null;\n    }\n\n    private static AbstractFile findLocalMenu(AbstractFile folder) {\n        if (folder == null) {\n            return null;\n        }\n        AbstractFile menu = getMenuFile(folder);\n        return menu != null ? menu : findLocalMenu(folder.getParent());\n    }\n\n    private static AbstractFile getMenuFile(AbstractFile folder) {\n        if (folder == null || !folder.exists()) {\n            return null;\n        }\n        try {\n            AbstractFile result = folder.getChild(\".trolcommander-menu.yml\");\n            if (result != null && result.exists()) {\n                return result;\n            }\n            result = folder.getChild(\".trolcommander-menu.yaml\");\n            return result != null && result.exists() ? result : null;\n        } catch (IOException e) {\n            return null;\n        }\n    }\n\n    private static void openEditorAndShowError(MainFrame mainFrame, AbstractFile localMenu, LoadUserMenuException e) {\n        TextFilesHistory.FileRecord historyRecord = TextFilesHistory.getInstance().get(localMenu);\n        historyRecord.update(e.getLine(), e.getLine(), e.getColumn(), historyRecord.getFileType(), historyRecord.getEncoding());\n\n        Image image = ActionProperties.getActionIcon(EditAction.Descriptor.ACTION_ID).getImage();\n        log.info(\"open frame {} {}:{}\", e.getMessage(), e.getLine(), e.getColumn());\n        EditorRegistrar.createEditorFrame(mainFrame, localMenu, image,\n                (fileFrame) -> {\n                    TextEditor textEditor = (TextEditor)fileFrame.getFilePresenter();\n                    textEditor.getStatusBar().showMessage(e.getMessage(), 1000);\n                });\n    }\n\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new UserMenuAction.Descriptor();\n    }\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"UserMenu\";\n\n        public String getId() {\n            return ACTION_ID;\n        }\n\n        public ActionCategory getCategory() {\n            return ActionCategory.COMMANDS;\n        }\n\n        public KeyStroke getDefaultAltKeyStroke() {\n            return null;\n        }\n\n        public KeyStroke getDefaultKeyStroke() {\n            return KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0);\n        }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String, Object> properties) {\n            return new UserMenuAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ViewAction.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.action.impl;\r\n\r\nimport com.mucommander.command.Command;\r\nimport com.mucommander.command.CommandManager;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.ui.action.AbstractActionDescriptor;\r\nimport com.mucommander.ui.action.ActionCategory;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Map;\r\n\r\n/**\r\n * User configurable variant of {@link InternalViewAction}.\r\n * @author Maxence Bernard, Nicolas Rinaudo\r\n */\r\npublic class ViewAction extends InternalViewAction {\r\n    // - Initialization ------------------------------------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------------------------------------\r\n    /**\r\n     * Creates a new instance of <code>ViewAction</code>.\r\n     * @param mainFrame  frame to which the action is attached.\r\n     * @param properties action's properties.\r\n     */\r\n    public ViewAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n        super(mainFrame, properties);\r\n    }\r\n\r\n\r\n    // - AbstractViewerAction implementation ---------------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------------------------------------\r\n    @Override\r\n    protected Command getCustomCommand(AbstractFile file) {\r\n        return CommandManager.getCommandForAlias(CommandManager.VIEWER_ALIAS, file);\r\n    }\r\n\r\n    @Override\r\n    public ActionDescriptor getDescriptor() {\r\n        return new Descriptor();\r\n    }\r\n\r\n\r\n    public static final class Descriptor extends AbstractActionDescriptor {\r\n    \tpublic static final String ACTION_ID = \"View\";\r\n    \t\r\n\t\tpublic String getId() {\r\n\t\t    return ACTION_ID;\r\n\t\t}\r\n\r\n\t\tpublic ActionCategory getCategory() {\r\n\t\t    return ActionCategory.FILES;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultAltKeyStroke() {\r\n\t\t    return null;\r\n\t\t}\r\n\r\n\t\tpublic KeyStroke getDefaultKeyStroke() {\r\n\t\t    return KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0);\r\n\t\t}\r\n\r\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\r\n            return new ViewAction(mainFrame, properties);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/impl/ViewAsAction.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2019 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.action.impl;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.ui.action.AbstractActionDescriptor;\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionDescriptor;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.quicklist.ViewAsQL;\n\nimport javax.swing.ImageIcon;\nimport javax.swing.KeyStroke;\nimport java.awt.event.KeyEvent;\nimport java.util.Map;\n\n/**\n *\n * @author Oleg Trifonov\n */\npublic class ViewAsAction extends SelectedFilesAction {\n    /**\n     * Creates a new instance of <code>ViewAsAction</code>.\n     * @param mainFrame  frame to which the action is attached.\n     * @param properties action's properties.\n     */\n    private ViewAsAction(MainFrame mainFrame, Map<String, Object> properties) {\n        super(mainFrame, properties);\n\n        ImageIcon icon = getStandardIcon(ViewAction.class);\n        if (icon != null) {\n            setIcon(icon);\n        }\n    }\n\n\n    @Override\n    public ActionDescriptor getDescriptor() {\n        return new Descriptor();\n    }\n\n    @Override\n    public void performAction(FileSet files) {\n        AbstractFile file = mainFrame.getActiveTable().getSelectedFile(false, true);\n\n        // At this stage, no assumption should be made on the type of file that is allowed to be viewed/edited:\n        // viewer/editor implementations will decide whether they allow a particular file or not.\n        if (file == null || file.isDirectory()) {\n            return;\n        }\n        new ViewAsQL(mainFrame, file).show();\n    }\n\n\n\n    public static final class Descriptor extends AbstractActionDescriptor {\n        public static final String ACTION_ID = \"ViewAs\";\n\n        public String getId() { return ACTION_ID; }\n\n        public ActionCategory getCategory() { return ActionCategory.FILES; }\n\n        public KeyStroke getDefaultAltKeyStroke() { return null; }\n\n        public KeyStroke getDefaultKeyStroke() { return KeyStroke.getKeyStroke(KeyEvent.VK_F3, KeyEvent.SHIFT_DOWN_MASK); }\n\n        public TcAction createAction(MainFrame mainFrame, Map<String,Object> properties) {\n            return new ViewAsAction(mainFrame, properties);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/action/package.html",
    "content": "<body>\n  Contains all actions used to interact with muCommander.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/autocomplete/AutocompleterTextComponent.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.autocomplete;\n\nimport com.mucommander.ui.combobox.EditableComboBox;\n\nimport javax.swing.text.BadLocationException;\nimport javax.swing.text.Document;\nimport javax.swing.text.JTextComponent;\nimport java.awt.*;\nimport java.awt.event.FocusListener;\nimport java.awt.event.KeyAdapter;\nimport java.awt.event.KeyEvent;\nimport java.awt.event.KeyListener;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * AutocompleterTextComponent convert any text component to auto-completion supported text component. \n * In order to support auto-completion two abstract methods need to be implemented:\n * <ul>\n *   <li>OnEnterPressed - What should the text component do when enter key is pressed and there is no item selected on the auto-completion popup list</li>\n *   <li>OnEscPressed - What should the text component do when escape key is pressed and there is no item selected on the auto-completion popup list</li>\n * </ul>\n * \n * @author Arik Hadas\n */\n\npublic abstract class AutocompleterTextComponent {\n\tprivate JTextComponent textComponent;\n\tprivate EditableComboBox editableComboBox = null;\n\t\n\tpublic AutocompleterTextComponent(JTextComponent textComp) {\n\t\tthis.textComponent = textComp;\t\n\t}\n\t\n\tprotected AutocompleterTextComponent(EditableComboBox editableComboBox) {\n\t\tthis.textComponent = editableComboBox.getTextField();\n\t\tthis.editableComboBox = editableComboBox;\n\t\t\n\t\t// Remove all key listeners which are defined for the EditableCombobox\n\t\tremoveAllKeyListeners();\n\t}\t\n\n\t// Abstract methods:\n\t/**\n\t * This function will be called when the text component has the focus and enter key is \n\t * pressed, while the auto-completion popup window is unvisible.\n\t */\n\tpublic abstract void OnEnterPressed(KeyEvent keyEvent);\n\t\n\t/**\n\t * This function will be called when the text component has the focus and escape key is \n\t * pressed, while the auto-completion popup window is unvisible.\n\t */\n\tpublic abstract void OnEscPressed(KeyEvent keyEvent);\n\t\n\tprivate void removeAllKeyListeners() {\n\t\tKeyListener[] l = editableComboBox.getTextField().getKeyListeners();\n\t\tfor (KeyListener aL : l) {\n\t\t\teditableComboBox.getTextField().removeKeyListener(aL);\n\t\t}\n\t}\n\t\n\t// Methods of the text component which are used by the auto-completion mechanism:\t\n\tpublic Document getDocument() { return textComponent.getDocument(); }\n\t\n\tpublic boolean isShowing() { return textComponent.isShowing(); }\n\t\n\tpublic void setText(String text) { textComponent.setText(text); }\n\t\n\tpublic String getText() { return textComponent.getText(); }\n\t\t\n\tpublic boolean hasFocus() { return textComponent.hasFocus(); }\n\t\n\tpublic boolean isEnabled() { return textComponent.isEnabled(); }\n\t\n\tpublic int getCaretPosition() { return textComponent.getCaretPosition(); }\n\t\n\tpublic void requestFocus() { textComponent.requestFocus(); }\n\t\n\tpublic int getHeight() { return textComponent.getHeight(); }\n\t\n\tRectangle modelToView() throws BadLocationException {\n\t\tint pos = textComponent.getCaretPosition();\n\t\treturn textComponent.modelToView2D(pos).getBounds();\n\t}\n\n\tvoid moveCaretToEndOfText() { textComponent.setCaretPosition(textComponent.getText().length()); }\n\t\n\tboolean isCaretAtEndOfTextAtInsertion() { return textComponent.getCaretPosition() == textComponent.getText().length() - 1; }\n\t\n\tboolean isCarentAtEndOfTextAtRemoval() { return textComponent.getCaretPosition() == textComponent.getText().length() + 1; }\n\t\n\tJTextComponent getTextComponent() { return textComponent; }\n\t\n\tpublic void addKeyListener(KeyAdapter adapter) { textComponent.addKeyListener(adapter); }\n\t\n\tpublic void addFocusListener(FocusListener listener) { textComponent.addFocusListener(listener); }\n\t\n\t\n\t/**\n\t * \tgetItemsNames\n\t * \n\t * @return empty Vector if component is not an EditableComboBox, otherwise return Vector which contains the names of the combobox items.\n\t */\n\tpublic List<String> getItemNames() {\n\t\tList<String> result = new ArrayList<>();\n\t\tif (editableComboBox != null) {\n\t\t\tint nbItems = editableComboBox.getItemCount();\n\t\t\tfor (int i = 0; i < nbItems; i++) {\n\t\t\t\tresult.add(editableComboBox.getItemAt(i).toString());\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\t\n\t/**\n\t * isPopupVisible\n\t * \n\t * @return false if component is not an EditableComboBox, otherwise, true if the combo-box list of items is visible.\n\t */\n\tpublic boolean isComponentsPopupVisible() {\n\t\treturn editableComboBox != null && editableComboBox.isPopupVisible();\n\t}\n\t\n\t/**\n\t * setPopupUnvisibe - make the combo-box list of items invisible.\n\t */\n\tvoid setComponentsPopupInvisible() {\n\t\tif (editableComboBox != null) {\n\t\t\teditableComboBox.setPopupVisible(false);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/autocomplete/BasicAutocompleterTextComponent.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.autocomplete;\n\nimport javax.swing.text.JTextComponent;\nimport java.awt.event.KeyEvent;\n\n/**\n * This <code>AutocompleterTextComponent</code> implements {@link #OnEnterPressed(java.awt.event.KeyEvent)}\n * and {@link #OnEscPressed(java.awt.event.KeyEvent)} as no-ops.\n *\n * @author Maxence Bernard\n */\npublic class BasicAutocompleterTextComponent extends AutocompleterTextComponent {\n\n    public BasicAutocompleterTextComponent(JTextComponent textComp) {\n        super(textComp);\n    }\n\n\n    ///////////////////////////////////////////////\n    // AutocompleterTextComponent implementation //\n    ///////////////////////////////////////////////\n\n    @Override\n    public void OnEnterPressed(KeyEvent keyEvent) {\n    }\n\n    @Override\n    public void OnEscPressed(KeyEvent keyEvent) {\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/autocomplete/CompleterFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.autocomplete;\n\nimport com.mucommander.ui.autocomplete.completers.ComboboxOptionsCompleter;\nimport com.mucommander.ui.autocomplete.completers.Completer;\nimport com.mucommander.ui.autocomplete.completers.LocationCompleter;\nimport com.mucommander.ui.autocomplete.completers.PathCompleter;\n\n/**\n * A factory class to produce completers.\n * \n * @author Arik Hadas\n */\npublic class CompleterFactory {\n\t\n\tpublic static Completer getComboboxOptionsCompleter() {\n\t\treturn new ComboboxOptionsCompleter();\n\t}\n\t\n\tpublic static Completer getPathCompleter() {\n\t\treturn new PathCompleter();\n\t}\n\t\n\tpublic static Completer getLocationCompleter() {\n\t\treturn new LocationCompleter();\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/autocomplete/CompletionType.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.autocomplete;\n\nimport com.mucommander.ui.autocomplete.completers.Completer;\n\nimport javax.swing.*;\nimport javax.swing.event.DocumentEvent;\nimport javax.swing.event.DocumentListener;\nimport java.awt.*;\nimport java.awt.event.FocusEvent;\nimport java.awt.event.FocusListener;\nimport java.awt.event.MouseEvent;\nimport java.awt.event.MouseListener;\n\n/**\n * AutoCompletionType defines the behavior of the auto-completion, such as:\n * - The key(s) that will open the popup window\n * - When should the documentListener be attached to the text component\n * - etc..\n * This abstract class contains the common things to all CompleterType implementations.\n * \n * @author Arik Hadas, based on the code of Santhosh Kumar: http://www.jroller.com/santhosh/entry/file_path_autocompletion\n */\n\npublic abstract class CompletionType {\n\t\t\n\tprivate Completer completer;\n\tAutocompleterTextComponent autocompletedtextComp;\n    DocumentListener documentListener;\n    protected JList<String> list = new JList<>();\n    protected final JPopupMenu popup = new JPopupMenu();\n    ShowingThread showingThread;\n    \n    // Constants:\n    final int VISIBLE_ROW_COUNT = 10;\n    private final int POPUP_DELAY_AT_TEXT_INSERTION = 1500;\n    private final int POPUP_DELAY_AT_TEXT_DELETION  = 500;\n    private final int POPUP_DELAY_AFTER_ACCEPTING_LIST_ITEM = 1500;\n    \n    /**\n     * ShowingThread is an abstract class for threads that show auto-completion popup\n     * window after a given delay.\n     * Each implementation of ShowingThread should implements an abstract function\n     * \"showPopup\" that contains the popup opening.\n     * \n     * @author Arik Hadas\n     */\n    protected abstract class ShowingThread extends Thread {\n    \t\n\t\tboolean isStopped;\n\t\tint delayTime;\n\t\t\n\t\tShowingThread(int delayTime) {\n\t\t\tisStopped = false;\n\t\t\tthis.delayTime = delayTime;\n\t\t}\n\t\t\n\t\t@Override\n        public void run() {\n\t\t\t// Hide the auto-completion popup window.\n\t\t\thideAutocompletionPopup();\n\t\t\t\n\t\t\tif (!autocompletedtextComp.isShowing() || !autocompletedtextComp.isEnabled())\n\t\t\t\treturn;\n\t\t\t\n\t\t\t// Sleep for delayTime milieconds.\n\t\t\tdelay(delayTime);\n\t\t\t\n\t\t\t// If this thread should stop, finish its execution.\n\t\t\tif (isStopped)\n\t\t\t\treturn;\n\t\t\t\n\t\t\t// Show auto-completion popup window.\n\t\t\tshowAutocompletionPopup();\t\t\t\t\t\t\n\t    }\n\t\t\n\t\t/**\n\t\t * Stop this thread execution.\n\t\t */\n\t\tpublic void done() {\n\t\t\tisStopped = true;\n\t\t}\n\t\t\n\t\t/**\n\t\t * Cause this thread sleep for the given time (in miliseconds).\n\t\t */\n\t\tprotected void delay(int miliseconds) {\n\t    \tif (miliseconds > 0) {\n\t\t\t\ttry {\n\t\t\t\t\tThread.sleep(miliseconds);\n\t\t\t\t} catch (InterruptedException ignore) { }\n\t    \t}\n\t    }\n\t\t\n\t\tabstract void showAutocompletionPopup();\n\t}    \n    \n    CompletionType(AutocompleterTextComponent comp, Completer completer) {\n    \tautocompletedtextComp = comp;\n    \tthis.completer = completer;\n\n//        JScrollPane scroll = new JScrollPane(list);\n        // Disable horizontal scrolling because they would sometimes appear under Mac OS X even though there was\n        // plenty of horizontal space to display the list.\n        JScrollPane scroll = new JScrollPane(list, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);\n        scroll.setBorder(null);\n \n        list.setFocusable( false );\n        popup.setFocusable( false );\n        \n        scroll.getVerticalScrollBar().setFocusable( false ); \n        scroll.getHorizontalScrollBar().setFocusable( false ); \n \n        popup.setBorder(BorderFactory.createLineBorder(Color.black)); \n        popup.add(scroll);         \n        \n        createDocumentListener();\n\n        addMouseListenerToList();\n        \n        list.setRequestFocusEnabled(false);\n        \n        autocompletedtextComp.addFocusListener(new FocusListener() {\n\n\t\t\tpublic void focusGained(FocusEvent e) { }\n\n\t\t\tpublic void focusLost(FocusEvent e) {\n\t\t\t\thideAutocompletionPopup();\n\t\t\t}\n        });\n    }\n    \n    // abstract methods:\n    /**\n     * Start a new thread that implement ShowingThread with the given delay.\n     */\n    protected abstract void startNewShowingThread(int delay);\n    \n    /**\n     * Hide the auto-completion popup window.\n     */\n    protected abstract void hideAutocompletionPopup();\n    \n    \n    /**\n     * update auto-completion popup's list model depending on the data in text component.\n     * \n     * @param list - Auto-completion popup's list.\n     * @return true if the list was updated successfully, false otherwise. \n     */\n    boolean updateListData(JList<String> list) {\n    \treturn completer.updateListData(list, autocompletedtextComp);\n    }\n \n    /**\n     * user has selected some item from the auto-completion popup list,\n     * update text component accordingly.\n     * \n     * @param selected - The selected item from the auto-completion popup's list.\n     */\n    private void updateTextComponent(String selected) {\n    \tcompleter.updateTextComponent(selected, autocompletedtextComp);\n    }\n    \n    /**\n     * createNewShowingThread shows the auto-completion popup window after \n     * a non-blocking delay.\n     * \n     * @param delay - The requested delay (in miliseconds) until the popup appear.\n     */\n    void createNewShowingThread(int delay) {\n    \t// stop current showing thread (if exist)\n    \tif (showingThread != null)\n    \t\tshowingThread.done();\n    \t// start new showing thread\n    \tstartNewShowingThread(delay);\n    }\n    \n    /**\n     * The function handles the case when an item of the auto-copmpletion popup was selected.\n     * it ask the text component to be updated according to the selected item and create\n     * a new thread that will open auto-completion popup windos after delay of\n     * POPUP_DELAY_AFTER_ACCEPTING_LIST_ITEM seconds. \n     */\n    protected void acceptListItem(String selected) {\n    \tupdateTextComponent(selected);\n    \tcreateNewShowingThread(POPUP_DELAY_AFTER_ACCEPTING_LIST_ITEM);\n    }    \n    \n    private void addMouseListenerToList() {\n    \tlist.addMouseListener(new MouseListener() {\n        \t\t\n\t\t\tpublic void mouseClicked(MouseEvent e) {\n\t\t\t\t// If there was double click on item of the popup's list, \n\t\t\t\t// select it, and update the text component.\n\t\t\t\tif (e.getClickCount() == 2) {\n\t\t             int index = list.locationToIndex(e.getPoint());\n\t\t             list.setSelectedIndex(index);\n\t\t             acceptListItem(list.getSelectedValue());\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpublic void mouseReleased(MouseEvent e) {}\n\t\t\t\n\t\t\tpublic void mouseEntered(MouseEvent e) {}\n\n\t\t\tpublic void mouseExited(MouseEvent e) {}\n\t\t\t\n\t\t\tpublic void mousePressed(MouseEvent e) {}\n        });\n    }\n    \n    private void createDocumentListener() {    \t\n\t    documentListener = new DocumentListener(){ \n\t        public void insertUpdate(DocumentEvent e){\n\t        \t// If text was inserted to the text component and carent is at the end of \n\t        \t// the text, then start a showingThread to open auto-completion popup.\n\t        \tif (autocompletedtextComp.isCaretAtEndOfTextAtInsertion())\n        \t\t\tcreateNewShowingThread(popup.isVisible() ? 0 : POPUP_DELAY_AT_TEXT_INSERTION);\t        \t\n\t        }\n\t \n\t        public void removeUpdate(DocumentEvent e){\n\t        \t// If text was deleted from the text component and carent is at the end of \n\t        \t// the text, then start a showingThread to open auto-completion popup.\n\t        \tif (autocompletedtextComp.isCarentAtEndOfTextAtRemoval())\n\t        \t\tcreateNewShowingThread(popup.isVisible() ? 0 : POPUP_DELAY_AT_TEXT_DELETION); \n\t        } \n\n\t        public void changedUpdate(DocumentEvent e){ } \n\t    };\n    }\n    \n    /** \n     * Returns true if the auto-completion popup window is visible.\n     */\n    boolean isPopupListShowing() {\n    \treturn popup.isShowing();\n    }\n    \n    /** \n     * Returns true if there is a selected item at the auto-completion popup window.\n     */\n    boolean isItemSelectedAtPopupList() {\n    \treturn popup.isShowing() && list.getSelectedIndex() >= 0;\n    }\n\n    /** \n     * Selects the first item in the list. \n     */\n    void selectFirstValue() {\n    \tlist.setSelectedIndex(0); \n        list.ensureIndexIsVisible(0);\n    }\n    \n    /** \n     * Selects the last item in the list. \n     */\n    void selectLastValue() {\n    \tint lastIndex = list.getModel().getSize() - 1;\n    \t\n    \tlist.setSelectedIndex(lastIndex); \n        list.ensureIndexIsVisible(lastIndex);\n    }\n    \n    /** \n     * Selects the item at (VISIBLE_ROW_COUNT - 1) places after the currently\n     * selected item in the list. \n     */\n    void selectNextPage() {\n    \tint si = list.getSelectedIndex();\n    \t\n    \tint nextIndex = 0;\n    \tif (si >= 0) {\n            nextIndex = Math.min(si + VISIBLE_ROW_COUNT - 1, list.getModel().getSize() - 1);\n        }\n    \tlist.setSelectedIndex(nextIndex); \n        list.ensureIndexIsVisible(nextIndex); \n    }\n    \n    /** \n     * Selects the item at (VISIBLE_ROW_COUNT - 1) places before the currently\n     * selected item in the list. \n     */\n    void selectPreviousPage() {\n    \tint si = list.getSelectedIndex(); \n    \t \n        if (si > 0){\n        \tint nextIndex = Math.max(si - (VISIBLE_ROW_COUNT - 1), 0);\n            list.setSelectedIndex(nextIndex); \n            list.ensureIndexIsVisible(nextIndex); \n        } \n    }\n    \n    /** \n     * Selects the next item in the list.  It won't change the selection if the \n     * currently selected item is already the last item. \n     */\n    void selectNextPossibleValue(){\n        int si = list.getSelectedIndex(); \n \n        if (si < list.getModel().getSize() - 1) {\n        \tint nextIndex = si + 1;\n            list.setSelectedIndex(nextIndex); \n            list.ensureIndexIsVisible(nextIndex); \n        } \n    } \n \n    /** \n     * Selects the previous item in the list.  It won't change the selection if the \n     * currently selected item is already the first item. \n     */\n    void selectPreviousPossibleValue(){\n        int si = list.getSelectedIndex(); \n \n        if (si > 0){\n        \tint nextIndex = si - 1;\n            list.setSelectedIndex(nextIndex); \n            list.ensureIndexIsVisible(nextIndex); \n        } \n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/autocomplete/EditableComboboxCompletion.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.autocomplete;\n\nimport java.awt.event.KeyAdapter;\nimport java.awt.event.KeyEvent;\n\nimport javax.swing.text.BadLocationException;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.ui.autocomplete.completers.Completer;\n\n/**\n * EditableComboboxCompleter is a CompleterType which suite to editable combobox.\n * \n * @author Arik Hadas, based on the code of Santhosh Kumar: http://www.jroller.com/santhosh/entry/file_path_autocompletion\n */\n\npublic class EditableComboboxCompletion extends CompletionType {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(EditableComboboxCompletion.class);\n\t\n\tprivate class ShowingThreadImp extends ShowingThread {\n    \tShowingThreadImp(int delay) {\n    \t\tsuper(delay);\n    \t}\n\n\t\t@Override\n        void showAutocompletionPopup() {\n\t        if (autocompletedtextComp.isShowing() && autocompletedtextComp.isEnabled() && updateListData(list)) {\n\t\t\t\t\t            \n\t            list.setVisibleRowCount(Math.min(list.getModel().getSize() ,VISIBLE_ROW_COUNT));\n\t            \n\t            int x;\n\t            try{\t                \n\t                x = autocompletedtextComp.modelToView().x;\n\t            } catch(BadLocationException e){ \n\t                // this should never happen!!! \n                    LOGGER.debug(\"Caught exception\", e);\n\t                return;\n\t            }\n\t            if (autocompletedtextComp.hasFocus()) {\t            \t\n\t            \tif (!isStopped) {\n\t            \t\tlist.ensureIndexIsVisible(0);\n\t            \t\tsynchronized(popup) {\n\t\t            \t\tpopup.show(autocompletedtextComp.getTextComponent(), x, autocompletedtextComp.getHeight());\n\t\t            \t\t\n\t\t            \t\t// probably because of swing's bug, sometimes the popup window looks\n\t\t            \t\t// as a gray rectangle - repainting solves it.\n\t\t            \t\tpopup.repaint();\n\t            \t\t}\n\t            \t}\n\t            }\t            \n\t        }\n\t\t}\n    }\n\t\n\tpublic EditableComboboxCompletion(AutocompleterTextComponent comp, Completer completer){\n    \tsuper(comp, completer);        \n    \t\n    \tautocompletedtextComp.getDocument().addDocumentListener(documentListener);\n    \t\n        autocompletedtextComp.addKeyListener(new KeyAdapter() {\n        \t@Override\n            public void keyPressed(KeyEvent keyEvent) {\n                switch(keyEvent.getKeyCode()) {\n                case KeyEvent.VK_ENTER:\n                \tif (isItemSelectedAtPopupList()) {\n                \t\thideAutocompletionPopup();\n                \t\tacceptListItem(list.getSelectedValue());\n                \t\tkeyEvent.consume();\n                \t}\n                \telse {\n\t\t\t\t\t\t// Stop the active showing-thread to prevent suggestions-popup \n                    \t// opening after the operation was initiated.\n                    \tif (showingThread != null) {\n\t\t\t\t\t\t\tshowingThread.done();\n\t\t\t\t\t\t}\n\t\t\t\t\t\n                \t\tautocompletedtextComp.OnEnterPressed(keyEvent);\n\t\t\t\t\t}\n                \tbreak;\n                case KeyEvent.VK_ESCAPE:\n                \tif (isPopupListShowing()) {\n                \t\tif (autocompletedtextComp.isEnabled()) {\n\t\t\t\t\t\t\thideAutocompletionPopup();\n\t\t\t\t\t\t}\n                \t\tkeyEvent.consume();\n                \t}\n                \telse\n                \t\tautocompletedtextComp.OnEscPressed(keyEvent);\n                \tbreak;\n                case KeyEvent.VK_UP:\n                \tif (autocompletedtextComp.isEnabled() && popup.isVisible()) {\n                \t\tselectPreviousPossibleValue();\n                \t\tkeyEvent.consume();\n                \t}\n                \tbreak;\n                case KeyEvent.VK_DOWN:\n                \tif (autocompletedtextComp.isEnabled()){\n                        if (popup.isVisible()) {\n                            selectNextPossibleValue();\n                            keyEvent.consume();\n                        }\n                    }\n                \tbreak;\n                case KeyEvent.VK_SPACE:\n                \t// The combination of cntrl+space makes open the auto-complete popup without delay.\n                \tif (keyEvent.isControlDown()) {\n                \t\tif (!popup.isVisible()) {\n                \t\t\tautocompletedtextComp.setComponentsPopupInvisible();\n                    \t\tautocompletedtextComp.moveCaretToEndOfText();\n                    \t\tcreateNewShowingThread(0);\n                    \t}\n                \t}\n                \tbreak;\n                case KeyEvent.VK_PAGE_DOWN:\n                \tif(autocompletedtextComp.isEnabled() && isPopupListShowing()) {\n                \t\tselectNextPage();\n                \t\tkeyEvent.consume();\n                \t}\n                \tbreak;\n                case KeyEvent.VK_PAGE_UP:\n                \tif(autocompletedtextComp.isEnabled() && isItemSelectedAtPopupList()) {\n                \t\tselectPreviousPage();\n                \t\tkeyEvent.consume();\n                \t}\n                \tbreak;\n                case KeyEvent.VK_HOME:\n                \tif(autocompletedtextComp.isEnabled() && isPopupListShowing()) {\n                \t\tselectFirstValue();\n                \t\tkeyEvent.consume();\n                \t}\n                \tbreak;\n                case KeyEvent.VK_END:\n                \tif(autocompletedtextComp.isEnabled() && isPopupListShowing()) {\n                \t\tselectLastValue();\n                \t\tkeyEvent.consume();\n                \t}\n                \tbreak;\n                case KeyEvent.VK_LEFT:\n                \thideAutocompletionPopup();\n                \tbreak;\n                default:\n                }\n        \t}\n        });\n    }\n\t\n\t@Override\n    protected void hideAutocompletionPopup() {\n\t\tsynchronized (popup) {\n\t\t\tif (popup.isVisible())\n        \t\tpopup.setVisible(false);\n\t\t}\n\t}\n\t\n\t@Override\n    protected void startNewShowingThread(int delay) {\n    \t(showingThread = new ShowingThreadImp(delay)).start();\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/autocomplete/OtherTextComponentCompletion.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.autocomplete;\n\nimport java.awt.event.KeyAdapter;\nimport java.awt.event.KeyEvent;\n\nimport javax.swing.text.BadLocationException;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.ui.autocomplete.completers.Completer;\n\n/**\n * OtherTextComponentCompleter is a CompleterType which suite to text components\n * other than editable combobox and text-field (such as textArea for example).\n * \n * This class was not tested.\n * \n * @author Arik Hadas, based on the code of Santhosh Kumar: http://www.jroller.com/santhosh/entry/file_path_autocompletion\n */\n\npublic class OtherTextComponentCompletion extends CompletionType {\t\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(OtherTextComponentCompletion.class);\n\t\n\tprivate class ShowingThreadImp extends ShowingThread {\n    \tShowingThreadImp(int delay) {\n    \t\tsuper(delay);\n    \t}\n\n\t\t@Override\n        void showAutocompletionPopup() {\n\t        if (autocompletedtextComp.isShowing() && autocompletedtextComp.isEnabled() && updateListData(list)){\n\t\t\t\t\t         \n\t            list.setVisibleRowCount(Math.min(list.getModel().getSize() ,VISIBLE_ROW_COUNT));\n\t            \n\t            int x; \n\t            try{\t                \n\t                x = autocompletedtextComp.modelToView().x;\n\t            } catch(BadLocationException e){ \n\t                // this should never happen!!! \n                    LOGGER.debug(\"Caught exception\", e);\n\t                return;\n\t            }\n\t            if (autocompletedtextComp.hasFocus()) {\t            \t\n\t            \tif (!isStopped) {\t            \t\t\n\t            \t\tlist.ensureIndexIsVisible(0);\n\t            \t\tsynchronized(popup) {\n\t\t            \t\tpopup.show(autocompletedtextComp.getTextComponent(), x, autocompletedtextComp.getHeight());\n\t\t            \t\t\n\t\t            \t\t// probably because of swing's bug, sometimes the popup window looks\n\t\t            \t\t// as a gray rectangle - repainting solves it.\n\t\t            \t\tpopup.repaint();\n\t            \t\t}\n\t            \t\tautocompletedtextComp.getDocument().addDocumentListener(documentListener);\n\t            \t}\n\t            }\t            \n\t        }\n\t\t}\n    }\n\t\n\tpublic OtherTextComponentCompletion(AutocompleterTextComponent comp, Completer completer){\n    \tsuper(comp, completer);        \n    \t\n        autocompletedtextComp.addKeyListener(new KeyAdapter() {\n        \t@Override\n            public void keyPressed(KeyEvent keyEvent) {\n                switch(keyEvent.getKeyCode()) {\n                case KeyEvent.VK_ENTER:\n                \tboolean itemSelected = isItemSelectedAtPopupList();\n                \t\n                    // Notify listeners that the text field has been validated\n                \tif (itemSelected) {\n                \t\thideAutocompletionPopup();\n                \t\tacceptListItem(list.getSelectedValue());\n                \t\tkeyEvent.consume();\n                \t}\n                \telse\n                \t\tautocompletedtextComp.OnEnterPressed(keyEvent);\n                \tbreak;\n                case KeyEvent.VK_ESCAPE:\n                \tif (isPopupListShowing()) {\n                \t\tif (autocompletedtextComp.isEnabled())\n                \t\t\thideAutocompletionPopup();\n                \t\tkeyEvent.consume();\n                \t}\n                \telse\n                \t\tautocompletedtextComp.OnEscPressed(keyEvent);\n                \tbreak;\n                case KeyEvent.VK_UP:\n                \tif(autocompletedtextComp.isEnabled() && popup.isVisible()) {\n                \t\tselectPreviousPossibleValue();\n                \t\tkeyEvent.consume();\n                \t}\n                \tbreak;\n                case KeyEvent.VK_DOWN:\n                \tif(autocompletedtextComp.isEnabled()){ \n                        if(popup.isVisible()) {\n                            selectNextPossibleValue();\n                            keyEvent.consume();\n                        }\n                    }\n                \tbreak;\n                case KeyEvent.VK_SPACE:\n                \tif (keyEvent.isControlDown()) {\n                \t\tif (!popup.isVisible()) {\n                    \t\tautocompletedtextComp.moveCaretToEndOfText();\n                    \t\tcreateNewShowingThread(0);\n                    \t}\n                \t}\n                \tbreak;\n                case KeyEvent.VK_PAGE_DOWN:\n                \tif(autocompletedtextComp.isEnabled() && isPopupListShowing()) {\n                \t\tselectNextPage();\n                \t\tkeyEvent.consume();\n                \t}\n                \tbreak;\n                case KeyEvent.VK_PAGE_UP:\n                \tif(autocompletedtextComp.isEnabled() && isItemSelectedAtPopupList()) {\n                \t\tselectPreviousPage();\n                \t\tkeyEvent.consume();\n                \t}\n                \tbreak;\n                case KeyEvent.VK_HOME:\n                \tif(autocompletedtextComp.isEnabled() && isPopupListShowing()) {\n                \t\tselectFirstValue();\n                \t\tkeyEvent.consume();\n                \t}\n                \tbreak;\n                case KeyEvent.VK_END:\n                \tif(autocompletedtextComp.isEnabled() && isPopupListShowing()) {\n                \t\tselectLastValue();\n                \t\tkeyEvent.consume();\n                \t}\n                \tbreak;\n                case KeyEvent.VK_LEFT:\n                \thideAutocompletionPopup();\n                \tbreak;\n            \tdefault:\n                }\n        \t}\n        });\n    }\n\t\n\t@Override\n    protected void hideAutocompletionPopup() {\n\t\tsynchronized (popup) {\n\t\t\tif (popup.isVisible())\n        \t\tpopup.setVisible(false);\n\t\t\tautocompletedtextComp.getDocument().removeDocumentListener(documentListener);\n\t\t}\n\t}\n\t\n\t@Override\n    protected void startNewShowingThread(int delay) {\n    \t(showingThread = new ShowingThreadImp(delay)).start();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/autocomplete/TextFieldCompletion.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.autocomplete;\n\nimport java.awt.event.KeyAdapter;\nimport java.awt.event.KeyEvent;\n\nimport javax.swing.text.BadLocationException;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.ui.autocomplete.completers.Completer;\n\n/**\n * TextFieldCompleter is a CompleterType which suite to text-field.\n * \n * @author Arik Hadas, based on the code of Santhosh Kumar: http://www.jroller.com/santhosh/entry/file_path_autocompletion\n */\n\npublic class TextFieldCompletion extends CompletionType {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(TextFieldCompletion.class);\n\t\n    private class ShowingThreadImp extends ShowingThread {\n    \tShowingThreadImp(int delay) {\n    \t\tsuper(delay);\n    \t}\n\n\t\t@Override\n        void showAutocompletionPopup() {\n\t        if (autocompletedtextComp.isShowing() && autocompletedtextComp.isEnabled() && updateListData(list)){\n\t\t\t\t\t            \n\t            list.setVisibleRowCount(Math.min(list.getModel().getSize() ,VISIBLE_ROW_COUNT));\n\t            \n\t            int x;\n\t            try{\t                \n\t                x = autocompletedtextComp.modelToView().x;\n\t            } catch(BadLocationException e){ \n\t                // this should never happen!!! \n                    LOGGER.debug(\"Caught exception\", e);\n\t                return;\n\t            }\n\t            if (autocompletedtextComp.hasFocus()) {\t            \t\n\t            \tif (!isStopped) {\n\t            \t\tlist.ensureIndexIsVisible(0);\n\t            \t\tsynchronized(popup) {\n\t\t            \t\tpopup.show(autocompletedtextComp.getTextComponent(), x, autocompletedtextComp.getHeight());\n\t\t            \t\t\n\t\t            \t\t// probably because of swing's bug, sometimes the popup window looks\n\t\t            \t\t// as a gray rectangle - repainting solves it.\n\t\t            \t\tpopup.repaint();\n\t            \t\t}\n\t            \t}\n\t            }\t            \n\t        }\n\t\t}\n    }\n        \n    public TextFieldCompletion(AutocompleterTextComponent comp, Completer completer){\n    \tsuper(comp, completer);\n\n    \tautocompletedtextComp.getDocument().addDocumentListener(documentListener);\n\n        autocompletedtextComp.addKeyListener(new KeyAdapter() {\n        \t@Override\n            public void keyPressed(KeyEvent keyEvent) {\n                \n                switch (keyEvent.getKeyCode()) {\n                case KeyEvent.VK_ENTER:\n                \tif (isItemSelectedAtPopupList()) {\n                \t\thideAutocompletionPopup();\n                \t\tacceptListItem(list.getSelectedValue());\n                \t\tkeyEvent.consume();\n                \t}\n                \telse\n                \t\tautocompletedtextComp.OnEnterPressed(keyEvent);\n                \tbreak;\n                case KeyEvent.VK_ESCAPE:\n                \tif (isPopupListShowing()) {\n                \t\tif (autocompletedtextComp.isEnabled())\n                \t\t\thideAutocompletionPopup();\n                \t\tkeyEvent.consume();\n                \t}\n                \telse\n                \t\tautocompletedtextComp.OnEscPressed(keyEvent);\n                \tbreak;\n                case KeyEvent.VK_UP:\n                \tif(autocompletedtextComp.isEnabled() && popup.isVisible()) {\n                \t\tselectPreviousPossibleValue();\n                \t\tkeyEvent.consume();\n                \t}\n                \tbreak;\n                case KeyEvent.VK_SPACE:\n                \t// The combination of cntrl+space makes open the auto-complete popup without delay.\n                \tif (keyEvent.isControlDown()) {\n                \t\tif (!popup.isVisible()) {\n                    \t\tautocompletedtextComp.moveCaretToEndOfText();\n                    \t\tcreateNewShowingThread(0);\n                    \t}\n                \t}\n                \tbreak;\n                case KeyEvent.VK_DOWN:\n                \tif(autocompletedtextComp.isEnabled()){ \n                        if(popup.isVisible()) {\n                            selectNextPossibleValue();\n                            keyEvent.consume();\n                        }\n                        else {                \t\n                        \tautocompletedtextComp.moveCaretToEndOfText();\n                    \t\tcreateNewShowingThread(0);\n                        }\n                    }\n                \tbreak;\n                case KeyEvent.VK_PAGE_DOWN:\n                \tif(autocompletedtextComp.isEnabled() && isPopupListShowing()) {\n                \t\tselectNextPage();\n                \t\tkeyEvent.consume();\n                \t}\n                \tbreak;\n                case KeyEvent.VK_PAGE_UP:\n                \tif(autocompletedtextComp.isEnabled() && isItemSelectedAtPopupList()) {\n                \t\tselectPreviousPage();\n                \t\tkeyEvent.consume();\n                \t}\n                \tbreak;\n                case KeyEvent.VK_HOME:\n                \tif(autocompletedtextComp.isEnabled() && isPopupListShowing()) {\n                \t\tselectFirstValue();\n                \t\tkeyEvent.consume();\n                \t}\n                \tbreak;\n                case KeyEvent.VK_END:\n                \tif(autocompletedtextComp.isEnabled() && isPopupListShowing()) {\n                \t\tselectLastValue();\n                \t\tkeyEvent.consume();\n                \t}\n                \tbreak;\n                case KeyEvent.VK_LEFT:\n                \thideAutocompletionPopup();\n                \tbreak;\n                default:\n                }\n            }\n        });\n    }\n            \n    @Override\n    protected void startNewShowingThread(int delay) {\n    \t(showingThread = new ShowingThreadImp(delay)).start();\n    }\n        \n    @Override\n    protected void hideAutocompletionPopup() {\n\t\tsynchronized (popup) {\n\t\t\tif (popup.isVisible())\n        \t\tpopup.setVisible(false);\n\t\t}\n\t}\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/autocomplete/TypicalAutocompleterEditableCombobox.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.autocomplete;\n\nimport com.mucommander.ui.combobox.AutocompleteEditableCombobox;\n\nimport java.awt.event.KeyEvent;\n\n/**\n * This <code>AutocompleterTextComponent</code> implements {@link #OnEnterPressed(java.awt.event.KeyEvent)}\n * and {@link #OnEscPressed(java.awt.event.KeyEvent)} as the typical AutocompleteEditableCombobox's ops.\n * \n * @author Arik Hadas\n */\n\npublic class TypicalAutocompleterEditableCombobox extends AutocompleterTextComponent {\n\tprivate AutocompleteEditableCombobox editableCombobox;\n\t\n\tpublic TypicalAutocompleterEditableCombobox(AutocompleteEditableCombobox editableCombobox) {\n\t\tsuper(editableCombobox);\t\t\n\t\tthis.editableCombobox = editableCombobox;\t\t\n\t}\n\n\t@Override\n    public void OnEnterPressed(KeyEvent keyEvent) {\n\t\teditableCombobox.respondToEnterKeyPressing(keyEvent);\n\t}\n\n\t@Override\n    public void OnEscPressed(KeyEvent keyEvent) {\n\t\teditableCombobox.respondToEscapeKeyPressing(keyEvent);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/autocomplete/completers/ComboboxOptionsCompleter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.autocomplete.completers;\n\nimport com.mucommander.ui.autocomplete.AutocompleterTextComponent;\nimport com.mucommander.ui.autocomplete.completers.services.PrefixFilter;\n\nimport java.util.List;\nimport java.util.Vector;\n\n/**\n * ComboboxOptionsCompleter is a Completer based on the items of combo-box.\n * \n * @author Arik Hadas\n */\n\npublic class ComboboxOptionsCompleter extends Completer {\n\n\tpublic ComboboxOptionsCompleter() {\t}\n\n\t@Override\n    protected List<String> getUpdatedSuggestions(AutocompleterTextComponent component) {\n    \treturn PrefixFilter.createPrefixFilter(component.getText()).filter(component.getItemNames());\n\t}\n\t\n\t@Override\n    public void updateTextComponent(final String selected, AutocompleterTextComponent comp) {\n\t\tcomp.setText(selected);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/autocomplete/completers/Completer.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.autocomplete.completers;\n\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.ui.autocomplete.AutocompleterTextComponent;\nimport com.mucommander.ui.autocomplete.completers.services.CompletionService;\n\nimport javax.swing.*;\nimport java.net.MalformedURLException;\nimport java.util.*;\n\n/**\n * Interface that each type of completion must implement.\n * It defines 2 methods:\n * * <ul>\n *   <li>updateListData - Update what the auto-completion popup shows, depending on the data in text component</li>\n *   <li>updateTextComponent - Update the text component's text according to the selected item from the auto-completion popup</li>\n * </ul>\n *  \n * @author Arik Hadas, based on the code of Santhosh Kumar: http://www.jroller.com/santhosh/entry/file_path_autocompletion\n */\n\npublic abstract class Completer {\n\tprivate final Set<CompletionService> services = new LinkedHashSet<>();\n\n\t\n\t/**\n\t * This function gets an AutocompleterTextComponent and returns a Vector of suggestions for\n\t * completion, for the component's value.\n\t * \n\t * @param component - an AutocompleterTextComponent.\n\t * @return Vector of suggestions for completion.\n\t */\n\tprotected abstract List<String> getUpdatedSuggestions(AutocompleterTextComponent component);\n    \n\t/**\n\t * update list model depending on the data in text component\n\t * \n\t * @param list - auto-completion popup's list that should be updated.\n\t * @param comp - text component\n\t * @return true if an auto-completion popup with the updated list should be shown, false otherwise.\n\t */\n    public boolean updateListData(final JList<String> list, AutocompleterTextComponent comp) {\n    \tlist.setListData(getUpdatedSuggestions(comp).toArray(String[]::new));\n\n    \tif (list.getModel().getSize() == 1) {\n    \t\ttry {\n\t\t\t\tString typedFilename = FileURL.getFileURL(comp.getText()).getFilename();\n\n\t\t\t\t// in case the suggestions-list contains only one suggestion, and it\n\t\t\t\t// match the typed path - do not show an auto-completion popup.\n\t\t\t\tif (typedFilename == null || typedFilename.equalsIgnoreCase(list.getModel().getElementAt(0)))\n\t\t\t\t\treturn false;\n\t\t\t} catch (MalformedURLException e) {\n\t\t\t\te.printStackTrace();\n\t\t\t}\n    \t}\n    \t\n    \treturn list.getModel().getSize() > 0;\n    }\n \n    /**\n     * update text component according to the given string.\n     * \n     * @param selected - selected item from auto-completion popup list.\n     * @param comp - text component.\n     */\n    public abstract void updateTextComponent(final String selected, AutocompleterTextComponent comp);\n    \n    /**\n\t * Add service to this completer.\n\t * \n\t * The order in which the services is being registered is important, \n\t * see: <code>tryToCompleteFromServices</code>.\n\t * \n\t * @param service - Service to be added. \n\t */\n\tprotected void registerService(CompletionService service) {\n\t\tservices.add(service);\n\t}\n\t\n\t/**\n\t * Gather the possible completions for the given path from\n\t * all the services registered to this completer.\n\t * \n\t * @param path - The path to be completed.\n\t * @return Vector that contain all the possible completions\n\t * \t\t\twhich were retured from the registered services.\n\t */\n\tprotected Vector<String> getPossibleCompletionsFromServices(String path) {\n\t\tVector<String> result = new Vector<>();\n        for (CompletionService service : services)\n            result.addAll(service.getPossibleCompletions(path));\n\t\treturn result;\n\t}\n\t\n\t/**\n\t * Given the selected string (from the auto-completion's list), try to \n\t * get a completion from the registered services.\n\t * \n\t * The first completion that found will be returned, thus the order in which\n\t * the services are registered is significant. \n\t * \n\t * @param selectedString - selected string (from the auto-completion's list).\n\t * @return null if could not found any completion for the given string \n\t * from the registered services, otherwise the founded completion is returned.\n\t */\n\tprotected String tryToCompleteFromServices(String selectedString) {\n        for (CompletionService service : services) {\n\t\t\tString location = (service).complete(selectedString);\n\t\t\tif (location != null) {\n\t\t\t\treturn location;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/autocomplete/completers/LocationCompleter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.autocomplete.completers;\n\nimport com.mucommander.ui.autocomplete.AutocompleterTextComponent;\n\nimport java.util.Vector;\n\n/**\n * LocationCompleter is a Completer based on locations, meaning root folders, \n * browsable file paths, bookmarks and system variables.\n * \n * @author Arik Hadas, based on the code of Santhosh Kumar: http://www.jroller.com/santhosh/entry/file_path_autocompletion\n */\n\npublic class LocationCompleter extends Completer {\n\t\n\tpublic LocationCompleter(){ \n        registerService(ServiceFactory.getVolumesService());\n        registerService(ServiceFactory.getBrowsableFilesService());\n        registerService(ServiceFactory.getBookmarksService());\n        registerService(ServiceFactory.getSystemVariablesService());\n    }\n\n\t@Override\n    protected Vector<String> getUpdatedSuggestions(AutocompleterTextComponent component) {\n    \treturn getPossibleCompletionsFromServices(component.getText());\n    }\n \n    @Override\n    public void updateTextComponent(final String selected, AutocompleterTextComponent comp){\n        if (selected == null) {\n            return;\n        }\n        \n        String location = tryToCompleteFromServices(selected);        \n        if (comp.isEnabled() && location != null) {\n            comp.setText(location);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/autocomplete/completers/PathCompleter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.autocomplete.completers;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.autocomplete.AutocompleterTextComponent;\n\nimport java.util.Vector;\n\n/**\n * FileCompleter is a Completer based on root folders and file paths. \n * \n * @author Arik Hadas, based on the code of Santhosh Kumar: http://www.jroller.com/santhosh/entry/file_path_autocompletion\n */\n \npublic class PathCompleter extends Completer {\n\n    private String currentLocation;\n\n\tpublic PathCompleter() {\n        super();\n\t\tregisterService(ServiceFactory.getVolumesService());\n\t\tregisterService(ServiceFactory.getAllFilesService());\n    }\n \n\t@Override\n    protected Vector<String> getUpdatedSuggestions(AutocompleterTextComponent component) {\n        String text = component.getText();\n        Vector<String> result = getPossibleCompletionsFromServices(text);\n        if (currentLocation != null) {\n            result.addAll(getPossibleCompletionsFromServices(currentLocation + text));\n        }\n        return result;\n    }\n \n    @Override\n    public void updateTextComponent(final String selected, AutocompleterTextComponent comp){\n        if (selected == null) {\n            return;\n        }\n                                 \n        String location = tryToCompleteFromServices(selected);\n        if (comp.isEnabled() && location != null) {\n            comp.setText(location);\n        }\n    }\n\n    public void setCurrentLocation(AbstractFile currentLocation) {\n        this.currentLocation = currentLocation.getAbsolutePath();\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/autocomplete/completers/ServiceFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.autocomplete.completers;\n\nimport com.mucommander.commons.file.filter.AttributeFileFilter;\nimport com.mucommander.commons.file.filter.AttributeFileFilter.FileAttribute;\nimport com.mucommander.ui.autocomplete.completers.services.AllFilesService;\nimport com.mucommander.ui.autocomplete.completers.services.BookmarksService;\nimport com.mucommander.ui.autocomplete.completers.services.CompletionService;\nimport com.mucommander.ui.autocomplete.completers.services.FilteredFilesService;\nimport com.mucommander.ui.autocomplete.completers.services.SystemVariablesService;\nimport com.mucommander.ui.autocomplete.completers.services.VolumesService;\n\n/**\n * A factory class to produce completion-services.\n * \n * @author Arik Hadas\n */\n\npublic class ServiceFactory {\n\t\n\tpublic static CompletionService getAllFilesService() {\n\t\treturn new AllFilesService();\n\t}\n\t\n\tpublic static CompletionService getBrowsableFilesService() {\n\t\treturn new FilteredFilesService(new AttributeFileFilter(FileAttribute.BROWSABLE));\n\t}\n\t\n\tpublic static CompletionService getVolumesService() {\n\t\treturn new VolumesService();\n\t}\n\t\n\tpublic static CompletionService getBookmarksService() {\n\t\treturn new BookmarksService();\n\t}\n\t\n\tpublic static CompletionService getSystemVariablesService() {\n\t\treturn new SystemVariablesService();\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/autocomplete/completers/services/AllFilesService.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.autocomplete.completers.services;\n\nimport com.mucommander.commons.file.AbstractFile;\n\nimport java.io.IOException;\n\n/**\n * This <code>FilesService</code> returns all the files in a given directory.\n * \n * @author Arik Hadas\n */\n\npublic class AllFilesService extends FilesService {\n\n\t@Override\n    protected AbstractFile[] getFiles(AbstractFile directory) throws IOException {\n\t\treturn directory.ls();\n\t}\t\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/autocomplete/completers/services/BookmarksService.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.autocomplete.completers.services;\n\nimport com.mucommander.bookmark.Bookmark;\nimport com.mucommander.bookmark.BookmarkListener;\nimport com.mucommander.bookmark.BookmarkManager;\n\nimport java.util.Arrays;\nimport java.util.Vector;\n\n/**\n * This <code>CompletionService</code> handles bookmarks completion.\n * \n * @author Arik Hadas\n */\n\npublic class BookmarksService implements CompletionService, BookmarkListener {\n\tprivate String[] sortedBookmarkNames;\n\tprivate String[] sortedBookmarkLocations;\n\t\n\tpublic BookmarksService() {\n\t\tfetchBookmarks();\n\t\t\n\t\t// Register as a bookmark-listener, in order to be up-to-date with existing bookmarks.\n        BookmarkManager.addBookmarkListener(this);\n\t}\n\t\n\tpublic Vector<String> getPossibleCompletions(String path) {\n\t\tVector<String> result = new Vector<>();\n\t\tPrefixFilter filter = PrefixFilter.createPrefixFilter(path);\n\t\tresult.addAll(filter.filter(sortedBookmarkNames));\n\t\tresult.addAll(filter.filter(sortedBookmarkLocations)); \n\t\treturn result;\n\t}\n\t\n\tpublic String complete(String selectedCompletion) {\n\t\tString result = null;\n\t\tint nbBookmarks = sortedBookmarkNames.length;\n\t\tint i;\n\t\tfor (i = 0; i < nbBookmarks; i++)\n\t\t\tif (sortedBookmarkNames[i].equalsIgnoreCase(selectedCompletion)) {\n\t\t\t\tresult = sortedBookmarkNames[i];\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\tfor (i = 0; i < nbBookmarks; i++)\n\t\t\tif (sortedBookmarkLocations[i].equalsIgnoreCase(selectedCompletion)) {\n\t\t\t\tresult = sortedBookmarkLocations[i];\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\treturn result;\n\t}    \n\t\n    /**\n     * Returns a sorted array of bookmarks names.\n     *\n     * @return a sorted array of bookmarks names\n     */\n    private String[] getSortedBookmarkNames() {\n    \tVector<Bookmark> bookmarks = BookmarkManager.getBookmarks();\n        int nbBookmarks = bookmarks.size();\n    \tString[] result = new String[nbBookmarks];\n    \tfor (int i=0; i<nbBookmarks; i++)\n    \t\tresult[i] = bookmarks.elementAt(i).getName();\n    \tArrays.sort(result, String.CASE_INSENSITIVE_ORDER);\n    \treturn result;\n    }\n    \n    /**\n     * Returns array of bookmarks locations - the item at index i in the returned array, \n     * \tis the location of the bookmark at index i in sortedBookmarkNames array.\n     *\n     * @return array of bookmarks locations.\n     */\n    private String[] getLocationsOfBookmarks() {    \t\n        int nbBookmarks = sortedBookmarkNames.length;\n    \tString[] result = new String[nbBookmarks];\n    \tfor (int i=0; i<nbBookmarks; i++)\n    \t\tresult[i] = BookmarkManager.getBookmark(sortedBookmarkNames[i]).getLocation();\n        Arrays.sort(result, String.CASE_INSENSITIVE_ORDER);\n    \treturn result;\n    }\n    \n    protected void fetchBookmarks() {\n    \t// get bookmarks names.\n    \tsortedBookmarkNames = getSortedBookmarkNames();\n    \t// get bookmarks locations.\n    \tsortedBookmarkLocations = getLocationsOfBookmarks();\n    }\n    \n    // Bookmarks listening:\n\tpublic void bookmarksChanged() {\n\t\tfetchBookmarks();\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/autocomplete/completers/services/CompletionService.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.autocomplete.completers.services;\r\n\r\nimport java.util.List;\r\n\r\n/**\r\n * CompletionService is used to handle completions according to certain criteria.\r\n * It defines 2 methods:\r\n * * <ul>\r\n *   <li>getPossibleCompletions - return possible completions for a given path</li>\r\n *   <li>complete - return a path corresponding to a given completion</li>\r\n * </ul>\r\n * \r\n * @author Arik Hadas\r\n */\r\n\r\npublic interface CompletionService {\r\n\t\r\n\t/**\r\n\t * Return a group of suggested completions corresponding to the given path, \r\n\t * according to this service's criteria.\r\n\t * \r\n\t * @param path - a path.\r\n\t * @return a Vector of possible completions.\r\n\t */\r\n\tList<String> getPossibleCompletions(String path);\r\n\t\r\n\t/**\r\n\t *  If the given completion match one of my suggested completions, return \r\n\t *  a corresponding path, null otherwise.\r\n\t *   \r\n\t * @param selectedCompletion - string that represent a completion.\r\n\t * @return a path if the given completion was suggested by this service, null otherwise. \r\n\t */\r\n\tString complete(String selectedCompletion);\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/autocomplete/completers/services/FilesService.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.autocomplete.completers.services;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\n\n/**\n * This <code>CompletionService</code> handles file paths completion.\n * \n * @author Arik Hadas\n */\n\npublic abstract class FilesService implements CompletionService {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(FilesService.class);\n\t\n\tprivate String cachedDirectoryName;\n\tprivate String[] cachedDirectoryFileNames;\n\tprivate long cachedDirectoryDate;\n\t\n\tpublic FilesService() {\n\t\tcachedDirectoryFileNames = new String[0];\n\t\tcachedDirectoryDate = -1;\n\t}\n\n\t/**\n\t * This abstract function gets a directory and should return it's children\n\t * files that match a certain criteria.\n\t * \n\t * @param directory - a directory.\n\t * @return subgroup of the given directory's children files.\n\t * @throws IOException\n\t */\n\tprotected abstract AbstractFile[] getFiles(AbstractFile directory) throws IOException;\n\t\n\tpublic List<String> getPossibleCompletions(String path) {\n\t\tList<String> result = new ArrayList<>();\n\t\tint index = Math.max(path.lastIndexOf('\\\\'), path.lastIndexOf('/'));\t\n\t\tif (index != -1) {\n\t        String currentDirectoryName = path.substring(0, index+1);\n\t        \n\t        AbstractFile currentDirectory = FileFactory.getFile(currentDirectoryName);\n\t        if (currentDirectory != null && currentDirectory.exists()) {\t        \n\t\t        long currentDirectoryDate = currentDirectory.getLastModifiedDate();\n\t\t        if (cachedDirectoryName == null || !cachedDirectoryName.equals(currentDirectoryName) || currentDirectoryDate != cachedDirectoryDate) {\n\t\t        \tAbstractFile[] currentDirectoryFiles;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tcurrentDirectoryFiles = getFiles(currentDirectory);\n\t\t\t\t\t} catch (IOException e) {\n                        LOGGER.debug(\"Caught exception\", e);\n\t\t\t\t\t\treturn new ArrayList<>();\n\t\t\t\t\t}\n\t\t\n\t\t        \tint nbCurrentDirectoryFiles = currentDirectoryFiles.length;\n\t\t        \tcachedDirectoryFileNames = new String[nbCurrentDirectoryFiles];\n\t\t        \t\n\t\t        \tfor (int i=0; i<nbCurrentDirectoryFiles; i++) {\n\t\t        \t\tAbstractFile abstractFileI = currentDirectoryFiles[i];\n\t\t\t\t\t\tcachedDirectoryFileNames[i] = abstractFileI.getName() + (abstractFileI.isDirectory() ? abstractFileI.getSeparator() : \"\");\n\t\t        \t}\n\t\t        \t\n\t\t        \tArrays.sort(cachedDirectoryFileNames, String.CASE_INSENSITIVE_ORDER);\n\t\t        \t\n\t\t        \tcachedDirectoryName = currentDirectory.getAbsolutePath() + (currentDirectory.isDirectory() ? \"\" : currentDirectory.getSeparator());\n\t\t        \tcachedDirectoryDate = currentDirectoryDate;\n\t\t        }\n\t\t\t\t\n\t\t        final String prefix = index==path.length()-1 ? null : path.substring(index + 1).toLowerCase();\n\t\t        result = PrefixFilter.createPrefixFilter(prefix).filter(cachedDirectoryFileNames);\n\t        }\n\t\t}\n\t\treturn result;\n\t}\n\t\n\tpublic String complete(String selectedCompletion) {\n        for (String cachedDirectoryFileName : cachedDirectoryFileNames)\n            if (cachedDirectoryFileName.equalsIgnoreCase(selectedCompletion)) {\n                return cachedDirectoryName + cachedDirectoryFileName;\n            }\n        return null;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/autocomplete/completers/services/FilteredFilesService.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.autocomplete.completers.services;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.filter.FileFilter;\n\nimport java.io.IOException;\n\n/**\n * This <code>FilesService</code> returns filtered files in a given directory,\n * according to a certain <code>FileFilter</code>.\n * \n * @author Arik Hadas\n */\n\npublic class FilteredFilesService extends FilesService {\n\tprivate FileFilter fileFilter;\n\t\n\tpublic FilteredFilesService(FileFilter fileFilter) {\n\t\tthis.fileFilter = fileFilter;\n\t}\n\n\t@Override\n    protected AbstractFile[] getFiles(AbstractFile directory) throws IOException {\n\t\treturn fileFilter.filter(directory.ls());\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/autocomplete/completers/services/PrefixFilter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.autocomplete.completers.services;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * A <code>PrefixFilter</code> matches strings that start with certain prefix.\n * \n * @author Arik Hadas\n */\n\npublic class PrefixFilter {\n\tprivate final String prefix;\n\t\n\tprivate PrefixFilter(String prefix) {\n\t\tthis.prefix = prefix!=null ? prefix.toLowerCase() : null;\n\t}\n\t\n\t/**\n\t * \n\t * @param prefix - The prefix that each string should start with in order to pass this IMAGE_FILTER.\n\t * @return A IMAGE_FILTER of the given prefix.\n\t */\n\tpublic static PrefixFilter createPrefixFilter(String prefix) {\n\t\treturn new PrefixFilter(prefix);\n\t}\n\t\n\t/**\n\t * \n\t * @param input - Some string.\n\t * @return <code>true</code> if the given input was accepted by this IMAGE_FILTER, <code>false</code> otherwise.\n\t */\n\tpublic boolean accept(String input) {\n\t\treturn prefix == null || input.toLowerCase().startsWith(prefix);\n\t}\n\t\t\n\t/**\n\t * Convenient method that filters out strings that do not start with this IMAGE_FILTER's prefix.\n\t * \n\t * @param strings - Array of strings.\n\t * @return Vector of strings which start with this IMAGE_FILTER's prefix.\n\t */\n\tpublic List<String> filter(String[] strings) {\n\t\tList<String> result = new ArrayList<>();\n        for (String s : strings) {\n            if (accept(s))\n                result.add(s);\n        }\n\t\treturn result;\n\t}\n\t\n\t/**\n\t * Convenient method that filters out strings that do not start with this IMAGE_FILTER's prefix.\n\t * \n\t * @param strings - Vector of strings.\n\t * @return Vector of strings which start with this IMAGE_FILTER's prefix.\n\t */\n\tpublic List<String> filter(List<String> strings) {\n\t\tList<String> result = new ArrayList<>();\n        for (String s : strings) {\n\t\t\tif (accept(s))\n\t\t\t\tresult.add(s);\n\t\t}\t\t\n\t\treturn result;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/autocomplete/completers/services/SystemVariablesService.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.autocomplete.completers.services;\n\nimport java.util.*;\n\n/**\n * This <code>CompletionService</code> handles system variables completion.\n *\n * @author Arik Hadas\n */\n\npublic class SystemVariablesService implements CompletionService {\n    private final String[] cachedKeyNames;\n\n    public SystemVariablesService() {\n        Set<String> keys = System.getenv().keySet();\n        int nbKeys = keys.size();\n        cachedKeyNames = new String[nbKeys];\n        Iterator<String> iter = keys.iterator();\n        for (int i = 0; i < nbKeys; i++)\n            cachedKeyNames[i] = \"$\" + iter.next();\n        Arrays.sort(cachedKeyNames, String.CASE_INSENSITIVE_ORDER);\n    }\n\n    public List<String> getPossibleCompletions(String path) {\n        return PrefixFilter.createPrefixFilter(path).filter(cachedKeyNames);\n    }\n\n    public String complete(String selectedCompletion) {\n        for (String cachedKeyName : cachedKeyNames)\n            if (cachedKeyName.equalsIgnoreCase(selectedCompletion)) {\n                return cachedKeyName;\n            }\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/autocomplete/completers/services/VolumesService.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.autocomplete.completers.services;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.impl.local.LocalFile;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Vector;\n\n/**\n * This <code>CompletionService</code> handles root folders completion.\n * \n * @author Arik Hadas\n */\n\npublic class VolumesService implements CompletionService {\n\tprivate List<String> lastSuggestedCompletions = new Vector<>();\n\t\n\tpublic VolumesService() {}\n\n    /**\n     * Resolves and returns a sorted array of root (top level) folder names. Those folders are purposively not cached\n     * so that newly mounted folders will be returned.\n     *\n     * @return a sorted array of root folder names\n     */\n\tpublic List<String> getPossibleCompletions(String path) {\n\t\tlastSuggestedCompletions.clear();\n\t\tint index = Math.max(path.lastIndexOf('\\\\'), path.lastIndexOf('/'));\n\t\tif (index == -1) {\n\t\t\tAbstractFile[] fileRoots = LocalFile.getVolumes();\n\t    \tint nbFolders = fileRoots.length;\n\t    \tString[] rootFolderNames = new String[nbFolders];\n\t    \tfor (int i=0; i<nbFolders; i++)\n\t    \t\trootFolderNames[i] = fileRoots[i].getAbsolutePath();\n\t    \tArrays.sort(rootFolderNames, String.CASE_INSENSITIVE_ORDER);\n\t    \tlastSuggestedCompletions = PrefixFilter.createPrefixFilter(path).filter(rootFolderNames);\n\t\t}\n\t\treturn lastSuggestedCompletions;\n\t}\n\n\tpublic String complete(String selectedCompletion) {\n        for (String lastSuggestedCompletion : lastSuggestedCompletions)\n            if (lastSuggestedCompletion.equalsIgnoreCase(selectedCompletion)) {\n                return lastSuggestedCompletion;\n            }\n\t\t\n\t\treturn null;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/border/MutableLineBorder.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.border;\n\nimport javax.swing.border.LineBorder;\nimport java.awt.*;\n\n/**\n * Implementation of <code>LineBorder</code> that allows applications to change the color after it's been instantiated.\n * @author Nicolas Rinaudo\n */\npublic class MutableLineBorder extends LineBorder {\n    /**\n     * Creates a line border with the specified color and a thickness = 1.\n     * @param color the color of the border.\n     */\n    public MutableLineBorder(Color color) {super(color);}\n\n    /**\n     * Creates a line border with the specified color and thickness.\n     * @param color     the color of the border\n     * @param thickness the thickness of the border\n     */\n    public MutableLineBorder(Color color, int thickness) {super(color, thickness);}\n\n    /**\n     * Creates a line border with the specified color, thickness, and corner shape.\n     * @param color          the color of the border\n     * @param thickness      the thickness of the border\n     * @param roundedCorners whether border corners should be round\n     */\n    public MutableLineBorder(Color color, int thickness, boolean roundedCorners) {super(color, thickness, roundedCorners);}\n\n\n\n    /**\n     * Sets this border's color.\n     * @param color the color of the border.\n     */\n    public void setLineColor(Color color) {lineColor = color;}\n\n    /**\n     * Sets this border's corner shape.\n     * @param roundedCorners whether border corners should be round\n     */\n    public void setRoundedCorners(boolean roundedCorners) {this.roundedCorners = roundedCorners;}\n\n    /**\n     * Sets this border's thickness.\n     * @param thickness the thickness of the border.\n     */\n    public void setThickness(int thickness) {this.thickness = thickness;}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/border/package.html",
    "content": "<body>\n  Provides various Border implementations.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/button/ArrowButton.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.button;\n\nimport com.mucommander.ui.icon.IconManager;\n\nimport javax.swing.*;\n\n/**\n * ArrowButton is a button displaying an arrow icon pointing to a specified direction (up/down/left/right).\n * The direction of the arrow can be changed at any time using {@link #setArrowDirection(Direction)}.\n *\n * @author Maxence Bernard\n */\npublic class ArrowButton extends JButton {\n\n    public enum Direction {\n        UP(\"arrow_up.png\"),\n        DOWN(\"arrow_down.png\"),\n        LEFT(\"arrow_left.png\"),\n        RIGHT(\"arrow_right.png\");\n\n        private final String fileName;\n\n        Direction(String fileName) {\n            this.fileName = fileName;\n        }\n    }\n\n\n    /**\n     * Creates a new ArrowButton with no initial arrow icon.\n     */\n    ArrowButton() {\n        super();\n    }\n\n    /**\n     * Creates a new ArrowButton showing an arrow icon pointing to the specified direction.\n     */\n    public ArrowButton(Direction direction) {\n        setArrowDirection(direction);\n    }\n\n    /**\n     * Creates a new ArrowButton using the specified Action and showing an arrow icon pointing to the specified direction.\n     */\n    public ArrowButton(Action action, Direction direction) {\n        super(action);\n\n        setArrowDirection(direction);\n    }\n\n\n    /**\n     * Changes the direction of the arrow icon to the specified one.\n     *\n     * @param direction can have one of the following values: {@link Direction#UP}, {@link Direction#DOWN},\n     * {@link Direction#LEFT} or {@link Direction#RIGHT}\n     */\n    void setArrowDirection(Direction direction) {\n        setIcon(IconManager.getIcon(IconManager.IconSet.COMMON, direction.fileName));\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/button/ButtonChoicePanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.button;\n\nimport com.mucommander.ui.helper.MnemonicHelper;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.FocusEvent;\nimport java.awt.event.FocusListener;\nimport java.awt.event.KeyEvent;\nimport java.awt.event.KeyListener;\n\n\n/**\n * ButtonChoicePanel lays out an array of buttons on a grid and provides an easy way\n * for the user to navigate and select buttons :\n * <ul>\n *  <li>At any given time, the current active button (the first one initially) can be selected by pressing ENTER\n *  <li>LEFT key goes back to the previous button, to the last button if current button is the first one\n *  <li>RIGHT key goes forward one button, to the first button if current button is the last one\n *  <li>UP key goes up one row, to the last row if current button is on the first row\n *  <li>DOWN key goes down one row, to the first row if current button is on the last row\n *  <li>Buttons can directly be selected by pressing the Mnemonic associated with the button (if any)\n * </ul>\n * This does not interfere with regular focus management where TAB (resp. Shift+TAB) goes to the next (resp. previous)\n * focusable component.\n * \n * @author Maxence Bernard\n */\npublic class ButtonChoicePanel extends JPanel implements KeyListener, FocusListener {\n\n    /** Provided JButton instances */\n    private final JButton[] buttons;\n\t\n    /** RootPane associated with this ButtonChoicePanel */\n    private final JRootPane rootPane;\n\n    /** Number of columns of the buttons grid */\n    private final int nbCols;\n    /** Number of row of the buttons grid */\n    private final int nbRows;\n\n    /** Current button, i.e. the one that currently has focus */\n    private int currentButton;\n\t\n    /** Total number of buttons */\n    private final int nbButtons;\n\n\t\n    /**\n     * Creates a new ButtonChoicePanel and lays out the given buttons on a grid\n     * according to the provided number of columns.\n     *\n     * <p>Initial focus will be given to the first button.\n     *\n     * @param buttons  the JButton instances to layout\n     * @param nbCols   number of columns for the buttons grid, if &lt;= 0 all buttons will be put on a single row\n     * @param rootPane associated with this ButtonChoicePanel\n     */\n    public ButtonChoicePanel(JButton[] buttons, int nbCols, JRootPane rootPane) {\n        this.buttons = buttons;\n        this.nbButtons = buttons.length;\n        this.nbCols = nbCols <= 0 ? nbButtons : nbCols;\n        this.rootPane = rootPane;\n        this.nbRows = nbCols <= 0 ? 1 : nbButtons/nbCols+(nbButtons % nbCols == 0 ? 0 : 1);\n\n        // If the provided number of columns is <= 0, lay out all buttons on a single row\n        // and use FlowLayout to do that\n        if (nbCols <= 0) {\n            setLayout(new FlowLayout(FlowLayout.RIGHT));\n        }\n        // Use GridLayout to lay out buttons on 2-dimensional grid\n        else {\n            setLayout(new GridLayout(0, nbCols));\n        }\n\n        for(int i = 0; i < nbButtons; i++) {\n            JButton button = buttons[i];\n            // Listener to key events to transfer focus \n            button.addKeyListener(this);\n\n            // Allow buttons to be made 'default buttons' when enter is pressed\n            button.setDefaultCapable(true);\n\n            // Listen to focus events to make the focused button the default button\n            button.addFocusListener(this);\n\n            add(button);\n        }\n\n        // Set mnemonics to buttons\n        updateMnemonics();\n\n        // First button is made 'default button'\n        rootPane.setDefaultButton(buttons[0]);\n        this.currentButton = 0;\n    }\n\n\n    /**\n     * Updates the buttons mnemonics. This method should be called when at least one of the button's label has changed.\n     */\n    public void updateMnemonics() {\n        MnemonicHelper mnemonicHelper = new MnemonicHelper();\n        char mnemonic;\n\n        JButton button;\n        for(int i=0; i<nbButtons; i++) {\n            button = buttons[i];\n            mnemonic = mnemonicHelper.getMnemonic(button);\n            if(mnemonic!=0)\n                button.setMnemonic(mnemonic);\n        }\n    }\n\n\n    /////////////////////////\n    // KeyListener methods //\n    /////////////////////////\n\n    public void keyPressed(KeyEvent e) {\n    \tint keyCode = e.getKeyCode();\n\n        int oldCurrentButton = currentButton;\n\n        // LEFT key goes back one button, to the last button if current button is the first one\n        if (keyCode==KeyEvent.VK_LEFT) {\n            this.currentButton = currentButton==0?nbButtons-1:currentButton-1;\n        }\n        // RIGHT key goes forward one button, to the first button if current button is the last one\n        else if (keyCode==KeyEvent.VK_RIGHT) {\n            this.currentButton = currentButton==nbButtons-1?0:currentButton+1;\n        }\n        // UP key goes up one row, to the last row if current button is on the first row\n        else if (keyCode==KeyEvent.VK_UP) {\n            if (currentButton<nbCols) {\t\t// If current button is on the first row\n                this.currentButton = (nbRows-1)*nbCols+currentButton%nbCols;\n                if(this.currentButton>nbButtons-1)\n                    this.currentButton -= nbCols;\n            }\n            else\n                this.currentButton -= nbCols;\n        }\n        // DOWN key goes down one row, to the first row if current button is on the last row\n        else if (keyCode==KeyEvent.VK_DOWN) {\n            if(nbButtons-currentButton>0 && nbButtons-currentButton<=nbCols)\t\t// If current button is on the last row\n                this.currentButton = currentButton%nbCols;\n            else\n                this.currentButton += nbCols;\n        }\n        // Click button when a key that corresponds to one of the buttons' mnemonic has been pressed\n        else if(!e.isAltDown()) {\n            for(int i=0; i<nbButtons; i++) {\n                if (keyCode==buttons[i].getMnemonic()) {\n                    buttons[i].doClick();\n                    return;\n                }\n            }\n        }\n\n        // Make the new button the default button and request focus on this button\n        if(oldCurrentButton!=currentButton) {\n            rootPane.setDefaultButton(buttons[currentButton]);\n            buttons[currentButton].requestFocus();\n        }\n    }\n\n    public void keyReleased(KeyEvent e) {\n    }\n\n    public void keyTyped(KeyEvent e) {\n    }\n\n\n    //////////////////////////////////\n    // FocusListener implementation //\n    //////////////////////////////////\n\n    public void focusGained(FocusEvent focusEvent) {\n        // Makes the newly focused button the default button\n        rootPane.setDefaultButton((JButton)focusEvent.getComponent());\n    }\n\n    public void focusLost(FocusEvent focusEvent) {\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/button/CollapseExpandButton.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.button;\n\nimport com.mucommander.ui.dialog.FocusDialog;\n\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.awt.event.KeyEvent;\nimport java.awt.event.KeyListener;\n\n/**\n * CollapseExpandButton provides an expand/collapse functionality to a component: clicking the button expands/collapses\n * the associated component making it visible/unvisible, and resizes the window that contains it so that it properly\n * fits.\n *\n * <p>This button shows a down/right arrow icon to reflect the current expanded/collapsed state.\n *\n * @author Maxence Bernard\n */\npublic class CollapseExpandButton extends ArrowButton implements ActionListener, KeyListener {\n\n    /** The component to collapse/expand */\n    private final Component comp;\n\n    /** True if this button is in the 'expanded' state and the associated component is being displayed */\n    private boolean expandedState;\n\n    \n    /**\n     * Creates a new CollapseExpandButton that renders the specified component visible/invisible.\n     *\n     * @param label the label to use for this button\n     * @param component the component that is to be expanded/collapsed\n     * @param expanded initial expanded/collapsed state\n     */\n    public CollapseExpandButton(String label, Component component, boolean expanded) {\n        this.comp = component;\n\n        setText(label);\n        setExpandedState(expanded, false);\n\n        addActionListener(this);\n        addKeyListener(this);\n    }\n\n\n    /**\n     * Expands/collapses the associated component, resizes the window that contains it, and updates the button's icon\n     * to reflect the new state.\n     *\n     * @param expanded if <code>true</code>, the component will be expanded, collapsed if <code>false</code>.\n     */\n    public void setExpandedState(boolean expanded) {\n        setExpandedState(expanded, true);\n    }\n\n\n    /**\n     * Returns the current expanded state of the component: <code>true</code> for expanded, <code>false</code> for\n     * collapsed.\n     *\n     * @return the current expanded state of the component: true for expanded, false for collapsed.\n     */\n    public boolean getExpandedState() {\n        return expandedState;\n    }\n\n    /**\n     * Sets the new expanded state for the component: <code>true</code> for expanded, <code>false</code> for collapsed.\n     * If specified, the window which contains the expanded/collapsed component will be repacked so that the component\n     * fits properly.\n     *\n     * @param expanded the new expanded state: true for expanded, false for collapsed.\n     * @param packWindow If true, the window which contains the expanded/collapsed component will be repacked so that\n     * the component fits properly.\n     */\n    private void setExpandedState(boolean expanded, boolean packWindow) {\n        comp.setVisible(expanded);\n        setArrowDirection(expanded ? Direction.DOWN : Direction.RIGHT);\n        this.expandedState = expanded;\n\n        final Container tla = getTopLevelAncestor();\n        if (packWindow) {\n            if (tla instanceof Window) {\n                ((Window) tla).pack();\n            }\n        }\n        if (tla instanceof FocusDialog) {\n            ((FocusDialog) tla).setStorageSuffix(expanded ? \"expanded\" : null);\n        }\n    }\n\n\n    ///////////////////////////////////\n    // ActionListener implementation //\n    ///////////////////////////////////\n\n    public void actionPerformed(ActionEvent actionEvent) {\n        setExpandedState(!expandedState, true);\n    }\n\n\n    ////////////////////////////////\n    // KeyListener implementation //\n    ////////////////////////////////\n\n\n    public void keyPressed(KeyEvent keyEvent) {\n        int keyCode = keyEvent.getKeyCode();\n        if (keyCode == KeyEvent.VK_RIGHT && !expandedState) {\n            setExpandedState(true, true);\n        } else if (keyCode == KeyEvent.VK_LEFT && expandedState) {\n            setExpandedState(false, true);\n        }\n    }\n\n    public void keyReleased(KeyEvent keyEvent) {\n    }\n\n    public void keyTyped(KeyEvent keyEvent) {\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/button/HelpButton.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.button;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport javax.swing.JButton;\nimport javax.swing.border.EmptyBorder;\n\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.commons.runtime.OsVersion;\nimport com.mucommander.ui.action.impl.GoToDocumentationAction;\nimport com.mucommander.ui.main.MainFrame;\n\n/**\n * This is a contextual 'Help' button to be used wherever help is available or needed. When clicked, it opens\n * muCommander's online documentation in the system's default browser. If a specified help topic is passed to the\n * constructor, the browser will open the said topic. If no topic is specified, the base documentation URL will be opened.\n *\n * <p>Unless explicitly set, this button has a standard help icon but no text label. A tooltip is displayed when\n * hovering over the button.\n *\n * @see com.mucommander.ui.action.impl.GoToDocumentationAction\n * @author Maxence Bernard\n */\npublic class HelpButton extends JButton {\n\n    /**\n     * Creates a new HelpButton with no initial topic.\n     *\n     * @param mainFrame the MainFrame this button is associated with\n     */\n    public HelpButton(MainFrame mainFrame) {\n        this(mainFrame, null);\n    }\n\n    /**\n     * Creates a new HelpButton with the specified topic.\n     *\n     * @param mainFrame the MainFrame this button is associated with\n     * @param helpTopic the help topic this button will open when clicked, <code>null</code> to open the base documentation URL\n     */\n    public HelpButton(MainFrame mainFrame, String helpTopic) {\n        Map<String, Object> properties = new HashMap<>();\n\n        GoToDocumentationAction action = new GoToDocumentationAction(mainFrame, properties);\n        setAction(action);\n\n        if (helpTopic != null) {\n            setHelpTopic(helpTopic);\n        }\n\n        // Note: the button's text and icon must be set after the action otherwise they'll be replaced by the action's\n\n        // Remove the action's label from this button's text\n        setText(null);\n\n        // Use the action's label as a tooltip\n        setToolTipText(action.getLabel());\n\n        if (OsFamily.MAC_OS_X.isCurrent() && OsVersion.MAC_OS_X_10_5.isCurrentOrHigher()) {\n            // If running Mac OS X 10.5 (and up), use the special client property to have a standard help button.\n            putClientProperty(\"JButton.buttonType\", \"help\");\n\n            // Remove the action's icon\n            setIcon(null);\n        } else {\n            setContentAreaFilled(false);\n            setBorderPainted(false);\n            setBorder(new EmptyBorder(0, 0, 0, 0));\n        }\n\n        setFocusable(false);\n    }\n\n    /**\n     * Returns the help topic this button will open when clicked, <code>null</code> if there is none.\n     *\n     * @return the help topic this button will open when clicked, <code>null</code> if there is none\n     */\n    public String getHelpTopic() {\n        return (String)getAction().getValue(GoToDocumentationAction.TOPIC_PROPERTY_KEY);\n    }\n    \n    /**\n     * Sets the help topic this button will open when clicked, <code>null</code> to open the base documentation URL.\n     *\n     * @param helpTopic the help topic this button will open when clicked, <code>null</code> to open the base documentation URL\n     */\n    public void setHelpTopic(String helpTopic) {\n        getAction().putValue(GoToDocumentationAction.TOPIC_PROPERTY_KEY, helpTopic);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/button/HelpButtonPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.button;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n/**\n * A simple panel that lays out a {@link HelpButton}, aligning to the right.\n *\n * @author Maxence Bernard\n */\npublic class HelpButtonPanel extends JPanel {\n\n    public HelpButtonPanel(HelpButton helpButton) {\n        super(new BorderLayout());\n\n        add(helpButton, BorderLayout.EAST);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/button/NonFocusableButton.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.button;\n\nimport javax.swing.Action;\nimport javax.swing.Icon;\nimport javax.swing.JButton;\nimport javax.swing.UIManager;\n\nimport com.mucommander.commons.runtime.OsFamily;\n\n/**\n * NonFocusableButton is a JButton which is non focusable, i.e. that cannot hold keyboard focus.\n *\n * @author Maxence Bernard\n */\npublic class NonFocusableButton extends JButton {\n\n    public NonFocusableButton() {\n        setLookAndFeelProperties();\n    }\n\n    public NonFocusableButton(Action a) {\n        super(a);\n        setLookAndFeelProperties();\n    }\n\n    public NonFocusableButton(Icon icon) {\n        super(icon);\n        setLookAndFeelProperties();\n    }\n\n    public NonFocusableButton(String text) {\n        super(text);\n        setLookAndFeelProperties();\n    }\n\n    public NonFocusableButton(String text, Icon icon) {\n        super(text, icon);\n        setLookAndFeelProperties();\n    }\n\n\n    private void setLookAndFeelProperties() {\n        // Fill the content area under the Windows L&F only, required for the borders to be painted.\n        // Note: filing the content area under Metal L&F looks like absolute crap.\n        setContentAreaFilled(OsFamily.WINDOWS.isCurrent() && \"Windows\".equals(UIManager.getLookAndFeel().getName()));\n    }\n\n\n    @Override\n    public boolean isFocusable() {\n        return false;\n    }\n\n    @Override\n    public void updateUI() {\n        super.updateUI();\n\n        // Update L&F properties \n        setLookAndFeelProperties();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/button/PopupButton.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.button;\r\n\r\nimport com.mucommander.desktop.DesktopManager;\r\nimport com.mucommander.ui.action.impl.MuteProxyAction;\r\nimport lombok.Getter;\r\nimport lombok.Setter;\r\n\r\nimport javax.swing.*;\r\nimport javax.swing.event.PopupMenuEvent;\r\nimport javax.swing.event.PopupMenuListener;\r\nimport java.awt.*;\r\nimport java.awt.event.ActionEvent;\r\nimport java.awt.event.MouseEvent;\r\nimport java.awt.event.MouseListener;\r\n\r\n/**\r\n * PopupButton is a compound component that combines a JButton with a JPopupMenu.\r\n *\r\n * <p>When the mouse is held down on the button, a popup menu is displayed below the button. When the button is clicked,\r\n * if a specific action was specified at creation time or using {@link #setAction(Action)}, this action is performed.\r\n * If not, a popup menu is displayed below the button.\r\n *\r\n * <p>This class is abstract. Derived classes must implement {@link #getPopupMenu()} to return a JPopupMenu instance\r\n * to be displayed.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic abstract class PopupButton extends NonFocusableButton {\r\n\r\n    /** Custom action performed when the button is clicked. If null, popup menu will be displayed when mouse is clicked */\r\n    private Action buttonClickedAction;\r\n\r\n    /** Timestamp when popup menu was closed */\r\n    private long popupMenuClosedTime;\r\n\r\n    /** Non-null while popup menu is visible */\r\n    private JPopupMenu popupMenu;\r\n\r\n    /** Location of the popup menu, relative to the button */\r\n    @Setter\r\n    @Getter\r\n    private int popupMenuLocation = BOTTOM;\r\n\r\n    /** Controls the number of milliseconds to hold down the mouse button on the button to display the popup menu */\r\n    private final static int POPUP_DELAY = 300;\r\n    \r\n    /** \r\n     * Box-orientation constant used to specify the buttom-left oriented side of a box.\r\n     */\r\n    private static final int BOTTOM_LEFT_ORIENTED = 5;\r\n\r\n\r\n    /**\r\n     * Creates a new PopupButton with no custom action for when this button is clicked.\r\n     * When this button is clicked, the popup menu as returned by {@link #getPopupMenu()} will be displayed.\r\n     */\r\n    protected PopupButton() {\r\n        this(null);\r\n    }\r\n\r\n    /**\r\n     * Creates a new PopupButton with a custom action to performed when this button is clicked.\r\n     *\r\n     * @param buttonClickedAction custom action to performed when this button is clicked\r\n     */\r\n    public PopupButton(Action buttonClickedAction) {\r\n        setAction(buttonClickedAction);\r\n\r\n        // Listen to mouse events on this button\r\n        addMouseListener(new PopupMenuHandler());\r\n    }\r\n\r\n\r\n    /**\r\n     * Sets the action to be performed when this button is clicked. If <code>null</code> is passed, a popup menu will\r\n     * be displayed when this button is clicked.\r\n     */\r\n    @Override\r\n    public void setAction(Action buttonClickedAction) {\r\n        if (buttonClickedAction == null) {\r\n            super.setAction(null);\r\n        } else {\r\n            // Pass a MuteProxyAction to JButton that does nothing when the action is performed.\r\n            // We need this to keep the use the Action's properties but handle action events ourself.\r\n            super.setAction(new MuteProxyAction(buttonClickedAction));\r\n        }\r\n\r\n        this.buttonClickedAction = buttonClickedAction;\r\n    }\r\n\r\n    /**\r\n     * Returns true if a popup menu is currently being displayed.\r\n     */\r\n    private boolean isPopupMenuVisible() {\r\n        return popupMenu!=null;\r\n    }\r\n\r\n    /**\r\n     * Displays the popup menu as returned by {@link #getPopupMenu()}.\r\n     */\r\n    public synchronized void popupMenu() {\r\n        this.popupMenu = getPopupMenu();\r\n\r\n        popupMenu.addPopupMenuListener(new PopupMenuListener() {\r\n            public void popupMenuWillBecomeVisible(PopupMenuEvent popupMenuEvent) {\r\n            }\r\n\r\n            public void popupMenuWillBecomeInvisible(PopupMenuEvent popupMenuEvent) {\r\n                popupMenuClosed();\r\n            }\r\n\r\n            public void popupMenuCanceled(PopupMenuEvent popupMenuEvent) {\r\n                popupMenuClosed();\r\n            }\r\n\r\n            private void popupMenuClosed() {\r\n                // Set popup menu reference to null once it has been made invisible so that it can be garbage collected,\r\n                // and remember the time at which the popup was closed, so that a mouse press on the button closes\r\n                // the popup menu and does not bring it back up.\r\n                popupMenuClosedTime = System.currentTimeMillis();\r\n                popupMenu = null;\r\n                setSelected(false);\r\n            }\r\n        });\r\n\r\n        // Popup up the menu underneath under this button. This has to be executed by the event thread, otherwise some\r\n        // weird repaint issue will arise under Windows at least (Note: this method can be executed by a thread other\r\n        // than the event thread).\r\n        SwingUtilities.invokeLater(() -> {\r\n                Dimension popupMenuSize = popupMenu.getPreferredSize();\r\n\r\n                popupMenu.show(PopupButton.this,\r\n                \t\tpopupMenuLocation==RIGHT?getWidth():popupMenuLocation==LEFT?-(int)popupMenuSize.getWidth():popupMenuLocation== BOTTOM_LEFT_ORIENTED ?getWidth()-(int)popupMenuSize.getWidth():0,\r\n                        popupMenuLocation==BOTTOM?getHeight():popupMenuLocation==TOP?-(int)popupMenuSize.getHeight():popupMenuLocation== BOTTOM_LEFT_ORIENTED ?getHeight():0\r\n                );\r\n\r\n        });\r\n\r\n        // Leave the button selected (shows that button has focus) while the popup menu is visible\r\n        setSelected(true);\r\n\r\n        // Note: focus MUST NOT be requested on the popup menu because:\r\n        // a/ it's not necessary, focus is automatically transfered to the popup menu\r\n        // b/ it creates a weird bug under Windows which prevents enter key from selecting any menu item\r\n    }\r\n\r\n\r\n    /**\r\n     * This method is invoked when the popup menu is about to be displayed. This method must return the JPopupMenu\r\n     * instance to display.\r\n     */\r\n    public abstract JPopupMenu getPopupMenu();\r\n\r\n\r\n    ///////////////////\r\n    // Inner classes //\r\n    ///////////////////\r\n\r\n    /**\r\n     * This inner class controls this button's behavior and actions when the mouse button is clicked or held down.\r\n     */\r\n    private class PopupMenuHandler implements MouseListener, Runnable {\r\n\r\n        /** Contains the time at which a mouse button was pressed, 0 when a mouse button is not currently being pressed */\r\n        private long pressedTime;\r\n\r\n        /** Returns true to indicate that a mouse event should currently be ignored because the popup menu\r\n         * is visible, or was closed recently (less than POPUP_DELAY ms ago) */\r\n        private boolean shouldIgnoreMouseEvent() {\r\n            return isPopupMenuVisible() || System.currentTimeMillis() - popupMenuClosedTime<POPUP_DELAY;\r\n        }\r\n\r\n        //////////////////////////////////\r\n        // MouseListener implementation //\r\n        //////////////////////////////////\r\n\r\n        public synchronized void mousePressed(MouseEvent mouseEvent) {\r\n            if (!isEnabled() || shouldIgnoreMouseEvent())    // Ignore event if button is disabled\r\n                return;\r\n\r\n            if (DesktopManager.isRightMouseButton(mouseEvent)) {\r\n            \tpopupMenu();\r\n            } else {\r\n            \tpressedTime = System.currentTimeMillis();\r\n\r\n            \t// Spawn a thread to check if mouse is still pressed in POPUP_DELAY ms. If that is the case, popup menu\r\n            \t// will be displayed.\r\n            \tnew Thread(this).start();\r\n            }\r\n        }\r\n\r\n        public synchronized void mouseClicked(MouseEvent mouseEvent) {\r\n            if (!isEnabled() || shouldIgnoreMouseEvent()) {    // Ignore event if button is disabled\r\n                return;\r\n            }\r\n\r\n            // Indicate to Thread spawn by mousePressed that mouse is not pressed anymore\r\n            pressedTime = 0;\r\n\r\n            if (buttonClickedAction != null) {   // Perform the action if there is one\r\n                buttonClickedAction.actionPerformed(new ActionEvent(PopupButton.this, ActionEvent.ACTION_PERFORMED, \"clicked\"));\r\n            } else {               // No action, popup menu\r\n                popupMenu();\r\n            }\r\n        }\r\n\r\n        public synchronized void mouseReleased(MouseEvent mouseEvent) {\r\n            // Indicate to Thread spawn by mousePressed that mouse is not pressed anymore\r\n            pressedTime = 0;\r\n        }\r\n\r\n        public synchronized void mouseExited(MouseEvent mouseEvent) {\r\n            // Indicate to Thread spawn by mousePressed that mouse is not pressed anymore\r\n            pressedTime = 0;\r\n        }\r\n\r\n        public void mouseEntered(MouseEvent mouseEvent) {\r\n        }\r\n\r\n\r\n        /////////////////////////////\r\n        // Runnable implementation //\r\n        /////////////////////////////\r\n\r\n        public void run() {\r\n                try {\r\n                    Thread.sleep(POPUP_DELAY);\r\n                } catch(InterruptedException ignore) {}\r\n\r\n                synchronized(this) {\r\n                    // Popup menu if a popup menu is not already being displayed and if mouse is still pressed\r\n                    if (!isPopupMenuVisible() && pressedTime != 0 && System.currentTimeMillis()-pressedTime >= POPUP_DELAY) {\r\n                        popupMenu();\r\n                    }\r\n                }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/button/RolloverButtonAdapter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.button;\n\nimport javax.swing.*;\nimport java.awt.event.MouseEvent;\nimport java.awt.event.MouseListener;\n\n/**\n * This class allows to add a rollover effect to a <code>JButton</code>. Rollover-enabled buttons have no borders\n * by default. It is only when the mouse cursor is over the button that the button's borders get painted.\n * This rollover effect gives the user a visual indication that the button can be pressed (in other words, that the\n * button is indeed a button), while not cluttering the interface with button borders.\n * Such buttons are particularly effective for toolbars, where a large number of buttons are usually present.\n *\n * <p>\n * To 'rollover-enable' a button, the {@link #decorateButton(javax.swing.JButton)} method must first be called to\n * set decoration properties. Then, the button must register an instance of <code>RolloverButtonAdapter</code> as a\n * mouse listener. Note that a single <code>RolloverButtonAdapter</code> instance can be registered with several buttons.  \n *\n * @author Maxence Bernard\n */\npublic class RolloverButtonAdapter implements MouseListener {\n\n    private static final RolloverButtonAdapter INSTANCE = new RolloverButtonAdapter();\n\n    /**\n     * Creates a new RolloverButtonAdapter.\n     */\n    public RolloverButtonAdapter() {\n    }\n\n    /**\n     * Sets the decoration properties required to give the specified button a 'rollover' look and feel.\n     *\n     * @param button the button to 'rollover-enable'\n     */\n    public static void decorateButton(JButton button) {\n        // Set button decorations and rollover behavior\n        button.setRolloverEnabled(true);\n        button.setFocusPainted(false);\n        button.setBorderPainted(false);\n        button.setContentAreaFilled(false);\n//        if (OsVersion.MAC_OS_X_10_5.isCurrentOrHigher()) {\n//            button.putClientProperty(\"JButton.buttonType\", \"textured\");\n//        }\n        button.setRolloverEnabled(true);\n        button.addMouseListener(INSTANCE);\n    }\n\n\n    @Override\n    public void mouseEntered(MouseEvent e) {\n        ((JButton)e.getSource()).setBorderPainted(true);\n    }\n\n    @Override\n    public void mouseExited(MouseEvent e) {\n        ((JButton)e.getSource()).setBorderPainted(false);\n    }\n\n    @Override\n    public void mouseClicked(MouseEvent e) {\n    }\n\n    @Override\n    public void mouseReleased(MouseEvent e) {\n    }\n\n    @Override\n    public void mousePressed(MouseEvent e) {\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/button/ToolbarMoreButton.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.button;\n\nimport java.awt.BorderLayout;\nimport java.awt.Component;\nimport java.awt.Insets;\nimport java.awt.Rectangle;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.awt.event.ComponentAdapter;\nimport java.awt.event.ComponentEvent;\nimport java.awt.event.MouseAdapter;\nimport java.awt.event.MouseEvent;\n\nimport javax.swing.AbstractButton;\nimport javax.swing.JPanel;\nimport javax.swing.JPopupMenu;\nimport javax.swing.JSeparator;\nimport javax.swing.JToggleButton;\nimport javax.swing.JToolBar;\nimport javax.swing.event.PopupMenuEvent;\nimport javax.swing.event.PopupMenuListener;\n\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.commons.runtime.OsVersion;\nimport com.mucommander.ui.icon.IconManager;\n\n/*\n * MySwing: Advanced Swing Utilites\n * Copyright (C) 2005  Santhosh Kumar T\n * <p/>\n * This library is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 2.1 of the License, or (at your option) any later version.\n * <p/>\n * This library is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n */\n\n/**\n * Provides a way for toolbar buttons to be displayed when the toolbar itself doesn't have enough space for all buttons.\n * The buttons that are cropped are displayed in a secondary toolbar, using a vertical layout.\n * <p>\n * To use this Feature replace:\n * <code>\n * frame.getContentPane().add(toolbar, BorderLayout.NORTH);\n * </code>\n * with\n * <code>\n * frame.getContentPane().add(MoreButton.wrapToolBar(toolBar), BorderLayout.NORTH);\n * </code>\n * <p>\n * <p>\n * This class is based on the code of Santhosh Kumar T, see\n * <a href=\"http://www.jroller.com/santhosh/entry/jtoolbar_with_more_button\">this link</a> for more information.\n *\n * @author Santhosh Kumar T, Leo Welsch\n */\npublic class ToolbarMoreButton extends JToggleButton implements ActionListener {\n\n    private static JToolBar moreToolbar;\n    JToolBar toolbar;\n\n    private ToolbarMoreButton(final JToolBar toolbar) {\n        super(IconManager.getIcon(IconManager.IconSet.COMMON, \"more.png\"));\n        this.toolbar = toolbar;\n        addActionListener(this);\n        setFocusPainted(false);\n\n        setMargin(new Insets(0, 0, 0, 0));\n        setContentAreaFilled(false);\n        setBorderPainted(false);\n        // Use new JButton decorations introduced in Mac OS X 10.5 (Leopard)\n        if (OsFamily.MAC_OS_X.isCurrent() && OsVersion.MAC_OS_X_10_5.isCurrentOrHigher()) {\n            putClientProperty(\"JComponent.sizeVariant\", \"small\");\n            putClientProperty(\"JButton.buttonType\", \"textured\");\n        }\n\n        // paint border only when necessary\n        addMouseListener(new MouseAdapter() {\n\n            @Override\n            public void mouseExited(MouseEvent e) {\n                setBorderPainted(false);\n            }\n\n            @Override\n            public void mouseEntered(MouseEvent e) {\n                setBorderPainted(true);\n            }\n        });\n\n        // hide & seek\n        toolbar.addComponentListener(new ComponentAdapter() {\n\n            @Override\n            public void componentResized(ComponentEvent e) {\n                int nbToolbarComponents = toolbar.getComponentCount();\n\n                final boolean aFlag = nbToolbarComponents > 0 && !isVisible(toolbar.getComponent(nbToolbarComponents - 1), null);\n                setVisible(aFlag);\n                moreToolbar.setVisible(aFlag);\n            }\n        });\n    }\n\n    // check visibility\n    // partially visible is treated as not visible\n    private boolean isVisible(Component comp, Rectangle rect) {\n        if (rect == null) {\n            rect = toolbar.getVisibleRect();\n        }\n        return comp.getLocation().x + comp.getWidth() <= rect.getWidth();\n    }\n\n    public void actionPerformed(ActionEvent e) {\n        Component[] comp = toolbar.getComponents();\n        Rectangle visibleRect = toolbar.getVisibleRect();\n        for (int i = 0; i < comp.length; i++) {\n            if (!isVisible(comp[i], visibleRect)) {\n                JPopupMenu popup = new JPopupMenu();\n                for (; i < comp.length; i++) {\n                    if (comp[i] instanceof AbstractButton button) {\n                        if (button.getAction() != null) {\n                            popup.add(button.getAction());\n                        }\n                    } else if (comp[i] instanceof JSeparator) {\n                        popup.addSeparator();\n                    }\n                }\n\n                // on popup close make more-button unselected\n                popup.addPopupMenuListener(new PopupMenuListener() {\n\n                    public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {\n                        setSelected(false);\n                    }\n\n                    public void popupMenuCanceled(PopupMenuEvent e) {\n                    }\n\n                    public void popupMenuWillBecomeVisible(PopupMenuEvent e) {\n                    }\n                });\n                popup.show(this, 0, getHeight());\n            }\n        }\n    }\n\n    public static JPanel wrapToolBar(JToolBar toolbar) {\n        moreToolbar = new JToolBar();\n        moreToolbar.setRollover(true);\n        moreToolbar.setFloatable(false);\n        moreToolbar.add(new ToolbarMoreButton(toolbar));\n        moreToolbar.setBorderPainted(false);\n\n        JPanel panel = new JPanel(new BorderLayout());\n        panel.add(toolbar, BorderLayout.CENTER);\n        panel.add(moreToolbar, BorderLayout.EAST);\n\n        return panel;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/button/package.html",
    "content": "<body>\n  Provides various JButton implementations.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/chooser/ColorChangeEvent.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.chooser;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n/**\n * @author Maxence Bernard\n */\npublic class ColorChangeEvent {\n\n    private final JComponent source;\n    private final Color color;\n\n\n    public ColorChangeEvent(JComponent source, Color color) {\n        this.source = source;\n        this.color = color;\n    }\n\n    public JComponent getSource() {\n        return source;\n    }\n\n    public Color getColor() {\n        return color;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/chooser/ColorChangeListener.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.chooser;\n\n/**\n * @author Maxence Bernard\n */\npublic interface ColorChangeListener {\n\n    void colorChanged(ColorChangeEvent event);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/chooser/ColorChooser.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.chooser;\n\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.FocusDialog;\nimport com.mucommander.ui.layout.XBoxPanel;\nimport com.mucommander.ui.layout.YBoxPanel;\n\nimport javax.swing.*;\nimport javax.swing.event.ChangeEvent;\nimport javax.swing.event.ChangeListener;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\n\n/**\n * Component used to let users pick a color.\n * <p>\n * The main reason for this component's existence is Swing's <code>JColorChooser</code> does not offer\n * alpha transparency edition, and that its localisation is incomplete. This is a wrapper that fixes both\n * of these shortcomings.\n * <p>\n * This component can be used either as a regular widget and be added to a <code>container</code> or as\n * a dialog box through the {@link #createDialog(Frame,ColorChooser) createDialog} method.\n *\n * @author Nicolas Rinaudo, Maxence Bernard\n */\npublic class ColorChooser extends YBoxPanel implements ChangeListener {\n\n    // - UI components ----------------------------------------------------------\n    // --------------------------------------------------------------------------\n    /** Component that displays a preview of the current color */\n    private JComponent previewComponent;\n    /** Color chooser. */\n    private JColorChooser  chooser;\n    /** Alpha transparency chooser. */\n    private IntegerChooser alpha;\n\n\n    // - Color editing ----------------------------------------------------------\n    // --------------------------------------------------------------------------\n    /** Currently selected color. */\n    private Color          currentColor;\n    /** Color on which the dialog was initialized. */\n    private final Color initialColor;\n    /** Property to change in the preview component when the current color changes */\n    private String previewColorPropertyName;\n\n\n    // - Localisation -----------------------------------------------------------\n    // --------------------------------------------------------------------------\n    static {\n        String buffer;\n\n        // Tab labels.\n        UIManager.put(\"ColorChooser.rgbNameText\",        Translator.get(\"color_chooser.rgb\"));\n        UIManager.put(\"ColorChooser.hsbNameText\",        Translator.get(\"color_chooser.hsb\"));\n        UIManager.put(\"ColorChooser.swatchesNameText\",   Translator.get(\"color_chooser.swatches\"));\n\n        // Red color name.\n        UIManager.put(\"ColorChooser.rgbRedText\",         buffer = Translator.get(\"color_chooser.red\"));\n        UIManager.put(\"ColorChooser.hsbRedText\",         buffer);\n\n        // Green color name.\n        UIManager.put(\"ColorChooser.rgbGreenText\",       buffer = Translator.get(\"color_chooser.green\"));\n        UIManager.put(\"ColorChooser.hsbGreenText\",       buffer);\n\n        // Blue color name.\n        UIManager.put(\"ColorChooser.rgbBlueText\",        buffer = Translator.get(\"color_chooser.blue\"));\n        UIManager.put(\"ColorChooser.hsbBlueText\",        buffer);\n\n        // HSB tab specific strings.\n        UIManager.put(\"ColorChooser.hsbHueText\",         Translator.get(\"color_chooser.hue\"));\n        UIManager.put(\"ColorChooser.hsbSaturationText\",  Translator.get(\"color_chooser.saturation\"));\n        UIManager.put(\"ColorChooser.hsbBrightnessText\",  Translator.get(\"color_chooser.brightness\"));\n\n        // Swatches tab specific strings.\n        UIManager.put(\"ColorChooser.swatchesRecentText\", Translator.get(\"color_chooser.recent\"));\n    }\n\n    public ColorChooser() {\n        this(Color.WHITE, null, null);\n    }\n\n    public ColorChooser(Color initialColor) {\n        this(initialColor, null, null);\n    }\n\n    public ColorChooser(Color initialColor, JComponent previewComponent, String previewColorPropertyName) {\n        this.currentColor  = initialColor;\n        this.initialColor = initialColor;\n\n        // Initializes the UI.\n        add(createChooserPanel());\n        add(createTransparencyPanel());\n\n        alpha.setValue(initialColor.getAlpha());\n        chooser.setColor(initialColor);\n\n        if(previewComponent!=null && previewColorPropertyName!=null) {\n            this.previewComponent = previewComponent;\n            this.previewColorPropertyName = previewColorPropertyName;\n            add(createPreviewPanel(previewComponent));\n\n            updatePreview();\n        }\n    }\n\n    /**\n     * Creates a dialog containing the specified color chooser.\n     * @param  parent  component on which to center the dialog.\n     * @param  chooser chooser to use within the dialog.\n     * @return         a dialog containing the specified chooser.\n     */\n    public static FocusDialog createDialog(Dialog parent, ColorChooser chooser) {\n        return new ChooserDialog(parent, chooser);\n    }\n\n    /**\n     * Creates a dialog containing the specified color chooser.\n     * @param  parent  component on which to center the dialog.\n     * @param  chooser chooser to use within the dialog.\n     * @return         a dialog containing the specified chooser.\n     */\n    public static FocusDialog createDialog(Frame parent, ColorChooser chooser) {\n        return new ChooserDialog(parent, chooser);\n    }\n\n    /**\n     * Creates the preview panel.\n     */\n    private JPanel createPreviewPanel(JComponent previewComponent) {\n        // Sets the label's preferred size (same width as the chooser, twice the normal label height).\n        Dimension size = previewComponent.getPreferredSize();\n        size.width = chooser.getPreferredSize().width;\n        size.height *= 2;\n        previewComponent.setPreferredSize(size);\n\n        // Sets the preview label appearance.\n        previewComponent.setOpaque(true);\n\n        // Creates the preview panel.\n        JPanel panel = new JPanel();\n        panel.add(previewComponent);\n        panel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"preview\")));\n\n        return panel;\n    }\n\n    /**\n     * Creates the color chooser panel.\n     */\n    private JColorChooser createChooserPanel() {\n        // Creates the color chooser.\n        chooser = new JColorChooser();\n        chooser.setPreviewPanel(new JPanel());\n        chooser.getSelectionModel().addChangeListener(this);\n\n        return chooser;\n    }\n\n    /**\n     * Creates the transparency selection panel.\n     */\n    private JPanel createTransparencyPanel() {\n        // Creates and initializes the transparency selector.\n        alpha = new IntegerChooser(0, 255, 255);\n        alpha.setMajorTickSpacing(85);\n        alpha.setMinorTickSpacing(17);\n        alpha.setPaintTicks(true);\n        alpha.setPaintLabels(true);\n        alpha.setBorder(BorderFactory.createTitledBorder(Translator.get(\"color_chooser.alpha\")));\n        alpha.addChangeListener(this);\n\n        return alpha;\n    }\n\n\n    /**\n     * Returns the color selected by the user.\n     * @return the color selected by the user.\n     */\n    public Color getColor() {\n        return currentColor;\n    }\n\n    /**\n     * Resets the dialog to the initial color.\n     */\n    public void reset() {\n        reset(true);\n    }\n\n    /**\n     * Resets the dialog to the initial color.\n     * @param updateUI if set to <code>false</code>, the component's UI won't be updated.\n     */\n    private void reset(boolean updateUI) {\n        currentColor = initialColor;\n\n        /// Propagates the color to the choosers.\n        if (updateUI) {\n            alpha.setValue(currentColor.getAlpha());\n            chooser.setColor(currentColor);\n\n            currentColor = initialColor;    // Need to set it again as the value is changed by stateChanged()\n            updatePreview();\n        }\n    }\n\n    /**\n     * Update the preview panel to the current color.\n     */\n    private void updatePreview() {\n        if (previewComponent != null) {\n            previewComponent.putClientProperty(previewColorPropertyName, currentColor);\n        }\n    }\n\n    /**\n     * This method is public as an implementation side effect and should not be called directly.\n     */\n    public void stateChanged(ChangeEvent e) {\n        // Creates the new current color.\n        Color buffer = chooser.getColor();\n        currentColor = new Color(buffer.getRed(), buffer.getGreen(), buffer.getBlue(), alpha.getValue());\n\n        updatePreview();\n    }\n\n\n    /**\n     * Component used to present a <code>ColorChooser</code> from within a modal dialog.\n     * @author Nicolas Rinaudo\n     */\n    private static class ChooserDialog extends FocusDialog implements ActionListener {\n        /** Resets the color to the original one. */\n        private JButton resetButton;\n        /** Closes the dialog without applying the color selection. */\n        private JButton cancelButton;\n        /** Color chooser contained by this dialog. */\n        private ColorChooser chooser;\n\n        /**\n         * Creates a new dialog containing the specified color chooser.\n         */\n        ChooserDialog(Frame parent, ColorChooser chooser) {\n            super(parent, i18n(\"color_chooser.title\"), parent);\n            initUI(chooser);\n        }\n\n        /**\n         * Creates a new dialog containing the specified color chooser.\n         */\n        ChooserDialog(Dialog parent, ColorChooser chooser) {\n            super(parent, i18n(\"color_chooser.title\"), parent);\n            initUI(chooser);\n        }\n\n        /**\n         * Initializes the dialog's UI.\n         */\n        private void initUI(ColorChooser chooser) {\n            this.chooser = chooser;\n\n            // Initializes the dialog and its content pane.\n            Container contentPane = getContentPane();\n            contentPane.setLayout(new BorderLayout());\n\n            // Creates the content of the dialog.\n            contentPane.add(chooser, BorderLayout.CENTER);\n            contentPane.add(createButtonsPanel(), BorderLayout.SOUTH);\n        }\n\n        /**\n         * Creates the panel that contains the dialog's buttons.\n         */\n        private JPanel createButtonsPanel() {\n            // Creates the panel and buttons.\n            XBoxPanel buttonsPanel = new XBoxPanel();\n            buttonsPanel.add(resetButton = new JButton(i18n(\"reset\")));\n            buttonsPanel.addSpace(20);\n            JButton okButton = new JButton(i18n(\"ok\"));\n            buttonsPanel.add(okButton);\n            buttonsPanel.add(cancelButton = new JButton(i18n(\"cancel\")));\n\n            // Tracks events.\n            resetButton.addActionListener(this);\n            okButton.addActionListener(this);\n            cancelButton.addActionListener(this);\n\n            // OK will be selected when the user presses enter.\n            getRootPane().setDefaultButton(okButton);\n\n            // Aligns the buttons to the right of the panel.\n            JPanel panel = new JPanel();\n            panel.setLayout(new FlowLayout(FlowLayout.RIGHT));\n            panel.add(buttonsPanel);\n\n            return panel;\n        }\n\n        /**\n         * In case the dialog was cancelled, resets the color before closing it.\n         */\n        @Override\n        public void cancel() {\n            chooser.reset(false);\n            super.cancel();\n        }\n\n        /**\n         * This method is public as an implementation side effect and should not be called directly.\n         */\n        public void actionPerformed(ActionEvent e) {\n            // Resets the current color.\n            if (e.getSource() == resetButton) {\n                chooser.reset(true);\n            }// Closes the dialog, applying modifications if necessary.\n            else {\n                if (e.getSource() == cancelButton) {\n                    chooser.reset(false);\n                }\n                dispose();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/chooser/ColorPicker.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.chooser;\n\nimport com.mucommander.ui.icon.IconManager;\nimport lombok.Getter;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.AWTEventListener;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.awt.event.MouseEvent;\nimport java.awt.image.BufferedImage;\nimport java.util.WeakHashMap;\n\n/**\n * @author Maxence Bernard\n */\npublic class ColorPicker extends JButton implements ActionListener, AWTEventListener {\n\n    private Robot robot;\n    @Getter\n    private boolean isActive;\n\n    private final WeakHashMap<ColorChangeListener, ?> listeners = new WeakHashMap<>();\n\n    /**\n     * True if this component is supported (java.awt.Robot can be used)\n     */\n    private static boolean isSupported;\n\n\n    static {\n        try {\n            new Robot();\n            isSupported = true;\n        } catch (Exception e) {\n            // java.awt.Robot constructor throws an AWTException \"if the platform configuration does not allow low-level input control.\"\n            // In this case, isSupported will be false\n        }\n    }\n\n    public ColorPicker() {\n        super(IconManager.getIcon(IconManager.IconSet.COMMON, \"picker.png\"));\n        addActionListener(this);\n    }\n\n\n    public static boolean isSupported() {\n        return isSupported;\n    }\n\n\n    public void setActive(boolean active) {\n        if (active == isActive) {\n            return;\n        }\n\n        final Toolkit toolkit = Toolkit.getDefaultToolkit();\n\n        if (active) {\n            if (!isVisible())\n                return;\n\n            try {\n                // create a java.awt.Robot operating on the screen device that contains the window this component is in.\n                // Not sure what happens if the window spawns across 2 screens...\n                robot = new Robot(getTopLevelAncestor().getGraphicsConfiguration().getDevice());\n            } catch (Exception e) {\n                // If Robot is not available, ColorPicker will simply be ineffective, clicking it won't do anything\n                return;\n            }\n\n            // Change the cursor to the 'eye dropper' icon filled with white\n            setPickerCursor(Color.WHITE);\n\n            // These are invoked after all pending events are processed\n            SwingUtilities.invokeLater(() -> {\n                // Listen to all mouse events on the window that contains this button\n                toolkit.addAWTEventListener(ColorPicker.this, AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK);\n\n                // Leave this button selected until a color is picked or this button is pressed again (to cancel)\n                setSelected(true);\n            });\n        } else {\n            // Stop listening to mouse events\n            toolkit.removeAWTEventListener(this);\n\n            // Restore default cursor\n            setCustomCursor(Cursor.getDefaultCursor());\n\n            // Make the button unselected\n            setSelected(false);\n        }\n\n        this.isActive = active;\n    }\n\n\n    public void addColorChangeListener(ColorChangeListener listener) {\n        listeners.put(listener, null);\n    }\n\n    public void removeColorChangeListener(ColorChangeListener listener) {\n        listeners.remove(listener);\n    }\n\n\n    private void setPickerCursor(Color fillColor) {\n        ImageIcon cursorIcon = (ImageIcon) getIcon();\n        int iconWidth = cursorIcon.getIconWidth();\n        int iconHeight = cursorIcon.getIconHeight();\n        int colorRGB = fillColor.getRGB();\n\n        // Retrieve the cursor icon fill mask as an alpha-enabled BufferedImage\n        BufferedImage iconMaskBi = new BufferedImage(iconWidth, iconHeight, BufferedImage.TYPE_INT_ARGB);\n        Graphics g = iconMaskBi.getGraphics();\n        g.drawImage(IconManager.getIcon(IconManager.IconSet.COMMON, \"picker_mask.png\").getImage(), 0, 0, null);\n\n        // Replace solid (non-transparent) pixels with specified fill color\n        for (int y = 0; y < iconHeight; y++) {\n            for (int x = 0; x < iconWidth; x++) {\n                int rgba = iconMaskBi.getRGB(x, y);\n                if ((rgba >> 24) != 0)\n                    iconMaskBi.setRGB(x, y, colorRGB);\n            }\n        }\n\n        // Retrieve the cursor icon as an alpha-enabled BufferedImage and paint the fill mask on it\n        BufferedImage iconBi = new BufferedImage(cursorIcon.getIconWidth(), cursorIcon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);\n        g = iconBi.getGraphics();\n        g.drawImage(cursorIcon.getImage(), 0, 0, null);\n        g.drawImage(iconMaskBi, 0, 0, null);\n\n        setCustomCursor(Toolkit.getDefaultToolkit().createCustomCursor(iconBi, new Point(0, 15), getClass().getName()));\n    }\n\n    private void setCustomCursor(Cursor cursor) {\n        getTopLevelAncestor().setCursor(cursor);\n    }\n\n    private void fireColorPicked(Color color) {\n        // Iterate on all listeners\n        for (ColorChangeListener listener : listeners.keySet())\n            listener.colorChanged(new ColorChangeEvent(this, color));\n    }\n\n\n    @Override\n    public void actionPerformed(ActionEvent actionEvent) {\n        setActive(!isActive);\n    }\n\n\n    @Override\n    public void eventDispatched(AWTEvent awtEvent) {\n        if (awtEvent instanceof MouseEvent mouseEvent) {\n\n            Point mousePoint = mouseEvent.getPoint();\n            Component source = (Component) mouseEvent.getSource();\n\n            // Convert the mouse X/Y into screen coordinates\n            SwingUtilities.convertPointToScreen(mousePoint, source);\n\n            int x = (int) mousePoint.getX();\n            int y = (int) mousePoint.getY();\n\n            // Retrieve the color of the pixel the mouse is currently over\n            Color color = robot.getPixelColor(x, y);\n\n            int button = mouseEvent.getButton();\n            if (button != MouseEvent.NOBUTTON) {\n                // If left button was clicked (not released)\n                if (button == MouseEvent.BUTTON1 && (mouseEvent.getModifiersEx() & MouseEvent.MOUSE_CLICKED) != 0) {\n                    // If this color picker was clicked, cancel the color picking without firing an event\n                    if (source != this)\n                        fireColorPicked(color);\n\n                    // Consume the event so that it doesn't get caught by a clicked component\n                    mouseEvent.consume();\n\n                    // We're done\n                    setActive(false);\n                }\n\n                // If any other button was clicked or if this is not a MOUSE_CLICKED event, do nothing\n                return;\n            }\n\n            setPickerCursor(color);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/chooser/FontChooser.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.chooser;\n\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.combobox.TcComboBox;\nimport com.mucommander.ui.layout.YBoxPanel;\n\nimport javax.swing.*;\nimport javax.swing.event.ChangeEvent;\nimport javax.swing.event.ChangeListener;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.util.WeakHashMap;\n\n/**\n * Component used to let users choose a font.\n * @author Nicolas Rinaudo\n */\npublic class FontChooser extends YBoxPanel implements ActionListener {\n    /** Legal font sizes. */\n    private final static int[] FONT_SIZES = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 22, 24, 28, 32, 36, 40};\n\n    /** Lists all the available font families. */\n    private JComboBox<String> families;\n    /** Lists all the legal font sizes. */\n    private JComboBox<String> sizes;\n    /** Whether, or not the font should be italic. */\n    private JCheckBox   italic;\n    /** Whether, or not the font should be bold. */\n    private JCheckBox   bold;\n    /** Used to display a preview of the current font. */\n    private JLabel      preview;\n    /** Currently selected font. */\n    private Font        font;\n    /** List of all registered state change listeners. */\n    private final WeakHashMap<ChangeListener, ?> listeners = new WeakHashMap<>();\n\n\n    /**\n     * Creates a new FontChooser with the specified selection.\n     * @param selection font that should be pre-selected.\n     */\n    public FontChooser(Font selection) {\n        super();\n        initUI(selection);\n    }\n\n    /**\n     * Initializes the font chooser's UI.\n     * @param selection default font selection (ignored if <code>null</code>).\n     */\n    private void initUI(Font selection) {\n        setAlignmentX(LEFT_ALIGNMENT);\n\n        // Font families.\n        families = new TcComboBox<>();\n        String[] familyNames = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();\n        int selectedIndex = 0;\n        for (int i = 0; i < familyNames.length; i++) {\n            families.addItem(familyNames[i]);\n            if (selection.getFamily().equalsIgnoreCase(familyNames[i])) {\n                selectedIndex = i;\n            }\n        }\n        families.setSelectedIndex(selectedIndex);\n        families.addActionListener(this);\n\n        // Adds the font families to the component.\n        JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        panel.add(families);\n        add(panel);\n\n        // Font sizes.\n        sizes = new TcComboBox<>();\n        for (int fontSize : FONT_SIZES) {\n            sizes.addItem(Integer.toString(fontSize));\n        }\n        sizes.setSelectedItem(Integer.toString(selection.getSize()));\n        sizes.addActionListener(this);\n\n        // Font styles.\n        bold = new JCheckBox(Translator.get(\"font_chooser.font_bold\"));\n        italic = new JCheckBox(Translator.get(\"font_chooser.font_italic\"));\n        bold.setSelected(selection.isBold());\n        bold.addActionListener(this);\n        italic.setSelected(selection.isItalic());\n        italic.addActionListener(this);\n\n        // Adds the font size and styles to the component.\n        panel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        panel.add(new JLabel(Translator.get(\"font_chooser.font_size\")+\": \"));\n        panel.add(sizes);\n        panel.add(Box.createRigidArea(new Dimension(10, 0)));\n        panel.add(bold);\n        panel.add(italic);\n        add(panel);\n\n        // Initializes the current font.\n        font = selection;\n\n        // Creates the preview panel.\n        panel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        panel.add(new JLabel(Translator.get(\"preview\")+\": \"));\n        preview = new JLabel(\"aBcDeFgHiJkLmNoPqRsTuVwXyZ\");\n        updatePreview();\n        panel.add(preview);\n        add(panel);\n    }\n\n\n\n    // - Content access ---------------------------------------------------------\n    // --------------------------------------------------------------------------\n    /**\n     * Creates a font from the current selection.\n     * @return the font described by the current selection.\n     */\n    private Font createFont() {\n        int style = (bold.isSelected() ? Font.BOLD : 0) | (italic.isSelected() ? Font.ITALIC : 0);\n        int size = Integer.parseInt((String)sizes.getSelectedItem());\n        return new Font((String)families.getSelectedItem(), style, size);\n    }\n\n    /**\n     * Returns the font currently selected in the chooser.\n     * @return the font currently selected in the chooser.\n     */\n    public Font getCurrentFont() {\n        return font;\n    }\n\n\n\n    // - Listener code ----------------------------------------------------------\n    // --------------------------------------------------------------------------\n    /**\n     * Updates the preview panel.\n     */\n    private void updatePreview() {\n        preview.setFont(font);\n        preview.repaint();\n    }\n\n    /**\n     * Called when the font description has been changed.\n     */\n    public void actionPerformed(ActionEvent e) {\n        font = createFont();\n        updatePreview();\n\n        // Notifies listeners.\n        ChangeEvent event = new ChangeEvent(this);\n        for (ChangeListener listener : listeners.keySet()) {\n            listener.stateChanged(event);\n        }\n    }\n\n\n\n    // - State changing code ----------------------------------------------------\n    // --------------------------------------------------------------------------\n    /**\n     * Registers the specified object as a change listener.\n     */\n    public void addChangeListener(ChangeListener listener) {\n        listeners.put(listener, null);\n    }\n\n    /**\n     * Un-registers the specified object as a change listener.\n     */\n    public void removeChangeListener(ChangeListener listener) {\n        listeners.remove(listener);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/chooser/IntegerChooser.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.chooser;\n\nimport javax.swing.*;\nimport javax.swing.event.ChangeEvent;\nimport javax.swing.event.ChangeListener;\nimport java.awt.*;\nimport java.util.WeakHashMap;\n\n/**\n * Component meant to let users choose an integer value from a specific range.\n * <p>\n * An IntegerChooser is an compound-component created by associating a JSpinner and a JSlider.\n * <p>\n * In order to track the chooser's state, listeners can be registered through {@link #addChangeListener(ChangeListener)}.\n *\n * @author Nicolas Rinaudo\n */\npublic class IntegerChooser extends JPanel implements ChangeListener {\n    /** List of all registered state change listeners. */\n    private final WeakHashMap<ChangeListener, ?> listeners;\n\n\n    /** Integer slider. */\n    private final JSlider  slider;\n    /** Integer spinner. */\n    private final JSpinner spinner;\n\n\n    /**\n     * Creates a new integer chooser.\n     * @param min          chooser's minimum value.\n     * @param max          chooser's maximum value.\n     * @param initialValue chooser's initial value.\n     */\n    public IntegerChooser(int min, int max, int initialValue) {\n        super();\n\n        // Initializes the listeners.\n        listeners = new WeakHashMap<>();\n\n        // Creates the components.\n        slider  = new JSlider(JSlider.HORIZONTAL, min, max, initialValue);\n        spinner = new JSpinner(new SpinnerNumberModel(initialValue, min, max, 1));\n\n        // Registers listeners.\n        slider.addChangeListener(this);\n        spinner.addChangeListener(this);\n\n        // Creates the panel.\n        this.add(slider);\n        this.add(spinner, BorderLayout.EAST);\n    }\n\n\n\n    // - Slider modifications ---------------------------------------------------\n    // --------------------------------------------------------------------------\n    /**\n     * This method sets the major tick spacing.\n     * The number that is passed-in represents the distance, measured in values, between each major tick mark. If you have a\n     * slider with a range from 0 to 50 and the major tick spacing is set to 10, you will get major ticks next to the following\n     * values: 0, 10, 20, 30, 40, 50.\n     * @param spacing major tick spacing\n     */\n    public void setMajorTickSpacing(int spacing) {\n        slider.setMajorTickSpacing(spacing);\n    }\n\n    /**\n     * This method sets the minor tick spacing.\n     * The number that is passed-in represents the distance, measured in values, between each minor tick mark. If you have a\n     * slider with a range from 0 to 50 and the minor tick spacing is set to 10, you will get minor ticks next to the following\n     * values: 0, 10, 20, 30, 40, 50.\n     * @param spacing minor ticks spacing\n     */\n    public void setMinorTickSpacing(int spacing) {\n        slider.setMinorTickSpacing(spacing);\n    }\n\n    /**\n     * Determines whether tick marks are painted on the slider\n     * @param b true if tick marks are painted on the slider\n     */\n    public void setPaintTicks(boolean b) {\n        slider.setPaintTicks(b);\n    }\n\n    /**\n     * Determines whether labels are painted on the slider\n     * @param b true if labels marks are painted on the slider\n     */\n    public void setPaintLabels(boolean b) {\n        slider.setPaintLabels(b);\n    }\n\n    /**\n     * Determines whether labels are painted on the slider.\n     */\n    public void setValue(int value) {\n        slider.setValue(value);\n        spinner.setValue(value);\n    }\n\n\n\n    // - Value retrieval --------------------------------------------------------\n    // --------------------------------------------------------------------------\n    /**\n     * Returns the chooser's current value.\n     * @return the chooser's current value.\n     */\n    public int getValue() {return slider.getValue();}\n\n\n\n\n    /**\n     * Registers the specified object as a change listener.\n     */\n    public void addChangeListener(ChangeListener listener) {\n        listeners.put(listener, null);\n    }\n\n    /**\n     * Un-registers the specified object as a change listener.\n     */\n    public void removeChangeListener(ChangeListener listener) {\n        listeners.remove(listener);\n    }\n\n    /**\n     * This method is public as an implementation side effect and should never be called directly.\n     */\n    public void stateChanged(ChangeEvent e) {\n        // Updates the chooser's value.\n        if (e.getSource() == spinner) {\n            slider.setValue((Integer) spinner.getValue());\n        } else if(e.getSource() == slider) {\n            spinner.setValue(slider.getValue());\n        }\n\n        // Notifies listeners.\n        ChangeEvent event = new ChangeEvent(this);\n        for (ChangeListener listener : listeners.keySet()) {\n            listener.stateChanged(event);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/chooser/KeyboardShortcutChooser.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.chooser;\n\nimport com.mucommander.ui.combobox.ComboBoxListener;\nimport com.mucommander.ui.combobox.SaneComboBox;\nimport com.mucommander.ui.text.KeyStrokeUtils;\nimport com.mucommander.utils.text.Translator;\nimport org.intellij.lang.annotations.MagicConstant;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javax.swing.*;\nimport javax.swing.text.AttributeSet;\nimport javax.swing.text.BadLocationException;\nimport javax.swing.text.PlainDocument;\nimport java.awt.*;\nimport java.awt.event.*;\n\n/**\n * @author Maxence Bernard\n */\npublic class KeyboardShortcutChooser extends JPanel implements ItemListener, ComboBoxListener, FocusListener, KeyListener {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(KeyboardShortcutChooser.class);\n\t\n    private final JTextField textField;\n    private final JCheckBox[] modifierCheckBoxes;\n    private final SaneComboBox<KeyChoice> keyComboBox;\n\n    private KeyStroke currentKeyStroke;\n\n    private boolean updatingTextField;\n    private boolean updatingComboBox;\n    private boolean updatingCheckBoxes;\n\n    private final String noneString = \"<\"+Translator.get(\"none\")+\">\";\n\n    private final static int[] KEY_CHOICES = new int[] {\n        KeyEvent.VK_ESCAPE, KeyEvent.VK_TAB, KeyEvent.VK_DELETE, KeyEvent.VK_BACK_SPACE, KeyEvent.VK_ENTER,\n        KeyEvent.VK_BACK_QUOTE, KeyEvent.VK_MINUS, KeyEvent.VK_EQUALS, KeyEvent.VK_OPEN_BRACKET, KeyEvent.VK_CLOSE_BRACKET, KeyEvent.VK_BACK_SLASH, KeyEvent.VK_SEMICOLON, KeyEvent.VK_QUOTE, KeyEvent.VK_COMMA, KeyEvent.VK_PERIOD, KeyEvent.VK_SLASH,\n        KeyEvent.VK_UP, KeyEvent.VK_DOWN, KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT,\n        KeyEvent.VK_PAGE_UP, KeyEvent.VK_PAGE_DOWN,\n        KeyEvent.VK_HOME, KeyEvent.VK_END, KeyEvent.VK_INSERT, KeyEvent.VK_PRINTSCREEN,\n        KeyEvent.VK_A, KeyEvent.VK_B, KeyEvent.VK_C, KeyEvent.VK_D, KeyEvent.VK_E, KeyEvent.VK_F, KeyEvent.VK_G, KeyEvent.VK_H, KeyEvent.VK_I, KeyEvent.VK_J, KeyEvent.VK_K, KeyEvent.VK_L, KeyEvent.VK_M, KeyEvent.VK_N, KeyEvent.VK_O, KeyEvent.VK_P, KeyEvent.VK_Q, KeyEvent.VK_R, KeyEvent.VK_S, KeyEvent.VK_T, KeyEvent.VK_U, KeyEvent.VK_V, KeyEvent.VK_W, KeyEvent.VK_X, KeyEvent.VK_Y, KeyEvent.VK_Z,\n        KeyEvent.VK_0, KeyEvent.VK_1, KeyEvent.VK_2, KeyEvent.VK_3, KeyEvent.VK_4, KeyEvent.VK_5, KeyEvent.VK_6, KeyEvent.VK_7, KeyEvent.VK_8, KeyEvent.VK_9,\n        KeyEvent.VK_F1, KeyEvent.VK_F2, KeyEvent.VK_F3, KeyEvent.VK_F4, KeyEvent.VK_F5, KeyEvent.VK_F6, KeyEvent.VK_F7, KeyEvent.VK_F8, KeyEvent.VK_F9, KeyEvent.VK_F10, KeyEvent.VK_F11, KeyEvent.VK_F12,\n        KeyEvent.VK_ADD, KeyEvent.VK_SUBTRACT, KeyEvent.VK_MULTIPLY, KeyEvent.VK_DIVIDE, KeyEvent.VK_NUM_LOCK,\n        KeyEvent.VK_NUMPAD0, KeyEvent.VK_NUMPAD1, KeyEvent.VK_NUMPAD2, KeyEvent.VK_NUMPAD3, KeyEvent.VK_NUMPAD4, KeyEvent.VK_NUMPAD5, KeyEvent.VK_NUMPAD6, KeyEvent.VK_NUMPAD7, KeyEvent.VK_NUMPAD8, KeyEvent.VK_NUMPAD9\n    };\n\n\n    private final static int[] MODIFIER_TABLE = {\n        KeyEvent.SHIFT_DOWN_MASK,\n        KeyEvent.CTRL_DOWN_MASK,\n        KeyEvent.ALT_DOWN_MASK,\n        KeyEvent.META_DOWN_MASK\n    };\n\n    private final static Color FOCUSED_TEXT_FIELD_FOREGROUND = Color.BLACK;\n    private final static Color UNFOCUSED_TEXT_FIELD_FOREGROUND = Color.DARK_GRAY;\n\n\n    public KeyboardShortcutChooser() {\n        this(null);\n    }\n\n    public KeyboardShortcutChooser(KeyStroke keyStroke) {\n        super(new BorderLayout());\n\n        JPanel flowPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n\n        this.currentKeyStroke = keyStroke;\n\n        textField = new JTextField(30) {\n            @Override\n            protected boolean processKeyBinding(KeyStroke keyStroke, KeyEvent keyEvent, int i, boolean b) {\n                return true;\n            }\n        };\n\n        textField.setDocument(new PlainDocument() {\n            @Override\n            public void insertString(int i, String string, AttributeSet attributeSet) throws BadLocationException {\n                if (updatingTextField) {\n                    super.insertString(i, string, attributeSet);\n                }\n            }\n\n            @Override\n            public void remove(int i, int i1) throws BadLocationException {\n                if (updatingTextField) {\n                    super.remove(i, i1);\n                }\n            }\n        });\n\n        flowPanel.add(textField);\n\n        modifierCheckBoxes = new JCheckBox[MODIFIER_TABLE.length];\n        for (int i = 0; i <  MODIFIER_TABLE.length; i++) {\n            @MagicConstant(flagsFromClass = java.awt.event.InputEvent.class) int modifier = MODIFIER_TABLE[i];\n            modifierCheckBoxes[i] = new JCheckBox(InputEvent.getModifiersExText(modifier));\n            flowPanel.add(modifierCheckBoxes[i]);\n            modifierCheckBoxes[i].addItemListener(this);\n        }\n        \n\n        keyComboBox = new SaneComboBox<>();\n        keyComboBox.addItem(new KeyChoice(0, noneString));\n        for (int keyChoice : KEY_CHOICES) {\n            addKeyChoice(keyChoice);\n        }\n\n        flowPanel.add(keyComboBox);\n\n        add(flowPanel, BorderLayout.NORTH);\n\n        updateKeyComboBox();\n        updateCheckBoxes();\n        updateTextField();\n\n        keyComboBox.addComboBoxListener(this);\n        textField.addKeyListener(this);\n        textField.addFocusListener(this);\n        textField.setForeground(UNFOCUSED_TEXT_FIELD_FOREGROUND);\n    }\n\n    private void addKeyChoice(int keyValue) {\n        keyComboBox.addItem(new KeyChoice(keyValue, KeyEvent.getKeyText(keyValue)));\n    }\n\n    private void updateTextField() {\n        LOGGER.trace(\"currentKeyStroke=\"+currentKeyStroke+\" keyCode=\"+ (currentKeyStroke==null?\"null\":\"\"+currentKeyStroke.getKeyCode()));\n\n        updatingTextField = true;\n\n        if (currentKeyStroke == null || currentKeyStroke.getKeyCode() == 0) {\n            textField.setText(noneString);\n        } else {\n            textField.setText(KeyStrokeUtils.getKeyStrokeDisplayableRepresentation(currentKeyStroke));\n        }\n\n        updatingTextField = false;\n    }\n\n    private void updateKeyComboBox() {\n        updatingComboBox = true;\n        int keyCode = currentKeyStroke==null?0:currentKeyStroke.getKeyCode();\n\n        LOGGER.trace(\"keyCode=\"+ keyCode);\n\n        int nbChoices = keyComboBox.getItemCount();\n        for (int i=1; i<nbChoices; i++) {\n            LOGGER.trace(\"i=\"+i+\" value=\"+(keyComboBox.getItemAt(i)).getKeyValue()+\" label=\"+(keyComboBox.getItemAt(i)).getKeyLabel());\n            if ((keyComboBox.getItemAt(i)).getKeyValue()== keyCode) {\n                keyComboBox.setSelectedIndex(i);\n                updatingComboBox = false;\n                return;\n            }\n        }\n\n        keyComboBox.setSelectedIndex(0);\n        updatingComboBox = false;\n    }\n\n    private void updateCheckBoxes() {\n        int modifiers = currentKeyStroke == null ? 0 : currentKeyStroke.getModifiers();\n\n        updatingCheckBoxes = true;\n        for (int i = 0; i < MODIFIER_TABLE.length; i++) {\n            modifierCheckBoxes[i].setSelected((modifiers & MODIFIER_TABLE[i]) != 0);\n        }\n        updatingCheckBoxes = false;\n    }\n\n    private void updateKeyStroke() {\n        int modifiers = 0;\n\n        for (int i = 0; i < MODIFIER_TABLE.length; i++) {\n            if (modifierCheckBoxes[i].isSelected()) {\n                modifiers |= MODIFIER_TABLE[i];\n            }\n        }\n\n        KeyChoice keyChoice = ((KeyChoice)keyComboBox.getSelectedItem());\n        if (keyChoice != null) {\n            currentKeyStroke = KeyStroke.getKeyStroke(keyChoice.getKeyValue(), modifiers);\n        }\n    }\n\n    @Override\n    public void itemStateChanged(ItemEvent itemEvent) {\n        if (!updatingCheckBoxes) {\n            updateKeyStroke();\n            updateTextField();\n        }\n    }\n\n    @Override\n    public void comboBoxSelectionChanged(SaneComboBox source) {\n        if (!updatingComboBox) {\n            updateKeyStroke();\n            updateTextField();\n        }\n    }\n\n\n    @Override\n    public void focusGained(FocusEvent focusEvent) {\n        textField.setText(\"\");\n        textField.setForeground(FOCUSED_TEXT_FIELD_FOREGROUND);\n    }\n\n    @Override\n    public void focusLost(FocusEvent focusEvent) {\n        textField.setForeground(UNFOCUSED_TEXT_FIELD_FOREGROUND);\n        updateTextField();\n    }\n\n    @Override\n    public void keyPressed(KeyEvent keyEvent) {\n        LOGGER.trace(\"keyModifiers={} keyCode={}\", keyEvent.getModifiersEx(), keyEvent.getKeyCode());\n\n        int keyCode = keyEvent.getKeyCode();\n        if (keyCode==KeyEvent.VK_SHIFT || keyCode==KeyEvent.VK_CONTROL || keyCode==KeyEvent.VK_ALT || keyCode==KeyEvent.VK_META) {\n            return;\n        }\n\n        currentKeyStroke = KeyStroke.getKeyStrokeForEvent(keyEvent);\n\n        updateKeyComboBox();\n        updateCheckBoxes();\n        updateTextField();\n    }\n\n    @Override\n    public void keyReleased(KeyEvent keyEvent) {\n    }\n\n    @Override\n    public void keyTyped(KeyEvent keyEvent) {\n    }\n\n\n\n    private static class KeyChoice {\n        private final int keyValue;\n        private final String keyLabel;\n\n        private KeyChoice(int choiceValue, String choiceLabel) {\n            this.keyValue = choiceValue;\n            this.keyLabel = choiceLabel;\n        }\n\n        private int getKeyValue() {\n            return keyValue;\n        }\n\n        private String getKeyLabel() {\n            return keyLabel;\n        }\n\n        public boolean equals(Object o) {\n            if (o instanceof KeyChoice) {\n                return ((KeyChoice)o).keyValue == keyValue;\n            }\n            return false;\n        }\n\n        public String toString() {\n            return keyLabel;\n        }\n    }\n\n\n\n    public static void main(String[] args) {\n        Translator.init();\n\n        JFrame frame = new JFrame();\n\n        frame.getContentPane().add(new KeyboardShortcutChooser());\n\n        frame.pack();\n//        Dimension d = frame.getSize();\n//        frame.setSize(new Dimension(d.width+200, d.height));\n        frame.setVisible(true);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/chooser/PreviewLabel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.chooser;\n\nimport com.mucommander.utils.text.Translator;\n\nimport javax.swing.*;\nimport javax.swing.border.Border;\nimport javax.swing.border.LineBorder;\nimport java.awt.*;\nimport java.beans.PropertyChangeEvent;\nimport java.beans.PropertyChangeListener;\n\n/**\n * PreviewLabel is a component used to preview a color selection that will eventually be used on a label.\n * This component is used by {@link ColorChooser} to preview the current color selection.\n *\n * @author Nicolas Rinaudo, Maxence Bernard\n */\npublic class PreviewLabel extends JLabel implements PropertyChangeListener, Cloneable {\n\n    /** Color painted on top of the label. */\n    private Color overlayColor;\n\n    /** Label's border, if necessary. */\n    private Border border;\n\n    /** Controls whether the overlay should be painted over or under the text. */\n    private boolean overlayUnderText;\n\n    public final static String FOREGROUND_COLOR_PROPERTY_NAME = \"PreviewLabel.ForegroundColor\";\n    public final static String BACKGROUND_COLOR_PROPERTY_NAME = \"PreviewLabel.BackgroundColor\";\n    public final static String OVERLAY_COLOR_PROPERTY_NAME = \"PreviewLabel.OverlayColor\";\n    public final static String BORDER_COLOR_PROPERTY_NAME = \"PreviewLabel.BorderColor\";\n\n\n    /**\n     * Creates a new preview label.\n     */\n    public PreviewLabel() {\n        super(\" \");\n        addPropertyChangeListener(this);\n    }\n\n    /**\n     * Sets the label's overlay color.\n     */\n    public void setOverlay(Color color) {\n        putClientProperty(OVERLAY_COLOR_PROPERTY_NAME, color);\n    }\n\n    public void setTextPainted(boolean b) {\n        setText(b ? Translator.get(\"sample_text\") : \" \");\n    }\n\n    public void setOverlayUnderText(boolean b) {\n        overlayUnderText = b;\n        repaint();\n    }\n\n    public void setBorderColor(Color color) {\n        putClientProperty(BORDER_COLOR_PROPERTY_NAME, color);\n    }\n\n    private void paintText(Graphics g) {\n        g.setColor(getForeground());\n        g.setFont(getFont());\n        FontMetrics metrics = getFontMetrics(getFont());\n        g.drawString(getText(), (getWidth() - metrics.stringWidth(getText())) / 2, (getHeight() - metrics.getHeight()) / 2 + metrics.getAscent());\n    }\n\n\n    @Override\n    public void setForeground(Color color) {\n        putClientProperty(FOREGROUND_COLOR_PROPERTY_NAME, color);\n    }\n\n    @Override\n    public void setBackground(Color color) {\n        putClientProperty(BACKGROUND_COLOR_PROPERTY_NAME, color);\n    }\n\n    @Override\n    public Object clone() throws CloneNotSupportedException {\n        PreviewLabel label = (PreviewLabel)super.clone();\n        label.addPropertyChangeListener(label);\n        return label;\n    }\n\n    /**\n     * Paints the preview label.\n     */\n    @Override\n    public void paint(Graphics g) {\n        int width = getWidth();\n        int height = getHeight();\n\n        g.setColor(getBackground());\n        g.fillRect(0, 0, width, height);\n\n        if (!overlayUnderText) {\n            paintText(g);\n        }\n\n        if (overlayColor != null) {\n            g.setColor(overlayColor);\n            g.fillRect(0, 0, width/2, height);\n        }\n\n        if (overlayUnderText) {\n            paintText(g);\n        }\n\n        if (border != null) {\n            border.paintBorder(this, g, 0, 0, width, height);\n        }\n    }\n\n    @Override\n    public Dimension getPreferredSize() {\n        Dimension dimension = super.getPreferredSize();\n        dimension.setSize(dimension.getWidth()+8, dimension.getHeight()+6);\n\n        return dimension;\n    }\n\n    @Override\n    public void propertyChange(PropertyChangeEvent event) {\n        String name = event.getPropertyName();\n        Object value = event.getNewValue();\n\n        if (FOREGROUND_COLOR_PROPERTY_NAME.equals(name)) {\n            super.setForeground((Color)value);\n        } else if (BACKGROUND_COLOR_PROPERTY_NAME.equals(name)) {\n            super.setBackground((Color)value);\n        } else if (OVERLAY_COLOR_PROPERTY_NAME.equals(name)) {\n            overlayColor = (Color)value;\n            repaint();\n        } else if (BORDER_COLOR_PROPERTY_NAME.equals(name)) {\n            border = new LineBorder((Color)value, 1);\n            repaint();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/chooser/SizeChooser.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.chooser;\r\n\r\nimport com.mucommander.utils.text.SizeFormat;\r\nimport com.mucommander.ui.combobox.TcComboBox;\r\n\r\nimport javax.swing.*;\r\nimport javax.swing.event.ChangeEvent;\r\nimport javax.swing.event.ChangeListener;\r\nimport java.awt.*;\r\nimport java.util.WeakHashMap;\r\n\r\n/**\r\n * <code>SizeChooser</code> is a compound component made of a <code>JComboBox</code> and a <code>JSpinner</code> that\r\n * allows the user to enter a size in multiple of a selectable unit: byte, kilobyte, megabyte, ...\r\n * Each time the value changes, a <code>ChangeEvent</code> is fired to registered listeners.\r\n *\r\n * <p>This component can also serve to enter a speed in byte/kilobyte/megabyte/... per second. This only affects the\r\n * units displayed, this component works in the exact same way otherwise.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class SizeChooser extends JPanel {\r\n\r\n    /** Allows to enter a value in multiple of the current unit */\r\n    private final JSpinner valueSpinner;\r\n\r\n    /** Allows to select the size/speed unit */\r\n    private final JComboBox<String> unitComboBox;\r\n\r\n    /** Contains all registered listeners, stored as weak references */\r\n    private final WeakHashMap<ChangeListener, ChangeListener> listeners = new WeakHashMap<>();\r\n\r\n    /** Maximum value allowed by the spinner */\r\n    private final static int MAX_SPINNER_VALUE = Integer.MAX_VALUE;\r\n\r\n    /** Value increase/decrease when clicking the spinner's up/down buttons */\r\n    private final static int SPINNER_STEP = 100;\r\n\r\n    /** Maximum number of columns that the spinner's text field can have */\r\n    private final static int MAX_SPINNER_COLUMNS = 7;\r\n\r\n\r\n    /**\r\n     * Creates a new SizeChooser.\r\n     *\r\n     * @param speedUnits if true, speed units will be displayed (B/s, KB/s, MB/s, ...) instead of size unit (B, KB, MB, ...).\r\n     */\r\n    public SizeChooser(boolean speedUnits) {\r\n        setLayout(new BoxLayout(this, BoxLayout.X_AXIS));\r\n\r\n        valueSpinner = new JSpinner(new SpinnerNumberModel(0, 0, MAX_SPINNER_VALUE, SPINNER_STEP));\r\n        valueSpinner.addChangeListener(e -> fireChangeEvent());\r\n\r\n        // Limit the number of columns of the spinner's JTextField to a reasonable amount.\r\n        // By default, the text field has as many columns as needed to fit the spinner maximum value.\r\n        // If this maximum value is Integer.MAX_VALUE, the text field has 13 columns which makes it enormous.\r\n        JComponent editor = valueSpinner.getEditor();\r\n        if (editor instanceof JSpinner.DefaultEditor) {\r\n            JTextField textField = ((JSpinner.DefaultEditor)editor).getTextField();\r\n            int nbColumns = textField.getColumns();\r\n            if (nbColumns > MAX_SPINNER_COLUMNS ) {\r\n                textField.setColumns(MAX_SPINNER_COLUMNS);\r\n            }\r\n            textField.setMaximumSize(textField.getPreferredSize());\r\n        }\r\n        add(valueSpinner);\r\n\r\n        unitComboBox = new TcComboBox<>();\r\n        for (int i = SizeFormat.BYTE_UNIT; i <= SizeFormat.GIGABYTE_UNIT; i++) {\r\n            unitComboBox.addItem(SizeFormat.getUnitString(i, speedUnits));\r\n        }\r\n        unitComboBox.setSelectedIndex(SizeFormat.KILOBYTE_UNIT);\r\n        unitComboBox.addItemListener(e -> fireChangeEvent());\r\n        add(unitComboBox);\r\n    }\r\n\r\n    @Override\r\n    public Dimension getSize() {\r\n        // the panel height can't be greater than the text editor height\r\n        Dimension d = super.getSize();\r\n        final int maxHeight = (int)valueSpinner.getEditor().getPreferredSize().getHeight();\r\n        if (d.getHeight() > maxHeight) {\r\n            d.height = maxHeight;\r\n        }\r\n        return d;\r\n    }\r\n\r\n    /**\r\n     * Returns the current value expressed in bytes.\r\n     *\r\n     * @return the current value expressed in bytes\r\n     */\r\n    public long getValue() {\r\n        return SizeFormat.getUnitBytes(unitComboBox.getSelectedIndex())* (Integer) valueSpinner.getValue();\r\n    }\r\n\r\n\r\n    /**\r\n     * Adds the specified ChangedListener to the list of registered listeners.\r\n     *\r\n     * <p>Listeners are stored as weak references so {@link #remove}\r\n     * doesn't need to be called for listeners to be garbage collected when they're not used anymore.\r\n     *\r\n     * @param listener the ChangeListener to add to the list of registered listeners.\r\n     */\r\n    public synchronized void addChangeListener(ChangeListener listener) {\r\n        listeners.put(listener, null);\r\n    }\r\n\r\n    /**\r\n     * Removes the specified ChangeListener from the list of registered listeners.\r\n     *\r\n     * @param listener the ChangeListener to remove from the list of registered listeners.\r\n     */\r\n    public synchronized void removeChangeListener(ChangeListener listener) {\r\n        listeners.remove(listener);\r\n    }\r\n\r\n    /**\r\n     * Notifies all registered ChangeListener that the current value has changed. This method is called as the result\r\n     * of a change in the spinner or the combo box.\r\n     */\r\n    public synchronized void fireChangeEvent() {\r\n        for (ChangeListener listener : listeners.keySet())\r\n            listener.stateChanged(new ChangeEvent(this));\r\n    }\r\n\r\n    @Override\r\n    public void setEnabled(boolean enabled) {\r\n        valueSpinner.setEnabled(enabled);\r\n        unitComboBox.setEnabled(enabled);\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/chooser/package.html",
    "content": "<body>\n  Provides various UI components used to let users choose between sets of values.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/combobox/AutocompleteEditableCombobox.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.combobox;\n\nimport com.mucommander.ui.autocomplete.EditableComboboxCompletion;\nimport com.mucommander.ui.autocomplete.TypicalAutocompleterEditableCombobox;\nimport com.mucommander.ui.autocomplete.completers.Completer;\n\nimport javax.swing.*;\nimport java.awt.event.KeyEvent;\nimport java.util.Vector;\n\n/**\n * <code>AutocompleteEditableCombobox</code> is an editable combo-box that provides\n * auto-completion capabilities based on the given <code>Completer</code>.\n * \n * @author Arik Hadas\n */\npublic class AutocompleteEditableCombobox<E> extends EditableComboBox<E> {\n\n\t/**\n     * Creates a new editable combo box and a JTextField to be used as the editor.\n     * Has the same effect as calling {@link EditableComboBox#EditableComboBox(javax.swing.JTextField)} with a null value.\n     */\n    public AutocompleteEditableCombobox(Completer completer) {\n        super();\n        enableAutoCompletion(completer);\n    }\n\n    /**\n     * Creates a new editable combo box using the given text field as the editor.\n     *\n     * @param textField the text field to be used as the combo box's editor. If null, a new JTextField instance\n     * will be created and used.\n     */\n    public AutocompleteEditableCombobox(JTextField textField, Completer completer) {\n        super(textField);\n        enableAutoCompletion(completer);\n    }\n\n    /**\n     * Creates a new editable combo box using the given text field as the editor and ComboBoxModel.\n     *\n     * @param textField the text field to be used as the combo box's editor. If null, a new JTextField instance\n     * will be created and used.\n     * @param comboBoxModel the ComboBoxModel to use for this combo box\n     * @param completer Completer object\n     */\n    public AutocompleteEditableCombobox(JTextField textField, ComboBoxModel<E> comboBoxModel, Completer completer) {\n        super(textField, comboBoxModel);\n        enableAutoCompletion(completer);\n    }\n\n    /**\n     * Creates a new editable combo box using the given text field as the editor and items to populate the initial items list.\n     *\n     * @param textField the text field to be used as the combo box's editor. If null, a new JTextField instance\n     * will be created and used.\n     * @param items items used to populate the initial items list.\n     * @param completer Completer object\n     */\n    public AutocompleteEditableCombobox(JTextField textField, E[] items, Completer completer) {\n        super(textField, items);\n        enableAutoCompletion(completer);\n    }\n\n    /**\n     * Creates a new editable combo box using the given text field as the editor and items to populate the initial items list.\n     *\n     * @param textField the text field to be used as the combo box's editor. If null, a new JTextField instance\n     * will be created and used.\n     * @param items items used to populate the initial items list.\n     * @param completer Completer object\n     */\n    public AutocompleteEditableCombobox(JTextField textField, Vector<E> items, Completer completer) {\n        super(textField, items);\n        enableAutoCompletion(completer);\n    }\n    \n    private void enableAutoCompletion(Completer completer) {\n    \tnew EditableComboboxCompletion(new TypicalAutocompleterEditableCombobox(this), completer);\n    }\n\t\n    /**\n     * The desired behavior of this editable combo-box when enter key is pressed.\n     * \n     * @param keyEvent - the KeyEvent that occurred. \n     */\n\tpublic void respondToEnterKeyPressing(KeyEvent keyEvent) {\n\t\t// Combo popup menu is visible\n\t\tif (isPopupVisible()) {\n\t\t\t// Note that since the event is not consumed, JComboBox will catch it and fire\n\t\t}\n\t\t// Combo popup menu is not visible, these events really belong to the text field\n\t\telse {\n\t\t\t// Notify listeners that the text field has been validated\n\t\t\tfireComboFieldValidated();\n            \n            // /!\\ Consume the event so to prevent JComboBox from firing an ActionEvent (default JComboBox behavior)\n            keyEvent.consume();\n\t\t}\t\t\n\t}\n\n\t/**\n     * The desired behavior of this editable combo-box when escape key is pressed.\n     * \n     * @param keyEvent - the KeyEvent that occurred. \n     */\n\tpublic void respondToEscapeKeyPressing(KeyEvent keyEvent) {\n\t\t// Combo popup menu is visible\n\t\tif (isPopupVisible()) {\n\t\t\t // Explicitely hide popup menu, JComboBox does not seem do it automatically (at least under Mac OS X + Java 1.5 and Java 1.4)\n            hidePopup();\n            // Consume the event so that it is not propagated, since dialogs catch this event to close the window\n            keyEvent.consume();\n\t\t} else {    // Combo popup menu is not visible, these events really belong to the text field\n        \t// Notify listeners that the text field has been cancelled\n        \tfireComboFieldCancelled();\n        }\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/combobox/ComboBoxCellRenderer.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.combobox;\n\nimport lombok.Setter;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n/**\n * @author Nicolas Rinaudo\n */\npublic class ComboBoxCellRenderer<E> implements ListCellRenderer<E> {\n    private Color textColor;\n    private Color backgroundColor;\n    private Color selectedTextColor;\n    private Color selectedBackgroundColor;\n    @Setter\n    private Font  font;\n    private final JLabel label;\n\n    public ComboBoxCellRenderer() {\n        label = new JLabel();\n        label.setOpaque(true);\n    }\n\n    public void setForeground(Color color) {textColor = color;}\n    public void setBackground(Color color) {backgroundColor = color;}\n    public void setSelectionForeground(Color color) {selectedTextColor = color;}\n    public void setSelectionBackground(Color color) {selectedBackgroundColor = color;}\n\n    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean hasFocus) {\n        if(value!=null)\n            label.setText(value.toString());\n\n        if(font == null)\n            label.setFont(list.getFont());\n        else\n            label.setFont(font);\n\n        if(isSelected) {\n            if(selectedBackgroundColor == null)\n                label.setBackground(list.getSelectionBackground());\n            else\n                label.setBackground(selectedBackgroundColor);\n            if(selectedTextColor == null)\n                label.setForeground(list.getSelectionForeground());\n            else\n                label.setForeground(selectedTextColor);\n        }\n        else {\n            if(backgroundColor == null)\n                label.setBackground(list.getBackground());\n            else\n                label.setBackground(backgroundColor);\n            if(textColor == null)\n                label.setForeground(list.getForeground());\n            else\n                label.setForeground(textColor);\n        }\n        return label;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/combobox/ComboBoxListener.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.combobox;\n\n\n/**\n * Interface to be implemented by classes that wish to be notified of selections occuring on a {@link SaneComboBox}.\n * @author Maxence Bernard\n */\npublic interface ComboBoxListener {\n    /**\n     * This method is called when an item has been selected from the specified combo box popup menu.\n     * The item may have been selected either with the 'Enter' key, or by clicking on the item.\n     *\n     * @param source the SaneComboBox on which the event was triggered\n     */\n    void comboBoxSelectionChanged(SaneComboBox source);\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/combobox/EditableComboBox.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.combobox;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.swing.*;\nimport javax.swing.plaf.basic.BasicComboBoxEditor;\nimport java.awt.*;\nimport java.awt.event.KeyAdapter;\nimport java.awt.event.KeyEvent;\nimport java.util.Vector;\nimport java.util.WeakHashMap;\n\n/**\n * EditableComboBox is an editable combo box (really!) that can use a specified JTextField to be used as the editor.\n *\n * <p>EditableComboBox also extends JComboBox to make it much easier to use, instead of having to work around its\n * numerous bugs and weird behavior (understatement). Registering a {@link EditableComboBoxListener} makes it\n * easy to know for sure when an item has been selected from the combo popup menu, or when the text field has been\n * validated ('Enter' key pressed) or cancelled ('Escape' key pressed). It is strongly recommanded to use this interface\n * instead of ActionListener / ItemListener, their already erratic behavior could be further aggravated by the tweakings\n * used in this class.\n *\n * <p>The {@link #setComboSelectionUpdatesTextField(boolean)} method allows to automatically replace the text field's\n * contents when an item is selected from the associated combo box, replacing its value by the selected item's\n * string representation. This feature is disabled by default.\n *\n * @author Maxence Bernard\n * @see EditableComboBoxListener\n */\npublic class EditableComboBox<E> extends SaneComboBox<E> {\n    /**\n     * Used to render the content of the combo box.\n     */\n    private ComboBoxCellRenderer<E> renderer;\n    /**\n     * The text field used as the combo box's editor\n     * -- GETTER --\n     *  Returns the text field used as the combo box's editor.\n     */\n    @Getter\n    private JTextField textField;\n\n    /**\n     * Contains all registered EditableComboBoxListener instances, stored as weak references\n     */\n    private final WeakHashMap<EditableComboBoxListener, Object> listeners = new WeakHashMap<>();\n\n    /**\n     * Specifies whether the text field's contents is updated when an item is selected in the associated combo box\n     * -- SETTER --\n     *  If true is specified, when an item is selected in this combo box, the text field's contents\n     *  will be automatically replaced by the selected item's string representation.\n\n     */\n    @Setter\n    private boolean comboSelectionUpdatesTextField;\n\n\n    /**\n     * Creates a new editable combo box and a JTextField to be used as the editor.\n     * Has the same effect as calling {@link #EditableComboBox(javax.swing.JTextField)} with a null value.\n     */\n    public EditableComboBox() {\n        init(null);\n    }\n\n    /**\n     * Creates a new editable combo box using the given text field as the editor.\n     *\n     * @param textField the text field to be used as the combo box's editor. If null, a new JTextField instance\n     *                  will be created and used.\n     */\n    public EditableComboBox(JTextField textField) {\n        init(textField);\n    }\n\n    /**\n     * Creates a new editable combo box using the given text field as the editor and ComboBoxModel.\n     *\n     * @param textField     the text field to be used as the combo box's editor. If null, a new JTextField instance\n     *                      will be created and used.\n     * @param comboBoxModel the ComboBoxModel to use for this combo box\n     */\n    public EditableComboBox(JTextField textField, ComboBoxModel<E> comboBoxModel) {\n        super(comboBoxModel);\n        init(textField);\n    }\n\n    /**\n     * Creates a new editable combo box using the given text field as the editor and items to populate the initial items list.\n     *\n     * @param textField the text field to be used as the combo box's editor. If null, a new JTextField instance\n     *                  will be created and used.\n     * @param items     items used to populate the initial items list.\n     */\n    public EditableComboBox(JTextField textField, E[] items) {\n        super(items);\n        init(textField);\n    }\n\n    /**\n     * Creates a new editable combo box using the given text field as the editor and items to populate the initial items list.\n     *\n     * @param textField the text field to be used as the combo box's editor. If null, a new JTextField instance\n     *                  will be created and used.\n     * @param items     items used to populate the initial items list.\n     */\n    public EditableComboBox(JTextField textField, Vector<E> items) {\n        super(items);\n        init(textField);\n    }\n\n\n    /**\n     * If true is returned, when an item is selected in this combo box, the text field's contents\n     * will be automatically replaced by the selected item's string representation.\n     * This feature is disabled by default (false is returned).\n     */\n    public boolean getComboSelectionUpdatesTextField() {\n        return comboSelectionUpdatesTextField;\n    }\n\n    /**\n     * Initializes the combo box to make it editable and use the given text field.\n     *\n     * @param textField the text field to be used as the combo box's editor. If null, a JTextField instance will be created and used.\n     */\n    private void init(JTextField textField) {\n        setRenderer(renderer = new ComboBoxCellRenderer<>());\n        // create a new JTextField if no text field was specified\n        if (textField == null) {\n            this.textField = new JTextField();\n        }\n        // Use the specified text field\n        else\n            this.textField = textField;\n\n        // Use a custom editor that uses the text field\n        setEditor(new BasicComboBoxEditor() {\n            @Override\n            public Component getEditorComponent() {\n                return EditableComboBox.this.textField;\n            }\n        });\n\n        // Make this combo box editable\n        setEditable(true);\n\n        // Note: the default JComboBox behavior is to also fire an ActionEvent when enter is pressed on the text field\n        // and the popup menu is not visible, making the ActionEvent indistinguishable from a combo item selection.\n        // This awful behavior is overridden by keyPressed() so that ActionEvent is only fired for item selections.\n\n        // Listen to the text field's key events. These are fired regardless of the combo box popup menu being visible or not.\n        // The following KeyListener is added as an anonymous inner class so that any class overridding EditableComboBox\n        // can safely implement KeyListener without risking to override those methods by accident.\n        this.textField.addKeyListener(new KeyAdapter() {\n\n            @Override\n            public void keyPressed(KeyEvent keyEvent) {\n                int keyCode = keyEvent.getKeyCode();\n\n                // Combo popup menu is visible\n                if (isPopupVisible()) {\n                    if (keyCode == KeyEvent.VK_ENTER) {\n                        // Note that since the event is not consumed, JComboBox will catch it and fire\n                    } else if (keyCode == KeyEvent.VK_ESCAPE) {\n                        // Explicitly hide popup menu, JComboBox does not seem do it automatically (at least under Mac OS X + Java 1.5 and Java 1.4)\n                        hidePopup();\n                        // Consume the event so that it is not propagated, since dialogs catch this event to close the window\n                        keyEvent.consume();\n                    }\n                }\n                // Combo popup menu is not visible, these events really belong to the text field\n                else {\n                    if (keyCode == KeyEvent.VK_ENTER) {\n                        // Notify listeners that the text field has been validated\n                        fireComboFieldValidated();\n                        // /!\\ Consume the event so to prevent JComboBox from firing an ActionEvent (default JComboBox behavior)\n                        keyEvent.consume();\n                    } else if (keyCode == KeyEvent.VK_ESCAPE) {\n                        // Notify listeners that the text field has been cancelled\n                        fireComboFieldCancelled();\n                    }\n                }\n            }\n        });\n    }\n\n\n    //////////////////////////////////////////////\n    // EditableComboBoxListener support methods //\n    //////////////////////////////////////////////\n\n    /**\n     * Adds the specified EditableComboBoxListener to the list of registered listeners.\n     *\n     * <p>Listeners are stored as weak references so {@link #removeEditableComboBoxListener(EditableComboBoxListener)}\n     * doesn't need to be called for listeners to be garbage collected when they're not used anymore.\n     *\n     * @param listener the EditableComboBoxListener to add to the list of registered listeners.\n     */\n    public void addEditableComboBoxListener(EditableComboBoxListener listener) {\n        addComboBoxListener(listener);\n        listeners.put(listener, null);\n    }\n\n    /**\n     * Removes the specified EditableComboBoxListener from the list of registered listeners.\n     *\n     * @param listener the EditableComboBoxListener to remove from the list of registered listeners.\n     */\n    public void removeEditableComboBoxListener(EditableComboBoxListener listener) {\n        removeComboBoxListener(listener);\n        listeners.remove(listener);\n    }\n\n\n    /**\n     * Overrides {@link SaneComboBox#fireComboBoxSelectionChanged()} to set the text field's contents to the item that\n     * has been selected, if {@link #setComboSelectionUpdatesTextField(boolean)} has been enabled.\n     */\n    @Override\n    protected void fireComboBoxSelectionChanged() {\n        if (comboSelectionUpdatesTextField) {\n            // Replace the text field's contents by the selected item's string representation,\n            // only if this feature has been enabled\n            if (getSelectedIndex() != -1)\n                textField.setText(getSelectedItem().toString());\n        }\n\n        super.fireComboBoxSelectionChanged();\n    }\n\n\n    /**\n     * Notifies all registered EditableComboBoxListener instances that the text field has been validated, that is\n     * the 'Enter' key has been pressed in the text field, without the popup menu being visible.\n     *\n     * <p>Note: Unlike JComboBox's weird ActionEvent handling, this event is *not* fired when 'Enter' is pressed\n     * in the combo popup menu.\n     */\n    protected void fireComboFieldValidated() {\n        // Iterate on all listeners\n        for (EditableComboBoxListener listener : listeners.keySet())\n            listener.textFieldValidated(this);\n    }\n\n\n    /**\n     * Notifies all registered EditableComboBoxListener instances that the text field has been cancelled, that is\n     * the 'Escape' key has been pressed in the text field, without the popup menu being visible.\n     *\n     * <p>Note: This event is *not* fired when 'Escape' is pressed in the combo popup menu.\n     */\n    protected void fireComboFieldCancelled() {\n        // Iterate on all listeners\n        for (EditableComboBoxListener listener : listeners.keySet())\n            listener.textFieldCancelled(this);\n    }\n\n\n    // - Aspect managenement -------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    @Override\n    public void setForeground(Color color) {\n        if (renderer == null)\n            super.setForeground(color);\n        else {\n            renderer.setForeground(color);\n            textField.setForeground(color);\n        }\n    }\n\n    @Override\n    public void setBackground(Color color) {\n        if (renderer == null)\n            super.setBackground(color);\n        else {\n            renderer.setBackground(color);\n            textField.setBackground(color);\n        }\n    }\n\n    public void setSelectionForeground(Color color) {\n        if (renderer != null) {\n            renderer.setSelectionForeground(color);\n            textField.setSelectedTextColor(color);\n        }\n    }\n\n    public void setSelectionBackground(Color color) {\n        if (renderer != null) {\n            renderer.setSelectionBackground(color);\n            textField.setSelectionColor(color);\n        }\n    }\n\n    @Override\n    public void setFont(Font font) {\n        super.setFont(font);\n        if (renderer != null) {\n            renderer.setFont(font);\n            textField.setFont(font);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/combobox/EditableComboBoxListener.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.combobox;\n\n/**\n * Interface to be implemented by classes that wish to be notified of actions occuring on a {@link EditableComboBox}.\n * Those classes need to be registered to receive those events, this can be done by calling\n * {@link EditableComboBox#addEditableComboBoxListener(EditableComboBoxListener)}.\n *\n * @author Maxence Bernard\n */\npublic interface EditableComboBoxListener extends ComboBoxListener {\n\n    /**\n     * This method is called when the text field has been validated, that is the 'Enter' key has been pressed\n     * in the text field, without the popup menu being visible.\n     *\n     * <p>Note: Unlike JComboBox's weird ActionEvent handling, this method is *not* called when 'Enter' is pressed\n     * in the combo popup menu.\n     *\n     * @param source the EditableComboBox containing the JTextField on which the event was triggered\n     */\n    void textFieldValidated(EditableComboBox source);\n\n\n    /**\n     * Notifies all registered EditableComboBoxListener instances that the text field has been cancelled, that is\n     * the 'Escape' key has been pressed in the text field, without the popup menu being visible.\n     *\n     * <p>Note: This method is *not* called when 'Escape' is pressed in the combo popup menu.\n     *\n     * @param source the EditableComboBox containing the JTextField on which the event was triggered\n     */\n    void textFieldCancelled(EditableComboBox source);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/combobox/SaneComboBox.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.combobox;\n\nimport com.mucommander.ui.dialog.FocusDialog;\n\nimport javax.swing.*;\nimport java.awt.Container;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.KeyEvent;\nimport java.util.Vector;\nimport java.util.WeakHashMap;\n\n\n/**\n * SaneComboBox is a JComboBox which does not have the awful default JComboBox behavior of firing ActionEvents\n * when navigating with the arrow keys between choices of the popup menu.\n * This page describes the problem in details: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4199622\n *\n * <p>Also when using {@link ComboBoxListener}, action events that are normally triggered by JComboBox\n * when the add/insert/remove item methods are called are filtered out, only actual selection changes performed\n * by the user are fired.\n *\n * @author Maxence Bernard\n */\npublic class SaneComboBox<E> extends JComboBox<E> {\n\n    private WeakHashMap<ComboBoxListener, Object> listeners = new WeakHashMap<>();\n    private boolean ignoreActionEvent;\n\n\n    public SaneComboBox() {\n        super();\n        init();\n    }\n\n    public SaneComboBox(ComboBoxModel<E> comboBoxModel) {\n        super(comboBoxModel);\n        init();\n    }\n\n    public SaneComboBox(E[] items) {\n        super(items);\n        init();\n    }\n\n    public SaneComboBox(Vector<E> items) {\n        super(items);\n        init();\n    }\n\n\n    private void init() {\n        // Prevent up/down keys from firing ActionEvents\n        // for Java 1.3\n        putClientProperty(\"JComboBox.lightweightKeyboardNavigation\",\"Lightweight\");\n// Commented as it causes rendering issues under Mac OS X Leopard (does not render like a native combo box)\n//        // for Java 1.4 and up\n//        putClientProperty(\"JComboBox.isTableCellEditor\", Boolean.TRUE);\n\n        // Listen to combo box action events, these are fired each time an item is selected when the popup menu\n        // is visible, either by pressing 'Enter' on an item or by clicking on it.\n        addActionListener(new AbstractAction() {\n            public void actionPerformed(ActionEvent actionEvent) {\n                // Filter out action events triggered by the add/insert/remove item methods\n                if (!ignoreActionEvent)\n                    fireComboBoxSelectionChanged();\n            }\n        });\n    }\n\n\n    //////////////////////////////////////\n    // ComboBoxListener support methods //\n    //////////////////////////////////////\n\n    /**\n     * Adds the specified ComboBoxListener to the list of registered listeners.\n     *\n     * <p>Listeners are stored as weak references so {@link #removeComboBoxListener(ComboBoxListener)}\n     * doesn't need to be called for listeners to be garbage collected when they're not used anymore.\n     *\n     * @param listener the ComboBoxListener to add to the list of registered listeners.\n     */\n    public void addComboBoxListener(ComboBoxListener listener) {\n        listeners.put(listener, null);\n    }\n\n    /**\n     * Removes the specified ComboBoxListener from the list of registered listeners.\n     *\n     * @param listener the ComboBoxListener to remove from the list of registered listeners.\n     */\n    public void removeComboBoxListener(ComboBoxListener listener) {\n        listeners.remove(listener);\n    }\n\n    /**\n     * Notifies all registered ComboBoxListener instances that an item has been selected from the\n     * combo box popup menu. The item may have been selected either with the 'Enter' key, or by clicking on the item.\n     *\n     * <p>Unlike JComboBox ActionListener behavior, calls to the add/insert/remove item methods do *not* trigger\n     * a selection event.\n     */\n    protected void fireComboBoxSelectionChanged() {\n        // Iterate on all listeners\n        for (ComboBoxListener listener : listeners.keySet())\n            listener.comboBoxSelectionChanged(this);\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n\n    @Override\n    public void addItem(E object) {\n        ignoreActionEvent = true;\n        super.addItem(object);\n        ignoreActionEvent = false;\n    }\n\n    @Override\n    public void insertItemAt(E object, int i) {\n        ignoreActionEvent = true;\n        super.insertItemAt(object, i);\n        ignoreActionEvent = false;\n    }\n\n    @Override\n    public void removeItem(Object object) {\n        ignoreActionEvent = true;\n        super.removeItem(object);\n        ignoreActionEvent = false;\n    }\n\n    @Override\n    public void removeItemAt(int i) {\n        ignoreActionEvent = true;\n        super.removeItemAt(i);\n        ignoreActionEvent = false;\n    }\n\n    @Override\n    public void removeAllItems() {\n        ignoreActionEvent = true;\n        super.removeAllItems();\n        ignoreActionEvent = false;\n    }\n\n    @Override\n    public void processKeyEvent(KeyEvent e) {\n        boolean popupVisible = isPopupVisible();\n        super.processKeyEvent(e);\n        // Close parent FocusDialog if ESC pressed\n        if (!popupVisible && e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == KeyEvent.VK_ESCAPE) {\n            Container container = getParent();\n            while (container != null) {\n                if (container instanceof FocusDialog) {\n                    ((FocusDialog) container).dispose();\n                    break;\n                }\n                container = container.getParent();\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/combobox/TcComboBox.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2020 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.combobox;\n\nimport com.mucommander.ui.dialog.FocusDialog;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.KeyEvent;\n\n/**\n * ComboBox with correct Esc handling.\n *\n * Created on 20/10/14.\n * @author Oleg Trifonov\n */\npublic class TcComboBox<E> extends JComboBox<E> {\n\n    public TcComboBox() {\n        super();\n    }\n\n    public TcComboBox(java.util.List<E> items) {\n        super();\n        for (E item : items) {\n            addItem(item);\n        }\n    }\n\n    public TcComboBox(E[] items) {\n        super();\n        for (E item : items) {\n            addItem(item);\n        }\n    }\n\n    @Override\n    public void processKeyEvent(KeyEvent e) {\n        boolean popupVisible = isPopupVisible();\n        super.processKeyEvent(e);\n        // Close parent FocusDialog if ESC pressed\n        if (!popupVisible && e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == KeyEvent.VK_ESCAPE) {\n            disposeParentFocusDialog();\n        }\n    }\n\n    private void disposeParentFocusDialog() {\n        Container container = getParent();\n        while (container != null) {\n            if (container instanceof FocusDialog) {\n                ((FocusDialog) container).dispose();\n                break;\n            }\n            container = container.getParent();\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/combobox/package.html",
    "content": "<body>\n  Provides various classes meant to work around the many JComboBox issues.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/DialogOwner.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog;\n\nimport java.awt.*;\n\n/**\n * This class wraps a dialog owner, which can either be a {@link Frame} or a {@link Dialog}.\n * Its purpose is to avoid the duplication of constructors of {@link Dialog} subclasses.\n *\n * @author Maxence Bernard\n */\npublic class DialogOwner {\n\n    protected Window owner;\n\n    /**\n     * Creates a new <code>DialogOwner</code> wrapping the given {@link Frame}.\n     *\n     * @param frame the dialog owner\n     */\n    public DialogOwner(Frame frame) {\n        this.owner = frame;\n    }\n\n    /**\n     * Creates a new <code>DialogOwner</code> wrapping the given {@link Dialog}.\n     *\n     * @param dialog the dialog owner\n     */\n    public DialogOwner(Dialog dialog) {\n        this.owner = dialog;\n    }\n\n    /**\n     * Returns the owner {@link Window} which was passed to the constructor.\n     *\n     * @return the owner {@link Window} which was passed to the constructor.\n     */\n    public Window getOwner() {\n        return owner;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/DialogToolkit.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.dialog;\n\nimport java.awt.Component;\nimport java.awt.Dialog;\nimport java.awt.Dimension;\nimport java.awt.Frame;\nimport java.awt.HeadlessException;\nimport java.awt.Rectangle;\nimport java.awt.Toolkit;\nimport java.awt.Window;\nimport java.awt.event.ActionListener;\n\nimport javax.swing.JButton;\nimport javax.swing.JOptionPane;\nimport javax.swing.JPanel;\nimport javax.swing.JRootPane;\n\nimport com.mucommander.conf.TcSnapshot;\nimport com.mucommander.ui.button.ButtonChoicePanel;\nimport com.mucommander.ui.helper.MnemonicHelper;\nimport com.mucommander.ui.helper.ScreenServices;\n\n\n/**\n * Placeholder for convenience methods that ease dialog creation.\n *\n * @author Maxence Bernard\n */\npublic class DialogToolkit {\n\n    public static boolean fitToMinDimension(Window window, Dimension minD) {\n        return fitToDimension(window, minD, true);\n    }\n\t\n    static boolean fitToMaxDimension(Window window, Dimension maxD) {\n        return fitToDimension(window, maxD, false);\n    }\n\n    public static boolean fitToScreen(Window window) {\n        Rectangle screenBounds = ScreenServices.getFullScreenBounds(window);\n        return fitToMaxDimension(window, new Dimension((int)screenBounds.getWidth(), (int)screenBounds.getHeight()));\n    }\n\t\n    private static boolean fitToDimension(Window window, Dimension d, boolean min) {\n        int maxWidth = (int)d.getWidth();\n        int maxHeight = (int)d.getHeight();\n        int windowWidth = window.getWidth();\n        int windowHeight = window.getHeight();\n        boolean changeSize = false;\n\t\t\n        // Minimum dimension\n        if (min) {\n            if (windowWidth < maxWidth) {\n                windowWidth = maxWidth;\n                changeSize = true;\n            }\n\t\t\t\t\n            if (windowHeight < maxHeight) {\n                windowHeight = maxHeight;\n                changeSize = true;\n            }\n        }\n        // Maximum dimension\n        else {\n            if (windowWidth > maxWidth) {\n                windowWidth = maxWidth;\n                changeSize = true;\n            }\n\t\t\t\t\n            if (windowHeight > maxHeight) {\n                windowHeight = maxHeight;\n                changeSize = true;\n            }\n        }\n\t\t\n        // Dimension needs to be changed\n        if (changeSize) {\n            window.setSize(windowWidth, windowHeight);\n        }\n\t\t\n        // Return true if dimension was changed \n        return changeSize;\n    }\n\t\n    \n    /**\n     * Sets the given component's (JFrame, JDialog...) location to be centered on screen.\n     * @param c component to center on screen\n     */\n    public static void centerOnScreen(Component c) {\n        Dimension screenSize = TcSnapshot.getScreenSize();\n        c.setLocation(screenSize.width/2 - c.getWidth()/2,\n                    screenSize.height/2 - c.getHeight()/2);\n    }\n\n    /**\n     * Centers the specified component on the specified window.\n     * <p>\n     * Note that this method assumes <code>c</code>'s dimension to be at most that of the screen.\n     * This can be ensured through {@link #fitToScreen(Window)}. If this constraint is not respected,\n     * behavior is unpredictable.\n     *\n     * @param c      component to center.\n     * @param window window to center on.\n     */\n    public static void centerOnWindow(Component c, Window window) {\n        int x = Math.max(0, window.getX() + (window.getWidth() - c.getWidth()) / 2);\n        int y = Math.max(0, window.getY() + (window.getHeight() - c.getHeight()) / 2);\n        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();\n        int buffer;\n        if ((buffer = screenSize.width - (c.getWidth() + x)) < 0) {\n            x += buffer;\n        }\n        if ((buffer = screenSize.height - (c.getHeight() + y)) < 0) {\n            y += buffer;\n        }\n        c.setLocation(x, y);\n    }\n    \n\t\n    /**\n     * Creates an OK/Cancel panel using the given buttons, and register the given listener for button actions.\n     * @param okButton OK button\n     * @param cancelButton Cancel button\n     * @param actionListener action listener\n     * @param rootPane root panel\n     */\n    public static JPanel createOKCancelPanel(JButton okButton, JButton cancelButton, JRootPane rootPane, ActionListener actionListener) {\n        return createButtonPanel(rootPane, actionListener, okButton, cancelButton);\n    }\n\n    /**\n     * Creates an OK panel using the given button, and register the given listener for button actions.\n     * @param okButton OK button\n     * @param actionListener action listener\n     * @param rootPane root panel\n     */\n    public static JPanel createOKPanel(JButton okButton, JRootPane rootPane, ActionListener actionListener) {\n        return createButtonPanel(rootPane, actionListener, okButton);\n    }\n\n    /**\n     * Creates a button panel using the given buttons, and register the given listener for button actions.\n     * Buttons are disposed horizontally, aligned to the right.\n     * @param rootPane root panel\n     * @param actionListener action listener\n     * @param buttons buttons\n     */\n    public static JPanel createButtonPanel(JRootPane rootPane, ActionListener actionListener, JButton ... buttons) {\n        JPanel panel = new ButtonChoicePanel(buttons, 0, rootPane);\n        \n        MnemonicHelper mnemonicHelper = new MnemonicHelper();\n        for (JButton button : buttons) {\n            button.setMnemonic(mnemonicHelper.getMnemonic(button.getText()));\n            button.addActionListener(actionListener);\n            panel.add(button);\n        }\n\n        return panel;\n    }\n\n    /**\n     * Returns the specified component's top level <code>Frame</code> or\n     * <code>Dialog</code>.\n     *\n     * @param parentComponent the <code>Component</code> to check for a\n     *\t\t<code>Frame</code> or <code>Dialog</code>\n     * @return the <code>Frame</code> or <code>Dialog</code> that\n     *\t\tcontains the component, or the default\n     *         \tframe if the component is <code>null</code>,\n     *\t\tor does not have a valid\n     *         \t<code>Frame</code> or <code>Dialog</code> parent\n     * @exception HeadlessException if\n     *   <code>GraphicsEnvironment.isHeadless</code> returns\n     *   <code>true</code>\n     * @see java.awt.GraphicsEnvironment#isHeadless\n     */\n    static Window getWindowForComponent(Component parentComponent) throws HeadlessException {\n        // Note: this method is a shameless rip from javax.swing.JOptionPane\n        if (parentComponent == null) {\n            return JOptionPane.getRootFrame();\n        }\n        if (parentComponent instanceof Frame || parentComponent instanceof Dialog) {\n            return (Window) parentComponent;\n        }\n        return getWindowForComponent(parentComponent.getParent());\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/FocusDialog.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\n\r\npackage com.mucommander.ui.dialog;\r\n\r\nimport java.awt.*;\r\nimport java.awt.event.*;\r\n\r\nimport javax.swing.*;\r\nimport javax.swing.border.EmptyBorder;\r\n\r\nimport com.mucommander.cache.WindowsStorage;\r\nimport com.mucommander.ui.macosx.IMacOsWindow;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.utils.text.Translator;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.ui.helper.FocusRequester;\r\n\r\n\r\n/**\r\n * FocusDialog is a modal dialog which extends JDialog to provide the following additional functionalities :\r\n * <ul>\r\n *   <li>focus can be requested on a specified JComponent once the dialog has been made visible</li>\r\n *   <li>the screen location of the window can be set relatively to a Component specified in the constructor</li>\r\n *   <li>a minimum and/or maximum size can be specified and will be used by {@link #pack()} to calculate the effective dialog size</li>\r\n *   <li>by default, the 'Escape' key disposes the dialog, this can be disabled using {@link #setKeyboardDisposalEnabled(boolean)}</li>\r\n * </ul>\r\n * @author Maxence Bernard\r\n */\r\npublic class FocusDialog extends JDialog implements WindowListener, IMacOsWindow {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(FocusDialog.class);\r\n    private static final EmptyBorder BORDER = new EmptyBorder(6, 8, 6, 8);\r\n\t\r\n    /** Minimum dimensions of this dialog, may be null */\r\n    private Dimension minimumDimension;\r\n\r\n    /** Maximum dimensions of this dialog, may be null */\r\n    private Dimension maximumDimension;\r\n\r\n    /** Has this window been activated yet ? */\r\n    private boolean firstTimeActivated;\r\n\r\n    /** The component that will receive the focus when this window is activated for the first time, may be null */\r\n    private JComponent initialFocusComponent;\r\n\r\n    private Component locationRelativeComp;\r\n\r\n    private boolean keyboardDisposalEnabled = true;\r\n\r\n    /**\r\n     * Suffix of keyname for position/size storage\r\n     */\r\n    private String storageSuffix;\r\n\r\n    private boolean storeSizes = true;\r\n\r\n    private final static String CUSTOM_DISPOSE_EVENT = \"CUSTOM_DISPOSE_EVENT\";\r\n\r\n    private static long lastCreateTime;\r\n    private static String lastCreateTitle;\r\n    private static Class lastCreateClass;\r\n    /**\r\n     * Saved to restore focus\r\n     */\r\n    private Component ownerFocusedComponent;\r\n\r\n    \r\n    public FocusDialog(Frame owner, String title, Component locationRelativeComp) {\r\n        super(owner, title, true);\r\n        init(locationRelativeComp);\r\n        if (owner != null) {\r\n            ownerFocusedComponent = owner.getFocusOwner();\r\n        }\r\n        boolean kill = false;\r\n        if (title != null && title.equals(lastCreateTitle)) {\r\n            long dt = System.currentTimeMillis() - lastCreateTime;\r\n            // sometimes EventDispatchThread duplicates events that caused double windows\r\n            if (dt < 250 && lastCreateClass != null && lastCreateClass.equals(getClass())) {\r\n                kill = true;\r\n            }\r\n        }\r\n        lastCreateTime = System.currentTimeMillis();\r\n        lastCreateTitle = title;\r\n        lastCreateClass = getClass();\r\n        if (kill) {\r\n            dispose();\r\n            throw new RuntimeException(\"EventDispatchThread error\");\r\n        }\r\n//        if (owner != null) {\r\n//            showOnScreen(owner);\r\n//        }\r\n    }\r\n\r\n    public FocusDialog(Dialog owner, String title, Component locationRelativeComp) {\r\n        super(owner, title, true);\r\n        init(locationRelativeComp);\r\n        if (owner != null) {\r\n            ownerFocusedComponent = owner.getFocusOwner();\r\n        }\r\n\r\n        boolean kill = false;\r\n        if (title != null && title.equals(lastCreateTitle)) {\r\n            long dt = System.currentTimeMillis() - lastCreateTime;\r\n            // sometimes EventDispatchThread duplicates events that caused double windows\r\n            if (dt < 250 && lastCreateClass != null && lastCreateClass.equals(getClass())) {\r\n                kill = true;\r\n            }\r\n        }\r\n        lastCreateTime = System.currentTimeMillis();\r\n        lastCreateTitle = title;\r\n        lastCreateClass = getClass();\r\n        if (kill) {\r\n            dispose();\r\n            throw new RuntimeException(\"EventDispatchThread error\");\r\n        }\r\n//        if (owner != null) {\r\n//            showOnScreen(owner);\r\n//        }\r\n    }\r\n\r\n\r\n    public void showOnScreen(Window parent) {\r\n        if (getWidth() == 0) {\r\n            return;\r\n        }\r\n//System.out.println(\"SIZE \" + getWidth() + \"x\" + getHeight() + \"     \" + getLocation());\r\n        GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();\r\n        GraphicsDevice[] devices = env.getScreenDevices();\r\n        final GraphicsDevice frameDevice = parent.getGraphicsConfiguration().getDevice();\r\n        for (GraphicsDevice graphicsDevice : devices) {\r\n//System.out.println(frameDevice.getIDstring() + \" ' \" + graphicsDevice.getIDstring());\r\n            if (frameDevice.equals(graphicsDevice) || frameDevice.getIDstring().equals(graphicsDevice.getIDstring())) {\r\n                final Rectangle monitorBounds = graphicsDevice.getDefaultConfiguration().getBounds();\r\n                final int x = monitorBounds.x + (monitorBounds.width - getWidth()) /2;\r\n                final int y = monitorBounds.y + (monitorBounds.height - getHeight()) / 2;\r\n//System.out.println(\"  \" + monitorBounds.width + 'x' + monitorBounds.height + \"    \" + monitorBounds.x + ' ' + monitorBounds.y);\r\n                setLocation(x, y);\r\n            }\r\n        }\r\n    }\r\n\r\n    private void init(Component locationRelativeComp) {\r\n        this.locationRelativeComp = locationRelativeComp;\r\n        setLocationRelativeTo(locationRelativeComp);\r\n\r\n        initLookAndFeel();\r\n\r\n        JPanel contentPane = (JPanel)getContentPane();\r\n        contentPane.setBorder(BORDER);\r\n        setResizable(true);\r\n\r\n        // Important: dispose (release resources) window on close, default is HIDE_ON_CLOSE\r\n        setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r\n\t\r\n        // Catch escape key presses and have them close the dialog by mapping the escape keystroke to a custom dispose Action\r\n        InputMap inputMap = contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);\r\n        ActionMap actionMap = contentPane.getActionMap();\r\n        AbstractAction disposeAction = new AbstractAction() {\r\n            public void actionPerformed(ActionEvent e){\r\n                if (keyboardDisposalEnabled) {\r\n                    cancel();\r\n                }\r\n            }\r\n        };\r\n\t\r\n        // Maps the dispose action to the 'Escape' keystroke\r\n        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), CUSTOM_DISPOSE_EVENT);\r\n        actionMap.put(CUSTOM_DISPOSE_EVENT, disposeAction);\r\n\t\t\r\n        // Maps the dispose action to the 'Apple+W' keystroke under Mac OS X\r\n        if (OsFamily.MAC_OS_X.isCurrent()) {\r\n            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.META_DOWN_MASK), CUSTOM_DISPOSE_EVENT);\r\n        }\r\n\r\n        // Under Windows, Alt+F4 automatically disposes the dialog, nothing to do\r\n    }\r\n\r\n    /**\r\n     * Method called when the user has canceled through the escape key.\r\n     * <p>\r\n     * This method is equivalent to a call to {@link #dispose()}. It's meant to be\r\n     * overridden by those implementations of <code>FocusDialog</code> that need to init\r\n     * code before canceling the dialog.\r\n     */\r\n    public void cancel() {\r\n        dispose();\r\n    }\r\n\r\n\r\n    @Override\r\n    public void dispose() {\r\n        try {\r\n            WindowsStorage.getInstance().put(this, storageSuffix);\r\n            saveState();\r\n        } catch (Throwable t) {\r\n            t.printStackTrace();\r\n        }\r\n        super.dispose();\r\n\r\n        // fixed issue: return to main frame form FocusDialog\r\n        if ((ownerFocusedComponent instanceof JRootPane && getOwner() instanceof JFrame)) {\r\n            ownerFocusedComponent = null;\r\n        }\r\n\r\n        FocusRequester.requestFocus(ownerFocusedComponent != null ? ownerFocusedComponent : getOwner());\r\n    }\r\n\r\n    /**\r\n     * Sets the component that will receive focus once this dialog has been made visible.\r\n     *\r\n     * @param initialFocusComponent the component that will receive focus once this dialog has been made visible, if\r\n     * null, the first component in the dialog will receive focus.\r\n     */\r\n    public void setInitialFocusComponent(JComponent initialFocusComponent) {\r\n        this.initialFocusComponent = initialFocusComponent;\r\n\r\n        if (initialFocusComponent == null) {\r\n            removeWindowListener(this);\r\n        } else {\r\n            addWindowListener(this);\r\n        }\r\n    }\r\n\t\r\n\t\r\n    /**\r\n     * Sets a maximum width and height for this dialog.\r\n     */\r\n    @Override\r\n    public void setMaximumSize(Dimension dimension) {\r\n        this.maximumDimension = dimension;\r\n    }\r\n\r\n    /**\r\n     * Sets a minimum width and height for this dialog.\r\n     */\r\n    @Override\r\n    public void setMinimumSize(Dimension dimension) {\r\n        this.minimumDimension = dimension;\r\n    }\r\n\r\n\r\n    /**\r\n     * Specifies whether this dialog can be automatically disposed using the 'Escape' key and 'Apple+W' under Mac OS X.\r\n     * If enabled, {@link #dispose()} will be called when one of those keystrokes is pressed from any component\r\n     * within this dialog.\r\n     *\r\n     * @param enabled true to enable automatic keyboard disposal, false to disable it\r\n     */\r\n    protected void setKeyboardDisposalEnabled(boolean enabled) {\r\n        this.keyboardDisposalEnabled = enabled;\r\n    }\r\n\r\n\r\n    /**\r\n     * Overrides Window.pack() to take into account minimum and maximum dialog size (if specified).\r\n     */\r\n    @Override\r\n    public void pack()  {\r\n        super.pack();\r\n\r\n        if (maximumDimension != null) {\r\n            DialogToolkit.fitToMaxDimension(this, maximumDimension);\r\n        } else {\r\n            super.setMaximumSize(getSize());\r\n            DialogToolkit.fitToScreen(this);\r\n        }\r\n\r\n        if (minimumDimension != null) {\r\n            DialogToolkit.fitToMinDimension(this, minimumDimension);\r\n        } else {\r\n            super.setMinimumSize(getSize());\r\n        }\r\n    }\r\n\r\n    protected void packDialog() {\r\n        super.pack();\r\n    }\r\n\r\n    protected void setMinimumSizeDialog(Dimension d) {\r\n        minimumDimension = d;\r\n        super.setMinimumSize(d);\r\n    }\r\n\r\n    protected void setMaximumSizeDialog(Dimension d) {\r\n        maximumDimension = d;\r\n        super.setMaximumSize(d);\r\n    }\r\n\r\n\t\r\n    /**\r\n     * Packs this dialog, makes it non-resizable and visible.\r\n     */\r\n    public void showDialog() {\r\n        if (!WindowsStorage.getInstance().init(this, storageSuffix, storeSizes)) {\r\n            pack();\r\n            if (locationRelativeComp == null) {\r\n                DialogToolkit.centerOnScreen(this);\r\n            } else {\r\n                final int x = locationRelativeComp.getX() + (locationRelativeComp.getWidth() - getWidth()) / 2;\r\n                final int y = locationRelativeComp.getY() + (locationRelativeComp.getHeight() - getHeight()) / 2;\r\n                setLocation(x, y);\r\n            }\r\n        }\r\n        SwingUtilities.invokeLater(this::toFront);\r\n        setVisible(true);\r\n    }\r\n\r\n\r\n    /**\r\n     * Return <code>true</code> if the dialog has been activated (see WindowListener.windowActivated()).\r\n     *\r\n     * @return <code>true</code> if the dialog has been activated\r\n     */\r\n    public boolean isActivated() {\r\n        return firstTimeActivated;\r\n    }\r\n\r\n\r\n    @Override\r\n    public void windowOpened(WindowEvent e) {\r\n    }\r\n\r\n    @Override\r\n    public void windowActivated(WindowEvent e) {\r\n        // (this method is called each time the dialog is activated)\r\n        if (!firstTimeActivated && initialFocusComponent != null) {\r\n            LOGGER.trace(\"requesting focus on initial focus component\");\r\n\r\n            // First try using requestFocusInWindow() which is preferred over requestFocus(). If it fails\r\n            // (returns false), call requestFocus:\r\n            // \"The focus behavior of this method can be implemented uniformly across platforms, and thus developers are\r\n            // strongly encouraged to use this method over requestFocus when possible. Code which relies on requestFocus\r\n            // may exhibit different focus behavior on different platforms.\"\r\n            if (!initialFocusComponent.requestFocusInWindow()) {\r\n                LOGGER.trace(\"requestFocusInWindow failed, calling requestFocus\");\r\n                FocusRequester.requestFocus(initialFocusComponent);\r\n            }\r\n\r\n            firstTimeActivated = true;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void windowClosing(WindowEvent e) {\r\n    }\r\n\r\n    @Override\r\n    public void windowClosed(WindowEvent e) {\r\n    }\r\n\r\n    @Override\r\n    public void windowDeactivated(WindowEvent e) {\r\n    }\r\n\r\n    @Override\r\n    public void windowIconified(WindowEvent e) {\r\n    }\r\n\r\n    @Override\r\n    public void windowDeiconified(WindowEvent e) {\r\n    }\r\n\r\n    protected void saveState() {\r\n\r\n    }\r\n\r\n    public void setStorageSuffix(String storageSuffix) {\r\n        this.storageSuffix = storageSuffix;\r\n    }\r\n\r\n    public void setStoreSizes(boolean storeSizes) {\r\n        this.storeSizes = storeSizes;\r\n    }\r\n\r\n    public FocusDialog returnFocusTo(Component c) {\r\n        this.ownerFocusedComponent = c;\r\n        return this;\r\n    }\r\n\r\n    public Component getReturnFocusTo() {\r\n        return ownerFocusedComponent;\r\n    }\r\n\r\n\r\n    protected void fixHeight() {\r\n        addComponentListener(new ComponentAdapter() {\r\n            @Override\r\n            public void componentResized(ComponentEvent e) {\r\n                Dimension preferredSize = getPreferredSize();\r\n                int width = getWidth();\r\n                int minWidth = minimumDimension != null ? minimumDimension.width : preferredSize.width;\r\n                setSize(new Dimension(Math.max(width, minWidth), preferredSize.height));\r\n                super.componentResized(e);\r\n            }\r\n        });\r\n    }\r\n\r\n    protected static String i18n(String key, String... params) {\r\n        return Translator.get(key, params);\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/InformationDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog;\n\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.button.CollapseExpandButton;\nimport com.mucommander.ui.layout.InformationPane;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.text.FontUtils;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\n\n/**\n * This class provides static methods to display 'information' dialogs of different kinds:\n * {@link #ERROR_DIALOG_TYPE error}, {@link #INFORMATION_DIALOG_TYPE information}, {@link #WARNING_DIALOG_TYPE warning}\n * or {@link #QUESTION_DIALOG_TYPE question}.\n * <p>\n * While this class is very similar to {@link JOptionPane}, it extends the functionality by adding optional caption\n * message and exception details to the dialog. It also allows to use generic title and messages for certain\n * dialog types.\n * <p>\n * This class uses {@link InformationPane} to display the icon and the main and caption messages.\n *\n * @see InformationPane\n * @author Maxence Bernard\n */\npublic class InformationDialog {\n\n    /** Minimum dialog size */\n    private static final Dimension MIN_DIALOG_SIZE = new Dimension(360, 0);\n\n    /** Maximum dialog size */\n    private static final Dimension MAX_DIALOG_SIZE = new Dimension(480, 10000);\n\n    /** Error dialog type */\n    public static final int ERROR_DIALOG_TYPE = 1;\n\n    /** Information dialog type */\n    public static final int INFORMATION_DIALOG_TYPE = 2;\n\n    /** Warning dialog type */\n    public static final int WARNING_DIALOG_TYPE = 3;\n\n    /** Question dialog type */\n    public static final int QUESTION_DIALOG_TYPE = 4;\n\n\n    /**\n     * Brings up an error dialog with a generic localized title and message.\n     *\n     * @param parentComponent determines the <code>Frame</code> in which the dialog is displayed; if <code>null</code>,\n     */\n    public static void showErrorDialog(Component parentComponent) {\n        showErrorDialog(parentComponent, null, null, null, null);\n    }\n\n    /**\n     * Brings up an error dialog with the specified message and a generic localized title.\n     *\n     * @param parentComponent determines the <code>Frame</code> in which the dialog is displayed; if <code>null</code>,\n     * or if the parentComponent has no <code>Frame</code>, a default <code>Frame</code> is used\n     * @param message the error message to display in the dialog\n     */\n    public static void showErrorDialog(Component parentComponent, String message) {\n        showErrorDialog(parentComponent, null, message, null, null);\n    }\n\n    /**\n     * Brings up an error dialog with the specified title and message.\n     *\n     * @param parentComponent determines the <code>Frame</code> in which the dialog is displayed; if <code>null</code>,\n     * or if the parentComponent has no <code>Frame</code>, a default <code>Frame</code> is used\n     * @param title the dialog's title, <code>null</code> for a generic localized title.\n     * @param message the error message to display in the dialog, <code>null</code> for a generic localized message.\n     */\n    public static void showErrorDialog(Component parentComponent, String title, String message) {\n        showErrorDialog(parentComponent, title, message, null, null);\n    }\n\n    /**\n     * Brings up an error dialog with the specified title, main and caption messages.\n     *\n     * @param parentComponent determines the <code>Frame</code> in which the dialog is displayed; if <code>null</code>,\n     * or if the parentComponent has no <code>Frame</code>, a default <code>Frame</code> is used\n     * @param title the dialog's title, <code>null</code> for a generic localized title.\n     * @param message the error message to display in the dialog, <code>null</code> for a generic localized message.\n     * @param captionMessage the caption message to display underneath the error message, <code>null</code> for none.\n     */\n    public static void showErrorDialog(Component parentComponent, String title, String message, String captionMessage) {\n        showErrorDialog(parentComponent, title, message, captionMessage, null);\n    }\n\n    /**\n     * Brings up an error dialog with the specified title, main and caption messages, and stack trace of the specified\n     * exception inside an expandable panel.\n     *\n     * @param parentComponent determines the <code>Frame</code> in which the dialog is displayed; if <code>null</code>,\n     * or if the parentComponent has no <code>Frame</code>, a default <code>Frame</code> is used\n     * @param title the dialog's title, <code>null</code> for a generic localized title.\n     * @param message the error message to display in the dialog, <code>null</code> for a generic localized message.\n     * @param captionMessage the caption message to display underneath the error message, <code>null</code> for none.\n     * @param throwable exception for which to show the stack trace, <code>null</code> for none.\n     */\n    public static void showErrorDialog(Component parentComponent, String title, String message, String captionMessage, Throwable throwable) {\n        showDialog(ERROR_DIALOG_TYPE, parentComponent, title==null?Translator.get(\"error\"):title, message==null?Translator.get(\"generic_error\"):message, captionMessage, throwable);\n    }\n\n   /**\n     * Brings up a warning dialog with the specified message and a generic localized title.\n     *\n     * @param parentComponent determines the <code>Frame</code> in which the dialog is displayed; if <code>null</code>,\n     * or if the parentComponent has no <code>Frame</code>, a default <code>Frame</code> is used\n     * @param message the main message to display in the dialog\n     */\n    public static void showWarningDialog(Component parentComponent, String message) {\n        showWarningDialog(parentComponent, null, message, null);\n    }\n\n    /**\n     * Brings up a warning dialog with the specified title and message.\n     *\n     * @param parentComponent determines the <code>Frame</code> in which the dialog is displayed; if <code>null</code>,\n     * or if the parentComponent has no <code>Frame</code>, a default <code>Frame</code> is used\n     * @param title the dialog's title, <code>null</code> for a generic localized title.\n     * @param message the main message to display in the dialog.\n     */\n    public static void showWarningDialog(Component parentComponent, String title, String message) {\n        showWarningDialog(parentComponent, title, message, null);\n    }\n\n    /**\n     * Brings up a warning dialog with the specified title, main and caption messages.\n     *\n     * @param parentComponent determines the <code>Frame</code> in which the dialog is displayed; if <code>null</code>,\n     * or if the parentComponent has no <code>Frame</code>, a default <code>Frame</code> is used\n     * @param title the dialog's title, <code>null</code> for a generic localized title.\n     * @param message the main message to display in the dialog, <code>null</code> for a generic localized message.\n     * @param captionMessage the caption message to display underneath the main message, <code>null</code> for none.\n     */\n    public static void showWarningDialog(Component parentComponent, String title, String message, String captionMessage) {\n        showDialog(WARNING_DIALOG_TYPE, parentComponent, title==null?Translator.get(\"warning\"):title, message, captionMessage, null);\n    }\n\n    /**\n     * Brings up a dialog of the specified type and with the specified title, main and caption messages, and stack trace\n     * of the specified exception inside an expandable panel.\n     *\n     * @param dialogType type of dialog, see constant fields for allow values.\n     * @param parentComponent determines the <code>Frame</code> in which the dialog is displayed; if <code>null</code>,\n     * or if the parentComponent has no <code>Frame</code>, a default <code>Frame</code> is used\n     * @param title the dialog's title, <code>null</code> for a generic localized title, if one exists for the\n     * dialog type.\n     * @param message the main message to display in the dialog, <code>null</code> for a generic localized message, if\n     * one exists for the dialog type.\n     * @param captionMessage the caption message to display underneath the main message, <code>null</code> for none.\n     * @param throwable exception for which to show the stack trace, <code>null</code> for none.\n     */\n    public static void showDialog(int dialogType, Component parentComponent, String title, String message, String captionMessage, Throwable throwable) {\n        Window owner = DialogToolkit.getWindowForComponent(parentComponent);\n\n        final FocusDialog dialog;\n        if(owner instanceof Frame)\n            dialog = new FocusDialog((Frame)owner, title, parentComponent);\n        else\n            dialog = new FocusDialog((Dialog)owner, title, parentComponent);\n\n        dialog.setMinimumSize(MIN_DIALOG_SIZE);\n        dialog.setMaximumSize(MAX_DIALOG_SIZE);\n\n        YBoxPanel mainPanel = new YBoxPanel();\n\n        InformationPane informationPane = new InformationPane(message, captionMessage, captionMessage==null?Font.PLAIN:Font.BOLD, getInformationPaneIconId(dialogType));\n        mainPanel.add(informationPane);\n        mainPanel.addSpace(10);\n\n        JButton okButton = new JButton(Translator.get(\"ok\"));\n        JPanel okPanel = DialogToolkit.createOKPanel(okButton, dialog.getRootPane(), e -> dialog.dispose());\n\n        JPanel buttonPanel = new JPanel();\n        buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));\n\n        mainPanel.add(buttonPanel);\n\n        // Show the exception's stack trace in an expandable/collapsible panel\n        if(throwable !=null) {\n            JTextArea detailsArea = new JTextArea();\n            detailsArea.setEditable(false);\n\n            // Get the stack trace as a string\n            StringWriter sw = new StringWriter();\n            PrintWriter pw = new PrintWriter(sw, true);\n            throwable.printStackTrace(pw);\n            pw.close();\n            // Fill the area with the stack trace.\n            // Tabs by space characters to reduce the text's width\n            detailsArea.setText(sw.toString().replace('\\t', ' '));\n\n            FontUtils.makeMini(detailsArea);\n\n            JScrollPane scrollPane = new JScrollPane(detailsArea, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);\n            buttonPanel.add(new CollapseExpandButton(Translator.get(\"details\"), scrollPane, false));\n            mainPanel.add(scrollPane);\n        }\n\n        buttonPanel.add(Box.createVerticalGlue());\n        buttonPanel.add(okPanel);\n        \n        dialog.getContentPane().add(mainPanel);\n\n        // Give initial keyboard focus to the 'OK' button\n        dialog.setInitialFocusComponent(okButton);\n\n        // Call dispose() when dialog is closed\n        dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);\n\n        dialog.showDialog();\n    }\n\n    /**\n     * Returns an {@link InformationPane} icon id corresponding to the given dialog type.\n     *\n     * @param dialogType type of dialog, see constant fields for allow values.\n     * @return an {@link InformationPane} icon id corresponding to the given dialog type.\n     */\n    private static int getInformationPaneIconId(int dialogType) {\n        int iconId;\n        switch(dialogType) {\n            case ERROR_DIALOG_TYPE:\n                iconId = InformationPane.ERROR_ICON;\n                break;\n            case INFORMATION_DIALOG_TYPE:\n                iconId = InformationPane.INFORMATION_ICON;\n                break;\n            case WARNING_DIALOG_TYPE:\n                iconId = InformationPane.WARNING_ICON;\n                break;\n            case QUESTION_DIALOG_TYPE:\n                iconId = InformationPane.QUESTION_ICON;\n                break;\n            default:\n                iconId = InformationPane.ERROR_ICON;\n                break;\n        }\n\n        return iconId;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/PasswordDialog.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.dialog;\n\nimport com.mucommander.ui.button.ButtonChoicePanel;\nimport com.mucommander.ui.layout.YBoxPanel;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\n\n/**\n * @author Oleg Trifonov\n * Created on 28/10/16.\n */\npublic class PasswordDialog extends FocusDialog implements ActionListener {\n\n    private JPasswordField edtPassword;\n    private JButton btnOk;\n    private JButton btnCancel;\n    private volatile Boolean canceled;\n\n    /** Minimum dialog size */\n    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(160, 100);\n\n    /** Maximum dialog size */\n    private final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(1024, 500);\n\n\n    private PasswordDialog(Frame owner, String title, Component locationRelativeComp) {\n        super(owner, title, locationRelativeComp);\n        init();\n    }\n\n    public PasswordDialog(String title) {\n        this(null,title, null);\n    }\n\n\n    private void init() {\n        // Sets minimum and maximum dimensions for this dialog\n        setMinimumSize(MINIMUM_DIALOG_DIMENSION);\n        setMaximumSize(MAXIMUM_DIALOG_DIMENSION);\n\n        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);\n\n        YBoxPanel mainPanel = new YBoxPanel();\n        edtPassword = new JPasswordField(10);\n        mainPanel.add(new JLabel(i18n(\"password\")));\n        mainPanel.addSpace(5);\n        mainPanel.add(edtPassword);\n        mainPanel.addSpace(10);\n\n        btnOk = new JButton(i18n(\"ok\"));\n        btnOk.addActionListener(this);\n        btnCancel = new JButton(i18n(\"cancel\"));\n        btnCancel.addActionListener(this);\n        JButton[] buttons = new JButton[]{btnOk, btnCancel};\n\n        setInitialFocusComponent(edtPassword);\n        mainPanel.add(new ButtonChoicePanel(buttons, 2, getRootPane()));\n        getContentPane().add(mainPanel, BorderLayout.NORTH);\n\n        requestFocus();\n        edtPassword.requestFocus();\n        pack();\n        fixHeight();\n    }\n\n    public String getPassword() {\n        SwingUtilities.invokeLater(this::showDialog);\n        while (canceled == null) {\n            try {\n                Thread.sleep(100);\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n        }\n        SwingUtilities.invokeLater(this::dispose);\n\n        return canceled ? null : new String(edtPassword.getPassword());\n    }\n\n    @Override\n    public void cancel() {\n        canceled = true;\n        super.cancel();\n    }\n\n    @Override\n    public void actionPerformed(ActionEvent e) {\n        if (e.getSource() == btnOk) {\n            canceled = false;\n            dispose();\n        } else if (e.getSource() == btnCancel) {\n            canceled = true;\n            dispose();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/QuestionDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.dialog;\n\nimport com.mucommander.job.ui.DialogResult;\nimport com.mucommander.ui.button.ButtonChoicePanel;\nimport com.mucommander.ui.layout.InformationPane;\nimport com.mucommander.ui.layout.YBoxPanel;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\n\n\n/**\n * \n *\n * @author Maxence Bernard\n */\npublic class QuestionDialog extends FocusDialog implements ActionListener, DialogResult {\n\t\n    /** Dialog owner */\n    private JButton[] buttons;\n    private int[] actionValues;\n\t\n    private int retValue = DIALOG_DISPOSED_ACTION;\n\n    private YBoxPanel mainPanel;\n\n    /** This value is returned by {@link #getActionValue()} when the dialog has been disposed without the user\n     * selecting a custom action */\n    private final static int DIALOG_DISPOSED_ACTION = -1;\n\n    /** Minimum dialog size */\n    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(360, 0);\n\n    /** Maximum dialog size */\n    private final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(1024, 10000);\n\n\n    /**\n     *\n     * @param actionValues values for actions, each of them must be &gt;= 0\n     */\n    public QuestionDialog(Frame owner, String title, String msg, Component locationRelative, String[] actionText, int[] actionValues, int maxNbCols) {\n        super(owner, title, locationRelative);\n        init(new InformationPane(msg, null, Font.PLAIN, InformationPane.QUESTION_ICON), actionText, actionValues, maxNbCols);\n    }\n\n    /**\n     *\n     * @param actionValues values for actions, each of them must be &gt;= 0\n     */\n    public QuestionDialog(Dialog owner, String title, String msg, Component locationRelative, String[] actionText, int[] actionValues, int maxNbCols) {\n        super(owner, title, locationRelative);\n        init(new InformationPane(msg, null, Font.PLAIN, InformationPane.QUESTION_ICON), actionText, actionValues, maxNbCols);\n    }\n\n    /**\n     *\n     * @param actionValues values for actions, each of them must be &gt;= 0\n     */\n    public QuestionDialog(Frame owner, String title, Component msgComp, Component locationRelative, String[] actionText, int[] actionValues, int maxNbCols) {\n        super(owner, title, locationRelative);\n        init(msgComp, actionText, actionValues, maxNbCols);\n    }\n\n    /**\n     *\n     * @param actionValues values for actions, each of them must be &gt;= 0\n     */\n    public QuestionDialog(Dialog owner, String title, Component msgComp, Component locationRelative, String[] actionText, int[] actionValues, int maxNbCols) {\n        super(owner, title, locationRelative);\n        init(msgComp, actionText, actionValues, maxNbCols);\n    }\n\n\t\n    protected QuestionDialog(Frame owner, String title, Component locationRelative) {\n        super(owner, title, locationRelative);\n    }\n\n    protected QuestionDialog(Dialog owner, String title, Component locationRelative) {\n        super(owner, title, locationRelative);\n    }\n\n\n    protected void init(Component comp, String[] actionText, int[] actionValues, int maxNbCols) {\n        this.actionValues = actionValues;\n\n        // Sets minimum and maximum dimensions for this dialog\n        setMinimumSize(MINIMUM_DIALOG_DIMENSION);\n        setMaximumSize(MAXIMUM_DIALOG_DIMENSION);\n\n        mainPanel = new YBoxPanel();\n\n        if (comp != null) {\n            mainPanel.addSpace(5);\n            mainPanel.add(comp);\n            mainPanel.addSpace(10);\n        }\n\n        int nbButtons = actionText.length;\n        buttons = new JButton[nbButtons];\n\n        for (int i = 0; i < nbButtons; i++) {\n            String text = actionText[i];\n            buttons[i] = new JButton(text);\n            buttons[i].addActionListener(this);\n        }\n\n        setInitialFocusComponent(buttons[0]);\n        mainPanel.add(new ButtonChoicePanel(buttons, maxNbCols, getRootPane()));\n\n        getContentPane().add(mainPanel, BorderLayout.NORTH);\n    }\n\t\n\n    /**\n     * Adds a component to this dialog, under the buttons panel.\n     *\n     * @param comp the component to add\n     */\n    protected void addComponent(JComponent comp) {\n        mainPanel.add(comp);\n    }\n\t\n\t\n    /**\n     * Shows this dialog, waits for an action/button to be selected and returns the selected action's value.\n     * The dialog may be closed without the user selecting a custom action. In this case,\n     * {@link #DIALOG_DISPOSED_ACTION} (-1) will be returned.\n     * @return action value\n     */\n    public int getActionValue() {\n//        // Beep !\n//        Toolkit.getDefaultToolkit().beep();\n        // Returns only when this dialog has been disposed\n        // by actionPerformed or if window has been closed (-1)\n        super.showDialog();\n        return retValue;\n    }\n\n\n\n    @Override\n    public void actionPerformed(ActionEvent e) {\n        Object source = e.getSource();\n\t\t\n        for (int i = 0; i < buttons.length; i++)\n            if (buttons[i] == source) {\n                retValue = actionValues[i];\n                break;\n            }\n\t\n        dispose();\n    }\n\n    @Override\n    public Object getUserInput() {\n        super.showDialog();\n        return retValue;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/about/AboutDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.about;\n\nimport java.awt.BorderLayout;\nimport java.awt.Component;\nimport java.awt.Container;\nimport java.awt.Dimension;\nimport java.awt.FlowLayout;\nimport java.awt.Font;\nimport java.awt.Insets;\nimport java.awt.Point;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.nio.charset.Charset;\nimport java.util.Locale;\n\nimport javax.swing.JButton;\nimport javax.swing.JLabel;\nimport javax.swing.JPanel;\nimport javax.swing.JScrollPane;\nimport javax.swing.JTextPane;\nimport javax.swing.SwingUtilities;\nimport javax.swing.text.BadLocationException;\nimport javax.swing.text.Style;\nimport javax.swing.text.StyleConstants;\nimport javax.swing.text.StyleContext;\nimport javax.swing.text.StyledDocument;\n\nimport com.mucommander.RuntimeConstants;\nimport com.mucommander.desktop.DesktopManager;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.GoToWebsiteAction;\nimport com.mucommander.ui.action.impl.ShowAboutAction;\nimport com.mucommander.ui.dialog.FocusDialog;\nimport com.mucommander.ui.icon.IconManager;\nimport com.mucommander.ui.layout.FluentPanel;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.theme.Theme;\nimport com.mucommander.ui.theme.ThemeManager;\n\n/**\n * Dialog displaying information about muCommander.\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic class AboutDialog extends FocusDialog implements ActionListener {\n    /** Style for normal text. */\n    private static final String STYLE_NORMAL  = \"normal\";\n    /** Style for headers. */\n    private static final String STYLE_HEADER  = \"header\";\n    /** Style for URLs. */\n    private static final String STYLE_URL     = \"url\";\n    /** Style for an item's details. */\n    private static final String STYLE_DETAILS = \"details\";\n    /** Style for a section title. */\n    private static final String STYLE_TITLE   = \"title\";\n    /** Line break string. */\n    private static final String LINE_BREAK    = System.lineSeparator();\n\n    /** Button that closes the dialog. */\n    private JButton btnOk;\n    /** Button that opens trolCommander's homepage in a browser. */\n    private JButton btnHome;\n    /** Button that opens the trolCommander's license. */\n    private JButton btnLicense;\n    /** Panel in which all the textual information is displayed. */\n    private JScrollPane textPanel;\n\n\n\n    /**\n     * Creates a new AboutDialog.\n     * @param mainFrame frame this dialog is relative to.\n     */\n    public AboutDialog(MainFrame mainFrame) {\n        super(mainFrame.getJFrame(), ActionProperties.getActionLabel(ShowAboutAction.Descriptor.ACTION_ID), mainFrame.getJFrame());\n\n        // Initializes the dialog's content.\n        Container contentPane = getContentPane();\n        contentPane.add(createIconPanel(), BorderLayout.WEST);\n        contentPane.add(createCreditsPanel(), BorderLayout.EAST);\n        setResizable(false);\n\n        // Makes sure the scroll pane is properly initialized.\n        SwingUtilities.invokeLater(() -> textPanel.getViewport().setViewPosition(new Point(0, 0)));\n\n        pack();\n\n        // Makes OK the default action.\n        setInitialFocusComponent(btnOk);\n        getRootPane().setDefaultButton(btnOk);\n    }\n\n    /**\n     * Creates the panel that contains all the about box's text.\n     */\n    private JScrollPane createCreditsPanel() {\n        JTextPane text = new JTextPane();\n        StyledDocument doc  = text.getStyledDocument();\n\n        text.setBackground(ThemeManager.getCurrentColor(Theme.FILE_TABLE_BACKGROUND_COLOR));\n\n        setStyles(doc);\n        text.setEditable(false);\n        try {\n            // Team.\n            insertTitle(doc,          \"The trolCommander team\");\n\n            // Core developers.\n            insertHeader(doc,         \"Developers and contributors\");\n            insertNormalString(doc,   \"Maxence Bernard\");\n            insertNormalString(doc,   \"Arik Hadas\");\n            insertNormalString(doc,   \"Mariusz Jakubowski\");\n            insertNormalString(doc,   \"Nicolas Rinaudo\");\n            insertNormalString(doc,   \"Oleg Trifonov\");\n            insertNormalString(doc,   \"Ondřej Zima\");\n            insertNormalString(doc,   \"Martin Kortkamp\");\n//            insertLineBreak(doc);\n\n            // Contributors.\n  //          insertHeader(doc,         \"Contributors\");\n            insertNormalString(doc,   \"Ivan Baidakov\");\n            insertNormalString(doc,   \"Vassil Dichev\");\n            insertNormalString(doc,   \"Karel Klic\");\n            insertNormalString(doc,   \"David Kovar\");\n            insertNormalString(doc,   \"Joshua Lebo\");\n            insertNormalString(doc,   \"LeO\");\n            insertNormalString(doc,   \"Xavier Martin\");\n            insertNormalString(doc,   \"Alejandro Scandroli\");\n            insertNormalString(doc,   \"Alexander Yerenkow\");\n            insertNormalString(doc,   \"Johann Schmitz\");\n            insertLineBreak(doc);\n\n            // QA\n            insertHeader(doc,         \"QA\");\n            insertNormalString(doc,   \"Joshua Lebo\");\n            insertLineBreak(doc);\n\n            // Translators.\n            insertHeader(doc,         \"Translators\");\n            insertDetailedString(doc, \"4X_Pro\",              \"Russian\");\n            insertDetailedString(doc, \"Roberto Angeletti\",   \"Italian\");\n            insertDetailedString(doc, \"Tamás Balogh-Walder\", \"Hungarian\");\n            insertDetailedString(doc, \"Mykola Bilovus\",      \"Ukrainian\");\n            insertDetailedString(doc, \"ChArLoK_16\",          \"Arabic\");\n            insertDetailedString(doc, \"György Varga\",        \"Hungarian\");\n            insertDetailedString(doc, \"Frank Berger\",        \"German\");\n            insertDetailedString(doc, \"Tony Klüver\",         \"German\");\n            insertDetailedString(doc, \"Marcos Cobeña\",       \"Spanish\");\n            insertDetailedString(doc, \"Cristiano Duarte\",    \"Brazilian Portuguese\");\n            insertDetailedString(doc, \"Jakob Ekström\",       \"Swedish\");\n            insertDetailedString(doc, \"Catalin Hritcu\",      \"Romanian\");\n            insertDetailedString(doc, \"Kent Hsu\",            \"Traditional Chinese\");\n            insertDetailedString(doc, \"Jioh L. Jung\",        \"Korean\");\n            insertDetailedString(doc, \"Andrzej Kosiński\",    \"Polish\");\n            insertDetailedString(doc, \"Joze Kovacic\",        \"Slovenian\");\n            insertDetailedString(doc, \"Oleksandr Kovalchuk\", \"Ukrainian\");\n            insertDetailedString(doc, \"Pieter Kristensen\",   \"Dutch\");\n            insertDetailedString(doc, \"Ján Ľudvík\",          \"Slovak\");\n            insertDetailedString(doc, \"Jaromír Mára\",        \"Czech\");\n            insertDetailedString(doc, \"Xavi Miró\",           \"Spanish\");\n            insertDetailedString(doc, \"Evgeny Morozov\",      \"Russian\");\n            insertDetailedString(doc, \"Jonathan Murphy\",     \"British English\");\n            insertDetailedString(doc, \"Nardog\",              \"Japanese\");\n            insertDetailedString(doc, \"Jordi Plantalech\",    \"Catalan\");\n            insertDetailedString(doc, \"Alexey Sirotov\",      \"Russian\");\n            insertDetailedString(doc, \"Jeppe Toustrup\",      \"Danish\");\n            insertDetailedString(doc, \"Peter Vasko\",         \"Czech\");\n            insertDetailedString(doc, \"vboo\",                \"Belarusian\");\n            insertDetailedString(doc, \"whiteriver\",          \"Simplified Chinese\");\n            insertLineBreak(doc);\n\n            // Special thanks.\n            insertHeader(doc,         \"Special thanks\");\n            insertDetailedString(doc, \"Semyon Filippov\",    \"muCommander icon\");\n            insertDetailedString(doc, \"Stefano Perelli\",    \"Former muCommander icon\");\n            insertLineBreak(doc);\n            insertLineBreak(doc);\n\n            // Powered by.\n            insertTitle(doc,         \"Powered by\");\n\n            // External Libraries.\n            insertHeader(doc,         \"Libraries\");\n            insertDetailedUrl(doc,    \"Apache Commons\",      \"Apache License\",                       \"http://commons.apache.org\");\n            insertDetailedUrl(doc,    \"Apache Hadoop\",       \"Apache License\",                       \"http://hadoop.apache.org\");\n            insertDetailedUrl(doc,    \"Furbelow\",            \"LGPL\",                                 \"http://sourceforge.net/projects/furbelow\");\n            insertDetailedUrl(doc,    \"ICU4J\",               \"ICU License\",                          \"http://www.icu-project.org\");\n            insertDetailedUrl(doc,    \"J2SSH\",               \"LGPL\",                                 \"http://sourceforge.net/projects/sshtools\");\n            insertDetailedUrl(doc,    \"J7Zip\",               \"LGPL\",                                 \"http://sourceforge.net/projects/p7zip/\");\n            insertDetailedUrl(doc,    \"jCIFS\",               \"LGPL\",                                 \"http://jcifs.samba.org\");\n            insertDetailedUrl(doc,    \"JetS3t\",              \"Apache License\",                       \"http://jets3t.s3.amazonaws.com/index.html\");\n            insertDetailedUrl(doc,    \"JmDNS\",               \"LGPL\",                                 \"http://jmdns.sourceforge.net\");\n            insertDetailedUrl(doc,    \"JNA\",                 \"LGPL\",                                 \"http://jna.dev.java.net\");\n            insertDetailedUrl(doc,    \"JUnRar\",              \"Freeware\",                             \"https://github.com/edmund-wagner/junrar\");\n            insertDetailedUrl(doc,    \"Yanfs\",               \"BSD\",                                  \"http://yanfs.dev.java.net\");\n            insertDetailedUrl(doc,    \"RSyntaxTextArea\",     \"Modified BSD\",                         \"http://fifesoft.com/rsyntaxtextarea\");\n            insertDetailedUrl(doc,    \"ICEpdf\",              \"Apache License\",                       \"http://www.icesoft.org/java/home.jsf\");\n            insertDetailedUrl(doc,    \"image4j\",             \"LGPL\",                                 \"http://image4j.sourceforge.net\");\n            insertDetailedUrl(doc,    \"JediTerm\",            \"LGPL\",                                 \"https://github.com/JetBrains/jediterm\");\n            insertDetailedUrl(doc,    \"JADB\",                \"Apache License 2.0\",                   \"https://github.com/vidstige/jadb\");\n\n            insertDetailedUrl(doc,    \"Mark James' icons\",   \"Creative Commons Attribution License\", \"http://famfamfam.com\");\n            insertLineBreak(doc);\n\n            // External tools.\n            insertHeader(doc,         \"Tools\");\n            insertDetailedUrl(doc,    \"jdeb\",                \"Apache Software License\",              \"http://vafer.org/projects/jdeb/\");\n            insertDetailedUrl(doc,    \"Launch4j\",            \"GPL\",                                  \"http://launch4j.sourceforge.net\");\n            insertDetailedUrl(doc,    \"NSIS\",                \"zlib/libpng license\",                  \"http://nsis.sourceforge.net\");\n            insertDetailedUrl(doc,    \"p7zip\",               \"LGPL\",                                 \"http://p7zip.sourceforge.net\");\n            insertLineBreak(doc);\n            insertLineBreak(doc);\n\n            // Version information\n            insertTitle(doc,         \"Version information\");\n\n            // VM information.\n            insertHeader(doc,         \"trolCommander\");\n            insertNormalString(doc,   \"Version: \" + RuntimeConstants.VERSION);\n            insertNormalString(doc,   \"Build date: \" + getFormatedDate());\n            insertLineBreak(doc);\n\n            // VM information.\n            insertHeader(doc,         \"JVM\");\n            insertNormalString(doc,   \"Runtime version: \" + System.getProperty(\"java.version\"));\n            insertNormalString(doc,   \"VM name: \" + System.getProperty(\"java.vm.name\"));\n            insertNormalString(doc,   \"VM version: \" + System.getProperty(\"java.vm.version\"));\n            insertNormalString(doc,   \"VM vendor: \" + System.getProperty(\"java.vm.vendor\"));\n            insertLineBreak(doc);\n\n            // OS information.\n            insertHeader(doc,         \"OS\");\n            insertNormalString(doc,   \"Name: \" + System.getProperty(\"os.name\"));\n            insertNormalString(doc,   \"Version: \" + System.getProperty(\"os.version\"));\n            insertNormalString(doc,   \"Architecture: \" + System.getProperty(\"os.arch\"));\n            insertLineBreak(doc);\n\n            // Locale information.\n            Locale locale = Locale.getDefault();\n            insertHeader(doc,         \"Locale\");\n            insertNormalString(doc,   \"Language: \" + locale.getLanguage());\n            insertNormalString(doc,   \"Country: \" + locale.getCountry());\n            insertNormalString(doc,   \"Encoding: \" + Charset.defaultCharset().displayName());\n        } catch(Exception ignore) {}\n\n        textPanel = new JScrollPane(text, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);\n        textPanel.getViewport().setPreferredSize(new Dimension((int)text.getPreferredSize().getWidth(), 350));\n\n        return textPanel;\n    }\n\n    /**\n     * Creates the about box's left panel.\n     */\n\tprivate JPanel createIconPanel() {\n\n\t\t// Makes sure the panel's a bit roomier than the default configuration.\n\t\treturn new FluentPanel(new BorderLayout()) {\n\t\t\t@Override\n\t\t\tpublic Insets getInsets() {\n\t\t\t\treturn new Insets(10, 10, 0, 10);\n\t\t\t}\n\t\t}.add(new FluentPanel(new BorderLayout())\n                  .add(new JLabel(IconManager.getIcon(IconManager.IconSet.TROLCOMMANDER, \"icon128_24.png\")), BorderLayout.NORTH)\n                  .add(new FluentPanel(new FlowLayout(FlowLayout.CENTER)).add(createAppString()), BorderLayout.CENTER)\n  \t\t\t\t  .add(new FluentPanel(new FlowLayout(FlowLayout.CENTER)).add(createCopyright()), BorderLayout.SOUTH),\n\n              BorderLayout.NORTH)\n\t\t .add(new FluentPanel(new BorderLayout())\n                         .add(createHomeComponent(), BorderLayout.NORTH)\n                         .add(createLicenseButton(), BorderLayout.CENTER)\n                         .add(createOkButton(), BorderLayout.SOUTH),\n                 BorderLayout.SOUTH);\n\t}\n\n    private JLabel createCopyright() {\n        return new JLabel(\"<html>©\" + RuntimeConstants.COPYRIGHT+ \" Oleg Trifonov<p/><br/>Based on <b>muCommander</b><br>\");\n    }\n\n\tprivate JLabel createAppString() {\n\t\treturn createBoldLabel(RuntimeConstants.APP_STRING);\n\t}\n\n\tprivate Component createHomeComponent() {\n\t\treturn DesktopManager.canBrowse() ?\n      \t\t  createHomeButton()\n      \t\t  : new FluentPanel(new FlowLayout(FlowLayout.CENTER)).add(new JLabel(RuntimeConstants.HOMEPAGE_URL));\n\t}\n\n\tprivate JButton createHomeButton() {\n\t\tbtnHome = new JButton(ActionProperties.getActionLabel(GoToWebsiteAction.Descriptor.ACTION_ID));\n\t\tbtnHome.addActionListener(this);\n\t\treturn btnHome;\n\t}\n\n\tprivate JButton createLicenseButton() {\n\t\tbtnLicense = new JButton(i18n(\"license\"));\n\t\tbtnLicense.addActionListener(this);\n\t\treturn btnLicense;\n\t}\n\n\tprivate JButton createOkButton() {\n\t\tbtnOk = new JButton(i18n(\"ok\"));\n\t\tbtnOk.addActionListener(this);\n\t\treturn btnOk;\n\t}\n\n\n\n    /**\n     * Creates different styles in the specified <code>StyledDocument</code>\n     * @param doc document in which to create the styles.\n     */\n    private static void setStyles(StyledDocument doc) {\n        Style master = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);\n        Font font = ThemeManager.getCurrentFont(Theme.FILE_TABLE_FONT);\n\n        // Normal style.\n        master = doc.addStyle(STYLE_NORMAL, master);\n        StyleConstants.setFontFamily(master, font.getFamily());\n        StyleConstants.setFontSize(master, font.getSize());\n        StyleConstants.setForeground(master, ThemeManager.getCurrentColor(Theme.FILE_FOREGROUND_COLOR));\n        StyleConstants.setBackground(master, ThemeManager.getCurrentColor(Theme.FILE_TABLE_BACKGROUND_COLOR));\n        StyleConstants.setLeftIndent(master, 10);\n        StyleConstants.setRightIndent(master, 10);\n        StyleConstants.setLineSpacing(master, (float)0.2);\n        doc.setParagraphAttributes(0, 0, master, true);\n\n        // Header style.\n        Style currentStyle = doc.addStyle(STYLE_HEADER, master);\n        StyleConstants.setBold(currentStyle, true);\n        StyleConstants.setFontSize(currentStyle, font.getSize() + 2);\n        StyleConstants.setForeground(currentStyle, ThemeManager.getCurrentColor(Theme.FOLDER_FOREGROUND_COLOR));\n        StyleConstants.setBackground(currentStyle, ThemeManager.getCurrentColor(Theme.FILE_TABLE_BACKGROUND_COLOR));\n\n        // Title style.\n        currentStyle = doc.addStyle(STYLE_TITLE, currentStyle);\n        StyleConstants.setAlignment(currentStyle, StyleConstants.ALIGN_CENTER);\n        StyleConstants.setForeground(currentStyle, ThemeManager.getCurrentColor(Theme.ARCHIVE_FOREGROUND_COLOR));\n        StyleConstants.setBackground(currentStyle, ThemeManager.getCurrentColor(Theme.FILE_TABLE_BACKGROUND_COLOR));\n\n        // Details style.\n        currentStyle = doc.addStyle(STYLE_DETAILS, master);\n        StyleConstants.setForeground(currentStyle, ThemeManager.getCurrentColor(Theme.HIDDEN_FILE_FOREGROUND_COLOR));\n        StyleConstants.setBackground(currentStyle, ThemeManager.getCurrentColor(Theme.FILE_TABLE_BACKGROUND_COLOR));\n\n        // URL style.\n        currentStyle = doc.addStyle(STYLE_URL, master);\n        StyleConstants.setForeground(currentStyle, ThemeManager.getCurrentColor(Theme.SYMLINK_FOREGROUND_COLOR));\n        StyleConstants.setBackground(currentStyle, ThemeManager.getCurrentColor(Theme.FILE_TABLE_BACKGROUND_COLOR));\n        StyleConstants.setUnderline(currentStyle, true);\n    }\n\n    /**\n     * Inserts the specified header in the specified document.\n     * @param  doc                  document in which to insert the text.\n     * @param  string               text to insert.\n     * @throws BadLocationException thrown if something wrong happened to the document.\n     */\n    private static void insertHeader(StyledDocument doc, String string) throws BadLocationException {\n        doc.insertString(doc.getLength(), string + LINE_BREAK, doc.getStyle(STYLE_HEADER));\n    }\n\n    /**\n     * Inserts the specified string in the specified document.\n     * @param  doc                  document in which to insert the text.\n     * @param  string               text to insert.\n     * @throws BadLocationException thrown if something wrong happened to the document.\n     */\n    private static void insertNormalString(StyledDocument doc, String string) throws BadLocationException {\n        doc.insertString(doc.getLength(), string + LINE_BREAK, doc.getStyle(STYLE_NORMAL));\n    }\n\n    /**\n     * Inserts the specified string and details in the specified document.\n     * @param  doc                  document in which to insert the text.\n     * @param  string               text to insert.\n     * @param  details              details that will be added to the text.\n     * @throws BadLocationException thrown if something wrong happened to the document.\n     */\n    private static void insertDetailedString(StyledDocument doc, String string, String details) throws BadLocationException {\n        doc.insertString(doc.getLength(), string + \" \", doc.getStyle(STYLE_NORMAL));\n        doc.insertString(doc.getLength(), \"(\" + details + \")\" + LINE_BREAK, doc.getStyle(STYLE_DETAILS));\n    }\n\n    /**\n     * Inserts the specified URL in the specified document.\n     * @param  doc                  document in which to insert the text.\n     * @param  url                  url to insert.\n     * @throws BadLocationException thrown if something wrong happened to the document.\n     */\n    private static void insertUrl(StyledDocument doc, String url) throws BadLocationException {\n        doc.insertString(doc.getLength(), \"    \", doc.getStyle(STYLE_NORMAL));\n        doc.insertString(doc.getLength(), url + LINE_BREAK, doc.getStyle(STYLE_URL));\n    }\n\n    /**\n     * Inserts a line break in the specified document.\n     * @param  doc                  document in which to insert the text.\n     * @throws BadLocationException thrown if something wrong happened to the document.\n     */\n    private static void insertLineBreak(StyledDocument doc) throws BadLocationException {\n        doc.insertString(doc.getLength(), LINE_BREAK, doc.getStyle(STYLE_NORMAL));\n    }\n\n    /**\n     * Inserts the specified string, details and URL in the specified document.\n     * @param  doc                  document in which to insert the text.\n     * @param  string               text to insert.\n     * @param  details              details that will be added to the text.\n     * @param  url                  url that will be added to the text.\n     * @throws BadLocationException thrown if something wrong happened to the document.\n     */\n    private static void insertDetailedUrl(StyledDocument doc, String string, String details, String url) throws BadLocationException {\n        insertDetailedString(doc, string, details);\n        insertUrl(doc, url);\n    }\n\n    /**\n     * Inserts the specified title in the specified document.\n     * @param  doc                  document in which to insert the text.\n     * @param  string               text to insert.\n     * @throws BadLocationException thrown if something wrong happened to the document.\n     */\n    private static void insertTitle(StyledDocument doc, String string) throws BadLocationException {\n        int pos = doc.getLength();\n        Style style = doc.getStyle(STYLE_TITLE);\n\n        string += LINE_BREAK;\n        doc.insertString(pos, string, style);\n        doc.setParagraphAttributes(pos, string.length(), style, true);\n        doc.setParagraphAttributes(doc.getLength(), 0, doc.getStyle(STYLE_NORMAL), true);\n\n        insertLineBreak(doc);\n    }\n\n\n\n    /**\n     * Reacts to validations of the <code>OK</code> or <code>home</code> buttons.\n     */\n    @Override\n    public void actionPerformed(ActionEvent e) {\n        if (e.getSource() == btnOk) {\n            dispose();\n        } else if(e.getSource() == btnHome) {\n            try {\n                DesktopManager.browse(new URI(RuntimeConstants.HOMEPAGE_URL).toURL());\n            } catch(IOException | URISyntaxException ignored) {} // Ignores errors here as there really isn't anything we can do.\n\n        } else if(e.getSource() == btnLicense) {\n            new LicenseDialog(this).showDialog();\n        }\n    }\n\n\n\n    /**\n     * Returns a formatted version of trolCommander's build date.\n     * @return a formatted version of trolCommander's build date.\n     */\n    private String getFormatedDate() {\n        return RuntimeConstants.BUILD_DATE.substring(0, 4) + '/' +\n                RuntimeConstants.BUILD_DATE.substring(4, 6) +\n                '/' +\n                RuntimeConstants.BUILD_DATE.substring(6, 8);\n    }\n\n    /**\n     * Creates a <code>JLabel</code> displaying the specified text using a bold font.\n     * @param  text text to display in the label.\n     * @return       a <code>JLabel</code> displaying the specified text using a bold font.\n     */\n    private static JLabel createBoldLabel(String text) {\n        JLabel label = new JLabel(text);\n        Font font  = label.getFont();\n        label.setFont(new Font(font.getFontName(), font.getStyle() | Font.BOLD, font.getSize()));\n\n        return label;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/about/LicenseDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.about;\n\nimport com.mucommander.RuntimeConstants;\nimport com.mucommander.ui.dialog.FocusDialog;\nimport com.mucommander.ui.theme.Theme;\nimport com.mucommander.ui.theme.ThemeManager;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.io.InputStreamReader;\n\n/**\n * Dialog used to display muCommander's license file.\n * @author Nicolas Rinaudo\n */\npublic class LicenseDialog extends FocusDialog implements ActionListener {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(LicenseDialog.class);\n\t\n    /** Button used to close the dialog. */\n    private JButton     okButton;\n    /** Panel in which to display the license. */\n    private JScrollPane licensePanel;\n\n\n\n    /**\n     * Creates a new license dialog centered on the specified window.\n     * @param dialog window on which to center the new dialog.\n     */\n    LicenseDialog(Dialog dialog) {\n        super(dialog, i18n(\"license\"), dialog);\n        initUI();\n    }\n\n    /**\n     * Creates a new license dialog centered on the specified window.\n     * @param frame window on which to center the new dialog.\n     */\n    public LicenseDialog(Frame frame) {\n        super(frame, i18n(\"license\"), frame);\n        initUI();\n    }\n\n    /**\n     * Creates the 'ok' button panel.\n     * @return the 'ok' button panel.\n     */\n    private JPanel createButtonPanel() {\n        JPanel panel = new JPanel();\n        panel.setLayout(new FlowLayout(FlowLayout.RIGHT));\n\n        okButton = new JButton(i18n(\"ok\"));\n        okButton.addActionListener(this);\n        panel.add(okButton);\n\n        return panel;\n    }\n\n    /**\n     * Creates the panel in which the license text is displayed.\n     * @return the panel in which the license text is displayed.\n     */\n    private JScrollPane createLicensePanel() {\n        JTextArea license = new JTextArea();\n        license.setEditable(false);\n\n        // Applies the file editor's theme to the license text.\n        license.setForeground(ThemeManager.getCurrentColor(Theme.EDITOR_FOREGROUND_COLOR));\n        license.setBackground(ThemeManager.getCurrentColor(Theme.EDITOR_BACKGROUND_COLOR));\n        license.setSelectedTextColor(ThemeManager.getCurrentColor(Theme.EDITOR_SELECTED_FOREGROUND_COLOR));\n        license.setSelectionColor(ThemeManager.getCurrentColor(Theme.EDITOR_SELECTED_BACKGROUND_COLOR));\n        license.setFont(ThemeManager.getCurrentFont(Theme.EDITOR_FONT));\n\n        license.setText(getLicenseText());\n\n        // Sets the scroll policy and preferred dimensions.\n        licensePanel = new JScrollPane(license, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);\n        licensePanel.getViewport().setPreferredSize(new Dimension((int)license.getPreferredSize().getWidth(), 400));\n\n        return licensePanel;\n    }\n\n    /**\n     * Initializes the dialog's UI.\n     */\n    private void initUI() {\n        Container contentPane = getContentPane();\n\n        // Adds the UI components.\n        contentPane.add(createLicensePanel(), BorderLayout.CENTER);\n        contentPane.add(createButtonPanel(), BorderLayout.SOUTH);\n\n        // Makes OK the default action.\n        setInitialFocusComponent(okButton);\n        getRootPane().setDefaultButton(okButton);\n\n        // Makes sure the scroll pane is initializes on its first line.\n        SwingUtilities.invokeLater(() -> licensePanel.getViewport().setViewPosition(new Point(0,0)));\n        pack();\n    }\n\n\n\n    // - IO code ----------------------------------------------------------------\n    // --------------------------------------------------------------------------\n    /**\n     * Loads the license text.\n     * @return the license text.\n     */\n    private String getLicenseText() {\n        StringBuilder text = new StringBuilder();\n        try (InputStreamReader in  = new InputStreamReader(LicenseDialog.class.getResourceAsStream(RuntimeConstants.LICENSE))) {\n            char[] buffer = new char[2048];\n\n            int count;\n            while ((count = in.read(buffer)) != -1) {\n                text.append(buffer, 0, count);\n            }\n        } catch(Exception e) {\n            LOGGER.warn(\"Failed to read license file\", e);\n        }\n        return text.toString();\n    }\n\n\n\n    // - Listener code ----------------------------------------------------------\n    // --------------------------------------------------------------------------\n    /**\n     * If the event originates from the OK button, closes the window.\n     */\n    public void actionPerformed(ActionEvent e) {\n        if (e.getSource() == okButton) {\n            dispose();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/about/package.html",
    "content": "<body>\n  Contains components used to display the muCommander About dialog.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/auth/AuthDialog.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\n\r\npackage com.mucommander.ui.dialog.auth;\r\n\r\nimport com.mucommander.auth.CredentialsManager;\r\nimport com.mucommander.auth.CredentialsMapping;\r\nimport com.mucommander.commons.file.Credentials;\r\nimport com.mucommander.commons.file.FileURL;\r\nimport com.mucommander.commons.util.StringUtils;\r\nimport com.mucommander.ui.combobox.EditableComboBox;\r\nimport com.mucommander.ui.combobox.EditableComboBoxListener;\r\nimport com.mucommander.ui.combobox.SaneComboBox;\r\nimport com.mucommander.ui.dialog.DialogToolkit;\r\nimport com.mucommander.ui.dialog.FocusDialog;\r\nimport com.mucommander.ui.helper.FocusRequester;\r\nimport com.mucommander.ui.layout.InformationPane;\r\nimport com.mucommander.ui.layout.XAlignedComponentPanel;\r\nimport com.mucommander.ui.layout.YBoxPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.*;\r\nimport java.awt.event.ActionEvent;\r\nimport java.awt.event.ActionListener;\r\n\r\n\r\n/**\r\n * This dialog is used to ask the user for credentials (login/password) to access a particular location and offer him\r\n * to store them to disk.\r\n *\r\n * <p>It uses CredentialsManager to retrieve and display a list of credentials matching the location so\r\n * they can quickly be recalled.\r\n *\r\n * @see CredentialsManager\r\n * @author Maxence Bernard\r\n */\r\npublic class AuthDialog extends FocusDialog implements ActionListener, EditableComboBoxListener {\r\n\r\n    private final JButton btnOk;\r\n    private final JButton btnCancel;\r\n\r\n    private JRadioButton guestRadioButton;\r\n    private JRadioButton userRadioButton;\r\n\r\n    private final JTextField loginField;\r\n    private EditableComboBox<String> loginComboBox;\r\n\r\n    private final JPasswordField passwordField;\r\n\r\n    private final JCheckBox cbSaveCredentials;\r\n\r\n    private CredentialsMapping selectedCredentialsMapping;\r\n    private boolean guestCredentialsSelected;\r\n\r\n    private final FileURL fileURL;\r\n\r\n    private final CredentialsMapping[] credentialsMappings;\r\n\r\n    // Dialog size constraints\r\n    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(320,0);\r\n    private final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(480,10000);\r\n\r\n\r\n    public AuthDialog(MainFrame mainFrame, FileURL fileURL, boolean authFailed, String errorMessage) {\r\n        super(mainFrame.getJFrame(), i18n(\"auth_dialog.title\"), mainFrame.getJFrame());\r\n\t\r\n        Container contentPane = getContentPane();\r\n        contentPane.setLayout(new BorderLayout());\r\n\r\n        YBoxPanel yPanel = new YBoxPanel();\r\n\r\n        if (authFailed) {\r\n            yPanel.add(new InformationPane(i18n(\"auth_dialog.authentication_failed\"), errorMessage, errorMessage==null?Font.PLAIN:Font.BOLD, InformationPane.ERROR_ICON));\r\n            yPanel.addSpace(5);\r\n            yPanel.add(new JSeparator());\r\n        }\r\n\r\n        yPanel.addSpace(5);\r\n        \r\n        this.fileURL = fileURL;\r\n\r\n        // Retrieve guest credentials (if any)\r\n        Credentials guestCredentials = fileURL.getGuestCredentials();\r\n        // Fetch credentials from the specified FileURL (if any) and use them only if they're different from the guest ones\r\n        Credentials urlCredentials = fileURL.getCredentials();\r\n        if (urlCredentials != null && urlCredentials.equals(guestCredentials)) {\r\n            urlCredentials = null;\r\n        }\r\n        // Retrieve a list of credentials matching the URL from CredentialsManager\r\n        credentialsMappings = CredentialsManager.getMatchingCredentials(fileURL);\r\n\r\n        XAlignedComponentPanel compPanel = new XAlignedComponentPanel(10);\r\n\r\n        // Connect as Guest/User radio buttons, displayed only if the URL has guest credentials\r\n        if (guestCredentials != null) {\r\n            addGuestCredentials(guestCredentials, compPanel);\r\n        } else {\r\n            // If not, display an introduction label (\"please enter a login and password\")\r\n            yPanel.add(new JLabel(i18n(\"auth_dialog.desc\")));\r\n            yPanel.addSpace(15);\r\n        }\r\n\r\n        // Server URL for which the user has to authenticate\r\n        compPanel.addRow(i18n(\"auth_dialog.server\"), new JLabel(fileURL.toString(false)), 10);\r\n\r\n        // Login field: create either a text field or an editable combo box, depending on whether\r\n        // CredentialsManager returned matches (-> combo box) or not (-> text field).\r\n        int nbCredentials = credentialsMappings.length;\r\n        JComponent loginComponent;\r\n        if (nbCredentials > 0) {\r\n            // Editable combo box\r\n            loginComboBox = new EditableComboBox<>();\r\n            this.loginField = loginComboBox.getTextField();\r\n\r\n            // Add credentials to the combo box's choices\r\n            for (CredentialsMapping credentialsMapping : credentialsMappings) {\r\n                loginComboBox.addItem(credentialsMapping.getCredentials().getLogin());\r\n            }\r\n\r\n            loginComboBox.addEditableComboBoxListener(this);\r\n            loginComponent = loginComboBox;\r\n        } else {\r\n            // Simple text field\r\n            loginField = new JTextField();\r\n            loginComponent = loginField;\r\n        }\r\n\r\n        compPanel.addRow(i18n(\"login\"), loginComponent, 5);\r\n\r\n        // create password field\r\n        this.passwordField = new JPasswordField();\r\n        passwordField.addActionListener(this);\r\n        compPanel.addRow(i18n(\"password\"), passwordField, 10);\r\n\r\n        // Contains the credentials to set in the login and password text fields\r\n        Credentials selectedCredentials = null;\r\n        // Whether the 'save credentials' checkbox should be enabled\r\n        boolean saveCredentialsCheckBoxSelected = false;\r\n\r\n        // If the provided URL contains credentials, use them\r\n        if (urlCredentials != null) {\r\n            selectedCredentials = urlCredentials;\r\n        } else if (nbCredentials > 0) {\r\n            // Else if CredentialsManager had matching credentials, use the best ones\r\n            CredentialsMapping bestCredentialsMapping = credentialsMappings[0];\r\n\r\n            selectedCredentials = bestCredentialsMapping.getCredentials();\r\n            saveCredentialsCheckBoxSelected = bestCredentialsMapping.isPersistent();\r\n        }\r\n\r\n        yPanel.add(compPanel);\r\n\r\n        this.cbSaveCredentials = new JCheckBox(i18n(\"auth_dialog.store_credentials\"), saveCredentialsCheckBoxSelected);\r\n        yPanel.add(cbSaveCredentials);\r\n\r\n        yPanel.addSpace(5);\r\n        contentPane.add(yPanel, BorderLayout.CENTER);\r\n\r\n        // If we have some existing credentials for this location...\r\n        if (selectedCredentials != null) {\r\n            // Pre-fill the login and password fields with the selected credentials\r\n            loginField.setText(selectedCredentials.getLogin());\r\n            passwordField.setText(selectedCredentials.getPassword());\r\n\r\n            // Select the text fields' so their content can be erased just by typing the replacement string\r\n            loginField.selectAll();\r\n            passwordField.selectAll();\r\n\r\n            // Select the 'Connect as User' radio button if there is one\r\n            if (userRadioButton != null) {\r\n                userRadioButton.setSelected(true);\r\n            }\r\n        } else {\r\n            // Pre-fill the login field with the current user's name (ticket #185)\r\n            loginField.setText(System.getProperty(\"user.name\"));\r\n\r\n            // Select the 'Connect as Guest' radio button if there is one\r\n            if (guestRadioButton != null) {\r\n                guestRadioButton.setSelected(true);\r\n\r\n                loginField.setEnabled(false);\r\n                passwordField.setEnabled(false);\r\n                cbSaveCredentials.setEnabled(false);\r\n            }\r\n        }\r\n\r\n        // Add OK/Cancel buttons\r\n        this.btnOk = new JButton(i18n(\"ok\"));\r\n        this.btnCancel = new JButton(i18n(\"cancel\"));\r\n        contentPane.add(DialogToolkit.createOKCancelPanel(btnOk, btnCancel, getRootPane(), this), BorderLayout.SOUTH);\r\n\r\n        // Set the component that will receive the initial focus\r\n        setInitialFocusComponent(guestRadioButton == null ? loginField : guestRadioButton.isSelected() ? guestRadioButton:loginField);\r\n\r\n        // Set minimum dimension\r\n        setMinimumSize(MINIMUM_DIALOG_DIMENSION);\r\n\r\n        // Set minimum dimension\r\n        setMaximumSize(MAXIMUM_DIALOG_DIMENSION);\r\n    }\r\n\r\n    private void addGuestCredentials(Credentials guestCredentials, XAlignedComponentPanel compPanel) {\r\n        guestRadioButton = new JRadioButton(StringUtils.capitalize(guestCredentials.getLogin()));\r\n        guestRadioButton.addActionListener(this);\r\n        compPanel.addRow(i18n(\"auth_dialog.connect_as\"), guestRadioButton, 0);\r\n\r\n        userRadioButton = new JRadioButton(i18n(\"user\"));\r\n        userRadioButton.addActionListener(this);\r\n        compPanel.addRow(\"\", userRadioButton, 15);\r\n\r\n        ButtonGroup buttonGroup = new ButtonGroup();\r\n        buttonGroup.add(guestRadioButton);\r\n        buttonGroup.add(userRadioButton);\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the <Code>CredentialsMapping</code> corresponding to the credentials selected by the user, either\r\n     * entered in the login and password fields, or the guest credentials.\r\n     *\r\n     * @return the credentials entered by the user, <code>null</code> if the dialog was cancelled\r\n     */\r\n    public CredentialsMapping getCredentialsMapping() {\r\n        return selectedCredentialsMapping;\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the user chose the guest credentials (radio button) in the dialog.\r\n     * If <code>true</code>, {@link #getCredentialsMapping()} will return the guest credentials.\r\n     *\r\n     * @return <code>true</code> if the user chose the guest credentials (radio button) in the dialog\r\n     */\r\n    public boolean guestCredentialsSelected() {\r\n        return guestCredentialsSelected;\r\n    }\r\n\r\n    /**\r\n     * Called when the dialog has been validated by the user, when the OK button has been pressed or when enter has\r\n     * been pressed in a text field.\r\n     */\r\n    private void setCredentialMapping() {\r\n        if (guestRadioButton != null && guestRadioButton.isSelected()) {\r\n            guestCredentialsSelected = true;\r\n            selectedCredentialsMapping = new CredentialsMapping(fileURL.getGuestCredentials(), fileURL, false);\r\n        } else {\r\n            Credentials enteredCredentials = new Credentials(loginField.getText(), new String(passwordField.getPassword()));\r\n            guestCredentialsSelected = false;\r\n\r\n            boolean isPersistent = cbSaveCredentials.isSelected();\r\n            selectedCredentialsMapping = new CredentialsMapping(enteredCredentials, fileURL, isPersistent);\r\n\r\n            // Look for an existing matching CredentialsMapping instance to re-use the realm which may contain\r\n            // connection properties.\r\n            for (CredentialsMapping cm : credentialsMappings) {\r\n                if (cm.getCredentials().equals(enteredCredentials, true)) {  // Comparison must be password-sensitive\r\n                    // create a new CredentialsMapping instance in case the 'isPersistent' flag has changed.\r\n                    // (original credentials may have originally been added as 'volatile' and then made persistent by\r\n                    // ticking the checkbox, or vice-versa)\r\n                    selectedCredentialsMapping = new CredentialsMapping(cm.getCredentials(), cm.getRealm(), isPersistent);\r\n                    break;\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n    @Override\r\n    public void actionPerformed(ActionEvent e) {\r\n        Object source = e.getSource();\r\n\r\n        if (source == btnOk || source == loginField || source == passwordField) {\r\n            setCredentialMapping();\r\n            dispose();\r\n        } else if (source == btnCancel) {\r\n            dispose();\r\n        } else if (source == guestRadioButton) {\r\n            loginField.setEnabled(false);\r\n            passwordField.setEnabled(false);\r\n            cbSaveCredentials.setEnabled(false);\r\n        } else if(source == userRadioButton) {\r\n            loginField.setEnabled(true);\r\n            passwordField.setEnabled(true);\r\n            cbSaveCredentials.setEnabled(true);\r\n\r\n            loginField.selectAll();\r\n            FocusRequester.requestFocus(loginField);\r\n        }\r\n    }\r\n\r\n\r\n    @Override\r\n    public void comboBoxSelectionChanged(SaneComboBox source) {\r\n        CredentialsMapping selectedCredentialsMapping = credentialsMappings[loginComboBox.getSelectedIndex()];\r\n        Credentials selectedCredentials = selectedCredentialsMapping.getCredentials();\r\n        loginField.setText(selectedCredentials.getLogin());\r\n        passwordField.setText(selectedCredentials.getPassword());\r\n\r\n        // Enable/disable 'save credentials' checkbox depending on whether the selected credentials are persistent or not\r\n        if (cbSaveCredentials != null) {\r\n            cbSaveCredentials.setSelected(selectedCredentialsMapping.isPersistent());\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void textFieldValidated(EditableComboBox source) {\r\n        setCredentialMapping();\r\n        dispose();\r\n    }\r\n\r\n    @Override\r\n    public void textFieldCancelled(EditableComboBox source) {\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/auth/EditCredentialsDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.auth;\n\nimport java.awt.BorderLayout;\nimport java.awt.Container;\nimport java.awt.Dimension;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\n\nimport javax.swing.Box;\nimport javax.swing.JButton;\nimport javax.swing.JPasswordField;\nimport javax.swing.JTextField;\nimport javax.swing.event.ListSelectionEvent;\nimport javax.swing.event.ListSelectionListener;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.auth.CredentialsManager;\nimport com.mucommander.auth.CredentialsMapping;\nimport com.mucommander.commons.collections.AlteredVector;\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.EditCredentialsAction;\nimport com.mucommander.ui.dialog.FocusDialog;\nimport com.mucommander.ui.helper.MnemonicHelper;\nimport com.mucommander.ui.layout.XAlignedComponentPanel;\nimport com.mucommander.ui.layout.XBoxPanel;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.list.DynamicList;\nimport com.mucommander.ui.list.SortableListPanel;\nimport com.mucommander.ui.main.MainFrame;\n\n\n/**\n * This dialog contains a list of all persistent credentials and allows the user to edit, remove, go to and reorder them.\n *\n * <p>If the contents of this list is modified, credentials will be saved to disk when this dialog is disposed.\n *\n * @author Maxence Bernard\n */\npublic class EditCredentialsDialog extends FocusDialog implements ActionListener, ListSelectionListener {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(EditCredentialsDialog.class);\n\t\n    private final MainFrame mainFrame;\n\n    private final JButton removeButton;\n    private final JButton goToButton;\n    private final JButton closeButton;\n\n    private final JTextField loginField;\n    private final JPasswordField passwordField;\n\n    private final AlteredVector<CredentialsMapping> credentials;\n    private final DynamicList<CredentialsMapping> credentialsList;\n\n    private CredentialsMapping lastSelectedItem;\n\n    // Dialog's size has to be at least 400x300\n    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(440,330);\n\n    // Dialog's size has to be at most 600x400\n    private final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(600,400);\n\n\n\n    public EditCredentialsDialog(MainFrame mainFrame) {\n        super(mainFrame.getJFrame(), ActionProperties.getActionLabel(EditCredentialsAction.Descriptor.ACTION_ID), mainFrame.getJFrame());\n\n        this.mainFrame = mainFrame;\n\n        Container contentPane = getContentPane();\n\n        // Retrieve persistent credentials list\n        this.credentials = CredentialsManager.getPersistentCredentialMappings();\n\n        // create the sortable credentials list panel\n        SortableListPanel<CredentialsMapping> listPanel = new SortableListPanel<>(credentials);\n        this.credentialsList = listPanel.dynamicList;\n        this.lastSelectedItem = credentialsList.getSelectedValue();\n\n        contentPane.add(listPanel, BorderLayout.CENTER);\n\n        // Text fields panel\n        XAlignedComponentPanel compPanel = new XAlignedComponentPanel();\n\n        // Add login field\n        this.loginField = new JTextField();\n        compPanel.addRow(i18n(\"login\")+\":\", loginField, 5);\n\n        // Add password field\n        this.passwordField = new JPasswordField();\n        compPanel.addRow(i18n(\"password\")+\":\", passwordField, 10);\n\n        YBoxPanel yPanel = new YBoxPanel(10);\n        yPanel.add(compPanel);\n\n        XBoxPanel buttonsPanel = new XBoxPanel();\n        MnemonicHelper mnemonicHelper = new MnemonicHelper();\n\n        // Remove button\n        removeButton = new JButton(credentialsList.getRemoveAction());\n        removeButton.setMnemonic(mnemonicHelper.getMnemonic(removeButton));\n\n        buttonsPanel.add(removeButton);\n\n        // Go to button\n        goToButton = new JButton(i18n(\"go_to\"));\n        goToButton.setMnemonic(mnemonicHelper.getMnemonic(goToButton));\n        goToButton.addActionListener(this);\n\n        buttonsPanel.add(goToButton);\n\n        // Button that closes the window\n        closeButton = new JButton(i18n(\"close\"));\n        closeButton.setMnemonic(mnemonicHelper.getMnemonic(closeButton));\n        closeButton.addActionListener(this);\n\n        buttonsPanel.add(Box.createHorizontalGlue());\n        buttonsPanel.add(closeButton);\n\n        yPanel.add(buttonsPanel);\n\n        contentPane.add(yPanel, BorderLayout.SOUTH);\n\n        // Set initial text components and buttons' enabled state\n        updateComponents();\n\n        // Listen to selection changes to reflect the change\n        credentialsList.addListSelectionListener(this);\n\n        // table will receive initial focus\n        setInitialFocusComponent(credentialsList);\n\t\t\n        // Selects 'Done' button when enter is pressed\n        getRootPane().setDefaultButton(closeButton);\n\n        // Packs dialog\n        setMinimumSize(MINIMUM_DIALOG_DIMENSION);\n        setMaximumSize(MAXIMUM_DIALOG_DIMENSION);\n\n        // Call dispose() on close and write credentials file\n        setDefaultCloseOperation(DISPOSE_ON_CLOSE);\n\n        showDialog();\n    }\n\n\n    /**\n     * Updates text fields and buttons' enabled state based on the current selection. Should be called\n     * whenever the list selection has changed.\n     */\n    private void updateComponents() {\n        String loginValue = null;\n        String passwordValue = null;\n\n        boolean componentsEnabled = false;\n\n        if (!credentialsList.isSelectionEmpty() && !credentials.isEmpty()) {\n            componentsEnabled = true;\n\n            CredentialsMapping credentialsMapping = credentialsList.getSelectedValue();\n            Credentials credentials = credentialsMapping.getCredentials();\n            loginValue = credentials.getLogin();\n            passwordValue = credentials.getPassword();\n        }\n\n        loginField.setText(loginValue);\n        loginField.setEnabled(componentsEnabled);\n\n        passwordField.setText(passwordValue);\n        passwordField.setEnabled(componentsEnabled);\n\n        removeButton.setEnabled(componentsEnabled);\n    }\n\n\n    /**\n     * Updates the value of the item that was being editing. Should be called whenever the list selection has changed.\n     */\n    private void modifyCredentials() {\n        // Make sure that the item still exists (could have been removed) before trying to modify its value\n        int itemIndex = credentials.indexOf(lastSelectedItem);\n        if (lastSelectedItem != null && itemIndex >= 0) {\n            credentials.setElementAt(new CredentialsMapping(new Credentials(loginField.getText(), new String(passwordField.getPassword())), lastSelectedItem.getRealm(), true), itemIndex);\n        }\n        \n        this.lastSelectedItem = credentialsList.getSelectedValue();\n    }\n\n\n    /**\n     * Overrides dispose() to write credentials if needed (if at least one item has been changed).\n     */\n    @Override\n    public void dispose() {\n        super.dispose();\n\n        // Write credentials file to disk, only if changes were made\n        try {\n            CredentialsManager.writeCredentials(false);\n        } catch (Exception e) {\n            e.printStackTrace();\n            // We should probably pop an error dialog here...\n        }\n    }\n\n\n\n    @Override\n    public void actionPerformed(ActionEvent e) {\n        Object source = e.getSource();\n\n        // Dispose the dialog (credentials save is performed in dispose())\n        if (source == closeButton)  {\n            // Commit current credentials modifications.\n            // Note: if the dialog is cancelled, current modifications will be cancelled (i.e. not committed) \n            modifyCredentials();\n            \n            dispose();\n        } else if (source == goToButton) {\n            // Dispose dialog first\n            dispose();\n            // Go to credentials' realm location\n            mainFrame.getActivePanel().tryChangeCurrentFolder((credentialsList.getSelectedValue()).getRealm());\n        }\n    }\n\n\n    ///////////////////////////////////\n    // ListSelectionListener methods //\n    ///////////////////////////////////\n\n    public void valueChanged(ListSelectionEvent e) {\n        LOGGER.trace(\"called, e.getValueIsAdjusting=\"+e.getValueIsAdjusting()+\" getSelectedIndex=\"+ credentialsList.getSelectedIndex());\n\n        if (e.getValueIsAdjusting()) {\n            return;\n        }\n\n        // Commit current credentials modifications\n        modifyCredentials();\n\n        // Update components to reflect the new selection\n        updateComponents();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/auth/package.html",
    "content": "<body>\n  Components for creating and editing sets of credentials.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/bookmark/AddBookmarkDialog.kt",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.dialog.bookmark\n\nimport com.mucommander.bookmark.Bookmark\nimport com.mucommander.bookmark.BookmarkManager\nimport com.mucommander.ui.action.ActionProperties\nimport com.mucommander.ui.action.impl.AddBookmarkAction\nimport com.mucommander.ui.dialog.DialogToolkit\nimport com.mucommander.ui.dialog.FocusDialog\nimport com.mucommander.ui.layout.XAlignedComponentPanel\nimport com.mucommander.ui.layout.YBoxPanel\nimport com.mucommander.ui.main.MainFrame\nimport java.awt.BorderLayout\nimport java.awt.Dimension\nimport java.awt.event.ActionEvent\nimport java.awt.event.ActionListener\nimport javax.swing.JButton\nimport javax.swing.JTextField\nimport javax.swing.event.DocumentEvent\nimport javax.swing.event.DocumentListener\n\n/**\n * This dialog allows the user to add a bookmark and enter a name for it. User can also\n * choose to store login and password information in the bookmark's URL if the bookmark\n * contains login/password information.\n * \n * @author Maxence Bernard\n */\nclass AddBookmarkDialog(mainFrame: MainFrame) : FocusDialog(\n    mainFrame.jFrame,\n    ActionProperties.getActionLabel(AddBookmarkAction.Descriptor.ACTION_ID),\n    mainFrame.jFrame\n), ActionListener, DocumentListener {\n    private val edtName: JTextField\n    private val edtLocation: JTextField\n    private val cbParent: BookmarkParentComboBox\n\n    private val addButton: JButton\n    private val cancelButton: JButton\n\n    init {\n        val currentFolder = mainFrame.activePanel.currentFolder\n\n        // Text fields panel\n        val compPanel = XAlignedComponentPanel().apply {\n            edtName = JTextField(currentFolder.getName()).apply {\n                isEditable = true\n                document.addDocumentListener(this@AddBookmarkDialog)    // Monitors text changes to disable 'Add' button if name field is empty\n            }\n            addRow(i18n(\"name\") + \":\", edtName, 10)\n\n            edtLocation = JTextField(currentFolder.canonicalPath)\n            addRow(i18n(\"location\") + \":\", edtLocation, 10)\n\n            cbParent = BookmarkParentComboBox()\n            addRow(i18n(\"parent\") + \":\", cbParent, 10)\n        }\n        val mainPanel = YBoxPanel(5).apply {\n            add(compPanel)\n        }\n\n        contentPane.add(mainPanel, BorderLayout.NORTH)\n\n        addButton = JButton(i18n(\"add_bookmark_dialog.add\"))\n        cancelButton = JButton(i18n(\"cancel\"))\n        contentPane.add(\n            DialogToolkit.createOKCancelPanel(addButton, cancelButton, getRootPane(), this),\n            BorderLayout.SOUTH\n        )\n\n        // Select text in name field and transfer focus to it for immediate user change\n        edtName.selectAll()\n        setInitialFocusComponent(edtName)\n\n        // Packs dialog\n        minimumSize = MINIMUM_DIALOG_DIMENSION\n        maximumSize = MAXIMUM_DIALOG_DIMENSION\n\n        showDialog()\n    }\n\n\n    /**\n     * Checks if bookmark name is empty (or white space), and enable/disable 'Add' button\n     * accordingly, in order to prevent user from adding a bookmark with an empty name.\n     */\n    private fun checkEmptyName() {\n        if (edtName.getText().isBlank()) {\n            if (addButton.isEnabled) {\n                addButton.setEnabled(false)\n            }\n        } else {\n            if (!addButton.isEnabled) {\n                addButton.setEnabled(true)\n            }\n        }\n    }\n\n\n    override fun actionPerformed(e: ActionEvent) {\n        val source = e.getSource()\n\n        if (source === addButton) {\n            // Starts by disposing the dialog\n            dispose()\n\n            // Add bookmark and write bookmarks file to disk\n            BookmarkManager.addBookmark(\n                Bookmark(\n                    edtName.getText(),\n                    edtLocation.getText(),\n                    cbParent.getSelectedParent()\n                )\n            )\n            try {\n                BookmarkManager.writeBookmarks(false)\n            } catch (_: Exception) {\n                // TODO We should probably pop an error dialog here.\n            }\n        } else if (source === cancelButton) {\n            dispose()\n        }\n    }\n\n\n    override fun changedUpdate(e: DocumentEvent) = checkEmptyName()\n    override fun insertUpdate(e: DocumentEvent) = checkEmptyName()\n    override fun removeUpdate(e: DocumentEvent) = checkEmptyName()\n\n    companion object {\n        // Dialog's width has to be at least 320\n        private val MINIMUM_DIALOG_DIMENSION = Dimension(320, 0)\n\n        // Dialog's width has to be at most 400\n        private val MAXIMUM_DIALOG_DIMENSION = Dimension(400, 10000)\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/bookmark/BookmarkParentComboBox.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2018 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.dialog.bookmark;\n\nimport com.mucommander.bookmark.Bookmark;\nimport com.mucommander.bookmark.BookmarkManager;\nimport com.mucommander.ui.combobox.TcComboBox;\nimport com.mucommander.utils.text.Translator;\n\nimport javax.swing.event.PopupMenuEvent;\nimport javax.swing.event.PopupMenuListener;\nimport java.util.List;\n\npublic class BookmarkParentComboBox extends TcComboBox<String> implements PopupMenuListener {\n\n    private String childName;\n\n    BookmarkParentComboBox() {\n        super();\n        addPopupMenuListener(this);\n    }\n\n\n    @Override\n    public void popupMenuWillBecomeVisible(PopupMenuEvent e) {\n        removeAllItems();\n        List<Bookmark> parents = BookmarkManager.getParentBookmarks();\n        addItem(\"<\" + Translator.get(\"root\") + \">\");\n        for (Bookmark bookmark : parents) {\n            if (bookmark.getName().equals(BookmarkManager.BOOKMARKS_SEPARATOR)) {\n                continue;\n            }\n            if (childName == null || !childName.equals(bookmark.getName())) {\n                addItem(bookmark.getName());\n            }\n        }\n    }\n\n    @Override\n    public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {\n\n    }\n\n    @Override\n    public void popupMenuCanceled(PopupMenuEvent e) {\n\n    }\n\n    public String getChildName() {\n        return childName;\n    }\n\n    public void setChildName(String childName) {\n        this.childName = childName;\n    }\n\n    public String getSelectedParent() {\n        if (getSelectedIndex() == 0) {\n            return null;\n        }\n        return getSelectedItem() == null ? null : getSelectedItem().toString();\n    }\n\n    public void setSelectedParent(String parent) {\n        getModel().setSelectedItem(parent);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/bookmark/EditBookmarksDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.bookmark;\n\nimport com.mucommander.bookmark.Bookmark;\nimport com.mucommander.bookmark.BookmarkManager;\nimport com.mucommander.commons.collections.AlteredVector;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.EditBookmarksAction;\nimport com.mucommander.ui.dialog.FocusDialog;\nimport com.mucommander.ui.helper.MnemonicHelper;\nimport com.mucommander.ui.layout.XAlignedComponentPanel;\nimport com.mucommander.ui.layout.XBoxPanel;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.list.DynamicList;\nimport com.mucommander.ui.list.SortableListPanel;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.text.FilePathField;\n\nimport javax.swing.*;\nimport javax.swing.event.DocumentEvent;\nimport javax.swing.event.DocumentListener;\nimport javax.swing.event.ListSelectionEvent;\nimport javax.swing.event.ListSelectionListener;\nimport javax.swing.text.Document;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\n\n\n/**\n * This dialog contains a list of all bookmarks and allows the user to edit, remove, duplicate, go to and reorder them.\n *\n * <p>If the contents of this list is modified, bookmarks will be saved to disk when this dialog is disposed.\n *\n * @author Maxence Bernard\n */\npublic class EditBookmarksDialog extends FocusDialog implements ActionListener, ListSelectionListener, DocumentListener {\n\n    private final MainFrame mainFrame;\n\n    private final JButton btnNew;\n    private final JButton btnDuplicate;\n    private final JButton btnRemove;\n    private final JButton btnGoto;\n    private final JButton btnClose;\n\n    private final JTextField edtName;\n    private final JLabel locationLabel;\n    private final JTextField edtLocation;\n    private final BookmarkParentComboBox cbParent;\n    // separatorNoticePrefix is required to keep the size of the 1st column\n    private final JLabel separatorNoticePrefix;\n    private final JLabel separatorNoticeLabel;\n\n    private final AlteredVector<Bookmark> bookmarks;\n    private final DynamicList<Bookmark> bookmarkList;\n\n    private int currentListIndex;\n    private Bookmark currentBookmarkSave;\n\n    private boolean ignoreDocumentListenerEvents;\n\n    // Dialog's size has to be at least 400x300\n    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(440,330);\t\n\n    // Dialog's size has to be at most 600x400\n    private final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(600,400);\n\n\n\n    public EditBookmarksDialog(MainFrame mainFrame) {\n        super(mainFrame.getJFrame(), ActionProperties.getActionLabel(EditBookmarksAction.Descriptor.ACTION_ID), mainFrame.getJFrame());\n\n        this.mainFrame = mainFrame;\n\n        Container contentPane = getContentPane();\n\n        // Retrieve bookmarks list\n        this.bookmarks = BookmarkManager.getBookmarks();\n\n        // Temporarily suspend bookmark change events, otherwise an event would be fired for each character\n        // typed in the name / location fields. Events will be resumed when this dialog is disposed\n        BookmarkManager.setFireEvents(false);\n\n        // create the sortable bookmarks list panel\n        SortableListPanel<Bookmark> listPanel = new SortableListPanel<>(bookmarks);\n        this.bookmarkList = listPanel.dynamicList;\n\n        contentPane.add(listPanel, BorderLayout.CENTER);\n\n        // Text fields panel\n        XAlignedComponentPanel compPanel = new XAlignedComponentPanel();\n\n        // Add bookmark name field\n        this.edtName = new JTextField();\n        edtName.getDocument().addDocumentListener(this);\n        compPanel.addRow(i18n(\"name\")+\":\", edtName, 5);\n\n        // create a path field with auto-completion capabilities\n        this.edtLocation = new FilePathField();\n        this.locationLabel = new JLabel(i18n(\"location\")+\":\");\n        edtLocation.getDocument().addDocumentListener(this);\n        compPanel.addRow(locationLabel, edtLocation, 10);\n\n        this.cbParent = new BookmarkParentComboBox();\n        cbParent.addActionListener(this);\n        compPanel.addRow(i18n(\"parent\")+\":\", cbParent, 5);\n\n        this.separatorNoticePrefix = new JLabel();\n        this.separatorNoticeLabel = new JLabel(\" \"+i18n(\"edit_bookmarks_dialog.is_separator\"));\n        compPanel.addRow(separatorNoticePrefix, separatorNoticeLabel, 10);\n\n        YBoxPanel yPanel = new YBoxPanel(10);\n        yPanel.add(compPanel);\n\n        // Add buttons: 'remove', 'move up' and 'move down' buttons are enabled\n        // only if there is at least one bookmark in the table\n        XBoxPanel buttonsPanel = new XBoxPanel();\n        JPanel buttonGroupPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n\n        MnemonicHelper mnemonicHelper = new MnemonicHelper();\n\n        // New bookmark button\n        btnNew = new JButton(i18n(\"edit_bookmarks_dialog.new\"));\n        btnNew.setMnemonic(mnemonicHelper.getMnemonic(btnNew));\n        btnNew.addActionListener(this);\n        buttonGroupPanel.add(btnNew);\n\n        // Duplicate bookmark button\n        btnDuplicate = new JButton(i18n(\"duplicate\"));\n        btnDuplicate.setMnemonic(mnemonicHelper.getMnemonic(btnDuplicate));\n        btnDuplicate.addActionListener(this);\n        buttonGroupPanel.add(btnDuplicate);\n\n        // Remove bookmark button\n        btnRemove = new JButton(bookmarkList.getRemoveAction());\n        btnRemove.setMnemonic(mnemonicHelper.getMnemonic(btnRemove));\n        buttonGroupPanel.add(btnRemove);\n\n        // Go to bookmark button\n        btnGoto = new JButton(i18n(\"go_to\"));\n        btnGoto.setMnemonic(mnemonicHelper.getMnemonic(btnGoto));\n        btnGoto.addActionListener(this);\n        buttonGroupPanel.add(btnGoto);\n\n        buttonsPanel.add(buttonGroupPanel);\n\n        // Button that closes the window\n        btnClose = new JButton(i18n(\"close\"));\n        btnClose.setMnemonic(mnemonicHelper.getMnemonic(btnClose));\n        btnClose.addActionListener(this);\n\n        buttonsPanel.add(Box.createHorizontalGlue());\n        buttonsPanel.add(btnClose);\n\n        yPanel.add(buttonsPanel);\n\n        contentPane.add(yPanel, BorderLayout.SOUTH);\n\n        // Set initial text components and buttons' enabled state\n        updateComponents();\n\n        // Listen to selection changes to reflect the change\n        bookmarkList.addListSelectionListener(this);\n\n        // table will receive initial focus\n        setInitialFocusComponent(bookmarkList);\n\t\t\n        // Selects OK when enter is pressed\n        getRootPane().setDefaultButton(btnClose);\n\n        // Packs dialog\n        setMinimumSize(MINIMUM_DIALOG_DIMENSION);\n        setMaximumSize(MAXIMUM_DIALOG_DIMENSION);\n\t\t\n        // Call dispose() on close and write bookmarks file\n        setDefaultCloseOperation(DISPOSE_ON_CLOSE);\n        \n        showDialog();\n    }\n\n\n    /**\n     * Updates text fields and buttons' enabled state based on the current selection. Should be called\n     * whenever the list selection has changed.\n     */\n    private void updateComponents() {\n        String nameValue = null;\n        String locationValue = null;\n        String parentValue = null;\n\n        boolean componentsEnabled = false;\n\n        if (!bookmarkList.isSelectionEmpty() && !bookmarks.isEmpty()) {\n            componentsEnabled = true;\n\n            Bookmark b = bookmarkList.getSelectedValue();\n            nameValue = b.getName();\n            locationValue = b.getLocation();\n            parentValue = b.getParent();\n        }\n\n        // Ignore text field events while setting values\n        ignoreDocumentListenerEvents = true;\n\n        edtName.setText(nameValue);\n        edtName.setEnabled(componentsEnabled);\n\n        edtLocation.setText(locationValue);\n        edtLocation.setEnabled(componentsEnabled);\n\n        cbParent.setChildName(nameValue);\n        cbParent.setSelectedParent(parentValue);\n\n        ignoreDocumentListenerEvents = false;\n\n        btnGoto.setEnabled(componentsEnabled && locationValue != null && !locationValue.isEmpty());\n        btnDuplicate.setEnabled(componentsEnabled);\n        btnRemove.setEnabled(componentsEnabled);\n\n        updateSeparatorNoticeVisibility();\n    }\n\n    /**\n     * Updates visibility of `The specified name defines a separator` notice\n     */\n    private void updateSeparatorNoticeVisibility() {\n        String nameFieldValue = edtName.getText();\n        boolean isSeparator = nameFieldValue != null && nameFieldValue.equals(BookmarkManager.BOOKMARKS_SEPARATOR);\n        if (separatorNoticeLabel.isVisible() == isSeparator) {\n            return;\n        }\n\n        separatorNoticePrefix.setVisible(isSeparator);\n        separatorNoticeLabel.setVisible(isSeparator);\n        if (isSeparator) {\n            // inherit the preferred sizes of locationXXXX controls,\n            // to keep the layout still\n            separatorNoticePrefix.setPreferredSize(locationLabel.getPreferredSize());\n            separatorNoticeLabel.setPreferredSize(edtLocation.getPreferredSize());\n        }\n        locationLabel.setVisible(!isSeparator);\n        edtLocation.setVisible(!isSeparator);\n    }\n\n\n    /**\n     * Called whenever a value in one of the text fields has been modified, and updates the current Bookmark instance to\n     * use the new value.\n     *\n     * @param sourceDocument the javax.swing.text.Document of the JTextField that was modified\n     */\n    private void modifyBookmark(Document sourceDocument) {\n        if (ignoreDocumentListenerEvents || bookmarks.isEmpty()) {\n            return;\n        }\n\n        int selectedIndex = bookmarkList.getSelectedIndex();\n\n        // Make sure that the selected index is not out of bounds\n        if (!bookmarkList.isIndexValid(selectedIndex)) {\n            return;\n        }\n\n        Bookmark selectedBookmark = bookmarks.elementAt(selectedIndex);\n\n        if (currentBookmarkSave == null) {\n            // create a clone of the current bookmark in order to cancel any modifications made to it if the dialog\n            // is cancelled.\n            try {\n                currentBookmarkSave = (Bookmark)selectedBookmark.clone();\n            } catch(CloneNotSupportedException ignored) {}\n\n            this.currentListIndex = selectedIndex;\n        }\n\n        // Update name\n        if (sourceDocument == edtName.getDocument()) {\n            String name = edtName.getText();\n            if (name.trim().isEmpty()) {\n                name = getFreeNameVariation(i18n(\"untitled\"));\n            }\n\n            selectedBookmark.setName(name);\n            bookmarkList.itemModified(selectedIndex, false);\n            updateSeparatorNoticeVisibility();\n        }\n        // Update location\n        else if (sourceDocument == edtLocation.getDocument()) {\n            String location = edtLocation.getText();\n            selectedBookmark.setLocation(location);\n            bookmarkList.itemModified(selectedIndex, false);\n            btnGoto.setEnabled(location != null && !location.isEmpty());\n        } else {\n            selectedBookmark.setParent(cbParent.getSelectedParent());\n            bookmarkList.itemModified(selectedIndex, false);\n        }\n    }\n\n\n    /**\n     * Returns the first variation of the given name that is not already used by another bookmark, e.g. :\n     * <br>\"music\" -> \"music (2)\" if there already is bookmark with the \"music\" name\n     * <br>\"music (2)\" -> \"music (3)\" and so on...\n     */\n    private String getFreeNameVariation(String name) {\n        if (!containsName(name)) {\n            return name;\n        }\n\n        int len = name.length();\n        char c;\n        int num = 2;\n        if (len > 4 && name.charAt(len - 1) == ')'\n                && (c = name.charAt(len - 2)) >= '0' && c <= '9'\n                && name.charAt(len - 3) == '('\n                && name.charAt(len - 4) == ' ')\n        {\n            num = (c - '0') + 1;\n            name = name.substring(0, len - 4);\n        }\n\n\n        String newName;\n        while (containsName(newName = (name + \" (\" + num++ + \")\"))) ;\n\n        return newName;\n    }\n\n\n    /**\n     * Returns true if the bookmarks list contains a bookmark that has the specified name.\n     */\n    private boolean containsName(String name) {\n        int nbBookmarks = bookmarks.size();\n        for (int i = 0; i < nbBookmarks; i++) {\n            if (bookmarks.elementAt(i).getName().equals(name)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n\n\n    /**\n     * Overrides dispose() to write bookmarks to disk (if needed).\n     */\n    @Override\n    public void dispose() {\n        super.dispose();\n\n        // Rollback current bookmark's modifications if the dialog was cancelled\n        if (currentBookmarkSave!=null) {\n            bookmarks.setElementAt(currentBookmarkSave, currentListIndex);\n            currentBookmarkSave = null;\n        }\n\n        // Resume bookmark change events\n        BookmarkManager.setFireEvents(true);\n\n        // Write bookmarks file to disk, only if changes were made to bookmarks\n        try {\n            BookmarkManager.writeBookmarks(false);\n        } catch(Exception e) {\n            // We should probably pop an error here.\n            e.printStackTrace();\n        }\n    }\n\n\n\t@Override\n    public void actionPerformed(ActionEvent e) {\n        Object source = e.getSource();\n\t\t\n        // Dispose the dialog (bookmarks save is performed in dispose())\n        if (source == btnClose)  {\n            // Do not rollback current bookmark's modifications on dispose()\n            currentBookmarkSave = null;\n\n            dispose();\n        } else if (source == btnNew || source == btnDuplicate) {\n            addBookmark(source == btnDuplicate);\n        } else if (source== btnGoto) {\n            // Dispose dialog first\n            dispose();\n            // Change active panel's folder\n            mainFrame.getActivePanel().tryChangeCurrentFolder((bookmarkList.getSelectedValue()).getLocation());\n        } else if (source == cbParent) {\n            modifyBookmark(null);\n        }\n    }\n\n\n    private void addBookmark(boolean duplicate) {\n        Bookmark newBookmark;\n        if (duplicate) {\n            try {\n                Bookmark currentBookmark = bookmarkList.getSelectedValue();\n                newBookmark = (Bookmark)currentBookmark.clone();\n                newBookmark.setName(getFreeNameVariation(currentBookmark.getName()));\n            } catch (CloneNotSupportedException ex) {\n                return;\n            }\n        } else {\n            newBookmark = new Bookmark(getFreeNameVariation(i18n(\"untitled\")), \"\", null);\n        }\n\n        bookmarks.add(newBookmark);\n\n        int newBookmarkIndex = bookmarks.size()-1;\n        bookmarkList.selectAndScroll(newBookmarkIndex);\n\n        updateComponents();\n\n        edtName.selectAll();\n        edtName.requestFocus();\n    }\n\n\t@Override\n    public void valueChanged(ListSelectionEvent e) {\n        if (e.getValueIsAdjusting()) {\n            return;\n        }\n\n        // Reset current bookmark's save\n        currentBookmarkSave = null;\n//        currentListIndex = bookmarkList.getSelectedIndex();\n\n        // Update components to reflect the new selection\n        updateComponents();\n    }\n\n\n    @Override\n    public void changedUpdate(DocumentEvent e) {\n        modifyBookmark(e.getDocument());\n    }\n\n    @Override\n    public void insertUpdate(DocumentEvent e) {\n        modifyBookmark(e.getDocument());\n    }\n\n    public void removeUpdate(DocumentEvent e) {\n        modifyBookmark(e.getDocument());\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/bookmark/package.html",
    "content": "<body>\n  Components for creating and editing bookmarks.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/commands/CommandsPanel.kt",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2026 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.dialog.commands\n\nimport com.jidesoft.hints.FileIntelliHints\nimport com.mucommander.command.Command\nimport com.mucommander.command.CommandManager\nimport com.mucommander.command.CommandType\nimport com.mucommander.commons.collections.AlteredVector\nimport com.mucommander.ui.helper.MnemonicHelper\nimport com.mucommander.ui.layout.XAlignedComponentPanel\nimport com.mucommander.ui.layout.XBoxPanel\nimport com.mucommander.ui.layout.YBoxPanel\nimport com.mucommander.ui.list.DynamicList\nimport com.mucommander.ui.list.SortableListPanel\nimport com.mucommander.ui.text.FilePathField\nimport com.mucommander.utils.text.Translator\nimport java.awt.BorderLayout\nimport java.awt.FlowLayout\nimport java.awt.event.ActionEvent\nimport java.awt.event.ActionListener\nimport javax.swing.JButton\nimport javax.swing.JPanel\nimport javax.swing.JTextField\nimport javax.swing.event.DocumentEvent\nimport javax.swing.event.DocumentListener\nimport javax.swing.event.ListSelectionEvent\nimport javax.swing.event.ListSelectionListener\n\n/**\n * @author Oleg Trifonov\n * Created on 10/10/14.\n */\nclass CommandsPanel(private val parent: EditCommandsDialog, private val commandType: String?) : JPanel(),\n    ActionListener, DocumentListener, ListSelectionListener {\n    private val commands: AlteredVector<CommandWrapper>\n    private val commandsList: DynamicList<CommandWrapper>\n    private val btnNew: JButton\n    private val btnDuplicate: JButton\n    private val btnRemove: JButton\n\n    private val edtAlias: JTextField\n    private val edtCommand: JTextField\n    private val edtDisplay: JTextField\n    private val edtFilemask: JTextField\n\n    private var ignoreDocumentListenerEvents = false\n\n\n    private inner class CommandWrapper(val command: Command) {\n        override fun toString(): String {\n            var result: String = if (commandType == null) {\n                command.alias + \":\\t\"\n            } else {\n                \"\"\n            }\n            command.displayName?.let {\n                result += it + \"\\t\"\n            }\n            command.fileMask?.let {\n                result += \"[\" + command.fileMask + \"]\\t\"\n            }\n            result += command.command\n            return result\n        }\n    }\n\n    init {\n        setLayout(BorderLayout())\n\n        val mnemonicHelper = MnemonicHelper()\n        val list = mutableListOf<CommandWrapper>().apply {\n            for (cmd in CommandManager.getCommands(commandType)) {\n                add(CommandWrapper(cmd))\n            }\n        }\n\n        commands = AlteredVector<CommandWrapper>(list)\n        // create the sortable commands list panel\n        val listPanel = SortableListPanel(commands)\n        commandsList = listPanel.dynamicList.apply {\n            addListSelectionListener(this@CommandsPanel)\n        }\n\n        add(listPanel, BorderLayout.CENTER)\n\n        ignoreDocumentListenerEvents = true\n\n        // Add alias field\n        edtAlias = JTextField().apply {\n            document.addDocumentListener(this@CommandsPanel)\n        }\n\n        // Text fields panel\n        val compPanel = XAlignedComponentPanel().apply {\n            addRow(Translator.get(\"EditCommands.alias\") + \":\", edtAlias, 5)\n            if (commandType != null) {\n                edtAlias.text = commandType\n                edtAlias.setEnabled(false)\n            }\n\n            edtCommand = FilePathField().apply {\n                document.addDocumentListener(this@CommandsPanel)\n            }\n            addRow(Translator.get(\"EditCommands.command\") + \":\", edtCommand, 10)\n\n            edtDisplay = FilePathField().apply {\n                document.addDocumentListener(this@CommandsPanel)\n            }\n            addRow(Translator.get(\"EditCommands.display_name\") + \":\", edtDisplay, 10)\n\n            edtFilemask = FilePathField().apply {\n                document.addDocumentListener(this@CommandsPanel)\n            }\n            addRow(Translator.get(\"EditCommands.filemask\") + \":\", edtFilemask, 10)\n        }\n\n        FileIntelliHints(edtCommand)\n\n\n        val yPanel = YBoxPanel(10).apply {\n            add(compPanel)\n        }\n\n        val buttonGroupPanel = JPanel(FlowLayout(FlowLayout.LEFT)).apply {\n            btnNew = JButton(Translator.get(\"EditCommands.new\")).also {\n                it.setMnemonic(mnemonicHelper.getMnemonic(it))\n                it.addActionListener(this@CommandsPanel)\n                add(it)\n            }\n            btnDuplicate = JButton(Translator.get(\"duplicate\")).also {\n                it.setMnemonic(mnemonicHelper.getMnemonic(it))\n                it.addActionListener(this@CommandsPanel)\n                add(it)\n            }\n            btnRemove = JButton(commandsList.removeAction).also {\n                it.setMnemonic(mnemonicHelper.getMnemonic(it))\n                it.addActionListener(this@CommandsPanel)\n                add(it)\n            }\n        }\n\n        // Add buttons: 'remove', 'move up' and 'move down' buttons are enabled only if there is at least one command in the table\n        val buttonsPanel = XBoxPanel().apply {\n            add(buttonGroupPanel)\n        }\n\n        yPanel.add(buttonsPanel)\n\n        add(yPanel, BorderLayout.SOUTH)\n        ignoreDocumentListenerEvents = false\n        updateComponents()\n    }\n\n    /**\n     * Updates text fields and buttons' enabled state based on the current selection. Should be called\n     * whenever the list selection has changed.\n     */\n    private fun updateComponents() {\n        var alias: String? = null\n        var value: String? = null\n        var display: String? = null\n        var filemask: String? = null\n\n        var componentsEnabled = false\n\n        if (!commandsList.isSelectionEmpty && !commands.isEmpty()) {\n            componentsEnabled = true\n\n            val cmd = commandsList.getSelectedValue()!!.command\n            alias = cmd.alias\n            value = cmd.command\n            display = cmd.displayName\n            filemask = cmd.fileMask\n        }\n        ignoreDocumentListenerEvents = true\n\n        if (commandType == null) {\n            edtAlias.text = alias\n        }\n        edtCommand.text = value\n        edtDisplay.text = display\n        edtFilemask.text = filemask\n        ignoreDocumentListenerEvents = false\n\n        btnDuplicate.setEnabled(componentsEnabled)\n        btnRemove.setEnabled(componentsEnabled)\n    }\n\n    override fun actionPerformed(e: ActionEvent) {\n        val source = e.getSource()\n\n        // create a new empty command / duplicate the currently selected command\n        if (source === btnNew || source === btnDuplicate) {\n            parent.enableSave()\n            val newCommand: Command?\n            if (source === btnNew) {\n                val alias = commandType ?: \"\"\n                newCommand = Command(alias, $$\"$f\")\n            } else { // source == btnDuplicate\n                val currentCommand = commandsList.getSelectedValue()!!.command\n                newCommand = Command(currentCommand)\n            }\n            commands.add(CommandWrapper(newCommand))\n\n            val newCommandIndex = commands.size - 1\n            commandsList.selectAndScroll(newCommandIndex)\n\n            updateComponents()\n\n            edtAlias.selectAll()\n            edtAlias.requestFocus()\n        } else if (source === btnRemove) {\n            parent.enableSave()\n        }\n    }\n\n\n    /**\n     * Called whenever a value in one of the text fields has been modified, and updates the current Command instance to\n     * use the new value.\n     */\n    private fun modifyCommand() {\n        if (ignoreDocumentListenerEvents || commands.isEmpty()) {\n            return\n        }\n\n        val selectedIndex = commandsList.selectedIndex\n\n        // Make sure that the selected index is not out of bounds\n        if (!commandsList.isIndexValid(selectedIndex)) return\n\n        val selectedCommand = Command(\n            edtAlias.getText(),\n            edtCommand.getText(),\n            CommandType.NORMAL_COMMAND,\n            edtDisplay.getText(),\n            edtFilemask.getText()\n        )\n        commands.setElementAt(CommandWrapper(selectedCommand), selectedIndex)\n\n        parent.enableSave()\n    }\n\n\n    override fun insertUpdate(e: DocumentEvent?) = modifyCommand()\n\n    override fun removeUpdate(e: DocumentEvent?) = modifyCommand()\n\n    override fun changedUpdate(e: DocumentEvent?) = modifyCommand()\n\n    override fun valueChanged(e: ListSelectionEvent) {\n        if (!e.valueIsAdjusting) {\n            updateComponents()\n        }\n    }\n\n    fun getCommands(): List<Command> = mutableListOf<Command>().apply {\n        for (cw in commands) {\n            add(cw.command)\n        }\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/commands/EditCommandsDialog.kt",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2026 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.dialog.commands\n\nimport com.mucommander.command.CommandException\nimport com.mucommander.command.CommandManager\nimport com.mucommander.ui.dialog.FocusDialog\nimport com.mucommander.ui.layout.XBoxPanel\nimport org.slf4j.LoggerFactory\nimport java.awt.BorderLayout\nimport java.awt.Component\nimport java.awt.FlowLayout\nimport java.awt.Frame\nimport java.awt.event.ActionEvent\nimport java.awt.event.ActionListener\nimport java.io.IOException\nimport javax.swing.JButton\nimport javax.swing.JPanel\nimport javax.swing.JTabbedPane\n\n/**\n * @author Oleg Trifonov\n * Created on 10/10/14.\n */\nclass EditCommandsDialog(\n    owner: Frame,\n    locationRelativeComp: Component\n) :\n    FocusDialog(owner, i18n(\"EditCommands.label\"), locationRelativeComp), ActionListener {\n\n    private val log = LoggerFactory.getLogger(EditCommandsDialog::class.java)\n\n    private val btnApply: JButton\n    private val btnOk: JButton\n    private val btnCancel: JButton\n    private val panels = mutableListOf<CommandsPanel>()\n\n\n\n\n    init {\n        // Initializes the tabbed pane.\n        val tabbedPane = JTabbedPane(JTabbedPane.TOP)\n\n        CommandsPanel(this, CommandManager.VIEWER_ALIAS).also {\n            tabbedPane.addTab(i18n(\"EditCommands.group.view\"), it)\n            panels.add(it)\n        }\n        CommandsPanel(this, CommandManager.EDITOR_ALIAS).also {\n            tabbedPane.addTab(i18n(\"EditCommands.group.edit\"), it)\n            panels.add(it)\n\n        }\n        CommandsPanel(this, null).also {\n            tabbedPane.addTab(i18n(\"EditCommands.group.others\"), it)\n            panels.add(it)\n        }\n\n        // Adds the tabbed pane\n        val contentPane = contentPane.apply {\n            setLayout(BorderLayout())\n            add(tabbedPane, BorderLayout.CENTER)\n        }\n\n        // Buttons panel.\n        val buttonsPanel = XBoxPanel().apply {\n            btnApply = JButton(i18n(\"apply\")).apply {\n                setEnabled(false)\n                addActionListener(this@EditCommandsDialog)\n            }\n            add(btnApply)\n            addSpace(20)\n            btnOk = JButton(i18n(\"ok\")).apply {\n                setEnabled(false)\n                addActionListener(this@EditCommandsDialog)\n            }\n            add(btnOk)\n            btnCancel = JButton(i18n(\"cancel\")).apply {\n                addActionListener(this@EditCommandsDialog)\n            }\n            add(btnCancel)\n        }\n\n        // Aligns the button panel to the right.\n        val tempPanel = JPanel(FlowLayout(FlowLayout.RIGHT)).apply {\n            add(buttonsPanel)\n        }\n        contentPane.add(tempPanel, BorderLayout.SOUTH)\n\n        getRootPane().setDefaultButton(btnOk)\n    }\n\n\n    override fun actionPerformed(e: ActionEvent) {\n        val source = e.getSource()\n\n        if (source === btnOk || source === btnApply) {\n            commit()\n        }\n\n        if (source === btnOk || source === btnCancel) {\n            dispose()\n        }\n    }\n\n\n    fun enableSave() {\n        btnApply.setEnabled(true)\n        btnOk.setEnabled(true)\n    }\n\n\n    private fun commit() {\n        // clear all Normal commands\n        CommandManager.removeAllNormalCommands()\n        // register all commands from editor\n        panels.forEach {\n            for (cmd in it.getCommands()) {\n                CommandManager.registerCommand(cmd)\n            }\n        }\n        // write file\n        try {\n            CommandManager.writeCommands()\n        } catch (e: IOException) {\n            log.error(\"Commands write error\", e)\n        } catch (e: CommandException) {\n            log.error(\"Commands error\", e)\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/customization/CommandBarDialog.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.customization;\r\n\r\nimport com.mucommander.commons.collections.AlteredVector;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.action.ActionManager;\r\nimport com.mucommander.ui.action.ActionProperties;\r\nimport com.mucommander.ui.action.impl.CustomizeCommandBarAction;\r\nimport com.mucommander.ui.layout.YBoxPanel;\r\nimport com.mucommander.ui.list.DynamicHorizontalWrapList;\r\nimport com.mucommander.ui.list.DynamicList;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.commandbar.CommandBarAttributes;\r\nimport com.mucommander.ui.main.commandbar.CommandBarButtonForDisplay;\r\nimport com.mucommander.ui.main.commandbar.CommandBarIO;\r\nimport com.mucommander.ui.text.RecordingKeyStrokeTextField;\r\n\r\nimport javax.swing.*;\r\nimport javax.swing.Box.Filler;\r\nimport java.awt.*;\r\nimport java.awt.datatransfer.DataFlavor;\r\nimport java.awt.datatransfer.Transferable;\r\nimport java.awt.datatransfer.UnsupportedFlavorException;\r\nimport java.awt.event.KeyEvent;\r\nimport java.io.IOException;\r\nimport java.util.*;\r\nimport java.util.List;\r\n\r\n/**\r\n * Dialog used to customize the command-bar.\r\n * \r\n * @author Arik Hadas\r\n */\r\npublic class CommandBarDialog extends CustomizeDialog {\r\n\t\r\n\t/** List that contains all available buttons, i.e buttons that are not used by the command bar */\r\n\tprivate DynamicHorizontalWrapList<JButton> commandBarAvailableButtonsList;\r\n\t/** List that contains the command-bar regular buttons (i.e, not alternative buttons) */\r\n\tprivate DynamicList<JButton>  commandBarButtonsList;\r\n\t/** List that contains the command-bar alternative buttons */\r\n\tprivate DynamicList<JButton> commandBarAlternateButtonsList;\r\n\t\r\n\t/** Vector that contains the buttons in the available buttons list */\r\n\tprivate AlteredVector<JButton> commandBarAvailableButtons;\r\n\t/** Vector that contains the buttons in the command-bar regular buttons list */\r\n\tprivate AlteredVector<JButton> commandBarButtons;\r\n\t/** Vector that contains the buttons in the command-bar alternate buttons list */\r\n\tprivate AlteredVector<JButton> commandBarAlternateButtons;\r\n\t/** Field which allows the user to enter new KeyStroke modifier for command-bar */\r\n\tprivate RecordingKeyStrokeTextField modifierField;\r\n\t\r\n\t/** Modifier text field length  */\r\n\tprivate static final int MODIFIER_FIELD_MAX_LENGTH = 6;\r\n\t\r\n\t/** The default color of button's border */\r\n\tprivate static final Color JBUTTON_BACKGROUND_COLOR = UIManager.getColor(\"button.background\");\r\n\t\r\n\t/** Comparator for buttons according to their text */\r\n\tprivate static final Comparator<JButton> BUTTONS_COMPARATOR = (b1, b2) -> {\r\n        if (b1.getText() == null)\r\n            return 1;\r\n        if (b2.getText() == null)\r\n            return -1;\r\n        return b1.getText().compareTo(b2.getText());\r\n    };\r\n\t\r\n    public CommandBarDialog(MainFrame mainFrame) {\r\n\t\tsuper(mainFrame.getJFrame(), ActionProperties.getActionLabel(CustomizeCommandBarAction.Descriptor.ACTION_ID));\r\n\t}\r\n\t\r\n    @Override\r\n    protected void componentChanged() {\r\n    \tsetCommitButtonsEnabled(getNumberOfButtons() > 0 && (areActionsChanged() || areAlternativeActionsChanged() || isModifierChanged()));    \t\t\r\n    }\r\n    \r\n    @Override\r\n    protected void commit() {\r\n\t\tint nbNewActions = getNumberOfButtons();\r\n\t\tString[] newActionIds = new String[nbNewActions];\r\n\t\tfor (int i=0; i<nbNewActions; ++i) {\r\n\t\t\tnewActionIds[i] = ((CommandBarButtonForDisplay) commandBarButtons.get(i)).getActionId();\r\n\t\t}\r\n\t\t\r\n\t\tint nbNewAlternativeActions = commandBarAlternateButtons.size();\r\n\t\tString[] newAlternativeActionIds = new String[nbNewAlternativeActions];\r\n\t\tfor (int i=0; i<nbNewAlternativeActions; ++i) {\r\n\t\t\tObject button = commandBarAlternateButtons.get(i);\r\n\t\t\tnewAlternativeActionIds[i] = (button != null) ? \r\n\t\t\t\t\t\t\t\t\t\t((CommandBarButtonForDisplay) button).getActionId() : null;\r\n\t\t}\r\n\t\t\r\n\t\tCommandBarAttributes.setAttributes(newActionIds, newAlternativeActionIds, modifierField.getKeyStroke());\r\n\t\tCommandBarIO.setModified();\r\n\t}\r\n\r\n\t@Override\r\n    protected JPanel createCustomizationPanel() {\r\n\t\tJPanel panel = new JPanel(new BorderLayout());\r\n\t\t\r\n\t\tcommandBarAvailableButtons   = new AlteredVector<>();\r\n\t\tcommandBarButtons            = new AlteredVector<>();\r\n\t\tcommandBarAlternateButtons   = new AlteredVector<>();\r\n\t\t\r\n\t\t// a Set that contains all actions that are used by the command-bar (as regular or alternate buttons).\r\n\t\tSet<String> usedActions = new HashSet<>();\r\n\t\tusedActions.addAll(initCommandBarActionsList());\r\n\t\tusedActions.addAll(initCommandBarAlternateActionsList());\r\n\t\tinitActionsPoolList(usedActions);\r\n\t\t\r\n\t\tpanel.add(createAvailableButtonsPanel(), BorderLayout.CENTER);\r\n\t\tpanel.add(createCommandBarPanel(), BorderLayout.SOUTH);\r\n\t\t\r\n\t\treturn panel;\r\n\t}\r\n    \r\n    private boolean areActionsChanged() {\r\n    \t// Fetch command-bar action ids\r\n    \tString[] commandBarActionIds = CommandBarAttributes.getActions();\r\n    \tint nbActions = commandBarActionIds.length;\r\n    \t\r\n    \tif (nbActions != getNumberOfButtons())\r\n    \t\treturn true;\r\n    \t\r\n    \tfor (int i=0; i<nbActions; ++i) {\r\n    \t\tCommandBarButtonForDisplay buttonI = (CommandBarButtonForDisplay) commandBarButtons.get(i);\r\n    \t\tif (buttonI == null) {\r\n    \t\t\tif (commandBarActionIds[i] != null)\r\n    \t\t\t\treturn true;\r\n    \t\t}\r\n    \t\telse if (!buttonI.getActionId().equals(commandBarActionIds[i]))\r\n    \t\t\treturn true;\r\n    \t}\r\n    \treturn false;\r\n    }\r\n    \r\n    private boolean areAlternativeActionsChanged() {\r\n    \t// Fetch command-bar alternative actions\r\n    \tString[] commandBarAlternativeActionIds = CommandBarAttributes.getAlternateActions();\r\n    \tint nbActions = commandBarAlternativeActionIds.length;\r\n    \t\r\n    \tif (nbActions != commandBarAlternateButtons.size())\r\n    \t\treturn true;\r\n    \t\r\n    \tfor (int i=0; i<nbActions; ++i) {\r\n    \t\tCommandBarButtonForDisplay buttonI = (CommandBarButtonForDisplay) commandBarAlternateButtons.get(i);\r\n    \t\tif (buttonI == null) {\r\n    \t\t\tif (commandBarAlternativeActionIds[i] != null)\r\n    \t\t\t\treturn true;\r\n    \t\t}\r\n    \t\telse if (!buttonI.getActionId().equals(commandBarAlternativeActionIds[i]))\r\n    \t\t\treturn true;\r\n    \t}\r\n    \treturn false;\r\n    }\r\n    \r\n    private boolean isModifierChanged() {\r\n    \treturn !modifierField.getKeyStroke().equals(CommandBarAttributes.getModifier());\r\n    }\r\n    \r\n\tprivate Collection<String> initCommandBarActionsList() {\r\n\t\tString[] commandBarActionIds = CommandBarAttributes.getActions();\r\n\t\t//int nbCommandBarActionIds = commandBarActionIds.length;\r\n        for (String commandBarActionId : commandBarActionIds)\r\n            commandBarButtons.add(CommandBarButtonForDisplay.create(commandBarActionId));\r\n\t\t\r\n\t\tcommandBarButtonsList = new DynamicList<>(commandBarButtons);\r\n\t\t\r\n\t\t// Set lists cells renderer\r\n\t\tcommandBarButtonsList.setCellRenderer(new CommandBarButtonListCellRenderer());\r\n\t\t// Horizontal layout\r\n\t\tcommandBarButtonsList.setLayoutOrientation(JList.HORIZONTAL_WRAP);\r\n\t\t// All buttons appear in one row\r\n\t\tcommandBarButtonsList.setVisibleRowCount(1);\r\n\t\t// Drag operations are supported\r\n\t\tcommandBarButtonsList.setDragEnabled(true);\r\n\t\t// Set transfer handler\r\n\t\tcommandBarButtonsList.setTransferHandler(new TransferHandler(){\r\n\t\t\t\r\n\t\t\t@Override\r\n\t\t\tpublic int getSourceActions(JComponent c) {\r\n\t\t\t\treturn MOVE;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t@Override\r\n\t\t\tpublic Transferable createTransferable(JComponent c) {\r\n\t\t\t\tif (c instanceof JList list) {\r\n                    return new TransferableButton((JButton) list.getSelectedValue());\r\n\t\t\t\t}\r\n\t\t\t\treturn null;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t@Override\r\n\t\t\tpublic void exportDone(JComponent c, Transferable t, int action) {\r\n\t\t\t\tif (action == TransferHandler.MOVE) {\r\n\t\t\t\t\tJList list = (JList) c;\r\n\t\t\t\t\tremoveCommandBarButtonAtIndex(list.getSelectedIndex());\r\n\t\t\t\t\tcomponentChanged();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t@Override\r\n\t\t\tpublic boolean canImport(TransferHandler.TransferSupport support) {\r\n\t\t\t\treturn support.isDataFlavorSupported(TransferableButton.buttonFlavor);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t@Override\r\n\t\t\tpublic boolean importData(TransferHandler.TransferSupport support) {\r\n\t\t\t\tif (!canImport(support))\r\n\t\t\t\t\treturn false;\r\n\t\t\t\ttry {\r\n\t\t\t\t\tPoint dropLocation = support.getDropLocation().getDropPoint();\r\n\t\t\t\t\tJButton button = (JButton) support.getTransferable().getTransferData(TransferableButton.buttonFlavor);\r\n\t\t\t\t\tint index = addCommandBarButtonAtLocation(dropLocation, button);\r\n\t\t\t\t\tcommandBarButtonsList.ensureIndexIsVisible(index);\r\n\t\t\t\t\tcommandBarButtonsList.repaint();\r\n\t\t\t\t\treturn true;\r\n\t\t\t\t} catch (UnsupportedFlavorException | IOException e) {\r\n\t\t\t\t\te.printStackTrace();\r\n\t\t\t\t}\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t});\r\n\t\t\r\n\t\t// Set list's background color\r\n\t\tcommandBarButtonsList.setBackground(JBUTTON_BACKGROUND_COLOR);\r\n\r\n\t\tcommandBarButtonsList.setDropMode(DropMode.INSERT);\r\n\t\t\r\n\t\treturn Arrays.asList(commandBarActionIds);\r\n\t}\r\n\t\r\n\tprivate void removeCommandBarButtonAtIndex(int index) {\r\n\t\tcommandBarButtons.remove(index);\r\n\t\tif (commandBarButtons.isEmpty()) {\r\n\t\t\tcommandBarButtons.add(null);\r\n\t\t\tcommandBarButtonsList.setDropMode(DropMode.ON);\r\n\t\t}\r\n\t\tJButton alternateButtonAtIndex = commandBarAlternateButtons.remove(index);\r\n\t\tif (alternateButtonAtIndex != null)\r\n\t\t\tinsertInOrder(commandBarAvailableButtons, alternateButtonAtIndex);\r\n\t}\r\n\t\r\n\tprivate int getNumberOfButtons() {\r\n\t\tint commandBarButtonsSize = commandBarButtons.size();\r\n\t\treturn commandBarButtonsSize == 1 && commandBarButtons.getFirst() == null ? 0 : commandBarButtonsSize;\r\n\t}\r\n\t\r\n\tprivate int addCommandBarButtonAtLocation(Point dropLocation, JButton button) {\r\n\t\tif (getNumberOfButtons() == 0) {\r\n\t\t\tint index = 0;\r\n\t\t\tcommandBarButtons.set(index, button);\r\n\t\t\tcommandBarAlternateButtons.add(index, null);\r\n\t\t\tcommandBarButtonsList.setDropMode(DropMode.INSERT);\r\n\t\t\treturn index;\r\n\t\t} else {\r\n\t\t\tint index = commandBarButtonsList.locationToIndex(dropLocation);\r\n\t\t\tindex += dropLocation.x > commandBarButtonsList.indexToLocation(index).x + CommandBarButtonForDisplay.PREFERRED_SIZE.width/2 ? 1 : 0;\r\n\t\t\tcommandBarButtons.add(index, button);\r\n\t\t\tcommandBarAlternateButtons.add(index, null);\r\n\t\t\treturn index;\r\n\t\t}\r\n\t}\r\n\t\r\n\tprivate Collection<String> initCommandBarAlternateActionsList() {\r\n\t\tString[] commandBarActionIds = CommandBarAttributes.getAlternateActions();\r\n\t\t//int nbCommandBarActionIds = commandBarActionIds.length;\r\n        for (String commandBarActionId : commandBarActionIds) {\r\n            commandBarAlternateButtons.add(CommandBarButtonForDisplay.create(commandBarActionId));\r\n        }\r\n\t\t\r\n\t\tcommandBarAlternateButtonsList = new DynamicList<>(commandBarAlternateButtons);\r\n\t\t\r\n\t\t// Set lists cells renderer\r\n\t\tcommandBarAlternateButtonsList.setCellRenderer(new CommandBarAlternativeButtonListRenderer());\r\n\t\t// Horizontal layout\r\n\t\tcommandBarAlternateButtonsList.setLayoutOrientation(JList.HORIZONTAL_WRAP);\r\n\t\t// All buttons appear in one row\r\n\t\tcommandBarAlternateButtonsList.setVisibleRowCount(1);\r\n\t\t// Drag operations are supported\r\n\t\tcommandBarAlternateButtonsList.setDragEnabled(true);\r\n\t\t// Set transfer handler\r\n\t\tcommandBarAlternateButtonsList.setTransferHandler(new TransferHandler() {\r\n\t\t\r\n\t\t\t@Override\r\n\t\t\tpublic int getSourceActions(JComponent c) {\r\n\t\t\t\treturn MOVE;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t@Override\r\n\t\t\tpublic Transferable createTransferable(JComponent c) {\r\n\t\t\t\tif (c instanceof JList list) {\r\n                    return new TransferableButton((JButton) list.getSelectedValue());\r\n\t\t\t\t}\r\n\t\t\t\treturn null;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t@Override\r\n\t\t\tpublic void exportDone(JComponent c, Transferable t, int action) {\r\n\t\t\t\tif (action == TransferHandler.MOVE) {\r\n\t\t\t\t\tJList list = (JList) c;\r\n\t\t\t\t\tcommandBarAlternateButtons.set(list.getSelectedIndex(), null);\r\n\t\t\t\t\tcomponentChanged();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t@Override\r\n\t\t\tpublic boolean canImport(TransferHandler.TransferSupport support) {\r\n\t\t\t\treturn support.isDataFlavorSupported(TransferableButton.buttonFlavor);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t@Override\r\n\t\t\tpublic boolean importData(TransferHandler.TransferSupport support) {\r\n\t\t\t\tif (!canImport(support))\r\n\t\t\t\t\treturn false;\r\n\t\t\t\ttry {\r\n\t\t\t\t\tPoint dropLocation = support.getDropLocation().getDropPoint();\r\n\t\t\t\t\tint index = commandBarButtonsList.locationToIndex(dropLocation);\r\n\t\t\t\t\tJButton prevButton = commandBarAlternateButtons.get(index);\r\n\t\t\t\t\tif (prevButton != null) {\r\n                        insertInOrder(commandBarAvailableButtons, prevButton);\r\n                    }\r\n\r\n\t\t\t\t\tcommandBarAlternateButtons.set(index, (JButton) support.getTransferable().getTransferData(TransferableButton.buttonFlavor));\r\n\t\t\t\t\tcommandBarAlternateButtonsList.ensureIndexIsVisible(index);\r\n\t\t\t\t\tcommandBarAlternateButtonsList.repaint();\r\n\t\t\t\t\treturn true;\r\n\t\t\t\t} catch (UnsupportedFlavorException | IOException e) {\r\n\t\t\t\t\te.printStackTrace();\r\n\t\t\t\t}\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t});\r\n\t\t\r\n\t\t// Set list's background color\r\n\t\tcommandBarAlternateButtonsList.setBackground(JBUTTON_BACKGROUND_COLOR);\r\n\t\t\r\n\t\tcommandBarAlternateButtonsList.setDropMode(DropMode.ON);\r\n\t\t\r\n\t\treturn Arrays.asList(commandBarActionIds);\r\n\t}\r\n\t\r\n\tprivate void initActionsPoolList(Set<String> usedActions) {\r\n\t\tIterator<String> actionIds = ActionManager.getActionIds();\r\n\t\twhile(actionIds.hasNext()) {\r\n            String actionId = actionIds.next();\r\n            // Filter out actions that are currently used in the command bar, and those that are parametrized\r\n\t\t\tif (!usedActions.contains(actionId) && !ActionProperties.getActionDescriptor(actionId).isParameterized())\r\n\t\t\t\tinsertInOrder(commandBarAvailableButtons, CommandBarButtonForDisplay.create(actionId));\t\t\t\r\n\t\t}\r\n\r\n\t\tcommandBarAvailableButtonsList = new DynamicHorizontalWrapList<>(commandBarAvailableButtons, CommandBarButtonForDisplay.PREFERRED_SIZE.width, 6);\r\n\t\t\r\n\t\tcommandBarAvailableButtonsList.setCellRenderer(new AvailableButtonCellListRenderer());\r\n\t\tcommandBarAvailableButtonsList.setDragEnabled(true);\r\n\t\tcommandBarAvailableButtonsList.setTransferHandler(new TransferHandler() {\r\n\t\t\r\n\t\t\t@Override\r\n\t\t\tpublic int getSourceActions(JComponent c) {\r\n\t\t\t\treturn MOVE;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t@Override\r\n\t\t\tpublic Transferable createTransferable(JComponent c) {\r\n\t\t\t\tif (c instanceof JList) {\r\n\t\t\t\t\treturn new TransferableButton((JButton) ((JList) c).getSelectedValue());\r\n\t\t\t\t}\r\n\t\t\t\treturn null;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t@Override\r\n\t\t\tpublic void exportDone(JComponent c, Transferable t, int action) {\r\n\t\t\t\tif (action == TransferHandler.MOVE) {\r\n\t\t\t\t\tif (c instanceof JList list) {\r\n                        Object button = list.getSelectedValue();\r\n                        if (button instanceof JButton) {\r\n                            commandBarAvailableButtons.remove(button);\r\n                        } else {\r\n                            throw new RuntimeException(\"JButton expected\");\r\n                        }\r\n\t\t\t\t\t\t//commandBarAvailableButtons.remove(((JList<JButton>) c).getSelectedValue());\r\n                    }\r\n\t\t\t\t\tcomponentChanged();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t@Override\r\n\t\t\tpublic boolean canImport(TransferHandler.TransferSupport support) {\r\n                return support.isDataFlavorSupported(TransferableButton.buttonFlavor);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t@Override\r\n\t\t\tpublic boolean importData(TransferHandler.TransferSupport support) {\r\n\t\t\t\tif (!canImport(support))\r\n\t\t\t\t\treturn false;\r\n\t\t\t\ttry {\r\n\t\t\t\t\tint insertedIndex = insertInOrder(commandBarAvailableButtons, (JButton) support.getTransferable().getTransferData(TransferableButton.buttonFlavor));\r\n\t\t\t\t\tcommandBarAvailableButtonsList.ensureIndexIsVisible(insertedIndex);\r\n\t\t\t\t\treturn true;\r\n\t\t\t\t} catch (UnsupportedFlavorException | IOException e) {\r\n\t\t\t\t\te.printStackTrace();\r\n\t\t\t\t}\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t});\r\n\t\t\r\n\t\tcommandBarAvailableButtonsList.setBackground(JBUTTON_BACKGROUND_COLOR);\r\n\t\t\r\n\t\tcommandBarAvailableButtonsList.setDropMode(DropMode.ON);\r\n\t}\r\n\t\r\n\tprivate JPanel createAvailableButtonsPanel() {\r\n\t\tJPanel panel = new JPanel(new GridLayout(1,0));\r\n\t\tpanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"command_bar_customize_dialog.available_actions\")));\r\n\t\t\r\n\t\tJScrollPane scrollPane = new JScrollPane(commandBarAvailableButtonsList, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);\r\n\t\tscrollPane.setBorder(null);\r\n\t\tpanel.add(scrollPane);\r\n\t\t\r\n\t\treturn panel;\r\n\t}\r\n\t\r\n\tprivate JPanel createCommandBarPanel() {\r\n\t\tYBoxPanel panel = new YBoxPanel();\r\n\t\tpanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"preview\")));\r\n\t\t\r\n\t\tpanel.add(Box.createRigidArea(new Dimension(0, 5)));\r\n\t\t\r\n\t\tYBoxPanel listsPanel = new YBoxPanel();\r\n\t\tlistsPanel.add(commandBarButtonsList);\r\n\t\tlistsPanel.add(commandBarAlternateButtonsList);\r\n\r\n\t\tJScrollPane scrollPane = new JScrollPane(listsPanel, JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);\r\n\t\tscrollPane.setBorder(null);\r\n\t\tpanel.add(scrollPane);\r\n\r\n\t\tpanel.add(Box.createRigidArea(new Dimension(0, 5)));\r\n\t\t\r\n\t\tpanel.add(new JLabel(\"(\" + Translator.get(\"command_bar_dialog.help\") + \")\"));\r\n\t\t\r\n\t\tpanel.add(Box.createRigidArea(new Dimension(0, 5)));\r\n\t\t\r\n\t\tJPanel modifierPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\r\n\t\tmodifierField = new RecordingKeyStrokeTextField(MODIFIER_FIELD_MAX_LENGTH, CommandBarAttributes.getModifier()) {\r\n\t\t\t\r\n\t\t\t@Override\r\n            public void setText(String t) {\r\n\t\t\t\tsuper.setText(t);\r\n\t\t\t\tcomponentChanged();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t@Override\r\n            public void keyPressed(KeyEvent e) {\r\n\t\t\t\tint pressedKeyCode = e.getKeyCode();\r\n\t\t\t\t// Accept modifier keys only\r\n\t\t\t\tif (pressedKeyCode == KeyEvent.VK_CONTROL || pressedKeyCode == KeyEvent.VK_ALT\r\n\t\t\t\t\t\t|| pressedKeyCode == KeyEvent.VK_META || pressedKeyCode == KeyEvent.VK_SHIFT)\r\n\t\t\t\t\tsuper.keyPressed(e);\r\n\t\t\t}\r\n\t\t};\r\n\t\t\r\n\t\tmodifierPanel.add(new JLabel(Translator.get(\"command_bar_customize_dialog.modifier\")));\r\n\t\tmodifierPanel.add(modifierField);\r\n\t\tpanel.add(modifierPanel);\r\n\t\t\r\n\t\treturn panel;\r\n\t}\r\n\t\r\n\tprivate static class TransferableButton implements Transferable {\r\n    \tstatic DataFlavor buttonFlavor = new DataFlavor(CommandBarButtonForDisplay.class, null);\r\n    \t\r\n    \tprivate final JButton button;\r\n    \t\r\n    \tTransferableButton(JButton button) {\r\n    \t\tthis.button = button;\r\n    \t}\r\n    \t\r\n            public Object getTransferData(DataFlavor flavor) {\r\n    \t\treturn button;\r\n    \t}\r\n    \t\r\n    \tpublic DataFlavor[] getTransferDataFlavors() {\r\n    \t\treturn new DataFlavor[] {buttonFlavor};\r\n    \t}\r\n    \r\n    \tpublic boolean isDataFlavorSupported(DataFlavor flavor) {\r\n    \t\treturn buttonFlavor.equals(flavor);\r\n    \t}\r\n    }\r\n\t\r\n\tprivate static class CommandBarButtonListCellRenderer implements ListCellRenderer<JButton> {\r\n        @Override\r\n        public Component getListCellRendererComponent(JList<? extends JButton> list, JButton value, int index, boolean isSelected, boolean cellHasFocus) {\r\n            return value == null ? createBoxFiller() : value;\r\n        }\r\n    }\r\n\r\n\tprivate static class CommandBarAlternativeButtonListRenderer implements ListCellRenderer<JButton> {\r\n\r\n\t\tpublic Component getListCellRendererComponent(JList list, JButton value,\r\n\t\t\t\tint index, boolean isSelected, boolean cellHasFocus) {\r\n\t\t\treturn value == null ? createBoxFiller() : value;\r\n        }\r\n\t}\r\n\r\n\tprivate static class AvailableButtonCellListRenderer implements ListCellRenderer<JButton> {\r\n\r\n\t\tpublic Component getListCellRendererComponent(JList list, JButton value, int index, boolean isSelected, boolean cellHasFocus) {\r\n\t\t\tJPanel panel = new JPanel(new BorderLayout());\r\n\t\t\tpanel.add(value, BorderLayout.CENTER);\r\n\t\t\tpanel.setToolTipText(value.getToolTipText());\r\n\t\t\treturn panel;\r\n        }\r\n\t}\t\r\n\r\n\t//////////////////////////\r\n\t///// Helper methods /////\r\n\t//////////////////////////\r\n\t\r\n\tprivate static Filler createBoxFiller() {\r\n\t\tBox.Filler filler = (Filler) Box.createHorizontalGlue();\r\n\t\tfiller.setPreferredSize(CommandBarButtonForDisplay.PREFERRED_SIZE);\t\t\r\n\t\treturn filler;\r\n\t}\r\n\t\r\n\tprivate static int insertInOrder(List<JButton> vector, JButton element) {\r\n\t\tif (!vector.isEmpty()) {\r\n\t\t\tint index = findPlace(vector, element, BUTTONS_COMPARATOR, 0, vector.size() - 1);\r\n\t\t\tvector.add(index, element);\r\n\t\t\treturn index;\r\n\t\t}\r\n\t\telse {\r\n\t\t\tvector.add(element);\r\n\t\t\treturn vector.size();\r\n\t\t}\r\n\t}\r\n\t\r\n\tprivate static int findPlace(List<JButton> vector, JButton element, Comparator<JButton> comparator, int first, int last) {\r\n\t\tif (comparator.compare(vector.get(last), element) < 0)\r\n\t\t\treturn last + 1;\r\n\t\tif (comparator.compare(vector.get(first), element) > 0)\r\n\t\t\treturn first;\r\n\t\tif (last - first == 1)\r\n\t\t\treturn last;\r\n\t\t\r\n\t\tint middle = (first + last) / 2;\r\n\t\tint result = comparator.compare(vector.get(middle), element);\r\n\t\treturn result > 0 ?\r\n\t\t\t\tfindPlace(vector, element, comparator, first, middle) :\r\n\t\t\t\tfindPlace(vector, element, comparator, middle, last);\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/customization/CustomizeDialog.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.customization;\r\n\r\nimport com.mucommander.ui.dialog.FocusDialog;\r\nimport com.mucommander.ui.layout.XBoxPanel;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.*;\r\nimport java.awt.event.ActionEvent;\r\nimport java.awt.event.ActionListener;\r\n\r\n/**\r\n * Dialog which let users customize UI element.\r\n * \r\n * @author Arik Hadas.\r\n */\r\npublic abstract class CustomizeDialog extends FocusDialog implements ActionListener {\r\n\t\r\n\tprivate static final Dimension PREFERRED_SIZE = new Dimension(700, 500);\r\n\t\r\n\t/** Apply button. */\r\n    private JButton     applyButton;\r\n\t/** OK button. */\r\n    private JButton     okButton;\r\n    /** Cancel button. */\r\n    private JButton     cancelButton;\r\n\r\n\tCustomizeDialog(Frame parent, String title) {\r\n        super(parent, title, parent);\r\n        initUI();\r\n    }\r\n\r\n    public CustomizeDialog(Dialog parent, String title) {\r\n        super(parent, title, parent);\r\n        initUI();\r\n    }\r\n  \r\n    private void initUI() {\r\n    \t// Get content-pane and set its layout.\r\n        Container contentPane = getContentPane();\r\n        contentPane.setLayout(new BorderLayout());\r\n        \r\n        // Add customization panel\r\n        contentPane.add(createCustomizationPanel(), BorderLayout.CENTER);\r\n    \t\r\n    \t// Buttons panel.\r\n        XBoxPanel buttonsPanel = new XBoxPanel();\r\n        buttonsPanel.add(applyButton = new JButton(i18n(\"apply\")));\r\n        buttonsPanel.addSpace(20);\r\n        buttonsPanel.add(okButton     = new JButton(i18n(\"ok\")));\r\n        buttonsPanel.add(cancelButton = new JButton(i18n(\"cancel\")));\r\n        \r\n        // Disable \"commit buttons\".\r\n        applyButton.setEnabled(false);\r\n        okButton.setEnabled(false);\r\n        \r\n        // Buttons listening.\r\n        applyButton.addActionListener(this);\r\n        okButton.addActionListener(this);\r\n        cancelButton.addActionListener(this);\r\n        \r\n        // Aligns the button panel to the right.\r\n        JPanel tempPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));\r\n        tempPanel.add(buttonsPanel);\r\n        contentPane.add(tempPanel, BorderLayout.SOUTH);\r\n        \r\n        // Selects OK when enter is pressed\r\n        getRootPane().setDefaultButton(cancelButton);\r\n        \r\n        // Set preferred size\r\n        setPreferredSize(PREFERRED_SIZE);\r\n    }\r\n    \r\n    protected abstract JPanel createCustomizationPanel();\r\n\r\n    protected abstract void commit();\r\n    \r\n    protected abstract void componentChanged();\r\n    \r\n    // - Listener code ----------------------------------------------------------\r\n    // --------------------------------------------------------------------------\r\n    /**\r\n     * Reacts to buttons being pushed.\r\n     */\r\n    public void actionPerformed(ActionEvent e) {\r\n        Object source = e.getSource();\r\n\r\n        // Commit changes\r\n        if (source == okButton || source == applyButton) {\r\n            commit();\r\n        }\r\n\r\n        // Disable OK & Apply buttons\r\n        if (source == applyButton) {\r\n            setCommitButtonsEnabled(false);\r\n        }\r\n        \r\n        // Dispose dialog\r\n        if (source == okButton || source == cancelButton) {\r\n            dispose();\r\n        }\r\n    }\r\n    \r\n    void setCommitButtonsEnabled(boolean enabled) {\r\n    \tapplyButton.setEnabled(enabled);\r\n    \tokButton.setEnabled(enabled);\r\n    \t\r\n    \t// if commit buttons are enabled then set the \"okButton\" as default button, \r\n    \t// otherwise set the \"cancelButton\" as default button.\r\n    \tgetRootPane().setDefaultButton(enabled ? okButton : cancelButton);\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/debug/DebugConsoleAppender.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.debug;\n\nimport java.util.LinkedList;\nimport java.util.List;\n\nimport ch.qos.logback.classic.spi.ILoggingEvent;\nimport ch.qos.logback.core.AppenderBase;\nimport ch.qos.logback.core.Layout;\n\nimport com.mucommander.utils.TcLogging;\nimport com.mucommander.utils.TcLogging.LogLevel;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\n\n/**\n * This <code>java.util.logging</code> <code>Handler</code> collects the last log messages that were published by\n * the different muCommander loggers, so they can be displayed at any time in the {@link DebugConsoleDialog}.\n * Log records are kept in memory as a sliding window. The number of log records is controlled by the\n * {@link TcPreferences#LOG_BUFFER_SIZE} configuration variable: the more records, the more memory is used.\n *\n * @see DebugConsoleDialog\n * @see TcPreferences#LOG_BUFFER_SIZE\n * @author Maxence Bernard, Arik Hadas\n */\npublic class DebugConsoleAppender extends AppenderBase<ILoggingEvent> {\n\n    /** Maximum number of log records to keep in memory */\n    private int bufferSize;\n\n    /** Contains the last LogRecord instances. */\n    private List<LogbackLoggingEvent> loggingEventsList;\n    \n    /** The layout of the logging event representation */\n    private Layout<ILoggingEvent> loggingEventLayout;\n\n    /**\n     * Creates a new <code>DebugConsoleHandler</code>. This constructor is automatically by\n     * <code>java.util.logging</code> when it is configured and should never be called directly.\n     */\n    public DebugConsoleAppender(Layout<ILoggingEvent> loggingEventsLayout) {\n    \tthis.loggingEventLayout = loggingEventsLayout;\n    \t\n        bufferSize = TcConfigurations.getPreferences().getVariable(TcPreference.LOG_BUFFER_SIZE, TcPreferences.DEFAULT_LOG_BUFFER_SIZE);\n        loggingEventsList = new LinkedList<>();\n    }\n\n    /**\n     * Returns the last records that were collected by this handler.\n     *\n     * @return the last records that were collected by this handler.\n     */\n    synchronized LoggingEvent[] getLogRecords() {\n    \tLogbackLoggingEvent[] records = new LogbackLoggingEvent[0];\n    \trecords = loggingEventsList.toArray(records);\n\n    \treturn records;\n    }\n\n\n    /////////////////////////////\n    // Appender implementation //\n    /////////////////////////////\n\n    @Override\n    protected void append(ILoggingEvent record) {\n\t\tif (loggingEventsList.size()== bufferSize) {\n            loggingEventsList.remove(0);\n        }\n\n        loggingEventsList.add(new LogbackLoggingEvent(record));\n\t}\n\n    /**\n     * Wraps a {@link ILoggingEvent} and overrides {@link #toString()} to have it return a properly formatted string\n     * representation of it so that it can be displayed in a {@link javax.swing.JList} or {@link javax.swing.JTable} and\n     * pasted to the clipboard.\n     * It also implements the LoggingEvent interface so that the logging event can be presented in the debug console.\n     */\n    public class LogbackLoggingEvent implements LoggingEvent {\n\n    \t/** The logging event */\n    \tprivate ILoggingEvent loggingEvent;\n\n    \t/** The log level of the event in mucommander's terms */\n    \tprivate LogLevel logLevel;\n\n        LogbackLoggingEvent(ILoggingEvent lr) {\n            this.loggingEvent = lr;\n        }\n\n        /**\n         * Returns a properly formatted string representation of the {@link ILoggingEvent}.\n         * \n         * @return a properly formatted string representation of the {@link ILoggingEvent}.\n         */\n        @Override\n        public String toString() {\n        \treturn loggingEventLayout.doLayout(loggingEvent);\n        }\n        \n        \n        ///////////////////////////////////////\n        /// LogRecordListItem Implementation //\n        ///////////////////////////////////////\n        \n        public boolean isLevelEqualOrHigherThan(LogLevel level) {\n        \treturn getLevel().compareTo(level) <= 0;\n        }\n        \n        public LogLevel getLevel() {\n        \tif (logLevel == null) {\n                logLevel = TcLogging.getLevel(loggingEvent);\n            }\n        \treturn logLevel;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/debug/DebugConsoleDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.debug;\n\nimport java.awt.BorderLayout;\nimport java.awt.Color;\nimport java.awt.Component;\nimport java.awt.Container;\nimport java.awt.Dimension;\nimport java.awt.FlowLayout;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.awt.event.ItemEvent;\nimport java.awt.event.ItemListener;\nimport java.util.Map;\n\nimport javax.swing.*;\n\nimport com.mucommander.ui.combobox.TcComboBox;\nimport com.mucommander.utils.TcLogging;\nimport com.mucommander.utils.TcLogging.LogLevel;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.RefreshAction;\nimport com.mucommander.ui.action.impl.ShowDebugConsoleAction;\nimport com.mucommander.ui.dialog.FocusDialog;\nimport com.mucommander.ui.main.MainFrame;\n\n/**\n * This dialog shows the last log messages collected by {@link DebugConsoleAppender} and allows them to be copied\n * to the clipboard. It also makes it possible to change the log level, the level combo box being preset to the\n * level returned by {@link TcLogging#getLogLevel()}.\n *\n * @see ShowDebugConsoleAction\n * @see DebugConsoleAppender\n * @see TcLogging#setLogLevel(LogLevel)\n * @author Maxence Bernard\n */\npublic class DebugConsoleDialog extends FocusDialog implements ActionListener, ItemListener {\n\n    /** Displays log events, and allows to copy their values to the clipboard */\n    private final JList<LoggingEvent> loggingEventsList = new JList<>();\n\n    /** Allows the log level to be changed */\n    private final JComboBox<LogLevel> levelComboBox = new TcComboBox<>(LogLevel.values());\n\n    /** Closes the debug console when pressed */\n    private final JButton btnClose;\n\n    /** Refreshes the list with the latest log records when pressed */\n    private final JButton btnRefresh;\n\n    /** Show threads tree */\n    private final JButton btnThreads;\n\n    /** Show active threads tree */\n    private final JButton btnActiveThreads;\n\n    /** Dialog size constraints */\n    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(600, 400);\n\n    /** Dialog width should not exceed 360, height is not an issue (always the same) */\n    private final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(700, 500);\n\n    /**\n     * Creates a new {@link DebugConsoleDialog} using the given {@link MainFrame} as a parent.\n     *\n     * @param mainFrame the {@link MainFrame} to use as a parent\n     */\n    public DebugConsoleDialog(MainFrame mainFrame) {\n        super(mainFrame.getJFrame(), ActionProperties.getActionLabel(ShowDebugConsoleAction.Descriptor.ACTION_ID), mainFrame.getJFrame());\n\n        Container contentPane = getContentPane();\n\n        // Autoscroll when dragged\n        loggingEventsList.setAutoscrolls(true);\n        loggingEventsList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);\n        loggingEventsList.setCellRenderer(new DebugListCellRenderer());\n        refreshLogRecords();\n\n        JScrollPane scrollPane = new JScrollPane(loggingEventsList, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);\n        contentPane.add(scrollPane, BorderLayout.CENTER);\n\n        JPanel southPanel = new JPanel(new BorderLayout());\n        southPanel.add(createComboPanel(), BorderLayout.WEST);\n\n        JPanel buttonPanel = new JPanel(new FlowLayout());\n\n        btnThreads = new JButton(i18n(\"debug_console_dialog.threads\"));\n        btnThreads.addActionListener(this);\n        buttonPanel.add(btnThreads);\n\n        btnActiveThreads = new JButton(i18n(\"debug_console_dialog.active_threads\"));\n        btnActiveThreads.addActionListener(this);\n        buttonPanel.add(btnActiveThreads);\n\n        btnRefresh = new JButton(new RefreshAction.Descriptor().getLabel());\n        btnRefresh.addActionListener(this);\n        buttonPanel.add(btnRefresh);\n\n        btnClose = new JButton(i18n(\"close\"));\n        btnClose.addActionListener(this);\n        buttonPanel.add(btnClose);\n\n        southPanel.add(buttonPanel, BorderLayout.EAST);\n        contentPane.add(southPanel, BorderLayout.SOUTH);\n\n        setInitialFocusComponent(btnClose);\n        setMinimumSize(MINIMUM_DIALOG_DIMENSION);\n        setMaximumSize(MAXIMUM_DIALOG_DIMENSION);\n\n        setInitialFocusComponent(btnClose);\n    }\n\n    /**\n     * Creates and returns a panel containing the level combo box and a leading localized label describing it.\n     *\n     * @return a panel containing the level combo box and a leading localized label describing it\n     */\n    private JPanel createComboPanel() {\n        JPanel comboPanel = new JPanel(new FlowLayout());\n        comboPanel.add(new JLabel(i18n(\"debug_console_dialog.level\")+\":\"));\n        LogLevel logLevel = TcLogging.getLogLevel();\n\n        levelComboBox.setSelectedItem(logLevel);\n        levelComboBox.addItemListener(this);\n\n        comboPanel.add(levelComboBox);\n\n        return comboPanel;\n    }\n\n    /**\n     * Refreshes the JList with the log records contained by {@link DebugConsoleAppender}.\n     */\n    private void refreshLogRecords() {\n        DebugConsoleAppender handler = TcLogging.getDebugConsoleAppender();\n        if (handler == null) {\n            return;\n        }\n        final LoggingEvent[] records = handler.getLogRecords();\n        final LogLevel currentLogLevel = TcLogging.getLogLevel();\n        if (records == null) {\n            return;\n        }\n        DefaultListModel<LoggingEvent> listModel = new DefaultListModel<>();\n        for (LoggingEvent record : records) {\n            if (record.isLevelEqualOrHigherThan(currentLogLevel)) {\n                listModel.addElement(record);\n            }\n        }\n\n        loggingEventsList.setModel(listModel);\n\n        SwingUtilities.invokeLater(() -> loggingEventsList.ensureIndexIsVisible(records.length-1));\n    }\n    \n    /**\n     * Changes the log level to the selected combo box value.\n     */\n    private void updateLogLevel() {\n        LogLevel newLevel = (LogLevel) levelComboBox.getSelectedItem();\n        if (newLevel != null) {\n            TcLogging.setLogLevel(newLevel);\n        }\n    }\n\n\n\n    public void actionPerformed(ActionEvent e) {\n        Object source = e.getSource();\n\n        if (source == btnRefresh) {\n            refreshLogRecords();\n        } else if (source == btnClose) {\n            dispose();\n        } else if (source == btnThreads) {\n            printThreads(false);\n        } else if (source == btnActiveThreads) {\n            printThreads(true);\n        }\n    }\n\n    private void printThreads(boolean onlyActive) {\n        Map<Thread, StackTraceElement[]> stackTraces = Thread.getAllStackTraces();\n        DefaultListModel<LoggingEvent> model = new DefaultListModel<>();\n        ListModel<LoggingEvent> oldModel = loggingEventsList.getModel();\n        for (int i = 0; i < oldModel.getSize(); i++) {\n            model.add(i, oldModel.getElementAt(i));\n        }\n        model.addElement(buildStringEvent(LogLevel.INFO, \"----------[Threads]--------------\"));\n        for (Thread t : stackTraces.keySet()) {\n            if (onlyActive && t.getState() != Thread.State.RUNNABLE) {\n                continue;\n            }\n\n            model.addElement(buildStringEvent(LogLevel.INFO, t.getName() + \" (\" + t.getState() + \")\"));\n            StackTraceElement[] stackTraceElements = stackTraces.get(t);\n            for (StackTraceElement ste : stackTraceElements) {\n                model.addElement(buildStringEvent(LogLevel.TRACE, \"     \" + ste));\n            }\n        }\n        loggingEventsList.setModel(model);\n    }\n\n\n    private static LoggingEvent buildStringEvent(final LogLevel level, final String s) {\n        return new LoggingEvent() {\n\n            @Override\n            public boolean isLevelEqualOrHigherThan(LogLevel level) {\n                return true;\n            }\n\n            @Override\n            public LogLevel getLevel() {\n                return level;\n            }\n\n            @Override\n            public String toString() {\n                return s;\n            }\n        };\n    }\n\n\n    public void itemStateChanged(ItemEvent e) {\n        // Refresh the log records displayed in the JList whenever the selected level has been changed.\n        int selectedIndex = levelComboBox.getSelectedIndex();\n        if (selectedIndex >= 0) {\n            updateLogLevel();\n            refreshLogRecords();\n        }\n    }\n\n\n\n    /**\n     * Custom {@link ListCellRenderer} that renders {@link LoggingEvent} instances.\n     */\n    private class DebugListCellRenderer extends DefaultListCellRenderer {\n\n        private Color getLevelColor(LogLevel logLevel) {\n            return switch (logLevel) {\n                case ERROR -> Color.RED;\n                case WARNING -> new Color(255, 100, 0);     // Dark orange\n                case CONFIG -> Color.BLUE;\n                case INFO -> Color.BLACK;\n                case DEBUG -> Color.DARK_GRAY;\n                default -> new Color(110, 110, 110);    // Between Color.GRAY and Color.DARK_GRAY\n            };\n        }\n\n        @Override\n        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {\n            if (value == null) {\n                return null;\n            }\n\n            // TODO: line-wrap log items when the text is too long to fit on a single line\n            // A single-column JTable may be the easiest way to go, see:\n            // http://javaspecialists.co.za/archive/newsletter.do?issue=106&locale=en_US\n            // http://forums.sun.com/thread.jspa?threadID=702740&start=0&tstart=0\n\n            // Using a JTextArea with line-wrapping enabled does not work as a JList has by design a fixed height\n            // for cells\n\n            JLabel label = (JLabel)super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);\n            \n            // Change the label's foreground color to match the level of the log record\n            if (!isSelected) {\n                LogLevel level = ((LoggingEvent)value).getLevel();\n                Color color = getLevelColor(level);\n                label.setForeground(color);\n            }\n\n            // TODO: remove this when line-wrapping has been implemented\n            // If component's preferred width is larger than the list's width then the component is not entirely\n            // visible. In that case, we set a tooltip text that will display the whole text when mouse is over the\n            // component\n            String toolTip = loggingEventsList.getVisibleRect().getWidth() < label.getPreferredSize().getWidth() ?\n                    label.getText() : null;\n            // Have to set it to null because of the rubber-stamp rendering scheme (last value is kept)\n            label.setToolTipText(toolTip);\n\n            return label;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/debug/LoggingEvent.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.debug;\n\nimport com.mucommander.utils.TcLogging.LogLevel;\n\n/**\n * Logging events that presented in the debug console should implement this interface\n *\n * @author Arik Hadas\n */\npublic interface LoggingEvent {\n\n\t/**\n\t * Return true if the logging event's level is equal or higher than the given level, false otherwise.\n\t * \n\t * @param level logging event level\n\t * @return true if the logging event's level is equal or higher than the given level, false otherwise\n\t */\n    boolean isLevelEqualOrHigherThan(LogLevel level);\n    \n    /**\n     * Return the logging event's level.\n     * \n     * @return the logging event's level\n     */\n    LogLevel getLevel();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/AbstractCopyDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.file;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.desktop.DesktopManager;\nimport com.mucommander.ui.main.MainFrame;\n\n/**\n * This abstract class allows to factorize some code among its subclasses.\n *\n * @author Maxence Bernard\n */\npublic abstract class AbstractCopyDialog extends TransferDestinationDialog {\n\n    AbstractCopyDialog(MainFrame mainFrame, FileSet files, String title, String labelText, String okText, String errorDialogTitle) {\n        super(mainFrame, files, title, labelText, okText, errorDialogTitle, true);\n    }\n\n    /**\n     * Returns a {@link PathFieldContent} wrapping the given path and a selection corresponding to the filename, with\n     * a few subtleties: the file's extension is <b>not</b> selected for regular files or application containers\n     * (see {@link DesktopManager#isApplication(AbstractFile)}, but selected for directories.\n     * The rationale behind this is that it happens more often that ones wishes to rename a file's name\n     * than its extension. This is not the case for directories where the extension is usually an artifact of a\n     * filename that contains a '.'.\n     *\n     * @param file the file to be copied or renamed\n     * @param path the destination path\n     * @param filenameStart offset to the start of the filename in the given file\n     * @return a {@link PathFieldContent} wrapping the given path and a selection corresponding to the filename\n     */\n    public static PathFieldContent selectDestinationFilename(AbstractFile file, String path, int filenameStart) {\n        int endPosition;   // Index of the last selected character\n\n        // If the current file is a directory and not an application file (e.g. Mac OS X .app directory), select\n        // the whole file name.\n        if (file.isDirectory() && !DesktopManager.isApplication(file)) {\n            endPosition = path.length();\n        }\n        // Otherwise, select the file name without its extension, except when empty ('.DS_Store', for example).\n        else {\n            endPosition = path.lastIndexOf('.');\n\n            // Text is selected so that user can directly type and replace path\n            endPosition = endPosition > filenameStart ? endPosition : path.length();\n        }\n\n        return new PathFieldContent(path, filenameStart, endPosition);\n    }\n\n\n    //////////////////////////////////////////////////////\n    // TransferDestinationDialog partial implementation //\n    //////////////////////////////////////////////////////\n\n    @Override\n    protected PathFieldContent computeInitialPath(FileSet files) {\n        String fieldText;     // Text to display in the destination field.\n        int startPosition; // Index of the first selected character in the destination field.\n        int endPosition;   // Index of the last selected character in the destination field.\n\n        // Fill text field with absolute path, and if there is only one file,\n        // append file's name\n        fieldText = mainFrame.getInactivePanel().getCurrentFolder().getAbsolutePath(true);\n        // Append filename to destination path if there is only one file to copy\n        // and if the file is not a directory that already exists in destination\n        // (otherwise folder would be copied into the destination folder)\n        if (files.size() == 1) {\n            AbstractFile file = files.elementAt(0);\n            AbstractFile destFile;\n\n            startPosition  = fieldText.length();\n\n            if (!(file.isDirectory() && (destFile = FileFactory.getFile(fieldText+file.getName())) != null && destFile.exists() && destFile.isDirectory())) {\n                return selectDestinationFilename(file, fieldText + file.getName(), startPosition);\n            } else {\n                endPosition = fieldText.length();\n            }\n        } else {\n            endPosition = fieldText.length();\n            startPosition = 0;\n        }\n\n        return new PathFieldContent(fieldText, startPosition, endPosition);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/BatchRenameConfirmationDialog.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.file;\r\n\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.ui.action.ActionProperties;\r\nimport com.mucommander.ui.action.impl.BatchRenameAction;\r\nimport com.mucommander.ui.dialog.DialogToolkit;\r\nimport com.mucommander.ui.dialog.FocusDialog;\r\nimport com.mucommander.ui.layout.InformationPane;\r\nimport com.mucommander.ui.layout.YBoxPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.*;\r\nimport java.awt.event.ActionEvent;\r\nimport java.awt.event.ActionListener;\r\n\r\npublic class BatchRenameConfirmationDialog extends FocusDialog implements ActionListener {\r\n\r\n    private final JButton btnRename;\r\n    \r\n    private boolean proceedWithRename = false;\r\n \r\n    BatchRenameConfirmationDialog(MainFrame mainFrame, FileSet files, int changed, int unchanged) {\r\n        super(mainFrame.getJFrame(), ActionProperties.getActionLabel(BatchRenameAction.Descriptor.ACTION_ID), mainFrame.getJFrame());\r\n\r\n        YBoxPanel mainPanel = new YBoxPanel();\r\n        String msg = i18n(\"batch_rename_dialog.proceed_renaming\", Integer.toString(changed), Integer.toString(unchanged));\r\n        mainPanel.add(new InformationPane(msg,\r\n                i18n(\"this_operation_cannot_be_undone\"),\r\n                Font.BOLD, InformationPane.getPredefinedIcon(InformationPane.WARNING_ICON)));\r\n        mainPanel.addSpace(10);\r\n        btnRename = new JButton(i18n(\"rename\"));\r\n        JButton cancelButton = new JButton(i18n(\"cancel\"));\r\n        mainPanel.add(DialogToolkit.createOKCancelPanel(btnRename, cancelButton, getRootPane(), this));\r\n        getContentPane().add(mainPanel);\r\n        setInitialFocusComponent(btnRename);\r\n\r\n        // Call dispose() when dialog is closed\r\n        setDefaultCloseOperation(DISPOSE_ON_CLOSE);\r\n        \r\n        // Size dialog and show it to the screen\r\n        setResizable(false);\r\n        showDialog();\r\n    }\r\n    \r\n    \r\n    ///////////////////////////////////\r\n    // ActionListener implementation //\r\n    ///////////////////////////////////\r\n\r\n    public void actionPerformed(ActionEvent e) {\r\n        if(e.getSource()==btnRename) {\r\n            proceedWithRename = true;\r\n        }\r\n        dispose();\r\n    }\r\n    \r\n    boolean isProceedWithRename() {\r\n        return proceedWithRename;\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/BatchRenameDialog.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.file;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.commons.file.util.PathUtils;\r\nimport com.mucommander.commons.util.StringUtils;\r\nimport com.mucommander.job.BatchRenameJob;\r\nimport com.mucommander.ui.action.ActionProperties;\r\nimport com.mucommander.ui.action.impl.BatchRenameAction;\r\nimport com.mucommander.ui.dialog.FocusDialog;\r\nimport com.mucommander.ui.layout.XAlignedComponentPanel;\r\nimport com.mucommander.ui.layout.XBoxPanel;\r\nimport com.mucommander.ui.layout.YBoxPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport javax.swing.*;\r\nimport javax.swing.event.DocumentEvent;\r\nimport javax.swing.event.DocumentListener;\r\nimport javax.swing.event.TableModelEvent;\r\nimport javax.swing.table.AbstractTableModel;\r\nimport javax.swing.table.TableColumn;\r\nimport javax.swing.text.BadLocationException;\r\nimport java.awt.BorderLayout;\r\nimport java.awt.Color;\r\nimport java.awt.Container;\r\nimport java.awt.GridLayout;\r\nimport java.awt.event.ActionEvent;\r\nimport java.awt.event.ActionListener;\r\nimport java.awt.event.InputEvent;\r\nimport java.awt.event.KeyEvent;\r\nimport java.text.NumberFormat;\r\nimport java.util.*;\r\n\r\n/**\r\n * Dialog used to set parameters for renaming multiple files.\r\n * \r\n * @author Mariusz Jakubowski\r\n */\r\npublic class BatchRenameDialog extends FocusDialog implements ActionListener, DocumentListener {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(BatchRenameDialog.class);\r\n\t\r\n    private static final int CASE_UNCHANGED = 0;\r\n    private static final int CASE_LOWER = 1;\r\n    private static final int CASE_UPPER = 2;\r\n    private static final int CASE_FIRST_UPPER = 3;\r\n    private static final int CASE_WORD_UPPER = 4;\r\n    \r\n    private static final int COL_ORIG_NAME = 0;\r\n    private static final int COL_CHANGED_NAME = 1;\r\n    private static final int COL_CHANGE_BLOCK = 2;    \r\n\r\n    private final MainFrame mainFrame;\r\n    private JTextField edtFileNameMask;\r\n    private JTable tblNames;\r\n    private JButton btnRename;\r\n    private JButton btnClose;\r\n    private JTextField edtSearchFor;\r\n    private JTextField edtReplaceWith;\r\n    private JTextField edtCounterStart;\r\n    private JTextField edtCounterStep;\r\n    private JComboBox<String> cbCounterDigits;\r\n    private JComboBox<String> cbCase;\r\n    private JCheckBox cbRegExp;\r\n    private RenameTableModel tableModel;\r\n    private AbstractAction actRemove;\r\n    private JButton btnName;\r\n    private JButton btnNameRange;\r\n    private JButton btnExtension;\r\n    private JButton btnCounter;\r\n    private JLabel lblDuplicates;\r\n    private TableColumn colBlock;\r\n\r\n    /** files to rename */\r\n    private final FileSet files;\r\n\r\n    /** a map of old file names used to check for name conflicts */\r\n    private final Map<String, AbstractFile> oldNames = new HashMap<>();\r\n\r\n    /** a list of generated names */\r\n    private final List<String> newNames = new ArrayList<>();\r\n\r\n    /** a list of flags to block file rename */\r\n    private final List<Boolean> blockNames = new ArrayList<>();\r\n    \r\n    /** a list of parsed tokens */\r\n    private final List<AbstractToken> tokens = new ArrayList<>();\r\n\r\n\r\n\r\n    /**\r\n     * Creates a new batch-rename dialog.\r\n     * @param mainFrame the main frame\r\n     * @param files a list of files to rename\r\n     */\r\n    public BatchRenameDialog(MainFrame mainFrame, FileSet files) {\r\n        super(mainFrame.getJFrame(), ActionProperties.getActionLabel(BatchRenameAction.Descriptor.ACTION_ID), null);\r\n        this.mainFrame = mainFrame;\r\n        this.files = files;\r\n        for (AbstractFile f : files) {        \t\r\n            this.blockNames.add(Boolean.FALSE);\r\n            this.newNames.add(\"\");\r\n        \toldNames.put(PathUtils.removeTrailingSeparator(f.getAbsolutePath()), f);\t\t\t\r\n\t\t}\r\n        initialize();\r\n        generateNewNames();\r\n    }\r\n\r\n    /**\r\n     * Initializes the dialog.\r\n     */\r\n    private void initialize() {\r\n        Container content = getContentPane();\r\n        content.setLayout(new BorderLayout());\r\n        content.add(getPnlTop(), BorderLayout.NORTH);\r\n        content.add(new JScrollPane(getTblNames()), BorderLayout.CENTER);\r\n        content.add(getPnlButtons(), BorderLayout.SOUTH);\r\n        getRootPane().setDefaultButton(btnRename);\r\n    }\r\n\r\n    /**\r\n     * Creates bottom panel with buttons.\r\n     */\r\n    private JPanel getPnlButtons() {\r\n        JPanel pnlButtons = new JPanel(new BorderLayout());\r\n        pnlButtons.add(new JButton(getActRemove()), BorderLayout.WEST);\r\n        XBoxPanel pnlButtonsRight = new XBoxPanel();\r\n        lblDuplicates = new JLabel(i18n(\"batch_rename_dialog.duplicate_names\"));\r\n        lblDuplicates.setForeground(Color.red);\r\n        lblDuplicates.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 15));\r\n        pnlButtonsRight.add(lblDuplicates);\r\n        btnRename = new JButton(i18n(\"rename\"));\r\n        btnRename.addActionListener(this);\r\n        pnlButtonsRight.add(btnRename);\r\n        btnClose = new JButton(i18n(\"cancel\"));\r\n        btnClose.addActionListener(this);\r\n        pnlButtonsRight.add(btnClose);\r\n        pnlButtons.add(pnlButtonsRight, BorderLayout.EAST);\r\n        return pnlButtons;\r\n    }\r\n\r\n    /**\r\n     * Creates a table with file names.\r\n     */\r\n    private JTable getTblNames() {\r\n        if (tblNames == null) {\r\n            tableModel = new RenameTableModel();\r\n            tblNames = new JTable(tableModel);\r\n            // add del key for remove action\r\n            tblNames.getActionMap().put(\"del\", getActRemove());            \r\n            tblNames.getInputMap().put((KeyStroke) getActRemove().getValue(Action.ACCELERATOR_KEY), \"del\");\r\n            // add tab key\r\n            tblNames.getActionMap().put(\"tab\", new AbstractAction() {\r\n                    public void actionPerformed(ActionEvent e) {\r\n                        tblNames.transferFocus();\r\n                    }\r\n            });\r\n            tblNames.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0, false), \"tab\");\r\n            // add shift tab key\r\n            tblNames.getActionMap().put(\"shift tab\", new AbstractAction() {\r\n                public void actionPerformed(ActionEvent e) {\r\n                    tblNames.transferFocusBackward();\r\n                }\r\n            });\r\n            tblNames.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, \r\n                    InputEvent.SHIFT_DOWN_MASK, false), \"shift tab\");\r\n            // \r\n            tblNames.getColumnModel().getColumn(COL_CHANGED_NAME).setCellEditor(new DefaultCellEditor(new JTextField()));\r\n            colBlock = tblNames.getColumnModel().getColumn(COL_CHANGE_BLOCK); \r\n            colBlock.setMaxWidth(60);\r\n            tblNames.removeColumn(colBlock);\r\n        }\r\n        return tblNames;\r\n    }\r\n    \r\n    private Action getActRemove() {\r\n        if (actRemove == null) {\r\n            actRemove = new AbstractAction() {\r\n                public void actionPerformed(ActionEvent e) {\r\n                    removeSelectedFiles();\r\n                }\r\n            };\r\n            actRemove.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));\r\n            actRemove.putValue(Action.NAME, i18n(\"remove\"));\r\n        }\r\n        return actRemove;\r\n    }\r\n\r\n    /**\r\n     * Creates a panel with edit controls.\r\n     */\r\n    private JPanel getPnlTop() {\r\n        // file & extension mask\r\n        edtFileNameMask = new JTextField(\"[N].[E]\");\r\n        edtFileNameMask.setColumns(20);\r\n        edtFileNameMask.getDocument().addDocumentListener(this);\r\n        edtFileNameMask.setToolTipText(getPatternHelp());\r\n\r\n        // search & replace\r\n        edtSearchFor = new JTextField();\r\n        edtSearchFor.setColumns(20);\r\n        edtSearchFor.getDocument().addDocumentListener(this);\r\n\r\n        edtReplaceWith = new JTextField();\r\n        edtReplaceWith.setColumns(20);\r\n        edtReplaceWith.getDocument().addDocumentListener(this);\r\n\r\n        // upper/lower case\r\n        Vector<String> ulcase = new Vector<>();\r\n        ulcase.add(i18n(\"batch_rename_dialog.no_change\"));\r\n        ulcase.add(i18n(\"batch_rename_dialog.lower_case\"));\r\n        ulcase.add(i18n(\"batch_rename_dialog.upper_case\"));\r\n        ulcase.add(i18n(\"batch_rename_dialog.first_upper\"));\r\n        ulcase.add(i18n(\"batch_rename_dialog.word\"));\r\n        cbCase = new JComboBox<>(ulcase);\r\n        cbCase.addActionListener(this);\r\n\r\n        // Regexp\r\n        cbRegExp = new JCheckBox();\r\n        cbRegExp.addActionListener(this);\r\n\r\n        // counter\r\n        edtCounterStart = new JTextField(\"1\");\r\n        edtCounterStart.getDocument().addDocumentListener(this);\r\n        edtCounterStart.setColumns(2);\r\n\r\n        edtCounterStep = new JTextField(\"1\");\r\n        edtCounterStep.getDocument().addDocumentListener(this);\r\n        edtCounterStep.setColumns(2);\r\n\r\n        Vector<String> digits = new Vector<>();\r\n        String zeros = \"0000\";\r\n        for (int i = 1; i <= 5; i++) {\r\n            digits.add(zeros.substring(0, i - 1) + \"1\");\r\n        }\r\n        cbCounterDigits = new JComboBox<>(digits);\r\n        cbCounterDigits.addActionListener(this);\r\n\r\n        // add controls\r\n        XBoxPanel pnlTop = new XBoxPanel();\r\n        \r\n        YBoxPanel pnl1 = new YBoxPanel();\r\n        pnl1.setBorder(BorderFactory.createTitledBorder(i18n(\"batch_rename_dialog.mask\")));\r\n        pnl1.add(edtFileNameMask);\r\n        \r\n        JPanel pnl1Btns = new JPanel(new GridLayout(3, 2));\r\n\r\n        btnName = new JButton(\"[N] - \" + i18n(\"name\"));\r\n        btnName.addActionListener(this);\r\n        btnName.setHorizontalAlignment(SwingConstants.LEFT);\r\n        pnl1Btns.add(btnName);\r\n        \r\n        btnExtension = new JButton(\"[E] - \" + i18n(\"extension\"));\r\n        btnExtension.addActionListener(this);\r\n        btnExtension.setHorizontalAlignment(SwingConstants.LEFT);\r\n        pnl1Btns.add(btnExtension);\r\n\r\n        btnNameRange = new JButton(\"[N#-#] - \" + i18n(\"batch_rename_dialog.range\"));\r\n        btnNameRange.addActionListener(this);\r\n        btnNameRange.setHorizontalAlignment(SwingConstants.LEFT);\r\n        pnl1Btns.add(btnNameRange);\r\n        \r\n        btnCounter = new JButton(\"[C] - \" + i18n(\"batch_rename_dialog.counter\"));\r\n        btnCounter.addActionListener(this);\r\n        btnCounter.setHorizontalAlignment(SwingConstants.LEFT);\r\n        pnl1Btns.add(btnCounter);\r\n\r\n        pnl1.add(pnl1Btns);\r\n        pnl1.add(new JPanel());\r\n        pnlTop.add(pnl1);\r\n        \r\n        XAlignedComponentPanel pnl2 = new XAlignedComponentPanel(5);\r\n        pnl2.setBorder(BorderFactory.createTitledBorder(i18n(\"batch_rename_dialog.search_replace\")));\r\n        pnl2.addRow(i18n(\"batch_rename_dialog.search_for\"), edtSearchFor, 5);\r\n        pnl2.addRow(i18n(\"batch_rename_dialog.replace_with\"),  edtReplaceWith, 5);\r\n        pnl2.addRow(i18n(\"batch_rename_dialog.upper_lower_case\"), cbCase, 5);\r\n        pnl2.addRow(i18n(\"batch_rename_dialog.regexp\"), cbRegExp, 5);\r\n        pnlTop.add(pnl2);\r\n\r\n        XAlignedComponentPanel pnl3 = new XAlignedComponentPanel(5);\r\n        pnl3.setBorder(BorderFactory.createTitledBorder(i18n(\"batch_rename_dialog.counter\") + \" [C]\"));\r\n        pnl3.addRow(i18n(\"batch_rename_dialog.start_at\"), edtCounterStart, 5);\r\n        pnl3.addRow(i18n(\"batch_rename_dialog.step_by\"), edtCounterStep, 5);\r\n        pnl3.addRow(i18n(\"batch_rename_dialog.format\"), cbCounterDigits, 5);\r\n        pnlTop.add(pnl3);\r\n\r\n        return pnlTop;\r\n    }\r\n\r\n    /**\r\n     * Creates a label with help.\r\n     * @return a label with help\r\n     */     \r\n    private String getPatternHelp() {\r\n        return \"<html>\" +\r\n            \"[N] - the whole name<br>\" +\r\n            \"[N2,3] - 3 characters starting from the 2nd character of the name<br>\" +\r\n            \"[N2-5] - characters 2 to 5<br>\" +\r\n            \"[N2-] - all characters starting from the 2nd character<br>\" +\r\n            \"[N-3,2] - two characters starting at 3rd character from the end of the name<br>\" +\r\n            \"[N2--2] - characters from the 2nd to the 2nd-last character<br>\" +\r\n            \"[C] - inserts a counter<br>\" +\r\n            \"[C10,2,3] - inserts a counter starting at 10, steps by 2, uses 3 digits<br>\" +\r\n            \"[YMD] - inserts a year, month and day when the file was last modified\";       // TODO add to dictionary\r\n    }\r\n    \r\n\r\n    /**\r\n     * Removes selected files from a list of files to rename.\r\n     */\r\n    private void removeSelectedFiles() {\r\n        int[] sel = tblNames.getSelectedRows();\r\n        for (int i = sel.length - 1; i >= 0; i--) {\r\n            files.remove(sel[i]);\r\n            newNames.remove(sel[i]);\r\n            blockNames.remove(sel[i]);\r\n            tableModel.fireTableRowsDeleted(sel[i], sel[i]);\r\n        }\r\n        if (files.isEmpty()) {\r\n            dispose();\r\n        }\r\n    }\r\n    \r\n    /**\r\n     * Checks if there are duplicates in new file names. \r\n     */\r\n    private void checkForDuplicates() {\r\n        boolean duplicates = false;\r\n        boolean oldNamesConflict = false;\r\n        Set<String> names = new HashSet<>();\r\n        for (int i=0; i<newNames.size(); i++) {\r\n            String newName = newNames.get(i);\r\n            AbstractFile file = files.get(i);\r\n            AbstractFile parent = file.getParent();\r\n            if (parent != null) {\r\n                newName = parent.getAbsolutePath(true) + newName;\r\n            }\r\n            if (names.contains(newName)) {\r\n                duplicates = true;\r\n                break;\r\n            }\r\n            AbstractFile oldFile = oldNames.get(newName);\r\n            if (oldFile!=null && oldFile!=file) {\r\n            \toldNamesConflict = true;\r\n            \tbreak;\r\n            }\r\n            names.add(newName);\r\n        }            \r\n        if (duplicates) {\r\n        \tlblDuplicates.setText(i18n(\"batch_rename_dialog.duplicate_names\"));\r\n        }\r\n        if (oldNamesConflict) {\r\n        \tlblDuplicates.setText(i18n(\"batch_rename_dialog.names_conflict\"));\r\n        }\r\n        lblDuplicates.setVisible(duplicates || oldNamesConflict);\r\n        btnRename.setEnabled(!duplicates && !oldNamesConflict);\r\n    }\r\n    \r\n    /**\r\n     * Generate a new name for a file.\r\n     * \r\n     * @param file a file to change name to\r\n     * @return the new file name\r\n     */\r\n    private String generateNewName(AbstractFile file) {\r\n        // apply pattern\r\n        String newName = applyPattern(file);\r\n\r\n        // search & replace\r\n        if (!edtSearchFor.getText().isEmpty()) {\r\n            if (cbRegExp.isSelected()) {\r\n                try {\r\n                    newName = newName.replaceAll(edtSearchFor.getText(), edtReplaceWith.getText());\r\n                } catch (Exception e) {\r\n//                    if (!ignoreRegexpErrors) {\r\n//                        String errorMessage = Translator.get(\"batch_rename_dialog.regexp_error\");\r\n//                        if (e instanceof PatternSyntaxException) {\r\n//                            PatternSyntaxException pse = (PatternSyntaxException) e;\r\n//                            errorMessage += \": \" + pse.getPattern();\r\n//                        }\r\n//                        QuestionDialog dialog = new QuestionDialog(mainFrame,\r\n//                                Translator.get(\"error\"),\r\n//                                errorMessage,\r\n//                                mainFrame,\r\n//                                new String[]{Translator.get(\"ok\")},\r\n//                                new int[]{0},\r\n//                                0);\r\n//                        dialog.showDialog();\r\n//                    }\r\n                }\r\n            } else {\r\n                newName = newName.replace(edtSearchFor.getText(), edtReplaceWith.getText());\r\n            }\r\n        }\r\n\r\n        // remove trailing dot\r\n        if (newName.endsWith(\".\")) {\r\n            newName = newName.substring(0, newName.length() - 1);\r\n        }\r\n\r\n        // uppercase/lowercase\r\n        newName = changeCase(newName, cbCase.getSelectedIndex());\r\n\r\n        return newName;\r\n    }\r\n    \r\n    /**\r\n     * Generates new names for all files.\r\n     */\r\n    private void generateNewNames() {\r\n        compilePattern(edtFileNameMask.getText());\r\n        for (int i = 0; i < files.size(); i++) {\r\n            if (Boolean.FALSE.equals(blockNames.get(i))) {\r\n                AbstractFile file = files.get(i);\r\n                String newName = generateNewName(file);\r\n                newNames.set(i, newName);\r\n            }\r\n        }\r\n        checkForDuplicates();\r\n        tableModel.fireTableChanged(new TableModelEvent(tableModel, 0, newNames.size(), 1, TableModelEvent.UPDATE));\r\n    }\r\n    \r\n    /**\r\n     * Parses a pattern for a filename and it's extension and stores it in a\r\n     * list. A pattern is a combination of file and extension masks that a user\r\n     * enters in fields. These masks can contain special placeholders for\r\n     * previous name, part of it, counter, date, and others. These placeholders\r\n     * (or 'tokens') are always in brackets [ and ]. A part of pattern which is\r\n     * not in brackets is copied to a new name. This metod analyzes a pattern\r\n     * and stores token handlers responsible for substituting these placeholders\r\n     * for actual parts of a new file name.\r\n     * \r\n     * @see AbstractToken\r\n     * @param pattern a pattern for changing a file name and it's extension\r\n     */\r\n    private void compilePattern(String pattern) {\r\n        tokens.clear();\r\n        for (int i = 0; i < pattern.length(); i++) {\r\n            char c = pattern.charAt(i);\r\n            if (c == '[') {\r\n                int tokenEnd = pattern.indexOf(']', i);\r\n                if (tokenEnd == -1) {\r\n                    tokens.add(new CopyChar(pattern.substring(i)));\r\n                    break;\r\n                }\r\n                String strToken = pattern.substring(i + 1, tokenEnd);\r\n                if (!strToken.isEmpty()) {\r\n                    c = strToken.charAt(0);\r\n                    AbstractToken t;\r\n                    switch (c) {\r\n                    case 'N':\r\n                        t = new NameToken(strToken);\r\n                        break;\r\n                    case 'E':\r\n                        t = new ExtToken(strToken);\r\n                        break;\r\n                    case 'C':\r\n                        int start = StringUtils.parseIntDef(edtCounterStart.getText(), 0);\r\n                        int step = StringUtils.parseIntDef(edtCounterStep.getText(), 0);\r\n                        int digits = cbCounterDigits.getSelectedIndex() + 1;\r\n                        t = new CounterToken(strToken, start, step, digits);\r\n                        break;\r\n                    case 'P':\r\n                        t = new ParentDirToken(strToken);\r\n                        break;\r\n                    case 'Y':\r\n                    case 'M':\r\n                    case 'D':\r\n                    case 'h':\r\n                    case 'm':\r\n                    case 's':\r\n                        t = new DateToken(strToken);\r\n                        break;\r\n                    case '[':\r\n                        t = new CopyChar(\"[\");\r\n                        break;\r\n                    default:\r\n                        t = new CopyChar(\"[\" + strToken + \"]\");\r\n                        break;\r\n                    }\r\n                    t.parse();\r\n                    tokens.add(t);\r\n                }\r\n                i = tokenEnd;\r\n            } else {\r\n                tokens.add(new CopyChar(Character.toString(c)));\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Changes case of a file name.\r\n     * @param oldName a name of file to change case\r\n     * @param newCase a type of change\r\n     * @return the name with changed case\r\n     */\r\n    private String changeCase(String oldName, int newCase) {\r\n        String newName = \"\";\r\n        switch (newCase) {\r\n        case CASE_UNCHANGED:\r\n            newName = oldName;\r\n            break;\r\n        case CASE_LOWER:\r\n            newName = oldName.toLowerCase();\r\n            break;\r\n        case CASE_UPPER:\r\n            newName = oldName.toUpperCase();\r\n            break;\r\n        case CASE_FIRST_UPPER:\r\n            newName = oldName.substring(0, 1).toUpperCase() + oldName.substring(1).toLowerCase();\r\n            break;\r\n        case CASE_WORD_UPPER:\r\n            boolean afterSpace = true;\r\n            StringBuilder newNameCase = new StringBuilder();\r\n            for (int i = 0; i < oldName.length(); i++) {\r\n                if (oldName.charAt(i) == ' ') {\r\n                    newNameCase.append(' ');\r\n                    afterSpace = true;\r\n                } else {\r\n                    if (afterSpace) {\r\n                        newNameCase.append(Character.toUpperCase(oldName.charAt(i)));\r\n                        afterSpace = false;\r\n                    } else {\r\n                        newNameCase.append(oldName.charAt(i));\r\n                    }\r\n                }\r\n            }\r\n            newName = newNameCase.toString();\r\n            break;\r\n        }\r\n        return newName;\r\n    }\r\n\r\n    /**\r\n     * Applies a compiled pattern to a file name and it's extension.\r\n     * @param file a file\r\n     * @return the new file name after applying a pattern\r\n     */\r\n    private String applyPattern(AbstractFile file) {\r\n        StringBuilder filename = new StringBuilder();\r\n        for (AbstractToken token: tokens) {\r\n            filename.append(token.apply(file));\r\n        }\r\n        return filename.toString();\r\n    }\r\n    \r\n    /**\r\n     * Counts or removes unchanged files from change set.\r\n     * @param countOnly if true only counts files that are changing.\r\n     * @return number of changed files\r\n     */\r\n    private int removeUnchangedFiles(boolean countOnly) {\r\n        // remove non changed files\r\n        Iterator<AbstractFile> fi = files.iterator();\r\n        Iterator<String> ni = newNames.iterator();\r\n        int changed = 0;\r\n        while (fi.hasNext()) {        \r\n            AbstractFile file = fi.next();\r\n            String nn = ni.next();\r\n            if (file.getName().equals(nn)) {\r\n                if (!countOnly) {\r\n                    fi.remove();\r\n                    ni.remove();\r\n                }\r\n            } else {\r\n                changed++;\r\n            }\r\n        }\r\n        return changed;\r\n    }\r\n\r\n    /**\r\n     * Renames files.\r\n     */\r\n    private void doRename() {\r\n        removeUnchangedFiles(false);\r\n        // start rename job\r\n        if (!files.isEmpty()) {\r\n            ProgressDialog progressDialog = new ProgressDialog(mainFrame, i18n(\"progress_dialog.processing_files\"));\r\n            BatchRenameJob job = new BatchRenameJob(progressDialog, mainFrame, files, newNames);\r\n            progressDialog.start(job);\r\n        }\r\n    }\r\n    \r\n    /**\r\n     * Inserts pattern into pattern edit field.\r\n     * @param pattern a text to insert\r\n     */\r\n    private void insertPattern(String pattern) {\r\n        int pos = edtFileNameMask.getSelectionStart();\r\n        try {\r\n        \tint selLen = edtFileNameMask.getSelectionEnd() - edtFileNameMask.getSelectionStart();\r\n        \tif (selLen > 0) {\r\n        \t\tedtFileNameMask.getDocument().remove(edtFileNameMask.getSelectionStart(), selLen);\r\n        \t}\r\n            edtFileNameMask.getDocument().insertString(pos, pattern, null);\r\n        } catch (BadLocationException e) {\r\n        \tLOGGER.debug(\"Caught exception\", e);\r\n        }\r\n    }\r\n    \r\n\r\n    // /////////////////////////////////\r\n    // ActionListener implementation //\r\n    // /////////////////////////////////\r\n\r\n    public void actionPerformed(ActionEvent e) {\r\n        Object source = e.getSource();\r\n        if (source == btnClose) {\r\n            dispose();\r\n        } else if (source == btnRename) {\r\n            int unchanged = files.size();\r\n            int changed = removeUnchangedFiles(true);\r\n            if (changed > 0) {\r\n                BatchRenameConfirmationDialog dlg = new BatchRenameConfirmationDialog(mainFrame, files, changed, unchanged);\r\n                if (dlg.isProceedWithRename()) {\r\n                    dispose();\r\n                    doRename();\r\n                }\r\n            }\r\n        } else if (source == cbCase) {\r\n            generateNewNames();\r\n        } else if (source == cbRegExp) {\r\n            generateNewNames();\r\n        } else if (source == cbCounterDigits) {\r\n            generateNewNames();\r\n        } else if (source == btnName) {\r\n            insertPattern(\"[N]\");\r\n        } else if (source == btnExtension) {\r\n            insertPattern(\"[E]\");\r\n        } else if (source == btnCounter) {\r\n            insertPattern(\"[C]\");\r\n        } else if (source == btnNameRange) {\r\n            String firstFile = files.getFirst().getNameWithoutExtension();\r\n            BatchRenameSelectRange dlg = new BatchRenameSelectRange(this, firstFile);\r\n            dlg.showDialog();\r\n            String range = dlg.getRange();\r\n            if (range != null) {\r\n                insertPattern(range);\r\n            }\r\n        }\r\n    }\r\n\r\n    // these methods are invoked when one of edit boxes changes\r\n\r\n    public void changedUpdate(DocumentEvent e) {\r\n        generateNewNames();\r\n    }\r\n\r\n    public void insertUpdate(DocumentEvent e) {\r\n        generateNewNames();\r\n    }\r\n\r\n    public void removeUpdate(DocumentEvent e) {\r\n        generateNewNames();\r\n    }\r\n    \r\n\r\n    /**\r\n     * Table model with old and new file names.\r\n     * @author Mariusz Jakubowski\r\n     * \r\n     */\r\n    private class RenameTableModel extends AbstractTableModel {\r\n\r\n        public int getColumnCount() {\r\n            return 3;\r\n        }\r\n\r\n        public int getRowCount() {\r\n            return files.size();\r\n        }\r\n\r\n        public Object getValueAt(int rowIndex, int columnIndex) {\r\n            AbstractFile f = files.get(rowIndex);\r\n            return switch (columnIndex) {\r\n                case COL_ORIG_NAME -> f.getName();\r\n                case COL_CHANGED_NAME -> newNames.get(rowIndex);\r\n                case COL_CHANGE_BLOCK -> blockNames.get(rowIndex);\r\n                default -> null;\r\n            };\r\n        }\r\n        \r\n        /**\r\n         * Sets the value in the cell at columnIndex and rowIndex to aValue.\r\n         * Called when a user manually entered a new file name or blocked \r\n         * a name from a rename pattern.\r\n         */\r\n        @Override\r\n        public void setValueAt(Object value, int rowIndex, int columnIndex) {\r\n            switch (columnIndex) {\r\n            case COL_CHANGED_NAME:\r\n                if (!newNames.get(rowIndex).equals(value)) {\r\n                    newNames.set(rowIndex, (String)value);\r\n                    if (Boolean.FALSE.equals(blockNames.get(rowIndex))) {\r\n                        blockNames.set(rowIndex, Boolean.TRUE);\r\n                        if (tblNames.getColumnCount() == 2) {\r\n                            tblNames.addColumn(colBlock);\r\n                        }\r\n                        fireTableCellUpdated(rowIndex, COL_CHANGE_BLOCK);\r\n                    }\r\n                    checkForDuplicates();\r\n                }\r\n                break;\r\n            case COL_CHANGE_BLOCK:\r\n                blockNames.set(rowIndex, (Boolean)value);\r\n                if (Boolean.FALSE.equals(value)) {\r\n                    AbstractFile file = files.get(rowIndex);\r\n                    String newName = generateNewName(file);\r\n                    newNames.set(rowIndex, newName);\r\n                    fireTableCellUpdated(rowIndex, COL_CHANGED_NAME);\r\n                }\r\n                checkForDuplicates();\r\n                break;\r\n            }\r\n        }\r\n\r\n        @Override\r\n        public String getColumnName(int column) {\r\n            return switch (column) {\r\n                case COL_ORIG_NAME -> i18n(\"batch_rename_dialog.old_name\");\r\n                case COL_CHANGED_NAME -> i18n(\"batch_rename_dialog.new_name\");\r\n                case COL_CHANGE_BLOCK -> i18n(\"batch_rename_dialog.block_name\");\r\n                default -> \"\";\r\n            };\r\n        }\r\n\r\n        @Override\r\n        public Class<?> getColumnClass(int columnIndex) {\r\n            if (columnIndex == COL_CHANGE_BLOCK) {\r\n                return Boolean.class;\r\n            } else {\r\n                return String.class;\r\n            }\r\n        }\r\n\r\n        @Override\r\n        public boolean isCellEditable(int rowIndex, int columnIndex) {\r\n            return columnIndex != COL_ORIG_NAME; \r\n        }\r\n        \r\n        \r\n\r\n    }\r\n\r\n\r\n    /**\r\n     * Base class for handling tokens.\r\n     * \r\n     * @author Mariusz Jakubowski\r\n     * \r\n     */\r\n    private abstract static class AbstractToken {\r\n        /** a string with a token */\r\n        protected String token;\r\n\r\n        /** a current position in the token */\r\n        protected int pos = 1;\r\n\r\n        /** a length of the token */\r\n        protected int len;\r\n\r\n        AbstractToken(String token) {\r\n            this.token = token;\r\n            this.len = token.length();\r\n        }\r\n\r\n        /**\r\n         * Parses a token information.\r\n         */\r\n        protected abstract void parse();\r\n\r\n        /**\r\n         * Applies this token to a file.\r\n         * \r\n         * @param file a file\r\n         * @return a part of filename after applying this token\r\n         */\r\n        public abstract String apply(AbstractFile file);\r\n\r\n        /**\r\n         * Gets one char from this token.\r\n         * \r\n         * @return the next character in the token\r\n         */\r\n        public char getChar() {\r\n            if (pos < len) {\r\n                return token.charAt(pos++);\r\n            }\r\n            return 0;\r\n        }\r\n\r\n        /**\r\n         * Trys to get an integer from this token string. Advances the current\r\n         * position in the token string.\r\n         * \r\n         * @param def\r\n         *            a default value if an integer cannot be parsed\r\n         * @return the integer from this token string or the default value\r\n         */\r\n        int getInt(int def) {\r\n            int startPos = pos;\r\n            while (pos < len) {\r\n                char c = token.charAt(pos);\r\n                if (c < '0' || c > '9') {\r\n                    if (c != '-' || startPos != pos) {\r\n                        break;\r\n                    }\r\n                }\r\n                pos++;\r\n            }\r\n            return startPos == pos ? def : StringUtils.parseIntDef(token.substring(startPos, pos), def);\r\n        }\r\n\r\n    }\r\n\r\n    /**\r\n     * Token handler that copies a character from a source string to a\r\n     * destination. This is used for all characters without brackets.\r\n     * \r\n     * @author Mariusz Jakubowski\r\n     * \r\n     */\r\n    static class CopyChar extends AbstractToken {\r\n\r\n        CopyChar(String token) {\r\n            super(token);\r\n        }\r\n\r\n        @Override\r\n        protected void parse() {\r\n        }\r\n\r\n        @Override\r\n        public String apply(AbstractFile file) {\r\n            return token;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Token handler that parses file name. Examples:\r\n     * <ul>\r\n     * <li>[N] - whole name\r\n     * <li>[N2] - 2nd character of a name\r\n     * <li>[N2,3] - 3 characters starting at the 2nd character of a name\r\n     * <li>[N2-5] - characters 2 to 5\r\n     * <li>[N2-] - all characters starting from the 2nd character\r\n     * <li>[N-2] - 2nd character from the end of name\r\n     * <li>[N-3,2] - two characters starting at 3rd character from the end of a name\r\n     * <li>[N2--2] - characters from the 2nd to the 2nd-last character\r\n     * <li>[N-5-10] - characters from 5th from end to 10th from beginning of a name\r\n     * </ul>\r\n     * \r\n     * @author Mariusz Jakubowski\r\n     * \r\n     */\r\n    static class NameToken extends AbstractToken {\r\n        private int startIndex;\r\n        private int endIndex;\r\n        private int charCount;\r\n\r\n        NameToken(String token) {\r\n            super(token);\r\n        }\r\n\r\n        @Override\r\n        protected void parse() {\r\n            startIndex = getInt(0);\r\n            char sep = getChar();\r\n            switch (sep) {\r\n                case '-':\r\n                    endIndex = getInt(999); // default - to the end\r\n                    break;\r\n                case ',':\r\n                    charCount = getInt(0);\r\n            }\r\n        }\r\n\r\n        @Override\r\n        public String apply(AbstractFile file) {\r\n            // split name & extension\r\n            String oldName = file.getName();\r\n            int dot = oldName.lastIndexOf('.');\r\n            String name = dot >= 0 ? oldName.substring(0, dot) : oldName;\r\n            return extractNamePart(name);\r\n        }\r\n\r\n        /**\r\n         * Extracts a part of a name.\r\n         * \r\n         * @param name\r\n         *            a string from which extract a part\r\n         * @return the part of the name\r\n         */\r\n        String extractNamePart(String name) {\r\n            int targetLen = name.length();\r\n            int currentStartIndex = startIndex;\r\n            int currentEndIndex = endIndex;\r\n            if (currentStartIndex < 0) {\r\n                currentStartIndex = targetLen + currentStartIndex + 1;\r\n                if (currentStartIndex < 1) {\r\n                    currentStartIndex = 1;\r\n                }\r\n            }\r\n            if (currentEndIndex < 0) {\r\n                currentEndIndex = targetLen + currentEndIndex + 1;\r\n            }\r\n            if (currentStartIndex > 0) {\r\n                if (charCount > 0) {\r\n                    currentEndIndex = currentStartIndex + charCount - 1;\r\n                } else if (currentEndIndex == 0) {\r\n                    currentEndIndex = currentStartIndex;\r\n                }\r\n                if (currentStartIndex <= currentEndIndex && currentStartIndex - 1 < targetLen) {\r\n                    try {\r\n                        name = name.substring(currentStartIndex - 1, Math.min(currentEndIndex, targetLen));\r\n                    } catch (Exception e) {\r\n                    \tLOGGER.info(\"currentStartIndex={}, currentEndIndex={}\", currentStartIndex, currentEndIndex, e);\r\n                    }\r\n                } else {\r\n                    name = \"\";\r\n                }\r\n\r\n            }\r\n            return name;\r\n        }\r\n\r\n    }\r\n\r\n    /**\r\n     * Token handler that parses a file extension. [E] - an extension of a file,\r\n     * this token can also be used with parameters like in [N...]\r\n     * \r\n     * @author Mariusz Jakubowski\r\n     */\r\n    static class ExtToken extends NameToken {\r\n\r\n        ExtToken(String token) {\r\n            super(token);\r\n        }\r\n\r\n        @Override\r\n        public String apply(AbstractFile file) {\r\n            // split name & extension\r\n            String oldName = file.getName();\r\n            int dot = oldName.lastIndexOf('.');\r\n            String ext = dot >= 0 ? oldName.substring(dot + 1) : \"\";\r\n            return extractNamePart(ext);\r\n        }\r\n\r\n    }\r\n\r\n    /**\r\n     * Token handler that inserts a counter.\r\n     * Examples:\r\n     * <ul>\r\n     * <li>[C] - inserts counter, as defined on the dialog\r\n     * <li>[C10] - inserts counter starting at 10\r\n     * <li>[C10,2] - inserts counter starting at 10, step by 2\r\n     * <li>[C10,-2] - inserts counter starting at 10, step by -2\r\n     * <li>[C10,2,3] - inserts counter starting at 10, step by 2, use 3 digits to display\r\n     * <li>[C10,,3] - inserts counter starting at 10, step by as defined on the dialog, use 3 digits to display\r\n     * </ul> \r\n     * @author Mariusz Jakubowski\r\n     *\r\n     */\r\n    static class CounterToken extends AbstractToken {\r\n        private int start;\r\n        private int step;\r\n        private int digits;\r\n        private int current;\r\n        private NumberFormat numberFormat;\r\n\r\n        CounterToken(String token, int start, int step, int digits) {\r\n            super(token);\r\n            this.start = start;\r\n            this.step = step;\r\n            this.digits = digits;\r\n        }\r\n\r\n        @Override\r\n        protected void parse() {\r\n            start = getInt(start);\r\n            if (getChar() == ',') {\r\n                step = getInt(step);\r\n                if (getChar() == ',') {\r\n                    digits = getInt(digits);\r\n                }\r\n            }\r\n            numberFormat = NumberFormat.getIntegerInstance();\r\n            numberFormat.setMinimumIntegerDigits(digits);\r\n            numberFormat.setGroupingUsed(false);\r\n            current = start;\r\n        }\r\n\r\n        @Override\r\n        public String apply(AbstractFile file) {\r\n            String counter = numberFormat.format(current);\r\n            current += step;\r\n            return counter;\r\n        }\r\n\r\n    }\r\n\r\n    /**\r\n     * Token handler that inserts a directory information.\r\n     * [P] - inserts a name of the parent directory.\r\n     * @author Mariusz Jakubowski\r\n     */\r\n    static class ParentDirToken extends NameToken {\r\n\r\n        ParentDirToken(String token) {\r\n            super(token);\r\n        }\r\n\r\n        @Override\r\n        public String apply(AbstractFile file) {\r\n            AbstractFile parent = file.getParent();\r\n            return parent != null ? extractNamePart(parent.getName()) : \"\";\r\n        }\r\n\r\n    }\r\n\r\n    /**\r\n     * Inserts a date or time.\r\n     * <ul>\r\n     * <li>[Y] - inserts year (4 digits)\r\n     * <li>[M] - inserts month (2 digits)\r\n     * <li>[D] - inserts day of a month (2 digits)\r\n     * <li>[h] - inserts hours in 24-hour format (2 digits)\r\n     * <li>[m] - inserts minutes (2 digits)\r\n     * <li>[s] - inserts seconds\r\n     * <li>[YMD] - inserts file last modified year, month and day \r\n     * </ul>\r\n     * @author Mariusz Jakubowski\r\n     *\r\n     */\r\n    static class DateToken extends AbstractToken {\r\n        private final NumberFormat year;\r\n        private final NumberFormat digits2;\r\n\r\n        DateToken(String token) {\r\n            super(token);\r\n            year = NumberFormat.getIntegerInstance();\r\n            year.setMinimumIntegerDigits(4);\r\n            year.setGroupingUsed(false);\r\n            digits2 = NumberFormat.getIntegerInstance();\r\n            digits2.setMinimumIntegerDigits(2);\r\n            digits2.setGroupingUsed(false);\r\n        }\r\n\r\n        @Override\r\n        public String apply(AbstractFile file) {\r\n            Calendar c = Calendar.getInstance();\r\n            c.setTimeInMillis(file.getLastModifiedDate());\r\n            StringBuilder result = new StringBuilder();\r\n            for (int i = 0; i < len; i++) {\r\n                switch (token.charAt(i)) {\r\n                case 'Y':\r\n                    result.append(year.format(c.get(Calendar.YEAR)));\r\n                    break;\r\n                case 'M':\r\n                    result.append(digits2.format(c.get(Calendar.MONTH)));\r\n                    break;\r\n                case 'D':\r\n                    result.append(digits2.format(c.get(Calendar.DAY_OF_MONTH)));\r\n                    break;\r\n                case 'h':\r\n                    result.append(digits2.format(c.get(Calendar.HOUR_OF_DAY)));\r\n                    break;\r\n                case 'm':\r\n                    result.append(digits2.format(c.get(Calendar.MINUTE)));\r\n                    break;\r\n                case 's':\r\n                    result.append(digits2.format(c.get(Calendar.SECOND)));\r\n                    break;\r\n                }\r\n            }\r\n            return result.toString();\r\n        }\r\n\r\n        @Override\r\n        protected void parse() {\r\n        }\r\n\r\n    }\r\n    \r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/BatchRenameSelectRange.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.file;\r\n\r\nimport com.mucommander.ui.dialog.DialogToolkit;\r\nimport com.mucommander.ui.dialog.FocusDialog;\r\n\r\nimport javax.swing.*;\r\nimport javax.swing.text.AttributeSet;\r\nimport javax.swing.text.BadLocationException;\r\nimport javax.swing.text.PlainDocument;\r\nimport java.awt.*;\r\nimport java.awt.event.ActionEvent;\r\nimport java.awt.event.ActionListener;\r\n\r\n/**\r\n * A dialog that allows select a range of characters from file name. \r\n * Invoked from batch-rename dialog.\r\n *  \r\n * @author Mariusz Jakubowski\r\n *\r\n */\r\npublic class BatchRenameSelectRange extends FocusDialog implements ActionListener {\r\n\r\n    private final JTextField edtRange;\r\n    private final JButton btnCancel;\r\n    private final JButton btnOK;\r\n    private String range = null;\r\n    \r\n\r\n    BatchRenameSelectRange(Dialog owner, String filename) {\r\n        super(owner, i18n(\"batch_rename_dialog.range\"), owner);\r\n        edtRange = new JTextField();\r\n        ReadOnlyDocument doc = new ReadOnlyDocument();\r\n        edtRange.setDocument(doc);\r\n        edtRange.setText(filename);\r\n        edtRange.setColumns(filename.length() + 5);\r\n        edtRange.setSelectionStart(0);\r\n        edtRange.setSelectionEnd(filename.length());\r\n        doc.setReadOnly(true);\r\n        Container content = getContentPane();\r\n        content.setLayout(new BorderLayout());            \r\n        content.add(edtRange, BorderLayout.CENTER);\r\n\r\n        btnOK = new JButton(i18n(\"ok\"));\r\n        btnCancel = new JButton(i18n(\"cancel\"));\r\n        content.add(DialogToolkit.createOKCancelPanel(btnOK, btnCancel, getRootPane(), this), BorderLayout.SOUTH);\r\n    }\r\n\r\n\r\n    public void actionPerformed(ActionEvent e) {\r\n        Object source = e.getSource();\r\n        if (source == btnCancel) {\r\n            dispose();\r\n        } else if (source == btnOK) {\r\n            range = \"[N\" + (edtRange.getSelectionStart() + 1);\r\n            if (edtRange.getSelectionEnd() > 0 && edtRange.getSelectionEnd() > edtRange.getSelectionStart()+1) {\r\n                range += \"-\" + edtRange.getSelectionEnd();\r\n            }\r\n            range += \"]\";\r\n            dispose();\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Returns a token with selected range.\r\n     */\r\n    public String getRange() {\r\n        return range;\r\n    }\r\n    \r\n    /**\r\n     * A document model that can be disabled for editing.\r\n     * @author Mariusz Jakubowski\r\n     *\r\n     */\r\n    private static class ReadOnlyDocument extends PlainDocument {\r\n        private boolean readOnly = false;\r\n        \r\n        public void setReadOnly(boolean readOnly) {\r\n            this.readOnly = readOnly;\r\n        }\r\n        \r\n        @Override\r\n        public void insertString(int offs, String str, AttributeSet a)\r\n                throws BadLocationException {\r\n            if (!readOnly) {\r\n                super.insertString(offs, str, a);\r\n            }\r\n        }\r\n\r\n        @Override\r\n        public void remove(int offs, int len)\r\n                throws BadLocationException {\r\n            if (!readOnly) {\r\n                super.remove(offs, len);\r\n            }\r\n        }\r\n    \r\n    }\r\n    \r\n    \r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/CalculateChecksumDialog.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.file;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileFactory;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.commons.file.util.PathUtils;\r\nimport com.mucommander.commons.io.security.MuProvider;\r\nimport com.mucommander.job.CalculateChecksumJob;\r\nimport com.mucommander.ui.action.ActionProperties;\r\nimport com.mucommander.ui.action.impl.CalculateChecksumAction;\r\nimport com.mucommander.ui.combobox.TcComboBox;\r\nimport com.mucommander.ui.dialog.DialogToolkit;\r\nimport com.mucommander.ui.layout.YBoxPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.text.FilePathField;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.BorderLayout;\r\nimport java.awt.Dimension;\r\nimport java.awt.FlowLayout;\r\nimport java.awt.event.ActionEvent;\r\nimport java.awt.event.ActionListener;\r\nimport java.awt.event.ItemEvent;\r\nimport java.awt.event.ItemListener;\r\nimport java.io.IOException;\r\nimport java.security.MessageDigest;\r\nimport java.security.NoSuchAlgorithmException;\r\nimport java.security.Security;\r\nimport java.util.Comparator;\r\nimport java.util.SortedSet;\r\nimport java.util.TreeSet;\r\n\r\n/**\r\n * This dialog prepares a {@link com.mucommander.job.CalculateChecksumJob} and lets the user choose a checksum\r\n * algorithm, and a destination for the checksum file.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class CalculateChecksumDialog extends JobDialog implements ActionListener, ItemListener {\r\n\r\n    private final TcComboBox<String> algorithmComboBox = new TcComboBox<>();\r\n    private final JRadioButton specificLocationRadioButton;\r\n    private final JTextField specificLocationTextField;\r\n    private final JButton btnOk;\r\n\r\n    /** An instance of all MessageDigest implementations */\r\n    private final MessageDigest[] messageDigests;\r\n\r\n    /** Default checksum algorithm (most commonly used) */\r\n    private final static String DEFAULT_ALGORITHM = \"MD5\";\r\n\r\n    /** Last algorithm used, saved after validation of this dialog */\r\n    private static String lastUsedAlgorithm = DEFAULT_ALGORITHM;\r\n\r\n    /** Dialog size constraints */\r\n    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(320,0);\r\n\r\n\r\n    static {\r\n        // Register additional MessageDigest implementations provided by the muCommander API\r\n        MuProvider.registerProvider();\r\n    }\r\n\r\n\r\n    public CalculateChecksumDialog(MainFrame mainFrame, FileSet files) {\r\n        super(mainFrame, ActionProperties.getActionLabel(CalculateChecksumAction.Descriptor.ACTION_ID), files);\r\n\r\n        YBoxPanel mainPanel = new YBoxPanel();\r\n\r\n        // Retrieve all MessageDigest instances and sort them by alphabetical order of their algorithm\r\n\r\n        // create a TreeSet with a custom Comparator\r\n        SortedSet<MessageDigest> algorithmSortedSet = new TreeSet<>(Comparator.comparing(MessageDigest::getAlgorithm));\r\n\r\n        // Add all MessageDigest to the TreeSet\r\n        for (String algo : Security.getAlgorithms(\"MessageDigest\")) {\r\n            try {\r\n                algorithmSortedSet.add(MessageDigest.getInstance(algo));\r\n            } catch (NoSuchAlgorithmException e) {\r\n                // Should never happen and if it ever does, the digest will simply be discarded\r\n            }\r\n        }\r\n\r\n        // Convert the sorted set into an array\r\n        messageDigests = new MessageDigest[algorithmSortedSet.size()];\r\n        algorithmSortedSet.toArray(messageDigests);\r\n\r\n        // Add the sorted list of algorithms to a combo box to let the user choose one\r\n        for (MessageDigest messageDigest : messageDigests) {\r\n            algorithmComboBox.addItem(messageDigest.getAlgorithm());\r\n        }\r\n\r\n        // Select the last used algorithm (if any), or the default algorithm\r\n        algorithmComboBox.setSelectedItem(lastUsedAlgorithm);\r\n        algorithmComboBox.addItemListener(this);\r\n\r\n        FlowLayout flowLayout = new FlowLayout(FlowLayout.LEADING, 0, 0);\r\n        JPanel tempPanel = new JPanel(flowLayout);\r\n        tempPanel.add(new JLabel(i18n(\"calculate_checksum_dialog.checksum_algorithm\")+\" : \"));\r\n        tempPanel.add(algorithmComboBox);\r\n\r\n        mainPanel.add(tempPanel);\r\n        mainPanel.addSpace(10);\r\n\r\n        // create the components that allow to choose where the checksum file should be created\r\n\r\n        mainPanel.add(new JLabel(i18n(\"destination\")+\" :\"));\r\n        mainPanel.addSpace(5);\r\n\r\n        JRadioButton tempLocationRadioButton = new JRadioButton(i18n(\"calculate_checksum_dialog.temporary_file\"), true);\r\n        mainPanel.add(tempLocationRadioButton);\r\n\r\n        specificLocationRadioButton = new JRadioButton(\"\", false);\r\n        tempPanel = new JPanel(new BorderLayout());\r\n        tempPanel.add(specificLocationRadioButton, BorderLayout.WEST);\r\n        specificLocationRadioButton.addItemListener(this);\r\n        \r\n        // create a path field with auto-completion capabilities\r\n        specificLocationTextField = new FilePathField(getChecksumFilename(lastUsedAlgorithm));\r\n        specificLocationTextField.setEnabled(false);\r\n        tempPanel.add(specificLocationTextField, BorderLayout.CENTER);\r\n\r\n        JPanel tempPanel2 = new JPanel(new BorderLayout());\r\n        tempPanel2.add(tempPanel, BorderLayout.NORTH);\r\n        mainPanel.add(tempPanel2);\r\n\r\n        ButtonGroup buttonGroup = new ButtonGroup();\r\n        buttonGroup.add(tempLocationRadioButton);\r\n        buttonGroup.add(specificLocationRadioButton);\r\n\r\n        // create file details button and OK/cancel buttons and lay them out a single row\r\n\r\n        JPanel fileDetailsPanel = createFileDetailsPanel();\r\n\r\n        btnOk = new JButton(i18n(\"ok\"));\r\n        JButton cancelButton = new JButton(i18n(\"cancel\"));\r\n\r\n        mainPanel.add(createButtonsPanel(createFileDetailsButton(fileDetailsPanel),\r\n                DialogToolkit.createOKCancelPanel(btnOk, cancelButton, getRootPane(), this)));\r\n\r\n        mainPanel.addSpace(3);\r\n\r\n        mainPanel.add(fileDetailsPanel);\r\n\r\n//        mainPanel.add(new HelpButtonPanel(new HelpButton(mainFrame, \"CalculateChecksum\")));\r\n        \r\n        getContentPane().add(mainPanel);\r\n\r\n        // Give initial keyboard focus to the 'Delete' button\r\n        setInitialFocusComponent(algorithmComboBox);\r\n\r\n        // Call dispose() when dialog is closed\r\n        setDefaultCloseOperation(DISPOSE_ON_CLOSE);\r\n\r\n        // Size dialog and show it to the screen\r\n        setMinimumSize(MINIMUM_DIALOG_DIMENSION);\r\n        setResizable(true);\r\n    }\r\n\r\n    /**\r\n     * Returns the MessageDigest instance corresponding to the currently selected algorithm.\r\n     *\r\n     * @return the MessageDigest instance corresponding to the currently selected algorithm.\r\n     */\r\n    private MessageDigest getSelectedMessageDigest() {\r\n        return messageDigests[algorithmComboBox.getSelectedIndex()];\r\n    }\r\n\r\n    /**\r\n     * Returns a de-facto standard filename for the specified checksum algorithm, e.g. <code>MD5SUMS</code> for\r\n     * <code>md5</code>.\r\n     *\r\n     * @param algorithm a checksum algorithm\r\n     * @return a standard filename for the specified checksum algorithm\r\n     */\r\n    private String getChecksumFilename(String algorithm) {\r\n        // Adler32 -> ADLER32SUMS\r\n        // CRC32   -> <filename>.sfv    (needs special treatment)\r\n        // MD2     -> MD2SUMS\r\n        // MD4     -> MD4SUMS\r\n        // MD5     -> MD5SUMS\r\n        // SHA     -> SHA1SUMS          (needs special treatment)\r\n        // SHA-256 -> SHA256SUMS\r\n        // SHA-384 -> SHA384SUMS\r\n        // SHA-512 -> SHA512SUMS\r\n\r\n        algorithm = algorithm.toUpperCase();\r\n\r\n        if (\"SHA\".equals(algorithm)) {\r\n            return \"SHA1SUMS\";}\r\n\r\n        if (\"CRC32\".equals(algorithm)) {\r\n            return (files.size() == 1 ? files.elementAt(0) : files.getBaseFolder()).getName() + \".sfv\";\r\n        }\r\n\r\n        return algorithm.replace(\"-\", \"\")+\"SUMS\";\r\n    }\r\n\r\n\r\n    ///////////////////////////////////\r\n    // ActionListener implementation //\r\n    ///////////////////////////////////\r\n\r\n    public void actionPerformed(ActionEvent e) {\r\n        // Start by disposing this dialog\r\n        dispose();\r\n\r\n        if (e.getSource() != btnOk) {\r\n            return;\r\n        }\r\n        try {\r\n            MessageDigest digest = getSelectedMessageDigest();\r\n            String algorithm = digest.getAlgorithm();\r\n            AbstractFile checksumFile;\r\n\r\n            // Resolve the destination checksum file\r\n\r\n            if (specificLocationRadioButton.isSelected()) {\r\n                // User-defined checksum file\r\n                String enteredPath = specificLocationTextField.getText();\r\n\r\n                PathUtils.ResolvedDestination resolvedDest = PathUtils.resolveDestination(enteredPath, mainFrame.getActivePanel().getCurrentFolder());\r\n                // The path entered doesn't correspond to any existing folder\r\n                if (resolvedDest == null) {\r\n                    showErrorDialog(i18n(\"invalid_path\", enteredPath));\r\n                    return;\r\n                }\r\n\r\n                checksumFile = resolvedDest.getDestinationFile();\r\n                if (resolvedDest.getDestinationType()==PathUtils.ResolvedDestination.EXISTING_FOLDER) {\r\n                    checksumFile = checksumFile.getDirectChild(getChecksumFilename(algorithm));\r\n                }\r\n            } else {\r\n                // Temporary file\r\n                checksumFile = FileFactory.getTemporaryFile(getChecksumFilename(algorithm), true);\r\n            }\r\n\r\n            // Save the algorithm that was used for the next time this dialog is invoked\r\n            lastUsedAlgorithm = algorithm;\r\n\r\n            // Start processing files\r\n            ProgressDialog progressDialog = new ProgressDialog(mainFrame, i18n(\"properties_dialog.calculating\"));\r\n            CalculateChecksumJob job = new CalculateChecksumJob(progressDialog, mainFrame, files, checksumFile, digest);\r\n            progressDialog.start(job);\r\n        } catch (IOException ex) {\r\n            // Note: FileFactory.getTemporaryFile() should never throw an IOException\r\n\r\n            showErrorDialog(i18n(\"invalid_path\", specificLocationTextField.getText()));\r\n        }\r\n    }\r\n\r\n\r\n    @Override\r\n    public void itemStateChanged(ItemEvent e) {\r\n        Object source = e.getSource();\r\n\r\n        if (source == specificLocationRadioButton) {\r\n            // Enables/disables the text field when the corresponding radio button's selected state has changed.\r\n            specificLocationTextField.setEnabled(specificLocationRadioButton.isSelected());\r\n            specificLocationTextField.requestFocus();\r\n        } else if (source == algorithmComboBox) {\r\n            specificLocationTextField.setText(getChecksumFilename(getSelectedMessageDigest().getAlgorithm()));\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/ChangeDateDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.file;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileOperation;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.job.ChangeFileAttributesJob;\nimport com.mucommander.utils.text.CustomDateFormat;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.ChangeDateAction;\nimport com.mucommander.ui.dialog.DialogToolkit;\nimport com.mucommander.ui.layout.FluentPanel;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.awt.event.ItemEvent;\nimport java.awt.event.ItemListener;\nimport java.util.Date;\n\n/**\n * This dialog allows the user to change the date of the currently selected/marked file(s). By default, the date is now\n * but a specific date can be specified.\n *\n * @author Maxence Bernard\n */\npublic class ChangeDateDialog extends JobDialog implements ActionListener, ItemListener {\n\n    private final JRadioButton nowRadioButton;\n    private final JSpinner dateSpinner;\n    private final JCheckBox cdRecurseDir;\n    private final JButton btnOk;\n    private final JButton btnCancel;\n\n\n    public ChangeDateDialog(MainFrame mainFrame, FileSet files) {\n        super(mainFrame, ActionProperties.getActionLabel(ChangeDateAction.Descriptor.ACTION_ID), files);\n\n        YBoxPanel mainPanel = new YBoxPanel();\n\n        mainPanel.add(new JLabel(ActionProperties.getActionLabel(ChangeDateAction.Descriptor.ACTION_ID)+\" :\"));\n        mainPanel.addSpace(5);\n\n        ButtonGroup buttonGroup = new ButtonGroup();\n\n        AbstractFile destFile = files.size()==1?files.elementAt(0):files.getBaseFolder();\n        boolean canChangeDate = destFile.isFileOperationSupported(FileOperation.CHANGE_DATE);\n\n        nowRadioButton = new JRadioButton(i18n(\"change_date_dialog.now\"));\n        nowRadioButton.setSelected(true);\n        nowRadioButton.addItemListener(this);\n\n        mainPanel.add(new FluentPanel(new FlowLayout(FlowLayout.LEFT)).add(nowRadioButton));\n\n        buttonGroup.add(nowRadioButton);\n        JRadioButton specificDateRadioButton = new JRadioButton(i18n(\"change_date_dialog.specific_date\"));\n        buttonGroup.add(specificDateRadioButton);\n\n        this.dateSpinner = new JSpinner(new SpinnerDateModel());\n        dateSpinner.setEditor(new JSpinner.DateEditor(dateSpinner, CustomDateFormat.getDateFormatString()));\n        // Use the selected file's date if there is only one file, if not use base folder's date.\n        dateSpinner.setValue(new Date(destFile.getLastModifiedDate()));\n        // Spinner is disabled until the 'Specific date' radio button is selected \n        dateSpinner.setEnabled(false);\n\n        mainPanel.add(new FluentPanel(new FlowLayout(FlowLayout.LEFT))\n                          .add(specificDateRadioButton)\n                          .add(dateSpinner));\n\n        mainPanel.addSpace(10);\n        cdRecurseDir = new JCheckBox(i18n(\"recurse_directories\"));\n        mainPanel.add(cdRecurseDir);\n\n        mainPanel.addSpace(15);\n\n        // create file details button and OK/cancel buttons and lay them out a single row\n        JPanel fileDetailsPanel = createFileDetailsPanel();\n\n        btnOk = new JButton(i18n(\"change\"));\n        btnCancel = new JButton(i18n(\"cancel\"));\n\n        mainPanel.add(createButtonsPanel(createFileDetailsButton(fileDetailsPanel),\n                DialogToolkit.createOKCancelPanel(btnOk, btnCancel, getRootPane(), this)));\n        mainPanel.add(fileDetailsPanel);\n\n        getContentPane().add(mainPanel, BorderLayout.NORTH);\n\n        if (!canChangeDate) {\n            nowRadioButton.setEnabled(false);\n            specificDateRadioButton.setEnabled(false);\n            dateSpinner.setEnabled(false);\n            cdRecurseDir.setEnabled(false);\n            btnOk.setEnabled(false);\n        }\n\n        getRootPane().setDefaultButton(canChangeDate? btnOk : btnCancel);\n        setInitialFocusComponent(canChangeDate?nowRadioButton: btnCancel);\n        setResizable(false);\n    }\n\n\n    ///////////////////////////////////\n    // ActionListener implementation //\n    ///////////////////////////////////\n\n    public void actionPerformed(ActionEvent e) {\n        Object source = e.getSource();\n\n        if (source == btnOk) {\n            dispose();\n\n            // Starts copying files\n            ProgressDialog progressDialog = new ProgressDialog(mainFrame, i18n(\"progress_dialog.processing_files\"));\n            ChangeFileAttributesJob job = new ChangeFileAttributesJob(progressDialog, mainFrame, files,\n                nowRadioButton.isSelected() ? System.currentTimeMillis() : ((SpinnerDateModel)dateSpinner.getModel()).getDate().getTime(),\n                cdRecurseDir.isSelected());\n            progressDialog.start(job);\n        } else if (source == btnCancel) {\n            dispose();\n        }\n    }\n\n\n    /////////////////////////////////\n    // ItemListener implementation //\n    /////////////////////////////////\n\n    // Enable/disables the date spinner component when the radio button selection has changed  \n\n    public void itemStateChanged(ItemEvent e) {\n        if (e.getSource() == nowRadioButton) {\n            dateSpinner.setEnabled(!nowRadioButton.isSelected());\n        }\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/ChangePermissionsDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.file;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.PermissionAccesses;\nimport com.mucommander.commons.file.PermissionTypes;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.job.ChangeFileAttributesJob;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.ChangePermissionsAction;\nimport com.mucommander.ui.dialog.DialogToolkit;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.text.SizeConstrainedDocument;\n\nimport javax.swing.*;\nimport javax.swing.event.DocumentEvent;\nimport javax.swing.event.DocumentListener;\nimport javax.swing.text.AttributeSet;\nimport javax.swing.text.BadLocationException;\nimport javax.swing.text.Document;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.awt.event.ItemEvent;\nimport java.awt.event.ItemListener;\n\n/**\n * This dialog allows the user to change the permissions of the currently selected/marked file(s). The permissions can be\n * selected either by clicking individual read/write/executable checkboxes for each of the user/group/other accesses,\n * or by entering an octal permission value. \n *\n * @author Maxence Bernard\n */\npublic class ChangePermissionsDialog extends JobDialog\n        implements ActionListener, ItemListener, DocumentListener, PermissionTypes, PermissionAccesses {\n\n    private final JCheckBox[][] permCheckBoxes;\n\n    private final JTextField octalPermTextField;\n\n    private final JCheckBox recurseDirCheckBox;\n\n    /** If true, ItemEvent events should be ignored */\n    private boolean ignoreItemEvent;\n    /** If true, DocumentEvent events should be ignored */\n    private boolean ignoreDocumentEvent;\n\n    private final JButton btnOk;\n    private JButton btnCancel;\n\n\n    public ChangePermissionsDialog(MainFrame mainFrame, FileSet files) {\n        super(mainFrame, ActionProperties.getActionLabel(ChangePermissionsAction.Descriptor.ACTION_ID), files);\n\n        YBoxPanel mainPanel = new YBoxPanel();\n\n        mainPanel.add(new JLabel(ActionProperties.getActionLabel(ChangePermissionsAction.Descriptor.ACTION_ID)+\" :\"));\n        mainPanel.addSpace(10);\n\n        JPanel gridPanel = new JPanel(new GridLayout(4, 4));\n        permCheckBoxes = new JCheckBox[5][5];\n        JCheckBox permCheckBox;\n\n        AbstractFile firstFile = files.elementAt(0);\n        int permSetMask = firstFile.getChangeablePermissions().getIntValue();\n        boolean canSetPermission = permSetMask!=0;\n        int defaultPerms = firstFile.getPermissions().getIntValue();\n\n        gridPanel.add(new JLabel());\n        gridPanel.add(new JLabel(i18n(\"permissions.read\")));\n        gridPanel.add(new JLabel(i18n(\"permissions.write\")));\n        gridPanel.add(new JLabel(i18n(\"permissions.executable\")));\n\n        for (int a = USER_ACCESS; a >= OTHER_ACCESS; a--) {\n            gridPanel.add(new JLabel(i18n(a == USER_ACCESS ?\"permissions.user\" : a == GROUP_ACCESS ? \"permissions.group\" : \"permissions.other\")));\n\n            for (int p = READ_PERMISSION; p >= EXECUTE_PERMISSION; p = p>>1) {\n                permCheckBox = new JCheckBox();\n                permCheckBox.setSelected((defaultPerms & (p<<a*3))!=0);\n\n                // Enable the checkbox only if the permission can be set in the destination\n                if ((permSetMask & (p<<a*3))==0) {\n                    permCheckBox.setEnabled(false);\n                } else {\n                    permCheckBox.addItemListener(this);\n                }\n\n                gridPanel.add(permCheckBox);\n                permCheckBoxes[a][p] = permCheckBox;\n            }\n        }\n\n        mainPanel.add(gridPanel);\n\n        octalPermTextField = new JTextField(3);\n        // Constrains text field to 3 digits, from 0 to 7 (octal base)\n        Document doc = new SizeConstrainedDocument(3) {\n            @Override\n            public void insertString(int offset, String str, AttributeSet attributeSet) throws BadLocationException {\n                int strLen = str.length();\n                for (int i = 0; i < strLen; i++) {\n                    char c = str.charAt(i);\n                    if (c < '0' || c > '7') {\n                        return;\n                    }\n                }\n\n                super.insertString(offset, str, attributeSet);\n            }\n        };\n        octalPermTextField.setDocument(doc);\n        // Initializes the field's value\n        updateOctalPermTextField();\n\n        if (canSetPermission) {\n            doc.addDocumentListener(this);\n        } else {    // Disable text field if no permission bit can be set\n            octalPermTextField.setEnabled(false);\n        }\n\n        mainPanel.addSpace(10);\n        JPanel tempPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        tempPanel.add(new JLabel(i18n(\"permissions.octal_notation\")));\n        tempPanel.add(octalPermTextField);\n        mainPanel.add(tempPanel);\n\n        mainPanel.addSpace(15);\n\n        recurseDirCheckBox = new JCheckBox(i18n(\"recurse_directories\"));\n        // Disable check box if no permission bit can be set\n        recurseDirCheckBox.setEnabled(canSetPermission && (files.size()>1 || files.elementAt(0).isDirectory()));\n        mainPanel.add(recurseDirCheckBox);\n\n        // create file details button and OK/cancel buttons and lay them out a single row\n        JPanel fileDetailsPanel = createFileDetailsPanel();\n\n        btnOk = new JButton(i18n(\"change\"));\n        btnCancel = new JButton(i18n(\"cancel\"));\n\n        mainPanel.add(createButtonsPanel(createFileDetailsButton(fileDetailsPanel),\n                DialogToolkit.createOKCancelPanel(btnOk, btnCancel, getRootPane(), this)));\n        mainPanel.add(fileDetailsPanel);\n\n        getContentPane().add(mainPanel, BorderLayout.NORTH);\n\n        if (!canSetPermission) {\n            // Disable OK button if no permission bit can be set\n            btnOk.setEnabled(false);\n        }\n\n        getRootPane().setDefaultButton(canSetPermission ? btnOk : btnCancel);\n        setResizable(false);\n    }\n\n\n    /**\n     * Creates and returns a permissions int using the values of the permission checkboxes.\n     */\n    private int getPermInt() {\n        int perms = 0;\n\n        for (int a = USER_ACCESS; a >= OTHER_ACCESS; a--) {\n            for (int p = READ_PERMISSION; p >= EXECUTE_PERMISSION; p = p>>1) {\n                JCheckBox permCheckBox = permCheckBoxes[a][p];\n\n                if (permCheckBox.isSelected()) {\n                    perms |= (p << a * 3);\n                }\n            }\n        }\n\n        return perms;\n    }\n\n\n    /**\n     * Updates the octal permissions text field's value to reflect the permission checkboxes' values.\n     */\n    private void updateOctalPermTextField() {\n        String octalStr = Integer.toOctalString(getPermInt());\n        int len = octalStr.length();\n        for (int i = len; i < 3; i++) {\n            octalStr = \"0\" + octalStr;\n        }\n\n        octalPermTextField.setText(octalStr);\n    }\n\n\n    /**\n     * Updates the permission checkboxes' values to reflect the octal permissions text field.\n     */\n    private void updatePermCheckBoxes() {\n        String octalStr = octalPermTextField.getText();\n\n        int perms = octalStr.isEmpty() ? 0 : Integer.parseInt(octalStr, 8);\n\n        for (int a = USER_ACCESS; a >= OTHER_ACCESS; a--) {\n            for (int p = READ_PERMISSION; p >= EXECUTE_PERMISSION; p = p>>1) {\n                JCheckBox permCheckBox = permCheckBoxes[a][p];\n//                if(permCheckBox.isEnabled())\n                permCheckBox.setSelected((perms & (p<<a*3))!=0);\n            }\n        }\n\n    }\n\n\n    ///////////////////////////////////\n    // ActionListener implementation //\n    ///////////////////////////////////\n\n    public void actionPerformed(ActionEvent e) {\n        Object source = e.getSource();\n\n        if (source == btnOk) {\n            dispose();\n\n            // Starts copying files\n            ProgressDialog progressDialog = new ProgressDialog(mainFrame, i18n(\"progress_dialog.processing_files\"));\n            ChangeFileAttributesJob job = new ChangeFileAttributesJob(progressDialog, mainFrame, files, getPermInt(), recurseDirCheckBox.isSelected());\n            progressDialog.start(job);\n        } else if (source == btnCancel) {\n            dispose();\n        }\n    }\n\n\n    /////////////////////////////////\n    // ItemListener implementation //\n    /////////////////////////////////\n\n    // Update the octal permission text field whenever one of the permission checkboxes' value has changed\n\n    public void itemStateChanged(ItemEvent e) {\n        if (ignoreItemEvent) {\n            return;\n        }\n\n        ignoreDocumentEvent = true;\n        updateOctalPermTextField();\n        ignoreDocumentEvent = false;\n    }\n\n\n    //////////////////////////////\n    // DocumentListener methods //\n    //////////////////////////////\n\n    // Update the permission checkboxes' values whenever the octal permission text field has changed\n\n    public void changedUpdate(DocumentEvent e) {\n        if (ignoreDocumentEvent) {\n            return;\n        }\n\n        ignoreItemEvent = true;\n        updatePermCheckBoxes();\n        ignoreItemEvent = false;\n    }\n\n    public void insertUpdate(DocumentEvent e) {\n        if (ignoreDocumentEvent) {\n            return;\n        }\n\n        ignoreItemEvent = true;\n        updatePermCheckBoxes();\n        ignoreItemEvent = false;\n    }\n\n    public void removeUpdate(DocumentEvent e) {\n        if (ignoreDocumentEvent) {\n            return;\n        }\n\n        ignoreItemEvent = true;\n        updatePermCheckBoxes();\n        ignoreItemEvent = false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/ChangeReplicationDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.file;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileOperation;\nimport com.mucommander.commons.file.UnsupportedFileOperationException;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.job.ChangeFileAttributesJob;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.ChangeReplicationAction;\nimport com.mucommander.ui.dialog.DialogToolkit;\nimport com.mucommander.ui.layout.FluentPanel;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.*;\nimport javax.swing.text.AttributeSet;\nimport javax.swing.text.BadLocationException;\nimport javax.swing.text.Document;\nimport javax.swing.text.PlainDocument;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\n\n/**\n * This dialog allows the user to change the date of the currently selected/marked file(s). By default, the date is now\n * but a specific date can be specified.\n *\n * @author Maxence Bernard\n */\npublic class ChangeReplicationDialog extends JobDialog implements ActionListener {\n\n    private final IntTextField replication;\n\n    private final JCheckBox cbRecurseDir;\n\n    private final JButton btnOk;\n    private final JButton btnCancel;\n\n\n    public ChangeReplicationDialog(MainFrame mainFrame, FileSet files) {\n        super(mainFrame, ActionProperties.getActionLabel(ChangeReplicationAction.Descriptor.ACTION_ID), files);\n\n        YBoxPanel mainPanel = new YBoxPanel();\n\n        mainPanel.add(new JLabel(ActionProperties.getActionLabel(ChangeReplicationAction.Descriptor.ACTION_ID)+\" :\"));\n        mainPanel.addSpace(5);\n\n        AbstractFile destFile = files.size()==1?files.elementAt(0):files.getBaseFolder();\n        boolean canChangeReplication = destFile.isFileOperationSupported(FileOperation.CHANGE_REPLICATION);\n\n        short lastReplication=0;\n        try {\n            lastReplication=destFile.getReplication();\n        } catch (UnsupportedFileOperationException e) {\n            e.printStackTrace();\n        }\n        replication  = new IntTextField(lastReplication, 2);\n\n        JPanel tempPanel = new FluentPanel(new FlowLayout(FlowLayout.LEFT));\n        tempPanel.add(new JLabel(i18n(\"replication.number\")));\n        tempPanel.add(replication);\n        mainPanel.add(tempPanel);\n\n        mainPanel.addSpace(10);\n\n        cbRecurseDir = new JCheckBox(i18n(\"recurse_directories\"));\n        mainPanel.add(cbRecurseDir);\n\n        mainPanel.addSpace(15);\n\n        // create file details button and OK/cancel buttons and lay them out a single row\n        JPanel fileDetailsPanel = createFileDetailsPanel();\n\n        btnOk = new JButton(i18n(\"change\"));\n        btnCancel = new JButton(i18n(\"cancel\"));\n\n        mainPanel.add(createButtonsPanel(createFileDetailsButton(fileDetailsPanel),\n                DialogToolkit.createOKCancelPanel(btnOk, btnCancel, getRootPane(), this)));\n        mainPanel.add(fileDetailsPanel);\n\n        getContentPane().add(mainPanel, BorderLayout.NORTH);\n\n        if (!canChangeReplication) {\n            replication.setEnabled(false);\n            cbRecurseDir.setEnabled(false);\n            btnOk.setEnabled(false);\n        }\n\n        getRootPane().setDefaultButton(canChangeReplication? btnOk : btnCancel);\n        setInitialFocusComponent(canChangeReplication?replication: btnCancel);\n        setResizable(true);\n    }\n\n\n    ///////////////////////////////////\n    // ActionListener implementation //\n    ///////////////////////////////////\n\n    public void actionPerformed(ActionEvent e) {\n        Object source = e.getSource();\n\n        if (source == btnOk) {\n            dispose();\n\n            // Change replication\n            ProgressDialog progressDialog = new ProgressDialog(mainFrame, i18n(\"progress_dialog.processing_files\"));\n            ChangeFileAttributesJob job = new ChangeFileAttributesJob(progressDialog, mainFrame, files,\n                    (short)replication.getValue(),\n                cbRecurseDir.isSelected());\n            progressDialog.start(job);\n        } else if (source == btnCancel) {\n            dispose();\n        }\n    }\n\n\n    static class IntTextField extends JTextField {\n        IntTextField(int defval, int size) {\n            super(\"\" + defval, size);\n        }\n\n        protected Document createDefaultModel() {\n            return new IntTextDocument();\n        }\n\n        public boolean isValid() {\n            try {\n                Integer.parseInt(getText());\n                return true;\n            } catch (Exception e) {\n                return false;\n            }\n        }\n\n        public int getValue() {\n            try {\n                return Integer.parseInt(getText());\n            } catch (NumberFormatException e) {\n                return 0;\n            }\n        }\n\n        static class IntTextDocument extends PlainDocument {\n            public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {\n                if (str == null) {\n                    return;\n                }\n                String oldString = getText(0, getLength());\n                String newString = oldString.substring(0, offs) + str + oldString.substring(offs);\n                try {\n                    Integer.parseInt(newString + \"0\");\n                    super.insertString(offs, str, a);\n                } catch (NumberFormatException ignored) {}\n            }\n        }\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/CombineFilesDialog.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.file;\r\n\r\nimport java.io.IOException;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.filter.AndFileFilter;\r\nimport com.mucommander.commons.file.filter.AttributeFileFilter;\r\nimport com.mucommander.commons.file.filter.AttributeFileFilter.FileAttribute;\r\nimport com.mucommander.commons.file.filter.EqualsFilenameFilter;\r\nimport com.mucommander.commons.file.filter.StartsWithFilenameFilter;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.commons.file.util.PathUtils;\r\nimport com.mucommander.commons.file.util.PathUtils.ResolvedDestination;\r\nimport com.mucommander.job.CombineFilesJob;\r\nimport com.mucommander.job.TransferFileJob;\r\nimport com.mucommander.ui.action.ActionProperties;\r\nimport com.mucommander.ui.action.impl.CombineFilesAction;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\n/**\r\n * Dialog used to combine file parts into the original file.\r\n * \r\n * @author Mariusz Jakubowski\r\n */\r\npublic class CombineFilesDialog extends TransferDestinationDialog {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(CombineFilesDialog.class);\r\n\t\r\n    private final AbstractFile destFolder;\r\n\r\n    /**\r\n     * Creates a new combine file dialog.\r\n     * @param mainFrame the main frame\r\n     * @param files a list of files to combine\r\n     * @param destFolder default destination folder\r\n     */\r\n    public CombineFilesDialog(MainFrame mainFrame, FileSet files, AbstractFile destFolder) {\r\n        super(mainFrame, files, \r\n        \t\tActionProperties.getActionLabel(CombineFilesAction.Descriptor.ACTION_ID),\r\n                i18n(\"copy_dialog.destination\"),\r\n                i18n(\"combine\"),\r\n                i18n(\"combine_files_dialog.error_title\"),\r\n                true);\r\n\r\n        this.destFolder = destFolder;\r\n    }\r\n\r\n    /**\r\n     * Searches for parts of a file.  \r\n     * @param part1 first part of a file\r\n     */\r\n    private void searchParts(AbstractFile part1) {\r\n\t\tAbstractFile parent = part1.getParent();\r\n\t\tif (parent == null) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tString ext = part1.getExtension();\r\n\t\tint firstIndex;\r\n\t\ttry {\r\n\t\t\tfirstIndex = Integer.parseInt(ext);\r\n\t\t} catch (NumberFormatException e) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tAndFileFilter filter = new AndFileFilter(\r\n            new StartsWithFilenameFilter(part1.getNameWithoutExtension(), false),\r\n            new AttributeFileFilter(FileAttribute.FILE),\r\n            new EqualsFilenameFilter(part1.getName(), false, true)\r\n        );\r\n\r\n\t\ttry {\r\n\t\t\tAbstractFile[] otherParts = parent.ls(filter);\r\n            for (AbstractFile otherPart : otherParts) {\r\n                String ext2 = otherPart.getExtension();\r\n                try {\r\n                    int partIdx = Integer.parseInt(ext2);\r\n                    if (partIdx > firstIndex)\r\n                        files.add(otherPart);\r\n                } catch (NumberFormatException e) {\r\n                    // nothing\r\n                }\r\n            }\r\n\t\t} catch (IOException e) {\r\n            LOGGER.debug(\"Caught exception\", e);\r\n\t\t}\r\n\t\tsetFiles(files);\r\n\t}\r\n\r\n    @Override\r\n    protected boolean isValidDestination(PathUtils.ResolvedDestination resolvedDest, String destPath) {\r\n        // The path entered doesn't correspond to any existing folder\r\n        if (resolvedDest == null) {\r\n            showErrorDialog(i18n(\"invalid_path\", destPath), errorDialogTitle);\r\n            return false;\r\n        }\r\n        return true;\r\n\t}\r\n\r\n\r\n    //////////////////////////////////////////////\r\n    // TransferDestinationDialog implementation //\r\n    //////////////////////////////////////////////\r\n\r\n    @Override\r\n    protected PathFieldContent computeInitialPath(FileSet files) {\r\n        String path = destFolder.getAbsolutePath(true) + files.elementAt(0).getNameWithoutExtension();\r\n        if (files.size() == 1) {\r\n        \tsearchParts(files.elementAt(0));\r\n        }\r\n\r\n        return new PathFieldContent(path);\r\n    }\r\n\r\n    @Override\r\n    protected TransferFileJob createTransferFileJob(ProgressDialog progressDialog, ResolvedDestination resolvedDest, int defaultFileExistsAction) {\r\n\t\treturn new CombineFilesJob(progressDialog, mainFrame,\r\n\t\t       files, resolvedDest.getDestinationFile(), defaultFileExistsAction);\r\n\t}\r\n\r\n    @Override\r\n    protected String getProgressDialogTitle() {\r\n        return i18n(\"progress_dialog.processing_files\");\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/CopyDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.dialog.file;\n\nimport com.mucommander.commons.file.AbstractArchiveEntryFile;\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.ArchiveEntry;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.commons.file.util.PathUtils;\nimport com.mucommander.job.CopyJob;\nimport com.mucommander.job.TransferFileJob;\nimport com.mucommander.job.UnpackJob;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.CopyAction;\nimport com.mucommander.ui.main.MainFrame;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n\n/**\n * Dialog invoked when the user wants to copy currently selected files. The destination field is pre-filled with\n * the 'other' panel's path and, if there is only one file to copy, with the source file's name.\n *\n * @see com.mucommander.ui.action.impl.CopyAction\n * @author Maxence Bernard\n */\npublic class CopyDialog extends AbstractCopyDialog {\n\n    /**\n     * Creates a new <code>CopyDialog</code>.\n     *\n     * @param mainFrame the main frame that spawned this dialog.\n     * @param files files to be copied\n     */\n    public CopyDialog(MainFrame mainFrame, FileSet files) {\n        super(mainFrame, files,\n              ActionProperties.getActionLabel(CopyAction.Descriptor.ACTION_ID),\n              i18n(\"copy_dialog.destination\"),\n              i18n(\"copy\"),\n              i18n(\"copy_dialog.error_title\"));\n    }\n\n\n    //////////////////////////////////////////////\n    // TransferDestinationDialog implementation //\n    //////////////////////////////////////////////\n\n    @Override\n    protected TransferFileJob createTransferFileJob(ProgressDialog progressDialog, PathUtils.ResolvedDestination resolvedDest, int defaultFileExistsAction) {\n        AbstractFile baseFolder = files.getBaseFolder();\n        AbstractArchiveFile parentArchiveFile = baseFolder.getParentArchive();\n        TransferFileJob job;\n        String newName = resolvedDest.getDestinationType() == PathUtils.ResolvedDestination.EXISTING_FOLDER ? null:resolvedDest.getDestinationFile().getName();\n\n        // If the source files are located inside an archive, use UnpackJob instead of CopyJob to unpack archives in\n        // their natural order (more efficient)\n        if (parentArchiveFile != null) {\n            // Add all selected archive entries to a vector\n            //int nbFiles = files.size();\n            List<ArchiveEntry> selectedEntries = new ArrayList<>();\n            for (AbstractFile file : files) {\n                System.out.println(\": \" + file.getAbsolutePath() + \" => \" + file.getAncestor(AbstractArchiveEntryFile.class) + \" ==> \" + file.getAncestor(AbstractArchiveEntryFile.class).getUnderlyingFileObject());\n                //selectedEntries.add((ArchiveEntry)file.getAncestor(AbstractArchiveEntryFile.class).getUnderlyingFileObject());\n                selectedEntries.add((ArchiveEntry)file.getAncestor(AbstractArchiveEntryFile.class).getUnderlyingFileObject());\n            }\n            job = new UnpackJob(\n                progressDialog,\n                mainFrame,\n                parentArchiveFile,\n                PathUtils.getDepth(baseFolder.getAbsolutePath(), baseFolder.getSeparator()) - PathUtils.getDepth(parentArchiveFile.getAbsolutePath(), parentArchiveFile.getSeparator()),\n                resolvedDest.getDestinationFolder(),\n                newName,\n                defaultFileExistsAction,\n                selectedEntries\n            );\n        } else {\n            job = new CopyJob(\n                progressDialog,\n                mainFrame,\n                files,\n                resolvedDest.getDestinationFolder(),\n                newName,\n                CopyJob.Mode.COPY,\n                defaultFileExistsAction);\n        }\n\n        return job;\n    }\n\n    @Override\n    protected String getProgressDialogTitle() {\n        return i18n(\"copy_dialog.copying\");\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/DeleteDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.file;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.desktop.AbstractTrash;\nimport com.mucommander.desktop.DesktopManager;\nimport com.mucommander.job.DeleteJob;\nimport com.mucommander.ui.action.ActionManager;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.DeleteAction;\nimport com.mucommander.ui.action.impl.PermanentDeleteAction;\nimport com.mucommander.ui.dialog.DialogToolkit;\nimport com.mucommander.ui.layout.InformationPane;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.main.MainFrame;\nimport org.jetbrains.annotations.NotNull;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.awt.event.ItemEvent;\nimport java.awt.event.ItemListener;\n\n\n/**\n * Confirmation dialog invoked when the user wants to delete currently selected files. It allows to choose between two\n * different ways of deleting files: move them to the trash or permanently erase them. The former choice is only given\n * if a trash is available on the current platform and capable of moving the selected files.\n * The choice (use trash or not) is saved in the preferences and reused next time this dialog is invoked.   \n *\n * @see com.mucommander.ui.action.impl.DeleteAction\n * @author Maxence Bernard\n */\npublic class DeleteDialog extends JobDialog implements ItemListener, ActionListener {\n\n    /** Should files be moved to the trash or permanently erased */\n    private boolean moveToTrash;\n\n    /** Allows to control whether files should be moved to trash when deleted or permanently erased */\n    private JCheckBox cbMoveToTrash;\n\n    /** Informs the user about the consequences of deleting files, based on the current 'Move to trash' choice */\n    private final InformationPane informationPane;\n\n    /** The button that confirms deletion */\n    private final JButton btnDelete;\n\n    /** Dialog size constraints */\n    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(360, 0);\n\n\n    public DeleteDialog(MainFrame mainFrame, FileSet files, boolean deletePermanently) {\n        super(mainFrame, ActionProperties.getActionLabel(DeleteAction.Descriptor.ACTION_ID), files);\n\n        this.mainFrame = mainFrame;\n\n        YBoxPanel mainPanel = new YBoxPanel();\n\n        // Allow 'Move to trash' option only if:\n        // - the current platform has a trash\n        // - the base folder is not an archive\n        // - the base folder of the to-be-deleted files is not a trash folder or one of its children\n        // - the base folder can be moved to the trash (the eligibility conditions should be the same as the files to-be-deleted)\n        AbstractTrash trash = DesktopManager.getTrash();\n        AbstractFile baseFolder = files.getBaseFolder();\n        if (trash != null && !baseFolder.isArchive() && !trash.isTrashFile(baseFolder) && trash.canMoveToTrash(baseFolder)) {\n            moveToTrash = !deletePermanently;\n\n            cbMoveToTrash = new JCheckBox(i18n(\"delete_dialog.move_to_trash.option\"), moveToTrash);\n            cbMoveToTrash.addItemListener(this);\n        }\n\n        informationPane = new InformationPane();\n        mainPanel.add(informationPane);\n        mainPanel.addSpace(10);\n\n        // add panel with one file above buttons\n        JPanel fileDetailsPanel = createFileDetailsPanel(files.size() > 1);\n        if (files.size() == 1) {\n            mainPanel.add(fileDetailsPanel);\n        }\n\n        // create file details button and OK/cancel buttons and lay them out a single row\n        btnDelete = new JButton(i18n(\"delete\"));\n        JButton cancelButton = new JButton(i18n(\"cancel\"));\n\n        mainPanel.add(createButtonsPanel(files.size() > 1 ? createFileDetailsButton(fileDetailsPanel) : null,\n                DialogToolkit.createOKCancelPanel(btnDelete, cancelButton, getRootPane(), this)));\n\n        // add panel with multiple fil list below buttons\n        if (files.size() > 1) {\n            mainPanel.add(fileDetailsPanel);\n        }\n\n        if (cbMoveToTrash != null) {\n            mainPanel.add(cbMoveToTrash);\n        }\n\n        getContentPane().add(mainPanel);\n\n        // Give initial keyboard focus to the 'Delete' button\n        setInitialFocusComponent(btnDelete);\n\n        // Call dispose() when dialog is closed\n        setDefaultCloseOperation(DISPOSE_ON_CLOSE);\n\n        updateDialog();\n        if (files.size() > 1) {\n            // Size dialog and show it to the screen\n            setMinimumSize(MINIMUM_DIALOG_DIMENSION);\n            //setResizable(false);\n        } else {\n            Dimension d = getContentPane().getPreferredSize();\n            getContentPane().setMaximumSize(new Dimension(getContentPane().getMaximumSize().width, getContentPane().getPreferredSize().height));\n            setMinimumSizeDialog(new Dimension(d.width*6/5, d.height*6/5));\n            int maxWidth = Math.max(d.width*4, mainFrame.getJFrame().getWidth());\n            setMaximumSizeDialog(new Dimension(maxWidth, d.height*3/2));\n        }\n    }\n\n\n    /**\n     * Updates the information pane to reflect the current 'Move to trash' choice.\n     */\n    private void updateDialog() {\n        String textId = buildTitleId();\n        informationPane.getMainLabel().setText(i18n(textId));\n        String messageId = buildMessageId();\n        informationPane.getCaptionLabel().setText(i18n(messageId));\n        informationPane.setIcon(moveToTrash ? null : InformationPane.getPredefinedIcon(InformationPane.WARNING_ICON));\n        setTitle(ActionManager.getActionInstance(moveToTrash ? DeleteAction.Descriptor.ACTION_ID:PermanentDeleteAction.Descriptor.ACTION_ID, mainFrame).getLabel());\n    }\n\n    @NotNull\n    private String buildMessageId() {\n        if (moveToTrash) {\n            if (files.size() == 1) {\n                AbstractFile file = files.getFirst();\n                return file.isSymlink() ? \"this_operation_cannot_be_undone\" : \"delete_dialog.move_to_trash.confirmation_details_1\";\n            } else {\n                return \"delete_dialog.move_to_trash.confirmation_details\";\n            }\n        } else {\n            return \"this_operation_cannot_be_undone\";\n        }\n    }\n\n    @NotNull\n    private String buildTitleId() {\n        boolean singleFileMode = files.size() == 1;\n        if (singleFileMode) {\n            AbstractFile file = files.getFirst();\n            if (file.isSymlink()) {\n                return \"delete_dialog.permanently_delete.symlink_confirmation_1\";\n            } else {\n                return moveToTrash ? \"delete_dialog.move_to_trash.confirmation_1\" : \"delete_dialog.permanently_delete.confirmation_1\";\n            }\n        }\n        return moveToTrash ? \"delete_dialog.move_to_trash.confirmation\" : \"delete_dialog.permanently_delete.confirmation\";\n    }\n\n    @Override\n    public void itemStateChanged(ItemEvent e) {\n        moveToTrash = cbMoveToTrash.isSelected();\n        updateDialog();\n        pack();\n    }\n\n    @Override\n    public void actionPerformed(ActionEvent e) {\n        // Start by disposing this dialog\n        dispose();\n\n        if (e.getSource() == btnDelete) {\n            // Starts deleting files\n            ProgressDialog progressDialog = new ProgressDialog(mainFrame, i18n(\"delete_dialog.deleting\"));\n            if (getReturnFocusTo() != null) {\n                progressDialog.returnFocusTo(getReturnFocusTo());\n            }\n            DeleteJob deleteJob = new DeleteJob(progressDialog, mainFrame, files, moveToTrash);\n            progressDialog.start(deleteJob);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/DownloadDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.dialog.file;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.commons.file.util.PathUtils;\nimport com.mucommander.job.CopyJob;\nimport com.mucommander.job.TransferFileJob;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.main.MainFrame;\n\n\n/**\n * Dialog invoked when the user wants to download a file.\n *\n * @author Maxence Bernard\n */\npublic class DownloadDialog extends TransferDestinationDialog {\n\n    public DownloadDialog(MainFrame mainFrame, FileSet files) {\n        super(mainFrame, files,\n              Translator.get(\"download_dialog.download\"),\n              Translator.get(\"download_dialog.description\"),\n              Translator.get(\"download_dialog.download\"),\n              Translator.get(\"download_dialog.error_title\"),\n              true);\n    }\n\n    \n    //////////////////////////////////////////////\n    // TransferDestinationDialog implementation //\n    //////////////////////////////////////////////\n\n    @Override\n    protected PathFieldContent computeInitialPath(FileSet files) {\n        AbstractFile file = files.elementAt(0);\n\n        //\t\tAbstractFile activeFolder = mainFrame.getActiveTable().getCurrentFolder();\n        AbstractFile inactiveFolder = mainFrame.getInactivePanel().getCurrentFolder();\n        // Fill text field with current folder's absolute path and file name\n        return new PathFieldContent(inactiveFolder.getAbsolutePath(true)+file.getName());\n    }\n\n    @Override\n    protected TransferFileJob createTransferFileJob(ProgressDialog progressDialog, PathUtils.ResolvedDestination resolvedDest, int defaultFileExistsAction) {\n        return new CopyJob(\n                progressDialog,\n                mainFrame,\n                files,\n                resolvedDest.getDestinationFolder(),\n                resolvedDest.getDestinationType()==PathUtils.ResolvedDestination.EXISTING_FOLDER?null:resolvedDest.getDestinationFile().getName(),\n                CopyJob.Mode.DOWNLOAD,\n                defaultFileExistsAction);\n    }\n\n    @Override\n    protected String getProgressDialogTitle() {\n        return Translator.get(\"download_dialog.downloading\");\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/EmailFilesDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.file;\n\nimport java.awt.BorderLayout;\nimport java.awt.Container;\nimport java.awt.Dimension;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.awt.event.ItemEvent;\nimport java.awt.event.ItemListener;\nimport java.io.IOException;\n\nimport javax.swing.JButton;\nimport javax.swing.JCheckBox;\nimport javax.swing.JLabel;\nimport javax.swing.JScrollPane;\nimport javax.swing.JTextArea;\nimport javax.swing.JTextField;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.job.SendMailJob;\nimport com.mucommander.utils.text.SizeFormat;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.EmailAction;\nimport com.mucommander.ui.dialog.DialogToolkit;\nimport com.mucommander.ui.layout.XAlignedComponentPanel;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.main.MainFrame;\n\n/**\n * Dialog allowing the user to email files to someone.\n *\n * <p>One or several recipients, as well as a mail subject and body can be input.\n * The dialog also allows the user to review the files that have been marked,\n * select/unselect some, and displays the total file size.\n *\n * @author Maxence Bernard\n */\npublic class EmailFilesDialog extends JobDialog implements ActionListener, ItemListener {\n\n    private FileSet flattenedFiles;\n\t\n    private JTextField toField;\n    private JTextField subjectField;\n    private JTextArea bodyArea;\n    private JLabel infoLabel;\n    private JCheckBox fileCheckboxes[];\n\n    private static String lastTo = \"\";\n    private static String lastSubject = \"\";\n    private static String lastBody = \"\";\n\n    private JButton okButton;\n    private JButton cancelButton;\n\n    // Dialog size constraints\n    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(400,0);\t\n    private final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(550,400);\t\n\t\n\t\n    public EmailFilesDialog(MainFrame mainFrame, FileSet files) {\n        super(mainFrame, ActionProperties.getActionLabel(EmailAction.Descriptor.ACTION_ID), files);\n\t\t\n        try {\n            // Figures out which files to send and calculates the number of files and the number of bytes\n            this.flattenedFiles = getFlattenedFiles(files);\n\t\t\t\n            Container contentPane = getContentPane();\n\t\t\t\n            YBoxPanel mainPanel = new YBoxPanel(5);\n\t\n            // Text fields panel\n            XAlignedComponentPanel compPanel = new XAlignedComponentPanel();\n\n            // From (sender) field, non editable\n            JLabel fromLabel = new JLabel(TcConfigurations.getPreferences().getVariable(TcPreference.MAIL_SENDER_NAME)\n                                          +\" <\"+ TcConfigurations.getPreferences().getVariable(TcPreference.MAIL_SENDER_ADDRESS)+\">\");\n            //\t\t\tfromField.setEditable(false);\n            compPanel.addRow(i18n(\"email_dialog.from\")+\":\", fromLabel, 10);\n\t\t\t\n            // To (recipients) field\n            toField = new JTextField(lastTo);\n            compPanel.addRow(i18n(\"email_dialog.to\")+\":\", toField, 10);\n\t\t\t\n            // Subject field\n            subjectField = new JTextField(lastSubject);\n            compPanel.addRow(i18n(\"email_dialog.subject\")+\":\", subjectField, 15);\n\n            mainPanel.add(compPanel);\t\t\n\t\n            // Body area\n            bodyArea = new JTextArea(lastBody);\n            bodyArea.setRows(6);\n            bodyArea.setLineWrap(true);\n            JScrollPane scrollPane = new JScrollPane(bodyArea, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);\n            mainPanel.add(scrollPane);\n\t\t\t\n            mainPanel.addSpace(15);\n\t\t\t\n            // Label showing the number of files and total size\n            infoLabel = new JLabel();\n            mainPanel.add(infoLabel);\t\t\t\n\n            contentPane.add(mainPanel, BorderLayout.NORTH);\n\t\t\t\n            // checkbox showing all files that are to be sent, allowing them to be unselected\n            int nbFiles = flattenedFiles.size();\n            fileCheckboxes = new JCheckBox[nbFiles];\n            if (nbFiles > 0) {\n                YBoxPanel tempPanel2 = new YBoxPanel();\n                for(int i=0; i<nbFiles; i++) {\n                    AbstractFile file = flattenedFiles.elementAt(i);\n                    fileCheckboxes[i] = new JCheckBox(file.getName()\n                                                      +\" (\"+ SizeFormat.format(file.getSize(), SizeFormat.DIGITS_MEDIUM| SizeFormat.UNIT_SHORT| SizeFormat.INCLUDE_SPACE| SizeFormat.ROUND_TO_KB)+\")\", true);\n                    fileCheckboxes[i].addItemListener(this);\n                    tempPanel2.add(fileCheckboxes[i]);\n                }\n                scrollPane = new JScrollPane(tempPanel2, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);\n                contentPane.add(scrollPane, BorderLayout.CENTER);\n            }\n            updateInfoLabel();\n\t\t\t\t\n            // OK / Cancel buttons panel\n            okButton = new JButton(i18n(\"email_dialog.send\"));\n            cancelButton = new JButton(i18n(\"cancel\"));\n            contentPane.add(DialogToolkit.createOKCancelPanel(okButton, cancelButton, getRootPane(), this), BorderLayout.SOUTH);\n\t\n            // 'To' field will receive initial focus\n            setInitialFocusComponent(toField);\t\t\n\t\t\t\n            // Packs dialog\n            setMinimumSize(MINIMUM_DIALOG_DIMENSION);\n            setMaximumSize(MAXIMUM_DIALOG_DIMENSION);\n        } catch(IOException e) {\n            showErrorDialog(i18n(\"email_dialog.read_error\"), i18n(\"email_dialog.error_title\"));\n        }\n    }\n\n\n    /**\n     * Updates the number of selected files and their total size.\n     */\n    private void updateInfoLabel() {\n        int nbFiles = fileCheckboxes.length;\n        int nbSelected = 0;\n        int bytesTotal = 0;\n        long fileSize;\n        for (int i=0; i<nbFiles; i++) {\n            if (fileCheckboxes[i].isSelected()) {\n                fileSize = flattenedFiles.elementAt(i).getSize();\n                if (fileSize > 0) {\n                    bytesTotal += fileSize;\n                }\n                nbSelected++;\n            }\n        }\n        String text =\n                i18n(\"nb_files\", \"\"+nbSelected)\n            +(nbSelected==0?\"\":\" (\"+ SizeFormat.format(bytesTotal, SizeFormat.DIGITS_MEDIUM| SizeFormat.UNIT_LONG| SizeFormat.ROUND_TO_KB)+\")\");\n        infoLabel.setText(text);\n        infoLabel.repaint(100);\n    }\n\n\n    /**\n     * Returns a FileSet of *files* (as opposed to folders) that have been found either in the given \n     * FileSet or in one of the subfolders. \n     *\n     * @param originalFiles files as selected by the user which may contain folders\n     */\n    private FileSet getFlattenedFiles(FileSet originalFiles) throws IOException {\n        int nbFiles = originalFiles.size();\n        FileSet flattenedFiles = new FileSet(originalFiles.getBaseFolder());\n        for (int i=0; i<nbFiles; i++) {\n            recurseOnFolder(originalFiles.elementAt(i), flattenedFiles);\n        }\n\n        return flattenedFiles;\n    }\n\n    /**\n     * Adds the given file to the FileSet if it's not a folder, recurses otherwise.\n     */\n    private void recurseOnFolder(AbstractFile file, FileSet flattenedFiles) throws IOException {\n        if (file.isDirectory() && !file.isSymlink()) {\n            AbstractFile children[] = file.ls();\n            for (AbstractFile child : children) {\n                recurseOnFolder(child, flattenedFiles);\n            }\n        } else {\n            flattenedFiles.add(file);\n        }\n    }\n\t\n\t\n    ////////////////////////////\n    // ActionListener methods //\n    ////////////////////////////\n\t\n    public void actionPerformed(ActionEvent e) {\n        Object source = e.getSource();\n\t\t\n        // OK Button\n        if (source==okButton)  {\n            String to = toField.getText().trim();\n            String subject = subjectField.getText();\n            String body = bodyArea.getText();\n            if (!to.isEmpty()) {\n                lastTo = to;\n                lastSubject = subject;\n                lastBody = body;\n\t\t\t\t\n                // Starts by disposing the dialog\n                dispose();\n\n                // Creates new FileSet with files that have been selected\n                FileSet filesToSend = new FileSet(flattenedFiles.getBaseFolder());\n                int nbFiles = fileCheckboxes.length;\n                for (int i = 0; i < nbFiles; i++) {\n                    if (fileCheckboxes[i].isSelected()) {\n                        filesToSend.add(flattenedFiles.elementAt(i));\n                    }\n                }\n\n                // Starts sending files\n                ProgressDialog progressDialog = new ProgressDialog(mainFrame, i18n(\"email_dialog.sending\"));\n                SendMailJob mailJob = new SendMailJob(progressDialog, mainFrame, filesToSend, to, subject, body);\n                progressDialog.start(mailJob);\n            }\n        }\n        // Cancel button\n        else if (source == cancelButton)  {\n            dispose();\t\t\t\n        }\n    }\n\t\n    //////////////////////////\n    // ItemListener methods //\n    //////////////////////////\n\t\t\n    /**\n     * Updates label text whenever a checkbox has been checked or unchecked.\n     */\n    public void itemStateChanged(ItemEvent e) {\t\n        updateInfoLabel();\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/FileCollisionDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.dialog.file;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.job.FileCollisionChecker;\nimport com.mucommander.utils.text.CustomDateFormat;\nimport com.mucommander.utils.text.SizeFormat;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.QuestionDialog;\nimport com.mucommander.ui.layout.CompareImagesPanel;\nimport com.mucommander.ui.layout.InformationPane;\nimport com.mucommander.ui.layout.XAlignedComponentPanel;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.notifier.AbstractNotifier;\nimport com.mucommander.ui.notifier.NotificationType;\nimport com.mucommander.ui.text.FileLabel;\nimport com.mucommander.ui.text.FontUtils;\nimport ru.trolsoft.ui.TMenuSeparator;\n\nimport javax.swing.*;\nimport java.awt.Component;\nimport java.awt.Dialog;\nimport java.awt.Font;\nimport java.awt.Frame;\nimport java.util.ArrayList;\nimport java.util.List;\n\n\n/**\n * Dialog used to inform the user that a file collision has been detected and ask him how to resolve the conflict.\n * Prior to invoking this dialog, {@link com.mucommander.job.FileCollisionChecker} can be used to check for file collisions. \n *\n * @see com.mucommander.job.FileCollisionChecker\n * @author Maxence Bernard\n */\npublic class FileCollisionDialog extends QuestionDialog {\n\n    /** This value is used by some FileJob classes */\n    public final static int ASK_ACTION = -1;\n\t\n    public final static int CANCEL_ACTION = 0;\n    public final static int SKIP_ACTION = 1;\n    public final static int OVERWRITE_ACTION = 2;\n    public final static int OVERWRITE_IF_OLDER_ACTION = 3;\n    public final static int RESUME_ACTION = 4;\n    public final static int RENAME_ACTION = 5;\n\n    public final static String CANCEL_TEXT = Translator.get(\"cancel\");\n    final static String SKIP_TEXT = Translator.get(\"skip\");\n    final static String OVERWRITE_TEXT = Translator.get(\"overwrite\");\n    final static String OVERWRITE_IF_OLDER_TEXT = Translator.get(\"overwrite_if_older\");\n    final static String RESUME_TEXT = Translator.get(\"resume\");\n    final static String RENAME_TEXT = Translator.get(\"rename\");\n\n    private JCheckBox applyToAllCheckBox;\n\n    private JLabel lblImageSize1, lblImageSize2;\n\n\t\n    /**\n     * Creates a new FileCollisionDialog.\n     *\n     * @param owner the Frame that owns this dialog\n     * @param locationRelative component the location of this dialog will be based on\n     * @param collisionType the type of collision as returned by {@link com.mucommander.job.FileCollisionChecker}\n     * @param sourceFile the source file that 'conflicts' with the destination file, can be null.\n     * @param destFile the destination file which already exists\n     * @param multipleFilesMode if true, options that apply to multiple files will be displayed (skip, apply to all)\n     * @param allowRename if true, display an option to rename a file\n     */\n    public FileCollisionDialog(Dialog owner, Component locationRelative, int collisionType, AbstractFile sourceFile, AbstractFile destFile, boolean multipleFilesMode, boolean allowRename) {\n        super(owner, Translator.get(\"file_collision_dialog.title\"), locationRelative);\n\t\t\n        init(collisionType, sourceFile, destFile, multipleFilesMode, allowRename);\n    }\n\n    /**\n     * Creates a new FileCollisionDialog.\n     *\n     * @param owner the Frame that owns this dialog\n     * @param locationRelative component the location of this dialog will be based on\n     * @param collisionType the type of collision as returned by {@link com.mucommander.job.FileCollisionChecker}\n     * @param sourceFile the source file that 'conflicts' with the destination file, can be null.\n     * @param destFile the destination file which already exists\n     * @param multipleFilesMode if true, options that apply to multiple files will be displayed (skip, apply to all)\n     * @param allowRename if true, display an option to rename a file\n     */\n    public FileCollisionDialog(Frame owner, Component locationRelative, int collisionType, AbstractFile sourceFile, AbstractFile destFile, boolean multipleFilesMode, boolean allowRename) {\n        super(owner, Translator.get(\"file_collision_dialog.title\"), locationRelative);\n\n        init(collisionType, sourceFile, destFile, multipleFilesMode, allowRename);\n    }\n\n\n    private void init(int collisionType, AbstractFile sourceFile, AbstractFile destFile, boolean multipleFilesMode, boolean allowRename) {\n\n        // Init choices\n\n        List<String> choicesTextV = new ArrayList<>();\n        List<Integer> choicesActionsV = new ArrayList<>();\n\n        choicesTextV.add(CANCEL_TEXT);\n        choicesActionsV.add(CANCEL_ACTION);\n\n        if (multipleFilesMode) {\n            choicesTextV.add(SKIP_TEXT);\n            choicesActionsV.add(SKIP_ACTION);\n        }\n\n        // Add 'overwrite' / 'overwrite if older' / 'resume' actions only for 'destination file already exists' collision type\n        if (collisionType == FileCollisionChecker.DESTINATION_FILE_ALREADY_EXISTS && !destFile.isDirectory()) {\n            choicesTextV.add(OVERWRITE_TEXT);\n            choicesActionsV.add(OVERWRITE_ACTION);\n\n            if (sourceFile != null) {\n                choicesTextV.add(OVERWRITE_IF_OLDER_TEXT);\n                choicesActionsV.add(OVERWRITE_IF_OLDER_ACTION);\n\n                // Give resume option only if destination file is smaller than source file\n                long destSize = destFile.getSize();\n                long sourceSize = sourceFile.getSize();\n                if (destSize != -1 && (sourceSize == -1 || destSize < sourceSize)) {\n                    choicesTextV.add(RESUME_TEXT);\n                    choicesActionsV.add(RESUME_ACTION);\n                }\n\n                if (allowRename) {\n                    choicesTextV.add(RENAME_TEXT);\n                    choicesActionsV.add(RENAME_ACTION);\n                }\n            }\n        \n        }\n\n        // Convert choice vectors into arrays\n        int nbChoices = choicesActionsV.size();\n\n        String[] choicesText = new String[nbChoices];\n        choicesTextV.toArray(choicesText);\n\n        int[] choicesActions = new int[nbChoices];\n        for (int i = 0; i < nbChoices; i++) {\n            choicesActions[i] = choicesActionsV.get(i);\n        }\n\n        // Init UI\n\n        String desc;\n\n        if (collisionType == FileCollisionChecker.DESTINATION_FILE_ALREADY_EXISTS) {\n            desc = Translator.get(\"file_exists_in_destination\");\n        } else if (collisionType==FileCollisionChecker.SAME_SOURCE_AND_DESTINATION) {\n            desc = Translator.get(\"same_source_destination\");\n        } else if (collisionType==FileCollisionChecker.SOURCE_PARENT_OF_DESTINATION) {\n            desc = Translator.get(\"source_parent_of_destination\");\n        } else {\n            desc = null;\n        }\n\n        YBoxPanel yPanel = new YBoxPanel();\n\n        String destFilePath = destFile.getAbsolutePath().toLowerCase();\n        boolean imageMode = destFilePath.endsWith(\".png\") || destFilePath.endsWith(\".jpg\") || destFilePath.endsWith(\".jpeg\") || destFilePath.endsWith(\".bmp\") || destFilePath.endsWith(\".gif\");\n\n        if (imageMode) {\n            lblImageSize1 = new JLabel();\n            lblImageSize2 = new JLabel();\n        }\n\n        if (desc != null) {\n            if (imageMode) {\n                yPanel.add(new InformationPane(desc, null, Font.PLAIN, null));\n            } else {\n                yPanel.add(new InformationPane(desc, null, Font.PLAIN, InformationPane.QUESTION_ICON));\n                yPanel.addSpace(10);\n            }\n\n        }\n        if (imageMode) {\n            if (collisionType != FileCollisionChecker.SAME_SOURCE_AND_DESTINATION) {\n                yPanel.add(new CompareImagesPanel(sourceFile, destFile, this, lblImageSize1, lblImageSize2));\n            } else {\n                yPanel.add(new CompareImagesPanel(sourceFile, null, this, lblImageSize1, null));\n            }\n            yPanel.addSpace(10);\n        }\n\n        // Add a separator before file details\n        yPanel.add(new TMenuSeparator());\n\n        XAlignedComponentPanel tfPanel = new XAlignedComponentPanel(10);\n\n        // If collision type is 'same source and destination' no need to show both source and destination\n        if (collisionType == FileCollisionChecker.SAME_SOURCE_AND_DESTINATION) {\n            addFileDetails(tfPanel, sourceFile, Translator.get(\"name\"), lblImageSize1);\n        } else {\n            if (sourceFile != null) {\n                addFileDetails(tfPanel, sourceFile, Translator.get(\"source\"), lblImageSize1);\n            }\n            addFileDetails(tfPanel, destFile, Translator.get(\"destination\"), lblImageSize2);\n        }\n\n        yPanel.add(tfPanel);\n\n        // Add a separator after file details\n        yPanel.add(new TMenuSeparator());\n        \n        init(yPanel, choicesText, choicesActions, 3);\n\n        // 'Apply to all' is available only for 'destination file already exists' collision type\n        if (multipleFilesMode && collisionType == FileCollisionChecker.DESTINATION_FILE_ALREADY_EXISTS) {\n            applyToAllCheckBox = new JCheckBox(Translator.get(\"apply_to_all\"));\n            addComponent(applyToAllCheckBox);\n        }\n\n        // Send a system notification if a notifier is available and enabled\n        if (AbstractNotifier.isAvailable() && AbstractNotifier.getNotifier().isEnabled()) {\n            AbstractNotifier.getNotifier().displayBackgroundNotification(NotificationType.JOB_ERROR, getTitle(), desc);\n        }\n    }\n\n\n    private void addFileDetails(XAlignedComponentPanel panel, AbstractFile file, String nameLabel, JLabel imgSizeLabel) {\n        addFileDetailsRow(panel, nameLabel+\":\", new FileLabel(file, false), 0);\n\n        AbstractFile parent = file.getParent();\n\n        addFileDetailsRow(panel, Translator.get(\"location\")+\":\", new FileLabel((parent == null ? file:parent), true), 0);\n\n        addFileDetailsRow(panel, Translator.get(\"size\")+\":\", new JLabel(SizeFormat.format(file.getSize(), SizeFormat.DIGITS_FULL| SizeFormat.UNIT_LONG| SizeFormat.INCLUDE_SPACE)), 0);\n\n        addFileDetailsRow(panel, Translator.get(\"date\")+\":\", new JLabel(CustomDateFormat.format(file.getLastModifiedDate())), 0);\n\n        addFileDetailsRow(panel, Translator.get(\"permissions\")+\":\", new JLabel(file.getPermissionsString()), imgSizeLabel == null ? 10 :0);\n\n        if (imgSizeLabel != null) {\n            addFileDetailsRow(panel, Translator.get(\"image_size\")+\":\", imgSizeLabel, 10);\n        }\n    }\n\n    private void addFileDetailsRow(XAlignedComponentPanel panel, String label, JComponent comp, int ySpaceAfter) {\n        panel.addRow(FontUtils.makeMini(new JLabel(label)), FontUtils.makeMini(comp), ySpaceAfter);\n    }\n\n    /**\n     * Returns <code>true</code> if the 'apply to all' checkbox has been selected.\n     *\n     * @return <code>true</code> if the 'apply to all' checkbox has been selected.\n     */\n    public boolean applyToAllSelected() {\n        return applyToAllCheckBox != null && applyToAllCheckBox.isSelected();\n    }\n\t\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/FileCollisionRenameDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.dialog.file;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.job.CopyJob;\nimport com.mucommander.job.ui.DialogResult;\nimport com.mucommander.ui.dialog.DialogToolkit;\nimport com.mucommander.ui.dialog.FocusDialog;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\n\n/**\n * Dialog invoked when the user wants to change a file name after a collision has been detected\n * while copying or moving files.\n *\n * @see CopyJob\n * @author Mariusz Jakubowski\n */\npublic class FileCollisionRenameDialog extends FocusDialog implements ActionListener, DialogResult {\n\t\n    private final JTextField edtNewName;\n\n    private final JButton btnOk;\n\n\tprivate String newName;\n\n    // Dialog size constraints\n    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(320,0);\t\n    // Dialog width should not exceed 360, height is not an issue (always the same)\n    private final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(400,10000);\n\n\n    /**\n     * Creates a new rename file dialog.\n     *\n     * @param mainFrame the parent MainFrame \n     * @param file the file to rename.\n     */\n    public FileCollisionRenameDialog(MainFrame mainFrame, AbstractFile file) {\n        super(mainFrame.getJFrame(), i18n(\"rename\"), mainFrame.getJFrame());\n\n        Container contentPane = getContentPane();\n\n        YBoxPanel mainPanel = new YBoxPanel();\n        mainPanel.add(new JLabel(i18n(\"rename_dialog.new_name\") + \":\"));\n        edtNewName = new JTextField();\n        edtNewName.addActionListener(this);\n\n        // Sets the initial selection.\n        AbstractCopyDialog.selectDestinationFilename(file, file.getName(), 0).feedToPathField(edtNewName);\n        mainPanel.add(edtNewName);\n   \n        mainPanel.addSpace(10);\n        contentPane.add(mainPanel, BorderLayout.NORTH);\n        \n        btnOk = new JButton(i18n(\"rename\"));\n        JButton cancelButton = new JButton(i18n(\"cancel\"));\n        contentPane.add(DialogToolkit.createOKCancelPanel(btnOk, cancelButton, getRootPane(), this), BorderLayout.SOUTH);\n\n        // Path field will receive initial focus\n        setInitialFocusComponent(edtNewName);\n\n        setMinimumSize(MINIMUM_DIALOG_DIMENSION);\n        setMaximumSize(MAXIMUM_DIALOG_DIMENSION);\n    }\n\n\n    ///////////////////////////////////\n    // ActionListener implementation //\n    ///////////////////////////////////\n\t\n    public void actionPerformed(ActionEvent e) {\n        Object source = e.getSource();\n\t\t\n        // OK Button\n        if (source == btnOk || source == edtNewName) {\n        \tnewName = edtNewName.getText();\n        } else {\n            newName = null;\n        }\n        dispose();\n    }\n\n    public Object getUserInput() {\n        showDialog();\n        return newName;\n    }\n    \n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/FileSelectionDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.file;\n\nimport java.awt.BorderLayout;\nimport java.awt.Container;\nimport java.awt.Dimension;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.util.List;\n\nimport javax.swing.Box;\nimport javax.swing.BoxLayout;\nimport javax.swing.JButton;\nimport javax.swing.JCheckBox;\nimport javax.swing.JLabel;\nimport javax.swing.JPanel;\nimport javax.swing.JTextField;\n\nimport com.jidesoft.hints.ListDataIntelliHints;\nimport com.mucommander.cache.TextHistory;\nimport com.mucommander.commons.file.filter.AndFileFilter;\nimport com.mucommander.commons.file.filter.AttributeFileFilter;\nimport com.mucommander.commons.file.filter.AttributeFileFilter.FileAttribute;\nimport com.mucommander.commons.file.filter.FileFilter;\nimport com.mucommander.commons.file.filter.WildcardFileFilter;\nimport com.mucommander.ui.dialog.DialogToolkit;\nimport com.mucommander.ui.dialog.FocusDialog;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.FileTable;\n\n/**\n * This dialog allows the user to add (mark) or remove (unmark) files from the current selection,\n * based on a match criterium and string.\n *\n * @author Maxence Bernard\n */\npublic class FileSelectionDialog extends FocusDialog implements ActionListener {\n\n    /** Add to or remove from selection ? */\t \n    private final boolean addToSelection;\n\n    private final JTextField selectionField;\n\n    private final JCheckBox cbCaseSensitive;\n    private final JCheckBox cbIncludeFolders;\n\n    private final JButton btnOk;\n\n    private final MainFrame mainFrame;\n\t\n    /** \n     * Is selection case-sensitive? (initially false)\n     * <br>Note: this field is static so the value is kept after the dialog is OKed.\n     */ \n    private static boolean caseSensitive = false;\n\n    /** \n     * Does the selection include folders? (initially false)\n     * <br>Note: this field is static so the value is kept after the dialog is OKed.\n     */ \n    private static boolean includeFolders = false;\n\n    /** \n     * Keyword which has last been typed to mark or unmark files.\n     * <br>Note: this field is static so the value is kept after the dialog is OKed.\n     */ \n    private static String keywordString = \"*\";\n\t\n\n    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(320,0);\t\n    private final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(400,10000);\t\n\n\n    /**\n     * Creates a new 'mark' or 'unmark' dialog.\n     *\n     * @param addToSelection if <code>true</code>, files matching\n     */\n    public FileSelectionDialog(MainFrame mainFrame, boolean addToSelection) {\n        super(mainFrame.getJFrame(), i18n(addToSelection?\"file_selection_dialog.mark\":\"file_selection_dialog.unmark\"), mainFrame.getJFrame());\n\t\n        this.mainFrame = mainFrame;\n        this.addToSelection = addToSelection;\n\n        Container contentPane = getContentPane();\n        contentPane.setLayout(new BorderLayout());\n\n        YBoxPanel northPanel = new YBoxPanel(5);\n        JLabel label = new JLabel(i18n(addToSelection?\"file_selection_dialog.mark_description\":\"file_selection_dialog.unmark_description\")+\" :\");\n        northPanel.add(label);\n\n        JPanel tempPanel = new JPanel();\n        tempPanel.setLayout(new BoxLayout(tempPanel, BoxLayout.X_AXIS));\n\n        // selectionField is initialized with last textfield's value (if any)\n        selectionField = new JTextField(keywordString);\n        selectionField.addActionListener(this);\n        selectionField.setSelectionStart(0);\n        selectionField.setSelectionEnd(keywordString.length());\n\n        List<String> filesHistory = TextHistory.getInstance().getList(TextHistory.Type.FILE_NAME);\n        new ListDataIntelliHints<>(selectionField, filesHistory).setCaseSensitive(false);\n\n        tempPanel.add(selectionField);\n        northPanel.add(tempPanel);\n\n        // Add some vertical space\n        northPanel.addSpace(10);\n\t\t\n        cbCaseSensitive = new JCheckBox(i18n(\"file_selection_dialog.case_sensitive\"), caseSensitive);\n        northPanel.add(cbCaseSensitive);\n\n        cbIncludeFolders = new JCheckBox(i18n(\"file_selection_dialog.include_folders\"), includeFolders);\n        northPanel.add(cbIncludeFolders);\n\t\t\n        northPanel.addSpace(10);\n        northPanel.add(Box.createVerticalGlue());\n\n        contentPane.add(northPanel, BorderLayout.NORTH);\n\n        btnOk = new JButton(i18n(addToSelection?\"file_selection_dialog.mark\":\"file_selection_dialog.unmark\"));\n        contentPane.add(DialogToolkit.createOKCancelPanel(btnOk, new JButton(i18n(\"cancel\")), getRootPane(), this), BorderLayout.SOUTH);\n\n        // Selection field receives initial keyboard focus\n        setInitialFocusComponent(selectionField);\n\n        setMinimumSize(MINIMUM_DIALOG_DIMENSION);\n        setMaximumSize(MAXIMUM_DIALOG_DIMENSION);\n    }\n\n\n\n\t@Override\n    public void actionPerformed(ActionEvent e) {\n        Object source = e.getSource();\n\n        FileTable activeTable = mainFrame.getActiveTable();\n\n        // Action coming from the selection dialog\n        if (source == btnOk || source == selectionField) {\n            // Save values for next time this dialog is invoked\n            caseSensitive = cbCaseSensitive.isSelected();\n            includeFolders = cbIncludeFolders.isSelected();\n\n            keywordString = selectionField.getText();\n\n            // Instantiate the main file IMAGE_FILTER\n            FileFilter filter = new WildcardFileFilter(keywordString, caseSensitive);\n\n            // If folders are excluded, add a regular file IMAGE_FILTER and chain it with an AndFileFilter\n            if (!includeFolders) {\n                filter = new AndFileFilter(\n                    new AttributeFileFilter(FileAttribute.FILE),\n                    filter\n                );\n            }\n\n            // Mark/unmark the files using the IMAGE_FILTER\n            activeTable.getFileTableModel().setFilesMarked(filter, addToSelection);\n\n            // Notify registered listeners that currently marked files have changed on this FileTable\n            activeTable.fireMarkedFilesChangedEvent();\n\n            activeTable.repaint();\n        }\n\t\t\n        dispose();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/FindFileDialog.java",
    "content": "/*\r\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\r\n * Copyright (C) 2013-2025 Oleg Trifonov\r\n *\r\n * trolCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * trolCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\npackage com.mucommander.ui.dialog.file;\r\n\r\nimport com.jidesoft.hints.ListDataIntelliHints;\r\nimport com.mucommander.cache.TextHistory;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileFactory;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.conf.TcConfigurations;\r\nimport com.mucommander.conf.TcPreference;\r\nimport com.mucommander.conf.TcPreferencesAPI;\r\nimport com.mucommander.job.FileJob;\r\nimport com.mucommander.job.FindFileJob;\r\nimport com.mucommander.ui.action.ActionProperties;\r\nimport com.mucommander.ui.action.impl.FindFileAction;\r\nimport com.mucommander.ui.combobox.SaneComboBox;\r\nimport com.mucommander.ui.dialog.FocusDialog;\r\nimport com.mucommander.ui.encoding.EncodingPreferences;\r\nimport com.mucommander.ui.helper.MnemonicHelper;\r\nimport com.mucommander.ui.icon.IconManager;\r\nimport com.mucommander.ui.icon.SpinningDial;\r\nimport com.mucommander.ui.layout.ProportionalGridPanel;\r\nimport com.mucommander.ui.layout.XAlignedComponentPanel;\r\nimport com.mucommander.ui.layout.XBoxPanel;\r\nimport com.mucommander.ui.layout.YBoxPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.text.FilePathField;\r\nimport com.mucommander.ui.theme.ThemeCache;\r\nimport com.mucommander.ui.viewer.EditorRegistrar;\r\nimport com.mucommander.ui.viewer.ViewerRegistrar;\r\nimport ru.trolsoft.ui.InputField;\r\n\r\nimport javax.swing.*;\r\nimport javax.swing.event.DocumentEvent;\r\nimport javax.swing.event.DocumentListener;\r\n\r\n\r\nimport java.awt.BorderLayout;\r\nimport java.awt.Container;\r\nimport java.awt.Dimension;\r\nimport java.awt.FlowLayout;\r\nimport java.awt.event.*;\r\nimport java.util.List;\r\n\r\n/**\r\n * @author Oleg Trifonov\r\n * Find file dialog\r\n */\r\npublic class FindFileDialog extends FocusDialog implements ActionListener, DocumentListener {\r\n\r\n    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(640, 480);\r\n    private final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(10000, 1024);\r\n\r\n    /** How often should progress information be refreshed (in ms) */\r\n    private final static int REFRESH_RATE = 200;\r\n\r\n    private final MainFrame mainFrame;\r\n    private FindFileJob job;\r\n    private final SpinningDial dial;\r\n\r\n    private final JButton btnNewSearch;\r\n    private final JButton btnStop;\r\n    private final JButton btnClean;\r\n    private final JButton btnClose;\r\n\r\n    private final JTextField edtFileName;\r\n    private final InputField edtText;\r\n    private final JTextField edtFromDirectory;\r\n\r\n    private final JCheckBox cbSearchSubdirectories;\r\n    private final JCheckBox cbSearchArchives;\r\n    private final JCheckBox cbIgnoreHidden;\r\n    private final JCheckBox cbCaseSensitive;\r\n    private final JCheckBox cbSearchHex;\r\n    private final JComboBox<String> cbEncoding;\r\n\r\n    private DefaultListModel<AbstractFile> listModel = new DefaultListModel<>();\r\n    private JList<AbstractFile> list;\r\n    private final JLabel lblTotal;\r\n\r\n    private AbstractFile startDirectory;\r\n\r\n    private ListDataIntelliHints<String> textHints, hexHints;\r\n    private UpdateRunner updateRunner;\r\n\r\n    private class UpdateRunner extends SwingWorker<List<AbstractFile>, AbstractFile> {\r\n\r\n        @Override\r\n        protected List<AbstractFile> doInBackground() {\r\n            btnNewSearch.setEnabled(false);\r\n            while (job != null && job.getState() != FileJob.State.FINISHED) {\r\n                checkUpdates();\r\n                try {\r\n                    Thread.sleep(REFRESH_RATE);\r\n                } catch(InterruptedException ignore) {}\r\n            }\r\n            checkUpdates();\r\n            job = null;\r\n            return null;\r\n        }\r\n\r\n        @Override\r\n        protected void done() {\r\n            showProgress(false);\r\n            updateButtons();\r\n            super.done();\r\n        }\r\n\r\n        @Override\r\n        protected void process(List<AbstractFile> chunks) {\r\n            for (AbstractFile f : chunks) {\r\n                if (isCancelled()) {\r\n                    break;\r\n                }\r\n                listModel.addElement(f);\r\n                updateResultLabel();\r\n            }\r\n        }\r\n\r\n        private void checkUpdates() {\r\n            if (job == null) {\r\n                return;\r\n            }\r\n            final List<AbstractFile> jobResults = job.getResults();\r\n            synchronized (job) {\r\n                for (int i = listModel.size(); i < jobResults.size(); i++) {\r\n                    AbstractFile f = jobResults.get(i);\r\n                    publish(f);\r\n                }\r\n            }\r\n        }\r\n\r\n    }\r\n\r\n\r\n\r\n    public FindFileDialog(final MainFrame mainFrame, AbstractFile currentFolder) {\r\n        super(mainFrame.getJFrame(), ActionProperties.getActionLabel(FindFileAction.Descriptor.ACTION_ID), mainFrame.getJFrame());\r\n        this.mainFrame = mainFrame;\r\n        Container contentPane = getContentPane();\r\n\r\n        YBoxPanel yPanel = new YBoxPanel(10);\r\n\r\n        // Text fields panel\r\n        XAlignedComponentPanel compPanel = new XAlignedComponentPanel();\r\n\r\n        // Add filename field\r\n        this.edtFileName = new JTextField();\r\n        edtFileName.getDocument().addDocumentListener(this);\r\n        List<String> filesHistory = TextHistory.getInstance().getList(TextHistory.Type.FILE_NAME);\r\n        new ListDataIntelliHints<>(edtFileName, filesHistory).setCaseSensitive(false);\r\n        edtFileName.setText(\"\");\r\n        compPanel.addRow(i18n(\"find_dialog.name\") + \":\", edtFileName, 5);\r\n\r\n        // Add contains field\r\n        this.edtText = new InputField();\r\n        edtText.getDocument().addDocumentListener(this);\r\n//        List<String> textHistory = TextHistory.getInstance().getList(TextHistory.Type.TEXT_SEARCH);\r\n//        new ListDataIntelliHints<>(edtText, textHistory).setCaseSensitive(false);\r\n//        edtText.setText(\"\");\r\n        compPanel.addRow(i18n(\"find_dialog.contains\") + \":\", edtText, 5);\r\n\r\n        // Add encoding field\r\n        this.cbEncoding = new SaneComboBox<>();\r\n\r\n        List<String> encodings = EncodingPreferences.getPreferredEncodings();\r\n        for (String encoding: encodings) {\r\n            cbEncoding.addItem(encoding);\r\n        }\r\n        compPanel.addRow(i18n(\"find_dialog.encoding\") + \":\", cbEncoding, 5);\r\n\r\n        // create a path field with auto-completion capabilities\r\n        this.edtFromDirectory = new FilePathField();\r\n        this.edtFromDirectory.setText(currentFolder.toString());\r\n        edtFromDirectory.getDocument().addDocumentListener(this);\r\n        compPanel.addRow(i18n(\"find_dialog.initial_directory\") + \":\", edtFromDirectory, 10);\r\n\r\n        ProportionalGridPanel gridPanel = new ProportionalGridPanel(3);\r\n\r\n        // Checkboxes\r\n        this.cbSearchSubdirectories = new JCheckBox(i18n(\"find_dialog.search_subdirectories\"));\r\n        this.cbSearchArchives = new JCheckBox(i18n(\"find_dialog.search_archives\"));\r\n        this.cbCaseSensitive = new JCheckBox(i18n(\"find_dialog.case_sensitive\"));\r\n        this.cbIgnoreHidden = new JCheckBox(i18n(\"find_dialog.ignore_hidden\"));\r\n        this.cbSearchHex = new JCheckBox(i18n(\"find_dialog.search_hex\"));\r\n\r\n        TcPreferencesAPI prefs = TcConfigurations.getPreferences();\r\n        cbSearchSubdirectories.setSelected(prefs.getVariable(TcPreference.FIND_FILE_SUBDIRECTORIES, true));\r\n        cbSearchArchives.setSelected(prefs.getVariable(TcPreference.FIND_FILE_ARCHIVES, false));\r\n        cbCaseSensitive.setSelected(prefs.getVariable(TcPreference.FIND_FILE_CASE_SENSITIVE, false));\r\n        cbIgnoreHidden.setSelected(prefs.getVariable(TcPreference.FIND_FILE_IGNORE_HIDDEN, false));\r\n        cbSearchHex.setSelected(prefs.getVariable(TcPreference.FIND_FILE_SEARCH_HEX, false));\r\n        cbEncoding.setSelectedItem(prefs.getVariable(TcPreference.FIND_FILE_ENCODING, \"UTF-8\"));\r\n\r\n        cbSearchHex.addActionListener(e -> setHexMode(cbSearchHex.isSelected()));\r\n        setHexMode(cbSearchHex.isSelected());\r\n\r\n        gridPanel.add(cbSearchSubdirectories);\r\n        gridPanel.add(cbSearchArchives);\r\n        gridPanel.add(cbIgnoreHidden);\r\n        gridPanel.add(cbCaseSensitive);\r\n        gridPanel.add(cbSearchHex);\r\n\r\n        compPanel.addRow(gridPanel, 0);\r\n\r\n        yPanel.add(compPanel);\r\n\r\n\r\n        // Search results\r\n        yPanel.add(new JLabel(i18n(\"find_dialog.search_results\")));\r\n        list = new JList<>(listModel);\r\n        list.addMouseListener(new MouseAdapter() {\r\n            @Override\r\n            public void mouseClicked(MouseEvent e) {\r\n                final AbstractFile file = getSelectedFile();\r\n                if (file == null) {\r\n                    return;\r\n                }\r\n                //final FileTable table = mainFrame.getActivePanel().getFileTable();\r\n\r\n                if (e.getClickCount() >= 2) {\r\n                    mainFrame.getActivePanel().tryChangeCurrentFolder(file.getParent(), file, false);\r\n                }\r\n            }\r\n        });\r\n        list.addKeyListener(new KeyAdapter() {\r\n            @Override\r\n            public void keyPressed(KeyEvent e) {\r\n                super.keyPressed(e);\r\n                final AbstractFile file = getSelectedFile();\r\n                if (file == null) {\r\n                    return;\r\n                }\r\n                switch (e.getKeyCode()) {\r\n                    case KeyEvent.VK_F3:\r\n                        ViewerRegistrar.createViewerFrame(mainFrame, file, IconManager.getImageIcon(file.getIcon()).getImage(), (fileFrame) -> {\r\n                            fileFrame.returnFocusTo(getFocusOwner());\r\n                            if (cbSearchHex.isSelected()) {\r\n                                fileFrame.setSearchedBytes(edtText.getBytes());\r\n                            } else {\r\n                                fileFrame.setSearchedText(edtText.getText());\r\n                            }\r\n                        });\r\n                        break;\r\n\r\n                    case KeyEvent.VK_F4:\r\n                        EditorRegistrar.createEditorFrame(mainFrame, file, IconManager.getImageIcon(file.getIcon()).getImage(), (fileFrame) -> {\r\n                            fileFrame.returnFocusTo(getFocusOwner());\r\n                            if (cbSearchHex.isSelected()) {\r\n                                fileFrame.setSearchedBytes(edtText.getBytes());\r\n                            } else {\r\n                                fileFrame.setSearchedText(edtText.getText());\r\n                            }\r\n                        });\r\n                        break;\r\n\r\n                    case KeyEvent.VK_SPACE:\r\n                        mainFrame.getActivePanel().tryChangeCurrentFolder(file.getParent(), file, false);\r\n                        break;\r\n\r\n                    case KeyEvent.VK_F5:\r\n                        new CopyDialog(mainFrame, getSelectedFiles()).returnFocusTo(getFocusOwner()).showDialog();\r\n                        break;\r\n\r\n                    case KeyEvent.VK_F6:\r\n                        new MoveDialog(mainFrame, getSelectedFiles()).returnFocusTo(getFocusOwner()).showDialog();\r\n                        break;\r\n\r\n                    case KeyEvent.VK_F8:\r\n                    case KeyEvent.VK_DELETE:\r\n                        new DeleteDialog(mainFrame, getSelectedFiles(), false).returnFocusTo(getFocusOwner()).showDialog();\r\n                        break;\r\n\r\n                }\r\n            }\r\n\r\n        });\r\n        list.setCellRenderer(new FindFileResultRenderer());\r\n        list.setBackground(ThemeCache.backgroundColors[ThemeCache.ACTIVE][ThemeCache.NORMAL]);\r\n        JScrollPane scrollPane = new JScrollPane(list);\r\n        contentPane.add(scrollPane, BorderLayout.CENTER);\r\n\r\n        // Bottom line\r\n        MnemonicHelper mnemonicHelper = new MnemonicHelper();\r\n\r\n//        yPanel.add(new JLabel(dial = new SpinningDial()));\r\n        XBoxPanel buttonsPanel = new XBoxPanel();\r\n        JPanel buttonGroupPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\r\n\r\n        buttonsPanel.add(lblTotal = new JLabel());\r\n        buttonsPanel.add(new JLabel(dial = new SpinningDial()));\r\n        buttonsPanel.add(Box.createHorizontalGlue());\r\n\r\n        btnNewSearch = new JButton(i18n(\"search\"));\r\n        btnNewSearch.addActionListener(this);\r\n        btnNewSearch.setMnemonic(mnemonicHelper.getMnemonic(btnNewSearch));\r\n        buttonGroupPanel.add(btnNewSearch);\r\n\r\n        btnStop = new JButton(i18n(\"stop\"));\r\n        btnStop.addActionListener(this);\r\n        btnStop.setMnemonic(mnemonicHelper.getMnemonic(btnStop));\r\n        buttonGroupPanel.add(btnStop);\r\n\r\n        btnClean = new JButton(i18n(\"clean\"));\r\n        btnClean.addActionListener(this);\r\n        btnClean.setMnemonic(mnemonicHelper.getMnemonic(btnClean));\r\n        buttonGroupPanel.add(btnClean);\r\n\r\n        btnClose = new JButton(i18n(\"close\"));\r\n        btnClose.addActionListener(this);\r\n        btnClose.setMnemonic(mnemonicHelper.getMnemonic(btnClose));\r\n        buttonGroupPanel.add(btnClose);\r\n\r\n        buttonsPanel.add(buttonGroupPanel);\r\n\r\n        contentPane.add(buttonsPanel, BorderLayout.SOUTH);\r\n\r\n        contentPane.add(yPanel, BorderLayout.NORTH);\r\n\r\n        setInitialFocusComponent(edtFileName);\r\n\r\n        setMinimumSize(MINIMUM_DIALOG_DIMENSION);\r\n        setMaximumSize(MAXIMUM_DIALOG_DIMENSION);\r\n        updateButtons();\r\n        getRootPane().setDefaultButton(btnNewSearch);\r\n\r\n        setModal(false);\r\n    }\r\n\r\n\r\n    private void setHexMode(boolean hexMode) {\r\n        cbEncoding.setEnabled(!hexMode);\r\n        edtText.setText(\"\");\r\n        if (textHints != null) {\r\n            textHints.setAutoPopup(false);\r\n            textHints = null;\r\n        }\r\n        if (hexHints != null) {\r\n            hexHints.setAutoPopup(false);\r\n            hexHints = null;\r\n        }\r\n        edtText.setFilterType(cbSearchHex.isSelected() ? InputField.FilterType.HEX_DUMP : InputField.FilterType.ANY_TEXT);\r\n        if (hexMode) {\r\n            List<String> hexHistory = TextHistory.getInstance().getList(TextHistory.Type.HEX_DATA_SEARCH);\r\n            textHints = new ListDataIntelliHints<>(edtText, hexHistory);\r\n            textHints.setCaseSensitive(false);\r\n        } else {\r\n            List<String> textHistory = TextHistory.getInstance().getList(TextHistory.Type.TEXT_SEARCH);\r\n            hexHints = new ListDataIntelliHints<>(edtText, textHistory);\r\n            hexHints.setCaseSensitive(false);\r\n        }\r\n    }\r\n\r\n\r\n    private void updateButtons() {\r\n        btnNewSearch.setEnabled(job == null);\r\n        btnStop.setEnabled(!btnNewSearch.isEnabled());\r\n        btnClean.setEnabled(!(listModel == null || listModel.isEmpty()));\r\n    }\r\n\r\n    private void start() {\r\n        TextHistory.getInstance().add(TextHistory.Type.FILE_NAME, edtFileName.getText(), true);\r\n        TextHistory.getInstance().add(TextHistory.Type.TEXT_SEARCH, edtText.getText(), true);\r\n        showProgress(true);\r\n        clearResults();\r\n        job = new FindFileJob(mainFrame);\r\n        startDirectory = FileFactory.getFile(edtFromDirectory.getText());\r\n        job.setStartDirectory(startDirectory);\r\n        job.setup(edtFileName.getText(), edtText.getText(), cbSearchSubdirectories.isSelected(), cbSearchArchives.isSelected(),\r\n                cbCaseSensitive.isSelected(), cbIgnoreHidden.isSelected(), cbEncoding.getSelectedItem().toString(),\r\n                cbSearchHex.isSelected(), cbSearchHex.isSelected() ? edtText.getBytes() : null);\r\n        updateResultLabel();\r\n        job.start();\r\n        updateButtons();\r\n        updateRunner = new UpdateRunner();\r\n        updateRunner.execute();\r\n    }\r\n\r\n    private void clearResults() {\r\n        if (listModel != null) {\r\n            listModel.clear();\r\n        }\r\n        lblTotal.setText(\"\");\r\n    }\r\n\r\n    @Override\r\n    public void actionPerformed(ActionEvent e) {\r\n        if (e.getSource() == btnNewSearch) {\r\n            if (job == null) {\r\n                start();\r\n            }\r\n        } else if (e.getSource() == btnStop) {\r\n            if (job != null) {\r\n                job.interrupt();\r\n                job = null;\r\n            }\r\n        } else if (e.getSource() == btnClean) {\r\n            clearResults();\r\n        } else if (e.getSource() == btnClose) {\r\n            cancel();\r\n        }\r\n    }\r\n\r\n    private void showProgress(boolean show) {\r\n        dial.setAnimated(show);\r\n    }\r\n\r\n\r\n    @Override\r\n    public void insertUpdate(DocumentEvent e) {\r\n\r\n    }\r\n\r\n    @Override\r\n    public void removeUpdate(DocumentEvent e) {\r\n\r\n    }\r\n\r\n    @Override\r\n    public void changedUpdate(DocumentEvent e) {\r\n\r\n    }\r\n\r\n\r\n    private AbstractFile getSelectedFile() {\r\n        int index = list.getSelectedIndex();\r\n        if (index < 0) {\r\n            return null;\r\n        }\r\n        return listModel.get(index);\r\n    }\r\n\r\n\r\n    private FileSet getSelectedFiles() {\r\n        FileSet files = new FileSet(startDirectory);\r\n        int[] selectedIx = list.getSelectedIndices();\r\n        for (int aSelectedIx : selectedIx) {\r\n            AbstractFile file = listModel.get(aSelectedIx);\r\n            files.add(file);\r\n        }\r\n        return files;\r\n    }\r\n\r\n\r\n    private void updateResultLabel() {\r\n        lblTotal.setText(i18n(\"find_dialog.found\") + \": \" + listModel.size() + \" \");\r\n    }\r\n\r\n\r\n    @Override\r\n    public void cancel() {\r\n        if (job != null) {\r\n            job.interrupt();\r\n        }\r\n        TcPreferencesAPI prefs = TcConfigurations.getPreferences();\r\n        prefs.setVariable(TcPreference.FIND_FILE_ARCHIVES, cbSearchArchives.isSelected());\r\n        prefs.setVariable(TcPreference.FIND_FILE_CASE_SENSITIVE, cbCaseSensitive.isSelected());\r\n        prefs.setVariable(TcPreference.FIND_FILE_IGNORE_HIDDEN, cbIgnoreHidden.isSelected());\r\n        prefs.setVariable(TcPreference.FIND_FILE_SEARCH_HEX, cbSearchHex.isSelected());\r\n        prefs.setVariable(TcPreference.FIND_FILE_SUBDIRECTORIES, cbSearchSubdirectories.isSelected());\r\n        prefs.setVariable(TcPreference.FIND_FILE_ENCODING, cbEncoding.getSelectedItem().toString());\r\n\r\n        super.cancel();\r\n    }\r\n\r\n\r\n    @Override\r\n    public void dispose() {\r\n        super.dispose();\r\n        if (updateRunner != null) {\r\n            try {\r\n                updateRunner.cancel(true);\r\n            } catch (Throwable t) {\r\n                t.printStackTrace();\r\n            }\r\n        }\r\n        clearResults();\r\n        updateRunner = null;\r\n        listModel = null;\r\n        job = null;\r\n        list = null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/FindFileResultRenderer.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.dialog.file;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.main.table.*;\nimport com.mucommander.ui.theme.ThemeCache;\nimport com.mucommander.utils.FileIconsCache;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n/**\n * @author Oleg Trifonov\n * Created on 11.01.15.\n */\npublic class FindFileResultRenderer implements ListCellRenderer<AbstractFile> {\n\n    private final CellLabel cellLabel = new CellLabel();\n\n    @Override\n    public Component getListCellRendererComponent(JList<? extends AbstractFile> list, AbstractFile value, int index, boolean isSelected, boolean cellHasFocus) {\n        // Need to check that row index is not out of bounds because when the folder\n        ListModel<? extends AbstractFile> model = list.getModel();\n        if (value == null) {\n            return null;\n        }\n\n        // Retrieves the various indexes of the colors to apply.\n        // Selection only applies when the table is the active one\n        final int selectedIndex = isSelected ? ThemeCache.SELECTED : ThemeCache.NORMAL;\n        final int colorIndex = getColorIndex(value);\n\n        cellLabel.setIcon(FileIconsCache.getInstance().getIcon(value));\n\n        String text = value.getAbsolutePath();\n        Color foregroundColor;\n        if (isSelected) {\n            foregroundColor = ThemeCache.foregroundColors[ThemeCache.ACTIVE][ThemeCache.SELECTED][colorIndex];\n        } else {\n            int group = FileGroupResolver.getInstance().resolve(value);\n            foregroundColor = group >= 0 ? ThemeCache.groupColors[group] : ThemeCache.foregroundColors[ThemeCache.ACTIVE][ThemeCache.NORMAL][colorIndex];\n        }\n\n        cellLabel.setForeground(foregroundColor);\n\n        // Set the label's text, before calculating it width\n        cellLabel.setText(text);\n\n        cellLabel.setToolTipText(text);\n\n        // Set background color depending on whether the row is selected or not, and whether the table has focus or not\n        if (isSelected) {\n            cellLabel.setBackground(ThemeCache.backgroundColors[ThemeCache.ACTIVE][ThemeCache.SELECTED], ThemeCache.backgroundColors[ThemeCache.ACTIVE][ThemeCache.SECONDARY]);\n        } else {\n            int matchesColorIndex = (index % 2 == 0) ? ThemeCache.NORMAL : ThemeCache.ALTERNATE;\n            cellLabel.setBackground(ThemeCache.backgroundColors[ThemeCache.ACTIVE][matchesColorIndex], ThemeCache.backgroundColors[ThemeCache.ACTIVE][matchesColorIndex]);\n        }\n\n        if (selectedIndex == ThemeCache.SELECTED) {\n            cellLabel.setOutline(cellHasFocus ? ThemeCache.activeOutlineColor : ThemeCache.inactiveOutlineColor);\n        } else {\n            cellLabel.setOutline(null);\n        }\n\n        return cellLabel;\n    }\n\n    private static int getColorIndex(AbstractFile file) {\n        // Symlink.\n        if (file.isSymlink()) {\n            return ThemeCache.SYMLINK;\n        }\n\n        // Hidden file/folder.\n        if (file.isHidden()) {\n            return file.isDirectory() ? ThemeCache.HIDDEN_FOLDER : ThemeCache.HIDDEN_FILE;\n        }\n\n        // Directory.\n        if (file.isDirectory()) {\n            return ThemeCache.FOLDER;\n        }\n\n        // Archive.\n        if (file.isBrowsable()) {\n            return ThemeCache.ARCHIVE;\n        }\n\n        // Plain file.\n        return ThemeCache.PLAIN_FILE;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/JobDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.file;\n\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.ui.button.CollapseExpandButton;\nimport com.mucommander.ui.dialog.FocusDialog;\nimport com.mucommander.ui.dialog.InformationDialog;\nimport com.mucommander.ui.layout.AsyncPanel;\nimport com.mucommander.ui.list.FileList;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.*;\nimport java.awt.Container;\nimport java.awt.Window;\n\n/**\n * This abstract dialog is to be sub-classed by job confirmation dialogs and provides helper methods for common\n * components.\n *\n * @author Maxence Bernard\n */\npublic abstract class JobDialog extends FocusDialog {\n\n    /** Number of files displayed in the 'file details' text area */\n    private final static int NB_FILE_DETAILS_ROWS = 10;\n\n    private static boolean lastExpanded = false;\n\n    protected MainFrame mainFrame;\n    protected FileSet files;\n\n    private CollapseExpandButton collapseExpandButton;\n\n    JobDialog(MainFrame mainFrame, String title, FileSet files) {\n        super(mainFrame.getJFrame(), title, mainFrame.getJFrame());\n\n        this.mainFrame = mainFrame;\n        this.files = files;\n    }\n\n\n    /**\n     * Displays an error dialog with the specified message and title.\n     *\n     * @param message the error message\n     * @param title the error title\n     */\n    protected void showErrorDialog(String message, String title) {\n        InformationDialog.showErrorDialog(mainFrame.getJFrame(), title, message);\n    }\n\n    /**\n     * Displays an error dialog with the specified message and the default error title.\n     *\n     * @param message the error message\n     */\n    protected void showErrorDialog(String message) {\n        showErrorDialog(message, i18n(\"error\"));\n    }\n\n\n    /**\n     * Creates and returns a 'File details' panel, showing details about the files that the job will operate on. The file details\n     * are loaded in a separate thread, when the panel becomes visible.\n     *\n     * @param packOnUpdate pack the window after list loading\n     *\n     * @return a 'File details' panel, showing details about the files that the job will operate on\n     */\n    AsyncPanel createFileDetailsPanel(boolean packOnUpdate) {\n        return new AsyncPanel() {\n            @Override\n            public JComponent getTargetComponent(Exception e) {\n                FileList fileList = new FileList(files, true);\n                fileList.setVisibleRowCount(NB_FILE_DETAILS_ROWS);\n\n                return new JScrollPane(fileList, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);\n            }\n\n            @Override\n            public void initTargetComponent() {\n            }\n\n            @Override\n            protected void updateLayout() {\n                if (packOnUpdate) {\n                    Container tla = getTopLevelAncestor();\n                    if (tla instanceof Window) {\n                        ((Window) tla).pack();\n                    }\n                }\n            }\n        };\n    }\n\n    protected AsyncPanel createFileDetailsPanel() {\n        return createFileDetailsPanel(true);\n    }\n\n    /**\n     * Creates and returns a button that expands/collapses the specified 'File details' panel.\n     * The number of files that the job will operate on are displayed in the button's label.\n     *\n     * @param detailsPanel the 'File details' panel to expand/collapse\n     * @return a button that expands/collapses the specified 'File details' panel\n     */\n    protected CollapseExpandButton createFileDetailsButton(JPanel detailsPanel) {\n        collapseExpandButton = new CollapseExpandButton(i18n(\"nb_files\", String.valueOf(files.size())), detailsPanel, lastExpanded);\n        return collapseExpandButton;\n    }\n\n    /**\n     * Creates a panel where the specified 'File details' button and OK/cancel control buttons are laid out on a single row,\n     * with a space separation between the two components.\n     *\n     * @param fileDetailsButton the button that expands/collapses the 'File details' panel\n     * @param buttonsPanel the panel that contains the OK/cancel control buttons\n     * @return a panel where the specified 'File details' button and OK/cancel control buttons are laid out on a single row\n     */\n    protected JPanel createButtonsPanel(JButton fileDetailsButton, JPanel buttonsPanel) {\n        JPanel panel = new JPanel();\n        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));\n\n        if (fileDetailsButton != null) {\n            panel.add(fileDetailsButton);\n        }\n        panel.add(Box.createVerticalGlue());\n        panel.add(buttonsPanel);\n\n        return panel;\n    }\n    \n    /**\n     * Sets the list of files used by this job.\n     * @param files\n     */\n    protected void setFiles(FileSet files) {\n        this.files = files;\n        if (collapseExpandButton != null) {\n            collapseExpandButton.setText(i18n(\"nb_files\", Integer.toString(files.size())));\n        }\n    }\n\n    @Override\n    public void dispose() {\n        if (collapseExpandButton != null) {\n            lastExpanded = collapseExpandButton.getExpandedState();\n        }\n        super.dispose();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/LocalCopyDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.file;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.ui.main.MainFrame;\n\n/**\n * Dialog invoked when the user wants to copy a single file to the same directory under a different name.\n * The destination field is pre-filled with the file's name.\n *\n * @author Maxence Bernard\n */\npublic class LocalCopyDialog extends CopyDialog {\n\n    /**\n     * Creates a new <code>LocalCopyDialog</code>.\n     *\n     * @param mainFrame the main frame that spawned this dialog.\n     * @param files files to be copied\n     */\n    public LocalCopyDialog(MainFrame mainFrame, FileSet files) {\n        super(mainFrame, files);\n    }\n\n\n    @Override\n    protected PathFieldContent computeInitialPath(FileSet files) {\n        AbstractFile file = files.elementAt(0);\n        return selectDestinationFilename(file, file.getName(), 0);\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/MakeDirectoryFileDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.dialog.file;\n\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.awt.event.ItemEvent;\nimport java.awt.event.ItemListener;\nimport javax.swing.JButton;\nimport javax.swing.JCheckBox;\nimport javax.swing.JLabel;\nimport javax.swing.JPanel;\nimport javax.swing.JTextField;\nimport javax.swing.event.DocumentEvent;\nimport javax.swing.event.DocumentListener;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.commons.file.util.PathUtils;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.job.MakeDirectoryFileJob;\nimport com.mucommander.ui.action.ActionManager;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.EditAction;\nimport com.mucommander.ui.action.impl.MkdirAction;\nimport com.mucommander.ui.action.impl.MkfileAction;\nimport com.mucommander.ui.chooser.SizeChooser;\nimport com.mucommander.ui.dialog.DialogToolkit;\nimport com.mucommander.ui.dialog.FocusDialog;\nimport com.mucommander.ui.dialog.InformationDialog;\nimport com.mucommander.ui.helper.FocusRequester;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.text.FilePathField;\nimport com.mucommander.ui.viewer.EditorRegistrar;\nimport org.jetbrains.annotations.NotNull;\n\n\n/**\n * Dialog invoked when the user wants to create a new folder or an empty file in the current folder.\n *\n * @see MkdirAction\n * @see MkfileAction\n * @author Maxence Bernard\n */\npublic class MakeDirectoryFileDialog extends FocusDialog implements ActionListener, ItemListener {\n\n    private final MainFrame mainFrame;\n\t\n    private final JTextField pathField;\n\n    private JCheckBox cbAllocateSpace;\n    private JCheckBox cbOpenTextEditor;\n    private JCheckBox cbMakeExecutable;\n    private SizeChooser allocateSpaceChooser;\n\n    private final JButton btnOk;\n\n    private final boolean mkfileMode;\n    private boolean autoExecutableSelect;\n    private static boolean openInTextEditor = true;\n\n    /**\n     * Dialog size constraints\n     */\n    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(320, 0);\n\n    /**\n     * Dialog width should not exceed 360, height is not an issue (always the same)\n     */\n    private final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(400, 10000);\n\n    private JCheckBox convertWhiteSpaceCheckBox;\n    /**\n     * As a developer, it is annoyed to meet with Folder name that contains whitespace\n     */\n    private String oldDirName;\n\n\n    /**\n     * Creates a new Mkdir/Mkfile dialog.\n     *\n     * @param mkfileMode if true, the dialog will operate in 'mkfile' mode, if false in 'mkdir' mode\n     */\n    public MakeDirectoryFileDialog(MainFrame mainFrame, boolean mkfileMode) {\n        super(mainFrame.getJFrame(), ActionManager.getActionInstance(mkfileMode ? MkfileAction.Descriptor.ACTION_ID : MkdirAction.Descriptor.ACTION_ID, mainFrame).getLabel(), mainFrame.getJFrame());\n        this.mainFrame = mainFrame;\n        this.mkfileMode = mkfileMode;\n        setStorageSuffix(mkfileMode ? \"file\" : \"dir\");\n\n        Container contentPane = getContentPane();\n\n        YBoxPanel mainPanel = new YBoxPanel();\n        mainPanel.add(new JLabel(ActionProperties.getActionTooltip(mkfileMode ? MkfileAction.Descriptor.ACTION_ID : MkdirAction.Descriptor.ACTION_ID)+\" :\"));\n\n        // Create a path field with auto-completion capabilities\n        pathField = new FilePathField();\n        pathField.addActionListener(this);\n\n        // Sets the initial selection.\n        AbstractFile currentFile = mainFrame.getActiveTable().getSelectedFile();\n        if (currentFile != null) {\n            String initialValue = makeInitialValue(currentFile);\n            if (initialValue != null) {\n                pathField.setText(initialValue);\n            }\n        }\n        pathField.setSelectionStart(0);\n        pathField.setSelectionEnd(pathField.getText().length());\n        mainPanel.add(pathField);\n\n        if (mkfileMode) {\n            JPanel allocPanel = new JPanel(new BorderLayout());\n\n            cbAllocateSpace = new JCheckBox(i18n(\"mkfile_dialog.allocate_space\")+\":\", false);\n            cbAllocateSpace.addItemListener(this);\n            allocPanel.add(cbAllocateSpace, BorderLayout.WEST);\n\n            allocateSpaceChooser = new SizeChooser(false);\n            allocateSpaceChooser.setEnabled(false);\n            allocPanel.add(allocateSpaceChooser, BorderLayout.EAST);\n\n            mainPanel.add(allocPanel);\n\n            cbOpenTextEditor = new JCheckBox(i18n(\"mkfile_dialog.open_in_editor\"), false);\n            cbOpenTextEditor.addItemListener(this);\n            cbOpenTextEditor.setSelected(openInTextEditor);\n            mainPanel.add(cbOpenTextEditor);\n\n            if (OsFamily.getCurrent().isUnixBased()) {\n                cbMakeExecutable = new JCheckBox(i18n(\"mkfile_dialog.make_executable\"), false);\n                cbMakeExecutable.addItemListener(this);\n                mainPanel.add(cbMakeExecutable);\n\n                pathField.getDocument().addDocumentListener(new DocumentListener() {\n                    @Override\n                    public void insertUpdate(DocumentEvent e) {\n                        check();\n                    }\n\n                    @Override\n                    public void removeUpdate(DocumentEvent e) {\n                        check();\n                    }\n\n                    @Override\n                    public void changedUpdate(DocumentEvent e) {\n                        check();\n                    }\n\n                    private void check() {\n                        if (!autoExecutableSelect && pathField.getText().endsWith(\".sh\")) {\n                            cbMakeExecutable.setSelected(true);\n                            autoExecutableSelect = true;\n                        }\n                    }\n                });\n            }\n        } else {\n            JPanel convertWhitespacePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n            convertWhitespacePanel.add(new JLabel(i18n(\"mkfile_dialog.convert_whitespace\")));\n            this.convertWhiteSpaceCheckBox = new JCheckBox();\n            convertWhiteSpaceCheckBox.addItemListener(arg0 -> {\n\t\t\t\t\tif (convertWhiteSpaceCheckBox.isSelected()) {\n\t\t\t\t\t\toldDirName = pathField.getText();\n\t\t\t\t\t\tpathField.setText(oldDirName.replace(\" \", \"_\"));\n\t\t\t\t\t} else {\n\t\t\t\t\t\tpathField.setText(oldDirName);\n\t\t\t\t\t}\n\t\t\t\t});\n            convertWhitespacePanel.add(convertWhiteSpaceCheckBox);\n            mainPanel.add(convertWhitespacePanel);\n       }\n        \n        mainPanel.addSpace(10);\n        contentPane.add(mainPanel, BorderLayout.NORTH);\n        \n        btnOk = new JButton(i18n(\"create\"));\n        JButton cancelButton = new JButton(i18n(\"cancel\"));\n        contentPane.add(DialogToolkit.createOKCancelPanel(btnOk, cancelButton, getRootPane(), this), BorderLayout.SOUTH);\n\n        // Path field will receive initial focus\n        setInitialFocusComponent(pathField);\n\n        setMinimumSize(MINIMUM_DIALOG_DIMENSION);\n        setMaximumSize(MAXIMUM_DIALOG_DIMENSION);\n    }\n\n    private String makeInitialValue(AbstractFile currentFile) {\n        if (mkfileMode) {\n            return currentFile.getName();\n        } else {\n            return currentFile.getNameWithoutExtension();\n        }\n    }\n\n\n    /**\n     * Starts an {@link com.mucommander.job.MakeDirectoryFileJob}. This method is trigged by the 'OK' button or the return key.\n     */\n    private void startJob() {\n        String enteredPath = pathField.getText();\n\n        // Resolves destination folder\n        PathUtils.ResolvedDestination resolvedDest = PathUtils.resolveDestination(enteredPath, mainFrame.getActivePanel().getCurrentFolder(), false);\n        // The path entered doesn't correspond to any existing folder\n        if (resolvedDest == null) {\n            InformationDialog.showErrorDialog(mainFrame.getJFrame(), i18n(\"invalid_path\", enteredPath));\n            return;\n        }\n\n        // Checks if the directory already exists and reports the error if that's the case\n        int destinationType = resolvedDest.getDestinationType();\n        if (destinationType == PathUtils.ResolvedDestination.EXISTING_FOLDER) {\n            InformationDialog.showErrorDialog(mainFrame.getJFrame(), i18n(\"directory_already_exists\", enteredPath));\n            return;\n        }\n\n        // Don't check for existing regular files, MakeDirectoryFileJob will take of it and popup a FileCollisionDialog \n        AbstractFile destFile = resolvedDest.getDestinationFile();\n\n        FileSet fileSet = new FileSet(destFile.getParent());\n        // Job's FileSet needs to contain at least one file\n        fileSet.add(destFile);\n\n        ProgressDialog progressDialog = new ProgressDialog(mainFrame, getTitle());\n\n        MakeDirectoryFileJob job;\n        job = buildJob(fileSet, progressDialog);\n\n        progressDialog.start(job);\n    }\n\n    @NotNull\n    private MakeDirectoryFileJob buildJob(FileSet fileSet, ProgressDialog progressDialog) {\n        if (mkfileMode) {\n            long allocateSpace = cbAllocateSpace.isSelected() ? allocateSpaceChooser.getValue() : -1;\n            boolean executable = cbMakeExecutable != null && cbMakeExecutable.isSelected();\n            openInTextEditor = cbOpenTextEditor.isSelected();\n            return new MakeDirectoryFileJob(progressDialog, mainFrame, fileSet, allocateSpace, executable) {\n                @Override\n                protected boolean processFile(AbstractFile file, Object recurseParams) {\n                    boolean result = super.processFile(file, recurseParams);\n                    if (result && openInTextEditor) {\n                        Image icon = ActionProperties.getActionIcon(EditAction.Descriptor.ACTION_ID).getImage();\n                        EditorRegistrar.createEditorFrame(mainFrame, file, icon, FocusRequester::requestFocus);\n                    }\n                    return result;\n                }\n            };\n        } else {\n            return new MakeDirectoryFileJob(progressDialog, mainFrame, fileSet);\n        }\n    }\n\n\n    public void actionPerformed(ActionEvent e) {\n        Object source = e.getSource();\n        dispose();\n\t\t\n        // OK Button\n        if (source == btnOk || source == pathField) {\n            startJob();\n        }\n    }\n\n\n    public void itemStateChanged(ItemEvent e) {\n        allocateSpaceChooser.setEnabled(cbAllocateSpace.isSelected());\n        if (e.getItem() == cbAllocateSpace && cbAllocateSpace.isSelected()) {\n            cbOpenTextEditor.setSelected(false);\n        } else if (e.getItem() == cbOpenTextEditor && cbOpenTextEditor.isSelected()) {\n            cbAllocateSpace.setSelected(false);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/MoveDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.file;\n\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.commons.file.util.PathUtils;\nimport com.mucommander.job.MoveJob;\nimport com.mucommander.job.TransferFileJob;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.MoveAction;\nimport com.mucommander.ui.main.MainFrame;\n\n/**\n * Dialog invoked when the user wants to move or rename currently selected files.\n *\n * @see com.mucommander.ui.action.impl.MoveAction\n * @see com.mucommander.ui.action.impl.RenameAction\n * @author Maxence Bernard\n */\npublic class MoveDialog extends AbstractCopyDialog {\n\n    public MoveDialog(MainFrame mainFrame, FileSet files) {\n        super(mainFrame, files,\n                ActionProperties.getActionLabel(MoveAction.Descriptor.ACTION_ID),\n                Translator.get(\"move_dialog.move_description\"),\n                Translator.get(\"move\"),\n                Translator.get(\"move_dialog.error_title\"));\n    }\n\n\n\n    @Override\n    protected TransferFileJob createTransferFileJob(ProgressDialog progressDialog, PathUtils.ResolvedDestination resolvedDest, int defaultFileExistsAction) {\n        return new MoveJob(\n                progressDialog,\n                mainFrame,\n                files,\n                resolvedDest.getDestinationFolder(),\n                resolvedDest.getDestinationType()==PathUtils.ResolvedDestination.EXISTING_FOLDER?null:resolvedDest.getDestinationFile().getName(),\n                defaultFileExistsAction,\n                false);\n    }\n\n    @Override\n    protected String getProgressDialogTitle() {\n        return Translator.get(\"move_dialog.moving\");\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/PackDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.dialog.file;\n\nimport java.awt.FlowLayout;\nimport java.awt.event.ItemEvent;\nimport java.awt.event.ItemListener;\n\nimport javax.swing.*;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.archiver.ArchiveFormat;\nimport com.mucommander.commons.file.archiver.Archiver;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.commons.file.util.PathUtils;\nimport com.mucommander.desktop.DesktopManager;\nimport com.mucommander.job.ArchiveJob;\nimport com.mucommander.job.TransferFileJob;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.PackAction;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.text.FilePathField;\n\n\n/**\n * This dialog allows the user to pack marked files to an archive file of a selected format (Zip, TAR, ...)\n * and add an optional comment to the archive (for the formats that support it).\n *\n * @author Maxence Bernard\n */\npublic class PackDialog extends TransferDestinationDialog implements ItemListener {\n\n    private final JComboBox<String> formatsComboBox;\n    private final ArchiveFormat[] formats;\n\t\n    private final JTextArea commentArea;\n\n    /** Used to keep track of the last selected archive format. */\n    private int lastFormatIndex;\n\n    /** Last archive format used (Zip initially), selected by default when this dialog is created */\n    private static ArchiveFormat lastFormat = ArchiveFormat.ZIP;\n\n\n    public PackDialog(MainFrame mainFrame, FileSet files) {\n        super(mainFrame, files, ActionProperties.getActionLabel(PackAction.Descriptor.ACTION_ID), i18n(\"pack_dialog_description\"), i18n(\"pack\"), i18n(\"pack_dialog.error_title\"), false);\n\n        // Retrieve available formats for single file or many file archives\n        int nbFiles = files.size();\n        this.formats = Archiver.getFormats(nbFiles > 1 || (nbFiles > 0 && files.elementAt(0).isDirectory()));\n        int nbFormats = formats.length;\n\n        ArchiveFormat initialFormat = formats[0];\t\t// this value will only be used if last format is not available\n        int initialFormatIndex = 0;\t\t\t// this value will only be used if last format is not available\n        for (int i = 0; i<nbFormats; i++) {\n            if (formats[i] == lastFormat) {\n                initialFormat = formats[i];\n                initialFormatIndex = i;\n                break;\n            }\n        }\n        lastFormat = initialFormat;\n        lastFormatIndex = initialFormatIndex;\n\n        // Archive formats combo box\n\n        JPanel tempPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        tempPanel.add(new JLabel(i18n(\"pack_dialog.archive_format\")));\n        this.formatsComboBox = new JComboBox<>();\n        for (ArchiveFormat format : formats) {\n            formatsComboBox.addItem(format.name);\n        }\n\n        formatsComboBox.setSelectedIndex(lastFormatIndex);\n\t\t\n        formatsComboBox.addItemListener(this);\n        tempPanel.add(formatsComboBox);\n\n        YBoxPanel mainPanel = getMainPanel();\n        mainPanel.add(tempPanel);\t\t\n        mainPanel.addSpace(10);\n\t\t\n        // Comment area, enabled only if selected archive format has comment support\n\t\t\n        mainPanel.add(new JLabel(i18n(\"comment\")));\n        commentArea = new JTextArea();\n        commentArea.setRows(4);\n        mainPanel.add(commentArea);\n\n        cbBackgroundMode = new JCheckBox(i18n(\"destination_dialog.background_mode\"));\n        cbBackgroundMode.setSelected(enableBackgroundMode);\n        mainPanel.add(cbBackgroundMode);\n    }\n\t\n\n    //////////////////////////////////////////////\n    // TransferDestinationDialog implementation //\n    //////////////////////////////////////////////\n\n    @Override\n    protected PathFieldContent computeInitialPath(FileSet files) {\n        String initialPath = mainFrame.getInactivePanel().getCurrentFolder().getAbsolutePath(true);\n        AbstractFile file;\n        String fileName;\n        // Computes the archive's default name:\n        // - if it only contains one file, uses that file's name.\n        // - if it contains more than one file, uses the FileSet's parent folder's name.\n        if (files.size() == 1) {\n            file = files.elementAt(0);\n            fileName = file.isDirectory() && !DesktopManager.isApplication(file)\n                    ?file.getName()\n                    :file.getNameWithoutExtension();\n        } else {\n            file = files.getBaseFolder();\n            fileName = file.isRoot() ? \"\" : DesktopManager.isApplication(file) ? file.getNameWithoutExtension() : file.getName();\n        }\n\n        return new PathFieldContent(initialPath + fileName + \".\" + lastFormat.ext, initialPath.length(), initialPath.length() + fileName.length());\n    }\n\n    @Override\n    protected TransferFileJob createTransferFileJob(ProgressDialog progressDialog, PathUtils.ResolvedDestination resolvedDest, int defaultFileExistsAction) {\n        // Remember last format used, for next time this dialog is invoked\n        lastFormat = formats[formatsComboBox.getSelectedIndex()];\n\n        return new ArchiveJob(progressDialog, mainFrame, files, resolvedDest.getDestinationFile(), lastFormat, Archiver.formatSupportsComment(lastFormat)?commentArea.getText():null);\n    }\n\n    @Override\n    protected String getProgressDialogTitle() {\n        return i18n(\"pack_dialog.packing\");\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    @Override\n    protected boolean isValidDestination(PathUtils.ResolvedDestination resolvedDest, String destPath) {\n        if (resolvedDest == null) {\n            return false;\n        }\n\n        int destType = resolvedDest.getDestinationType();\n        return destType == PathUtils.ResolvedDestination.NEW_FILE || destType == PathUtils.ResolvedDestination.EXISTING_FILE;\n    }\n\n\n    //////////////////////////\n    // ItemListener methods //\n    //////////////////////////\n\n    public void itemStateChanged(ItemEvent e) {\n        FilePathField pathField = getPathField();\n        int newFormatIndex = formatsComboBox.getSelectedIndex();\n\n        // Updates the GUI if, and only if, the format selection has changed.\n        if (lastFormatIndex != newFormatIndex) {\n\n            String fileName = pathField.getText();  // Name of the destination archive file.\n            String oldFormatExtension = formats[lastFormatIndex].ext;\t// Old/current format's extension\n            if (fileName.endsWith(\".\" + oldFormatExtension)) {\n                // Saves the old selection.\n                int selectionStart = pathField.getSelectionStart();\n                int selectionEnd   = pathField.getSelectionEnd();\n\n                // Computes the new file name.\n                fileName = fileName.substring(0, fileName.length() - oldFormatExtension.length()) + formats[newFormatIndex].ext;\n\n                // Makes sure that the selection stays somewhat coherent.\n                if (selectionEnd == pathField.getText().length()) {\n                    selectionEnd = fileName.length();\n                }\n\n                // Resets the file path field.\n                pathField.setText(fileName);\n                pathField.setSelectionStart(selectionStart);\n                pathField.setSelectionEnd(selectionEnd);\n            }\n\n            commentArea.setEnabled(Archiver.formatSupportsComment(formats[formatsComboBox.getSelectedIndex()]));\n            lastFormatIndex = newFormatIndex;\n        }\n\n        // Transfer focus back to the text field \n        pathField.requestFocus();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/PathFieldContent.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.file;\n\nimport javax.swing.*;\n\n/**\n * This class wraps a path, and start and end offsets for the portion of the text to be selected in a text field.\n *\n * @author Maxence Bernard\n */\npublic class PathFieldContent {\n\n    protected String path;\n    protected int selectionStart;\n    protected int selectionEnd;\n\n    public PathFieldContent(String path) {\n        this(path, 0, path.length());\n    }\n\n    public PathFieldContent(String path, int selectionStart, int selectionEnd) {\n        this.path = path;\n        this.selectionStart = selectionStart;\n        this.selectionEnd = selectionEnd;\n    }\n\n    /**\n     * Sets the given {@link JTextField}'s text, selection start and end with that contained by this\n     * <code>PathFieldContent</code>.\n     *\n     * @param pathField instance of {@link JTextField} to update   \n     */\n    public void feedToPathField(JTextField pathField) {\n        // Set the initial path\n        pathField.setText(path);\n        // Text is selected so that user can directly type and replace path\n        pathField.setSelectionStart(selectionStart);\n        pathField.setSelectionEnd(selectionEnd);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/ProgressDialog.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.file;\r\n\r\nimport java.awt.BasicStroke;\r\nimport java.awt.BorderLayout;\r\nimport java.awt.Color;\r\nimport java.awt.Container;\r\nimport java.awt.Dimension;\r\nimport java.awt.Graphics;\r\nimport java.awt.Graphics2D;\r\nimport java.awt.Polygon;\r\nimport java.awt.RenderingHints;\r\nimport java.awt.Stroke;\r\nimport java.awt.event.ActionEvent;\r\nimport java.awt.event.ActionListener;\r\nimport java.awt.event.ItemEvent;\r\nimport java.awt.event.ItemListener;\r\nimport java.awt.event.WindowEvent;\r\nimport java.util.List;\r\nimport java.util.Vector;\r\n\r\nimport javax.swing.JButton;\r\nimport javax.swing.JCheckBox;\r\nimport javax.swing.JLabel;\r\nimport javax.swing.JPanel;\r\nimport javax.swing.JProgressBar;\r\nimport javax.swing.UIManager;\r\nimport javax.swing.event.ChangeEvent;\r\nimport javax.swing.event.ChangeListener;\r\n\r\nimport com.mucommander.ui.main.statusbar.TaskWidget;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport com.mucommander.conf.TcConfigurations;\r\nimport com.mucommander.conf.TcPreference;\r\nimport com.mucommander.conf.TcPreferences;\r\nimport com.mucommander.job.FileJob;\r\nimport com.mucommander.job.FileJobListener;\r\nimport com.mucommander.job.TransferFileJob;\r\nimport com.mucommander.job.progress.JobProgress;\r\nimport com.mucommander.job.progress.JobProgressListener;\r\nimport com.mucommander.job.progress.JobProgressMonitor;\r\nimport com.mucommander.utils.text.DurationFormat;\r\nimport com.mucommander.utils.text.SizeFormat;\r\nimport com.mucommander.ui.button.ButtonChoicePanel;\r\nimport com.mucommander.ui.button.CollapseExpandButton;\r\nimport com.mucommander.ui.chooser.SizeChooser;\r\nimport com.mucommander.ui.dialog.FocusDialog;\r\nimport com.mucommander.ui.icon.IconManager;\r\nimport com.mucommander.ui.layout.YBoxPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.statusbar.StatusBar;\r\n\r\nimport static com.mucommander.job.FileJob.State;\r\n\r\n/**\r\n * This dialog informs the user of the progress made by a FileJob and allows to control it: pause/resume it, stop it,\r\n * limit transfer rate...\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class ProgressDialog extends FocusDialog implements ActionListener, ItemListener, ChangeListener, FileJobListener, JobProgressListener {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(ProgressDialog.class);\r\n\t\r\n    // Button icons\r\n    private final static String RESUME_ICON = \"resume.png\";\r\n    private final static String PAUSE_ICON = \"pause.png\";\r\n    private final static String SKIP_ICON = \"skip.png\";\r\n    private final static String STOP_ICON = \"stop.png\";\r\n    private final static String CURRENT_SPEED_ICON = \"speed.png\";\r\n\r\n    // Dialog width is constrained to 320, height is not an issue (always the same)\r\n    private final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(640, 10000);\r\n    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(410, 0);\r\n\r\n    /** Height allocated to the 'speed graph' */\r\n    private final static int SPEED_GRAPH_HEIGHT = 80;\r\n\r\n\r\n    private JLabel lblCurrentFile;\r\n    private JLabel lblTotalTransferred;\r\n\r\n    private JProgressBar totalProgressBar;\r\n    private JProgressBar currentFileProgressBar;\r\n\r\n    private JLabel lblCurrentSpeed;\r\n    private JCheckBox cbLimitSpeed;\r\n    private SizeChooser speedChooser;\r\n    private JLabel lblElapsedTime;\r\n\r\n    private SpeedGraph speedGraph;\r\n\r\n    private CollapseExpandButton collapseExpandButton;\r\n    private ButtonChoicePanel buttonsChoicePanel;\r\n    private JButton btnPauseResume;\r\n    private JButton btnSkip;\r\n    private JButton btnStop;\r\n    private JButton btnBackground;\r\n    private JCheckBox cbCloseWhenFinished;\r\n//    private JButton hideButton;\r\n\r\n    private FileJob job;\r\n    private TransferFileJob transferFileJob;\r\n\r\n    private boolean firstTimeActivated = true;\r\n    private TaskWidget taskWidget;\r\n    private final MainFrame mainFrame;\r\n\r\n    static {\r\n        // Disable JProgressBar animation which is a real CPU hog under Mac OS X\r\n        UIManager.put(\"ProgressBar.repaintInterval\", Integer.MAX_VALUE);\r\n    }\r\n\r\n\r\n    public ProgressDialog(MainFrame mainFrame, String title, TaskWidget taskWidget) {\r\n        super(mainFrame.getJFrame(), title, mainFrame.getJFrame());\r\n        this.taskWidget = taskWidget;\r\n        this.mainFrame = mainFrame;\r\n\r\n        // Sets maximum and minimum dimensions for this dialog\r\n        setMaximumSize(MAXIMUM_DIALOG_DIMENSION);\r\n        setMinimumSize(MINIMUM_DIALOG_DIMENSION);\r\n\r\n        if (taskWidget != null) {\r\n            taskWidget.setProgressDialog(this);\r\n            taskWidget.setText(getTitle());\r\n        }\r\n//        setResizable(false);\r\n    }\r\n\r\n    public ProgressDialog(MainFrame mainFrame, String title) {\r\n        this(mainFrame, title, null);\r\n    }\r\n    \r\n    \r\n    private void initUi() {\r\n        Container contentPane = getContentPane();\r\n\r\n        totalProgressBar = new JProgressBar();\r\n        totalProgressBar.setStringPainted(true);\r\n        totalProgressBar.setAlignmentX(LEFT_ALIGNMENT);\r\n        lblCurrentFile = new JLabel(job.getStatusString());\r\n        lblCurrentFile.setAlignmentX(LEFT_ALIGNMENT);\r\n\t\t\r\n        YBoxPanel yPanel = new YBoxPanel();\r\n        // 2 progress bars\r\n        if (transferFileJob != null) {\r\n            yPanel.add(lblCurrentFile);\r\n            currentFileProgressBar = new JProgressBar();\r\n            currentFileProgressBar.setStringPainted(true);\r\n            yPanel.add(currentFileProgressBar);\r\n            yPanel.addSpace(10);\r\n\t\t\r\n            lblTotalTransferred = new JLabel(i18n(\"progress_dialog.starting\"));\r\n            yPanel.add(lblTotalTransferred);\r\n\t\t\t\r\n            yPanel.add(totalProgressBar);\r\n        }\r\n        // Single progress bar\r\n        else {\r\n            yPanel.add(lblCurrentFile);\r\n            yPanel.add(totalProgressBar);\r\n        }\r\n\r\n        yPanel.addSpace(10);\r\n        lblElapsedTime = new JLabel(i18n(\"progress_dialog.elapsed_time\")+\": \");\r\n        lblElapsedTime.setIcon(IconManager.getIcon(IconManager.IconSet.STATUS_BAR, StatusBar.WAITING_ICON));\r\n        yPanel.add(lblElapsedTime);\r\n\r\n        if (transferFileJob != null) {\r\n            JPanel tempPanel = new JPanel(new BorderLayout());\r\n\r\n            lblCurrentSpeed = new JLabel();\r\n            updateCurrentSpeedLabel(\"\");\r\n            lblCurrentSpeed.setIcon(IconManager.getIcon(IconManager.IconSet.PROGRESS, CURRENT_SPEED_ICON));\r\n            tempPanel.add(lblCurrentSpeed, BorderLayout.WEST);\r\n\r\n            YBoxPanel advancedPanel = new YBoxPanel();\r\n\r\n            speedGraph = new SpeedGraph();\r\n            speedGraph.setPreferredSize(new Dimension(0, SPEED_GRAPH_HEIGHT));\r\n            advancedPanel.add(speedGraph);\r\n\r\n            advancedPanel.addSpace(5);\r\n\r\n            JPanel tempPanel2 = new JPanel(new BorderLayout());\r\n            cbLimitSpeed = new JCheckBox(i18n(\"progress_dialog.limit_speed\")+\":\", false);\r\n            cbLimitSpeed.addItemListener(this);\r\n\r\n            tempPanel2.add(cbLimitSpeed, BorderLayout.WEST);\r\n\r\n            speedChooser = new SizeChooser(true);\r\n            speedChooser.setEnabled(false);\r\n            speedChooser.addChangeListener(this);\r\n\r\n            tempPanel2.add(speedChooser, BorderLayout.EAST);\r\n            advancedPanel.add(tempPanel2);\r\n            advancedPanel.addSpace(5);\r\n\r\n            // panel height can't be greater than speedChooser height\r\n            tempPanel2.setMaximumSize(new Dimension(tempPanel2.getMaximumSize().width, speedChooser.getPreferredSize().height));\r\n\r\n            collapseExpandButton = new CollapseExpandButton(i18n(\"progress_dialog.advanced\"), advancedPanel, true);\r\n            collapseExpandButton.setExpandedState(TcConfigurations.getPreferences().getVariable(TcPreference.PROGRESS_DIALOG_EXPANDED,\r\n                                                                                   TcPreferences.DEFAULT_PROGRESS_DIALOG_EXPANDED));\r\n            tempPanel.add(collapseExpandButton, BorderLayout.EAST);\r\n\r\n            // panel height can't be greater than collapseExpandButton height\r\n            tempPanel.setMaximumSize(new Dimension(tempPanel.getMaximumSize().width, collapseExpandButton.getPreferredSize().height));\r\n\r\n            yPanel.add(tempPanel);\r\n            yPanel.addSpace(5);\r\n\r\n            yPanel.add(advancedPanel);\r\n        }\r\n\r\n        cbCloseWhenFinished = new JCheckBox(i18n(\"progress_dialog.close_when_finished\"));\r\n        cbCloseWhenFinished.setSelected(TcConfigurations.getPreferences().getVariable(TcPreference.PROGRESS_DIALOG_CLOSE_WHEN_FINISHED,\r\n                                                                               TcPreferences.DEFAULT_PROGRESS_DIALOG_CLOSE_WHEN_FINISHED));\r\n        yPanel.add(cbCloseWhenFinished);\r\n\r\n//        yPanel.add(Box.createVerticalGlue());\r\n        contentPane.add(yPanel, BorderLayout.CENTER);\r\n\r\n        btnPauseResume = new JButton(i18n(\"pause\"), IconManager.getIcon(IconManager.IconSet.PROGRESS, PAUSE_ICON));\r\n        btnPauseResume.addActionListener(this);\r\n\r\n        if (transferFileJob != null) {\r\n            btnSkip = new JButton(i18n(\"skip\"), IconManager.getIcon(IconManager.IconSet.PROGRESS, SKIP_ICON));\r\n            btnSkip.addActionListener(this);\r\n        }\r\n\r\n        btnStop = new JButton(i18n(\"stop\"), IconManager.getIcon(IconManager.IconSet.PROGRESS, STOP_ICON));\r\n        btnStop.addActionListener(this);\r\n\r\n        btnBackground = new JButton(i18n(\"progress_dialog.hide\"));\r\n        btnBackground.addActionListener(this);\r\n\r\n        this.buttonsChoicePanel = new ButtonChoicePanel(\r\n                btnSkip == null ? new JButton[] {btnPauseResume, btnStop, btnBackground} : new JButton[] {btnPauseResume, btnSkip, btnStop, btnBackground},\r\n                0, getRootPane());\r\n        contentPane.add(buttonsChoicePanel, BorderLayout.SOUTH);\r\n\r\n        // Cancel button receives initial focus\r\n        setInitialFocusComponent(btnStop);\r\n\r\n        // Enter triggers cancel button\r\n        getRootPane().setDefaultButton(btnStop);\r\n    }\r\n\r\n\r\n    public void start(FileJob job) {\r\n        this.job = job;\r\n\r\n        // Listen to job state changes\r\n        job.addFileJobListener(this);\r\n\r\n        if (job instanceof TransferFileJob) {\r\n            this.transferFileJob = (TransferFileJob) job;\r\n        }\r\n\r\n        initUi();\r\n        \r\n\t\tJobProgressMonitor.getInstance().addJob(job);\r\n        JobProgressMonitor.getInstance().addJobProgressListener(this);\r\n\r\n        if (taskWidget == null) {\r\n            showDialog();\r\n        } else {\r\n            firstTimeActivated = false;\r\n            this.job.start();\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Stops repaint thread.\r\n     */\r\n    public void stop() {\r\n    \tJobProgressMonitor.getInstance().removeJobProgressListener(this);\r\n    }\r\n\r\n\r\n//    /**\r\n//     * This method is called by the registered FileJob starts each time a new file is being processed.\r\n//     */\r\n//    public void notifyCurrentFileChanged() {\r\n//        // Update current file label\r\n//        currentFileLabel.setText(job.getStatusString());\r\n//    }\r\n\r\n    private void updateThroughputLimit() {\r\n        transferFileJob.setThroughputLimit(cbLimitSpeed.isSelected() ? speedChooser.getValue() : -1);\r\n    }\r\n\r\n    private void updateCurrentSpeedLabel(String value) {\r\n        lblCurrentSpeed.setText(i18n(\"progress_dialog.current_speed\") + \": \" + value);\r\n    }\r\n\r\n\r\n    @Override\r\n    public void jobStateChanged(FileJob source, State oldState, State newState) {\r\n        LOGGER.debug(\"currentThread=\"+Thread.currentThread()+\" oldState=\"+oldState+\" newState=\"+newState);\r\n\r\n        if (newState == State.INTERRUPTED) {\r\n            // Stop repaint thread and dispose dialog\r\n            stop();\r\n            stopBackground();\r\n            dispose();\r\n        } else if (newState == State.FINISHED) {\r\n            stopBackground();\r\n            //  Dispose dialog only if 'Close when finished option' is selected\r\n            if (cbCloseWhenFinished.isSelected()) {\r\n                // Stop repaint thread and dispose dialog\r\n                stop();\r\n                dispose();\r\n            // If not, disable components to indicate that the job is finished and leave the dialog open\r\n            } else {\r\n                // Repaint thread should not be stopped now, it will die naturally after updating labels and progress\r\n                // bars to indicate that the job is finished\r\n\r\n                // Change 'Stop' button's label to 'Close'\r\n                btnStop.setText(i18n(\"close\"));\r\n\r\n                // Disable components\r\n                btnPauseResume.setEnabled(false);\r\n\r\n                if (transferFileJob != null) {\r\n                    btnSkip.setEnabled(false);\r\n                    cbLimitSpeed.setEnabled(false);\r\n                    speedChooser.setEnabled(false);\r\n                }\r\n            }\r\n        } else if (newState == State.PAUSED) {\r\n            btnPauseResume.setText(i18n(\"resume\"));\r\n            btnPauseResume.setIcon(IconManager.getIcon(IconManager.IconSet.PROGRESS, RESUME_ICON));\r\n\r\n            // Update buttons mnemonics\r\n            buttonsChoicePanel.updateMnemonics();\r\n            \r\n            if (transferFileJob != null) {\r\n                updateCurrentSpeedLabel(\"N/A\");\r\n            }\r\n        } else if (newState == State.RUNNING) {\r\n            btnPauseResume.setText(i18n(\"pause\"));\r\n            btnPauseResume.setIcon(IconManager.getIcon(IconManager.IconSet.PROGRESS, PAUSE_ICON));\r\n\r\n            // Update buttons mnemonics\r\n            buttonsChoicePanel.updateMnemonics();\r\n        }\r\n    }\r\n\r\n    \r\n    // Refresh current file label in a separate thread, more frequently than other components to give a sense\r\n    // of speed when small files are being transferred.\r\n    // This 'pull' approach allows to throttle the number label updates which have a cost VS updating the label\r\n    // for each file being processed (job notifications) which can hog the CPU when lots of small files\r\n    // are being transferred.\r\n    private void updateProgressLabel(JobProgress progress) {\r\n    \tlblCurrentFile.setText(progress.getJobStatusString());\r\n    }\r\n    \r\n    private void updateProgressUI(JobProgress progress) {\r\n        if (progress.isTransferFileJob()) {\r\n            currentFileProgressBar.setValue(progress.getFilePercentInt());\r\n            currentFileProgressBar.setString(progress.getFileProgressText());\r\n\r\n            // Update total transferred label\r\n            lblTotalTransferred.setText(\r\n                    i18n(\"progress_dialog.transferred\",\r\n                              SizeFormat.format(progress.getBytesTotal(), SizeFormat.DIGITS_MEDIUM | SizeFormat.UNIT_LONG | SizeFormat.ROUND_TO_KB),\r\n                              SizeFormat.format(progress.getTotalBps(), SizeFormat.UNIT_SPEED | SizeFormat.DIGITS_MEDIUM | SizeFormat.UNIT_SHORT | SizeFormat.ROUND_TO_KB))\r\n            );\r\n            \r\n            // Add new immediate bytes per second speed sample to speed graph and label and repaint it\r\n            // Skip this sample if job was paused and resumed, speed would not be accurate\r\n            if (progress.getLastTime() > progress.getJobPauseStartDate()) {\r\n                speedGraph.addSample(progress.getCurrentBps());\r\n                updateCurrentSpeedLabel(SizeFormat.format(progress.getCurrentBps(), SizeFormat.UNIT_SPEED| SizeFormat.DIGITS_MEDIUM| SizeFormat.UNIT_SHORT));\r\n            }\r\n        }\r\n        \r\n        totalProgressBar.setValue(progress.getTotalPercentInt());\r\n        totalProgressBar.setString(progress.getTotalProgressText());\r\n        if (taskWidget != null) {\r\n            taskWidget.setProgress(progress.getTotalPercentInt());\r\n        }\r\n\r\n        // Update elapsed time label\r\n        lblElapsedTime.setText(i18n(\"progress_dialog.elapsed_time\")+\": \"+DurationFormat.format(progress.getEffectiveJobTime()));\r\n    }\r\n    \r\n\r\n    @Override\r\n\tpublic void jobAdded(FileJob source, int idx) {\r\n\t\t// nothing here\t\t\r\n\t}\r\n\r\n    @Override\r\n\tpublic void jobRemoved(FileJob source, int idx) {\r\n\t\t// nothing here\t\t\r\n\t}\r\n\r\n    @Override\r\n\tpublic void jobProgress(FileJob source, int idx, boolean fullUpdate) {\r\n\t\tif (job.equals(source)) {\r\n\t\t\tupdateProgressLabel(source.getJobProgress());\r\n\t\t\tif (fullUpdate) {\r\n\t\t\t\tupdateProgressUI(source.getJobProgress());\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\r\n    @Override\r\n    public void actionPerformed(ActionEvent e) {\r\n        Object source = e.getSource();\r\n\r\n        if (source == btnStop) {\r\n            stopJob();\r\n        } else if (source == btnSkip) {\r\n            transferFileJob.skipCurrentFile();\r\n        } else if (source == btnPauseResume) {\r\n            // Pause/resume job\r\n            job.setPaused(job.getState() != State.PAUSED);\r\n        } else if (source == btnBackground) {\r\n            hideToBackground();\r\n        }\r\n//        else if(source==hideButton) {\r\n//            mainFrame.setState(Frame.ICONIFIED);\r\n//        }\r\n    }\r\n\r\n    private void stopJob() {\r\n        State jobState = job.getState();\r\n        // Case when 'Close when finished' isn't selected, stop button's action becomes 'Close'\r\n        if (jobState == State.FINISHED || jobState == State.INTERRUPTED) {\r\n            dispose();\r\n        } else {\r\n            job.interrupt();\r\n        }\r\n    }\r\n\r\n    private void hideToBackground() {\r\n        if (taskWidget == null) {\r\n            taskWidget = new TaskWidget();\r\n            taskWidget.setText(getTitle());\r\n            taskWidget.setProgressDialog(this);\r\n            mainFrame.getStatusBar().getTaskPanel().addTask(taskWidget);\r\n            mainFrame.getStatusBar().revalidate();\r\n            mainFrame.getStatusBar().repaint();\r\n        }\r\n        taskWidget.setVisible(true);\r\n        setVisible(false);\r\n    }\r\n\r\n\r\n    @Override\r\n    public void itemStateChanged(ItemEvent e) {\r\n        Object source = e.getSource();\r\n        if (source == cbLimitSpeed) {\r\n            boolean isEnabled = cbLimitSpeed.isSelected();\r\n            speedChooser.setEnabled(isEnabled);\r\n            updateThroughputLimit();\r\n        }\r\n    }\r\n\r\n\r\n    @Override\r\n    public void stateChanged(ChangeEvent e) {\r\n        if (e.getSource() == speedChooser) {\r\n            updateThroughputLimit();\r\n        }\r\n    }\r\n\r\n\r\n    @Override\r\n    public void windowActivated(WindowEvent e) {\r\n        // This method is called each time the dialog is activated\r\n        super.windowActivated(e);\r\n        if (firstTimeActivated) {\r\n            firstTimeActivated = false;\r\n            this.job.start();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void windowClosed(WindowEvent e) {\r\n        super.windowClosed(e);\r\n        \r\n        // Stop repaint thread if it isn't already\r\n        stop();\r\n\r\n        // Stop job if it isn't already\r\n        State jobState = job.getState();\r\n        if (jobState != State.FINISHED && jobState != State.INTERRUPTED) {\r\n            job.interrupt();\r\n        }\r\n\r\n        // Remember 'advanced panel' expanded state\r\n        if (collapseExpandButton != null) {\r\n        \tTcConfigurations.getPreferences().setVariable(TcPreference.PROGRESS_DIALOG_EXPANDED, collapseExpandButton.getExpandedState());\r\n        }\r\n\r\n        // Remember 'close window when finished' option state\r\n        TcConfigurations.getPreferences().setVariable(TcPreference.PROGRESS_DIALOG_CLOSE_WHEN_FINISHED, cbCloseWhenFinished.isSelected());\r\n    }\r\n\r\n\r\n\r\n    private void stopBackground() {\r\n        if (taskWidget != null) {\r\n            taskWidget.removeFromPanel();\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Transfer speed graph.\r\n     */\r\n    private class SpeedGraph extends JPanel {\r\n\r\n        private final Color GRAPH_OUTLINE_COLOR = new Color(70, 70, 70);\r\n        private final Color GRAPH_FILL_COLOR = new Color(215, 215, 215);\r\n        private final Color BPS_LIMIT_COLOR = new Color(204, 0, 0);\r\n        private static final int LINE_SPACING = 6;\r\n        private static final int NB_SAMPLES_MAX = 320;\r\n        private static final int STROKE_WIDTH = 1;\r\n\r\n        private final List<Long> samples = new Vector<>(NB_SAMPLES_MAX);\r\n        private final Stroke lineStroke = new BasicStroke(STROKE_WIDTH);\r\n\r\n\r\n        private SpeedGraph() {\r\n        }\r\n\r\n\r\n        private void addSample(long bytesPerSecond) {\r\n            synchronized(samples) {     // Ensures that paint() is not currently accessing the Vector\r\n                // Capacity reached, remove first sample\r\n                if (samples.size() == NB_SAMPLES_MAX) {\r\n                    samples.removeFirst();\r\n                }\r\n\r\n                // Add sample to the vector\r\n                samples.add(bytesPerSecond);\r\n            }\r\n            repaint();\r\n        }\r\n\r\n\r\n        @Override\r\n        public void paint(Graphics g) {\r\n            Graphics2D g2d = (Graphics2D)g;\r\n\r\n            // Enable antialiasing, looks way better\r\n            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);\r\n\r\n            int width = getWidth();\r\n            int height = getHeight();\r\n\r\n            // Fill the background with the panel's background color\r\n            g.setColor(getBackground());\r\n            g.fillRect(0, 0, width, height);\r\n\r\n            g2d.setStroke(lineStroke);\r\n\r\n            synchronized(samples) {     // Ensures that addSample() is not currently accessing the Vector\r\n                // Number of collected sample\r\n                int nbSamples = samples.size();\r\n                // Number of displayable samples based on their spacing\r\n                int nbDisplayableSamples = (width-2*STROKE_WIDTH)/LINE_SPACING;\r\n                // Index of the first sample\r\n                int firstSample = nbSamples>nbDisplayableSamples?nbSamples-nbDisplayableSamples:0;\r\n                // Number of lines to be drawn\r\n                int nbLines = Math.min(nbSamples, nbDisplayableSamples);\r\n\r\n                // Calculate the maximum bytes per second of all the samples to be displayed\r\n                long maxBps = 0;\r\n                for (int i = firstSample; i < firstSample+nbLines; i++) {\r\n                    long sample = samples.get(i);\r\n                    if (sample > maxBps) {\r\n                        maxBps = sample;\r\n                    }\r\n                }\r\n\r\n                // Y-scale projection ratio, leave some space on both sides of the graph\r\n                float yRatio = maxBps/((float)height-2*STROKE_WIDTH);\r\n\r\n                // Draw throughput limit as an horizontal line, only if there is a limit\r\n                long bpsLimit = transferFileJob.getThroughputLimit();\r\n                if (bpsLimit > 0) {\r\n                    g.setColor(BPS_LIMIT_COLOR);\r\n                    int y = height-STROKE_WIDTH - (int)(bpsLimit/yRatio);\r\n                    g.drawLine(0, y, width, y);\r\n                }\r\n\r\n                // Fill the graph\r\n                g.setColor(GRAPH_FILL_COLOR);\r\n                int x = STROKE_WIDTH;\r\n                Polygon p = new Polygon();\r\n                int sampleOffset = firstSample;\r\n                for (int l = 0; l < nbLines; l++) {\r\n                    p.addPoint(x, height-STROKE_WIDTH-(int)(samples.get(sampleOffset++) /yRatio));\r\n                    x += LINE_SPACING;\r\n                }\r\n                p.addPoint(x-LINE_SPACING, height-1);\r\n                p.addPoint(0, height-1);\r\n                g.fillPolygon(p);\r\n\r\n                // Draw the graph outline in a darker color\r\n                g.setColor(GRAPH_OUTLINE_COLOR);\r\n                x = STROKE_WIDTH;\r\n                sampleOffset = firstSample;\r\n                for (int l = 0; l < nbLines-1; l++) {\r\n                    g.drawLine(x, height-STROKE_WIDTH-(int)(samples.get(sampleOffset) /yRatio),\r\n                              (x+=LINE_SPACING), height-STROKE_WIDTH-(int)(samples.get(++sampleOffset) /yRatio));\r\n                }\r\n\r\n                // Draw an horizontal line at the bottom of the graph\r\n                g.drawLine(0, height-1,width-1, height-1);\r\n\r\n                // Unsuccessful rendering test using curves\r\n    //            GeneralPath gp = new GeneralPath();\r\n    //            int x = STROKE_WIDTH;\r\n    //            gp.moveTo(x, height-STROKE_WIDTH-(int)(((Long)samples.elementAt(sampleOffset++)).longValue()/yRatio));\r\n    //            x += LINE_SPACING;\r\n    //            for(int l=1; l<nbLines-2; l+=2) {\r\n    //                gp.quadTo(\r\n    //                    x, height-STROKE_WIDTH-(int)(((Long)samples.elementAt(sampleOffset)).longValue()/yRatio),\r\n    //                    x+LINE_SPACING, height-STROKE_WIDTH-(int)(((Long)samples.elementAt(sampleOffset+1)).longValue()/yRatio)\r\n    //                );\r\n    //\r\n    //                sampleOffset += 2;\r\n    //                x += 2*LINE_SPACING;\r\n    //\r\n    //                g2d.draw(gp);\r\n    //            }\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/PropertiesDialog.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.file;\r\n\r\nimport java.awt.BorderLayout;\r\nimport java.awt.Container;\r\nimport java.awt.Dimension;\r\nimport java.awt.FlowLayout;\r\nimport java.awt.event.ActionEvent;\r\nimport java.awt.event.ActionListener;\r\nimport java.awt.event.WindowEvent;\r\nimport java.io.IOException;\r\nimport java.text.SimpleDateFormat;\r\nimport java.util.List;\r\n\r\nimport javax.swing.*;\r\nimport javax.swing.border.EmptyBorder;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileFactory;\r\nimport com.mucommander.commons.file.FileOperation;\r\nimport com.mucommander.commons.file.UnsupportedFileOperationException;\r\nimport com.mucommander.commons.file.impl.local.LocalFile;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.commons.file.util.OSXFileUtils;\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.commons.runtime.OsVersion;\r\nimport com.mucommander.commons.util.Pair;\r\nimport com.mucommander.desktop.DesktopManager;\r\nimport com.mucommander.job.FileJob;\r\nimport com.mucommander.job.PropertiesJob;\r\nimport com.mucommander.utils.text.SizeFormat;\r\nimport com.mucommander.ui.action.ActionProperties;\r\nimport com.mucommander.ui.action.impl.ShowFilePropertiesAction;\r\nimport com.mucommander.ui.dialog.DialogToolkit;\r\nimport com.mucommander.ui.dialog.FocusDialog;\r\nimport com.mucommander.ui.icon.FileIcons;\r\nimport com.mucommander.ui.icon.IconManager;\r\nimport com.mucommander.ui.icon.SpinningDial;\r\nimport com.mucommander.ui.layout.XAlignedComponentPanel;\r\nimport com.mucommander.ui.layout.YBoxPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.text.FileLabel;\r\nimport com.mucommander.ui.text.MultiLineLabel;\r\nimport com.mucommander.utils.Convert;\r\n\r\n/**\r\n * This dialog shows properties of a file or a group of files : number of files, file kind,\r\n * combined size and location.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class PropertiesDialog extends FocusDialog implements Runnable, ActionListener {\r\n    private final PropertiesJob job;\r\n    private Thread repaintThread;\r\n    private final SpinningDial dial;\r\n\t\r\n\tprivate final JTextField textfield;\r\n    private final JLabel lblCounter;\r\n    private final JLabel lblSize;\r\n    private JLabel ownerLabel;\r\n    private JLabel groupLabel;\r\n\tprivate final JLabel lblLastMod;\r\n\tprivate final JLabel lblCreateTime;\r\n\tprivate final JLabel lblLastAccess;\r\n\r\n\tAbstractFile file;\r\n\tSimpleDateFormat sdf = new SimpleDateFormat(\"MM/dd/yyyy HH:mm:ss\");\r\n\r\n    private final JButton btnOkCancel;\r\n\tprivate String newName;\r\n\tprivate JTextField edtNewName;\r\n    // Dialog width is constrained to 320, height is not an issue (always the same)\r\n    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(360, 0);\r\n    private final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(1024, 800);\r\n\r\n    /** How often should progress information be refreshed (in ms) */\r\n    private final static int REFRESH_RATE = 500;\r\n\r\n    /** Dimension of the large file icon displayed on left side of the dialog */\r\n    private final static Dimension ICON_DIMENSION = new Dimension(64, 64);\r\n\r\n\t\r\n    public PropertiesDialog(MainFrame mainFrame, FileSet files) {\r\n        super(mainFrame.getJFrame(),\r\n              files.size() > 1 ? ActionProperties.getActionLabel(ShowFilePropertiesAction.Descriptor.ACTION_ID) :\r\n                      i18n(\"properties_dialog.file_properties\", files.elementAt(0).getName()), mainFrame.getJFrame());\r\n\r\n        this.job = new PropertiesJob(files, mainFrame);\r\n\t\t\r\n        Container contentPane = getContentPane();\r\n\r\n        JPanel fileDetailsPanel = new JPanel(new BorderLayout());\r\n\r\n        Icon icon;\r\n        boolean isSingleFile = files.size()==1;\r\n        AbstractFile singleFile = isSingleFile?files.elementAt(0):null;\r\n        if (isSingleFile) {\r\n            icon = FileIcons.getFileIcon(singleFile, ICON_DIMENSION);\r\n        } else {\r\n            ImageIcon imageIcon = IconManager.getIcon(IconManager.IconSet.COMMON, \"many_files.png\");\r\n            icon = IconManager.getScaledIcon(imageIcon, (float)ICON_DIMENSION.getWidth()/imageIcon.getIconWidth());\r\n        }\r\n\r\n        JLabel iconLabel = new JLabel(icon);\r\n        iconLabel.setVerticalAlignment(JLabel.TOP);\r\n        iconLabel.setBorder(new EmptyBorder(0, 0, 0, 8));\r\n\r\n        fileDetailsPanel.add(iconLabel, BorderLayout.WEST);\r\n\r\n        XAlignedComponentPanel labelPanel = new XAlignedComponentPanel(10);\r\n\r\n\t\t// Name of file for renaming\r\n\t\tfile = files.elementAt(0);\r\n\t\ttextfield = new JTextField();\r\n\t\tlabelPanel.addRow(\"Name\" + \":\", textfield, 6);\r\n\t\ttextfield.setText(file.getName());\r\n\t\ttextfield.addActionListener(this);\r\n\t\ttextfield.setEditable(true);\r\n        // Contents (set later)\r\n        lblCounter = new JLabel(\"\");\r\n        labelPanel.addRow(i18n(\"properties_dialog.contents\")+\":\", lblCounter, 6);\r\n\r\n        // Location (set here)\r\n        labelPanel.addRow(i18n(\"location\")+\":\", new FileLabel(files.getBaseFolder(), true), 6);\r\n\r\n        // Combined size (set later)\r\n        JPanel sizePanel;\r\n        sizePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\r\n        sizePanel.add(lblSize = new JLabel(\"\"));\r\n        sizePanel.add(new JLabel(dial = new SpinningDial()));\r\n        labelPanel.addRow(i18n(\"size\") + \":\", sizePanel, 6);\r\n\r\n\r\n\r\n\t\t// more information\r\n\t\tlblLastMod = new JLabel(\"\");\r\n\t\tlabelPanel.addRow(\"Last Modified\" + \":\", lblLastMod, 6);\r\n\t\tlblCreateTime = new JLabel(\"\");\r\n\t\tlabelPanel.addRow(\"Created\" + \":\", lblCreateTime, 6);\r\n\t\tlblLastAccess = new JLabel(\"\");\r\n\t\tlabelPanel.addRow(\"Last Accessed\" + \":\", lblLastAccess, 6);\r\n\r\n        if (isSingleFile) {\r\n            if (singleFile.canGetOwner()) {\r\n                String owner = singleFile.getOwner();\r\n                if (owner != null) {\r\n                    labelPanel.addRow(i18n(\"owner\") + \":\", new JLabel(owner), 6);\r\n                }\r\n            }\r\n            if (singleFile.canGetGroup()) {\r\n                String group = singleFile.getGroup();\r\n                if (group != null) {\r\n                    labelPanel.addRow(i18n(\"group\") + \":\", new JLabel(group), 6);\r\n                }\r\n            }\r\n            if (singleFile.isFileOperationSupported(FileOperation.GET_REPLICATION)) {\r\n                short replication;\r\n                try {\r\n                    replication = singleFile.getReplication();\r\n                    labelPanel.addRow(i18n(\"replication\") + \":\", new JLabel(Short.toString(replication)), 6);\r\n                } catch (UnsupportedFileOperationException e) {\r\n                    e.printStackTrace();\r\n                }\r\n            }\r\n            if (singleFile.isFileOperationSupported(FileOperation.GET_BLOCKSIZE)) {\r\n                long blocksize;\r\n                try {\r\n                    blocksize = singleFile.getBlocksize();\r\n                    labelPanel.addRow(i18n(\"blocksize\") + \":\", new JLabel(Convert.readableFileSize(blocksize)), 6);\r\n                } catch (UnsupportedFileOperationException e) {\r\n                    e.printStackTrace();\r\n                }\r\n            }\r\n        }\r\n\r\n        if (OsFamily.MAC_OS_X.isCurrent() && OsVersion.MAC_OS_X_10_4.isCurrentOrHigher() && isSingleFile && singleFile.hasAncestor(LocalFile.class)) {\r\n            String comment = OSXFileUtils.getSpotlightComment(singleFile);\r\n            JLabel commentLabel = new JLabel(i18n(\"comment\")+\":\");\r\n            commentLabel.setAlignmentY(JLabel.TOP_ALIGNMENT);\r\n            commentLabel.setVerticalAlignment(SwingConstants.TOP);\r\n\r\n            labelPanel.addRow(commentLabel, new MultiLineLabel(comment), 6);\r\n        }\r\n\r\n        if (isSingleFile && singleFile.hasAncestor(LocalFile.class)) {\r\n            List<Pair<JLabel, JComponent>> infos = DesktopManager.getExtendedFileProperties(singleFile);\r\n            infos.forEach(info -> labelPanel.addRow(info.first, info.second, 6));\r\n        }\r\n\r\n        updateLabels();\r\n\r\n        fileDetailsPanel.add(labelPanel, BorderLayout.CENTER);\r\n\r\n        YBoxPanel yPanel = new YBoxPanel(5);\r\n        yPanel.add(fileDetailsPanel);\r\n        contentPane.add(yPanel, BorderLayout.NORTH);\r\n\r\n        btnOkCancel = new JButton(i18n(\"cancel\"));\r\n        contentPane.add(DialogToolkit.createOKPanel(btnOkCancel, getRootPane(), this), BorderLayout.SOUTH);\r\n\r\n        // OK button will receive initial focus\r\n        setInitialFocusComponent(btnOkCancel);\r\n\t\t\r\n        setMinimumSize(MINIMUM_DIALOG_DIMENSION);\r\n        setMaximumSize(MAXIMUM_DIALOG_DIMENSION);\r\n\t\t\r\n        start();\r\n    }\r\n\r\n\r\n    private void updateLabels() {\r\n        int nbFiles = job.getNbFilesRecurse();\r\n        int nbFolders = job.getNbFolders();\r\n        lblCounter.setText(\r\n                             (nbFiles > 0 ? i18n(\"nb_files\", \"\"+nbFiles) : \"\")\r\n                             +(nbFiles > 0 && nbFolders > 0 ? \", \":\"\")\r\n                             +(nbFolders > 0 ? i18n(\"nb_folders\", \"\"+nbFolders):\"\")\r\n                             );\r\n        lblSize.setText(SizeFormat.format(job.getTotalBytes(), SizeFormat.DIGITS_MEDIUM | SizeFormat.UNIT_LONG | SizeFormat.INCLUDE_SPACE| SizeFormat.ROUND_TO_KB) +\r\n\t\t\t  \" (\" + SizeFormat.format(job.getTotalBytes(), SizeFormat.DIGITS_FULL | SizeFormat.UNIT_LONG | SizeFormat.INCLUDE_SPACE) + \")\");\r\n\r\n\t\t\r\n\t\t//adding last modification time and date\r\n\t\tlblLastMod.setText(sdf.format(file.getLastModifiedDate()));\r\n\r\n\r\n\t\t\r\n//\t\tBasicFileAttributes attr = Files.readAttributes(Paths.get(file.getAbsolutePath())),\r\n//\t\t\t\tBasicFileAttributes.class);\r\n//\t\tcreateTimeLabel.setText(attr.creationTime().toString());\r\n//\r\n//\t\tlastacces.setText(attr.lastAccessTime().toString());\r\n\r\n        long lastAccessTime;\r\n        try {\r\n            lastAccessTime = file.getLastAccessDate();\r\n        } catch (IOException e) {\r\n            lastAccessTime = -1;\r\n        }\r\n        lblLastAccess.setText(lastAccessTime > 0 ? sdf.format(lastAccessTime) : i18n(\"unknown\"));\r\n        long createTime;\r\n        try {\r\n            createTime = file.getCreationDate();\r\n        } catch (IOException e) {\r\n            createTime = -1;\r\n        }\r\n        lblCreateTime.setText(createTime > 0 ? sdf.format(createTime) : i18n(\"unknown\"));\r\n        lblCounter.repaint(REFRESH_RATE);\r\n        lblSize.repaint(REFRESH_RATE);\r\n    }\r\n\r\n\t\tprotected AbstractFile createDestinationFile(AbstractFile destFolder, String destFileName) {\r\n\t\tAbstractFile destFile;\r\n\t\tdo { // Loop for retry\r\n\t\t\ttry {\r\n\t\t\t\tdestFile = destFolder.getDirectChild(destFileName);\r\n\t\t\t\tbreak;\r\n\t\t\t} catch (IOException ignore) {}\r\n\t\t} while (true);\r\n\t\treturn destFile;\r\n\t}\r\n\tstatic void renameFile(AbstractFile destFile, String newName){\r\n\r\n\t\tAbstractFile destination = FileFactory.getFile(destFile.getParent()+\"/\"+newName);\r\n\t\t\r\n\t\ttry {\r\n\t\t\tdestFile.moveTo(destination);\r\n\t\t} catch (IOException e) {\r\n\t\t\t// TODO Auto-generated catch block\r\n\t\t\te.printStackTrace();\r\n\t\t}\t\r\n\t}\r\n\r\n\r\n    public void start() {\r\n        job.start();\r\n\t\t\r\n        repaintThread = new Thread(this, \"com.mucommander.ui.dialog.file.PropertiesDialog's Thread\");\r\n        repaintThread.start();\r\n    }\r\n\r\n\t@Override\r\n    public void run() {\r\n        dial.setAnimated(true);\r\n        while (repaintThread != null && job.getState()!= FileJob.State.FINISHED) {\r\n            updateLabels();\r\n\t\t\t\r\n            try {\r\n                Thread.sleep(REFRESH_RATE);\r\n            } catch(InterruptedException ignore) {}\r\n        }\r\n\r\n        // Updates button labels and stops spinning dial.\r\n        updateLabels();\r\n        btnOkCancel.setText(i18n(\"ok\"));\r\n        dial.setAnimated(false);\r\n    }\r\n\r\n\r\n    @Override\r\n    public void actionPerformed(ActionEvent e) {\r\n        if (e.getSource() == btnOkCancel) {\r\n            renameFile(file,textfield.getText());\r\n\t\t}\r\n\t\tdispose();\r\n    }\r\n\r\n\r\n    @Override\r\n    public void windowClosed(WindowEvent e) {\r\n        super.windowClosed(e);\r\n\t\t\r\n        // Stop threads\r\n        job.interrupt();\r\n        repaintThread = null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/SplitFileDialog.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.file;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.commons.file.util.PathUtils;\r\nimport com.mucommander.job.SplitFileJob;\r\nimport com.mucommander.utils.text.SizeFormat;\r\nimport com.mucommander.ui.action.ActionProperties;\r\nimport com.mucommander.ui.action.impl.SplitFileAction;\r\nimport com.mucommander.ui.combobox.EditableComboBox;\r\nimport com.mucommander.ui.dialog.DialogToolkit;\r\nimport com.mucommander.ui.layout.XAlignedComponentPanel;\r\nimport com.mucommander.ui.layout.XBoxPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.text.FilePathField;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.*;\r\nimport java.awt.event.ActionEvent;\r\nimport java.awt.event.ActionListener;\r\nimport java.awt.event.KeyAdapter;\r\nimport java.awt.event.KeyEvent;\r\nimport java.text.DecimalFormat;\r\nimport java.text.NumberFormat;\r\n\r\n/**\r\n * Dialog used to split a file into several parts.\r\n * \r\n * @author Mariusz Jakubowski\r\n */\r\npublic class SplitFileDialog extends JobDialog implements ActionListener {\r\n    \r\n\tprivate static final int MAX_PARTS = 100;\r\n\r\n\tprivate static final String[] unitNames = new String[] {\r\n\t\t\ti18n(\"unit.bytes_short\").toLowerCase(),\r\n\t\t\ti18n(\"unit.kb\").toLowerCase(),\r\n\t\t\ti18n(\"unit.mb\").toLowerCase(),\r\n\t\t\ti18n(\"unit.gb\").toLowerCase()\r\n\t};\r\n\t\r\n\tprivate static final int[] unitBytes = new int[] {\r\n\t\t1,\r\n\t\t1024,\r\n\t\t1024*1024,\r\n\t\t1024*1024*1024\r\n\t};\r\n\t\r\n    private final static DecimalFormat DECIMAL_FORMAT = (DecimalFormat)NumberFormat.getInstance();\r\n\t\r\n    static {\r\n    \tDECIMAL_FORMAT.setGroupingUsed(false);\r\n    }\r\n    \r\n    private String MSG_AUTO = i18n(\"split_file_dialog.auto\");\r\n\r\n    private AbstractFile file;\r\n\tprivate AbstractFile destFolder;\r\n\r\n    private JButton btnSplit;\r\n    private JButton btnClose;\r\n\r\n\tprivate FilePathField edtTargetDirectory;\r\n\tprivate JCheckBox cbGenerateCRC;\r\n\tprivate JTextField edtSize;\r\n\tprivate JSpinner spnParts;\r\n\r\n\tprivate boolean edtChange;\r\n\r\n\r\n    /**\r\n     * Creates a new split file dialog.\r\n     * @param mainFrame the main frame\r\n     * @param file a file to split\r\n     * @param destFolder default destination folder \r\n     */\r\n    public SplitFileDialog(MainFrame mainFrame, AbstractFile file, AbstractFile destFolder) {\r\n        super(mainFrame, ActionProperties.getActionLabel(SplitFileAction.Descriptor.ACTION_ID), new FileSet());\r\n        this.file = file;\r\n        this.destFolder = destFolder;\r\n        initialize();\r\n    }\r\n\r\n    /**\r\n     * Initializes the dialog.\r\n     */\r\n    protected void initialize() {\r\n        Container content = getContentPane();\r\n        content.setLayout(new BorderLayout(0, 5));\r\n        XAlignedComponentPanel pnlMain = new XAlignedComponentPanel(10);\r\n\r\n        pnlMain.addRow(i18n(\"split_file_dialog.file_to_split\") + \":\", new JLabel(file.getName()), 0);\r\n        String size = SizeFormat.format(file.getSize(), SizeFormat.DIGITS_FULL | SizeFormat.UNIT_LONG | SizeFormat.INCLUDE_SPACE);\r\n        pnlMain.addRow(i18n(\"size\") + \":\", new JLabel(size), 10);\r\n        \r\n\t\tedtTargetDirectory = new FilePathField(destFolder.getAbsolutePath(), 40);\r\n        pnlMain.addRow(i18n(\"split_file_dialog.target_directory\") + \":\", edtTargetDirectory, 5);\r\n\r\n        XBoxPanel pnlSize = new XBoxPanel();\r\n\t\tString[] sizes = new String[] {\r\n\t\t\tMSG_AUTO,\t\r\n\t\t\t\"10 \" + i18n(\"unit.mb\"),\r\n\t\t\t\"100 \" + i18n(\"unit.mb\"),\r\n\t\t\t\"250 \" + i18n(\"unit.mb\"),\r\n\t\t\t\"650 \" + i18n(\"unit.mb\"),\r\n\t\t\t\"700 \" + i18n(\"unit.mb\")\r\n\t\t};\r\n\t\tedtSize = new JTextField();\r\n\t\tEditableComboBox<String> cbSize = new EditableComboBox<>(edtSize, sizes);\r\n\t\tcbSize.setComboSelectionUpdatesTextField(true);\r\n\t\tcbSize.setSelectedIndex(1);\r\n\t\tedtSize.addKeyListener(new KeyAdapter() {\r\n\t\t\t@Override\r\n            public void keyReleased(KeyEvent e) {\r\n\t\t\t\tupdatePartsNumber();\r\n\t\t\t}\r\n\t\t});\r\n\t\tcbSize.addComboBoxListener(source -> updatePartsNumber());\r\n\t\tpnlSize.add(cbSize);\r\n\t\tpnlSize.addSpace(10);\r\n\t\tpnlSize.add(new JLabel(i18n(\"split_file_dialog.parts\") + \":\"));\r\n\t\tpnlSize.addSpace(5);\r\n\t\tspnParts = new JSpinner(new SpinnerNumberModel(1, 1, file.getSize(), 1));\r\n\t\tspnParts.addChangeListener(e -> {\r\n            if (!edtChange) {\r\n                long parts = ((Number)spnParts.getValue()).longValue();\r\n                long newSize = file.getSize() / parts;\r\n                if (file.getSize() % parts != 0) {\r\n                    newSize++;\r\n                }\r\n                if (getBytes() != newSize) {\r\n                    edtSize.setText(Long.toString(newSize));\r\n                }\r\n            }\r\n        });\r\n\t\tpnlSize.add(spnParts);\r\n        pnlMain.addRow(i18n(\"split_file_dialog.part_size\") + \":\", pnlSize, 0);\r\n        \r\n\t\tcbGenerateCRC = new JCheckBox(i18n(\"split_file_dialog.generate_CRC\"));\r\n\t\tcbGenerateCRC.setSelected(true);\r\n\t\tpnlMain.addRow(\"\", cbGenerateCRC, 0);\r\n\r\n\t\tcontent.add(pnlMain, BorderLayout.CENTER);\r\n        content.add(getPnlButtons(), BorderLayout.SOUTH);\r\n        getRootPane().setDefaultButton(btnSplit);\r\n        updatePartsNumber();\r\n    }\r\n\r\n\r\n\t/**\r\n     * Creates bottom panel with buttons.\r\n     */\r\n    private JPanel getPnlButtons() {\r\n        btnSplit = new JButton(i18n(\"split\"));\r\n        btnClose = new JButton(i18n(\"cancel\"));\r\n        return DialogToolkit.createOKCancelPanel(btnSplit, btnClose, getRootPane(), this);\r\n    }\r\n\r\n    \r\n    /**\r\n     * Executes the split job.\r\n     */\r\n\tprivate void startJob() {\r\n\t\tlong size = getBytes();\r\n\t\tif (size < 1) { \r\n\t\t\treturn;\t\t\r\n\t\t}\r\n\r\n\t\tString destPath = edtTargetDirectory.getText();\r\n        PathUtils.ResolvedDestination resolvedDest = \r\n        \tPathUtils.resolveDestination(destPath, mainFrame.getActivePanel().getCurrentFolder());\r\n        // The path entered doesn't correspond to any existing folder\r\n        if (resolvedDest == null || (files.size() > 1 &&\r\n        \t\tresolvedDest.getDestinationType() != PathUtils.ResolvedDestination.EXISTING_FOLDER)) {\r\n            showErrorDialog(i18n(\"invalid_path\", destPath), i18n(\"split_file_dialog.error_title\"));\r\n            return;\r\n        }\r\n\r\n        long parts = getParts();\r\n        if (parts > MAX_PARTS) {\r\n            showErrorDialog(i18n(\"split_file_dialog.max_parts\",\r\n            \t\tInteger.toString(MAX_PARTS)), i18n(\"split_file_dialog.error_title\"));\r\n        \treturn;\r\n        }\r\n        ProgressDialog progressDialog = new ProgressDialog(mainFrame, i18n(\"progress_dialog.processing_files\"));\r\n\t\tSplitFileJob job = new SplitFileJob(progressDialog, mainFrame,\r\n\t\t        file, resolvedDest.getDestinationFolder(), size, (int)parts);\r\n\t\tjob.setIntegrityCheckEnabled(cbGenerateCRC.isSelected());\r\n        progressDialog.start(job);\r\n\t}\r\n \r\n    /**\r\n     * Returns number of bytes entered in \"Bytes per part\" control.\r\n     * @return number of bytes entered in \"Bytes per part\" control.\r\n     */\r\n    private long getBytes() {\r\n\t\tString strVal = edtSize.getText().trim();\r\n\t\tif (MSG_AUTO.equals(strVal)) {\r\n\t\t\treturn file.getSize();\r\n\t\t}\r\n\t\tString[] strArr = strVal.split(\" \");\r\n\t\tif (strArr.length < 1 || strArr.length > 2) {\r\n\t\t\treturn -1;\r\n\t\t}\r\n\t\tint unit = 1;\r\n\t\tif (strArr.length == 2) {\r\n\t\t\tunit = -1;\r\n\t\t\tstrArr[1] = strArr[1].toLowerCase();\r\n\t\t\tfor (int i = 0; i < unitNames.length; i++) {\r\n\t\t\t\tif (unitNames[i].equals(strArr[1])) {\r\n\t\t\t\t\tunit = unitBytes[i];\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (unit == -1) {\r\n\t\t\t\treturn -1;\r\n\t\t\t}\r\n\t\t}\r\n\t\ttry {\r\n\t\t\tdouble size = DECIMAL_FORMAT.parse(strArr[0]).doubleValue();\r\n\t\t\tsize *= unit;\r\n\t\t\treturn (long) size;\r\n\t\t} catch (Exception e) {\r\n\t\t\treturn -1;\r\n\t\t}\r\n    }\r\n    \r\n    /**\r\n     * Returns number of parts this file will be splitted.\r\n     * @return number of parts this file will be splitted.\r\n     */\r\n    private long getParts() {\r\n\t\tlong size = getBytes();\r\n\t\tif (size < 1) { \r\n\t\t\treturn -1;\r\n\t\t}\r\n\t\treturn (long) Math.ceil((double)file.getSize() / (double)size);\r\n    }\r\n\r\n    /**\r\n     * Updates number of parts displayed in \"Parts\" control based on\r\n     * \"Bytes per part\" control.\r\n     */\r\n\tprivate void updatePartsNumber() {\r\n\t\tlong parts = getParts();\r\n\t\tif (parts < 1) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tedtChange = true;\r\n\t\tspnParts.setValue(parts);\r\n\t\tedtChange = false;\r\n\t}\r\n\r\n    // /////////////////////////////////\r\n    // ActionListener implementation //\r\n    // /////////////////////////////////\r\n\r\n    public void actionPerformed(ActionEvent e) {\r\n        Object source = e.getSource();\r\n        if (source == btnClose) {\r\n            dispose();\r\n        } else if (source == btnSplit) {\r\n            dispose();\r\n            startJob();\r\n        } \r\n    }\r\n    \r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/TransferDestinationDialog.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\n\r\npackage com.mucommander.ui.dialog.file;\r\n\r\nimport java.awt.*;\r\nimport java.awt.event.ActionEvent;\r\nimport java.awt.event.ActionListener;\r\nimport java.awt.event.WindowAdapter;\r\nimport java.awt.event.WindowEvent;\r\nimport java.lang.reflect.InvocationTargetException;\r\n\r\nimport javax.swing.JButton;\r\nimport javax.swing.JCheckBox;\r\nimport javax.swing.JComboBox;\r\nimport javax.swing.JLabel;\r\nimport javax.swing.JPanel;\r\nimport javax.swing.SwingUtilities;\r\nimport javax.swing.event.DocumentEvent;\r\nimport javax.swing.event.DocumentListener;\r\n\r\nimport com.mucommander.ui.combobox.TcComboBox;\r\nimport com.mucommander.ui.main.statusbar.TaskWidget;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.commons.file.util.PathUtils;\r\nimport com.mucommander.job.TransferFileJob;\r\nimport com.mucommander.ui.dialog.DialogToolkit;\r\nimport com.mucommander.ui.icon.SpinningDial;\r\nimport com.mucommander.ui.layout.YBoxPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.text.FilePathField;\r\n\r\n\r\n/**\r\n * This class is an abstract dialog which allows the user to enter the destination of a transfer in a text field\r\n * and control some options such as the default action to perform when a file already exists in the destination, or\r\n * if the files should be checked for integrity.\r\n * <p>\r\n * The initial path displayed in the text field is the one returned by {@link #computeInitialPath(FileSet)}.\r\n * When the dialog is confirmed by the user, either by pressing the 'OK' button or the 'Enter' key, the destination\r\n * path is resolved and checked with {@link #isValidDestination(PathUtils.ResolvedDestination, String)}. If the\r\n * path is a valid destination, a job instance is created using\r\n * {@link #createTransferFileJob(ProgressDialog, PathUtils.ResolvedDestination, int)} and started. If it isn't,\r\n * the user is notified with an error message.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic abstract class TransferDestinationDialog extends JobDialog implements ActionListener, DocumentListener {\r\n    /**\r\n     *\r\n     */\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(TransferDestinationDialog.class);\r\n    /**\r\n     * Dialog size constraints\r\n     */\r\n    protected final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(360, 0);\r\n    /**\r\n     * Dialog width should not exceed 360, height is not an issue (always the same)\r\n     */\r\n    protected final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(400, 10000);\r\n\t\r\n    static boolean enableBackgroundMode;\r\n\r\n\r\n    protected String errorDialogTitle;\r\n    private final boolean enableTransferOptions;\r\n    private final YBoxPanel mainPanel;\r\n    private final FilePathField pathField;\r\n    private final SpinningDial spinningDial;\r\n\r\n    private final JComboBox<String> cbFileExistsAction = new TcComboBox<>();\r\n    private JCheckBox cbSkipErrors;\r\n    private JCheckBox cbVerifyIntegrity;\r\n    JCheckBox cbBackgroundMode;\r\n    private final JButton btnOk;\r\n\r\n    /** Background thread that is currently being executed, <code>null</code> if there is none. */\r\n    private Thread thread;\r\n\r\n    /**\r\n     * for background operations\r\n     */\r\n    private TaskWidget taskWidget;\r\n\r\n\r\n\t\r\n    private final static int[] DEFAULT_ACTIONS = {\r\n        FileCollisionDialog.CANCEL_ACTION,\r\n        FileCollisionDialog.SKIP_ACTION,\r\n        FileCollisionDialog.OVERWRITE_ACTION,\r\n        FileCollisionDialog.OVERWRITE_IF_OLDER_ACTION,\r\n        FileCollisionDialog.RESUME_ACTION,\r\n        FileCollisionDialog.RENAME_ACTION\r\n    };\r\n\r\n    private final static String[] DEFAULT_ACTIONS_TEXT = {\r\n        FileCollisionDialog.CANCEL_TEXT,\r\n        FileCollisionDialog.SKIP_TEXT,\r\n        FileCollisionDialog.OVERWRITE_TEXT,\r\n        FileCollisionDialog.OVERWRITE_IF_OLDER_TEXT,\r\n        FileCollisionDialog.RESUME_TEXT,\r\n        FileCollisionDialog.RENAME_TEXT\r\n    };\r\n\r\n\r\n    TransferDestinationDialog(MainFrame mainFrame, FileSet files, String title, String labelText, String okText, String errorDialogTitle, boolean enableTransferOptions) {\r\n        super(mainFrame, title, files);\r\n\r\n        this.errorDialogTitle = errorDialogTitle;\r\n        this.enableTransferOptions = enableTransferOptions;\r\n\r\n        mainPanel = new YBoxPanel();\r\n        mainPanel.add(new JLabel(labelText+\" :\"));\r\n\r\n        // create a path field with auto-completion capabilities\r\n        pathField = new FilePathField();\r\n\r\n        JPanel borderPanel = new JPanel(new BorderLayout());\r\n        borderPanel.add(pathField, BorderLayout.CENTER);\r\n        // Spinning dial displayed while I/O-bound operations are being performed in a separate thread\r\n        spinningDial = new SpinningDial(false);\r\n        borderPanel.add(new JLabel(spinningDial), BorderLayout.EAST);\r\n        mainPanel.add(borderPanel);\r\n        mainPanel.addSpace(10);\r\n        pathField.getDocument().addDocumentListener(this);\r\n\r\n        // Path field will receive initial focus\r\n        setInitialFocusComponent(pathField);\t\t\r\n\r\n        if (enableTransferOptions) {\r\n            // Combo box that allows the user to choose the default action when a file already exists in destination\r\n            mainPanel.add(new JLabel(i18n(\"destination_dialog.file_exists_action\") + \" :\"));\r\n            cbFileExistsAction.addItem(i18n(\"ask\"));\r\n            for (String s : DEFAULT_ACTIONS_TEXT) {\r\n                cbFileExistsAction.addItem(s);\r\n            }\r\n            mainPanel.add(cbFileExistsAction);\r\n\r\n            mainPanel.addSpace(10);\r\n\r\n            cbSkipErrors = new JCheckBox(i18n(\"destination_dialog.skip_errors\"));\r\n            mainPanel.add(cbSkipErrors);\r\n\r\n            cbVerifyIntegrity = new JCheckBox(i18n(\"destination_dialog.verify_integrity\"));\r\n            mainPanel.add(cbVerifyIntegrity);\r\n\r\n            cbBackgroundMode = new JCheckBox(i18n(\"destination_dialog.background_mode\"));\r\n            cbBackgroundMode.setSelected(enableBackgroundMode);\r\n            mainPanel.add(cbBackgroundMode);\r\n\r\n            //mainPanel.addSpace(10);\r\n        }\r\n\r\n        getContentPane().add(mainPanel, BorderLayout.NORTH);\r\n\r\n        // create file details button and OK/cancel buttons and lay them out a single row\r\n        JPanel fileDetailsPanel = createFileDetailsPanel();\r\n\r\n        btnOk = new JButton(okText);\r\n\r\n        // Prevent the dialog from being validated while the initial path is being set.\r\n        setEnabledOkButtons(false);\r\n\r\n        JButton cancelButton = new JButton(i18n(\"cancel\"));\r\n\r\n        YBoxPanel buttonsPanel = new YBoxPanel(10);\r\n        buttonsPanel.add(createButtonsPanel(createFileDetailsButton(fileDetailsPanel),\r\n            DialogToolkit.createButtonPanel(getRootPane(), this, btnOk, cancelButton)\r\n        //    DialogToolkit.createOKCancelPanel(okButton, btnCancel, getRootPane(), this)\r\n        ));\r\n        buttonsPanel.add(fileDetailsPanel);\r\n\r\n        getContentPane().add(buttonsPanel, BorderLayout.SOUTH);\r\n\r\n        // Set minimum/maximum dimension\r\n        setMinimumSize(MINIMUM_DIALOG_DIMENSION);\r\n        setMaximumSize(MAXIMUM_DIALOG_DIMENSION);\r\n\r\n        // Dispose this dialog when the close window button is pressed\r\n        setDefaultCloseOperation(DISPOSE_ON_CLOSE);\r\n\r\n        addWindowListener(new WindowAdapter() {\r\n            @Override\r\n            public void windowOpened(WindowEvent e) {\r\n                // Spawn a new thread that retrieves the initial path (I/O-bound) and sets the path field accordingly\r\n                startThread(new InitialPathRetriever());\r\n            }\r\n\r\n            @Override\r\n            public void windowClosed(WindowEvent e) {\r\n                // Interrupt any ongoing thread when the dialog has been closed, regardless of how it has been closed.\r\n                interruptOngoingThread();\r\n            }\r\n        });\r\n    }\r\n\r\n    /**\r\n     * Returns the main panel that contains the path field.\r\n     *\r\n     * @return the main panel that contains the path field.\r\n     */\r\n    protected YBoxPanel getMainPanel() {\r\n        return mainPanel;\r\n    }\r\n\r\n    /**\r\n     * Returns the field where the destination path has to be entered.\r\n     *\r\n     * @return the field where the destination path has to be entered.\r\n     */\r\n    FilePathField getPathField() {\r\n        return pathField;\r\n    }\r\n\r\n    /**\r\n     * Interrupts any ongoing thread and starts the given one. The spinning dial is set to 'animated'.\r\n     *\r\n     * @param thread the thread to start\r\n     */\r\n    private synchronized void startThread(Thread thread) {\r\n        // Interrupt any ongoing thread\r\n        interruptOngoingThread();\r\n\r\n        // Spin the dial\r\n        spinningDial.setAnimated(true);\r\n\r\n        // Start the thread\r\n        this.thread = thread;\r\n        thread.start();\r\n    }\r\n\r\n    /**\r\n     * Interrupts the ongoing thread if there is one, does nothing otherwise.\r\n     */\r\n    private synchronized void interruptOngoingThread() {\r\n        if (thread != null) {\r\n            LOGGER.trace(\"Calling interrupt() on \" + thread);\r\n            thread.interrupt();\r\n            // Set the current thread to null\r\n            thread = null;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * This method checks that the given resolved destination is valid. This implementation returns <code>true</code>\r\n     * if the resolved destination is not <code>null</code> and, in case there is more than one file to process, if the\r\n     * destination is a folder that exists. This method can safely be overridden by subclasses to change the behavior.\r\n     * <p>\r\n     * Returning <code>true</code> will cause the job to go ahead and be started. Returning <code>false</code> will\r\n     * pop up an error dialog that notifies the user that the path is incorrect.\r\n     * <p>\r\n     * This method is called from a dedicated thread so that it can safely perform I/O operations without any chance\r\n     * of locking the event thread.\r\n     *\r\n     * @param resolvedDest the resolved destination\r\n     * @param destPath the path, as it was entered in the path field\r\n     * @return <code>true</code> if the given resolved destination is valid\r\n     */\r\n\tprotected boolean isValidDestination(PathUtils.ResolvedDestination resolvedDest, String destPath) {\r\n        return (resolvedDest != null && (files.size() == 1 || resolvedDest.getDestinationType() == PathUtils.ResolvedDestination.EXISTING_FOLDER));\r\n\t}\r\n\r\n    /**\r\n     * This method is called after the destination has been validated to start the job, with the resolved destination\r\n     * that has been validated by {@link #isValidDestination(PathUtils.ResolvedDestination, String)}.\r\n     *\r\n     * @param resolvedDest the resolved destination\r\n     */\r\n    private void startJob(PathUtils.ResolvedDestination resolvedDest) {\r\n        int defaultFileExistsAction;\r\n        boolean skipErrors;\r\n        boolean verifyIntegrity;\r\n        if (enableTransferOptions) {\r\n            // Retrieve default action when a file exists in destination, default choice\r\n            // (if not specified by the user) is 'Ask'\r\n            defaultFileExistsAction = cbFileExistsAction.getSelectedIndex();\r\n            if (defaultFileExistsAction == 0) {\r\n                defaultFileExistsAction = FileCollisionDialog.ASK_ACTION;\r\n            } else {\r\n                defaultFileExistsAction = DEFAULT_ACTIONS[defaultFileExistsAction - 1];\r\n            }\r\n            // Note: we don't remember default action on purpose: we want the user to specify it each time,\r\n            // it would be too dangerous otherwise.\r\n\r\n            skipErrors = cbSkipErrors.isSelected();\r\n            verifyIntegrity = cbVerifyIntegrity.isSelected();\r\n        } else {\r\n            defaultFileExistsAction = FileCollisionDialog.ASK_ACTION;\r\n            skipErrors = false;\r\n            verifyIntegrity = false;\r\n        }\r\n\r\n        ProgressDialog progressDialog = new ProgressDialog(mainFrame, getProgressDialogTitle(), taskWidget);\r\n        if (getReturnFocusTo() != null) {\r\n            progressDialog.returnFocusTo(getReturnFocusTo());\r\n        }\r\n        TransferFileJob job = createTransferFileJob(progressDialog, resolvedDest, defaultFileExistsAction);\r\n\r\n        if (job != null) {\r\n            job.setAutoSkipErrors(skipErrors);\r\n            job.setIntegrityCheckEnabled(verifyIntegrity);\r\n            progressDialog.start(job);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Called when the path has changed while {@link InitialPathRetriever} is running.\r\n     */\r\n    private void textUpdated() {\r\n        synchronized(this) {\r\n            if (thread != null && thread instanceof InitialPathRetriever) {\r\n                // Interrupt InitialPathRetriever\r\n                interruptOngoingThread();\r\n\r\n                // Enable\r\n                setEnabledOkButtons(true);\r\n\r\n                pathField.getDocument().removeDocumentListener(this);\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n\r\n    @Override\r\n    public void insertUpdate(DocumentEvent e) {\r\n        textUpdated();\r\n    }\r\n\r\n    @Override\r\n    public void removeUpdate(DocumentEvent e) {\r\n        textUpdated();\r\n    }\r\n\r\n    @Override\r\n    public void changedUpdate(DocumentEvent e) {\r\n    }\r\n\r\n    @Override\r\n    public void actionPerformed(ActionEvent e) {\r\n        Object source = e.getSource();\r\n\r\n        if (source == btnOk) {\r\n            boolean backgroundMode = cbBackgroundMode != null && cbBackgroundMode.isSelected();\r\n            enableBackgroundMode = backgroundMode;\r\n\r\n            // Disable the OK buttons and path field while the current path is being resolved\r\n            setEnabledOkButtons(false);\r\n            pathField.setEnabled(false);\r\n\r\n            if (backgroundMode) {\r\n                taskWidget = new TaskWidget();\r\n                //taskWidget.setText(okButton.getText());\r\n                mainFrame.getStatusBar().getTaskPanel().addTask(taskWidget);\r\n                mainFrame.getStatusBar().revalidate();\r\n                mainFrame.getStatusBar().repaint();\r\n            }\r\n\r\n            // Start resolving the path\r\n            startThread(new PathResolver());\r\n        } else {              // Cancel button\r\n            dispose();\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Called when the dialog has just been created to compute the initial path, based on the user file selection.\r\n     *\r\n     * <p>This method is called from a dedicated thread so that it can safely perform I/O operations without any chance\r\n     * of locking the event thread.\r\n     *\r\n     * @param files files that were selected/marked by the user\r\n     * @return a {@link PathFieldContent} containing the initial path to set in the path field\r\n     */\r\n    protected abstract PathFieldContent computeInitialPath(FileSet files);\r\n\r\n    /**\r\n     * Called after the dialog has been confirmed by the user and the resolved destination has been\r\n     * {@link #isValidDestination(PathUtils.ResolvedDestination, String) validated} to create the\r\n     * {@link TransferFileJob} instance that will subsequently be started.\r\n     *\r\n     * <p>This method is called from a dedicated thread so that it can safely perform I/O operations without any chance\r\n     * of locking the event thread.\r\n     *\r\n     * @param progressDialog the progress dialog that will show the job's progression\r\n     * @param resolvedDest the resolved and validated destination\r\n     * @param defaultFileExistsAction the value of the 'default action when file exists' choice\r\n     * @return the {@link TransferFileJob} instance that will subsequently be started\r\n     */\r\n    protected abstract TransferFileJob createTransferFileJob(ProgressDialog progressDialog, PathUtils.ResolvedDestination resolvedDest, int defaultFileExistsAction);\r\n\r\n    /**\r\n     * Returns the title to be used in the progress dialog.\r\n     *\r\n     * @return the title to be used in the progress dialog.\r\n     */\r\n    protected abstract String getProgressDialogTitle();\r\n\r\n\r\n\r\n    /**\r\n     * Retrieves the initial path to be set in the path field by calling {@link TransferDestinationDialog#computeInitialPath(FileSet)}.\r\n     * Since this operation can be I/O-bound, it is performed in a separate thread.\r\n     */\r\n    private class InitialPathRetriever extends Thread {\r\n\r\n        /** True if the thread has been interrupted */\r\n        private boolean interrupted;\r\n\r\n        @Override\r\n        public void run() {\r\n            final PathFieldContent pathFieldContent = computeInitialPath(files);\r\n\r\n            // Perform UI tasks in the AWT event thread\r\n            try {\r\n                SwingUtilities.invokeAndWait(() -> {\r\n                        spinningDial.setAnimated(false);\r\n\r\n                        if (!interrupted) {\r\n                            // Document change events are no longer needed\r\n                            pathField.getDocument().removeDocumentListener(TransferDestinationDialog.this);\r\n\r\n                            // Set the path field's text and selection \r\n                            pathFieldContent.feedToPathField(pathField);\r\n\r\n                            setEnabledOkButtons(true);\r\n                        }\r\n                });\r\n            } catch (InterruptedException e) {\r\n                LOGGER.trace(\"Interrupted\", e);\r\n            } catch (InvocationTargetException e) {\r\n                LOGGER.debug(\"Caught exception\", e);\r\n            }\r\n\r\n            // Set the current thread to null\r\n            synchronized (TransferDestinationDialog.this) {\r\n                if (thread == this)        // This thread may have been interrupted already\r\n                    thread = null;\r\n            }\r\n        }\r\n\r\n        /**\r\n         * Overridden to trap interruptions ({@link #isInterrupted()} doesn't seem to be working as advertised).\r\n         */\r\n        @Override\r\n        public void interrupt() {\r\n            super.interrupt();\r\n            this.interrupted = true;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Resolves the path entered in the path field into a {@link PathUtils.ResolvedDestination} instance and validates\r\n     * it using {@link TransferDestinationDialog#isValidDestination(PathUtils.ResolvedDestination, String)}.\r\n     * Since both of those operations can be I/O-bound, they are performed in a separate thread.\r\n     * <p>\r\n     * If the destination is valid, the job is started using {@link TransferDestinationDialog#startJob(PathUtils.ResolvedDestination)}\r\n     * and this dialog is disposed. Otherwise, a error dialog is displayed to notify the user that the path he has\r\n     * entered is invalid and invite him to try again.\r\n     */\r\n    private class PathResolver extends Thread {\r\n\r\n        /** True if the thread has been interrupted */\r\n        private boolean interrupted;\r\n\r\n        @Override\r\n        public void run() {\r\n            spinningDial.setAnimated(false);\r\n\r\n            final String destPath = pathField.getText();\r\n            // Resolves destination folder (I/O bound)\r\n            final PathUtils.ResolvedDestination resolvedDest = PathUtils.resolveDestination(destPath, mainFrame.getActivePanel().getCurrentFolder());\r\n            // Resolves destination folder (I/O bound)\r\n            final boolean isValid = isValidDestination(resolvedDest, destPath);\r\n\r\n            // Perform UI tasks in the AWT event thread\r\n            SwingUtilities.invokeLater(() -> {\r\n                if (interrupted) {\r\n                    dispose();\r\n                } else if (isValid) {\r\n                    dispose();\r\n                    startJob(resolvedDest);\r\n                } else {\r\n                    showErrorDialog(i18n(\"invalid_path\", destPath), errorDialogTitle);\r\n                    // Re-enable the OK button and path field so that a new path can be entered\r\n                    setEnabledOkButtons(true);\r\n                    pathField.setEnabled(true);\r\n                }\r\n            });\r\n\r\n            // Set the current thread to null\r\n            synchronized(TransferDestinationDialog.this) {\r\n                if (thread == this) {       // This thread may have been interrupted already\r\n                    thread = null;\r\n                }\r\n            }\r\n        }\r\n\r\n        /**\r\n         * Overridden to trap interruptions ({@link #isInterrupted()} doesn't seem to be working as advertised).\r\n         */\r\n        @Override\r\n        public void interrupt() {\r\n            super.interrupt();\r\n            this.interrupted = true;\r\n        }\r\n    }\r\n\r\n\r\n    private void setEnabledOkButtons(boolean enabled) {\r\n        btnOk.setEnabled(enabled);\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/UnpackDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.dialog.file;\n\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.commons.file.util.PathUtils;\nimport com.mucommander.job.TransferFileJob;\nimport com.mucommander.job.UnpackJob;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.UnpackAction;\nimport com.mucommander.ui.main.MainFrame;\n\n\n/**\n * Dialog that allows the user to choose the destination to unpack files to.\n *\n * @author Maxence Bernard\n */\npublic class UnpackDialog extends TransferDestinationDialog {\n\n    /**\n     * Creates and displays a new UnpackDialog.\n     *\n     * @param mainFrame the main frame this dialog is attached to\n     * @param files the set of files to unpack\n     */\n    public UnpackDialog(MainFrame mainFrame, FileSet files) {\n        super(mainFrame, files,\n        \t  ActionProperties.getActionLabel(UnpackAction.Descriptor.ACTION_ID),\n              i18n(\"unpack_dialog.destination\"),\n              i18n(\"unpack\"),\n              i18n(\"unpack_dialog.error_title\"),\n              true);\n    }\n\n    \n    //////////////////////////////////////////////\n    // TransferDestinationDialog implementation //\n    //////////////////////////////////////////////\n\n    @Override\n    protected PathFieldContent computeInitialPath(FileSet files) {\n        return new PathFieldContent(mainFrame.getInactivePanel().getCurrentFolder().getAbsolutePath(true));\n    }\n\n    @Override\n    protected TransferFileJob createTransferFileJob(ProgressDialog progressDialog, PathUtils.ResolvedDestination resolvedDest, int defaultFileExistsAction) {\n        int destinationType = resolvedDest.getDestinationType();\n        if(destinationType==PathUtils.ResolvedDestination.EXISTING_FILE) {\n            showErrorDialog(i18n(\"invalid_path\", resolvedDest.getDestinationFile().getAbsolutePath()));\n            return null;\n        }\n\n        return new UnpackJob(\n                progressDialog,\n                mainFrame,\n                files,\n                destinationType==PathUtils.ResolvedDestination.NEW_FILE?resolvedDest.getDestinationFile():resolvedDest.getDestinationFolder(),\n                defaultFileExistsAction);\n    }\n\n    @Override\n    protected String getProgressDialogTitle() {\n        return i18n(\"unpack_dialog.unpacking\");\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/file/package.html",
    "content": "<body>\n  Various dialogs used to display file related messages.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/help/ShortcutsDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.dialog.help;\n\nimport com.mucommander.ui.action.ActionCategory;\nimport com.mucommander.ui.action.ActionKeymap;\nimport com.mucommander.ui.action.ActionManager;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.ShowKeyboardShortcutsAction;\nimport com.mucommander.ui.dialog.DialogToolkit;\nimport com.mucommander.ui.dialog.FocusDialog;\nimport com.mucommander.ui.layout.XAlignedComponentPanel;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.text.KeyStrokeUtils;\nimport org.jetbrains.annotations.Nullable;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.util.*;\nimport java.util.List;\n\n/**\n * Dialog that displays shortcuts used in the application, sorted by topics.\n *\n * @author Maxence Bernard, Arik Hadas\n */\npublic class ShortcutsDialog extends FocusDialog implements ActionListener {\n    private final static String QUICK_SEARCH_TITLE = \"shortcuts_dialog.quick_search\";\n    private final static Map<String, String> QUICK_SEARCH_SHORTCUTS = new Hashtable<>() {\n        {\n            put(\"shortcuts_dialog.quick_search.start_search\", \"\");\n            put(\"shortcuts_dialog.quick_search.jump_to_previous\", \"UP\");\n            put(\"shortcuts_dialog.quick_search.jump_to_next\", \"DOWN\");\n            put(\"shortcuts_dialog.quick_search.remove_last_char\", \"BACKSPACE\");\n            put(\"shortcuts_dialog.quick_search.mark_jump_next\", \"INSERT\");\n            put(\"shortcuts_dialog.quick_search.cancel_search\", \"ESCAPE\");\n        }\n    };\n\n    /** Comparator of actions according to their labels */\n    private static final Comparator<String> ACTIONS_COMPARATOR = (id1, id2) -> {\n        String label1 = ActionProperties.getActionLabel(id1);\n        if (label1 == null) {\n            return 1;\n        }\n\n        String label2 = ActionProperties.getActionLabel(id2);\n        if (label2 == null) {\n            return -1;\n        }\n\n        return label1.compareTo(label2);\n    };\n\n    public ShortcutsDialog(MainFrame mainFrame) {\n        super(mainFrame.getJFrame(), ActionProperties.getActionLabel(ShowKeyboardShortcutsAction.Descriptor.ACTION_ID), mainFrame.getJFrame());\n\n        Container contentPane = getContentPane();\n        JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.TOP);\n\n        // Separate the actions according to their categories. \n        Map<ActionCategory, LinkedList<String>> categoryToItsActionsWithShortcutsIdsMap = createCategoryToItsActionsWithShortcutsMap();\n\n        //create tab and panel for each category\n        for (ActionCategory category: categoryToItsActionsWithShortcutsIdsMap.keySet()) {\n            // Get the list of actions from the above category which have shortcuts assigned to them\n            LinkedList<String> categoryActionsWithShortcuts = categoryToItsActionsWithShortcutsIdsMap.get(category);\n            categoryActionsWithShortcuts.sort(ACTIONS_COMPARATOR);\n\n            // If there is at least one action in the category with shortcuts assigned to it, add tab for the category\n            if (!categoryActionsWithShortcuts.isEmpty()) {\n                addTopic(tabbedPane, \"\"+category, categoryActionsWithShortcuts.iterator());\n            }\n        }\n\n        // create tab for quick-search category\n        addTopic(tabbedPane, i18n(QUICK_SEARCH_TITLE), QUICK_SEARCH_SHORTCUTS);\n\n        contentPane.add(tabbedPane, BorderLayout.CENTER);\n\n        // Add an OK button\n        JButton okButton = new JButton(i18n(\"ok\"));\n        contentPane.add(DialogToolkit.createOKPanel(okButton, getRootPane(), this), BorderLayout.SOUTH);\n        // OK will be selected when enter is pressed\n        getRootPane().setDefaultButton(okButton);\n        // First tab will receive initial focus\n        setInitialFocusComponent(tabbedPane);\n\n        // Set a reasonable maximum size, scroll bars will be displayed if there is not enough space\n        setMaximumSize(new Dimension(600, 360));\n    }\n\n    private Map<ActionCategory, LinkedList<String>> createCategoryToItsActionsWithShortcutsMap() {\n        // Hashtable that maps actions-category to LinkedList of actions (Ids) from the category that have shortcuts assigned to them\n        Map<ActionCategory, LinkedList<String>> categoryToItsActionsWithShortcutsIdsMap = new Hashtable<>();\n\n        // Initialize empty LinkedList for each category\n        for(ActionCategory category : ActionProperties.getActionCategories())\n            categoryToItsActionsWithShortcutsIdsMap.put(category, new LinkedList<>());\n\n        // Go over all action ids\n        Iterator<String> actionIds = ActionManager.getActionIds();\n        while (actionIds.hasNext()) {\n            String actionId = actionIds.next();\n            ActionCategory category = ActionProperties.getActionCategory(actionId);\n            // If the action has category and there is a primary shortcut assigned to it, add its id to the list of the category\n            if (category != null && ActionKeymap.doesActionHaveShortcut(actionId)) {\n                categoryToItsActionsWithShortcutsIdsMap.get(category).add(actionId);\n            }\n        }\n\n        return categoryToItsActionsWithShortcutsIdsMap;\n    }\n\n    private void addTopic(JTabbedPane tabbedPane, String titleKey, Iterator<String> descriptionsIterator) {\n        XAlignedComponentPanel compPanel = new XAlignedComponentPanel(15);\n\n        // Add all shortcuts and their description\n        addShortcutList(compPanel, descriptionsIterator);\n\n        // Panel needs to be vertically aligned to the top\n        JPanel northPanel = new JPanel(new BorderLayout());\n        northPanel.add(compPanel, BorderLayout.NORTH);\n\n        // Horizontal/vertical scroll bars will be displayed if needed\n        JScrollPane scrollPane = new JScrollPane(northPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);\n        scrollPane.setBorder(null);\n\n        tabbedPane.addTab(titleKey, scrollPane);\n    }\n\n    private void addTopic(JTabbedPane tabbedPane, String titleKey, Map<String, String> actionsToShortcutsMap) {\n        XAlignedComponentPanel compPanel = new XAlignedComponentPanel(15);\n\n        // Add all shortcuts and their description\n        addShortcutList(compPanel, actionsToShortcutsMap);\n\n        // Panel needs to be vertically aligned to the top\n        JPanel northPanel = new JPanel(new BorderLayout());\n        northPanel.add(compPanel, BorderLayout.NORTH);\n\n        // Horizontal/vertical scroll bars will be displayed if needed\n        JScrollPane scrollPane = new JScrollPane(northPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);\n        scrollPane.setBorder(null);\n\n        tabbedPane.addTab(titleKey, scrollPane);\n    }\n\n    private void addShortcutList(XAlignedComponentPanel compPanel, Iterator<String> muActionIdsIterator) {\n        // Add all actions shortcut and label (or tooltip if available)\n        while (muActionIdsIterator.hasNext()) {\n            String actionId = muActionIdsIterator.next();\n\n            String shortcutsRep = getActionRepresentation(actionId);\n\n            compPanel.addRow(shortcutsRep, new JLabel(ActionProperties.getActionDescription(actionId)), 5);\n        }\n    }\n\n    @Nullable\n    private String getActionRepresentation(String actionId) {\n        KeyStroke shortcut = ActionKeymap.getAccelerator(actionId);\n        String shortcutsRep = KeyStrokeUtils.getKeyStrokeDisplayableRepresentation(shortcut);\n\n        KeyStroke alternativeShortcut = ActionKeymap.getAlternateAccelerator(actionId);\n        if (alternativeShortcut != null) {\n            return shortcutsRep + \" / \" + KeyStrokeUtils.getKeyStrokeDisplayableRepresentation(alternativeShortcut);\n        }\n        return shortcutsRep;\n    }\n\n    private void addShortcutList(XAlignedComponentPanel compPanel, Map<String, String> actionsToShortcutsMap) {\n        List<String> vec = new ArrayList<>(actionsToShortcutsMap.keySet());\n        Collections.sort(vec);\n\n        for (String action : vec) {\n            compPanel.addRow(actionsToShortcutsMap.get(action), new JLabel(i18n(action)), 5);\n        }\n    }\n\n    public void actionPerformed(ActionEvent e) {\n        // OK disposes the dialog\n        dispose();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/help/package.html",
    "content": "<body>\n  Various components used to display muCommander's help messages.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/package.html",
    "content": "<body>\n  Provides various helper classes for dealing with JDialogs.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/PreferencesDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.dialog.pref;\n\nimport com.mucommander.ui.dialog.FocusDialog;\nimport com.mucommander.ui.dialog.pref.component.PrefComponent;\nimport com.mucommander.ui.icon.IconManager;\nimport com.mucommander.ui.layout.XBoxPanel;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.util.ArrayList;\nimport java.util.List;\n\n\n/**\n * Dialog meant to let users edit software preferences.\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic abstract class PreferencesDialog extends FocusDialog implements ActionListener {\n    /** Displays the different panels. */\n    private JTabbedPane tabbedPane;\n    /** Stores the different panels. */\n    private List<PreferencesPanel> prefPanels;\n    /** Apply button. */\n    private JButton btnApply;\n    /** OK button. */\n    private JButton btnOk;\n    /** Cancel button. */\n    private JButton btnCancel;\n\n\n\n    /**\n     * Creates a new preferences dialog.\n     * @param parent parent of the dialog.\n     * @param title  title of the dialg.\n     */\n    public PreferencesDialog(Frame parent, String title) {\n        super(parent, title, parent);\n        initUI();\n    }\n\n    /**\n     * Creates a new preferences dialog.\n     * @param parent parent of the dialog.\n     * @param title  title of the dialg.\n     */\n    public PreferencesDialog(Dialog parent, String title) {\n        super(parent, title, parent);\n        initUI();\n    }\n\n\n\n    /**\n     * Initializes the tabbed panel's UI.\n     */\n    private void initUI() {\n        // Initializes the tabbed pane.\n        prefPanels = new ArrayList<>();\n        tabbedPane = new JTabbedPane(JTabbedPane.TOP);\n\n        // Adds the tabbed pane.\n        Container contentPane = getContentPane();\n        contentPane.setLayout(new BorderLayout());\n        contentPane.add(tabbedPane, BorderLayout.CENTER);\n\n        // Buttons panel.\n        XBoxPanel buttonsPanel = new XBoxPanel();\n        buttonsPanel.add(btnApply = new JButton(i18n(\"apply\")));\n        buttonsPanel.addSpace(20);\n        buttonsPanel.add(btnOk = new JButton(i18n(\"ok\")));\n        buttonsPanel.add(btnCancel = new JButton(i18n(\"cancel\")));\n        \n        // Disable \"commit buttons\".\n        btnOk.setEnabled(false);\n    \tbtnApply.setEnabled(false);\n\n        // Buttons listening.\n        btnApply.addActionListener(this);\n        btnOk.addActionListener(this);\n        btnCancel.addActionListener(this);\n\n        // Aligns the button panel to the right.\n        JPanel tempPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));\n        tempPanel.add(buttonsPanel);\n        contentPane.add(tempPanel, BorderLayout.SOUTH);\n\n        // Selects OK when enter is pressed\n        getRootPane().setDefaultButton(btnOk);\n    }\n\n\n    private Component getTabbedPanel(PreferencesPanel prefPanel, boolean scroll) {\n        if (!scroll) {\n            return prefPanel;\n        }\n        JScrollPane scrollPane = new JScrollPane(prefPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);\n        scrollPane.setBorder(null);\n        return scrollPane;\n    }\n\n    /**\n     * Adds the specified preferences panel to this dialog.\n     * @param prefPanel panel to add.\n     * @param iconName  name of the icon that represents this dialog.\n     * @param scroll    whether this panel should be wrapped in a scroll panel.\n     */\n    private void addPreferencesPanel(PreferencesPanel prefPanel, String iconName, boolean scroll) {\n        tabbedPane.addTab(prefPanel.getTitle(), IconManager.getIcon(IconManager.IconSet.PREFERENCES, iconName), getTabbedPanel(prefPanel, scroll));\n        prefPanels.add(prefPanel);\n    }\n\n    /**\n     * Adds a new preferences panel and creates a new tab with an icon.\n     * @param prefPanel panel to add.\n     * @param iconName  name of the icon that represents this dialog.\n     */\n    protected void addPreferencesPanel(PreferencesPanel prefPanel, String iconName) {\n        addPreferencesPanel(prefPanel, iconName, true);\n    }\n\n    /**\n     * Adds the specified preferences panel to this dialog.\n     * @param prefPanel panel to add.\n     * @param scroll    whether this panel should be wrapped in a scroll panel.\n     */\n    protected void addPreferencesPanel(PreferencesPanel prefPanel, boolean scroll) {\n        tabbedPane.addTab(prefPanel.getTitle(), getTabbedPanel(prefPanel, scroll));\n        prefPanels.add(prefPanel);\n    }\n\n    /**\n     * Adds the specified preferences panel to this dialog.\n     * @param prefPanel panel to add.\n     */\n    protected void addPreferencesPanel(PreferencesPanel prefPanel) {\n        addPreferencesPanel(prefPanel, true);\n    }\n\n    /**\n     * Calls {@link PreferencesPanel#commit()} on all registered preference panels.\n     */\n    public void commit() {\n        // Ask pref panels to commit changes\n        for (PreferencesPanel panel : prefPanels) {\n            panel.commit();\n        }\n        setCommitButtonsEnabled(false);\n    }\n\n    /**\n     * Notifies all panels that changes are about to be commited.\n     * <p>\n     * This gives preference panels a chance to display warning or errors before changes are\n     * committed.\n     *\n     * @return <code>true</code> if all preference panels are ok with commiting the changes, <code>false</code> otherwise.\n     */\n    public boolean checkCommit() {\n        // Ask pref panels to commit changes\n        for (PreferencesPanel panel : prefPanels) {\n            if (!panel.checkCommit()) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Sets the currently active tab.\n     * @param index index of the tab to select.\n     */\n    public void setActiveTab(int index) {\n        tabbedPane.setSelectedIndex(index);\n    }\n\n\n\n    /**\n     * Reacts to buttons being pushed.\n     */\n    public void actionPerformed(ActionEvent e) {\n        Object source = e.getSource();\n\n        // Commit changes\n        if (source == btnOk || source == btnApply) {\n            if (!checkCommit()) {\n                return;\n            }\n            commit();\n        }\n\n        // Dispose dialog\n        if (source == btnOk || source == btnCancel) {\n            dispose();\n        }\n    }\n\n    /**\n     * Returns the index of the currently selected configuration panel.\n     * @return the index of the currently selected configuration panel.\n     */\n    protected int getSelectedPanelIndex() {\n        return tabbedPane.getSelectedIndex();\n    }\n    \n    /**\n     * This function set the \"commit buttons\", i.e apply &amp; ok buttons, enabled\\disabled\n     * according to the given parameter.\n     * \n     * @param enable - parameter that indicated if the commit button will turn to be\n     *  enabled (true) or disabled (false).\n     */\n    public void setCommitButtonsEnabled(boolean enable) {\n    \tbtnOk.setEnabled(enable);\n    \tbtnApply.setEnabled(enable);\n    }\n    \n    /**\n     * Function that will be called when the user change a value in a PrefComponent in this dialog.\n     * \n     * @param component - the PrefComponent that its value was changed.\n     */\n    public abstract void componentChanged(PrefComponent component);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/PreferencesPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.pref;\n\nimport com.mucommander.commons.conf.ValueList;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport lombok.Getter;\n\nimport javax.swing.*;\n\n/**\n * Abstract preferences panel.\n * @author Maxence Bernard\n */\npublic abstract class PreferencesPanel extends JPanel {\n    /** Preferences dialog that contains this panel. */\n    protected PreferencesDialog parent;\n    /** Panel's title.\n     * -- GETTER --\n     *  Returns the panel's title.\n     */\n    @Getter\n    protected String title;\n\n    /**\n     * Creates a new preferences panel.\n     * @param parent dialog that contains this panel.\n     * @param title  panel's title.\n     */\n    public PreferencesPanel(PreferencesDialog parent, String title) {\n        super();\n        this.title  = title;\n        this.parent = parent;\n    }\n\n    /**\n     * This method is called by PreferencesDialog after the user pressed 'OK'\n     * to save new preferences.\n     */\n    protected abstract void commit();\n\n    /**\n     * Checks whether this panel's data can be committed or whether it contains an error.\n     * @return <code>true</code> if the panel's data can be committed, <code>false</code> otherwise.\n     */\n    boolean checkCommit() {\n        return true;\n    }\n\n    protected static String getVariable(TcPreference preference) {\n        return TcConfigurations.getPreferences().getVariable(preference);\n    }\n\n    protected static boolean getVariable(TcPreference preference, boolean value) {\n        return TcConfigurations.getPreferences().getVariable(preference, value);\n    }\n\n    protected static String getVariable(TcPreference preference, String value) {\n        return TcConfigurations.getPreferences().getVariable(preference, value);\n    }\n\n    protected static float getVariable(TcPreference preference, float value) {\n        return TcConfigurations.getPreferences().getVariable(preference, value);\n    }\n\n    protected static int getVariable(TcPreference preference, int value) {\n        return TcConfigurations.getPreferences().getVariable(preference, value);\n    }\n\n    protected static ValueList getListVariable(TcPreference preference, String separator) {\n        return TcConfigurations.getPreferences().getListVariable(preference, separator);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/component/PrefCheckBox.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.pref.component;\r\n\r\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\r\n\r\nimport javax.swing.*;\r\n\r\n/**\r\n * @author Arik Hadas\r\n */\r\npublic class PrefCheckBox extends JCheckBox implements PrefComponent {\r\n\r\n\tpublic interface ChangeListener {\r\n\t\tboolean onChange(JCheckBox checkBox);\r\n\t}\r\n\r\n\tprivate final ChangeListener changeListener;\r\n\r\n\tpublic PrefCheckBox(String description, ChangeListener changeListener) {\r\n\t\tsuper(description);\r\n\t\tthis.changeListener = changeListener;\r\n\t}\r\n\t\r\n\tpublic void addDialogListener(final PreferencesDialog dialog) {\r\n\t\taddItemListener(e -> dialog.componentChanged(PrefCheckBox.this));\r\n\t}\r\n\r\n\t@Override\r\n\tpublic boolean hasChanged() {\r\n\t\treturn changeListener != null && changeListener.onChange(this);\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/component/PrefComboBox.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.pref.component;\r\n\r\nimport com.mucommander.ui.combobox.TcComboBox;\r\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\r\n\r\nimport java.util.List;\r\n\r\n/**\r\n * @author Arik Hadas, Oleg Trifonov\r\n */\r\npublic abstract class PrefComboBox<E> extends TcComboBox<E> implements PrefComponent {\r\n\r\n\tprotected PrefComboBox() {\r\n\t\tsuper();\r\n\t}\r\n\r\n    protected PrefComboBox(List<E> items) {\r\n        super();\r\n        addItems(items);\r\n    }\r\n\r\n    protected PrefComboBox(E[] items) {\r\n        super();\r\n        addItems(items);\r\n    }\r\n\r\n    private void addItems(List<E> items) {\r\n        for (E item : items) {\r\n            addItem(item);\r\n        }\r\n    }\r\n\r\n    private void addItems(E[] items) {\r\n        for (E item : items) {\r\n            addItem(item);\r\n        }\r\n    }\r\n\t\r\n\tpublic void addDialogListener(final PreferencesDialog dialog) {\r\n\t\taddItemListener(e -> dialog.componentChanged(PrefComboBox.this));\r\n\t}\r\n\r\n    @Override\r\n    public E getSelectedItem() {\r\n        @SuppressWarnings({\"unchecked\"})\r\n\t    E selected = (E)super.getSelectedItem();\r\n        return selected;\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/component/PrefComponent.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.pref.component;\r\n\r\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\r\n\r\n/**\r\n * Interface for components in preferences panels in which changes can be trackered.\r\n * \r\n * @author Arik Hadas\r\n */\r\npublic interface PrefComponent {\r\n\t\r\n\t/**\r\n\t * \r\n\t * @param dialog - parent dialog of the component parent panel.\r\n\t */\r\n\tvoid addDialogListener(final PreferencesDialog dialog);\r\n\t\r\n\t/**\r\n\t * This function checks if the component's value was changed from the value that is saved\r\n\t * in MuConfiguration.\r\n\t * \r\n\t * @return true if component's value differ from the value at MuConfiguration, else otherwise.\r\n\t */\r\n\tboolean hasChanged();\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/component/PrefEncodingSelectBox.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.pref.component;\r\n\r\nimport com.mucommander.ui.dialog.DialogOwner;\r\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\r\nimport com.mucommander.ui.encoding.EncodingListener;\r\nimport com.mucommander.ui.encoding.EncodingSelectBox;\r\n\r\n/**\r\n * @author Maxence Bernard\r\n */\r\npublic abstract class PrefEncodingSelectBox extends EncodingSelectBox implements PrefComponent {\r\n\r\n    protected PrefEncodingSelectBox(DialogOwner dialogOwner, String selectedEncoding) {\r\n        super(dialogOwner, selectedEncoding);\r\n    }\r\n\r\n    public void addDialogListener(final PreferencesDialog dialog) {\r\n        EncodingListener listener = (source, oldEncoding, newEncoding) -> dialog.componentChanged(PrefEncodingSelectBox.this);\r\n        addEncodingListener(listener);\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/component/PrefFilePathField.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.pref.component;\r\n\r\nimport com.mucommander.ui.autocomplete.BasicAutocompleterTextComponent;\r\nimport com.mucommander.ui.autocomplete.CompleterFactory;\r\nimport com.mucommander.ui.autocomplete.TextFieldCompletion;\r\n\r\n/**\r\n * @author Arik Hadas\r\n */\r\npublic abstract class PrefFilePathField  extends PrefTextField {\r\n\r\n    public PrefFilePathField(String text) {\r\n        super(text);\r\n        enableAutoCompletion();\r\n    }\r\n\r\n    /**\r\n     * Adds auto-completion capabilities to this text field.\r\n     */\r\n    private void enableAutoCompletion() {\r\n        new TextFieldCompletion(new BasicAutocompleterTextComponent(this), CompleterFactory.getPathCompleter());\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/component/PrefRadioButton.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.pref.component;\r\n\r\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\r\n\r\nimport javax.swing.*;\r\n\r\n/**\r\n * @author Arik Hadas\r\n */\r\npublic abstract class PrefRadioButton extends JRadioButton implements PrefComponent {\r\n\r\n\tprotected PrefRadioButton(String description) {\r\n\t\tsuper(description);\r\n\t}\r\n\t\r\n\tpublic void addDialogListener(final PreferencesDialog dialog) {\r\n\t\taddItemListener(e -> dialog.componentChanged(PrefRadioButton.this));\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/component/PrefTable.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.pref.component;\r\n\r\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\r\nimport org.jetbrains.annotations.NotNull;\r\n\r\nimport javax.swing.*;\r\nimport javax.swing.event.TableModelListener;\r\nimport javax.swing.table.TableColumn;\r\nimport javax.swing.table.TableModel;\r\nimport java.awt.*;\r\n\r\n/**\r\n * @author Arik Hadas\r\n */\r\npublic abstract class PrefTable extends JTable implements PrefComponent {\r\n\t\r\n\tprivate TableModelListener dialogListener;\r\n\r\n\tpublic PrefTable() { super(); }\r\n\t\r\n\tpublic PrefTable(TableModel model) { super(model); }\r\n\t\r\n\t/**\r\n\t * This function sets the widths of the table's columns according to the given array.\r\n\t *\r\n\t * @param percentages - array that contains the width of each column in percentage\r\n\t * \tfrom the width of the whole table.\r\n\t */\r\n\tpublic void setPreferredColumnWidths(double[] percentages) {\r\n\t\tfinal Dimension tableDim = this.getPreferredSize();\r\n\t\tdouble total = 0;\r\n\t\tint nbColumns = getColumnModel().getColumnCount();\r\n\t\t\r\n\t\tfor (int i = 0; i < nbColumns; ++i) {\r\n\t\t\ttotal += percentages[i];\r\n\t\t}\r\n\t\t\r\n\t\tfor (int i = 0; i < nbColumns; ++i) {\r\n\t\t\tTableColumn column = getColumnModel().getColumn(i);\r\n\t\t\tcolumn.setPreferredWidth((int) (tableDim.width * (percentages[i] / total)));\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void addDialogListener(final PreferencesDialog dialog) {\r\n\t\tgetModel().addTableModelListener(dialogListener = e -> dialog.componentChanged(PrefTable.this));\r\n\t}\r\n\t\r\n\t@Override\r\n    public void setModel(@NotNull TableModel model) {\r\n\t\tif (dialogListener != null) {\r\n\t\t\tmodel.addTableModelListener(dialogListener);\r\n\t\t}\r\n\t\tsuper.setModel(model);\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/component/PrefTextField.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.pref.component;\r\n\r\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\r\n\r\nimport javax.swing.*;\r\nimport javax.swing.event.DocumentEvent;\r\nimport javax.swing.event.DocumentListener;\r\n\r\n/**\r\n * @author Arik Hadas\r\n */\r\npublic abstract class PrefTextField extends JTextField implements PrefComponent {\r\n\t\r\n\tpublic PrefTextField(int columns) {\r\n\t\tsuper(columns);\r\n\t}\r\n\t\r\n\tpublic PrefTextField(String text) {\r\n\t\tsuper(text);\r\n\t}\r\n\t\r\n\tpublic void addDialogListener(final PreferencesDialog dialog) {\r\n\t\tgetDocument().addDocumentListener(new DocumentListener() {\r\n\r\n\t\t\tpublic void changedUpdate(DocumentEvent e) {\r\n\t\t\t\tdialog.componentChanged(PrefTextField.this);\t\r\n\t\t\t}\r\n\r\n\t\t\tpublic void insertUpdate(DocumentEvent e) {\r\n\t\t\t\tdialog.componentChanged(PrefTextField.this);\r\n\t\t\t}\r\n\r\n\t\t\tpublic void removeUpdate(DocumentEvent e) {\r\n\t\t\t\tdialog.componentChanged(PrefTextField.this);\t\r\n\t\t\t}\r\n\t\t});\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/general/AppearancePanel.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.pref.general;\r\n\r\nimport java.awt.BorderLayout;\r\nimport java.awt.Component;\r\nimport java.awt.Dimension;\r\nimport java.awt.FlowLayout;\r\nimport java.awt.event.ActionEvent;\r\nimport java.awt.event.ActionListener;\r\nimport java.awt.image.BufferedImage;\r\nimport java.io.File;\r\nimport java.io.IOException;\r\nimport java.util.*;\r\n\r\nimport javax.swing.BorderFactory;\r\nimport javax.swing.Box;\r\nimport javax.swing.ImageIcon;\r\nimport javax.swing.JButton;\r\nimport javax.swing.JFileChooser;\r\nimport javax.swing.JLabel;\r\nimport javax.swing.JList;\r\nimport javax.swing.JPanel;\r\nimport javax.swing.SwingUtilities;\r\nimport javax.swing.UIManager;\r\n\r\nimport com.mucommander.conf.TcPreferencesAPI;\r\nimport com.mucommander.ui.widgets.render.BasicComboBoxRenderer;\r\nimport com.mucommander.utils.FileIconsCache;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileFactory;\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.commons.runtime.OsVersion;\r\nimport com.mucommander.conf.TcConfigurations;\r\nimport com.mucommander.conf.TcPreference;\r\nimport com.mucommander.conf.TcPreferences;\r\nimport com.mucommander.extension.ClassFinder;\r\nimport com.mucommander.extension.ExtensionManager;\r\nimport com.mucommander.extension.LookAndFeelFilter;\r\nimport com.mucommander.job.FileCollisionChecker;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.dialog.InformationDialog;\r\nimport com.mucommander.ui.dialog.QuestionDialog;\r\nimport com.mucommander.ui.dialog.file.FileCollisionDialog;\r\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\r\nimport com.mucommander.ui.dialog.pref.PreferencesPanel;\r\nimport com.mucommander.ui.dialog.pref.component.PrefCheckBox;\r\nimport com.mucommander.ui.dialog.pref.component.PrefComboBox;\r\nimport com.mucommander.ui.dialog.pref.theme.ThemeEditorDialog;\r\nimport com.mucommander.ui.icon.FileIcons;\r\nimport com.mucommander.ui.icon.IconManager;\r\nimport com.mucommander.ui.icon.SpinningDial;\r\nimport com.mucommander.ui.layout.ProportionalGridPanel;\r\nimport com.mucommander.ui.layout.YBoxPanel;\r\nimport com.mucommander.ui.main.WindowManager;\r\nimport com.mucommander.ui.theme.Theme;\r\nimport com.mucommander.ui.theme.ThemeManager;\r\n\r\nimport static com.mucommander.conf.TcPreference.*;\r\n\r\n\r\n/**\r\n * 'Appearance' preferences panel.\r\n * @author Maxence Bernard, Nicolas Rinaudo\r\n */\r\nclass AppearancePanel extends PreferencesPanel implements ActionListener, Runnable {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(AppearancePanel.class);\r\n\t\r\n    // - Look and feel fields ------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /** Combo box containing the list of available look&feels. */\r\n    private PrefComboBox<String> lookAndFeelComboBox;\r\n    /** All available look&feels. */\r\n    private UIManager.LookAndFeelInfo[] lookAndFeels;\r\n    /** 'Use brushed metal look' checkbox */\r\n    private PrefCheckBox              brushedMetalCheckBox;\r\n    /** Triggers look and feel importing. */\r\n    private JButton                   importLookAndFeelButton;\r\n    /** Triggers look and feel deletion. */\r\n    private JButton                   deleteLookAndFeelButton;\r\n    /** Used to notify the user that the system is working. */\r\n    private SpinningDial              dial;\r\n    /** File from which to import looks and feels. */\r\n    private AbstractFile              lookAndFeelLibrary;\r\n\r\n\r\n\r\n    // - Icon size fields ----------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /** Displays the list of available sizes for toolbar icons. */\r\n    private PrefComboBox<String> toolbarIconsSizeComboBox;\r\n    /** Displays the list of available sizes for command bar icons. */\r\n    private PrefComboBox<String> commandBarIconsSizeComboBox;\r\n    /** Displays the list of available sizes for file icons. */\r\n    private PrefComboBox<String> fileIconsSizeComboBox;\r\n    /** All icon sizes label. */\r\n    private final static String[] ICON_SIZES = {\"100%\", \"125%\", \"150%\", \"175%\", \"200%\", \"300%\"};\r\n    /** All icon sizes scale factors. */\r\n    private final static float[] ICON_SCALE_FACTORS = {1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 3.0f};\r\n\r\n\r\n\r\n    // - Icons ---------------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /** Icon used to identify 'locked' themes. */\r\n    private ImageIcon lockIcon;\r\n    /** Transparent icon used to align non-locked themes with the others. */\r\n    private ImageIcon transparentIcon;\r\n\r\n\r\n\r\n    // - Theme fields --------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /** Lists all available themes. */\r\n    private PrefComboBox<Theme> themeComboBox;\r\n    /** Triggers the theme editor. */\r\n    private JButton btnEditTheme;\r\n    /** Triggers the theme duplication dialog. */\r\n    private JButton btnDuplicate;\r\n    /** Triggers the theme import dialog. */\r\n    private JButton btnImportTheme;\r\n    /** Triggers the theme export dialog. */\r\n    private JButton btnExportTheme;\r\n    /** Triggers the theme rename dialog. */\r\n    private JButton btnRenameTheme;\r\n    /** Triggers the theme delete dialog. */\r\n    private JButton btnDeleteTheme;\r\n    /** Used to display the currently selected theme's type. */\r\n    private JLabel lblType;\r\n    /** Whether to ignore theme combobox related events. */\r\n    private boolean      ignoreComboChanges;\r\n    /** Last folder that was selected in import or export operations. */\r\n    private AbstractFile lastSelectedFolder;\r\n\r\n    // - Editor Theme fields --------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /** Lists all available editor themes. */\r\n    private PrefComboBox<String> syntaxThemeComboBox;\r\n\r\n\r\n\r\n    // - Misc. fields --------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /** System icon combobox. */\r\n    private PrefComboBox<String> useSystemFileIconsComboBox;\r\n    /** Identifier of 'yes' actions in question dialogs. */\r\n    private final static int       YES_ACTION = 0;\r\n    /** Identifier of 'no' actions in question dialogs. */\r\n    private final static int       NO_ACTION = 1;\r\n    /** Identifier of 'cancel' actions in question dialogs. */\r\n    private final static int       CANCEL_ACTION = 2;\r\n    /** All known custom look and feels. */\r\n    private List<String> customLookAndFeels;\r\n\r\n\r\n\r\n    /**\r\n     * Creates a new appearance panel with the specified parent.\r\n     * @param parent dialog in which this panel is placed.\r\n     */\r\n    AppearancePanel(PreferencesDialog parent) {\r\n        super(parent, Translator.get(\"prefs_dialog.appearance_tab\"));\r\n        initUI();\r\n\r\n        initializeCustomLookAndFeels();\r\n    }\r\n\r\n\r\n\r\n    private void initUI() {\r\n        YBoxPanel mainPanel;\r\n\r\n        mainPanel = new YBoxPanel();\r\n\r\n        // Look and feel.\r\n        mainPanel.add(createLookAndFeelPanel());\r\n        mainPanel.add(Box.createRigidArea(new Dimension(0, 10)));\r\n\r\n        // Themes.\r\n        mainPanel.add(createThemesPanel());\r\n        mainPanel.add(Box.createRigidArea(new Dimension(0, 10)));\r\n\r\n        // Syntax highlighting.\r\n        mainPanel.add(createSyntaxHighlightThemePanel());\r\n        mainPanel.add(Box.createRigidArea(new Dimension(0, 10)));\r\n\r\n        // System icons.\r\n        mainPanel.add(createSystemIconsPanel());\r\n        mainPanel.add(Box.createVerticalGlue());\r\n\r\n        // Icon size.\r\n        mainPanel.add(createIconSizePanel());\r\n        mainPanel.add(Box.createRigidArea(new Dimension(0, 10)));\r\n\r\n        setLayout(new BorderLayout());\r\n        add(mainPanel, BorderLayout.NORTH);\r\n        \r\n        lookAndFeelComboBox.addDialogListener(parent);\r\n        themeComboBox.addDialogListener(parent);\r\n        syntaxThemeComboBox.addDialogListener(parent);\r\n        useSystemFileIconsComboBox.addDialogListener(parent);\r\n        toolbarIconsSizeComboBox.addDialogListener(parent);\r\n        commandBarIconsSizeComboBox.addDialogListener(parent);\r\n        fileIconsSizeComboBox.addDialogListener(parent);\r\n        if (brushedMetalCheckBox != null) {\r\n        \tbrushedMetalCheckBox.addDialogListener(parent);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Populates the look&feel combo box with all available look&feels.\r\n     */\r\n    private void populateLookAndFeels() {\r\n        lookAndFeelComboBox.removeAllItems();\r\n        initializeAvailableLookAndFeels();\r\n\r\n        // Populates the combo box.\r\n        int currentIndex = -1;\r\n        String currentName = UIManager.getLookAndFeel().getClass().getName();\r\n        for (int i = 0; i < lookAndFeels.length; i++) {\r\n            // Looks for the currently selected look&feel.\r\n            if (lookAndFeels[i].getClassName().equals(currentName)) {\r\n                currentIndex = i;\r\n            }\r\n            lookAndFeelComboBox.addItem(lookAndFeels[i].getName());\r\n        }\r\n\r\n        // Sets the initial selection.\r\n        if (currentIndex < 0) {\r\n            currentIndex = 0;\r\n        }\r\n        lookAndFeelComboBox.setSelectedIndex(currentIndex);\r\n    }\r\n\r\n    /**\r\n     * Creates the look and feel panel.\r\n     * @return the look and feel panel.\r\n     */\r\n    private JPanel createLookAndFeelPanel() {\r\n        // Creates the panel.\r\n        JPanel lnfPanel = new YBoxPanel();\r\n        lnfPanel.setAlignmentX(LEFT_ALIGNMENT);\r\n        lnfPanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"prefs_dialog.look_and_feel\")));\r\n\r\n        // Creates the look and feel combo box.\r\n        lookAndFeelComboBox = new PrefComboBox<>() {\r\n            public boolean hasChanged() {\r\n                String lnf = getVariable(LOOK_AND_FEEL);\r\n                int selectedIndex = getSelectedIndex();\r\n                return selectedIndex >= 0 && !lookAndFeels[selectedIndex].getClassName().equals(lnf);\r\n            }\r\n        };\r\n        lookAndFeelComboBox.setRenderer(new BasicComboBoxRenderer<>() {\r\n            @Override\r\n            public Component getListCellRendererComponent(JList<? extends String> list, String value, int index, boolean isSelected, boolean cellHasFocus) {\r\n                JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);\r\n                if (index < 0) {\r\n                    return label;\r\n                }\r\n                // All look and feels that are not modifiable must be flagged with a lock icon.\r\n                label.setIcon(isLookAndFeelModifiable(lookAndFeels[index]) ? transparentIcon : lockIcon);\r\n                return label;\r\n            }\r\n        });\r\n\r\n        // Populates the look and feel combo box.\r\n        populateLookAndFeels();\r\n\r\n        // Initializes buttons and event listening.\r\n        importLookAndFeelButton = new JButton(Translator.get(\"prefs_dialog.import\") + \"...\");\r\n        deleteLookAndFeelButton = new JButton(Translator.get(\"delete\"));\r\n        importLookAndFeelButton.addActionListener(this);\r\n        deleteLookAndFeelButton.addActionListener(this);\r\n        resetLookAndFeelButtons();\r\n        lookAndFeelComboBox.addActionListener(this);\r\n\r\n        // Adds the look and feel list and the action buttons to the panel.\r\n        JPanel flowPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\r\n        flowPanel.add(lookAndFeelComboBox);\r\n        flowPanel.add(importLookAndFeelButton);\r\n        flowPanel.add(deleteLookAndFeelButton);\r\n        flowPanel.add(new JLabel(dial = new SpinningDial()));\r\n        lnfPanel.add(flowPanel);\r\n\r\n        // For Mac OS X only, creates the 'brushed metal' checkbox.\r\n        // At the time of writing, the 'brushed metal' look causes the JVM to crash randomly under Leopard (10.5)\r\n        // so we disable brushed metal on that OS version but leave it for earlier versions where it works fine.\r\n        // See http://www.mucommander.com/forums/viewtopic.php?f=4&t=746 for more info about this issue.\r\n        if (OsFamily.MAC_OS_X.isCurrent() && OsVersion.MAC_OS_X_10_4.isCurrentOrLower()) {\r\n            // 'Use brushed metal look' option\r\n            brushedMetalCheckBox = new PrefCheckBox(Translator.get(\"prefs_dialog.use_brushed_metal\"),\r\n                    checkBox -> !String.valueOf(checkBox.isSelected()).equals(getVariable(USE_BRUSHED_METAL)));\r\n            brushedMetalCheckBox.setSelected(getVariable(USE_BRUSHED_METAL, TcPreferences.DEFAULT_USE_BRUSHED_METAL));\r\n            flowPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\r\n            flowPanel.add(brushedMetalCheckBox);\r\n            lnfPanel.add(flowPanel);\r\n        }\r\n\r\n        return lnfPanel;\r\n    }\r\n\r\n    /**\r\n     * Creates the icon size panel.\r\n     * @return the icon size panel.\r\n     */\r\n    private JPanel createIconSizePanel() {\r\n        ProportionalGridPanel gridPanel = new ProportionalGridPanel(2);\r\n\r\n        gridPanel.add(new JLabel(Translator.get(\"prefs_dialog.toolbar_icons\")));\r\n        gridPanel.add(toolbarIconsSizeComboBox = createIconSizeCombo(TOOLBAR_ICON_SCALE, TcPreferences.DEFAULT_TOOLBAR_ICON_SCALE));\r\n\r\n        gridPanel.add(new JLabel(Translator.get(\"prefs_dialog.command_bar_icons\")));\r\n        gridPanel.add(commandBarIconsSizeComboBox = createIconSizeCombo(COMMAND_BAR_ICON_SCALE, TcPreferences.DEFAULT_COMMAND_BAR_ICON_SCALE));\r\n\r\n        gridPanel.add(new JLabel(Translator.get(\"prefs_dialog.file_icons\")));\r\n        gridPanel.add(fileIconsSizeComboBox = createIconSizeCombo(TABLE_ICON_SCALE, TcPreferences.DEFAULT_TABLE_ICON_SCALE));\r\n\r\n        JPanel flowPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));\r\n        flowPanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"prefs_dialog.icons_size\")));\r\n        flowPanel.add(gridPanel);\r\n\r\n        return flowPanel;\r\n    }\r\n\r\n    /**\r\n     * Creates the themes panel.\r\n     * @return the themes panel.\r\n     */\r\n    private JPanel createThemesPanel() {\r\n        JPanel gridPanel = new ProportionalGridPanel(4);\r\n\r\n        // Creates the various panel's buttons.\r\n        btnEditTheme = new JButton(Translator.get(\"edit\") + \"...\");\r\n        btnImportTheme = new JButton(Translator.get(\"prefs_dialog.import\") + \"...\");\r\n        btnExportTheme = new JButton(Translator.get(\"prefs_dialog.export\") + \"...\");\r\n        btnRenameTheme = new JButton(Translator.get(\"rename\"));\r\n        btnDeleteTheme = new JButton(Translator.get(\"delete\"));\r\n        btnDuplicate = new JButton(Translator.get(\"duplicate\"));\r\n        btnEditTheme.addActionListener(this);\r\n        btnImportTheme.addActionListener(this);\r\n        btnExportTheme.addActionListener(this);\r\n        btnRenameTheme.addActionListener(this);\r\n        btnDeleteTheme.addActionListener(this);\r\n        btnDuplicate.addActionListener(this);\r\n\r\n        // Creates the panel's 'type label'.\r\n        lblType = new JLabel(\"\");\r\n\r\n        // Creates the theme combo box.\r\n        themeComboBox = new PrefComboBox<>() {\r\n            public boolean hasChanged() {\r\n                return !ThemeManager.isCurrentTheme(getSelectedItem());\r\n            }\r\n        };\r\n        themeComboBox.addActionListener(this);\r\n\r\n        // Sets the combobox's renderer.\r\n        lockIcon = IconManager.getIcon(IconManager.IconSet.PREFERENCES, \"lock.png\");\r\n        transparentIcon = new ImageIcon(new BufferedImage(lockIcon.getIconWidth(), lockIcon.getIconHeight(), BufferedImage.TYPE_INT_ARGB));\r\n        themeComboBox.setRenderer(new BasicComboBoxRenderer<>() {\r\n            @Override\r\n            public Component getListCellRendererComponent(JList<? extends Theme> list, Theme theme, int index, boolean isSelected, boolean cellHasFocus) {\r\n                JLabel label = (JLabel) super.getListCellRendererComponent(list, theme, index, isSelected, cellHasFocus);\r\n                if (ThemeManager.isCurrentTheme(theme)) {\r\n                    label.setText(theme.getName() + \" (\" + Translator.get(\"theme.current\") + \")\");\r\n                } else {\r\n                    label.setText(theme.getName());\r\n                }\r\n\r\n                label.setIcon(theme.getType() == Theme.Type.PREDEFINED ? lockIcon : transparentIcon);\r\n\r\n                return label;\r\n            }\r\n        });\r\n\r\n        // Initialises the content of the combo box.\r\n        populateThemes(ThemeManager.getCurrentTheme());\r\n\r\n        gridPanel.add(themeComboBox);\r\n        gridPanel.add(btnEditTheme);\r\n        gridPanel.add(btnImportTheme);\r\n        gridPanel.add(btnExportTheme);\r\n\r\n        gridPanel.add(lblType);\r\n        gridPanel.add(btnRenameTheme);\r\n        gridPanel.add(btnDeleteTheme);\r\n        gridPanel.add(btnDuplicate);\r\n\r\n        JPanel flowPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));\r\n        flowPanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"prefs_dialog.themes\")));\r\n        flowPanel.add(gridPanel);\r\n\r\n        return flowPanel;\r\n    }\r\n\r\n    private JPanel createSyntaxHighlightThemePanel() {\r\n        JPanel gridPanel = new ProportionalGridPanel(1);\r\n\r\n        syntaxThemeComboBox = new PrefComboBox<>(ThemeManager.predefinedSyntaxThemeNames()) {\r\n            @Override\r\n            public boolean hasChanged() {\r\n                String selectedTheme = getSelectedItem();\r\n                return !ThemeManager.getCurrentSyntaxThemeName().equalsIgnoreCase(selectedTheme);\r\n            }\r\n        };\r\n        syntaxThemeComboBox.addActionListener(this);\r\n        syntaxThemeComboBox.setSelectedItem(ThemeManager.getCurrentSyntaxThemeName());\r\n        gridPanel.add(syntaxThemeComboBox);\r\n\r\n        JPanel flowPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));\r\n        flowPanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"prefs_dialog.syntax_themes\")));\r\n        flowPanel.add(gridPanel);\r\n\r\n        return flowPanel;\r\n    }\r\n\r\n\tprivate void populateThemes(Theme selectedTheme) {\r\n        ignoreComboChanges = true;\r\n\r\n        themeComboBox.removeAllItems();\r\n\r\n\t\t// Creates new theme instances for all but the currentTheme (current as the currently active one - not\r\n\t\t// the currently selected one!) so we might need to find the new instance of our previously selected theme.\r\n        Iterator<Theme> themes = ThemeManager.availableThemes();\r\n\t\tTheme selectedThemeAvailableInstance = null;\r\n\r\n        while (themes.hasNext()) {\r\n\t\t\tfinal Theme availableTheme = themes.next();\r\n\t\t\tthemeComboBox.addItem(availableTheme);\r\n\t\t\tif (availableTheme.equals(selectedTheme)) {\r\n\t\t\t\tselectedThemeAvailableInstance = availableTheme;\r\n\t\t\t}\r\n        }\r\n\r\n        ignoreComboChanges = false;\r\n\r\n\t\tif (selectedThemeAvailableInstance != null) {\r\n\t\t\tthemeComboBox.setSelectedItem(selectedThemeAvailableInstance);\r\n\t\t} else {\r\n\t\t\tLOGGER.warn(\"selected theme not available anymore\");\r\n\t\t\tthemeComboBox.setSelectedIndex(0);\r\n\t\t}\r\n    }\r\n\r\n    /**\r\n     * Creates the system icons panel.\r\n     * @return the system icons panel.\r\n     */\r\n    private JPanel createSystemIconsPanel() {\r\n        // 'Use system file icons' combo box\r\n        this.useSystemFileIconsComboBox = new PrefComboBox<>() {\r\n            public boolean hasChanged() {\r\n                String systemIconsPolicy = switch (useSystemFileIconsComboBox.getSelectedIndex()) {\r\n                    case 0 -> FileIcons.USE_SYSTEM_ICONS_NEVER;\r\n                    case 1 -> FileIcons.USE_SYSTEM_ICONS_APPLICATIONS;\r\n                    default -> FileIcons.USE_SYSTEM_ICONS_ALWAYS;\r\n                };\r\n                return !systemIconsPolicy.equals(getVariable(USE_SYSTEM_FILE_ICONS, systemIconsPolicy));\r\n            }\r\n        };\r\n        useSystemFileIconsComboBox.addItem(Translator.get(\"prefs_dialog.use_system_file_icons.never\"));\r\n        useSystemFileIconsComboBox.addItem(Translator.get(\"prefs_dialog.use_system_file_icons.applications\"));\r\n        useSystemFileIconsComboBox.addItem(Translator.get(\"prefs_dialog.use_system_file_icons.always\"));\r\n        String systemIconsPolicy = FileIcons.getSystemIconsPolicy();\r\n\r\n        useSystemFileIconsComboBox.setSelectedIndex(FileIcons.USE_SYSTEM_ICONS_ALWAYS.equals(systemIconsPolicy) ? 2 : FileIcons.USE_SYSTEM_ICONS_APPLICATIONS.equals(systemIconsPolicy) ? 1 : 0);\r\n        JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));\r\n        panel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"prefs_dialog.use_system_file_icons\")));\r\n        panel.add(useSystemFileIconsComboBox);\r\n\r\n        return panel;\r\n    }\r\n\r\n    /**\r\n     * Creates a combo box that allows to choose a size for a certain type of icon. The returned combo box is filled\r\n     * with allowed choices, and the current configuration value is selected.\r\n     *\r\n     * @param defaultValue the default value for the icon scale factor if the configuration variable has no value\r\n     * @return a combo box that allows to choose a size for a certain type of icon\r\n     */\r\n    private PrefComboBox<String> createIconSizeCombo(final TcPreference preference, float defaultValue) {\r\n    \tPrefComboBox<String> iconSizeCombo = new PrefComboBox<>(ICON_SIZES) {\r\n            public boolean hasChanged() {\r\n                return !String.valueOf(ICON_SCALE_FACTORS[getSelectedIndex()]).equals(getVariable(preference));\r\n            }\r\n        };\r\n        float scaleFactor = getVariable(preference, defaultValue);\r\n        int index = 0;\r\n        for (int i = 0; i < ICON_SCALE_FACTORS.length; i++) {\r\n            if (scaleFactor == ICON_SCALE_FACTORS[i]) {\r\n                index = i;\r\n                break;\r\n            }\r\n        }\r\n        iconSizeCombo.setSelectedIndex(index);\r\n\r\n        return iconSizeCombo;\r\n    }\r\n\r\n\r\n\r\n    @Override\r\n    protected void commit() {\r\n        final TcPreferencesAPI pref = TcConfigurations.getPreferences();\r\n\r\n        // Look and Feel\r\n        if (pref.setVariable(LOOK_AND_FEEL, lookAndFeels[lookAndFeelComboBox.getSelectedIndex()].getClassName())) {\r\n            resetLookAndFeelButtons();\r\n            SwingUtilities.updateComponentTreeUI(parent);\r\n        }\r\n\r\n        if (brushedMetalCheckBox != null) {\r\n            pref.setVariable(USE_BRUSHED_METAL,  brushedMetalCheckBox.isSelected());\r\n        }\r\n\r\n        // Set ToolBar's icon size\r\n        float scaleFactor = ICON_SCALE_FACTORS[toolbarIconsSizeComboBox.getSelectedIndex()];\r\n        pref.setVariable(TOOLBAR_ICON_SCALE, scaleFactor);\r\n\r\n        // Set CommandBar's icon size\r\n        scaleFactor = ICON_SCALE_FACTORS[commandBarIconsSizeComboBox.getSelectedIndex()];\r\n        pref.setVariable(COMMAND_BAR_ICON_SCALE , scaleFactor);\r\n\r\n        // Set file icon size\r\n        scaleFactor = ICON_SCALE_FACTORS[fileIconsSizeComboBox.getSelectedIndex()];\r\n        // Set scale factor in FileIcons first so that it has the new value when ConfigurationListener instances call it\r\n        FileIcons.setScaleFactor(scaleFactor);\r\n        FileIconsCache.getInstance().clear();\r\n        pref.setVariable(TABLE_ICON_SCALE , scaleFactor);\r\n\r\n        // Sets the current theme.\r\n        if (!ThemeManager.isCurrentTheme(themeComboBox.getSelectedItem())) {\r\n            ThemeManager.setCurrentTheme(themeComboBox.getSelectedItem());\r\n            resetThemeButtons(themeComboBox.getSelectedItem());\r\n            themeComboBox.repaint();\r\n        }\r\n\r\n        // Sets the current syntax theme.\r\n        final String syntaxThemeName = syntaxThemeComboBox.getSelectedItem();\r\n        if (!ThemeManager.getCurrentSyntaxThemeName().equalsIgnoreCase(syntaxThemeName)) {\r\n            ThemeManager.setCurrentSyntaxTheme(syntaxThemeName);\r\n            syntaxThemeComboBox.repaint();\r\n        }\r\n\r\n        // Set system icons policy\r\n        int comboIndex = useSystemFileIconsComboBox.getSelectedIndex();\r\n        String systemIconsPolicy = comboIndex == 0 ? FileIcons.USE_SYSTEM_ICONS_NEVER : comboIndex == 1 ? FileIcons.USE_SYSTEM_ICONS_APPLICATIONS : FileIcons.USE_SYSTEM_ICONS_ALWAYS;\r\n        FileIcons.setSystemIconsPolicy(systemIconsPolicy);\r\n        pref.setVariable(USE_SYSTEM_FILE_ICONS, systemIconsPolicy);\r\n    }\r\n\r\n\r\n\r\n    // - Look and feel actions --------------------------------------------------\r\n    // --------------------------------------------------------------------------\r\n    /**\r\n     * Initializes the list of custom look&feels.\r\n     */\r\n    private void initializeCustomLookAndFeels() {\r\n        customLookAndFeels = getListVariable(CUSTOM_LOOK_AND_FEELS, TcPreferences.CUSTOM_LOOK_AND_FEELS_SEPARATOR);\r\n    }\r\n\r\n    /**\r\n     * Initializes the list of available look&feels.\r\n     */\r\n    private void initializeAvailableLookAndFeels() {\r\n        // Loads all available look and feels.\r\n        lookAndFeels = UIManager.getInstalledLookAndFeels();\r\n\r\n        // Sorts them.\r\n        Arrays.sort(lookAndFeels, new Comparator<>() {\r\n            public int compare(UIManager.LookAndFeelInfo a, UIManager.LookAndFeelInfo b) {\r\n                return a.getName().compareTo(b.getName());\r\n            }\r\n\r\n            public boolean equals(Object a) {\r\n                return false;\r\n            }\r\n        });\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the specified class name is that of a custom look and feel.\r\n     * @return <code>true</code> if the specified class name is that of a custom look and feel, <code>false</code> otherwise.\r\n     */\r\n    private boolean isCustomLookAndFeel(String className) {\r\n        return customLookAndFeels != null && customLookAndFeels.contains(className);\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the specified look and feel is modifiable.\r\n     * <p>\r\n     * To be modifiable, a look and feel must meet all of the following conditions:\r\n     * <ul>\r\n     *   <li>It must be a custom look and feel.</li>\r\n     *   <li>It cannot be the application's current look and feel.</li>\r\n     * </ul>\r\n     *\r\n     * @return <code>true</code> if the specified look and feel is modifiable, <code>false</code> otherwise.\r\n     */\r\n    private boolean isLookAndFeelModifiable(UIManager.LookAndFeelInfo laf) {\r\n        return isCustomLookAndFeel(laf.getClassName()) && !laf.getClassName().equals(UIManager.getLookAndFeel().getClass().getName());\r\n    }\r\n\r\n    /**\r\n     * Resets the enabled status of the various look and feel buttons depending on the current selection.\r\n     */\r\n    private void resetLookAndFeelButtons() {\r\n        // If the dial is animated, we're currently loading look&feels and should ignore this call.\r\n        if (dial == null || !dial.isAnimated()) {\r\n            int selectedIndex = lookAndFeelComboBox.getSelectedIndex();\r\n            if (selectedIndex >= 0) {\r\n                deleteLookAndFeelButton.setEnabled(isLookAndFeelModifiable(lookAndFeels[selectedIndex]));\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Uninstalls the specified look and feel.\r\n     * @param selection look and feel to uninstall.\r\n     */\r\n    private void uninstallLookAndFeel(UIManager.LookAndFeelInfo selection) {\r\n        UIManager.LookAndFeelInfo[] buffer;      // New array of installed look and feels.\r\n        int                         bufferIndex; // Current index in buffer.\r\n\r\n        // Copies the content of lookAndFeels into buffer, skipping over the look and feel to uninstall.\r\n        buffer      = new UIManager.LookAndFeelInfo[lookAndFeels.length - 1];\r\n        bufferIndex = 0;\r\n        for (UIManager.LookAndFeelInfo lookAndFeel : lookAndFeels) {\r\n            if (!selection.getClassName().equals(lookAndFeel.getClassName())) {\r\n                buffer[bufferIndex] = lookAndFeel;\r\n                bufferIndex++;\r\n            }\r\n        }\r\n\r\n        // Resets the list of installed look and feels.\r\n        UIManager.setInstalledLookAndFeels(lookAndFeels = buffer);\r\n    }\r\n\r\n    /**\r\n     * Deletes the specified look and feel from the list of custom look and feels.\r\n     * @param selection currently selection look and feel.\r\n     */\r\n    private void deleteCustomLookAndFeel(UIManager.LookAndFeelInfo selection) {\r\n        if (customLookAndFeels != null) {\r\n            if (customLookAndFeels.remove(selection.getClassName())) {\r\n                TcConfigurations.getPreferences().setVariable(CUSTOM_LOOK_AND_FEELS, customLookAndFeels, TcPreferences.CUSTOM_LOOK_AND_FEELS_SEPARATOR);\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Deletes the currently selected look and feel.\r\n     * <p>\r\n     * After receiving user confirmation, this method will:\r\n     * <ul>\r\n     *   <li>Remove the look and feel from the combobox.</li>\r\n     *   <li>Remove the look and feel from <code>UIManager</code>'s list of installed look and feels.</li>\r\n     *   <li>Remove the look and feel from the list of custom look and feels.</li>\r\n     * </ul>\r\n     */\r\n    private void deleteSelectedLookAndFeel() {\r\n        UIManager.LookAndFeelInfo selection; // Currently selected look and feel.\r\n\r\n        selection = lookAndFeels[lookAndFeelComboBox.getSelectedIndex()];\r\n\r\n        // Asks the user whether he's sure he wants to delete the selected look and feel.\r\n        if (new QuestionDialog(parent, null, Translator.get(\"prefs_dialog.delete_look_and_feel\", selection.getName()), parent,\r\n                              new String[] {Translator.get(\"yes\"), Translator.get(\"no\")},\r\n                              new int[]  {YES_ACTION, NO_ACTION},\r\n                              0).getActionValue() != YES_ACTION)\r\n            return;\r\n\r\n        // Removes the selected look and feel from the combo box.\r\n        lookAndFeelComboBox.removeItem(selection.getName());\r\n\r\n        // Removes the selected look and feel from the list of installed look and feels.\r\n        uninstallLookAndFeel(selection);\r\n\r\n        // Removes the selected look and feel from the list of custom look and feels.\r\n        deleteCustomLookAndFeel(selection);\r\n    }\r\n\r\n    /**\r\n     * Updates the different look&feel related UI widgets depending on whether they are busy or not.\r\n     * @param loading whether look&feels are loading.\r\n     */\r\n    private void setLookAndFeelsLoading(boolean loading) {\r\n        // Starts / stops the loading animation.\r\n        dial.setAnimated(loading);\r\n\r\n        // Disables / enables the import button and the combo box.\r\n        importLookAndFeelButton.setEnabled(!loading);\r\n        deleteLookAndFeelButton.setEnabled(!loading);\r\n        lookAndFeelComboBox.setEnabled(!loading);\r\n\r\n        // A special case must be made for the delete button\r\n        // as it might not need to be re-enabled.\r\n        if (loading) {\r\n            deleteLookAndFeelButton.setEnabled(false);\r\n        } else {\r\n            resetLookAndFeelButtons();\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Tries to import the specified library in the extensions folder.\r\n     * <p>\r\n     * If there is already a file with the same name in the extensions folder,\r\n     * this method will ask the user for confirmation before overwriting it.\r\n     *\r\n     * @param  library     library to import in the extensions folder.\r\n     * @return             <code>true</code> if the library was imported, <code>false</code> if the user cancelled the operation.\r\n     * @throws IOException if an I/O error occurred while importing the library\r\n     */\r\n    private boolean importLookAndFeelLibrary(AbstractFile library) throws IOException {\r\n        // Tries to import the file, but if a version of it is already present in the extensions folder,\r\n        // asks the user for confirmation.\r\n\r\n        AbstractFile destFile = ExtensionManager.getExtensionsFile(library.getName());\r\n\r\n        int collision = FileCollisionChecker.checkForCollision(library, destFile);\r\n        if (collision != FileCollisionChecker.NO_COLLISION) {\r\n            // Do not offer the multiple files mode options such as 'skip' and 'apply to all'\r\n            int action = new FileCollisionDialog(parent, parent, collision, library, destFile, false, false).getActionValue();\r\n\r\n            // User chose to overwrite the file\r\n            if (action == FileCollisionDialog.OVERWRITE_ACTION) {\r\n                // Simply continue and file will be overwritten\r\n            } else if (action==FileCollisionDialog.OVERWRITE_IF_OLDER_ACTION) {\r\n                // Overwrite if the source is more recent than the destination\r\n                if (library.getLastModifiedDate() <= destFile.getLastModifiedDate())\r\n                    return false;\r\n                // Simply continue and file will be overwritten\r\n            } else {\r\n                return false;       // User chose to cancel or closed the dialog\r\n            }\r\n        }\r\n\r\n        return ExtensionManager.importLibrary(library, true);\r\n    }\r\n\r\n    public void run() {\r\n        List<Class<?>> newLookAndFeels;\r\n\r\n        setLookAndFeelsLoading(true);\r\n        try {\r\n            // Identifies all the look&feels contained by the new library and adds them to the list of custom\r\n            // If no look&feel was found, notifies the user.\r\n            if ((newLookAndFeels = new ClassFinder().find(lookAndFeelLibrary, new LookAndFeelFilter())).isEmpty())\r\n                InformationDialog.showWarningDialog(this, Translator.get(\"prefs_dialog.no_look_and_feel\"));\r\n            else if (importLookAndFeelLibrary(lookAndFeelLibrary)) {\r\n                if (customLookAndFeels == null)\r\n                    customLookAndFeels = new Vector<>();\r\n\r\n                // Adds all new instances to the list of custom look&feels.\r\n                for (Class<?> newLookAndFeel : newLookAndFeels) {\r\n                    String currentName = newLookAndFeel.getName();\r\n                    if (!customLookAndFeels.contains(currentName)) {\r\n                        customLookAndFeels.add(currentName);\r\n                        try {\r\n                            WindowManager.installLookAndFeel(currentName);\r\n                        } catch (Throwable e) {\r\n                            e.printStackTrace();\r\n                        }\r\n                    }\r\n                }\r\n\r\n                if (customLookAndFeels.isEmpty()) {\r\n                    customLookAndFeels = null;\r\n                } else {\r\n                    TcConfigurations.getPreferences().setVariable(CUSTOM_LOOK_AND_FEELS, customLookAndFeels, TcPreferences.CUSTOM_LOOK_AND_FEELS_SEPARATOR);\r\n                }\r\n\r\n                populateLookAndFeels();\r\n            }\r\n        } catch(Exception e) {\r\n        \tLOGGER.debug(\"Exception caught\", e);\r\n            InformationDialog.showErrorDialog(this);\r\n        }\r\n        setLookAndFeelsLoading(false);\r\n    }\r\n\r\n    private void importLookAndFeel() {\r\n        // Initializes the file chooser.\r\n        JFileChooser chooser = createFileChooser();\r\n        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);\r\n        chooser.addChoosableFileFilter(new ExtensionFileFilter(\"jar\", Translator.get(\"prefs_dialog.jar_file\")));\r\n        chooser.setDialogTitle(Translator.get(\"prefs_dialog.import_look_and_feel\"));\r\n        chooser.setDialogType(JFileChooser.OPEN_DIALOG);\r\n\r\n        if (chooser.showDialog(parent, Translator.get(\"prefs_dialog.import\")) == JFileChooser.APPROVE_OPTION) {\r\n            AbstractFile file = FileFactory.getFile(chooser.getSelectedFile().getAbsolutePath());\r\n            if (file == null) {\r\n                return;\r\n            }\r\n            lastSelectedFolder = file.getParent();\r\n\r\n            // Makes sure the file actually exists - JFileChooser apparently doesn't enforce that properly in all look&feels.\r\n            if (!file.exists()) {\r\n                InformationDialog.showErrorDialog(this, Translator.get(\"this_file_does_not_exist\", file.getName()));\r\n                return;\r\n            }\r\n\r\n            // Imports the JAR in a separate thread.\r\n            lookAndFeelLibrary = file;\r\n            new Thread(this).start();\r\n        }\r\n    }\r\n\r\n\r\n\r\n    // - Theme actions ----------------------------------------------------------\r\n    // --------------------------------------------------------------------------\r\n    private void setTypeLabel(Theme theme) {\r\n        String label;\r\n\r\n        if (theme.getType() == Theme.Type.USER) {\r\n            label = Translator.get(\"theme.custom\");\r\n        } else if(theme.getType() == Theme.Type.PREDEFINED) {\r\n            label = Translator.get(\"theme.built_in\");\r\n        } else {\r\n            label = Translator.get(\"theme.add_on\");\r\n        }\r\n\r\n        lblType.setText(Translator.get(\"prefs_dialog.theme_type\", label));\r\n    }\r\n\r\n    private void resetThemeButtons(Theme theme) {\r\n        if (ignoreComboChanges) {\r\n            return;\r\n        }\r\n\r\n        setTypeLabel(theme);\r\n\r\n        if (theme.getType() != Theme.Type.CUSTOM) {\r\n            btnRenameTheme.setEnabled(false);\r\n            btnDeleteTheme.setEnabled(false);\r\n        } else {\r\n            btnRenameTheme.setEnabled(true);\r\n            btnDeleteTheme.setEnabled(!ThemeManager.isCurrentTheme(theme));\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Renames the specified theme.\r\n     * @param theme theme to rename.\r\n     */\r\n    private void renameTheme(Theme theme) {\r\n        ThemeNameDialog dialog = new ThemeNameDialog(parent, theme.getName());\r\n\r\n        if (dialog.wasValidated()) {\r\n            // If the rename operation was a success, makes sure the theme is located at its proper position.\r\n            try {\r\n                ThemeManager.renameCustomTheme(theme, dialog.getText());\r\n                themeComboBox.removeItem(theme);\r\n                insertTheme(theme);\r\n            } catch(Exception e) {\r\n                // Otherwise, notifies the user.\r\n                InformationDialog.showErrorDialog(this, Translator.get(\"prefs_dialog.rename_failed\", theme.getName()));\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Deletes the specified theme.\r\n     * @param theme theme to delete.\r\n     */\r\n    private void deleteTheme(Theme theme) {\r\n        // Asks the user whether he's sure he wants to delete the selected theme.\r\n        if (new QuestionDialog(parent, null, Translator.get(\"prefs_dialog.delete_theme\", theme.getName()), parent,\r\n                              new String[] {Translator.get(\"yes\"), Translator.get(\"no\")},\r\n                              new int[]  {YES_ACTION, NO_ACTION},\r\n                              0).getActionValue() != YES_ACTION)\r\n            return;\r\n\r\n        // Deletes the selected theme and removes it from the list.\r\n        try {\r\n            ThemeManager.deleteCustomTheme(theme.getName());\r\n            themeComboBox.removeItem(theme);\r\n        } catch(Exception e) {\r\n            InformationDialog.showErrorDialog(this);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Starts the theme editor on the specified theme.\r\n     * @param theme to edit.\r\n     */\r\n    private void editTheme(Theme theme) {\r\n        // If the edited theme was modified, we must re-populate the list.\r\n        final Theme modifiedTheme = new ThemeEditorDialog(parent, theme).editTheme();\r\n\t\tif (modifiedTheme != null) {\r\n\t\t\tpopulateThemes(modifiedTheme);\r\n\t\t\tparent.setCommitButtonsEnabled(true);\r\n\t\t}\r\n    }\r\n\r\n    /**\r\n     * Creates a file chooser initialized on the last selected folder.\r\n     */\r\n    private JFileChooser createFileChooser() {\r\n        if (lastSelectedFolder == null) {\r\n            return new JFileChooser();\r\n        }\r\n        return new JFileChooser((java.io.File)lastSelectedFolder.getUnderlyingFileObject());\r\n    }\r\n\r\n    private void insertTheme(Theme theme) {\r\n        int i;\r\n\r\n        int count = themeComboBox.getItemCount();\r\n        for (i = 0; i < count; i++) {\r\n            if((themeComboBox.getItemAt(i)).getName().compareTo(theme.getName()) >= 0) {\r\n                themeComboBox.insertItemAt(theme, i);\r\n                break;\r\n            }\r\n        }\r\n        if (i == count) {\r\n            themeComboBox.addItem(theme);\r\n        }\r\n        themeComboBox.setSelectedItem(theme);\r\n    }\r\n\r\n    /**\r\n     * Imports a new theme in muCommander.\r\n     */\r\n    private void importTheme() {\r\n        // Initializes the file chooser.\r\n        JFileChooser chooser = createFileChooser();\r\n        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);\r\n        chooser.addChoosableFileFilter(new ExtensionFileFilter(\"xml\", Translator.get(\"prefs_dialog.xml_file\")));\r\n        chooser.setDialogTitle(Translator.get(\"prefs_dialog.import_theme\"));\r\n        chooser.setDialogType(JFileChooser.OPEN_DIALOG);\r\n\r\n        if (chooser.showDialog(parent, Translator.get(\"prefs_dialog.import\")) == JFileChooser.APPROVE_OPTION) {\r\n            // Makes sure the file actually exists - JFileChooser apparently doesn't enforce that properly in all look&feels.\r\n            AbstractFile file = FileFactory.getFile(chooser.getSelectedFile().getAbsolutePath());\r\n            if (file == null) {\r\n                return;\r\n            }\r\n            lastSelectedFolder = file.getParent();\r\n            if (!file.exists()) {\r\n                InformationDialog.showErrorDialog(this, Translator.get(\"this_file_does_not_exist\", file.getName()));\r\n                return;\r\n            }\r\n\r\n            // Imports the theme and makes sure it appears in the combobox.\r\n            try {\r\n                insertTheme(ThemeManager.importTheme((java.io.File)file.getUnderlyingFileObject()));\r\n            } catch(Exception ex) { // Notifies the user that something went wrong.\r\n                InformationDialog.showErrorDialog(this, Translator.get(\"prefs_dialog.error_in_import\", file.getName()));\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Exports the specified theme.\r\n     * @param theme theme to export.\r\n     */\r\n    private void exportTheme(Theme theme) {\r\n        JFileChooser chooser = createFileChooser();\r\n        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);\r\n        chooser.addChoosableFileFilter(new ExtensionFileFilter(\"xml\", Translator.get(\"prefs_dialog.xml_file\")));\r\n\r\n        chooser.setDialogTitle(Translator.get(\"prefs_dialog.export_theme\", theme.getName()));\r\n        if (chooser.showDialog(parent, Translator.get(\"prefs_dialog.export\")) == JFileChooser.APPROVE_OPTION) {\r\n\r\n            AbstractFile file = FileFactory.getFile(chooser.getSelectedFile().getAbsolutePath());\r\n            lastSelectedFolder = file.getParent();\r\n\r\n            // Makes sure the file's extension is .xml.\r\n            try {\r\n                if (!\"xml\".equalsIgnoreCase(file.getExtension()))    // Note: getExtension() may return null if no extension\r\n                    file = lastSelectedFolder.getDirectChild(file.getName()+\".xml\");\r\n\r\n                int collision = FileCollisionChecker.checkForCollision(null, file);\r\n                if (collision != FileCollisionChecker.NO_COLLISION) {\r\n                    // Do not offer the multiple files mode options such as 'skip' and 'apply to all'\r\n                    int action = new FileCollisionDialog(parent, parent, collision, null, file, false, false).getActionValue();\r\n\r\n                    // User chose to overwrite the file\r\n                    if (action == FileCollisionDialog.OVERWRITE_ACTION) {\r\n                        // Simply continue and file will be overwritten\r\n                    } else { // User chose to cancel or closed the dialog\r\n                        return;\r\n                    }\r\n                }\r\n\r\n                // Exports the theme.\r\n                ThemeManager.exportTheme(theme, (java.io.File)file.getUnderlyingFileObject());\r\n\r\n                // If it was exported to the custom themes folder, reload the theme combobox to reflect the\r\n                // changes.\r\n                if (lastSelectedFolder.equals(ThemeManager.getCustomThemesFolder())) {\r\n                    populateThemes(theme);\r\n            }\r\n            } catch(Exception exception) { // Notifies users of errors.\r\n                InformationDialog.showErrorDialog(this, Translator.get(\"write_error\"), Translator.get(\"cannot_write_file\", file.getName()));\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Duplicates the specified theme.\r\n     */\r\n    private void duplicateTheme(Theme theme) {\r\n        try {\r\n            insertTheme(ThemeManager.duplicateTheme(theme));\r\n        } catch(Exception e) {\r\n            InformationDialog.showErrorDialog(this);\r\n        }\r\n    }\r\n\r\n\r\n\r\n\r\n    // - Listener code ----------------------------------------------------------\r\n    // --------------------------------------------------------------------------\r\n    /**\r\n     * Called when a button is pressed.\r\n     */\r\n    public void actionPerformed(ActionEvent e) {\r\n        Theme theme = themeComboBox.getSelectedItem();\r\n\r\n        // Theme combobox selection changed.\r\n        if(e.getSource() == themeComboBox)\r\n            resetThemeButtons(theme);\r\n\r\n        // Look and feel combobox selection changed.\r\n        else if(e.getSource() == lookAndFeelComboBox)\r\n            resetLookAndFeelButtons();\r\n\r\n        // Delete look and feel button has been pressed.\r\n        else if(e.getSource() == deleteLookAndFeelButton)\r\n            deleteSelectedLookAndFeel();\r\n\r\n        // Import look and feel button has been pressed.\r\n        else if(e.getSource() == importLookAndFeelButton)\r\n            importLookAndFeel();\r\n\r\n        // Rename button was pressed.\r\n        else if(e.getSource() == btnRenameTheme)\r\n            renameTheme(theme);\r\n\r\n        // Delete button was pressed.\r\n        else if(e.getSource() == btnDeleteTheme)\r\n            deleteTheme(theme);\r\n\r\n        // Edit button was pressed.\r\n        else if(e.getSource() == btnEditTheme)\r\n            editTheme(theme);\r\n\r\n        // Import button was pressed.\r\n        else if(e.getSource() == btnImportTheme)\r\n            importTheme();\r\n\r\n        // Export button was pressed.\r\n        else if(e.getSource() == btnExportTheme)\r\n            exportTheme(theme);\r\n\r\n        // Export button was pressed.\r\n        else if(e.getSource() == btnDuplicate)\r\n            duplicateTheme(theme);\r\n    }\r\n\r\n\r\n    // - File IMAGE_FILTER ------------------------------------------------------------\r\n    // --------------------------------------------------------------------------\r\n    /**\r\n     * Filter used to only display XML files in the JFileChooser.\r\n     * @author Nicolas Rinaudo\r\n     */\r\n    private static class ExtensionFileFilter extends javax.swing.filechooser.FileFilter {\r\n        /** Extension to match. */\r\n        private final String extension;\r\n        /** Filter's description. */\r\n        private final String description;\r\n\r\n        /**\r\n         * Creates a new extension file IMAGE_FILTER that will match files with the specified extension.\r\n         * @param extension extension to match.\r\n         */\r\n        ExtensionFileFilter(String extension, String description) {\r\n            this.extension   = extension;\r\n            this.description = description;\r\n        }\r\n\r\n        /**\r\n         * Returns <code>true</code> if the specified file should be displayed in the chooser.\r\n         */\r\n        @Override\r\n        public boolean accept(File file) {\r\n            // Directories are always displayed.\r\n            if (file.isDirectory()) {\r\n                return true;\r\n            }\r\n\r\n            // If the file has an extension, and it matches .xml, return true.\r\n            // Otherwise, return false.\r\n            String ext = AbstractFile.getExtension(file.getName());\r\n            return extension.equalsIgnoreCase(ext);\r\n        }\r\n\r\n        @Override\r\n        public String getDescription() {\r\n            return description;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/general/FoldersPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.pref.general;\n\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.awt.event.FocusEvent;\nimport java.awt.event.FocusListener;\nimport java.awt.event.ItemEvent;\nimport java.awt.event.ItemListener;\nimport java.awt.event.KeyEvent;\nimport java.awt.event.KeyListener;\nimport java.io.File;\n\nimport javax.swing.*;\nimport javax.swing.plaf.basic.BasicTextFieldUI;\nimport javax.swing.text.JTextComponent;\n\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.conf.TcPreferencesAPI;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\nimport com.mucommander.ui.dialog.pref.PreferencesPanel;\nimport com.mucommander.ui.dialog.pref.component.PrefCheckBox;\nimport com.mucommander.ui.dialog.pref.component.PrefComboBox;\nimport com.mucommander.ui.dialog.pref.component.PrefFilePathField;\nimport com.mucommander.ui.dialog.pref.component.PrefRadioButton;\nimport com.mucommander.ui.layout.SpringUtilities;\nimport com.mucommander.ui.layout.XBoxPanel;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.main.WindowManager;\n\nimport static com.mucommander.conf.TcPreference.*;\n\n\n/**\n * 'Folders' preferences panel.\n *\n * @author Maxence Bernard, Mariuz Jakubowski\n */\nclass FoldersPanel extends PreferencesPanel implements ItemListener, KeyListener, ActionListener {\n\n    // Startup folders\n    private PrefRadioButton lastFoldersRadioButton;\n    private PrefRadioButton customFoldersRadioButton;\n    \n    private PrefFilePathFieldWithDefaultValue leftCustomFolderTextField;\n    private JButton leftCustomFolderButton;\n\t\n    private PrefFilePathFieldWithDefaultValue rightCustomFolderTextField;\n\tprivate JButton rightCustomFolderButton;\n\n\tprivate QuickSearchTimeoutCombobox comboQuickSearchTimeout;\n\n    /** Show hidden files? */\n    private PrefCheckBox cbShowHiddenFiles;\n\n    /** Show Mac OS X .DS_Store? */\n    private PrefCheckBox cbShowDSStoreFiles;\n\n    /** Show system folders ? */\n    private PrefCheckBox cbShowSystemFolders;\n\n    /** Display compact file size ? */\n    private PrefCheckBox cbCompactSize;\n\t\n    /** Follow symlinks when changing directory ? */\n    private PrefCheckBox cbFollowSymlinks;\n    \n    /** Always show single tab's header ? */\n    private PrefCheckBox cbShowTabHeader;\n\n    /** Show quick search matches first in file panels */\n    private PrefCheckBox cbShowQuickSearchMatchesFirst;\n\n    /** Calculate folder size on mark action */\n    private PrefCheckBox cbCalculateFolderSizeOnMark;\n\n    FoldersPanel(PreferencesDialog parent) {\n        super(parent, Translator.get(\"prefs_dialog.folders_tab\"));\n\n        setLayout(new BorderLayout());\n\n        // Startup folders panel\n        YBoxPanel startupFolderPanel = new YBoxPanel();\n        startupFolderPanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"prefs_dialog.startup_folders\")));\n\t\t\n        // Last folders or custom folders selections\n        lastFoldersRadioButton = new PrefRadioButton(Translator.get(\"prefs_dialog.last_folder\")) {\n\t\t\tpublic boolean hasChanged() {\n\t\t\t\treturn !(isSelected() ? \n\t\t\t\t\t\tTcPreferences.STARTUP_FOLDERS_LAST\t: TcPreferences.STARTUP_FOLDERS_CUSTOM).equals(\n                        getVariable(STARTUP_FOLDERS));\n\t\t\t}\n\t\t};\n\t\tcustomFoldersRadioButton = new PrefRadioButton(Translator.get(\"prefs_dialog.custom_folder\")) {\n\t\t\tpublic boolean hasChanged() {\n\t\t\t\treturn !(isSelected() ? \n\t\t\t\t\t\tTcPreferences.STARTUP_FOLDERS_CUSTOM : TcPreferences.STARTUP_FOLDERS_LAST).equals(\n                        getVariable(STARTUP_FOLDERS));\n\t\t\t}\n        };\n        startupFolderPanel.add(lastFoldersRadioButton);\n        startupFolderPanel.addSpace(5);\n        startupFolderPanel.add(customFoldersRadioButton);\n\n        ButtonGroup buttonGroup = new ButtonGroup();\n        buttonGroup.add(lastFoldersRadioButton);\n        buttonGroup.add(customFoldersRadioButton);\n\n        customFoldersRadioButton.addItemListener(this);\n        \n        // Custom folders specification\n        JLabel leftFolderLabel = new JLabel(Translator.get(\"prefs_dialog.left_folder\"));\n        leftFolderLabel.setAlignmentX(LEFT_ALIGNMENT);\n\n        JLabel rightFolderLabel = new JLabel(Translator.get(\"prefs_dialog.right_folder\"));\n        rightFolderLabel.setAlignmentX(LEFT_ALIGNMENT);\n\n        // Panel that contains the text field and button for specifying custom left folder\n        XBoxPanel leftCustomFolderSpecifyingPanel = new XBoxPanel(5);\n        leftCustomFolderSpecifyingPanel.setAlignmentX(LEFT_ALIGNMENT);\n        \n        // create a path field with auto-completion capabilities\n        leftCustomFolderTextField = new PrefFilePathFieldWithDefaultValue(true);\n        leftCustomFolderTextField.addKeyListener(this);\n        leftCustomFolderSpecifyingPanel.add(leftCustomFolderTextField);\n\n        leftCustomFolderButton = new JButton(\"...\");\n        leftCustomFolderButton.addActionListener(this);\n        leftCustomFolderSpecifyingPanel.add(leftCustomFolderButton);\n\n        // Panel that contains the text field and button for specifying custom right folder\n        XBoxPanel rightCustomFolderSpecifyingPanel = new XBoxPanel(5);\n        rightCustomFolderSpecifyingPanel.setAlignmentX(LEFT_ALIGNMENT);\n\n        // create a path field with auto-completion capabilities\n        rightCustomFolderTextField = new PrefFilePathFieldWithDefaultValue(false);\n        rightCustomFolderTextField.addKeyListener(this);\n        rightCustomFolderSpecifyingPanel.add(rightCustomFolderTextField);\n\n        rightCustomFolderButton = new JButton(\"...\");\n        rightCustomFolderButton.addActionListener(this);\n        rightCustomFolderSpecifyingPanel.add(rightCustomFolderButton);\n        \n        JPanel container = new JPanel(new SpringLayout());\n        container.add(leftFolderLabel);\n        container.add(leftCustomFolderSpecifyingPanel);\n        container.add(rightFolderLabel);\n        container.add(rightCustomFolderSpecifyingPanel);\n        \n        //Lay out the panel.\n        SpringUtilities.makeCompactGrid(container,\n                                        2, 2,       // rows, cols\n                                        20, 6,      // initX, initY\n                                        6, 6);      // xPad, yPad\n        \n        startupFolderPanel.add(container);\n        \n        if (getVariable(STARTUP_FOLDERS, \"\").equals(TcPreferences.STARTUP_FOLDERS_LAST)) {\n            lastFoldersRadioButton.setSelected(true);\n            setCustomFolderComponentsEnabled(false);\n        } else {\n            customFoldersRadioButton.setSelected(true);\n        }\n        \n        // --------------------------------------------------------------------------------------------------------------\n\n        YBoxPanel northPanel = new YBoxPanel();\n        northPanel.add(startupFolderPanel);\n        northPanel.addSpace(5);\n\n        // ------- Quick search panel --------\n        JPanel pnlQuickSearch = new JPanel(new SpringLayout());\n        pnlQuickSearch.setBorder(BorderFactory.createTitledBorder(Translator.get(\"prefs_dialog.quick_search\")));\n        comboQuickSearchTimeout = new QuickSearchTimeoutCombobox();\n        JLabel lblQuickSearchTimeout = new JLabel(Translator.get(\"prefs_dialog.quick_search_timeout\"));\n        lblQuickSearchTimeout.setAlignmentX(LEFT_ALIGNMENT);\n\n        pnlQuickSearch.add(lblQuickSearchTimeout);\n        pnlQuickSearch.add(comboQuickSearchTimeout);\n\n        cbShowQuickSearchMatchesFirst = new PrefCheckBox(Translator.get(\"prefs_dialog.show_quick_search_matches_first\"),\n                checkBox -> checkBox.isSelected() != getVariable(SHOW_QUICK_SEARCH_MATCHES_FIRST, TcPreferences.DEFAULT_SHOW_QUICK_SEARCH_MATCHES_FIRST));\n        cbShowQuickSearchMatchesFirst.setSelected(getVariable(SHOW_QUICK_SEARCH_MATCHES_FIRST, TcPreferences.DEFAULT_SHOW_QUICK_SEARCH_MATCHES_FIRST));\n        pnlQuickSearch.add(cbShowQuickSearchMatchesFirst);\n        pnlQuickSearch.add(Box.createHorizontalGlue());\n\n        SpringUtilities.makeCompactGrid(pnlQuickSearch,\n                2, 2,       // rows, cols\n                6, 6,      // initX, initY\n                6, 6);      // xPad, yPad\n\n        northPanel.add(pnlQuickSearch);\n        northPanel.addSpace(10);\n\n        // ----- checkboxes ----\n\n        cbShowHiddenFiles = new PrefCheckBox(Translator.get(\"prefs_dialog.show_hidden_files\"),\n                checkBox -> checkBox.isSelected() != getVariable(SHOW_HIDDEN_FILES, TcPreferences.DEFAULT_SHOW_HIDDEN_FILES));\n        cbShowHiddenFiles.setSelected(getVariable(SHOW_HIDDEN_FILES, TcPreferences.DEFAULT_SHOW_HIDDEN_FILES));\n        northPanel.add(cbShowHiddenFiles);\n\n        // Mac OS X-only options\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            // Monitor cbShowHiddenFiles state to disable 'show .DS_Store files' option\n            // when 'Show hidden files' is disabled, as .DS_Store files are hidden files\n            cbShowHiddenFiles.addItemListener(this);\n\n            cbShowDSStoreFiles = new PrefCheckBox(Translator.get(\"prefs_dialog.show_ds_store_files\"),\n                    checkBox -> checkBox.isSelected() != getVariable(SHOW_DS_STORE_FILES, TcPreferences.DEFAULT_SHOW_DS_STORE_FILES));\n\n            cbShowDSStoreFiles.setSelected(getVariable(SHOW_DS_STORE_FILES, TcPreferences.DEFAULT_SHOW_DS_STORE_FILES));\n            cbShowDSStoreFiles.setEnabled(cbShowHiddenFiles.isSelected());\n            // Shift the check box to the right to indicate that it is a sub-option\n            northPanel.add(cbShowDSStoreFiles, 20);\n        }\n\n        if (OsFamily.MAC_OS_X.isCurrent() || OsFamily.WINDOWS.isCurrent()) {\n        \tcbShowSystemFolders = new PrefCheckBox(Translator.get(\"prefs_dialog.show_system_folders\"),\n                    checkBox -> checkBox.isSelected() != getVariable(SHOW_SYSTEM_FOLDERS, TcPreferences.DEFAULT_SHOW_SYSTEM_FOLDERS));\n        \tcbShowSystemFolders.setSelected(getVariable(SHOW_SYSTEM_FOLDERS, TcPreferences.DEFAULT_SHOW_SYSTEM_FOLDERS));\n        \tnorthPanel.add(cbShowSystemFolders);\n        }\n\n        cbCompactSize = new PrefCheckBox(Translator.get(\"prefs_dialog.compact_file_size\"),\n                checkBox -> checkBox.isSelected() != getVariable(DISPLAY_COMPACT_FILE_SIZE, TcPreferences.DEFAULT_DISPLAY_COMPACT_FILE_SIZE));\n\n        cbCompactSize.setSelected(getVariable(DISPLAY_COMPACT_FILE_SIZE, TcPreferences.DEFAULT_DISPLAY_COMPACT_FILE_SIZE));\n        northPanel.add(cbCompactSize);\n\n        cbFollowSymlinks = new PrefCheckBox(Translator.get(\"prefs_dialog.follow_symlinks_when_cd\"),\n                checkBox -> checkBox.isSelected() != getVariable(CD_FOLLOWS_SYMLINKS, TcPreferences.DEFAULT_CD_FOLLOWS_SYMLINKS));\n        cbFollowSymlinks.setSelected(getVariable(CD_FOLLOWS_SYMLINKS, TcPreferences.DEFAULT_CD_FOLLOWS_SYMLINKS));\n        northPanel.add(cbFollowSymlinks);\n\n        cbShowTabHeader = new PrefCheckBox(Translator.get(\"prefs_dialog.show_tab_header\"),\n                checkBox -> checkBox.isSelected() != getVariable(SHOW_TAB_HEADER, TcPreferences.DEFAULT_SHOW_TAB_HEADER));\n        cbShowTabHeader.setSelected(getVariable(SHOW_TAB_HEADER, TcPreferences.DEFAULT_SHOW_TAB_HEADER));\n        northPanel.add(cbShowTabHeader);\n\n\t\tcbCalculateFolderSizeOnMark = new PrefCheckBox(Translator.get(\"prefs_dialog.calculate_folder_size_on_mark\"),\n\t\t\tcheckBox -> checkBox.isSelected() != getVariable(CALCULATE_FOLDER_SIZE_ON_MARK, TcPreferences.DEFAULT_CALCULATE_FOLDER_SIZE_ON_MARK));\n\t\tcbCalculateFolderSizeOnMark.setSelected(getVariable(CALCULATE_FOLDER_SIZE_ON_MARK, TcPreferences.DEFAULT_CALCULATE_FOLDER_SIZE_ON_MARK));\n\t\tnorthPanel.add(cbCalculateFolderSizeOnMark);\n\n\n        add(northPanel, BorderLayout.NORTH);\n        \n        lastFoldersRadioButton.addDialogListener(parent);\n        customFoldersRadioButton.addDialogListener(parent);\n        rightCustomFolderTextField.addDialogListener(parent);\n        leftCustomFolderTextField.addDialogListener(parent);\n        cbShowHiddenFiles.addDialogListener(parent);\n        cbCompactSize.addDialogListener(parent);\n        cbFollowSymlinks.addDialogListener(parent);\n        cbShowTabHeader.addDialogListener(parent);\n        cbShowQuickSearchMatchesFirst.addDialogListener(parent);\n        cbCalculateFolderSizeOnMark.addDialogListener(parent);\n        comboQuickSearchTimeout.addDialogListener(parent);\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n        \tcbShowDSStoreFiles.addDialogListener(parent);\n        }\n        if (OsFamily.MAC_OS_X.isCurrent() || OsFamily.WINDOWS.isCurrent()) {\n        \tcbShowSystemFolders.addDialogListener(parent);\n        }\n    }\n\n    private void setCustomFolderComponentsEnabled(boolean enabled) {\n        leftCustomFolderTextField.setEnabled(enabled);\n        leftCustomFolderButton.setEnabled(enabled);\n        rightCustomFolderTextField.setEnabled(enabled);\n        rightCustomFolderButton.setEnabled(enabled);\n    }\n\n\n    /////////////////////////////////////\n    // PreferencesPanel implementation //\n    /////////////////////////////////////\n\n    @Override\n    protected void commit() {\n        final TcPreferencesAPI pref = TcConfigurations.getPreferences();\n    \tpref.setVariable(STARTUP_FOLDERS, lastFoldersRadioButton.isSelected() ? TcPreferences.STARTUP_FOLDERS_LAST : TcPreferences.STARTUP_FOLDERS_CUSTOM);\n\n    \tpref.setVariable(LEFT_CUSTOM_FOLDER, leftCustomFolderTextField.getFilePath());\n\n        pref.setVariable(RIGHT_CUSTOM_FOLDER, rightCustomFolderTextField.getFilePath());\n\n        pref.setVariable(DISPLAY_COMPACT_FILE_SIZE, cbCompactSize.isSelected());\n\n        pref.setVariable(CD_FOLLOWS_SYMLINKS, cbFollowSymlinks.isSelected());\n\n        pref.setVariable(SHOW_TAB_HEADER, cbShowTabHeader.isSelected());\n\n        pref.setVariable(SHOW_QUICK_SEARCH_MATCHES_FIRST, cbShowQuickSearchMatchesFirst.isSelected());\n\n\t\tpref.setVariable(CALCULATE_FOLDER_SIZE_ON_MARK, cbCalculateFolderSizeOnMark.isSelected());\n\n        pref.setVariable(QUICK_SEARCH_TIMEOUT, comboQuickSearchTimeout.getMilliseconds());\n\n        // If one of the show/hide file filters have changed, refresh current folders of current MainFrame\n        boolean refreshFolders = pref.setVariable(SHOW_HIDDEN_FILES, cbShowHiddenFiles.isSelected());\n        \n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            refreshFolders |= pref.setVariable(SHOW_DS_STORE_FILES, cbShowDSStoreFiles.isSelected());\n        }\n\n        if (OsFamily.MAC_OS_X.isCurrent() || OsFamily.WINDOWS.isCurrent()) {\n        \trefreshFolders |= pref.setVariable(SHOW_SYSTEM_FOLDERS, cbShowSystemFolders.isSelected());\n        }\n\n        if (refreshFolders) {\n            WindowManager.tryRefreshCurrentFolders();\n        }\n    }\n\n\n    /////////////////////////////////\n    // ItemListener implementation //\n    /////////////////////////////////\n\n    public void itemStateChanged(ItemEvent event) {\n        Object source = event.getSource();\n\n        // Disable 'show .DS_Store files' option when 'Show hidden files' is disabled, as .DS_Store files are hidden files\n        if (source == cbShowHiddenFiles) {\n            cbShowDSStoreFiles.setEnabled(cbShowHiddenFiles.isSelected());\n        } else if (source == customFoldersRadioButton) {\n            setCustomFolderComponentsEnabled(customFoldersRadioButton.isSelected());\n        }\n    }\n\n\n    ////////////////////////////////\n    // KeyListener implementation //\n    ////////////////////////////////\n\n    /**\n     * Catches key events to automatically select custom folder radio button if it was not already selected.\n     */\n    public void keyTyped(KeyEvent e) {\n        Object source = e.getSource();\n\t\t\n        if (source==leftCustomFolderTextField || source==rightCustomFolderTextField) {\n            if (!customFoldersRadioButton.isSelected()) {\n                customFoldersRadioButton.setSelected(true);\n            }\n        }\n    }\n\t\n    public void keyPressed(KeyEvent e) {\n    }\n\t\n    public void keyReleased(KeyEvent e) {\n    }\n\n\n    ///////////////////////////////////\n    // ActionListener implementation //\n    ///////////////////////////////////\n\n    /**\n     * Opens dialog for selecting starting folder.\n     */\n    public void actionPerformed(ActionEvent e) {\n        Object source = e.getSource();\n\n    \tJFileChooser chooser = new JFileChooser();\n    \tchooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);\n        chooser.setDialogTitle(Translator.get(\"choose_folder\"));\n        chooser.setDialogType(JFileChooser.OPEN_DIALOG);\n        if (chooser.showDialog(parent, Translator.get(\"choose\")) == JFileChooser.APPROVE_OPTION) {\n            File file = chooser.getSelectedFile();\n            if (source == leftCustomFolderButton) {\n                leftCustomFolderTextField.setText(file.getPath());\n                if (!customFoldersRadioButton.isSelected()) {\n                    customFoldersRadioButton.setSelected(true);\n                }\n            } else if (source == rightCustomFolderButton) {\n                rightCustomFolderTextField.setText(file.getPath());\n                if (!customFoldersRadioButton.isSelected()) {\n                    customFoldersRadioButton.setSelected(true);\n                }\n            }\n        }\n\t}\n    \n    public class PrefFilePathFieldWithDefaultValue extends PrefFilePathField {\n    \t\n    \tprivate boolean isLeft;\n    \tprivate final String HOME_FOLDER_PATH = System.getProperty(\"user.home\");\n    \t\n    \tPrefFilePathFieldWithDefaultValue(boolean isLeft) {\n    \t\tsuper(isLeft ? getVariable(LEFT_CUSTOM_FOLDER, \"\") : getVariable(RIGHT_CUSTOM_FOLDER, \"\"));\n    \t\tthis.isLeft = isLeft;\n    \t\t\n//    \t\tsetUI(new HintTextFieldUI(HOME_FOLDER_PATH, true));\n    \t}\n    \t\n\t\tpublic boolean hasChanged() {\n\t\t\treturn isLeft ? \n\t\t\t\t\t!getText().equals(getVariable(LEFT_CUSTOM_FOLDER)) :\n\t\t\t\t\t!getText().equals(getVariable(RIGHT_CUSTOM_FOLDER));\n\t\t}\n\t\t\n\t\tString getFilePath() {\n\t\t\tString text = super.getText();\n\t\t\t\n\t\t\treturn text.trim().isEmpty() ? HOME_FOLDER_PATH : text;\n\t\t}\n\n    \tprivate static class HintTextFieldUI extends BasicTextFieldUI implements FocusListener {\n\n    \t    private String hint;\n    \t    private boolean hideOnFocus;\n    \t    private Color color;\n\n    \t    public Color getColor() {\n    \t        return color;\n    \t    }\n\n    \t    public void setColor(Color color) {\n    \t        this.color = color;\n    \t        repaint();\n    \t    }\n\n    \t    private void repaint() {\n    \t        if (getComponent() != null) {\n    \t            getComponent().repaint();           \n    \t        }\n    \t    }\n\n    \t    public boolean isHideOnFocus() {\n    \t        return hideOnFocus;\n    \t    }\n\n    \t    public void setHideOnFocus(boolean hideOnFocus) {\n    \t        this.hideOnFocus = hideOnFocus;\n    \t        repaint();\n    \t    }\n\n    \t    public String getHint() {\n    \t        return hint;\n    \t    }\n\n    \t    public void setHint(String hint) {\n    \t        this.hint = hint;\n    \t        repaint();\n    \t    }\n    \t    public HintTextFieldUI(String hint) {\n    \t        this(hint,false);\n    \t    }\n\n    \t    HintTextFieldUI(String hint, boolean hideOnFocus) {\n    \t        this(hint,hideOnFocus, Color.gray);\n    \t    }\n\n    \t    HintTextFieldUI(String hint, boolean hideOnFocus, Color color) {\n    \t        this.hint = hint;\n    \t        this.hideOnFocus = hideOnFocus;\n    \t        this.color = color;\n    \t    }\n\n    \t    @Override\n    \t    protected void paintSafely(Graphics g) {\n    \t        super.paintSafely(g);\n    \t        JTextComponent comp = getComponent();\n    \t        if (hint != null && comp.getText().isEmpty() && (!(hideOnFocus && comp.hasFocus()))) {\n                    g.setColor(color != null ? color : comp.getForeground().brighter().brighter().brighter());\n    \t            int padding = (comp.getHeight() - comp.getFont().getSize())/2;\n    \t            g.drawString(hint, 3, comp.getHeight()-padding-1);          \n    \t        }\n    \t    }\n\n    \t    public void focusGained(FocusEvent e) {\n    \t        if (hideOnFocus) {\n                    repaint();\n                }\n    \t    }\n\n    \t    public void focusLost(FocusEvent e) {\n    \t        if (hideOnFocus) {\n                    repaint();\n                }\n    \t    }\n    \t    \n    \t    @Override\n    \t    protected void installListeners() {\n    \t        super.installListeners();\n    \t        getComponent().addFocusListener(this);\n    \t    }\n\n    \t    @Override\n    \t    protected void uninstallListeners() {\n    \t        super.uninstallListeners();\n    \t        getComponent().removeFocusListener(this);\n    \t    }\n    \t}\n    }\n\n\n    private class QuickSearchTimeoutCombobox extends PrefComboBox<String> {\n\n        QuickSearchTimeoutCombobox() {\n            super();\n            addItem(Translator.get(\"prefs_dialog.quick_search_timeout_never\"));\n            int selectedIndex = 0;\n            long prefVal = getPrefValue();\n            int step = 1;\n            for (int i = 1; i <= 60; i += step) {\n                if (i == 5) {\n                    step = 5;\n                } else if (i == 30) {\n                    step = 10;\n                }\n                addItem(i + \" \" + Translator.get(\"prefs_dialog.quick_search_timeout_sec\"));\n                if (i == prefVal/1000) {\n                    selectedIndex = getItemCount()-1;\n                }\n            }\n            setSelectedIndex(selectedIndex);\n        }\n\n        long getPrefValue() {\n            return TcConfigurations.getPreferences().getVariable(TcPreference.QUICK_SEARCH_TIMEOUT, TcPreferences.DEFAULT_QUICK_SEARCH_TIMEOUT);\n        }\n\n        long getMilliseconds() {\n            if (getSelectedIndex() == 0) {\n                return 0;\n            }\n            String val = getSelectedItem();\n            return Integer.parseInt(val.substring(0, val.indexOf(' '))) * 1000;\n        }\n\n        @Override\n        public boolean hasChanged() {\n            return getMilliseconds() != getPrefValue();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/general/GeneralPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.pref.general;\n\nimport java.awt.BorderLayout;\nimport java.awt.Component;\nimport java.awt.FlowLayout;\nimport java.awt.GridLayout;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.awt.event.ItemEvent;\nimport java.awt.event.ItemListener;\nimport java.text.SimpleDateFormat;\nimport java.util.Calendar;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.Locale;\n\nimport javax.swing.BorderFactory;\nimport javax.swing.Box;\nimport javax.swing.ButtonGroup;\nimport javax.swing.JLabel;\nimport javax.swing.JList;\nimport javax.swing.JPanel;\nimport javax.swing.event.DocumentEvent;\nimport javax.swing.event.DocumentListener;\nimport javax.swing.text.PlainDocument;\n\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.utils.text.CustomDateFormat;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\nimport com.mucommander.ui.dialog.pref.PreferencesPanel;\nimport com.mucommander.ui.dialog.pref.component.PrefCheckBox;\nimport com.mucommander.ui.dialog.pref.component.PrefComboBox;\nimport com.mucommander.ui.dialog.pref.component.PrefRadioButton;\nimport com.mucommander.ui.dialog.pref.component.PrefTextField;\nimport com.mucommander.ui.icon.IconManager;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.widgets.render.BasicComboBoxRenderer;\n\n\n/**\n * 'General' preferences panel.\n *\n * @author Maxence Bernard\n */\nclass GeneralPanel extends PreferencesPanel implements ItemListener, ActionListener, DocumentListener {\n\n    // Language\n    private final List<Locale> languages;\n    private final PrefComboBox<Locale> languageComboBox;\n\t\n    // Date/time format\n    private final PrefRadioButton time12RadioButton;\n    private final PrefComboBox<String> dateFormatComboBox;\n    private final PrefTextField dateSeparatorField;\n    private final PrefCheckBox showSecondsCheckBox;\n    private final PrefCheckBox showCenturyCheckBox;\n    private final JLabel previewLabel;\n    private final Date exampleDate;\n\n    private final static String DAY = Translator.get(\"prefs_dialog.day\");\n    private final static String MONTH = Translator.get(\"prefs_dialog.month\");\n    private final static String YEAR = Translator.get(\"prefs_dialog.year\");\n\n\n    private final static String[] DATE_FORMAT_LABELS = {\n        MONTH+\"/\"+DAY+\"/\"+YEAR,\n        DAY+\"/\"+MONTH+\"/\"+YEAR,\n        YEAR+\"/\"+MONTH+\"/\"+DAY,\n        MONTH+\"/\"+YEAR+\"/\"+DAY,\n        DAY+\"/\"+YEAR+\"/\"+MONTH,\n        YEAR+\"/\"+DAY+\"/\"+MONTH\n    };\n\n    private final static String[] DATE_FORMATS = {\n        \"MM/dd/yy\",\n        \"dd/MM/yy\",\n        \"yy/MM/dd\",\n        \"MM/yy/dd\",\n        \"dd/yy/MM\",\n        \"yy/dd/MM\"\n    };\n\n    private final static String[] DATE_FORMATS_WITH_CENTURY = {\n        \"MM/dd/yyyy\",\n        \"dd/MM/yyyy\",\n        \"yyyy/MM/dd\",\n        \"MM/yyyy/dd\",\n        \"dd/yyyy/MM\",\n        \"yyyy/dd/MM\"\n    };\n\n    private final static String HOUR_12_TIME_FORMAT = \"hh:mm a\";\n    private final static String HOUR_12_TIME_FORMAT_WITH_SECONDS = \"hh:mm:ss a\";\n    private final static String HOUR_24_TIME_FORMAT = \"HH:mm\";\n    private final static String HOUR_24_TIME_FORMAT_WITH_SECONDS = \"HH:mm:ss\";\n    private final static String YYYY = \"yyyy\";\n\n    private class LangComboBox extends PrefComboBox<Locale> {\n        LangComboBox() {\n            // Use a custom combo box renderer to display language icons\n            class LanguageComboBoxRenderer extends BasicComboBoxRenderer<Locale> {\n\n                @Override\n                public Component getListCellRendererComponent(JList<? extends Locale> list, Locale value, int index, boolean isSelected, boolean cellHasFocus) {\n                    JLabel label = (JLabel)super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);\n\n                    label.setText(Translator.get(\"language.\" + value.toLanguageTag()));\n                    label.setIcon(IconManager.getIcon(IconManager.IconSet.LANGUAGE, value.toLanguageTag() + \".png\"));\n\n                    return label;\n                }\n            }\n            setRenderer(new LanguageComboBoxRenderer());\n\n            // Add combo items and select current language (defaults to EN if current language can't be found)\n            for (Locale language : languages) {\n                addItem(language);\n            }\n            Locale currentLang = Locale.forLanguageTag(getVariable(TcPreference.LANGUAGE));\n            setSelectedItem(currentLang);\n\n        }\n\n        public boolean hasChanged() {\n            String lang = languages.get(getSelectedIndex()).toLanguageTag();\n            return !lang.equals(getVariable(TcPreference.LANGUAGE));\n        }\n    }\n\n\n    GeneralPanel(PreferencesDialog parent) {\n        super(parent, Translator.get(\"prefs_dialog.general_tab\"));\n\n        setLayout(new BorderLayout());\n        \n        YBoxPanel mainPanel = new YBoxPanel();\n\n        // Language\n        JPanel languagePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        languagePanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"prefs_dialog.language\")));\n        this.languages = Translator.getAvailableLanguages();\n\n        languageComboBox = new LangComboBox();\n        languagePanel.add(languageComboBox);\n        mainPanel.add(languagePanel);\n        mainPanel.addSpace(10);\n\t\t\n        // Date & time format panel\n        YBoxPanel dateTimeFormatPanel = new YBoxPanel();\n        dateTimeFormatPanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"prefs_dialog.date_time\")));\n\n        JPanel gridPanel = new JPanel(new GridLayout(1, 2));\n\n        YBoxPanel dateFormatPanel = new YBoxPanel();\n        dateFormatPanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"prefs_dialog.date\")));\n\n        // Date format combo\n        dateFormatComboBox = new PrefComboBox<String>() {\n\t\t\tpublic boolean hasChanged() {\n\t\t\t\treturn !getDateFormatString().equals(getVariable(TcPreference.DATE_FORMAT));\n\t\t\t}\n        };\n        String dateFormat = getVariable(TcPreference.DATE_FORMAT);\n        String separator = getVariable(TcPreference.DATE_SEPARATOR, TcPreferences.DEFAULT_DATE_SEPARATOR);\n        int dateFormatIndex = getDateFormatIndex(dateFormat, separator);\n        dateFormatComboBox.setSelectedIndex(dateFormatIndex);\n        dateFormatComboBox.addItemListener(this);\n\n        JPanel tempPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        tempPanel.add(dateFormatComboBox);\n        tempPanel.add(Box.createHorizontalGlue());\n        dateFormatPanel.add(tempPanel);\n\n        // Date separator field\n        tempPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        tempPanel.add(new JLabel(Translator.get(\"prefs_dialog.date_separator\")+\": \"));\n        dateSeparatorField = new PrefTextField(1) {\n\t\t\tpublic boolean hasChanged() {\n\t\t\t\treturn !getText().equals(getVariable(TcPreference.DATE_SEPARATOR));\n\t\t\t}\n        };\n        // Limit the number of characters in the text field to 1 and enforces only non-alphanumerical characters\n        PlainDocument doc = new PlainDocument() {\n            @Override\n            public void insertString(int param, String str, javax.swing.text.AttributeSet attributeSet) throws javax.swing.text.BadLocationException {\n                // Limit field to 1 character max\n                if (str != null && this.getLength() + str.length() > 1) {\n                    return;\n                }\n\n                // Reject letters and digits, as they don't make much sense, plus letters would be misinterpreted by SimpleDateFormat\n                if (!strContainsLetterOrDigit(str)) {\n                    super.insertString(param, str, attributeSet);\n                }\n            }\n        };\n        dateSeparatorField.setDocument(doc);\n        dateSeparatorField.setText(separator);\n        doc.addDocumentListener(this);\n        tempPanel.add(dateSeparatorField);\n        tempPanel.add(Box.createHorizontalGlue());\n        dateFormatPanel.add(tempPanel);\n\n        showCenturyCheckBox = new PrefCheckBox(Translator.get(\"prefs_dialog.show_century\"),\n                checkBox -> checkBox.isSelected() != getVariable(TcPreference.DATE_FORMAT).contains(YYYY));\n\n        showCenturyCheckBox.setSelected(dateFormat.contains(YYYY));\n        showCenturyCheckBox.addItemListener(this);\n        dateFormatPanel.add(showCenturyCheckBox);\n        dateFormatPanel.addSpace(10);\n        gridPanel.add(dateFormatPanel);\n\n        // Time format\n        YBoxPanel timeFormatPanel = new YBoxPanel();\n        timeFormatPanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"prefs_dialog.time\")));\n\n        time12RadioButton = new PrefRadioButton(Translator.get(\"prefs_dialog.time_12_hour\")) {\n\t\t\tpublic boolean hasChanged() {\n\t\t\t\tString timeFormat = getVariable(TcPreference.TIME_FORMAT);\n\t\t        return isSelected() != (timeFormat.equals(HOUR_12_TIME_FORMAT) || timeFormat.equals(HOUR_12_TIME_FORMAT_WITH_SECONDS)); \n\t\t\t}\n        };\n        time12RadioButton.addActionListener(this);\n        PrefRadioButton time24RadioButton = new PrefRadioButton(Translator.get(\"prefs_dialog.time_24_hour\")) {\n\t\t\tpublic boolean hasChanged() {\n\t\t\t\tString timeFormat = getVariable(TcPreference.TIME_FORMAT);\n\t\t        return isSelected() != (timeFormat.equals(HOUR_24_TIME_FORMAT) || timeFormat.equals(HOUR_24_TIME_FORMAT_WITH_SECONDS));\n\t\t\t}\n        };\n        time24RadioButton.addActionListener(this);\n        \n        String timeFormat = getVariable(TcPreference.TIME_FORMAT);\n        if (timeFormat.equals(HOUR_12_TIME_FORMAT) || timeFormat.equals(HOUR_12_TIME_FORMAT_WITH_SECONDS)) {\n            time12RadioButton.setSelected(true);\n        } else {\n            time24RadioButton.setSelected(true);\n        }\n            \n        ButtonGroup buttonGroup = new ButtonGroup();\n        buttonGroup.add(time12RadioButton);\n        buttonGroup.add(time24RadioButton);\n\n        timeFormatPanel.add(time12RadioButton);\n        timeFormatPanel.add(time24RadioButton);\n        timeFormatPanel.addSpace(10);\n\n        showSecondsCheckBox = new PrefCheckBox(Translator.get(\"prefs_dialog.show_seconds\"),\n                checkBox -> checkBox.isSelected() != getVariable(TcPreference.TIME_FORMAT).contains(\":ss\"));\n        showSecondsCheckBox.setSelected(timeFormat.contains(\":ss\"));\n        showSecondsCheckBox.addItemListener(this);\n        timeFormatPanel.add(showSecondsCheckBox);\n        timeFormatPanel.addSpace(10);\n        gridPanel.add(timeFormatPanel);\n\n        dateTimeFormatPanel.add(gridPanel);\n\n        // Date/time preview\n        tempPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        tempPanel.add(new JLabel(Translator.get(\"example\")+\": \"));\n        previewLabel = new JLabel();\n        Calendar calendar = Calendar.getInstance(); \n        calendar.set(calendar.get(Calendar.YEAR)-1, Calendar.DECEMBER, 31, 23, 59);\n        exampleDate = calendar.getTime();\n        updatePreviewLabel();\n        tempPanel.add(previewLabel);\n\n        dateTimeFormatPanel.add(tempPanel);\n      \n        mainPanel.add(dateTimeFormatPanel);\n\n        add(mainPanel, BorderLayout.NORTH);\n        \n        languageComboBox.addDialogListener(parent);\n        time12RadioButton.addDialogListener(parent);\n        time24RadioButton.addDialogListener(parent);\n        dateFormatComboBox.addDialogListener(parent);\n        dateSeparatorField.addDialogListener(parent);\n        showSecondsCheckBox.addDialogListener(parent);\n        showCenturyCheckBox.addDialogListener(parent);\n    }\n\n    private int getDateFormatIndex(String dateFormat, String separator) {\n        int dateFormatIndex = 0;\n        String buffer = dateFormat.replace(separator.charAt(0), '/');\n        for (int i = 0; i < DATE_FORMATS.length; i++) {\n            dateFormatComboBox.addItem(DATE_FORMAT_LABELS[i]);\n            if (buffer.equals(DATE_FORMATS[i]) || buffer.equals(DATE_FORMATS_WITH_CENTURY[i])) {\n                dateFormatIndex = i;\n            }\n        }\n        return dateFormatIndex;\n    }\n\n    private static boolean strContainsLetterOrDigit(String str) {\n        if (str == null) {\n            return false;\n        }\n        int len = str.length();\n        for (int i = 0; i < len; i++) {\n            if (Character.isLetterOrDigit(str.charAt(i))) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    private String getTimeFormatString() {\n        boolean showSeconds = showSecondsCheckBox.isSelected();\n\n        if (time12RadioButton.isSelected()) {\n            return showSeconds ? HOUR_12_TIME_FORMAT_WITH_SECONDS : HOUR_12_TIME_FORMAT;\n        } else {\n            return showSeconds ? HOUR_24_TIME_FORMAT_WITH_SECONDS : HOUR_24_TIME_FORMAT;\n        }\n    }\n\n    private String getDateFormatString() {\n        int selectedIndex = dateFormatComboBox.getSelectedIndex();\n        return CustomDateFormat.replaceDateSeparator(showCenturyCheckBox.isSelected()?DATE_FORMATS_WITH_CENTURY[selectedIndex]:DATE_FORMATS[selectedIndex], dateSeparatorField.getText());\n    }\n\n    private void updatePreviewLabel() {\n        SimpleDateFormat dateFormat = new SimpleDateFormat(getDateFormatString() + \" \" + getTimeFormatString());\n        previewLabel.setText(dateFormat.format(exampleDate));\n        previewLabel.repaint();\n    }\n\n\n    ///////////////////////\n    // PrefPanel methods //\n    ///////////////////////\n    @Override\n    protected void commit() {\n    \tTcConfigurations.getPreferences().setVariable(TcPreference.LANGUAGE, languageComboBox.getSelectedItem().toLanguageTag());\n    \tTcConfigurations.getPreferences().setVariable(TcPreference.DATE_FORMAT, getDateFormatString());\n    \tTcConfigurations.getPreferences().setVariable(TcPreference.DATE_SEPARATOR, dateSeparatorField.getText());\n    \tTcConfigurations.getPreferences().setVariable(TcPreference.TIME_FORMAT, getTimeFormatString());\n    }\n\n\n    //////////////////////////\n    // ItemListener methods //\n    //////////////////////////\n\t\n    public void itemStateChanged(ItemEvent e) {\n        updatePreviewLabel();\n    }\n\n\n    ////////////////////////////\n    // ActionListener methods //\n    ////////////////////////////\n\t\n    public void actionPerformed(ActionEvent e) {\n        updatePreviewLabel();\n    }\n\n\n    //////////////////////////////\n    // DocumentListener methods //\n    //////////////////////////////\n\t\n    public void changedUpdate(DocumentEvent e) {\n        updatePreviewLabel();\n    }\n\t\n    public void insertUpdate(DocumentEvent e) {\n        updatePreviewLabel();\n    }\n\t\n    public void removeUpdate(DocumentEvent e) {\n        updatePreviewLabel();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/general/GeneralPreferencesDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.pref.general;\n\nimport java.awt.*;\nimport java.util.LinkedHashSet;\nimport java.util.Set;\n\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcSnapshot;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.InformationDialog;\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\nimport com.mucommander.ui.dialog.pref.component.PrefComponent;\nimport com.mucommander.ui.dialog.pref.theme.ThemeEditorDialog;\nimport com.mucommander.ui.main.WindowManager;\nimport com.mucommander.ui.theme.ThemeManager;\n\n/**\n * This is the main preferences dialog that contains all preferences panels organized by tabs.\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic class GeneralPreferencesDialog extends PreferencesDialog {\n    /** Used to ensure we only have the one preferences dialog open at any given time. */\n    private static GeneralPreferencesDialog singleton;\n    /** Stores the components in the dialog that were changed and their current value is different from their saved value at MuConfiguration **/\n    private final Set<PrefComponent> modifiedComponents = new LinkedHashSet<>();\n\n\n    /* Dialog's minimum dimensions. */\n//    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(580, 300);\n    /* Dialog's maximum dimensions. */\n//    private final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(1024, 800);\n\n\n\n    // - Available tabs ---------------------------------------------------------\n    // --------------------------------------------------------------------------\n    // TODO use enum\n    /** Identifier of the 'general' tab. */\n    public static final int GENERAL_TAB    = 0;\n    /** Identifier of the 'folders' tab. */\n    public static final int FOLDERS_TAB    = 1;\n    /** Identifier of the 'appearance' tab. */\n    public static final int APPEARANCE_TAB = 2;\n    /** Identifier of the 'shortcuts' tab. */\n    public static final int SHORTCUTS_TAB = 3;\n    /** Identifier of the 'mail' tab. */\n    public static final int MAIL_TAB       = 4;\n    /** Identifier of the 'misc' tab. */\n    public static final int MISC_TAB       = 5;\n\n\n\n    // - Tab icons --------------------------------------------------------------\n    // --------------------------------------------------------------------------\n    /** Name of the icon used by the 'general' tab. */\n    private static final String GENERAL_ICON    = \"general.png\";\n    /** Name of the icon used by the 'folders' tab. */\n    private static final String FOLDERS_ICON    = \"folders.png\";\n    /** Name of the icon used by the 'appearance' tab. */\n    private static final String APPEARANCE_ICON = \"appearance.png\";\n    /** Name of the icon used by the 'mail' tab. */\n    private static final String MAIL_ICON       = \"mail.png\";\n    /** Name of the icon used by the 'misc' tab. */\n    private static final String MISC_ICON       = \"misc.png\";\n    /** Name of the icon used by the 'shortcuts' tab. */\n    private static final String SHORTCUTS_ICON  = \"shortcuts.png\";\n\n    /** Index of the tab that was last selected by the user. */\n    private static int      lastTabIndex = 0;\n\n\n\n    /**\n     * Creates a new instance of the <code>GeneralPreferencesDialog</code>.\n     */\n    private GeneralPreferencesDialog() {\n        super(WindowManager.getCurrentMainFrame().getJFrame(), Translator.get(\"prefs_dialog.title\"));\n\n        // Adds the preference tabs.\n        addPreferencesPanel(new GeneralPanel(this),    GENERAL_ICON);\n        addPreferencesPanel(new FoldersPanel(this),    FOLDERS_ICON);\n        addPreferencesPanel(new AppearancePanel(this), APPEARANCE_ICON);\n        addPreferencesPanel(new ShortcutsPanel(this),  SHORTCUTS_ICON);\n        addPreferencesPanel(new MailPanel(this),       MAIL_ICON);\n        addPreferencesPanel(new MiscPanel(this),       MISC_ICON);\n\n        // Sets the dialog's size.\n        Dimension screenSize = TcSnapshot.getScreenSize();\n        Dimension minimumSize = new Dimension(580, 300);\n        Dimension maximumSize = new Dimension(screenSize);\n        if (screenSize.getWidth() >= 1024 && screenSize.getHeight() > 700) {\n            minimumSize.setSize(640, 480);\n        }\n        setMinimumSize(minimumSize);\n        setMaximumSize(maximumSize);\n\n        // Restores the last selected index.\n        setActiveTab(lastTabIndex);\n\n        // prepare ThemeEditorDialog\n        // because first construction of this object can take time (about 500-1000 ms)\n        // we make that to reduce delay on show theme editor dialog\n        new Thread(() -> new ThemeEditorDialog((Dialog) null, ThemeManager.getCurrentTheme())).start();\n    }\n\n\n    /**\n     * Commits the changes and writes the configuration file if necessary.\n     */\n    @Override\n    public void commit() {\n        super.commit();\n        try {\n            TcConfigurations.savePreferences();\n        }\n        catch(Exception e) {\n            InformationDialog.showErrorDialog(this);\n        }\n    }\n\n    /**\n     * Releases the singleton.\n     */\n    @Override\n    public void dispose() {\n        releaseSingleton(getSelectedPanelIndex());\n        super.dispose();\n    }\n\n\n\n    // - Singleton management ---------------------------------------------------\n    // --------------------------------------------------------------------------\n    /**\n     * Returns an instance of <code>GeneralPreferencesDialog</code>.\n     * <p>\n     * This will not necessarily create a new instance - if a dialog is already in use, it\n     * will be returned. This is an attempt to ensure that the preferences dialog is not opened\n     * more than once.\n     *\n     * @return an instance of <code>GeneralPreferencesDialog</code>.\n     */\n    public static synchronized GeneralPreferencesDialog getDialog() {\n        // If no instance already exists, create a new one.\n        if(singleton == null)\n            singleton = new GeneralPreferencesDialog();\n\n        return singleton;\n    }\n\n    /**\n     * Releases the singleton.\n     * <p>\n     * After this method has been called, calls to {@link #getDialog()} will\n     * result in creating a new instance of <code>GeneralPreferencesDialog</code>.\n     *\n     * @param lastTab index of the last selected panel.\n     */\n    private static synchronized void releaseSingleton(int lastTab) {\n        singleton    = null;\n        lastTabIndex = lastTab;\n    }\n\n    @Override\n    public void componentChanged(PrefComponent component) {\n\t\tif (component.hasChanged()) {\n            modifiedComponents.add(component);\n        } else {\n            modifiedComponents.remove(component);\n        }\n\t\tsetCommitButtonsEnabled(!modifiedComponents.isEmpty());\n\t}\n    \n    @Override\n    public void setCommitButtonsEnabled(boolean enable) {\n    \tsuper.setCommitButtonsEnabled(enable);\n    \t// if \"commit buttons\" are disabled that's mean that there is no change in any component\n    \t// located in this dialog => we can clear the list of modified components in this dialog.\n    \tif (!enable) {\n            modifiedComponents.clear();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/general/MailPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.pref.general;\n\nimport java.awt.BorderLayout;\n\nimport javax.swing.BorderFactory;\n\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.conf.TcPreferencesAPI;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\nimport com.mucommander.ui.dialog.pref.PreferencesPanel;\nimport com.mucommander.ui.dialog.pref.component.PrefTextField;\nimport com.mucommander.ui.layout.XAlignedComponentPanel;\nimport com.mucommander.ui.layout.YBoxPanel;\n\n/**\n * 'Mail' preferences panel.\n *\n * @author Maxence Bernard\n */\nclass MailPanel extends PreferencesPanel {\n\n    /** Name of the user */\n    private final PrefTextField nameField;\n\t\n    /** Email address of the user */\n    private final PrefTextField emailField;\n\t\n    /** IP/hostname to the SMTP server */\n    private final PrefTextField smtpField;\n\t\n    /** TCP port to the SMTP server */\n    private final PrefTextField portField;\n\n\n    MailPanel(PreferencesDialog parent) {\n        super(parent, Translator.get(\"prefs_dialog.mail_tab\"));\n\n        setLayout(new BorderLayout());\n\n        YBoxPanel mainPanel = new YBoxPanel(5);\n        mainPanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"prefs_dialog.mail_settings\")));\n\n        // Text fields panel\n        XAlignedComponentPanel compPanel = new XAlignedComponentPanel();\n\n        // Name field\n        nameField = new PrefTextField(getVariable(TcPreference.MAIL_SENDER_NAME, \"\")) {\n\t\t\tpublic boolean hasChanged() {\n\t\t\t\treturn !nameField.getText().equals(getVariable(TcPreference.MAIL_SENDER_NAME, \"\"));\n\t\t\t}\n        };\n        compPanel.addRow(Translator.get(\"prefs_dialog.mail_name\"), nameField, 10);\n\t\t\n        // Email field\n        emailField = new PrefTextField(getVariable(TcPreference.MAIL_SENDER_ADDRESS, \"\")) {\n\t\t\tpublic boolean hasChanged() {\n\t\t\t\treturn !emailField.getText().equals(getVariable(TcPreference.MAIL_SENDER_ADDRESS, \"\"));\n\t\t\t}\n        };\n        compPanel.addRow(Translator.get(\"prefs_dialog.mail_address\"), emailField, 10);\n\n        // SMTP field\n        smtpField = new PrefTextField(getVariable(TcPreference.SMTP_SERVER, \"\")) {\n\t\t\tpublic boolean hasChanged() {\n\t\t\t\treturn !smtpField.getText().equals(getVariable(TcPreference.SMTP_SERVER, \"\"));\n\t\t\t}\n        };\n        compPanel.addRow(Translator.get(\"prefs_dialog.mail_server\"), smtpField, 10);\n\n        // SMTP port field\n        portField = new PrefTextField(getVariable(TcPreference.SMTP_PORT, \"\"+TcPreferences.DEFAULT_SMTP_PORT)) {\n\t\t\tpublic boolean hasChanged() {\n\t\t\t\treturn !portField.getText().equals(String.valueOf(getVariable(TcPreference.SMTP_PORT, TcPreferences.DEFAULT_SMTP_PORT)));\n\t\t\t}\n        };\n        compPanel.addRow(Translator.get(\"server_connect_dialog.port\"), portField, 10);\n\n        mainPanel.add(compPanel, BorderLayout.NORTH);\n        add(mainPanel, BorderLayout.NORTH);\n        \n        nameField.addDialogListener(parent);\n    \temailField.addDialogListener(parent);\n    \tsmtpField.addDialogListener(parent);\n    \tportField.addDialogListener(parent);\n    }\n\n\n    @Override\n    protected void commit() {\n        final TcPreferencesAPI pref = TcConfigurations.getPreferences();\n    \tpref.setVariable(TcPreference.MAIL_SENDER_NAME, nameField.getText());\n    \tpref.setVariable(TcPreference.MAIL_SENDER_ADDRESS, emailField.getText());\n    \tpref.setVariable(TcPreference.SMTP_SERVER, smtpField.getText());\n    \tpref.setVariable(TcPreference.SMTP_PORT, portField.getText());\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/general/MiscPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.pref.general;\n\nimport java.awt.BorderLayout;\nimport java.awt.FlowLayout;\nimport java.awt.event.ItemEvent;\nimport java.awt.event.ItemListener;\nimport java.util.Objects;\n\nimport javax.swing.BorderFactory;\nimport javax.swing.ButtonGroup;\nimport javax.swing.JLabel;\nimport javax.swing.JPanel;\nimport javax.swing.JRadioButton;\n\nimport com.mucommander.bonjour.BonjourDirectory;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.conf.TcPreferencesAPI;\nimport com.mucommander.desktop.DesktopManager;\nimport com.mucommander.desktop.macos.OSXApplications;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.DialogOwner;\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\nimport com.mucommander.ui.dialog.pref.PreferencesPanel;\nimport com.mucommander.ui.dialog.pref.component.PrefCheckBox;\nimport com.mucommander.ui.dialog.pref.component.PrefEncodingSelectBox;\nimport com.mucommander.ui.dialog.pref.component.PrefFilePathField;\nimport com.mucommander.ui.dialog.pref.component.PrefRadioButton;\nimport com.mucommander.ui.dialog.pref.component.PrefTextField;\nimport com.mucommander.ui.layout.XAlignedComponentPanel;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.notifier.AbstractNotifier;\n\nimport static com.mucommander.conf.TcPreference.*;\nimport static com.mucommander.conf.TcPreferences.TERMINAL_CUSTOM;\nimport static com.mucommander.conf.TcPreferences.TERMINAL_DEFAULT;\nimport static com.mucommander.conf.TcPreferences.TERMINAL_ITERM;\n\n\n/**\n * 'Misc' preferences panel.\n *\n * @author Maxence Bernard\n */\nclass MiscPanel extends PreferencesPanel implements ItemListener {\n\n    /** Custom shell command text field */\n    private final PrefTextField customShellField;\n\t\n    /** 'Use custom shell' radio button */\n    private final PrefRadioButton useCustomShellRadioButton;\n\n    /** 'Check for updates on startup' checkbox */\n    private final PrefCheckBox checkForUpdatesCheckBox;\n\n    /** 'Show confirmation dialog on quit' checkbox */\n    private final PrefCheckBox quitConfirmationCheckBox;\n    \n    /** 'Show splash screen' checkbox */\n    private final PrefCheckBox showSplashScreenCheckBox;\n\n    /** 'Enable system notifications' checkbox */\n    private PrefCheckBox systemNotificationsCheckBox;\n\n    /** 'Enable Bonjour services discovery' checkbox */\n    private final PrefCheckBox bonjourDiscoveryCheckBox;\n\n    /** Shell encoding auto-detect checkbox */\n    private PrefCheckBox shellEncodingAutoDetectCheckbox;\n\n    /** Shell encoding select box. */\n    private PrefEncodingSelectBox shellEncodingSelectBox;\n\n    /** Custom external terminal command text field */\n    private final PrefTextField customExternalTerminalField;\n\n    /** 'Use custom external terminal' radio button */\n    private final PrefRadioButton useCustomExternalTerminalRadioButton;\n\n    private final PrefRadioButton useITermExternalTerminalRadioButton;\n\n    /** Custom builtin terminal command text field */\n    private final PrefTextField customTerminalShellField;\n\n    /** 'Use custom builtin terminal shell' radio button */\n    private final PrefRadioButton useCustomTerminalShellRadioButton;\n\n\n    private JPanel createShellEncodingPanel(PreferencesDialog parent) {\n        JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEADING));\n\n        shellEncodingAutoDetectCheckbox = new PrefCheckBox(Translator.get(\"prefs_dialog.auto_detect_shell_encoding\"),\n                checkBox -> checkBox.isSelected() != getVariable(AUTODETECT_SHELL_ENCODING, TcPreferences.DEFAULT_AUTODETECT_SHELL_ENCODING));\n\n        boolean autoDetect = getVariable(AUTODETECT_SHELL_ENCODING, TcPreferences.DEFAULT_AUTODETECT_SHELL_ENCODING);\n        shellEncodingAutoDetectCheckbox.setSelected(autoDetect);\n        shellEncodingAutoDetectCheckbox.addItemListener(this);\n\n        panel.add(shellEncodingAutoDetectCheckbox);\n\n        shellEncodingSelectBox = new PrefEncodingSelectBox(new DialogOwner(parent), getVariable(SHELL_ENCODING)) {\n            public boolean hasChanged() {\n                return !getVariable(SHELL_ENCODING).equals(getSelectedEncoding());\n            }\n        };\n        shellEncodingSelectBox.setEnabled(!autoDetect); \n\n        panel.add(shellEncodingSelectBox);\n\n        return panel;\n    }\n\n\n    MiscPanel(PreferencesDialog parent) {\n        super(parent, Translator.get(\"prefs_dialog.misc_tab\"));\n\n        setLayout(new BorderLayout());\n\n        YBoxPanel northPanel = new YBoxPanel();\n\n        JRadioButton useDefaultShellRadioButton = new JRadioButton(Translator.get(\"prefs_dialog.default_shell\") + ':');\n        useCustomShellRadioButton = new PrefRadioButton(Translator.get(\"prefs_dialog.custom_shell\") + ':') {\n\t\t\tpublic boolean hasChanged() {\n\t\t\t\treturn isSelected() != getVariable(USE_CUSTOM_SHELL, TcPreferences.DEFAULT_USE_CUSTOM_SHELL);\n\t\t\t}\n        };\n\n        // Use system default or custom shell ?\n        if (getVariable(USE_CUSTOM_SHELL, TcPreferences.DEFAULT_USE_CUSTOM_SHELL)) {\n            useCustomShellRadioButton.setSelected(true);\n        } else {\n            useDefaultShellRadioButton.setSelected(true);\n        }\n\n        useCustomShellRadioButton.addItemListener(this);\n\n        ButtonGroup buttonGroup = new ButtonGroup();\n        buttonGroup.add(useDefaultShellRadioButton);\n        buttonGroup.add(useCustomShellRadioButton);\n\n        // Shell panel\n        XAlignedComponentPanel shellPanel = new XAlignedComponentPanel();\n        shellPanel.setLabelLeftAligned(true);\n        shellPanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"prefs_dialog.shell\")));\n\n        // create a path field with auto-completion capabilities\n        customShellField = new PrefFilePathField(getVariable(CUSTOM_SHELL, \"\")) {\n\t\t\tpublic boolean hasChanged() {\n\t\t\t\treturn isEnabled() && !getText().equals(getVariable(CUSTOM_SHELL));\n\t\t\t}\n        };\n        customShellField.setEnabled(useCustomShellRadioButton.isSelected());\n\n        shellPanel.addRow(useDefaultShellRadioButton, new JLabel(DesktopManager.getDefaultShell()), 5);\n        shellPanel.addRow(useCustomShellRadioButton, customShellField, 10);\n        shellPanel.addRow(Translator.get(\"prefs_dialog.shell_encoding\"), createShellEncodingPanel(parent), 5);\n\n        northPanel.add(shellPanel, 5);\n\n        northPanel.addSpace(10);\n\n\n        // External Terminal panel\n        XAlignedComponentPanel externalTerminalPanel = new XAlignedComponentPanel();\n        externalTerminalPanel.setLabelLeftAligned(true);\n        externalTerminalPanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"prefs_dialog.external_terminal\")));\n\n        PrefRadioButton useDefaultExternalTerminalRadioButton = new PrefRadioButton(Translator.get(\"prefs_dialog.default_terminal\") + ':') {\n            @Override\n            public boolean hasChanged() {\n                return isSelected() != (getTerminalType() == TERMINAL_DEFAULT);\n            }\n        };\n        useCustomExternalTerminalRadioButton = new PrefRadioButton(Translator.get(\"prefs_dialog.custom_terminal\") + ':') {\n            public boolean hasChanged() {\n                return isSelected() != (getTerminalType() == TERMINAL_CUSTOM);\n            }\n        };\n\n        // create a path field with auto-completion capabilities\n        customExternalTerminalField = new PrefFilePathField(getVariable(CUSTOM_EXTERNAL_TERMINAL, \"\")) {\n            public boolean hasChanged() {\n                return isEnabled() && !getText().equals(getVariable(CUSTOM_EXTERNAL_TERMINAL));\n            }\n        };\n\n        externalTerminalPanel.addRow(useDefaultExternalTerminalRadioButton, new JLabel(DesktopManager.getDefaultTerminalAppCommand()), 5);\n        if (OsFamily.getCurrent() == OsFamily.MAC_OS_X && OSXApplications.iTermInstalled()) {\n            useITermExternalTerminalRadioButton = new PrefRadioButton(Translator.get(\"prefs_dialog.iterm_terminal\") + ':') {\n                public boolean hasChanged() {\n                    return isSelected() != (getTerminalType() == TERMINAL_ITERM);\n                }\n            };\n            JLabel textField = new JLabel(\"open -a iTerm .\");\n            externalTerminalPanel.addRow(useITermExternalTerminalRadioButton, textField, 10);\n            useITermExternalTerminalRadioButton.addItemListener(this);\n        } else {\n            useITermExternalTerminalRadioButton = null;\n        }\n        externalTerminalPanel.addRow(useCustomExternalTerminalRadioButton, customExternalTerminalField, 10);\n\n        northPanel.add(externalTerminalPanel, 5);\n        northPanel.addSpace(10);\n\n        // Use system default or custom external terminal ?\n        switch (getTerminalType()) {\n            case TERMINAL_CUSTOM:\n                useCustomExternalTerminalRadioButton.setSelected(true);\n            case TERMINAL_DEFAULT:\n                useDefaultExternalTerminalRadioButton.setSelected(true);\n            case TERMINAL_ITERM:\n                Objects.requireNonNullElse(useITermExternalTerminalRadioButton, useDefaultExternalTerminalRadioButton).setSelected(true);\n        }\n\n        customExternalTerminalField.setEnabled(useCustomExternalTerminalRadioButton.isSelected());\n\n        useCustomExternalTerminalRadioButton.addItemListener(this);\n        useDefaultShellRadioButton.addItemListener(this);\n\n\n        buttonGroup = new ButtonGroup();\n        buttonGroup.add(useDefaultExternalTerminalRadioButton);\n        buttonGroup.add(useCustomExternalTerminalRadioButton);\n        if (useITermExternalTerminalRadioButton != null) {\n            buttonGroup.add(useITermExternalTerminalRadioButton);\n        }\n\n        // Builtin Terminal panel\n        XAlignedComponentPanel terminalPanel = new XAlignedComponentPanel();\n        terminalPanel.setLabelLeftAligned(true);\n        terminalPanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"prefs_dialog.builtin_terminal\")));\n\n        JRadioButton useDefaultTerminalShellRadioButton = new JRadioButton(Translator.get(\"prefs_dialog.default_shell\") + ':');\n        useCustomTerminalShellRadioButton = new PrefRadioButton(Translator.get(\"prefs_dialog.custom_shell\") + ':') {\n            public boolean hasChanged() {\n                return isSelected() != getVariable(TERMINAL_USE_CUSTOM_SHELL, TcPreferences.DEFAULT_TERMINAL_USE_CUSTOM_SHELL);\n            }\n        };\n\n        // create a path field with auto-completion capabilities\n        customTerminalShellField = new PrefFilePathField(getVariable(TERMINAL_SHELL, \"\")) {\n            public boolean hasChanged() {\n                return isEnabled() && !getText().equals(getVariable(TERMINAL_SHELL));\n            }\n        };\n\n        terminalPanel.addRow(useDefaultTerminalShellRadioButton, new JLabel(DesktopManager.getDefaultTerminalShellCommand()), 5);\n        terminalPanel.addRow(useCustomTerminalShellRadioButton, customTerminalShellField, 10);\n\n        northPanel.add(terminalPanel, 5);\n        northPanel.addSpace(10);\n\n        // Use system default or custom builtin terminal ?\n        if (getVariable(TERMINAL_USE_CUSTOM_SHELL, TcPreferences.DEFAULT_TERMINAL_USE_CUSTOM_SHELL))\n            useCustomTerminalShellRadioButton.setSelected(true);\n        else\n            useDefaultTerminalShellRadioButton.setSelected(true);\n        customTerminalShellField.setEnabled(useCustomTerminalShellRadioButton.isSelected());\n\n        useCustomTerminalShellRadioButton.addItemListener(this);\n\n        buttonGroup = new ButtonGroup();\n        buttonGroup.add(useCustomTerminalShellRadioButton);\n        buttonGroup.add(useDefaultTerminalShellRadioButton);\n\n\n        // 'Show splash screen' option\n        showSplashScreenCheckBox = new PrefCheckBox(Translator.get(\"prefs_dialog.show_splash_screen\"),\n                checkBox -> checkBox.isSelected() != getVariable(SHOW_SPLASH_SCREEN, TcPreferences.DEFAULT_SHOW_SPLASH_SCREEN));\n        showSplashScreenCheckBox.setSelected(getVariable(SHOW_SPLASH_SCREEN, TcPreferences.DEFAULT_SHOW_SPLASH_SCREEN));\n        northPanel.add(showSplashScreenCheckBox);\n\n        // 'Check for updates on startup' option\n        checkForUpdatesCheckBox = new PrefCheckBox(Translator.get(\"prefs_dialog.check_for_updates_on_startup\"),\n                checkBox -> checkBox.isSelected() != getVariable(CHECK_FOR_UPDATE, TcPreferences.DEFAULT_CHECK_FOR_UPDATE));\n        checkForUpdatesCheckBox.setSelected(getVariable(CHECK_FOR_UPDATE, TcPreferences.DEFAULT_CHECK_FOR_UPDATE));\n        northPanel.add(checkForUpdatesCheckBox);\n\n        // 'Show confirmation dialog on quit' option\n        quitConfirmationCheckBox = new PrefCheckBox(Translator.get(\"prefs_dialog.confirm_on_quit\"),\n                checkBox -> checkBox.isSelected() != getVariable(CONFIRM_ON_QUIT, TcPreferences.DEFAULT_CONFIRM_ON_QUIT));\n        quitConfirmationCheckBox.setSelected(getVariable(CONFIRM_ON_QUIT, TcPreferences.DEFAULT_CONFIRM_ON_QUIT));\n        northPanel.add(quitConfirmationCheckBox);\n\n        // 'Enable system notifications' option, displayed only if current platform supports system notifications\n        if (AbstractNotifier.isAvailable()) {\n            systemNotificationsCheckBox = new PrefCheckBox(Translator.get(\"prefs_dialog.enable_system_notifications\")+\" (\"+AbstractNotifier.getNotifier().getPrettyName()+\")\",\n                    checkBox -> checkBox.isSelected() != getVariable(ENABLE_SYSTEM_NOTIFICATIONS, TcPreferences.DEFAULT_ENABLE_SYSTEM_NOTIFICATIONS));\n            systemNotificationsCheckBox.setSelected(getVariable(ENABLE_SYSTEM_NOTIFICATIONS,\n                                                                                     TcPreferences.DEFAULT_ENABLE_SYSTEM_NOTIFICATIONS));\n            northPanel.add(systemNotificationsCheckBox);\n        }\n\n        // 'Enable Bonjour services discovery' option\n        bonjourDiscoveryCheckBox = new PrefCheckBox(Translator.get(\"prefs_dialog.enable_bonjour_discovery\"),\n                checkBox -> checkBox.isSelected() != getVariable(ENABLE_BONJOUR_DISCOVERY, TcPreferences.DEFAULT_ENABLE_BONJOUR_DISCOVERY));\n\n        bonjourDiscoveryCheckBox.setSelected(getVariable(ENABLE_BONJOUR_DISCOVERY, TcPreferences.DEFAULT_ENABLE_BONJOUR_DISCOVERY));\n        northPanel.add(bonjourDiscoveryCheckBox);\n\n        add(northPanel, BorderLayout.NORTH);\n        \n        customShellField.addDialogListener(parent);\n    \tuseCustomShellRadioButton.addDialogListener(parent);\n        useCustomExternalTerminalRadioButton.addDialogListener(parent);\n        useDefaultExternalTerminalRadioButton.addDialogListener(parent);\n        if (useITermExternalTerminalRadioButton != null) {\n            useITermExternalTerminalRadioButton.addDialogListener(parent);\n        }\n        customExternalTerminalField.addDialogListener(parent);\n        useCustomTerminalShellRadioButton.addDialogListener(parent);\n        customTerminalShellField.addDialogListener(parent);\n    \tcheckForUpdatesCheckBox.addDialogListener(parent);\n    \tquitConfirmationCheckBox.addDialogListener(parent);\n        showSplashScreenCheckBox.addDialogListener(parent);\n        bonjourDiscoveryCheckBox.addDialogListener(parent);\n        shellEncodingAutoDetectCheckbox.addDialogListener(parent);\n        shellEncodingSelectBox.addDialogListener(parent);\n        if (systemNotificationsCheckBox != null) {\n            systemNotificationsCheckBox.addDialogListener(parent);\n        }\n    }\n\n    private static int getTerminalType() {\n        return getVariable(EXTERNAL_TERMINAL_TYPE, TcPreferences.DEFAULT_TERMINAL_TYPE);\n    }\n\n\n    @Override\n    public void itemStateChanged(ItemEvent e) {\n        Object source = e.getSource();\n        if (source == useCustomShellRadioButton) {\n            customShellField.setEnabled(useCustomShellRadioButton.isSelected());\n        } else if (source == shellEncodingAutoDetectCheckbox) {\n            shellEncodingSelectBox.setEnabled(!shellEncodingAutoDetectCheckbox.isSelected());\n        } else if (source == useCustomExternalTerminalRadioButton) {\n            customExternalTerminalField.setEnabled(useCustomExternalTerminalRadioButton.isSelected());\n        } else if (source == useCustomTerminalShellRadioButton) {\n            customTerminalShellField.setEnabled(useCustomTerminalShellRadioButton.isSelected());\n        }\n    }\n\n\n    @Override\n    protected void commit() {\n        TcPreferencesAPI pref = TcConfigurations.getPreferences();\n    \tpref.setVariable(CHECK_FOR_UPDATE, checkForUpdatesCheckBox.isSelected());\n\n        // Saves the shell data.\n    \tpref.setVariable(USE_CUSTOM_SHELL, useCustomShellRadioButton.isSelected());\n        pref.setVariable(CUSTOM_SHELL, customShellField.getText());\n\n        // Saves the shell encoding data.\n        boolean isAutoDetect = shellEncodingAutoDetectCheckbox.isSelected();\n        pref.setVariable(AUTODETECT_SHELL_ENCODING, isAutoDetect);\n        if (!isAutoDetect) {\n            pref.setVariable(SHELL_ENCODING, shellEncodingSelectBox.getSelectedEncoding());\n        }\n        if (useCustomExternalTerminalRadioButton.isSelected()) {\n            pref.setVariable(EXTERNAL_TERMINAL_TYPE, TERMINAL_CUSTOM);\n        } else if (useITermExternalTerminalRadioButton != null && useITermExternalTerminalRadioButton.isSelected()) {\n            pref.setVariable(EXTERNAL_TERMINAL_TYPE, TERMINAL_ITERM);\n        } else {\n            pref.setVariable(EXTERNAL_TERMINAL_TYPE, TERMINAL_DEFAULT);\n        }\n\n        pref.setVariable(CUSTOM_EXTERNAL_TERMINAL, customExternalTerminalField.getText());\n\n        pref.setVariable(TERMINAL_USE_CUSTOM_SHELL, useCustomTerminalShellRadioButton.isSelected());\n        pref.setVariable(TERMINAL_SHELL, customTerminalShellField.getText());\n\n        pref.setVariable(CONFIRM_ON_QUIT, quitConfirmationCheckBox.isSelected());\n        pref.setVariable(SHOW_SPLASH_SCREEN, showSplashScreenCheckBox.isSelected());\n\n        boolean enabled;\n        if (systemNotificationsCheckBox != null) {\n            enabled = systemNotificationsCheckBox.isSelected();\n            pref.setVariable(ENABLE_SYSTEM_NOTIFICATIONS, enabled);\n            AbstractNotifier.getNotifier().setEnabled(enabled);\n        }\n\n        enabled = bonjourDiscoveryCheckBox.isSelected();\n        pref.setVariable(ENABLE_BONJOUR_DISCOVERY, enabled);\n        BonjourDirectory.setActive(enabled);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/general/ShortcutsPanel.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.pref.general;\r\n\r\nimport com.mucommander.commons.util.StringUtils;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.action.*;\r\nimport com.mucommander.ui.combobox.TcComboBox;\r\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\r\nimport com.mucommander.ui.dialog.pref.PreferencesPanel;\r\nimport com.mucommander.ui.text.KeyStrokeUtils;\r\nimport com.mucommander.ui.theme.ThemeCache;\r\n\r\nimport javax.swing.*;\r\nimport javax.swing.event.*;\r\nimport java.awt.*;\r\nimport java.awt.event.*;\r\nimport java.util.Locale;\r\n\r\nimport static com.mucommander.conf.TcPreference.*;\r\n\r\n\r\n/**\r\n * 'Shortcuts' preferences panel.\r\n * \r\n * @author Arik Hadas, Johann Schmitz\r\n */\r\npublic class ShortcutsPanel extends PreferencesPanel {\r\n\r\n    private static class Filter extends ShortcutsTable.ActionFilter {\r\n\r\n        private ActionCategory actionCategory;\r\n        private String text;\r\n\r\n        Filter() {\r\n            this.actionCategory = ActionCategory.ALL;\r\n        }\r\n\r\n        @Override\r\n        public boolean accept(String actionId) {\r\n            ActionDescriptor descriptor = ActionProperties.getActionDescriptor(actionId);\r\n            boolean containsText = text == null || text.isEmpty() || descriptor.getDescription().toLowerCase().contains(text) ||\r\n                    descriptor.getLabel().toLowerCase().contains(text);\r\n            return actionCategory.contains(actionId) && containsText;\r\n        }\r\n\r\n        void setActionCategory(ActionCategory actionCategory) {\r\n            this.actionCategory = actionCategory;\r\n        }\r\n\r\n        void setText(String text) {\r\n            this.text = text != null ? text.toLowerCase() : null;\r\n        }\r\n    }\r\n\r\n    private final Filter filter = new Filter();\r\n\t\r\n\t// The table with action mappings\r\n\tprivate ShortcutsTable shortcutsTable;\r\n\t\r\n\t// Area in which tooltip texts and error messages are shown below the table\r\n\tprivate TooltipBar tooltipBar;\r\n\t\r\n\tShortcutsPanel(PreferencesDialog parent) {\r\n\t\tsuper(parent, Translator.get(\"shortcuts_panel\" + \".title\"));\r\n\t\tinitUI();\r\n\t\tsetPreferredSize(new Dimension(0, 0));\r\n\t\t\r\n\t\tshortcutsTable.addDialogListener(parent);\r\n\t}\r\n\t\r\n\tprivate void initUI() {\r\n\t\tsetLayout(new BorderLayout());\r\n\r\n\t\ttooltipBar = new TooltipBar();\r\n\t\tshortcutsTable = new ShortcutsTable(tooltipBar);\r\n\t\t\r\n\t\tadd(createNorthPanel(), BorderLayout.NORTH);\r\n\t\tadd(createCenterPanel(), BorderLayout.CENTER);\r\n\t\tadd(createSouthPanel(), BorderLayout.SOUTH);\r\n\t}\r\n\t\r\n\t/**\r\n\t * Returns a panel that contains combo-box of action categories which is used for filtering \r\n\t * the actions shown at the shortcuts editor table.\r\n\t */\r\n\tprivate JPanel createNorthPanel() {\r\n\t\tJPanel panel = new JPanel(new BorderLayout());\r\n\t\tpanel.setBorder(BorderFactory.createEmptyBorder());\r\n\t\t\r\n\t\tpanel.add(createFilteringPanel(), BorderLayout.WEST);\r\n\t\t\r\n\t\treturn panel;\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a panel that contains the shortcuts editor table.\r\n\t */\r\n\tprivate JPanel createCenterPanel() {\r\n\t\tJPanel panel = new JPanel(new GridLayout(1,0));\r\n\t\tshortcutsTable.setPreferredColumnWidths(new double[] {0.6, 0.2, 0.2});\r\n\t\tpanel.add(new JScrollPane(shortcutsTable));\r\n\t\treturn panel;\r\n\t}\r\n\t\r\n\t/**\r\n\t * Returns a panel that contain the tooltip bar and the shortcuts editor buttons below it.\r\n\t */\r\n\tprivate JPanel createSouthPanel() {\r\n\t\tJPanel panel = new JPanel();\r\n\t\tpanel.setBorder(BorderFactory.createEmptyBorder());\r\n\t\tpanel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));\r\n\t\t\r\n\t\tJPanel tooltipBarPanel = new JPanel(new BorderLayout());\r\n\t\ttooltipBarPanel.add(tooltipBar, BorderLayout.WEST);\r\n\t\t\r\n\t\tpanel.add(tooltipBarPanel);\r\n\t\tpanel.add(createButtonsPanel());\r\n\t\t\r\n\t\treturn panel;\r\n\t}\r\n\t\r\n\tprivate JPanel createButtonsPanel() {\r\n\t\tJPanel panel = new JPanel(new FlowLayout(FlowLayout.RIGHT));\r\n\t\tpanel.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));\r\n\t\t\r\n\t\tRemoveButton removeButton = new RemoveButton();\t\r\n\t\t\r\n\t\tfinal JButton restoreDefaultButton = new JButton();\r\n\t\trestoreDefaultButton.setAction(new AbstractAction(Translator.get(\"shortcuts_panel\" + \".restore_defaults\")) {\r\n\t\t\tpublic void actionPerformed(ActionEvent e) {\r\n\t\t\t\tshortcutsTable.restoreDefaults();\r\n\t\t\t}\r\n\t\t});\r\n\t\t\r\n\t\tpanel.add(removeButton);\r\n\t\tpanel.add(restoreDefaultButton);\r\n\t\t\r\n\t\treturn panel;\r\n\t}\r\n\r\n\t\r\n\tprivate JPanel createFilteringPanel() {\r\n\t\tJPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));\r\n\t\tpanel.setBorder(BorderFactory.createEmptyBorder());\r\n\t\tpanel.add(new JLabel(Translator.get(\"shortcuts_panel.show\") + \":\"));\r\n\t\t\r\n\t\tfinal TcComboBox<ActionCategory> combo = new TcComboBox<>();\r\n\t\tcombo.addItem(ActionCategory.ALL);\r\n\t    for (ActionCategory category : ActionProperties.getActionCategories()) {\r\n            combo.addItem(category);\r\n        }\r\n\t    \r\n\t    combo.addActionListener(e -> {\r\n                filter.setActionCategory((ActionCategory)combo.getSelectedItem());\r\n\t\t\t\tshortcutsTable.updateModel(filter);\r\n\t\t\t\ttooltipBar.showDefaultMessage();\r\n        });\r\n\r\n\t    combo.setSelectedIndex(0);\r\n\t\t\r\n\t\tpanel.add(combo);\r\n        panel.add(new JLabel(Translator.get(\"shortcuts_panel.search\") + \":\"));\r\n\r\n        final JTextField searchText = new JTextField(16);\r\n        panel.add(searchText);\r\n\r\n        JTextField shortcutText = new JTextField(15);\r\n        resetShortcutFilterText(shortcutText);\r\n        shortcutText.setHorizontalAlignment(JTextField.CENTER);\r\n        shortcutText.setEditable(false);\r\n        shortcutText.setBackground(ThemeCache.backgroundColors[ThemeCache.ACTIVE][ThemeCache.SELECTED]);\r\n        shortcutText.setForeground(ThemeCache.foregroundColors[ThemeCache.ACTIVE][ThemeCache.SELECTED][ThemeCache.PLAIN_FILE]);\r\n        // It is required to disable the traversal keys in order to support keys combination that include the TAB key\r\n        setFocusTraversalKeysEnabled(false);\r\n        panel.add(shortcutText);\r\n\r\n        addCategoryFilter(searchText, combo, shortcutText);\r\n        addSearchTextFilter(searchText, combo, shortcutText);\r\n        addShortcutFilter(shortcutText);\r\n\r\n\r\n        return panel;\r\n\t}\r\n\r\n    private void resetShortcutFilterText(JTextField shortcutText) {\r\n        shortcutText.setText(Translator.get(\"shortcuts_table.type_in_a_shortcut\"));\r\n    }\r\n\r\n    private void addShortcutFilter(final JTextField shortcutText) {\r\n        shortcutText.addKeyListener(new KeyListener() {\r\n            public void keyPressed(KeyEvent keyEvent) {\r\n                int keyCode = keyEvent.getKeyCode();\r\n                if (keyCode == KeyEvent.VK_SHIFT || keyCode == KeyEvent.VK_CONTROL || keyCode == KeyEvent.VK_ALT || keyCode == KeyEvent.VK_META) {\r\n                    return;\r\n                }\r\n\r\n                final KeyStroke pressedKeyStroke = KeyStroke.getKeyStrokeForEvent(keyEvent);\r\n\r\n                shortcutText.setText(KeyStrokeUtils.getKeyStrokeDisplayableRepresentation(pressedKeyStroke));\r\n                updateFilter(pressedKeyStroke);\r\n                keyEvent.consume();\r\n            }\r\n\r\n            public void keyReleased(KeyEvent e) {e.consume();}\r\n\r\n   \t\t\tpublic void keyTyped(KeyEvent e) {e.consume();}\r\n        });\r\n    }\r\n\r\n    private void resetShortcutFilterWhenFocusGained(JComponent componentGainingFocus, final JTextField searchText, final JComboBox<ActionCategory> categoryCombo, final JTextField shortcutText) {\r\n        componentGainingFocus.addFocusListener(new FocusListener() {\r\n            @Override\r\n            public void focusLost(FocusEvent e) {}\r\n            @Override\r\n            public void focusGained(FocusEvent e) {\r\n                updateFilter(searchText, categoryCombo, shortcutText);\r\n            }\r\n        });\r\n    }\r\n\r\n\r\n    private void addCategoryFilter(final JTextField searchText, final JComboBox<ActionCategory> combo, final JTextField shortcutText) {\r\n        combo.addActionListener(e -> updateFilter(searchText, combo, shortcutText));\r\n        resetShortcutFilterWhenFocusGained(combo, searchText, combo, shortcutText);\r\n\t}\r\n\r\n\r\n    private void addSearchTextFilter(final JTextField searchText, final JComboBox<ActionCategory> categoryCombo, final JTextField shortcutText) {\r\n        searchText.getDocument().addDocumentListener(new DocumentListener() {\r\n            @Override\r\n            public void removeUpdate(DocumentEvent e) {\r\n                updateFilter(searchText, categoryCombo, shortcutText);\r\n            }\r\n            @Override\r\n            public void insertUpdate(DocumentEvent e) {\r\n                updateFilter(searchText, categoryCombo, shortcutText);\r\n            }\r\n            @Override\r\n            public void changedUpdate(DocumentEvent e) {\r\n            }\r\n        });\r\n\r\n        resetShortcutFilterWhenFocusGained(searchText, searchText, categoryCombo, shortcutText);\r\n\t}\r\n\r\n    private void updateFilter(final JTextField searchText, final JComboBox<ActionCategory> categoryCombo, JTextField shortcutText) {\r\n        final ActionCategory selectedActionCategory = (ActionCategory) categoryCombo.getSelectedItem();\r\n        final String filterText = searchText.getText();\r\n\r\n        shortcutsTable.updateModel(new ShortcutsTable.ActionFilter() {\r\n            final Locale currentLang = Locale.forLanguageTag(getVariable(LANGUAGE));\r\n            @Override\r\n            public boolean accept(String actionId) {\r\n                return selectedActionCategory.contains(actionId) && (\r\n                    StringUtils.isNullOrBlank(filterText)\r\n                    || StringUtils.containsIgnoreCase(ActionProperties.getActionLabel(actionId), filterText, currentLang)\r\n                    || StringUtils.containsIgnoreCase(ActionProperties.getActionTooltip(actionId), filterText, currentLang)\r\n                    // also search for id's to find typical english computer terms even in non english languages\r\n                    || StringUtils.containsIgnoreCase(ActionProperties.getActionLabelKey(actionId), filterText, currentLang)\r\n                    || StringUtils.containsIgnoreCase(actionId, filterText, currentLang));\r\n            }\r\n        });\r\n        resetShortcutFilterText(shortcutText);\r\n        tooltipBar.showDefaultMessage();\r\n\t}\r\n\r\n   \tprivate void updateFilter(final KeyStroke pressedKeyStroke) {\r\n        shortcutsTable.updateModel(shortcutsTable.createCurrentAcceleratorsActionFilter(pressedKeyStroke));\r\n    }\r\n\t\t\r\n\t///////////////////////\r\n    // PrefPanel methods //\r\n    ///////////////////////\r\n\t\r\n\t@Override\r\n    protected void commit() {\r\n\t\tshortcutsTable.commitChanges();\r\n\t\tActionKeymapIO.setModified();\r\n\t}\r\n\t\r\n\tstatic class TooltipBar extends JLabel {\r\n\t\tprivate String lastActionTooltipShown;\r\n\t\tprivate final String DEFAULT_MESSAGE;\r\n\t\tprivate static final int MESSAGE_SHOWING_TIME = 3000;\r\n\t\tprivate MessageRemoverThread currentRemoverThread;\r\n\t\t\r\n\t\tTooltipBar() {\r\n\t\t\tDEFAULT_MESSAGE = Translator.get(\"shortcuts_panel.default_message\");\r\n\t\t\tFont tableFont = UIManager.getFont(\"TableHeader.font\");\r\n\t\t\tsetFont(new Font(tableFont.getName(), Font.BOLD, tableFont.getSize()));\r\n\t\t\tsetHorizontalAlignment(JLabel.LEFT);\r\n\t\t\tsetBorder(BorderFactory.createEmptyBorder(3, 5, 3, 5));\r\n\t\t\tsetText(DEFAULT_MESSAGE);\r\n\t\t}\r\n\t\t\r\n\t\tvoid showActionTooltip(String text) {\r\n\t\t\tsetText(lastActionTooltipShown = text == null ? \" \" : text);\r\n\t\t}\r\n\t\t\r\n\t\tvoid showDefaultMessage() {\r\n\t\t\tsetText(DEFAULT_MESSAGE);\r\n\t\t}\r\n\t\t\r\n\t\tvoid showErrorMessage(String text) {\r\n\t\t\tsetText(text);\r\n\t\t\tcreateMessageRemoverThread();\r\n\t\t}\r\n\t\t\r\n\t\tprivate void createMessageRemoverThread() {\r\n\t\t\tif (currentRemoverThread != null)\r\n\t\t\t\tcurrentRemoverThread.neutralize();\r\n\t\t\t(currentRemoverThread = new MessageRemoverThread()).start();\r\n\t\t}\r\n\t\t\r\n\t\tprivate class MessageRemoverThread extends Thread {\r\n\t\t\tprivate boolean stopped = false;\r\n\t\t\t\r\n\t\t\tvoid neutralize() {\r\n\t\t\t\tstopped = true;\r\n\t\t\t}\r\n\t\t\r\n\t\t\t@Override\r\n            public void run() {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tThread.sleep(MESSAGE_SHOWING_TIME);\r\n\t\t\t\t} catch (InterruptedException ignore) {\r\n                }\r\n\t\t\t\t\r\n\t\t\t\tif (!stopped) {\r\n\t\t\t\t\tshowActionTooltip(lastActionTooltipShown);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\tprivate class RemoveButton extends JButton implements ListSelectionListener, TableModelListener {\r\n\t\t\r\n\t\tRemoveButton() {\r\n\t\t\tsetEnabled(false);\r\n\t\t\tsetAction(new AbstractAction(Translator.get(\"remove\")) {\r\n\t\t\t\t\r\n\t\t\t\tpublic void actionPerformed(ActionEvent e) {\r\n\t\t\t\t\tshortcutsTable.setValueAt(ShortcutsTable.DELETE, shortcutsTable.getSelectedRow(), shortcutsTable.getSelectedColumn());\r\n\t\t\t\t\tshortcutsTable.repaint();\r\n\t\t\t\t\tshortcutsTable.requestFocus();\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\tshortcutsTable.getSelectionModel().addListSelectionListener(this);\r\n\t\t\tshortcutsTable.getColumnModel().getSelectionModel().addListSelectionListener(this);\r\n\t\t\tshortcutsTable.getModel().addTableModelListener(this);\r\n\t\t}\r\n\r\n\t\tpublic void valueChanged(ListSelectionEvent e) {\r\n\t\t\tupdateButtonState();\r\n\t\t}\r\n\r\n\t\tpublic void tableChanged(TableModelEvent e) {\r\n\t\t\tupdateButtonState();\t\t\t\r\n\t\t}\r\n\t\t\r\n\t\tprivate void updateButtonState() {\r\n\t\t\tint column = shortcutsTable.getSelectedColumn();\r\n\t\t\tint row = shortcutsTable.getSelectedRow();\r\n\t\t\tboolean canRemove = (column == ShortcutsTable.ACCELERATOR_COLUMN_INDEX || column == ShortcutsTable.ALTERNATE_ACCELERATOR_COLUMN_INDEX)\r\n\t\t\t\t\t\t\t\t&& row != -1 && shortcutsTable.getValueAt(shortcutsTable.getSelectedRow(), column) != null;\r\n\t\t\tsetEnabled(canRemove);\r\n\t\t}\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/general/ShortcutsTable.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.pref.general;\r\n\r\nimport java.awt.*;\r\nimport java.awt.event.FocusEvent;\r\nimport java.awt.event.FocusListener;\r\nimport java.awt.event.KeyEvent;\r\nimport java.awt.event.KeyListener;\r\nimport java.awt.image.BufferedImage;\r\nimport java.util.*;\r\nimport java.util.List;\r\n\r\nimport javax.swing.BorderFactory;\r\nimport javax.swing.DefaultCellEditor;\r\nimport javax.swing.ImageIcon;\r\nimport javax.swing.JTable;\r\nimport javax.swing.JTextField;\r\nimport javax.swing.JViewport;\r\nimport javax.swing.KeyStroke;\r\nimport javax.swing.event.DocumentEvent;\r\nimport javax.swing.event.DocumentListener;\r\nimport javax.swing.event.ListSelectionEvent;\r\nimport javax.swing.event.ListSelectionListener;\r\nimport javax.swing.table.DefaultTableModel;\r\nimport javax.swing.table.TableCellEditor;\r\nimport javax.swing.table.TableCellRenderer;\r\nimport javax.swing.table.TableModel;\r\n\r\nimport org.jetbrains.annotations.NotNull;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.commons.runtime.OsVersion;\r\nimport com.mucommander.commons.util.Pair;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.action.ActionDescriptor;\r\nimport com.mucommander.ui.action.ActionKeymap;\r\nimport com.mucommander.ui.action.ActionManager;\r\nimport com.mucommander.ui.action.ActionProperties;\r\nimport com.mucommander.ui.dialog.pref.component.PrefTable;\r\nimport com.mucommander.ui.dialog.pref.general.ShortcutsPanel.TooltipBar;\r\nimport com.mucommander.ui.icon.IconManager;\r\nimport com.mucommander.ui.main.table.CellLabel;\r\nimport com.mucommander.ui.table.CenteredTableHeaderRenderer;\r\nimport com.mucommander.ui.text.KeyStrokeUtils;\r\nimport com.mucommander.ui.theme.ColorChangedEvent;\r\nimport com.mucommander.ui.theme.FontChangedEvent;\r\nimport com.mucommander.ui.theme.Theme;\r\nimport com.mucommander.ui.theme.ThemeCache;\r\nimport com.mucommander.ui.theme.ThemeListener;\r\n\r\n/**\r\n * This class is the table in which the actions and their shortcuts are\r\n * present in the ShortcutsPanel.\r\n * \r\n * @author Arik Hadas, Johann Schmitz (johann@j-schmitz.net)\r\n */\r\npublic class ShortcutsTable extends PrefTable implements KeyListener, ListSelectionListener, FocusListener {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(ShortcutsTable.class);\r\n\t\r\n\t/** Base width and height of icons for a scale factor of 1 */\r\n    private final static int BASE_ICON_DIMENSION = 16;\r\n\r\n    private static final Stroke DOTTED_BORDER_STROKE = new BasicStroke(1.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER, 2.0f, new float[]{2.0f}, 0);\r\n\r\n    /** Transparent icon used to align non-locked themes with the others. */\r\n    private static final ImageIcon transparentIcon = new ImageIcon(new BufferedImage(BASE_ICON_DIMENSION, BASE_ICON_DIMENSION, BufferedImage.TYPE_INT_ARGB));\r\n\r\n\t/** Private object used to indicate that a delete operation was made */\r\n\tpublic static final Object DELETE = new Object();\r\n\t\r\n\tprivate final ShortcutsTableData data;\r\n\r\n\t/** Comparator of actions according to their labels */\r\n\tprivate static final Comparator<String> ACTIONS_COMPARATOR = (id1, id2) -> {\r\n        String label1 = ActionProperties.getActionLabel(id1);\r\n        if (label1 == null)\r\n            return 1;\r\n\r\n        String label2 = ActionProperties.getActionLabel(id2);\r\n        if (label2 == null)\r\n            return -1;\r\n\r\n        return label1.compareTo(label2);\r\n    };\r\n\t\r\n\t/** Last selected row in the table */\r\n\tprivate int lastSelectedRow = -1;\r\n\t\r\n\t/** The bar below the table in which messages can be displayed */\r\n\tprivate final TooltipBar tooltipBar;\r\n\t\r\n\t/** Number of mouse clicks required to enter cell's editing state */\r\n\tprivate static final int NUM_OF_CLICKS_TO_ENTER_EDITING_STATE = 2;\r\n\t\r\n\t/** Column indexes */\r\n\tprivate static final int ACTION_DESCRIPTION_COLUMN_INDEX     = 0;\r\n\tstatic final int ACCELERATOR_COLUMN_INDEX            = 1;\r\n\tstatic final int ALTERNATE_ACCELERATOR_COLUMN_INDEX  = 2;\r\n\r\n\t/** Number of columns in the table */\r\n\tprivate static final int NUM_OF_COLUMNS = 3;\r\n\t\r\n\t/** After the following time (msec) that cell is being in editing state \r\n\t *  and no pressing was made, the editing state is canceled */\r\n\tprivate static final int CELL_EDITING_STATE_PERIOD = 3000;\r\n\t\r\n\t/** Thread that cancel cell's editing state after CELL_EDITING_STATE_PERIOD time */\r\n\tprivate CancelEditingStateThread cancelEditingStateThread;\r\n\r\n\tprivate final ShortcutsTableCellRenderer cellRenderer;\r\n\t\r\n\tShortcutsTable(TooltipBar tooltipBar) {\r\n\t\tsuper();\r\n\t\tthis.tooltipBar = tooltipBar;\r\n\t\t\r\n\t\tsetModel(new KeymapTableModel(data = new ShortcutsTableData()));\r\n\t\t\r\n\t\tcellRenderer = new ShortcutsTableCellRenderer();\r\n\t\tsetShowGrid(false);\r\n\t\tsetIntercellSpacing(new Dimension(0,0));\r\n\t\tsetRowHeight(Math.max(getRowHeight(), BASE_ICON_DIMENSION + 2 * CellLabel.CELL_BORDER_HEIGHT));\r\n\t\tgetTableHeader().setReorderingAllowed(false);\r\n\t\tsetRowSelectionAllowed(false);\r\n\t\tsetAutoCreateColumnsFromModel(false);\r\n\t\tsetCellSelectionEnabled(false);\r\n\t\tsetColumnSelectionAllowed(false);\r\n\t\tsetDragEnabled(false);\r\n\t\tFontMetrics fm = getFontMetrics(getFont());\r\n\t\tint fontHeight = fm.getHeight();\r\n\t\tsetRowHeight(fontHeight);\r\n\r\n\t\tif (!usesTableHeaderRenderingProperties()) {\r\n\t\t\tCenteredTableHeaderRenderer renderer = new CenteredTableHeaderRenderer();\r\n\t\t\tgetColumnModel().getColumn(ACTION_DESCRIPTION_COLUMN_INDEX).setHeaderRenderer(renderer);\r\n\t\t\tgetColumnModel().getColumn(ACCELERATOR_COLUMN_INDEX).setHeaderRenderer(renderer);\r\n\t\t\tgetColumnModel().getColumn(ALTERNATE_ACCELERATOR_COLUMN_INDEX).setHeaderRenderer(renderer);\r\n\t\t}\r\n\r\n\t\tputClientProperty(\"terminateEditOnFocusLost\", Boolean.TRUE);\r\n\t\t\r\n\t\taddKeyListener(this);\r\n\t\taddFocusListener(this);\r\n\t\tgetSelectionModel().addListSelectionListener(this);\r\n\t\tgetColumnModel().getSelectionModel().addListSelectionListener(this);\r\n\t}\r\n\r\n    /**\r\n     * Paints a dotted border of the specified width, height and {@link Color}, and using the given {@link Graphics}\r\n     * object.\r\n     *\r\n     * @param g Graphics object to use for painting\r\n     * @param width border width\r\n     * @param height border height\r\n     * @param color border color\r\n     */\r\n    private static void paintDottedBorder(Graphics g, int width, int height, Color color) {\r\n        Graphics2D g2 = (Graphics2D) g;\r\n        g2.setStroke(DOTTED_BORDER_STROKE);\r\n        g2.setColor(color);\r\n\r\n        g2.drawLine(0, 0, width, 0);\r\n        g2.drawLine(0, height - 1, width, height - 1);\r\n        g2.drawLine(0, 0, 0, height - 1);\r\n        g2.drawLine(width-1, 0, width-1, height - 1);\r\n    }\r\n\r\n    private static boolean usesTableHeaderRenderingProperties() {\r\n        return OsFamily.MAC_OS_X.isCurrent() && OsVersion.MAC_OS_X_10_5.isCurrentOrHigher();\r\n    }\r\n    \r\n    /** \r\n     * Assumes table is contained in a JScrollPane. Scrolls the\r\n     * cell (rowIndex, vColIndex) so that it is visible within the viewport.\r\n \t*/\r\n\tprivate void scrollToVisible(int rowIndex, int vColIndex) {\r\n    \tif (!(getParent() instanceof JViewport viewport)) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n        // This rectangle is relative to the table where the\r\n        // northwest corner of cell (0,0) is always (0,0).\r\n        Rectangle rect = getCellRect(rowIndex, vColIndex, true);\r\n    \r\n        // The location of the viewport relative to the table\r\n        Point pt = viewport.getViewPosition();\r\n    \r\n        // Translate the cell location so that it is relative\r\n        // to the view, assuming the northwest corner of the\r\n        // view is (0,0)\r\n        rect.setLocation(rect.x-pt.x, rect.y-pt.y);\r\n    \r\n        // Scroll the area into view\r\n        viewport.scrollRectToVisible(rect);\r\n    }\r\n\r\n\t/**\r\n\t * create thread that will cancel the editing state of the given TableCellEditor\r\n\t * after CELL_EDITING_STATE_PERIOD time in which with no pressing was made.\r\n\t */\r\n\tprivate void createCancelEditingStateThread(TableCellEditor cellEditor) {\r\n\t\tif (cancelEditingStateThread != null) {\r\n\t\t\tcancelEditingStateThread.neutralize();\r\n\t\t}\r\n\t\t(cancelEditingStateThread = new CancelEditingStateThread(cellEditor)).start();\r\n\t}\r\n\t\r\n\t@Override\r\n    public TableCellRenderer getCellRenderer(int row, int column) {\r\n\t\treturn cellRenderer;\r\n\t}\r\n\t\r\n\t@Override\r\n    public void valueChanged(ListSelectionEvent e) {\r\n\t\tsuper.valueChanged(e);\r\n\t\t// Selection might be changed, update tooltip\r\n\t\tint selectedRow = getSelectedRow();\r\n\t\tif (selectedRow == -1) {// no row is selected\r\n\t\t\ttooltipBar.showDefaultMessage();\r\n\t\t} else {\r\n\t\t\ttooltipBar.showActionTooltip(data.getCurrentTooltip());\r\n\t\t}\r\n\t}\r\n\t\r\n\tvoid updateModel(ActionFilter filter) {\r\n\t\tdata.filter(filter);\r\n\t}\r\n\r\n\tActionFilter createCurrentAcceleratorsActionFilter(KeyStroke accelerator) {\r\n\t\treturn data.new CurrentActionAccceleratorsFilter(accelerator);\r\n\t}\r\n\r\n\t/**\r\n\t * Override this method so that calls for SetModel function outside this class\r\n\t * won't get to setModel(KeymapTableModel model) function.\r\n\t */\r\n\t@Override\r\n    public void setModel(@NotNull TableModel model) {\r\n\t\tsuper.setModel(model);\r\n\t}\r\n\t\r\n\tpublic boolean hasChanged() { return data.hasChanged(); }\r\n\t\r\n\t@Override\r\n    public TableCellEditor getCellEditor(int row, int column) {\r\n\t\treturn new KeyStrokeCellEditor(new RecordingKeyStrokeField((KeyStroke) getValueAt(row, column)));\r\n\t}\r\n\t\r\n\t/**\r\n\t * This method updates ActionKeymap with the modified shortcuts.\r\n\t */\r\n\tvoid commitChanges() {\r\n\t\tdata.submitChanges();\r\n\t}\r\n\t\r\n\tvoid restoreDefaults() {\r\n\t\tdata.restoreDefaultAccelerators();\r\n\t}\r\n\t\r\n\t@Override\r\n\tpublic void focusGained(FocusEvent e) {\r\n\t\tint currentSelectedRow = getSelectedRow();\r\n\t\tif (lastSelectedRow != currentSelectedRow)\r\n\t\t\ttooltipBar.showActionTooltip(data.getCurrentTooltip());\r\n\t\tlastSelectedRow = currentSelectedRow;\r\n\t}\r\n\r\n\tpublic void focusLost(FocusEvent e) { }\r\n\t\r\n\t@Override\r\n\tpublic void keyPressed(KeyEvent e) {\r\n\t\tint keyCode = e.getKeyCode();\r\n\t\tif (keyCode == KeyEvent.VK_ENTER) {\r\n\t\t\tif (editCellAt(getSelectedRow(), getSelectedColumn()))\r\n\t\t\t\tgetEditorComponent().requestFocusInWindow();\r\n\t\t\te.consume();\r\n\t\t}\r\n\t\telse if (keyCode == KeyEvent.VK_DELETE || keyCode == KeyEvent.VK_BACK_SPACE) {\r\n\t\t\tsetValueAt(DELETE, getSelectedRow(), getSelectedColumn());\r\n\t\t\trepaint();\r\n\t\t\te.consume();\r\n\t\t}\r\n\t\telse if (keyCode != KeyEvent.VK_LEFT && keyCode != KeyEvent.VK_RIGHT && keyCode != KeyEvent.VK_UP &&\r\n\t\t\t     keyCode != KeyEvent.VK_DOWN && keyCode != KeyEvent.VK_HOME  && keyCode != KeyEvent.VK_END &&\r\n\t\t\t     keyCode != KeyEvent.VK_F2   && keyCode != KeyEvent.VK_ESCAPE)\r\n\t\t\te.consume();\r\n\t}\r\n\r\n\tpublic void keyReleased(KeyEvent e) {}\r\n\t\r\n\tpublic void keyTyped(KeyEvent e) {}\r\n\t\r\n\tpublic static abstract class ActionFilter {\r\n\t\tpublic abstract boolean accept(String actionId);\r\n\t}\r\n\t\r\n\r\n\t/**\r\n\t * Helper Classes\r\n\t */\r\n\tprivate class KeyStrokeCellEditor extends DefaultCellEditor implements TableCellEditor {\r\n\t\t\r\n\t\tRecordingKeyStrokeField rec;\r\n\t\t\r\n\t\tKeyStrokeCellEditor(RecordingKeyStrokeField rec) {\r\n\t\t\tsuper(rec);\r\n\t\t\tthis.rec = rec;\r\n\t\t\trec.setSelectionColor(rec.getBackground());\r\n\t\t\trec.setSelectedTextColor(rec.getForeground());\r\n\t\t\trec.getDocument().addDocumentListener(new DocumentListener() {\r\n\t\t\t\tpublic void insertUpdate(DocumentEvent e) {\r\n\t\t\t\t\t// quit editing state after text is written to the text field.\r\n\t\t\t\t\tstopCellEditing();\r\n\t\t\t\t}\r\n\t\t\t\tpublic void changedUpdate(DocumentEvent e) {}\r\n\t\t\t\tpublic void removeUpdate(DocumentEvent e) {}\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\tsetClickCountToStart(NUM_OF_CLICKS_TO_ENTER_EDITING_STATE);\r\n\t\t\t\r\n\t\t\tcreateCancelEditingStateThread(this);\r\n\t\t}\r\n\r\n\t\t@Override\r\n        public Component getTableCellEditorComponent(JTable table, Object value,\r\n                boolean isSelected, int row, int col) {\r\n            return rec;\r\n        }\r\n \r\n        @Override\r\n        public Object getCellEditorValue() {\r\n        \treturn rec.getLastKeyStroke();\r\n        }\r\n\t}\r\n\t\r\n\tprivate static class CancelEditingStateThread extends Thread {\r\n\t\tprivate boolean stopped = false;\r\n\t\tprivate final TableCellEditor cellEditor;\r\n\r\n\t\tCancelEditingStateThread(TableCellEditor cellEditor) {\r\n\t\t\tthis.cellEditor = cellEditor;\r\n\t\t}\r\n\t\t\r\n\t\tvoid neutralize() {\r\n\t\t\tstopped = true;\r\n\t\t}\r\n\t\t\r\n\t\t@Override\r\n        public void run() {\r\n        \ttry {\r\n\t\t\t\tThread.sleep(CELL_EDITING_STATE_PERIOD);\r\n\t\t\t} catch (InterruptedException ignore) {}\r\n\t\t\t\r\n\t\t\tif (!stopped && cellEditor != null)\r\n\t\t\t\tcellEditor.stopCellEditing();\r\n        }\r\n\t}\r\n\t\r\n\tprivate class KeymapTableModel extends DefaultTableModel {\t\r\n\t\tprivate final ShortcutsTableData tableData;\r\n\r\n\t\tprivate KeymapTableModel(ShortcutsTableData data) {\r\n\t\t\tsuper(data.getTableData(), new String[] {Translator.get(\"shortcuts_table.action_description\"),\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t Translator.get(\"shortcuts_table.shortcut\"),\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t Translator.get(\"shortcuts_table.alternate_shortcut\")});\r\n\t\t\tthis.tableData = data;\r\n\t\t}\r\n\r\n\t\t@Override\r\n        public boolean isCellEditable(int row, int column) {\r\n            return switch (column) {\r\n                case ACTION_DESCRIPTION_COLUMN_INDEX -> false;\r\n                case ACCELERATOR_COLUMN_INDEX, ALTERNATE_ACCELERATOR_COLUMN_INDEX -> true;\r\n                default -> false;\r\n            };\r\n\t\t}\r\n\t\t\r\n\t\t@Override\r\n        public Object getValueAt(int row, int column) {\r\n\t\t\treturn tableData.getTableData(row, column);\r\n\t\t}\r\n\r\n\t\t@Override\r\n        public void setValueAt(Object value, int row, int column) {\r\n\t\t\t// if no keystroke was pressed\r\n\t\t\tif (value == null)\r\n\t\t\t\treturn;\r\n\t\t\t// if the user pressed a keystroke that is used to indicate a delete operation should be made\r\n\t\t\telse if (value == DELETE)\r\n\t\t\t\tvalue = null;\r\n\t\t\t\t\r\n\t\t\tKeyStroke typedKeyStroke = (KeyStroke) value;\r\n\t\t\tswitch (column){\r\n\t\t\t\tcase ACCELERATOR_COLUMN_INDEX:\r\n\t\t\t\t\ttableData.setAccelerator(typedKeyStroke, row);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase ALTERNATE_ACCELERATOR_COLUMN_INDEX:\r\n\t\t\t\t\ttableData.setAlternativeAccelerator(typedKeyStroke, row);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tdefault:\r\n\t\t\t\t\tLOGGER.debug(\"Unexpected column index: \" + column);\r\n\t\t\t}\r\n\r\n\t\t\tfireTableCellUpdated(row, column);\r\n\t\t\t\r\n\t\t\tLOGGER.trace(\"Value: \" + value + \", row: \" + row + \", col: \" + column);\r\n\t\t}\r\n\t}\r\n\t\r\n\tprivate class RecordingKeyStrokeField extends JTextField implements KeyListener {\r\n\t\t\r\n\t\t// The last KeyStroke that was entered to the field.\r\n\t\t// Before any keystroke is entered, it contains the keystroke appearing in the cell before entering the editing state.\r\n\t\tprivate KeyStroke lastKeyStroke;\r\n\t\t\r\n\t\tRecordingKeyStrokeField(KeyStroke currentKeyStroke) {\r\n\t\t\tsuper(Translator.get(\"shortcuts_table.type_in_a_shortcut\"));\r\n\t\t\t\r\n\t\t\tlastKeyStroke = currentKeyStroke;\t\t\t\r\n\t\t\tsetBorder(BorderFactory.createEmptyBorder());\r\n\t\t\tsetHorizontalAlignment(JTextField.CENTER);\r\n\t\t\tsetEditable(false);\r\n\t\t\tsetBackground(ThemeCache.backgroundColors[ThemeCache.ACTIVE][ThemeCache.SELECTED]);\r\n\t\t\tsetForeground(ThemeCache.foregroundColors[ThemeCache.ACTIVE][ThemeCache.SELECTED][ThemeCache.PLAIN_FILE]);\r\n\t\t\taddKeyListener(this);\r\n\t\t\t// It is required to disable the traversal keys in order to support keys combination that include the TAB key\r\n\t\t\tsetFocusTraversalKeysEnabled(false);\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * \r\n\t\t * @return the last KeyStroke the user entered to the field.\r\n\t\t */\r\n\t\tKeyStroke getLastKeyStroke() { return lastKeyStroke; }\r\n\r\n\r\n        ////////////////////////\r\n        // Overridden methods //\r\n        ////////////////////////\r\n\r\n        @Override\r\n        protected void paintBorder(Graphics g) {\r\n            paintDottedBorder(g, getWidth(), getHeight(), ThemeCache.backgroundColors[ThemeCache.ACTIVE][ThemeCache.NORMAL]);\r\n        }\r\n\r\n\t\t/////////////////////////////\r\n\t\t//// KeyListener methods ////\r\n\t\t/////////////////////////////\r\n\r\n\t\tpublic void keyPressed(KeyEvent keyEvent) {\r\n\t\t\tLOGGER.trace(\"keyModifiers={} keyCode={}\", keyEvent.getModifiersEx(), keyEvent.getKeyCode());\r\n\r\n\t        int keyCode = keyEvent.getKeyCode();\r\n\t        if(keyCode==KeyEvent.VK_SHIFT || keyCode==KeyEvent.VK_CONTROL || keyCode==KeyEvent.VK_ALT || keyCode==KeyEvent.VK_META)\r\n\t            return;\r\n\r\n\t        KeyStroke pressedKeyStroke = KeyStroke.getKeyStrokeForEvent(keyEvent);\r\n\r\n\t        if (pressedKeyStroke.equals(lastKeyStroke)) {\r\n\t        \tTableCellEditor activeCellEditor = getCellEditor();\r\n        \t\tif (activeCellEditor!= null)\r\n        \t\t\tactiveCellEditor.stopCellEditing();\r\n\t        }\r\n\t        else {\r\n\t        \tString actionId;\r\n\t        \tif ((actionId = data.contains(pressedKeyStroke)) != null) {\r\n\t        \t\tString errorMessage = \"The shortcut [\" + KeyStrokeUtils.getKeyStrokeDisplayableRepresentation(pressedKeyStroke)\r\n\t    \t\t\t+ \"] is already assigned to '\" + ActionProperties.getActionDescription(actionId) + \"'\";\r\n\t        \t\ttooltipBar.showErrorMessage(errorMessage);\r\n\t        \t\tcreateCancelEditingStateThread(getCellEditor());\r\n\t        \t}\r\n\t        \telse {\r\n\t        \t\tlastKeyStroke = pressedKeyStroke;\r\n\t\t        \tsetText(KeyStrokeUtils.getKeyStrokeDisplayableRepresentation(lastKeyStroke));\r\n\t        \t}\r\n\t        }\r\n\t        \r\n\t        keyEvent.consume();\r\n\t\t}\r\n\t\t\t\r\n\t\tpublic void keyReleased(KeyEvent e) {e.consume();}\r\n\r\n\t\tpublic void keyTyped(KeyEvent e) {e.consume();}\r\n\t}\r\n\t\r\n\tprivate class ShortcutsTableData {\r\n\r\n\t\tpublic class CurrentActionAccceleratorsFilter extends ActionFilter {\r\n\t\t\tprivate final KeyStroke accelerator;\r\n\r\n\t\t\tCurrentActionAccceleratorsFilter(KeyStroke accelerator) {\r\n\t\t\t\tthis.accelerator = accelerator;\r\n\t\t\t}\r\n\r\n\t\t\t@Override\r\n\t\t\tpublic boolean accept(String actionId) {\r\n\t\t\t\tMap<Integer, Object> actionProperties = db.get(actionId);\r\n\t\t\t\treturn ShortcutsTableData.this.equals(accelerator,\r\n\t\t\t\t        actionProperties.get(ShortcutsTableData.this.accelerator))\r\n\t\t\t\t        || ShortcutsTableData.this.equals(accelerator,\r\n\t\t\t\t                actionProperties.get(ShortcutsTableData.this.alt_accelerator));\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tprivate Object[][] data;\r\n\t\tprivate String[] actionIds;\r\n\t\tprivate String[] descriptions;\r\n\t\t\r\n\t\tprivate final Integer description = 0;\r\n\t\tprivate final Integer accelerator = 1;\r\n\t\tprivate final Integer alt_accelerator = 2;\r\n\t\tprivate final Integer tooltips = 3;\r\n\r\n\t\tprivate final List<String> allActionIds;\r\n\t\tprivate final HashMap<String, Map<Integer, Object>> db;\r\n\t\t\r\n\t\tShortcutsTableData() {\r\n            allActionIds = new ArrayList<>();\r\n            Iterator<String> iterator = ActionManager.getActionIds();\r\n            while(iterator.hasNext())\r\n                allActionIds.add(iterator.next());\r\n\t\t\tallActionIds.sort(ACTIONS_COMPARATOR);\r\n\t\t\t\r\n\t\t\tfinal int nbActions = allActionIds.size();\r\n\t\t\tdb = new HashMap<>(nbActions);\r\n\r\n\t\t\tint nbRows = allActionIds.size();\r\n\t\t\tdata = new Object[nbRows][NUM_OF_COLUMNS];\r\n\r\n            final Insets insets = new Insets(0, 4, 0, 4);\r\n            for(String actionId : allActionIds) {\r\n\t\t\t\tActionDescriptor actionDescriptor = ActionProperties.getActionDescriptor(actionId);\r\n\t\t\t\t\r\n\t\t\t\tMap<Integer, Object> actionProperties = new HashMap<>();\r\n\t\t\t\t\r\n\t\t\t\tImageIcon actionIcon = actionDescriptor.getIcon();\r\n\t\t\t\tif (actionIcon == null)\r\n\t\t\t\t\tactionIcon = transparentIcon;\r\n\t\t\t\tString actionLabel = actionDescriptor.getLabel();\r\n\t\t\t\t\r\n\t\t\t\t// 0 -> action's icon & name pair\r\n\t\t\t\tactionProperties.put(description, new Pair<>(IconManager.getPaddedIcon(actionIcon, insets), actionLabel));\r\n\t\t\t\t// 1 -> action's accelerator\r\n\t\t\t\tactionProperties.put(accelerator, ActionKeymap.getAccelerator(actionId));\r\n\t\t\t\t// 2 -> action's alternate accelerator\r\n\t\t\t\tactionProperties.put(alt_accelerator, ActionKeymap.getAlternateAccelerator(actionId));\r\n\t\t\t\t// 3 -> action's description\r\n\t\t\t\tactionProperties.put(tooltips, actionDescriptor.getDescription());\r\n\t\t\t\t\r\n\t\t\t\tdb.put(actionId, actionProperties);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tpublic void filter(ActionFilter filter) {\r\n\t\t\tList<String> filteredActionIds = filter(allActionIds, filter);\r\n\r\n\t\t\t// Build the table data\r\n\t\t\tint nbRows = filteredActionIds.size();\r\n\t\t\t\r\n\t\t\tactionIds = new String[nbRows];\r\n\t\t\tdescriptions = new String[nbRows];\r\n\t\t\tdata = new Object[nbRows][NUM_OF_COLUMNS];\r\n\t\t\t\r\n\t\t\tfor (int i = 0; i < nbRows; ++i) {\r\n\t\t\t\tString actionId = filteredActionIds.get(i);\r\n\t\t\t\tactionIds[i] = actionId;\r\n\t\t\t\tActionDescriptor actionDescriptor = ActionProperties.getActionDescriptor(actionId);\r\n\t\t\t\t\r\n\t\t\t\tdata[i][ACTION_DESCRIPTION_COLUMN_INDEX] = db.get(actionId).get(this.description);\r\n\t\t\t\t\r\n\t\t\t\tKeyStroke accelerator = (KeyStroke) db.get(actionId).get(this.accelerator);\r\n\t\t\t\tsetAccelerator(accelerator, i);\r\n\t\t\t\t\r\n\t\t\t\tKeyStroke alternativeAccelerator = (KeyStroke) db.get(actionId).get(this.alt_accelerator);\r\n\t\t\t\tsetAlternativeAccelerator(alternativeAccelerator, i);\r\n\t\t\t\t\r\n\t\t\t\tdescriptions[i] = actionDescriptor.getDescription();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tShortcutsTable.this.clearSelection();\r\n\t\t\t((DefaultTableModel) getModel()).setRowCount(data.length);\r\n\t\t\tShortcutsTable.this.repaint();\r\n\t\t\tShortcutsTable.this.scrollToVisible(0, 0);\r\n\t\t}\r\n\t\t\r\n\t\tObject[][] getTableData() { return data; }\r\n\t\t\r\n\t\tObject getTableData(int row, int col) { return data[row][col]; }\r\n\t\t\r\n\t\tString getCurrentTooltip() { return descriptions[getSelectedRow()]; }\r\n\t\t\r\n\t\tString getActionId(int row) { return actionIds[row]; }\r\n\t\t\r\n\t\tboolean hasChanged() {\r\n            for (String actionId : db.keySet()) {\r\n                Map<Integer, Object> actionProperties = db.get(actionId);\r\n                if (!equals(actionProperties.get(this.accelerator), ActionKeymap.getAccelerator(actionId)) ||\r\n                        !equals(actionProperties.get(this.alt_accelerator), ActionKeymap.getAlternateAccelerator(actionId)))\r\n                    return true;\r\n            }\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\t\r\n\t\tvoid restoreDefaultAccelerators() {\r\n            for (String actionId : allActionIds) {\r\n                (db.get(actionId)).put(this.accelerator, ActionProperties.getDefaultAccelerator(actionId));\r\n                (db.get(actionId)).put(this.alt_accelerator, ActionProperties.getDefaultAlternativeAccelerator(actionId));\r\n            }\r\n\t\t\t\r\n\t\t\tint nbRows = actionIds.length;\r\n\t\t\tfor (int i=0; i<nbRows; ++i) {\r\n\t\t\t\tdata[i][ACCELERATOR_COLUMN_INDEX] = db.get(actionIds[i]).get(this.accelerator);\r\n\t\t\t\tdata[i][ALTERNATE_ACCELERATOR_COLUMN_INDEX] = db.get(actionIds[i]).get(this.alt_accelerator);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t((DefaultTableModel) getModel()).fireTableDataChanged();\r\n\t\t}\r\n\t\t\r\n\t\tvoid submitChanges() {\r\n            for (String actionId : db.keySet()) {\r\n                Map<Integer, Object> actionProperties = db.get(actionId);\r\n                KeyStroke accelerator = (KeyStroke) actionProperties.get(this.accelerator);\r\n                KeyStroke alternateAccelerator = (KeyStroke) actionProperties.get(this.alt_accelerator);\r\n\r\n                // If action's accelerators differ from its saved accelerators, register them.\r\n                if (!equals(accelerator, ActionKeymap.getAccelerator(actionId)) ||\r\n                        !equals(alternateAccelerator, ActionKeymap.getAlternateAccelerator(actionId)))\r\n                    ActionKeymap.changeActionAccelerators(actionId, accelerator, alternateAccelerator);\r\n            }\r\n\t\t}\r\n\t\t\r\n\t\tpublic String contains(KeyStroke accelerator) {\r\n\t\t\tif (accelerator != null) {\r\n                for (String actionId : db.keySet()) {\r\n                    if (accelerator.equals(db.get(actionId).get(this.accelerator)) ||\r\n                            accelerator.equals(db.get(actionId).get(this.alt_accelerator)))\r\n                        return actionId;\r\n                }\r\n\t\t\t}\r\n\t\t\treturn null;\r\n\t\t}\r\n\t\t\r\n\t\tprivate void setAccelerator(KeyStroke accelerator, int row) {\r\n\t\t\tdata[row][ACCELERATOR_COLUMN_INDEX] = accelerator;\r\n\t\t\tdb.get(getActionId(row)).put(this.accelerator, accelerator);\r\n\t\t}\r\n\t\t\r\n\t\tprivate void setAlternativeAccelerator(KeyStroke altAccelerator, int row) {\r\n\t\t\tdata[row][ALTERNATE_ACCELERATOR_COLUMN_INDEX] = altAccelerator;\r\n\t\t\tdb.get(getActionId(row)).put(this.alt_accelerator, altAccelerator);\r\n\t\t}\r\n\t\t\r\n\t\tprivate List<String> filter(List<String> actionIds, ActionFilter filter) {\r\n\t\t\tList<String> filteredActionsList = new LinkedList<>();\r\n            for (String actionId : actionIds) {\r\n                // Discard actions that are parameterized, and those that are rejected by the IMAGE_FILTER\r\n                if (!ActionProperties.getActionDescriptor(actionId).isParameterized() && filter.accept(actionId))\r\n                    filteredActionsList.add(actionId);\r\n            }\r\n\t\t\treturn filteredActionsList;\r\n\t\t}\r\n\t\t\r\n\t\tprivate boolean equals(Object obj1, Object obj2) {\r\n\t\t\tif (obj1 == null)\r\n\t\t\t\treturn obj2 == null;\r\n\t\t\treturn obj1.equals(obj2);\r\n\t\t}\r\n\t}\r\n\t\r\n\tprivate class ShortcutsTableCellRenderer implements TableCellRenderer, ThemeListener {\r\n\t\t/** Custom JLabel that render specific column cells */\r\n\t    private final DotBorderedCellLabel[] cellLabels = new DotBorderedCellLabel[NUM_OF_COLUMNS];\r\n\t    \r\n\t    ShortcutsTableCellRenderer() {\r\n\t    \tfor(int i=0; i<NUM_OF_COLUMNS; ++i)\r\n\t            cellLabels[i] = new DotBorderedCellLabel();\r\n\r\n\t    \t// Set labels' font.\r\n\t        setCellLabelsFont(ThemeCache.tableFont);\r\n\t    \t\r\n\t    \tcellLabels[ACTION_DESCRIPTION_COLUMN_INDEX].setHorizontalAlignment(CellLabel.LEFT);\r\n\t    \tcellLabels[ACCELERATOR_COLUMN_INDEX].setHorizontalAlignment(CellLabel.CENTER);\r\n\t    \tcellLabels[ALTERNATE_ACCELERATOR_COLUMN_INDEX].setHorizontalAlignment(CellLabel.CENTER);\r\n\t    \t\r\n\t    \t// Listens to certain configuration variables\r\n\t        ThemeCache.addThemeListener(this);\r\n\t    }\r\n\t\t\r\n\t    /**\r\n\t     * Sets CellLabels' font to the current one.\r\n\t     */\r\n\t    private void setCellLabelsFont(Font newFont) {\r\n\t        // Set custom font\r\n\t        for (int i=0; i < NUM_OF_COLUMNS; ++i) {\r\n\t\t\t\tcellLabels[i].setFont(newFont);\r\n\t\t\t}\r\n\t    }\r\n\t    \r\n\t\tpublic Component getTableCellRendererComponent(JTable table, Object value,  boolean isSelected, boolean hasFocus, int rowIndex, int vColIndex) {\r\n\t\t\tint columnId = convertColumnIndexToModel(vColIndex);\r\n\t\t\tDotBorderedCellLabel label = cellLabels[columnId];\r\n\t\t\t\r\n\t\t\t// action's icon column: return ImageIcon instance\r\n\t\t\tif (columnId == ACTION_DESCRIPTION_COLUMN_INDEX) {\r\n\t\t\t\t@SuppressWarnings(\"unchecked\")\r\n\t\t\t\tPair<ImageIcon, String> description = (Pair<ImageIcon, String>) value;\r\n\t\t\t\tlabel.setIcon(description.first);\r\n\t\t\t\tlabel.setText(description.second);\r\n\t\t\t\t\r\n\t\t\t\t// set cell's foreground color\r\n\t\t\t\tlabel.setForeground(ThemeCache.foregroundColors[ThemeCache.ACTIVE][ThemeCache.NORMAL][ThemeCache.PLAIN_FILE]);\r\n\t\t\t}\r\n\t\t\t// Any other column\r\n\t\t\telse {\r\n\t\t\t\tfinal KeyStroke key = (KeyStroke) value;\r\n\t\t\t\tString text = key == null ? \"\" : KeyStrokeUtils.getKeyStrokeDisplayableRepresentation(key);\r\n\t\t\t\t\r\n\t\t\t\t// If component's preferred width is bigger than column width then the component is not entirely\r\n\t            // visible so we set a tooltip text that will display the whole text when mouse is over the component\r\n\t            if (table.getColumnModel().getColumn(vColIndex).getWidth() < label.getPreferredSize().getWidth()) {\r\n\t\t\t\t\tlabel.setToolTipText(text);\r\n\t\t\t\t} else { // Have to set it to null otherwise the defaultRender sets the tooltip text to the last one specified\r\n\t\t\t\t\tlabel.setToolTipText(null);\r\n\t\t\t\t}\r\n\t            \r\n\t            // Set label's text\r\n\t\t\t\tlabel.setText(text);\r\n\t\t\t\t// set cell's foreground color\r\n\t\t\t\tif (key != null) {\r\n\t\t\t\t\tboolean customized = switch (columnId) {\r\n                        case ACCELERATOR_COLUMN_INDEX ->\r\n                                !key.equals(ActionProperties.getDefaultAccelerator(data.getActionId(rowIndex)));\r\n                        case ALTERNATE_ACCELERATOR_COLUMN_INDEX ->\r\n                                !key.equals(ActionProperties.getDefaultAlternativeAccelerator(data.getActionId(rowIndex)));\r\n                        default -> false;\r\n                    };\r\n\r\n                    label.setForeground(ThemeCache.foregroundColors[ThemeCache.ACTIVE][ThemeCache.NORMAL][customized ? ThemeCache.PLAIN_FILE : ThemeCache.HIDDEN_FILE]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// set outline for the focused cell\r\n\t\t\tlabel.setOutline(hasFocus ? ThemeCache.backgroundColors[ThemeCache.ACTIVE][ThemeCache.SELECTED] : null);\r\n\t\t\t// set cell's background color\r\n\t\t\tlabel.setBackground(ThemeCache.backgroundColors[ThemeCache.ACTIVE][rowIndex % 2 == 0 ? ThemeCache.NORMAL : ThemeCache.ALTERNATE]);\r\n\t\t\t\r\n\t\t\treturn label;\r\n\t\t}\r\n\t\t\r\n\t\t// - Theme listening -------------------------------------------------------------\r\n\t    // -------------------------------------------------------------------------------\r\n\t    /**\r\n\t     * Receives theme color changes notifications.\r\n\t     */\r\n\t    public void colorChanged(ColorChangedEvent event) { }\r\n\r\n\t    /**\r\n\t     * Receives theme font changes notifications.\r\n\t     */\r\n\t    public void fontChanged(FontChangedEvent event) {\r\n\t        if (event.getFontId() == Theme.FILE_TABLE_FONT) {\r\n\t            setCellLabelsFont(ThemeCache.tableFont);\r\n\t        }\r\n\t    }\r\n\t}\r\n\t\r\n\t/**\r\n\t * CellLabel with a dotted outline.\r\n\t */\r\n\tprivate static class DotBorderedCellLabel extends CellLabel {\r\n\r\n\t\t@Override\r\n        protected void paintOutline(Graphics g) {\r\n            paintDottedBorder(g, getWidth(), getHeight(), outlineColor);\r\n\t\t}\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/general/ThemeNameDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.pref.general;\n\nimport com.mucommander.ui.button.ButtonChoicePanel;\nimport com.mucommander.ui.dialog.FocusDialog;\nimport com.mucommander.ui.layout.XAlignedComponentPanel;\nimport com.mucommander.ui.layout.YBoxPanel;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\n\n/**\n * Dialog used to ask a new theme name to the user.\n * @author Nicolas Rinaudo\n */\npublic class ThemeNameDialog extends FocusDialog implements ActionListener {\n    // - UI fields -----------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    /** Field in which the user will enter the new name. */\n    private JTextField nameField;\n    /** Ok button. */\n    private JButton okButton;\n    /** Cancel button. */\n    private JButton cancelButton;\n\n    /** Whether the dialog was closed by the ok button or by cancelling it. */\n    private boolean wasValidated;\n    /** Maximum dimensions for the dialog. */\n    private final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(480,10000);\t\n\n\n\n    /**\n     * Creates a new name dialog with the specified owner.\n     * @param owner component that will own this dialog.\n     * @param name  current name.\n     */\n    public ThemeNameDialog(Frame owner, String name) {\n        super(owner, i18n(\"rename\"), owner);\n        init(name);\n    }\n\n    /**\n     * Creates a new name dialog with the specified owner.\n     * @param owner component that will own this dialog.\n     * @param name  current name.\n     */\n    public ThemeNameDialog(Dialog owner, String name) {\n        super(owner, i18n(\"rename\"), owner);\n        init(name);\n    }\n\n    /**\n     * Creates the panel in which we'll store the label and name field.\n     * @param  name current name.\n     * @return      the panel in which we'll store the label and name field.\n     */\n    private JPanel createNamePanel(String name) {\n        XAlignedComponentPanel panel = new XAlignedComponentPanel(5);\n        nameField = new JTextField();\n        nameField.setText(name);\n        nameField.setSelectionStart(0);\n        nameField.setSelectionEnd(name.length());\n        panel.addRow(i18n(\"name\"), nameField, 0);\n\n        return panel;\n    }\n\n    /**\n     * Initializes the dialog's UI.\n     */\n    private void init(String name) {\n        setMaximumSize(MAXIMUM_DIALOG_DIMENSION);\n\n        // Creates the name panel.\n        YBoxPanel panel = new YBoxPanel();\n        panel.add(createNamePanel(name));\n\n        // Creates the button panel.\n        panel.add(new ButtonChoicePanel(new JButton[] {\n                okButton = new JButton(i18n(\"ok\")),\n                cancelButton = new JButton(i18n(\"cancel\"))\n        }, 2, getRootPane()));\n        okButton.addActionListener(this);\n        cancelButton.addActionListener(this);\n        getContentPane().add(panel, BorderLayout.NORTH);\n        pack();\n    }\n\n\n    /**\n     * Returns the name entered by the user.\n     * @return the name entered by the user.\n     */\n    public String getText() {return nameField.getText();}\n\n    /**\n     * Called when the dialog is closed through the ESC button.\n     */\n    @Override\n    public void cancel() {\n        wasValidated = false;\n        super.cancel();\n    }\n\n    /**\n     * Shows the dialog and returns <code>true</code> if it was validated by the user.\n     * @return <code>true</code> if it was validated by the user, <code>false</code> otherwise.\n     */\n    public boolean wasValidated() {\n        showDialog();\n        return wasValidated;\n    }\n\n    /**\n     * Called when OK or Cancel have been pressed.\n     * @param e describes the event.\n     */\n    public void actionPerformed(ActionEvent e) {\n        if (e.getSource() == okButton) {\n            wasValidated = true;\n        } else if (e.getSource() == cancelButton) {\n            wasValidated = false;\n        }\n        dispose();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/general/package.html",
    "content": "<body>\n  UI components used to let users modify their preferences.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/package.html",
    "content": "<body>\n  API for preference dialog boxes.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/theme/ColorButton.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.pref.theme;\n\nimport com.mucommander.ui.chooser.*;\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\nimport com.mucommander.ui.theme.ThemeData;\n\nimport javax.swing.*;\nimport javax.swing.border.Border;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.util.ArrayList;\n\n/**\n * @author Maxence Bernard, Nicolas Rinaudo\n */\nclass ColorButton extends JPanel implements ActionListener, ColorChangeListener {\n\n    /** ThemeData from which to retrieve the color chooser's values. */\n    private final ThemeData themeData;\n\n    /** Identifier of the color that's being edited. */\n    private final int colorId;\n\n    /** Dialog on which the color chooser should be centered and modal to. */\n    private final PreferencesDialog parent;\n\n    /** The preview component that is repainted when the current color changes (can be null) */\n    private final JComponent previewComponent;\n\n    /** Name of the preview component's property that gets updated with the current color of this button (can be null) */\n    private final String previewColorPropertyName;\n\n    private java.util.List<JComponent> updatedPreviewComponents;\n\n    /** Button's border. */\n    private final Border border = BorderFactory.createEtchedBorder();\n\n    /** The color button */\n    private final JButton button;\n\n    /** Current color displayed in the button */\n    private Color currentColor;\n\n\n    public ColorButton(PreferencesDialog parent, ThemeData themeData, int colorId) {\n        this(parent, themeData, colorId, null, null);\n    }\n\n    ColorButton(PreferencesDialog parent, ThemeData themeData, int colorId, String previewColorPropertyName) {\n        this(parent, themeData, colorId, previewColorPropertyName, null);\n    }\n\n    ColorButton(PreferencesDialog parent, ThemeData themeData, int colorId, String previewColorPropertyName, JComponent previewComponent) {\n        FlowLayout flowLayout = new FlowLayout();\n        flowLayout.setHgap(0);\n        flowLayout.setVgap(0);\n        setLayout(flowLayout);\n\n        this.themeData = themeData;\n        this.colorId = colorId;\n        this.parent = parent;\n        this.previewComponent = previewComponent;\n        this.previewColorPropertyName = previewColorPropertyName;\n\n        if (previewColorPropertyName != null && previewComponent != null) {\n            addUpdatedPreviewComponent(previewComponent);\n        }\n \n        button = new JButton() {\n            @Override\n            public Dimension getPreferredSize() {return new Dimension(70, 30);}\n\n            @Override\n            public void paint(Graphics g) {\n                int width  = getWidth();\n                int height = getHeight();\n\n                // Fill the button with the specified color\n                g.setColor((ColorButton.this).currentColor);\n                g.fillRect(0, 0, width, height);\n\n                // Paint custom border\n                border.paintBorder(this, g, 0, 0, width, height);\n            }\n        };\n\n        button.addActionListener(this);\n        button.setBorderPainted(true);\n\n        add(button);\n\n        // Add a ColorPicker only if this component is supported by the current environment\n        if (ColorPicker.isSupported()) {\n            ColorPicker colorPicker = new ColorPicker();\n            colorPicker.addColorChangeListener(this);\n            add(colorPicker);\n        }\n\n        setCurrentColor(themeData.getColor(colorId), false);\n    }\n\n\n    void addUpdatedPreviewComponent(JComponent previewComponent) {\n        if (previewColorPropertyName == null) {\n            return;\n        }\n        if (updatedPreviewComponents == null) {\n            updatedPreviewComponents = new ArrayList<>();\n        }\n        updatedPreviewComponents.add(previewComponent);\n        previewComponent.putClientProperty(previewColorPropertyName, currentColor);\n    }\n\n\tint getColorId() {\n\t\treturn colorId;\n\t}\n\n\tColor getCurrentColor() {\n\t\treturn currentColor;\n\t}\n\n    private void setCurrentColor(Color color, boolean initiatedByUser) {\n        currentColor = color;\n        if (themeData.isColorDifferent(colorId, currentColor)) {\n            initiatedByUser &= themeData.setColor(colorId, currentColor);\n        }\n        button.repaint();\n\n        if (updatedPreviewComponents != null && previewColorPropertyName != null) {\n            for (JComponent updatedPreviewComponent : updatedPreviewComponents) {\n                updatedPreviewComponent.putClientProperty(previewColorPropertyName, color);\n            }\n        }\n        if (initiatedByUser) {\n            parent.componentChanged(null);\n        }\n    }\n\n\n    private ColorChooser createColorChooser() {\n        if(previewComponent!=null && previewColorPropertyName!=null && (previewComponent instanceof PreviewLabel)) {\n            try {\n                return new ColorChooser(currentColor, (PreviewLabel)((PreviewLabel)previewComponent).clone(), previewColorPropertyName);\n            } catch(CloneNotSupportedException ignored) {}\n        }\n        return new ColorChooser(currentColor, new PreviewLabel(), PreviewLabel.BACKGROUND_COLOR_PROPERTY_NAME);\n    }\n\n\n    @Override\n    public void actionPerformed(ActionEvent e) {\n        ColorChooser chooser = createColorChooser();\n        ColorChooser.createDialog(parent, chooser).showDialog();\n\n        setCurrentColor(chooser.getColor(), true);\n    }\n\n    @Override\n    public void colorChanged(ColorChangeEvent event) {setCurrentColor(event.getColor(), true);}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/theme/CopyFilePanelColorsButton.java",
    "content": "package com.mucommander.ui.dialog.pref.theme;\n\nimport java.awt.Color;\nimport java.awt.Component;\nimport java.awt.Container;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.Map;\nimport java.util.Queue;\nimport javax.swing.JButton;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.chooser.ColorChangeEvent;\nimport com.mucommander.ui.dialog.pref.PreferencesPanel;\n\n/**\n * <code>CopyColorsButton</code> is a {@link JButton} that copies the values of {@link ColorButton}s from one panel to\n * another. Colors are mapped from active to inactive colors and vice versa by {@link FilePanelColorIds}.\n */\nclass CopyFilePanelColorsButton extends JButton {\n\n\tprivate static final long serialVersionUID = 1L;\n\n\t/**\n\t * {@link ColorButton}s of the source file panel by their colorId\n\t */\n\tprivate final Map<Integer, ColorButton> sourceColorButtons = new HashMap<>();\n\n\t/**\n\t * {@link ColorButton}s of the target file panel by their colorId\n\t */\n\tprivate final Map<Integer, ColorButton> targetColorButtons = new HashMap<>();\n\n\t/**\n\t * Constructs an instance that copies all colors from the other panel set with {@link #setSource(PreferencesPanel)} to the\n\t * corresponding {@link ColorButton}s in <code>targetColorButtonsContainer</code>.\n\t * \n\t * @param targetColorButtonsContainer\n\t */\n\tCopyFilePanelColorsButton(Container targetColorButtonsContainer, boolean isTargetActive) {\n\t\tsuper();\n\t\tfindColorButtons(targetColorButtonsContainer, targetColorButtons);\n\n\t\tFilePanelColorIds colorIds = new FilePanelColorIds();\n\n\t\taddActionListener(e -> {\n\n\t\t\tfor (Integer targetColorId : targetColorButtons.keySet()) {\n\n\t\t\t\tfinal boolean isSourceActive = !isTargetActive;\n\t\t\t\tint sourceColorId = isTargetActive\n\t\t                ? colorIds.getIdByActive(isSourceActive, targetColorId)\n\t\t                : colorIds.getIdByInactive(isSourceActive, targetColorId);\n\n\t\t\t\tColorButton sourceButton = sourceColorButtons.get(sourceColorId);\n\n\t\t\t\tif (sourceButton != null) {\n\t\t\t\t\tColor sourceColor = sourceButton.getCurrentColor();\n\n\t\t\t\t\tColorButton targetButton = targetColorButtons.get(targetColorId);\n\t\t\t\t\ttargetButton.colorChanged(new ColorChangeEvent(this, sourceColor));\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Sets the source container having all the {@link ColorButton}s to copy the colors from.\n\t * \n\t * @param otherPanelsColorButtonsContainer source container\n\t */\n\tpublic void setSource(PreferencesPanel otherPanelsColorButtonsContainer) {\n\t\tfindColorButtons(otherPanelsColorButtonsContainer, sourceColorButtons);\n\n\t\tsetText(Translator.get(\"theme_editor.copy_colors\", otherPanelsColorButtonsContainer.getTitle()));\n\t}\n\n\t/**\n\t * Finds all {@link ColorButton}s in <code>otherPanelsColorButtonsContainer</code> and its descendants and stores\n\t * them in <code>colorButtons</code>.\n\t * \n\t * @param otherPanelsColorButtonsContainer\n\t * @param colorButtons\n\t */\n\tprivate void findColorButtons(final Container otherPanelsColorButtonsContainer,\n\t        final Map<Integer, ColorButton> colorButtons) {\n\t\tfinal Queue<Component> children = new LinkedList<>();\n\t\tchildren.add(otherPanelsColorButtonsContainer);\n\n\t\tfor (Component component = children.poll(); component != null; component = children.poll()) {\n\n\t\t\tif (component instanceof ColorButton) {\n\n\t\t\t\tfinal ColorButton colorButton = (ColorButton) component;\n\t\t\t\tcolorButtons.put(colorButton.getColorId(), colorButton);\n\n\t\t\t} else if (component instanceof Container) {\n\t\t\t\tfinal Container container = (Container) component;\n\n\t\t\t\tfinal Component[] components;\n\t\t\t\tsynchronized (container.getTreeLock()) {\n\t\t\t\t\tcomponents = container.getComponents();\n\t\t\t\t}\n\t\t\t\tchildren.addAll(Arrays.asList(components));\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/theme/FileEditorPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.pref.theme;\n\nimport com.mucommander.RuntimeConstants;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.chooser.FontChooser;\nimport com.mucommander.ui.chooser.PreviewLabel;\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\nimport com.mucommander.ui.layout.ProportionalGridPanel;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.theme.ThemeData;\nimport com.mucommander.ui.theme.ThemeId;\nimport org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;\nimport ru.trolsoft.hexeditor.data.MemoryByteBuffer;\nimport ru.trolsoft.hexeditor.ui.HexTable;\nimport ru.trolsoft.hexeditor.ui.ViewerHexTableModel;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.beans.PropertyChangeListener;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.util.Random;\n\n/**\n * @author Nicolas Rinaudo, Maxence Bernard\n */\nclass FileEditorPanel extends ThemeEditorPanel implements ThemeId {\n    /** Used to textPreview the editor's theme. */\n    private RSyntaxTextArea textPreview;\n\n    private JScrollPane scrollHex;\n    private HexTable hexPreview;\n\n    private final PropertyChangeListener textPropertyChangeListener = event -> {\n        final String name = event.getPropertyName();\n        if (name.equals(PreviewLabel.BACKGROUND_COLOR_PROPERTY_NAME)) {\n            setTextBackgroundColors();\n        } else if (name.equals(PreviewLabel.FOREGROUND_COLOR_PROPERTY_NAME)) {\n            setTextForegroundColors();\n        }\n    };\n\n    private final PropertyChangeListener hexPropertyChangeListener = event -> {\n        final String name = event.getPropertyName();\n        if (name.equals(PreviewLabel.BACKGROUND_COLOR_PROPERTY_NAME) || name.equals(PreviewLabel.FOREGROUND_COLOR_PROPERTY_NAME)) {\n            setHexColors();\n        }\n    };\n\n    /**\n     * Creates a new file table editor.\n     * @param parent    dialog containing the panel.\n     * @param themeData  themeData being edited.\n     */\n    FileEditorPanel(PreferencesDialog parent, ThemeData themeData) {\n        super(parent, Translator.get(\"theme_editor.editor_tab\"), themeData);\n        initUI();\n    }\n\n\n\n    /**\n     * Creates the JPanel that contains all of the color configuration elements.\n     * @param fontChooser font chooser used by the editor panel.\n     * @return the JPanel that contains all of the color configuration elements.\n     */\n    private JPanel createTextColorsPanel(FontChooser fontChooser) {\n        ProportionalGridPanel gridPanel = new ProportionalGridPanel(3);\n\n        // Header\n        addLabelRow(gridPanel, false);\n\n        PreviewLabel label = new PreviewLabel();\n\n        // Color buttons\n        addColorButtons(gridPanel, fontChooser, \"theme_editor.normal\",\n                        EDITOR_FOREGROUND_COLOR, EDITOR_BACKGROUND_COLOR, label).addPropertyChangeListener(textPropertyChangeListener);\n        addColorButtons(gridPanel, fontChooser, \"theme_editor.selected\",\n                        EDITOR_SELECTED_FOREGROUND_COLOR, EDITOR_SELECTED_BACKGROUND_COLOR, label).addPropertyChangeListener(textPropertyChangeListener);\n\n        addColorButtons(gridPanel, fontChooser, \"theme_editor.current\",\n                -1, EDITOR_CURRENT_BACKGROUND_COLOR, label).addPropertyChangeListener(textPropertyChangeListener);\n\n        label.addPropertyChangeListener(textPropertyChangeListener);\n        //butt.addUpdatedPreviewComponent(label);\n\n        // Wraps everything in a flow layout.\n        JPanel colorsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        colorsPanel.add(gridPanel);\n        colorsPanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"theme_editor.colors\")));\n\n        return colorsPanel;\n    }\n\n    /**\n     * Creates the JPanel that contains all the color configuration elements.\n     * @param fontChooser font chooser used by the editor panel.\n     * @return the JPanel that contains all the color configuration elements.\n     */\n    private JPanel createHexColorsPanel(FontChooser fontChooser) {\n        // Initialisation\n        ProportionalGridPanel gridPanel = new ProportionalGridPanel(3);\n\n        // Header\n        addLabelRow(gridPanel, false);\n\n        PreviewLabel label = new PreviewLabel();\n\n        // Color buttons\n        addColorButtons(gridPanel, fontChooser, \"theme_editor.normal_hex\",\n                HEX_VIEWER_HEX_FOREGROUND_COLOR, HEX_VIEWER_BACKGROUND_COLOR, label).addPropertyChangeListener(hexPropertyChangeListener);\n        addColorButtons(gridPanel, fontChooser, \"theme_editor.normal_ascii\",\n                HEX_VIEWER_ASCII_FOREGROUND_COLOR, -1, label).addPropertyChangeListener(hexPropertyChangeListener);\n        addColorButtons(gridPanel, fontChooser, \"theme_editor.normal_offset\",\n                HEX_VIEWER_OFFSET_FOREGROUND_COLOR, -1, label).addPropertyChangeListener(hexPropertyChangeListener);\n        addColorButtons(gridPanel, fontChooser, \"theme_editor.alternate\",\n                -1, HEX_VIEWER_ALTERNATE_BACKGROUND_COLOR, label).addPropertyChangeListener(hexPropertyChangeListener);\n        addColorButtons(gridPanel, fontChooser, \"theme_editor.selected_hex\",\n                -1, HEX_VIEWER_SELECTED_BACKGROUND_COLOR, label).addPropertyChangeListener(hexPropertyChangeListener);\n        addColorButtons(gridPanel, fontChooser, \"theme_editor.selected_ascii\",\n                -1, HEX_VIEWER_SELECTED_ASCII_BACKGROUND_COLOR, label).addPropertyChangeListener(hexPropertyChangeListener);\n\n\n        label.addPropertyChangeListener(hexPropertyChangeListener);\n        //butt.addUpdatedPreviewComponent(label);\n\n        // Wraps everything in a flow layout.\n        JPanel colorsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        colorsPanel.add(gridPanel);\n        colorsPanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"theme_editor.colors\")));\n\n        return colorsPanel;\n    }\n\n    /**\n     * Initializes the panel's UI.\n     */\n    private void initUI() {\n        JTabbedPane tabbedPane = new JTabbedPane();\n//        JPanel textEditorPanel = createTextEditorPanel();\n\n        tabbedPane.add(Translator.get(\"theme_editor.text_editor_tab\"), createTextViewerPanel());\n        tabbedPane.add(Translator.get(\"theme_editor.hex_viewer_tab\"), createHexViewerPanel());\n\n        setLayout(new BorderLayout());\n        add(tabbedPane, BorderLayout.NORTH);\n//        // Layout.\n//        setLayout(new BorderLayout());\n//        add(mainPanel, BorderLayout.NORTH);\n    }\n\n    private JPanel createTextViewerPanel() {\n        // Font chooser and textPreview initialisation.\n        JPanel mainPanel = new JPanel(new BorderLayout());\n        FontChooser fontChooser = createFontChooser(EDITOR_FONT);\n        mainPanel.add(createTextPreviewPanel(), BorderLayout.CENTER);\n        addFontChooserListener(fontChooser, textPreview);\n\n        // Configuration panel initialization.\n        YBoxPanel configurationPanel = new YBoxPanel();\n        configurationPanel.add(fontChooser);\n        configurationPanel.addSpace(10);\n        configurationPanel.add(createTextColorsPanel(fontChooser));\n        mainPanel.add(configurationPanel, BorderLayout.WEST);\n\n        return mainPanel;\n    }\n\n    private JPanel createHexViewerPanel() {\n        JPanel panel = new JPanel(new BorderLayout());\n        FontChooser fontChooser = createFontChooser(HEX_VIEWER_FONT);\n        panel.add(createHexPreviewPanel(), BorderLayout.CENTER);\n        addFontChooserListener(fontChooser, hexPreview);\n\n        // Configuration panel initialization.\n        YBoxPanel configurationPanel = new YBoxPanel();\n        configurationPanel.add(fontChooser);\n        configurationPanel.addSpace(10);\n        configurationPanel.add(createHexColorsPanel(fontChooser));\n        panel.add(configurationPanel, BorderLayout.WEST);\n\n        return panel;\n    }\n\n    /**\n     * Creates the file editor textPreview panel.\n     * @return the file editor textPreview panel.\n     */\n    private JPanel createTextPreviewPanel() {\n        // Initializes the textPreview text area.\n        textPreview = new RSyntaxTextArea(15, 15);\n\n        // Initialises colors.\n        setTextBackgroundColors();\n        setTextForegroundColors();\n        // Creates the panel.\n        JPanel panel = new JPanel(new BorderLayout());\n        JScrollPane scroll = new JScrollPane(textPreview, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);\n        panel.add(scroll, BorderLayout.CENTER);\n        scroll.getViewport().setPreferredSize(textPreview.getPreferredSize());\n        panel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"preview\")));\n\n        loadText();\n        textPreview.setCaretPosition(0);\n\n        return panel;\n    }\n\n\n    private JPanel createHexPreviewPanel() {\n        MemoryByteBuffer dataBuffer = new MemoryByteBuffer(250);\n        ViewerHexTableModel model = new ViewerHexTableModel(dataBuffer, 8);\n        try {\n            model.load();\n        } catch (IOException ignore) {}\n        Random rnd = new Random();\n        for (int i = 0; i < dataBuffer.getCapacity(); i++) {\n            dataBuffer.setByte(i, rnd.nextInt());\n        }\n        hexPreview = new HexTable(model);\n\n        JPanel panel = new JPanel(new BorderLayout());\n        scrollHex = new JScrollPane(hexPreview, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);\n        panel.add(scrollHex, BorderLayout.CENTER);\n        scrollHex.getViewport().setPreferredSize(new Dimension(300, 200));\n        panel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"preview\")));\n\n        setHexColors();\n\n        return panel;\n    }\n\n\n    private void setTextBackgroundColors() {\n        Color background = themeData.getColor(EDITOR_BACKGROUND_COLOR);\n        textPreview.setBackground(background);\n        for (int i = 1; i <= textPreview.getSecondaryLanguageCount(); i++) {\n            textPreview.setSecondaryLanguageBackground(i, background);\n        }\n        textPreview.setSelectionColor(themeData.getColor(EDITOR_SELECTED_BACKGROUND_COLOR));\n        textPreview.setCurrentLineHighlightColor(themeData.getColor(EDITOR_CURRENT_BACKGROUND_COLOR));\n    }\n\n    private void setTextForegroundColors() {\n        textPreview.setForeground(themeData.getColor(EDITOR_FOREGROUND_COLOR));\n        textPreview.setCaretColor(themeData.getColor(EDITOR_FOREGROUND_COLOR));\n        textPreview.setSelectedTextColor(themeData.getColor(EDITOR_SELECTED_FOREGROUND_COLOR));\n    }\n\n    private void setHexColors() {\n        hexPreview.setBackground(themeData.getColor(HEX_VIEWER_BACKGROUND_COLOR));\n        hexPreview.setForeground(themeData.getColor(HEX_VIEWER_HEX_FOREGROUND_COLOR));\n        hexPreview.setAlternateBackground(themeData.getColor(HEX_VIEWER_ALTERNATE_BACKGROUND_COLOR));\n        hexPreview.setOffsetColumnColor(themeData.getColor(HEX_VIEWER_OFFSET_FOREGROUND_COLOR));\n        hexPreview.setAsciiColumnColor(themeData.getColor(HEX_VIEWER_ASCII_FOREGROUND_COLOR));\n        hexPreview.setAsciiSelectionBackgroundColor(themeData.getColor(HEX_VIEWER_SELECTED_ASCII_BACKGROUND_COLOR));\n        hexPreview.setSelectionBackground(themeData.getColor(HEX_VIEWER_SELECTED_BACKGROUND_COLOR));\n        hexPreview.setFont(themeData.getFont(HEX_VIEWER_FONT));\n        hexPreview.setAlternateRowBackground(true);\n\n        hexPreview.getTableHeader().setFont(new Font(\"Monospaced\", Font.PLAIN, 12));\n\n        scrollHex.getViewport().setBackground(hexPreview.getBackground());\n    }\n\n\n\n    // - Misc. ---------------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    private void loadText() {\n        try (InputStreamReader in = new InputStreamReader(FileEditorPanel.class.getResourceAsStream(RuntimeConstants.LICENSE))) {\n            char[] buffer = new char[2048];\n            int count;  // Number of characters read from the last read operation.\n            while ((count = in.read(buffer)) >= 0) {\n                textPreview.append(new String(buffer, 0, count));\n            }\n\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n\n\n\n    // - Modification management ---------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    /**\n     * Ignored.\n     */\n    @Override\n    public void commit() {}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/theme/FileGroupsPanel.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.dialog.pref.theme;\n\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferencesAPI;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.chooser.PreviewLabel;\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\nimport com.mucommander.ui.layout.ProportionalGridPanel;\nimport com.mucommander.ui.main.table.FileGroupResolver;\nimport com.mucommander.ui.theme.ThemeData;\nimport com.mucommander.ui.theme.ThemeId;\n\nimport javax.swing.JLabel;\nimport javax.swing.JPanel;\nimport javax.swing.JTextField;\nimport javax.swing.event.DocumentEvent;\nimport javax.swing.event.DocumentListener;\nimport java.awt.BorderLayout;\n\n/**\n * @author Oleg Trifonov\n */\npublic class FileGroupsPanel extends ThemeEditorPanel implements ThemeId {\n\n    private static final int NUMBER_OF_GROUPS = 10;\n\n    private final JTextField[] fileMasks = new JTextField[NUMBER_OF_GROUPS];\n\n    /**\n     * Creates a new <code>FilePanel</code>.\n     * @param parent   dialog containing the panel\n     * @param data     theme to edit.\n     */\n    FileGroupsPanel(final PreferencesDialog parent, ThemeData data) {\n        super(parent, Translator.get(\"theme_editor.file_groups\"), data);\n\n        DocumentListener documentListener = new DocumentListener() {\n            @Override\n            public void insertUpdate(DocumentEvent e) {\n                parent.setCommitButtonsEnabled(true);\n            }\n\n            @Override\n            public void removeUpdate(DocumentEvent e) {\n                parent.setCommitButtonsEnabled(true);\n            }\n\n            @Override\n            public void changedUpdate(DocumentEvent e) {\n            }\n        };\n\n        FilePreviewPanel preview = new FilePreviewPanel(themeData, true);\n        JPanel gridPanel = new ProportionalGridPanel(3);\n\n        // Header\n        gridPanel.add(new JLabel());\n        gridPanel.add(createCaptionLabel(\"theme_editor.normal_color\"));\n        gridPanel.add(createCaptionLabel(\"theme_editor.filemask\"));\n        TcPreferencesAPI prefs = TcConfigurations.getPreferences();\n        for (int i = 0; i < NUMBER_OF_GROUPS; i++) {\n            TcPreference preference = TcPreference.values()[TcPreference.FILE_GROUP_1_MASK.ordinal() + i];\n            String mask = prefs.getVariable(preference);\n            gridPanel.add(createCaptionLabelWithTitle(Translator.get(\"theme_editor.group_\") + \" \" + (i+1)));\n            ColorButton colorButton  = new ColorButton(parent, themeData, FILE_GROUP_1_FOREGROUND_COLOR + i, PreviewLabel.FOREGROUND_COLOR_PROPERTY_NAME, preview);\n            gridPanel.add(colorButton);\n            fileMasks[i] = new JTextField(24);\n            fileMasks[i].setText(mask);\n            gridPanel.add(fileMasks[i]);\n            fileMasks[i].getDocument().addDocumentListener(documentListener);\n        }\n\n\n        setLayout(new BorderLayout());\n        add(gridPanel, BorderLayout.WEST);\n        add(preview, BorderLayout.EAST);\n    }\n\n\n    @Override\n    protected void commit() {\n        TcPreferencesAPI prefs = TcConfigurations.getPreferences();\n        for (int i = 0; i < NUMBER_OF_GROUPS; i++) {\n            TcPreference preference = TcPreference.values()[TcPreference.FILE_GROUP_1_MASK.ordinal() + i];\n            prefs.setVariable(preference, fileMasks[i].getText().trim());\n        }\n        try {\n            TcConfigurations.savePreferences();\n        } catch(Exception ignore) {\n        }\n        FileGroupResolver.getInstance().init();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/theme/FilePanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.pref.theme;\n\nimport static com.mucommander.ui.theme.ThemeData.*;\nimport java.awt.BorderLayout;\nimport java.awt.Component;\nimport javax.swing.BoxLayout;\nimport javax.swing.JLabel;\nimport javax.swing.JPanel;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.chooser.FontChooser;\nimport com.mucommander.ui.chooser.PreviewLabel;\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\nimport com.mucommander.ui.dialog.pref.PreferencesPanel;\nimport com.mucommander.ui.layout.ProportionalGridPanel;\nimport com.mucommander.ui.theme.ThemeData;\n\n\n/**\n * @author Nicolas Rinaudo\n */\nclass FilePanel extends ThemeEditorPanel {\n\n\tprivate CopyFilePanelColorsButton copyColorsButton;\n\n    /**\n     * Creates a new <code>FilePanel</code>.\n     * @param parent   dialog containing the panel\n     * @param isActive whether the color values should be taken from the <i>active</i> or <i>inactive</i> state.\n     * @param data     theme to edit.\n     * @param fontChooser  File table font chooser.\n     */\n\tFilePanel(PreferencesDialog parent, boolean isActive, ThemeData data, FontChooser fontChooser) {\n        super(parent, Translator.get(isActive ? \"theme_editor.active_panel\" : \"theme_editor.inactive_panel\"), data);\n        initUI(isActive, fontChooser);\n    }\n\n\tprivate void initUI(boolean isActive, FontChooser fontChooser) {\n        JPanel gridPanel = new ProportionalGridPanel(3);\n        FilePreviewPanel preview   = new FilePreviewPanel(themeData, isActive);\n        addFontChooserListener(fontChooser, preview);\n\n        // Header\n        gridPanel.add(new JLabel());\n        gridPanel.add(createCaptionLabel(\"theme_editor.normal\"));\n        gridPanel.add(createCaptionLabel(\"theme_editor.selected\"));\n\n\t\tFilePanelColorIds colors = new FilePanelColorIds();\n\n\t\t// Background\n\t\tgridPanel.add(createCaptionLabel(\"theme_editor.background\"));\n\t\tColorButton backgroundButton = new ColorButton(parent, themeData,\n\t\t        colors.getIdByActive(isActive, FILE_TABLE_BACKGROUND_COLOR),\n\t\t        PreviewLabel.BACKGROUND_COLOR_PROPERTY_NAME, preview);\n\t\tgridPanel.add(backgroundButton);\n\t\tColorButton selectedBackgroundButton = new ColorButton(parent, themeData,\n\t\t        colors.getIdByActive(isActive, FILE_TABLE_SELECTED_BACKGROUND_COLOR),\n\t\t        PreviewLabel.BACKGROUND_COLOR_PROPERTY_NAME, preview);\n\t\tgridPanel.add(selectedBackgroundButton);\n\n\t\t// Alternate background\n\t\tgridPanel.add(createCaptionLabel(\"theme_editor.alternate_background\"));\n\t\tgridPanel.add(new ColorButton(parent, themeData,\n\t\t        colors.getIdByActive(isActive, FILE_TABLE_ALTERNATE_BACKGROUND_COLOR),\n\t\t        PreviewLabel.BACKGROUND_COLOR_PROPERTY_NAME, preview));\n\t\tgridPanel.add(new JLabel());\n\n\t\t// Folders.\n\t\tgridPanel.add(createCaptionLabel(\"theme_editor.folder\"));\n\t\taddForegroundColor(gridPanel, colors.getIdByActive(isActive, FOLDER_FOREGROUND_COLOR),\n\t\t        backgroundButton, fontChooser, preview);\n\t\taddForegroundColor(gridPanel, colors.getIdByActive(isActive, FOLDER_SELECTED_FOREGROUND_COLOR),\n\t\t        selectedBackgroundButton, fontChooser, preview);\n\n\t\t// Plain files.\n\t\tgridPanel.add(createCaptionLabel(\"theme_editor.plain_file\"));\n\t\taddForegroundColor(gridPanel, colors.getIdByActive(isActive, FILE_FOREGROUND_COLOR),\n\t\t        backgroundButton, fontChooser, preview);\n\t\taddForegroundColor(gridPanel, colors.getIdByActive(isActive, FILE_SELECTED_FOREGROUND_COLOR),\n\t\t        selectedBackgroundButton, fontChooser, preview);\n\n\t\t// Archives.\n\t\tgridPanel.add(createCaptionLabel(\"theme_editor.archive_file\"));\n\t\taddForegroundColor(gridPanel, colors.getIdByActive(isActive, ARCHIVE_FOREGROUND_COLOR),\n\t\t        backgroundButton, fontChooser, preview);\n\t\taddForegroundColor(gridPanel, colors.getIdByActive(isActive, ARCHIVE_SELECTED_FOREGROUND_COLOR),\n\t\t        selectedBackgroundButton, fontChooser, preview);\n\n\t\t// Hidden folders.\n\t\tgridPanel.add(createCaptionLabel(\"theme_editor.hidden_folder\"));\n\t\taddForegroundColor(gridPanel, colors.getIdByActive(isActive, HIDDEN_FOLDER_FOREGROUND_COLOR),\n\t\t\t\tbackgroundButton, fontChooser, preview);\n\t\taddForegroundColor(gridPanel, colors.getIdByActive(isActive, HIDDEN_FOLDER_SELECTED_FOREGROUND_COLOR),\n\t\t\t\tselectedBackgroundButton, fontChooser, preview);\n\n\t\t// Hidden files.\n\t\tgridPanel.add(createCaptionLabel(\"theme_editor.hidden_file\"));\n\t\taddForegroundColor(gridPanel, colors.getIdByActive(isActive, HIDDEN_FILE_FOREGROUND_COLOR),\n\t\t        backgroundButton, fontChooser, preview);\n\t\taddForegroundColor(gridPanel, colors.getIdByActive(isActive, HIDDEN_FILE_SELECTED_FOREGROUND_COLOR),\n\t\t        selectedBackgroundButton, fontChooser, preview);\n\n\t\t// Symlinks.\n\t\tgridPanel.add(createCaptionLabel(\"theme_editor.symbolic_link\"));\n\t\taddForegroundColor(gridPanel, colors.getIdByActive(isActive, SYMLINK_FOREGROUND_COLOR),\n\t\t        backgroundButton, fontChooser, preview);\n\t\taddForegroundColor(gridPanel, colors.getIdByActive(isActive, SYMLINK_SELECTED_FOREGROUND_COLOR),\n\t\t        selectedBackgroundButton, fontChooser, preview);\n\n\t\t// Marked files.\n\t\tgridPanel.add(createCaptionLabel(\"theme_editor.marked_file\"));\n\t\taddForegroundColor(gridPanel, colors.getIdByActive(isActive, MARKED_FOREGROUND_COLOR),\n\t\t        backgroundButton, fontChooser, preview);\n\t\taddForegroundColor(gridPanel, colors.getIdByActive(isActive, MARKED_SELECTED_FOREGROUND_COLOR),\n\t\t        selectedBackgroundButton, fontChooser, preview);\n\n\t\t// Executable files.\n\t\tgridPanel.add(createCaptionLabel(\"theme_editor.executable_file\"));\n\t\taddForegroundColor(gridPanel, colors.getIdByActive(isActive, EXECUTABLE_FOREGROUND_COLOR),\n\t\t        backgroundButton, fontChooser, preview);\n\t\taddForegroundColor(gridPanel, colors.getIdByActive(isActive, EXECUTABLE_SELECTED_FOREGROUND_COLOR),\n\t\t        selectedBackgroundButton, fontChooser, preview);\n\n\t\t// Border.\n\t\tgridPanel.add(createCaptionLabel(\"theme_editor.border\"));\n\t\tColorButton borderButton;\n\t\tgridPanel.add(borderButton = new ColorButton(parent, themeData,\n\t\t        colors.getIdByActive(isActive, FILE_TABLE_BORDER_COLOR),\n\t\t        PreviewLabel.BORDER_COLOR_PROPERTY_NAME));\n\t\tborderButton.addUpdatedPreviewComponent(preview);\n\t\tgridPanel.add(borderButton = new ColorButton(parent, themeData,\n\t\t        colors.getIdByActive(isActive, FILE_TABLE_SELECTED_OUTLINE_COLOR),\n\t\t        PreviewLabel.BORDER_COLOR_PROPERTY_NAME));\n\t\tborderButton.addUpdatedPreviewComponent(preview);\n\n\t\t// Copy colors\n\t\t// must be last when color buttons are alraedy added to gridPanel!\n\t\tcopyColorsButton = new CopyFilePanelColorsButton(gridPanel, isActive);\n\t\tcopyColorsButton.setAlignmentX(Component.CENTER_ALIGNMENT);\n\n\t\tJPanel rightSide = new JPanel();\n\t\trightSide.setLayout(new BoxLayout(rightSide, BoxLayout.Y_AXIS));\n\t\trightSide.add(preview);\n\t\trightSide.add(copyColorsButton);\n\n        setLayout(new BorderLayout());\n        add(gridPanel, BorderLayout.WEST);\n\t\tadd(rightSide, BorderLayout.EAST);\n    }\n\n\tprivate void addForegroundColor(JPanel to, int colorId, ColorButton background, FontChooser fontChooser, FilePreviewPanel previewPanel) {\n\t\tPreviewLabel preview = new PreviewLabel();\n\t\tpreview.setTextPainted(true);\n\t\tbackground.addUpdatedPreviewComponent(preview);\n\t\taddFontChooserListener(fontChooser, preview);\n\t\tColorButton button = new ColorButton(parent, themeData, colorId, PreviewLabel.FOREGROUND_COLOR_PROPERTY_NAME, preview);\n\t\tto.add(button);\n\t\tbutton.addUpdatedPreviewComponent(previewPanel);\n\t}\n\n\n\t@Override\n    public void commit() {}\n\n\tvoid setCopyColorButtonsSource(PreferencesPanel otherPanelsColorButtonsContainer) {\n\t\tcopyColorsButton.setSource(otherPanelsColorButtonsContainer);\n\t}\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/theme/FilePanelColorIds.java",
    "content": "package com.mucommander.ui.dialog.pref.theme;\n\nimport static com.mucommander.ui.theme.ThemeData.*;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\n/**\n * Mapping between colors of the active file panel and colors of the inactive panel.\n */\nclass FilePanelColorIds {\n\n\tprivate final Map<Integer, Integer> colorMap;\n\tprivate final Map<Integer, Integer> reverseColorMap;\n\n\tFilePanelColorIds() {\n\t\tsuper();\n\n\t\tcolorMap = new HashMap<>();\n\t\tadd(FILE_TABLE_BACKGROUND_COLOR, FILE_TABLE_INACTIVE_BACKGROUND_COLOR);\n\t\tadd(FILE_TABLE_SELECTED_BACKGROUND_COLOR, FILE_TABLE_INACTIVE_SELECTED_BACKGROUND_COLOR);\n\t\tadd(FILE_TABLE_ALTERNATE_BACKGROUND_COLOR, FILE_TABLE_INACTIVE_ALTERNATE_BACKGROUND_COLOR);\n\t\tadd(FOLDER_FOREGROUND_COLOR, FOLDER_INACTIVE_FOREGROUND_COLOR);\n\t\tadd(FOLDER_SELECTED_FOREGROUND_COLOR, FOLDER_INACTIVE_SELECTED_FOREGROUND_COLOR);\n\t\tadd(FILE_FOREGROUND_COLOR, FILE_INACTIVE_FOREGROUND_COLOR);\n\t\tadd(FILE_SELECTED_FOREGROUND_COLOR, FILE_INACTIVE_SELECTED_FOREGROUND_COLOR);\n\t\tadd(ARCHIVE_FOREGROUND_COLOR, ARCHIVE_INACTIVE_FOREGROUND_COLOR);\n\t\tadd(ARCHIVE_SELECTED_FOREGROUND_COLOR, ARCHIVE_INACTIVE_SELECTED_FOREGROUND_COLOR);\n\t\tadd(HIDDEN_FOLDER_FOREGROUND_COLOR, HIDDEN_FOLDER_INACTIVE_FOREGROUND_COLOR);\n\t\tadd(HIDDEN_FOLDER_SELECTED_FOREGROUND_COLOR, HIDDEN_FOLDER_INACTIVE_SELECTED_FOREGROUND_COLOR);\n\t\tadd(HIDDEN_FILE_FOREGROUND_COLOR, HIDDEN_FILE_INACTIVE_FOREGROUND_COLOR);\n\t\tadd(HIDDEN_FILE_SELECTED_FOREGROUND_COLOR, HIDDEN_FILE_INACTIVE_SELECTED_FOREGROUND_COLOR);\n\t\tadd(SYMLINK_FOREGROUND_COLOR, SYMLINK_INACTIVE_FOREGROUND_COLOR);\n\t\tadd(SYMLINK_SELECTED_FOREGROUND_COLOR, SYMLINK_INACTIVE_SELECTED_FOREGROUND_COLOR);\n\t\tadd(MARKED_FOREGROUND_COLOR, MARKED_INACTIVE_FOREGROUND_COLOR);\n\t\tadd(MARKED_SELECTED_FOREGROUND_COLOR, MARKED_INACTIVE_SELECTED_FOREGROUND_COLOR);\n\t\tadd(EXECUTABLE_FOREGROUND_COLOR, EXECUTABLE_INACTIVE_FOREGROUND_COLOR);\n\t\tadd(EXECUTABLE_SELECTED_FOREGROUND_COLOR, EXECUTABLE_INACTIVE_SELECTED_FOREGROUND_COLOR);\n\t\tadd(FILE_TABLE_BORDER_COLOR, FILE_TABLE_INACTIVE_BORDER_COLOR);\n\t\tadd(FILE_TABLE_SELECTED_OUTLINE_COLOR, FILE_TABLE_INACTIVE_SELECTED_OUTLINE_COLOR);\n\t\t\n\t\treverseColorMap = colorMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));\n\t}\n\n\t/**\n\t * Gets a color by specifying the id of the active file panel.\n\t * \n\t * @param isActive\n\t *            whether the id of the active or the inactive file panel should be returned\n\t * @param activeColorId\n\t *            the id of the color in the active file panel\n\t * @return the id of the active or inactive panel depending on <code>isActive</code>\n\t */\n\tint getIdByActive(boolean isActive, int activeColorId) {\n\t\treturn isActive ? activeColorId : colorMap.get(activeColorId);\n\t}\n\n\t/**\n\t * Gets a color by specifying the id of the inactive file panel.\n\t * \n\t * @param isActive\n\t *            whether the id of the active or the inactive file panel should be returned\n\t * @param inactiveColorId\n\t *            the id of the color in the inactive file panel\n\t * @return the id of the active or inactive panel depending on <code>isActive</code>\n\t */\n\tint getIdByInactive(boolean isActive, int inactiveColorId) {\n\t\treturn isActive ? reverseColorMap.get(inactiveColorId) : inactiveColorId;\n\t}\n\n\tprivate void add(int active, int inactive) {\n\t\tcolorMap.put(active, inactive);\n\t}\n\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/theme/FilePreviewPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.pref.theme;\n\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.border.MutableLineBorder;\nimport com.mucommander.ui.chooser.PreviewLabel;\nimport com.mucommander.ui.icon.CustomFileIconProvider;\nimport com.mucommander.ui.icon.FileIcons;\nimport com.mucommander.ui.icon.IconManager;\nimport com.mucommander.ui.main.table.CellLabel;\nimport com.mucommander.ui.theme.Theme;\nimport com.mucommander.ui.theme.ThemeData;\nimport com.mucommander.ui.theme.ThemeId;\n\nimport javax.swing.*;\nimport javax.swing.table.DefaultTableCellRenderer;\nimport javax.swing.table.TableCellRenderer;\nimport java.awt.*;\nimport java.beans.PropertyChangeEvent;\nimport java.beans.PropertyChangeListener;\n\n/**\n * @author Nicolas Rinaudo\n */\nclass FilePreviewPanel extends JScrollPane implements PropertyChangeListener, ThemeId {\n\n    private enum RowType {\n        FOLDER,\n        PLAIN_FILE,\n        ARCHIVE,\n        HIDDEN_FOLDER,\n        HIDDEN_FILE,\n        SYMLINK,\n        MARKED_FILE,\n        EXECUTABLE_FILE,\n\n        GROUP_1_FILE,\n        GROUP_2_FILE,\n        GROUP_3_FILE,\n        GROUP_4_FILE,\n        GROUP_5_FILE,\n        GROUP_6_FILE,\n        GROUP_7_FILE,\n        GROUP_8_FILE,\n        GROUP_9_FILE,\n        GROUP_10_FILE,\n    }\n\n\n    private final ThemeData    data;\n    private final boolean      isActive;\n    private PreviewTable table;\n    private final ImageIcon    symlinkIcon;\n\n\n    /**\n     * Creates a new preview panel on the specified theme data.\n     * @param data     data to preview.\n     * @param isActive whether we're previewing the active or inactive state.\n     */\n    FilePreviewPanel(ThemeData data, boolean isActive) {\n        super(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);\n        this.data     = data;\n        this.isActive = isActive;\n        symlinkIcon   = IconManager.getCompositeIcon(IconManager.getIcon(IconManager.IconSet.FILE, CustomFileIconProvider.FILE_ICON_NAME),\n                                                     IconManager.getIcon(IconManager.IconSet.FILE, CustomFileIconProvider.SYMLINK_ICON_NAME));\n\n        initUI();\n    }\n\n    /**\n     * Initializes the previwer's UI.\n     */\n    private void initUI() {\n        table = new PreviewTable();\n        setViewportView(table);\n\n        getViewport().setBackground(getColor(isActive ? Theme.FILE_TABLE_BACKGROUND_COLOR : Theme.FILE_TABLE_INACTIVE_BACKGROUND_COLOR));\n\n        setBorder(new MutableLineBorder(getColor(isActive ? Theme.FILE_TABLE_BORDER_COLOR : Theme.FILE_TABLE_INACTIVE_BORDER_COLOR)));\n\n        addPropertyChangeListener(this);\n    }\n\n    public void propertyChange(PropertyChangeEvent event) {\n        switch(event.getPropertyName()) {\n            case PreviewLabel.BACKGROUND_COLOR_PROPERTY_NAME:\n                getViewport().setBackground(getColor(isActive ? Theme.FILE_TABLE_BACKGROUND_COLOR : Theme.FILE_TABLE_INACTIVE_BACKGROUND_COLOR));\n                break;\n            case PreviewLabel.BORDER_COLOR_PROPERTY_NAME:\n                // Some (rather evil) look and feels will change borders outside of muCommander's control,\n                // this check is necessary to ensure no exception is thrown.\n                if (getBorder() instanceof MutableLineBorder) {\n                    ((MutableLineBorder)getBorder()).setLineColor(getColor(isActive ? Theme.FILE_TABLE_BORDER_COLOR : Theme.FILE_TABLE_INACTIVE_BORDER_COLOR));\n                }\n                break;\n            case PreviewLabel.FOREGROUND_COLOR_PROPERTY_NAME:\n                break;\n            default:\n                return;\n        }\n        repaint();\n    }\n\n\n    /**\n     * Resets the table's row height with the new font.\n     */\n    @Override\n    public void setFont(Font font) {\n        if(table != null)\n            table.setRowHeight(font);\n    }\n\n\n\n    /**\n     * Used to preview the current table theme.\n     * @author Nicolas Rinaudo\n     */\n    private class PreviewTable extends JTable {\n        private final PreviewCellRenderer cellRenderer;\n        private Dimension           preferredSize;\n\n        /**\n         * Creates a new preview table.\n         */\n        PreviewTable() {\n            super(new String[][] {{\"\", Translator.get(\"theme_editor.folder\")},\n                                  {\"\", Translator.get(\"theme_editor.plain_file\")},\n                                  {\"\", Translator.get(\"theme_editor.archive_file\")},\n                                  {\"\", Translator.get(\"theme_editor.hidden_folder\")},\n                                  {\"\", Translator.get(\"theme_editor.hidden_file\")},\n                                  {\"\", Translator.get(\"theme_editor.symbolic_link\")},\n                                  {\"\", Translator.get(\"theme_editor.marked_file\")},\n                                  {\"\", Translator.get(\"theme_editor.group_file_\")+1},\n                                  {\"\", Translator.get(\"theme_editor.group_file_\")+2},\n                                  {\"\", Translator.get(\"theme_editor.group_file_\")+3},\n                                  {\"\", Translator.get(\"theme_editor.group_file_\")+4},\n                                  {\"\", Translator.get(\"theme_editor.group_file_\")+5},\n                                  {\"\", Translator.get(\"theme_editor.group_file_\")+6},\n                                  {\"\", Translator.get(\"theme_editor.group_file_\")+7},\n                                  {\"\", Translator.get(\"theme_editor.group_file_\")+8},\n                                  {\"\", Translator.get(\"theme_editor.group_file_\")+9},\n                                  {\"\", Translator.get(\"theme_editor.group_file_\")+10},\n                    },\n                new String[] {\"\", Translator.get(\"preview\")});\n\n            // Initializes table painting.\n            cellRenderer = new PreviewCellRenderer();\n            setShowGrid(false);\n\n            // Initializes the table selection.\n            getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);\n            changeSelection(0, 0, false, false);\n\n            // Initializes row dimensions.\n            setRowHeight(data.getFont(FILE_TABLE_FONT));\n            setIntercellSpacing(new Dimension(0,0));\n\n            // Initializes the table header.\n            getTableHeader().setResizingAllowed(false);\n            getTableHeader().setReorderingAllowed(false);\n            ((DefaultTableCellRenderer)getTableHeader().getDefaultRenderer()).setHorizontalAlignment(SwingConstants.LEFT);\n        }\n\n        /**\n         * Resets column widths.\n         */\n        @Override\n        public void doLayout() {\n            int width = getIconWidth();\n            getColumnModel().getColumn(0).setWidth(width);\n            getColumnModel().getColumn(1).setWidth(Math.max(getWidth() - width, getLabelWidth()));\n        }\n\n        /**\n         * Returns the width of the icon label.\n         */\n        private int getIconWidth() {return (int)FileIcons.getIconDimension().getWidth() + 2 * CellLabel.CELL_BORDER_WIDTH;}\n\n        /**\n         * Returns the width of the text label.\n         */\n        private int getLabelWidth() {\n            FontMetrics fm = getFontMetrics(data.getFont(FILE_TABLE_FONT));\n            int rowCount = getModel().getRowCount();\n            int width = 0;\n            for (int i = 0; i < rowCount; i++) {\n                width = Math.max(width, fm.stringWidth(((String) (getModel().getValueAt(i, 1)))) + 2 * CellLabel.CELL_BORDER_WIDTH);\n            }\n            return width;\n        }\n\n        /**\n         * Returns the table's preferred size.\n         */\n        @Override\n        public Dimension getPreferredSize() {return new Dimension(getIconWidth() + 2 * getLabelWidth(), getModel().getRowCount()*getRowHeight());}\n\n        /**\n         * Returns the table's preferred size.\n         */\n        @Override\n        public Dimension getPreferredScrollableViewportSize() {\n            if (preferredSize == null) {\n                preferredSize = getPreferredSize();\n            }\n            return preferredSize;\n        }\n\n        /**\n         * Initializes the row height depending on the font.\n         */\n        private void setRowHeight(Font font) {\n            setRowHeight(2 * CellLabel.CELL_BORDER_HEIGHT + Math.max(getFontMetrics(font).getHeight(),\n                                                                     (int)FileIcons.getIconDimension().getHeight()));\n        }\n\n        /**\n         * Uses our preview cell renderer rather than the default one.\n         */\n        @Override\n        public TableCellRenderer getCellRenderer(int row, int column) {\n            return cellRenderer;\n        }\n\n        /**\n         * Cell are not editable.\n         */\n        @Override\n        public boolean isCellEditable(int row, int column) {return false;}\n    }\n\n\n\n    /**\n     * Used to render preview cells.\n     * @author Nicolas Rinaudo\n     */\n    private class PreviewCellRenderer implements TableCellRenderer {\n        private final CellLabel label;\n        private final CellLabel icon;\n\n        /**\n         * Creates a new preview cell renderer.\n         */\n        PreviewCellRenderer() {\n            label = new CellLabel();\n            icon  = new CellLabel();\n        }\n\n        /**\n         * Returns the foreground color of the specified cell.\n         */\n        private Color getForegroundColor(RowType row, boolean isSelected) {\n            switch(row) {\n                // Folders.\n                case FOLDER:\n                    if (FilePreviewPanel.this.isActive) {\n                        return getColor(isSelected ? FOLDER_SELECTED_FOREGROUND_COLOR : FOLDER_FOREGROUND_COLOR);\n                    } else {\n                        return getColor(isSelected ? FOLDER_INACTIVE_SELECTED_FOREGROUND_COLOR : FOLDER_INACTIVE_FOREGROUND_COLOR);\n                    }\n\n                // Plain files.\n                case PLAIN_FILE:\n                    if (FilePreviewPanel.this.isActive) {\n                        return getColor(isSelected ? FILE_SELECTED_FOREGROUND_COLOR : FILE_FOREGROUND_COLOR);\n                    } else {\n                        return getColor(isSelected ? FILE_INACTIVE_SELECTED_FOREGROUND_COLOR : FILE_INACTIVE_FOREGROUND_COLOR);\n                    }\n\n                // Archives.\n                case ARCHIVE:\n                    if (FilePreviewPanel.this.isActive) {\n                        return getColor(isSelected ? ARCHIVE_SELECTED_FOREGROUND_COLOR : ARCHIVE_FOREGROUND_COLOR);\n                    } else {\n                        return getColor(isSelected ? ARCHIVE_INACTIVE_SELECTED_FOREGROUND_COLOR : ARCHIVE_INACTIVE_FOREGROUND_COLOR);\n                    }\n                // Hidden folders.\n                case HIDDEN_FOLDER:\n                    if (FilePreviewPanel.this.isActive) {\n                        return getColor(isSelected ? HIDDEN_FOLDER_SELECTED_FOREGROUND_COLOR : HIDDEN_FOLDER_FOREGROUND_COLOR);\n                    } else {\n                        return getColor(isSelected ? HIDDEN_FOLDER_INACTIVE_SELECTED_FOREGROUND_COLOR : HIDDEN_FOLDER_INACTIVE_FOREGROUND_COLOR);\n                    }\n\n                // Hidden files.\n                case HIDDEN_FILE:\n                    if (FilePreviewPanel.this.isActive) {\n                        return getColor(isSelected ? HIDDEN_FILE_SELECTED_FOREGROUND_COLOR : HIDDEN_FILE_FOREGROUND_COLOR);\n                    } else {\n                        return getColor(isSelected ? HIDDEN_FILE_INACTIVE_SELECTED_FOREGROUND_COLOR : HIDDEN_FILE_INACTIVE_FOREGROUND_COLOR);\n                    }\n\n                // Symlinks.\n                case SYMLINK:\n                    if (FilePreviewPanel.this.isActive) {\n                        return getColor(isSelected ? SYMLINK_SELECTED_FOREGROUND_COLOR : SYMLINK_FOREGROUND_COLOR);\n                    } else {\n                        return getColor(isSelected ? SYMLINK_INACTIVE_SELECTED_FOREGROUND_COLOR : SYMLINK_INACTIVE_FOREGROUND_COLOR);\n                    }\n\n                // Marked files.\n                case MARKED_FILE:\n                    if (FilePreviewPanel.this.isActive) {\n                        return getColor(isSelected ? MARKED_SELECTED_FOREGROUND_COLOR : MARKED_FOREGROUND_COLOR);\n                    } else {\n                        return getColor(isSelected ? MARKED_INACTIVE_SELECTED_FOREGROUND_COLOR : MARKED_INACTIVE_FOREGROUND_COLOR);\n                    }\n\n                // Executable files.\n                case EXECUTABLE_FILE:\n                    if (FilePreviewPanel.this.isActive) {\n                        return getColor(isSelected ? EXECUTABLE_SELECTED_FOREGROUND_COLOR : EXECUTABLE_FOREGROUND_COLOR);\n                    } else {\n                        return getColor(isSelected ? EXECUTABLE_INACTIVE_SELECTED_FOREGROUND_COLOR : EXECUTABLE_INACTIVE_FOREGROUND_COLOR);\n                    }\n                case GROUP_1_FILE:\n                case GROUP_2_FILE:\n                case GROUP_3_FILE:\n                case GROUP_4_FILE:\n                case GROUP_5_FILE:\n                case GROUP_6_FILE:\n                case GROUP_7_FILE:\n                case GROUP_8_FILE:\n                case GROUP_9_FILE:\n                case GROUP_10_FILE:\n                    int group = row.ordinal() - RowType.GROUP_1_FILE.ordinal();\n                    return getColor(FILE_GROUP_1_FOREGROUND_COLOR + group);\n            }\n\n            // Impossible.\n            return null;\n        }\n\n        /**\n         * Returns the object used to render the specified cell.\n         */\n        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {\n            RowType rowType = RowType.values()[row];\n            CellLabel currentLabel;\n\n            // Icon label foreground.\n            if (column == 0) {\n                currentLabel = icon;\n                if (rowType == RowType.FOLDER) {\n                    currentLabel.setIcon(IconManager.getIcon(IconManager.IconSet.FILE, CustomFileIconProvider.FOLDER_ICON_NAME));\n                } else if (rowType == RowType.ARCHIVE) {\n                    currentLabel.setIcon(IconManager.getIcon(IconManager.IconSet.FILE, CustomFileIconProvider.ARCHIVE_ICON_NAME));\n                } else if (rowType == RowType.SYMLINK) {\n                    currentLabel.setIcon(symlinkIcon);\n                } else {\n                    currentLabel.setIcon(IconManager.getIcon(IconManager.IconSet.FILE, CustomFileIconProvider.FILE_ICON_NAME));\n                }\n            }\n            // Text label foreground.\n            else {\n                currentLabel = label;\n                currentLabel.setFont(data.getFont(FILE_TABLE_FONT));\n                currentLabel.setText((String)value);\n                currentLabel.setForeground(getForegroundColor(rowType, isSelected));\n            }\n\n            // Foreground.\n            if (isSelected) {\n                currentLabel.setOutline(getColor(isActive ? FILE_TABLE_SELECTED_OUTLINE_COLOR : FILE_TABLE_INACTIVE_SELECTED_OUTLINE_COLOR));\n            } else {\n                currentLabel.setOutline(null);\n            }\n\n            // Background.\n            if (FilePreviewPanel.this.isActive) {\n                if (isSelected) {\n                    currentLabel.setBackground(getColor(FILE_TABLE_SELECTED_BACKGROUND_COLOR));\n                } else {\n                    currentLabel.setBackground(getColor((row % 2 == 0) ? FILE_TABLE_BACKGROUND_COLOR : FILE_TABLE_ALTERNATE_BACKGROUND_COLOR));\n                }\n            } else {\n                if (isSelected) {\n                    currentLabel.setBackground(getColor(FILE_TABLE_INACTIVE_SELECTED_BACKGROUND_COLOR));\n                } else {\n                    currentLabel.setBackground(getColor((row % 2 == 0) ? FILE_TABLE_INACTIVE_BACKGROUND_COLOR : FILE_TABLE_INACTIVE_ALTERNATE_BACKGROUND_COLOR));\n                }\n            }\n\n            return currentLabel;\n        }\n    }\n\n    private Color getColor(int id) {\n        return data.getColor(id);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/theme/FolderPanePanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.pref.theme;\n\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.chooser.FontChooser;\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\nimport com.mucommander.ui.layout.ProportionalGridPanel;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.theme.ThemeData;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n/**\n * @author Nicolas Rinaudo, Maxence Bernard\n */\nclass FolderPanePanel extends ThemeEditorPanel {\n\n    private FileGroupsPanel fileGroupsPanel;\n\n    /**\n     * Creates a new file table editor.\n     * @param parent dialog containing the panel.\n     * @param themeData themeData being edited.\n     */\n    FolderPanePanel(PreferencesDialog parent, ThemeData themeData) {\n        super(parent, Translator.get(\"theme_editor.folder_tab\"), themeData);\n        initUI();\n    }\n\n\n    /**\n     * Initializes the panel's UI.\n     */\n    private void initUI() {\n        FontChooser fontChooser = createFontChooser(ThemeData.FILE_TABLE_FONT);\n\n        JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.TOP);\n\n        // Adds the general panel.\n        tabbedPane.add(Translator.get(\"theme_editor.general\"), createScrollPane(createGeneralPanel(fontChooser)));\n\n        // Adds the active panel.\n\t\tFilePanel activeFilePanel = new FilePanel(parent, true, themeData, fontChooser);\n\t\ttabbedPane.add(activeFilePanel.getTitle(), createScrollPane(activeFilePanel));\n\n        // Adds the inactive panel.\n\t\tFilePanel inactiveFilePanel = new FilePanel(parent, false, themeData, fontChooser);\n\t\ttabbedPane.add(inactiveFilePanel.getTitle(), createScrollPane(inactiveFilePanel));\n\n\t\t// Give the file panels the colors of the respective other one to let them copy from each other.\n\t\tactiveFilePanel.setCopyColorButtonsSource(inactiveFilePanel);\n\t\tinactiveFilePanel.setCopyColorButtonsSource(activeFilePanel);\n\n        // Adds the file groups panel.\n        fileGroupsPanel = new FileGroupsPanel(parent, themeData);\n        tabbedPane.add(fileGroupsPanel.getTitle(), createScrollPane(fileGroupsPanel));\n\n        // Creates the layout.\n        setLayout(new BorderLayout());\n        add(tabbedPane, BorderLayout.NORTH);\n    }\n\n    /**\n     * Creates the 'general' theme.\n     */\n    private JPanel createGeneralPanel(FontChooser chooser) {\n        ProportionalGridPanel panel = new ProportionalGridPanel(4);\n\n        // Initializes the quicksearch panel.\n        addLabelRow(panel);\n        panel.add(addColorButtons(panel, chooser, \"theme_editor.quick_search.unmatched_file\", ThemeData.FILE_TABLE_UNMATCHED_FOREGROUND_COLOR,\n                                  ThemeData.FILE_TABLE_UNMATCHED_BACKGROUND_COLOR));\n        JPanel quickSearchPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        quickSearchPanel.add(panel);\n        quickSearchPanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"theme_editor.quick_search\")));\n\n        // Initializes the panel.\n        YBoxPanel mainPanel = new YBoxPanel();\n        mainPanel.add(chooser);\n        mainPanel.addSpace(10);\n        mainPanel.add(quickSearchPanel);\n\n        // Wraps everything in a border layout.\n        JPanel wrapper = new JPanel(new BorderLayout());\n        wrapper.add(mainPanel, BorderLayout.NORTH);\n        return wrapper;\n    }\n\n\n    /**\n     * Modification management.\n     * Ignored.\n     */\n    @Override\n    public void commit() {\n        fileGroupsPanel.commit();\n    }\n}\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/theme/LocationBarPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.pref.theme;\n\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.chooser.FontChooser;\nimport com.mucommander.ui.chooser.PreviewLabel;\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\nimport com.mucommander.ui.layout.ProportionalGridPanel;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.progress.ProgressTextField;\nimport com.mucommander.ui.theme.ThemeData;\nimport com.mucommander.ui.theme.ThemeId;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.beans.PropertyChangeEvent;\nimport java.beans.PropertyChangeListener;\n\n/**\n * @author Nicolas Rinaudo, Maxence Bernard\n */\nclass LocationBarPanel extends ThemeEditorPanel implements PropertyChangeListener, ThemeId {\n    private ProgressTextField  normalPreview;\n    private ProgressTextField  progressPreview;\n\n    /**\n     * Creates a new file table editor.\n     * @param parent   dialog containing the panel.\n     * @param themeData themeData being edited.\n     */\n    LocationBarPanel(PreferencesDialog parent, ThemeData themeData) {\n        super(parent, Translator.get(\"theme_editor.locationbar_tab\"), themeData);\n        initUI();\n    }\n\n    private JPanel createConfigurationPanel() {\n        FontChooser           fontChooser;\n        YBoxPanel             mainPanel;\n        JPanel                flowPanel;\n        ProportionalGridPanel colorsPanel;\n        PreviewLabel          label;\n\n        fontChooser = createFontChooser(LOCATION_BAR_FONT);\n        addFontChooserListener(fontChooser, normalPreview);\n        addFontChooserListener(fontChooser, progressPreview);\n\n        addLabelRow(colorsPanel = new ProportionalGridPanel(3), false);\n\n        label = new PreviewLabel();\n        addColorButtons(colorsPanel, fontChooser, \"theme_editor.normal\",\n                          LOCATION_BAR_FOREGROUND_COLOR, LOCATION_BAR_BACKGROUND_COLOR, label).addPropertyChangeListener(this);\n        addColorButtons(colorsPanel, fontChooser, \"theme_editor.selected\",\n                          LOCATION_BAR_SELECTED_FOREGROUND_COLOR, LOCATION_BAR_SELECTED_BACKGROUND_COLOR).addPropertyChangeListener(this);\n\n        label.setTextPainted(true);\n        addFontChooserListener(fontChooser, label);\n        colorsPanel.add(createCaptionLabel(\"theme_editor.progress\"));\n        PreviewLabel previewLabel = new PreviewLabel();\n        previewLabel.setTextPainted(true);\n        colorsPanel.add(new JLabel());\n        ColorButton btn = new ColorButton(parent, themeData, LOCATION_BAR_PROGRESS_COLOR, PreviewLabel.OVERLAY_COLOR_PROPERTY_NAME, previewLabel);\n        colorsPanel.add(btn);\n        btn.addUpdatedPreviewComponent(label);\n        label.addPropertyChangeListener(this);\n\n        flowPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        flowPanel.add(colorsPanel);\n        flowPanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"theme_editor.colors\")));\n\n        mainPanel = new YBoxPanel();\n        mainPanel.add(fontChooser);\n        mainPanel.addSpace(10);\n        mainPanel.add(flowPanel);\n\n        /*\n        normalPreview.setPreferredSize(new Dimension((normalPreview.getPreferredSize().width * 3) / 2, normalPreview.getPreferredSize().height));\n        progressPreview.setPreferredSize(new Dimension((progressPreview.getPreferredSize().width * 3) / 2, progressPreview.getPreferredSize().height));\n        */\n\n        return mainPanel;\n    }\n\n    private JPanel createPreviewPanel() {\n        YBoxPanel panel;\n        JPanel    borderPanel;\n\n        panel = new YBoxPanel();\n\n        //        panel.add(new JLabel(Translator.get(\"theme_editor.normal\")));\n        panel.add(createCaptionLabel(\"theme_editor.normal\"));\n        normalPreview = new ProgressTextField(0, themeData.getColor(LOCATION_BAR_PROGRESS_COLOR));\n        panel.add(normalPreview);\n        normalPreview.setText(System.getProperty(\"user.home\"));\n\n        panel.addSpace(10);\n        panel.add(createCaptionLabel(\"theme_editor.progress\"));\n        progressPreview = new ProgressTextField(50, themeData.getColor(LOCATION_BAR_PROGRESS_COLOR));\n        panel.add(progressPreview);\n        progressPreview.setText(System.getProperty(\"user.home\"));\n        progressPreview.setEnabled(false);\n\n        borderPanel = new JPanel(new BorderLayout());\n        borderPanel.add(panel, BorderLayout.NORTH);\n        borderPanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"preview\")));\n\n        setBackgroundColors();\n        setForegroundColors();\n        setProgressColors();\n\n        return borderPanel;\n    }\n\n    /**\n     * Initializes the panel's UI.\n     */\n    private void initUI() {\n        JPanel panel;\n\n        panel = new JPanel(new BorderLayout());\n        panel.add(createPreviewPanel(), BorderLayout.EAST);\n        panel.add(createConfigurationPanel(), BorderLayout.CENTER);\n\n        setLayout(new BorderLayout());\n        add(panel, BorderLayout.NORTH);\n    }\n\n    public void propertyChange(PropertyChangeEvent event) {\n        // Background color changed.\n        if (event.getPropertyName().equals(PreviewLabel.BACKGROUND_COLOR_PROPERTY_NAME)) {\n            setBackgroundColors();\n\n            // Foreground color changed.\n        } else if (event.getPropertyName().equals(PreviewLabel.FOREGROUND_COLOR_PROPERTY_NAME)) {\n            setForegroundColors();\n\n            // Overlay color changed.\n        } else if(event.getPropertyName().equals(PreviewLabel.OVERLAY_COLOR_PROPERTY_NAME)) {\n            setProgressColors();\n        }\n    }\n\n    private void setBackgroundColors() {\n        normalPreview.setBackground(themeData.getColor(LOCATION_BAR_BACKGROUND_COLOR));\n        normalPreview.setSelectionColor(themeData.getColor(LOCATION_BAR_SELECTED_BACKGROUND_COLOR));\n        progressPreview.setBackground(themeData.getColor(LOCATION_BAR_BACKGROUND_COLOR));\n        progressPreview.setSelectionColor(themeData.getColor(LOCATION_BAR_SELECTED_BACKGROUND_COLOR));\n    }\n\n    private void setForegroundColors() {\n        normalPreview.setForeground(themeData.getColor(LOCATION_BAR_FOREGROUND_COLOR));\n        normalPreview.setSelectedTextColor(themeData.getColor(LOCATION_BAR_SELECTED_FOREGROUND_COLOR));\n        progressPreview.setForeground(themeData.getColor(LOCATION_BAR_FOREGROUND_COLOR));\n        progressPreview.setSelectedTextColor(themeData.getColor(LOCATION_BAR_SELECTED_FOREGROUND_COLOR));\n        progressPreview.setDisabledTextColor(themeData.getColor(LOCATION_BAR_FOREGROUND_COLOR));\n    }\n\n    private void setProgressColors() {\n        progressPreview.setProgressColor(themeData.getColor(LOCATION_BAR_PROGRESS_COLOR));\n    }\n\n    // - Modification management ---------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    @Override\n    public void commit() {}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/theme/QuickListPanel.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.pref.theme;\r\n\r\nimport java.awt.BorderLayout;\r\nimport java.awt.Dimension;\r\nimport java.awt.FlowLayout;\r\nimport java.awt.event.ComponentEvent;\r\nimport java.awt.event.ComponentListener;\r\nimport java.awt.event.KeyListener;\r\nimport java.beans.PropertyChangeEvent;\r\nimport java.beans.PropertyChangeListener;\r\n\r\nimport javax.swing.BorderFactory;\r\nimport javax.swing.Icon;\r\nimport javax.swing.JPanel;\r\nimport javax.swing.JScrollPane;\r\nimport javax.swing.JTabbedPane;\r\n\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.chooser.FontChooser;\r\nimport com.mucommander.ui.chooser.PreviewLabel;\r\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\r\nimport com.mucommander.ui.icon.CustomFileIconProvider;\r\nimport com.mucommander.ui.icon.IconManager;\r\nimport com.mucommander.ui.layout.ProportionalGridPanel;\r\nimport com.mucommander.ui.layout.YBoxPanel;\r\nimport com.mucommander.ui.quicklist.QuickList;\r\nimport com.mucommander.ui.quicklist.item.QuickListDataList;\r\nimport com.mucommander.ui.quicklist.item.QuickListDataListWithIcons;\r\nimport com.mucommander.ui.quicklist.item.QuickListHeaderItem;\r\nimport com.mucommander.ui.theme.ThemeData;\r\nimport com.mucommander.ui.theme.ThemeId;\r\n\r\n/**\r\n * @author Arik Hadas\r\n */\r\npublic class QuickListPanel extends ThemeEditorPanel implements PropertyChangeListener, ThemeId {\r\n\t// - Instance fields -----------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /** Used to preview the quick list. */\r\n\tprivate JPanel quickListPreviewPanel;\r\n\t\r\n\t/** The header of the sample quick list */\r\n    private QuickListHeaderItem header = new QuickListHeaderItem(Translator.get(\"sample_text\"));\r\n\r\n    /** The items of the sample quick list */\r\n    private final static String[] sampleData;\r\n    \r\n    /** The icon of the sample items */\r\n    private final Icon sampleIcon = IconManager.getIcon(IconManager.IconSet.FILE, CustomFileIconProvider.FOLDER_ICON_NAME);\r\n\r\n    static {\r\n        String sampleText = Translator.get(\"sample_text\");\r\n        sampleData = new String[10];\r\n        for (int i=0; i<sampleData.length; i++) {\r\n            sampleData[i] = sampleText + \" \" + (i + 1) + \"   \";\r\n        }\r\n    }\r\n\r\n    /** The list of items of the sample quick list */\r\n    private QuickListDataList<String> list = new QuickListDataListWithIcons<String>(sampleData) {\r\n    \t\r\n    \t{\r\n    \t\tfor (KeyListener listener : getKeyListeners())\r\n    \t\t\tremoveKeyListener(listener);\r\n    \t}\r\n    \t\r\n\t\t@Override\r\n        public Icon getImageIconOfItem(String item,  final Dimension preferredSize) {\r\n\t\t\treturn sampleIcon;\r\n\t\t}\r\n\t\t\r\n\t\t@Override\r\n        protected void addMouseListenerToList() { }\r\n    };\r\n    \r\n    /**\r\n     * Creates the quick list preview panel.\r\n     * @return the quick list preview panel.\r\n     */\r\n    private JPanel createPreviewPanel() {\r\n    \tJPanel      panel;  // Preview panel.\r\n        JScrollPane scroll; // Wraps the preview quick list.\r\n\r\n        // add JScrollPane that contains the TablePopupDataList to the popup.\r\n\t\tscroll = new JScrollPane(list,\r\n\t\t\t\tJScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,\r\n\t\t\t\tJScrollPane.HORIZONTAL_SCROLLBAR_NEVER);\t\t\t\t\r\n\t\tscroll.setBorder(null);\t\t        \r\n\t\tscroll.getVerticalScrollBar().setFocusable( false ); \r\n        scroll.getHorizontalScrollBar().setFocusable( false );\r\n\r\n        // Creates the panel.\r\n        panel = new JPanel();        \r\n        quickListPreviewPanel = new YBoxPanel();        \r\n        quickListPreviewPanel.add(header);\r\n        quickListPreviewPanel.add(scroll);\r\n        quickListPreviewPanel.setBorder(new QuickList.PopupsBorder());\r\n        panel.add(quickListPreviewPanel);\r\n        panel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"preview\")));\r\n        \r\n        return panel;\r\n    }\r\n    \r\n\t// - Initialization ------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /**\r\n     * Creates a new quick list editor.\r\n     * @param parent    dialog containing the panel.\r\n     * @param themeData  themeData being edited.\r\n     */\r\n    QuickListPanel(PreferencesDialog parent, ThemeData themeData) {\r\n        super(parent, Translator.get(\"quick_lists_menu\"), themeData);\r\n        initUI();\r\n    }\r\n\t\r\n\t// - UI initialization ---------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /**\r\n     * Creates the JPanel that contains all of the item's color configuration elements.\r\n     * @param fontChooser font chooser used by the editor panel.\r\n     * @return the JPanel that contains all of the item's color configuration elements.\r\n     */\r\n    private JPanel createItemColorsPanel(FontChooser fontChooser) {\r\n        ProportionalGridPanel gridPanel = new ProportionalGridPanel(3); // Contains all the color buttons.\r\n\r\n        addLabelRow(gridPanel, false);\r\n\r\n        PreviewLabel label = new PreviewLabel();\r\n        \r\n        // Color buttons.\r\n        addColorButtons(gridPanel, fontChooser, \"theme_editor.normal\",\r\n                QUICK_LIST_ITEM_FOREGROUND_COLOR, QUICK_LIST_ITEM_BACKGROUND_COLOR, label).addPropertyChangeListener(this);\r\n        addColorButtons(gridPanel, fontChooser, \"theme_editor.selected\",\r\n                QUICK_LIST_SELECTED_ITEM_FOREGROUND_COLOR, QUICK_LIST_SELECTED_ITEM_BACKGROUND_COLOR, label).addPropertyChangeListener(this);\r\n        label.addPropertyChangeListener(this);\r\n        \r\n        // Wraps everything in a flow layout.\r\n        JPanel colorsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\r\n        colorsPanel.add(gridPanel);\r\n        colorsPanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"theme_editor.colors\")));\r\n\r\n        return colorsPanel;\r\n    }\r\n    \r\n    /**\r\n     * Creates the JPanel that contains all the header's color configuration elements.\r\n     * @param fontChooser font chooser used by the editor panel.\r\n     * @return the JPanel that contains all the header's color configuration elements.\r\n     */\r\n    private JPanel createHeaderColorsPanel(FontChooser fontChooser) {\r\n        ProportionalGridPanel gridPanel = new ProportionalGridPanel(3);  // // Contains all the color buttons.\r\n\r\n        // Header.\r\n        addLabelRow(gridPanel, false);\r\n\r\n        PreviewLabel label = new PreviewLabel();\r\n        \r\n        // Color buttons.        \r\n        addColorButtons(gridPanel, fontChooser, \"\",\r\n        \t\tQUICK_LIST_HEADER_FOREGROUND_COLOR, QUICK_LIST_HEADER_BACKGROUND_COLOR, label).addPropertyChangeListener(this);\r\n        addColorButtons(gridPanel, fontChooser, \"\",\r\n                -1, QUICK_LIST_HEADER_SECONDARY_BACKGROUND_COLOR, label).addPropertyChangeListener(this);\r\n        label.addPropertyChangeListener(this);\r\n\r\n        // Wraps everything in a flow layout.\r\n        JPanel colorsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\r\n        colorsPanel.add(gridPanel);\r\n        colorsPanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"theme_editor.colors\")));\r\n\r\n        return colorsPanel;\r\n    }\r\n\r\n\t/**\r\n     * Initializes the panel's UI.\r\n     */\r\n    private void initUI() {\r\n        header.addComponentListener(new ComponentListener() {\r\n\r\n\t\t\tpublic void componentHidden(ComponentEvent e) {}\r\n\r\n\t\t\tpublic void componentMoved(ComponentEvent e) {}\r\n\r\n\t\t\tpublic void componentResized(ComponentEvent e) {\r\n\t\t\t\tquickListPreviewPanel.repaint();\r\n\t\t\t}\r\n\r\n\t\t\tpublic void componentShown(ComponentEvent e) {}\r\n        });\r\n\r\n\r\n        // Font chooser and preview initialization.\r\n        FontChooser fontChooser1 = createFontChooser(QUICK_LIST_HEADER_FONT);\r\n        FontChooser fontChooser2 = createFontChooser(QUICK_LIST_ITEM_FONT);\r\n        addFontChooserListener(fontChooser1, header);\r\n        addFontChooserListener(fontChooser2, list);\r\n\r\n        // Header configuration panel initialization.\r\n        YBoxPanel headerConfigurationPanel = new YBoxPanel();\r\n        headerConfigurationPanel.add(fontChooser1);\r\n        headerConfigurationPanel.addSpace(10);\r\n        headerConfigurationPanel.add(createHeaderColorsPanel(fontChooser1));\r\n\r\n        // Item configuration panel initialization.\r\n        YBoxPanel itemConfigurationPanel = new YBoxPanel();\r\n        itemConfigurationPanel.add(fontChooser2);\r\n        itemConfigurationPanel.addSpace(10);\r\n        itemConfigurationPanel.add(createItemColorsPanel(fontChooser1));\r\n        \r\n        // create the tabbed pane.\r\n        JTabbedPane tabbedPane = new JTabbedPane();\r\n        tabbedPane.add(Translator.get(\"theme_editor.header\"), headerConfigurationPanel);\r\n        tabbedPane.add(Translator.get(\"theme_editor.item\"), itemConfigurationPanel);\r\n        \r\n        // Main layout.\r\n        JPanel mainPanel   = new JPanel(new BorderLayout());\r\n        mainPanel.add(tabbedPane, BorderLayout.CENTER);\r\n        mainPanel.add(createPreviewPanel(), BorderLayout.EAST);\r\n        \r\n        // Layout.\r\n        setLayout(new BorderLayout());\r\n        add(mainPanel, BorderLayout.NORTH);\r\n    }\r\n    \r\n    /**\r\n     * Listens on changes on the foreground and background colors.\r\n     */\r\n    public void propertyChange(PropertyChangeEvent event) {\r\n        // Background color changed.\r\n        if (event.getPropertyName().equals(PreviewLabel.BACKGROUND_COLOR_PROPERTY_NAME)) {\r\n            header.setBackgroundColors(themeData.getColor(QUICK_LIST_HEADER_BACKGROUND_COLOR),\r\n            \t\tthemeData.getColor(QUICK_LIST_HEADER_SECONDARY_BACKGROUND_COLOR));\r\n            list.setBackgroundColors(themeData.getColor(QUICK_LIST_ITEM_BACKGROUND_COLOR),\r\n            \t\t\t\t\t\t themeData.getColor(QUICK_LIST_SELECTED_ITEM_BACKGROUND_COLOR));\r\n        }\r\n\r\n        // Foreground color changed.\r\n        else if (event.getPropertyName().equals(PreviewLabel.FOREGROUND_COLOR_PROPERTY_NAME)) {\r\n            header.setForegroundColor(themeData.getColor(QUICK_LIST_HEADER_FOREGROUND_COLOR));\r\n            list.setForegroundColors(themeData.getColor(QUICK_LIST_ITEM_FOREGROUND_COLOR),\r\n            \t\t\t\t\t\t themeData.getColor(QUICK_LIST_SELECTED_ITEM_FOREGROUND_COLOR));\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Ignored.\r\n     */\r\n    @Override\r\n    public void commit() {}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/theme/ShellPanel.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.pref.theme;\r\n\r\nimport com.mucommander.RuntimeConstants;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.chooser.FontChooser;\r\nimport com.mucommander.ui.chooser.PreviewLabel;\r\nimport com.mucommander.ui.combobox.EditableComboBox;\r\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\r\nimport com.mucommander.ui.layout.ProportionalGridPanel;\r\nimport com.mucommander.ui.layout.YBoxPanel;\r\nimport com.mucommander.ui.theme.ThemeData;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.BorderLayout;\r\nimport java.awt.FlowLayout;\r\nimport java.beans.PropertyChangeEvent;\r\nimport java.beans.PropertyChangeListener;\r\n\r\n/**\r\n * @author Nicolas Rinaudo, Maxence Bernard\r\n */\r\nclass ShellPanel extends ThemeEditorPanel implements PropertyChangeListener {\r\n    private JTextArea shellPreview;\r\n    private JTabbedPane tabbedPane;\r\n    private EditableComboBox<String> historyPreview;\r\n    private JLabel lblRun, lblOutput;\r\n\r\n\r\n    /**\r\n     * Creates a new file table editor.\r\n     * @param parent   dialog containing the panel.\r\n     * @param themeData themeData being edited.\r\n     */\r\n    ShellPanel(PreferencesDialog parent, ThemeData themeData) {\r\n        super(parent, Translator.get(\"theme_editor.shell_tab\"), themeData);\r\n        initUI();\r\n    }\r\n\r\n    private JComponent createConfigurationPanel(int fontId, int foregroundId, int backgroundId, int selectedForegroundId, int selectedBackgroundId, JComponent fontListener) {\r\n        YBoxPanel mainPanel = new YBoxPanel();\r\n\r\n        FontChooser fontChooser = createFontChooser(fontId);\r\n        mainPanel.add(fontChooser);\r\n        mainPanel.addSpace(10);\r\n        addFontChooserListener(fontChooser, fontListener);\r\n\r\n        ProportionalGridPanel colorPanel = new ProportionalGridPanel(3);\r\n        addLabelRow(colorPanel, false);\r\n\r\n        addColorButtons(colorPanel, fontChooser, \"theme_editor.normal\", foregroundId, backgroundId).addPropertyChangeListener(this);\r\n        addColorButtons(colorPanel, fontChooser, \"theme_editor.selected\", selectedForegroundId, selectedBackgroundId).addPropertyChangeListener(this);\r\n\r\n        JPanel flowPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\r\n        flowPanel.add(colorPanel);\r\n        flowPanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"theme_editor.colors\")));\r\n\r\n        mainPanel.add(flowPanel);\r\n\r\n        return createScrollPane(mainPanel);\r\n    }\r\n\r\n    private JPanel createPreviewPanel() {\r\n        JPanel panel = new JPanel(new BorderLayout());\r\n        panel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"preview\")));\r\n\r\n        YBoxPanel headerPanel = new YBoxPanel();\r\n        lblRun = new JLabel(Translator.get(\"run_dialog.run_command_description\") + \":\");\r\n        headerPanel.add(lblRun);\r\n        headerPanel.add(historyPreview = new EditableComboBox<>(new JTextField(\"trolcommander -v\")));\r\n        historyPreview.addItem(\"trolcommander -v\");\r\n        historyPreview.addItem(\"java -version\");\r\n\r\n        headerPanel.addSpace(10);\r\n        lblOutput = new JLabel(Translator.get(\"run_dialog.command_output\")+\":\");\r\n        headerPanel.add(lblOutput);\r\n\r\n        panel.add(headerPanel, BorderLayout.NORTH);\r\n\r\n        shellPreview = new JTextArea(20, 15);\r\n        JScrollPane scroll = new JScrollPane(shellPreview, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);\r\n        panel.add(scroll, BorderLayout.CENTER);\r\n        scroll.getViewport().setPreferredSize(shellPreview.getPreferredSize());\r\n        shellPreview.append(RuntimeConstants.APP_STRING);\r\n        shellPreview.append(\"\\nCopyright (C) \");\r\n        shellPreview.append(RuntimeConstants.COPYRIGHT);\r\n        shellPreview.append(\" Oleg Trifonov\\nThis is free software, distributed under the terms of the GNU General Public License.\");\r\n        shellPreview.setCaretPosition(0);\r\n\r\n        setForegroundColors();\r\n        setBackgroundColors();\r\n\r\n        return panel;\r\n    }\r\n\r\n\r\n    /**\r\n     * Initializes the panel's UI.\r\n     */\r\n    private void initUI() {\r\n        setLayout(new BorderLayout());\r\n\r\n        tabbedPane = new JTabbedPane();\r\n        JPanel previewPanel = createPreviewPanel();\r\n\r\n        tabbedPane.add(Translator.get(\"theme_editor.shell_tab\"),\r\n                       createConfigurationPanel(ThemeData.SHELL_FONT, ThemeData.SHELL_FOREGROUND_COLOR, ThemeData.SHELL_BACKGROUND_COLOR,\r\n                                                ThemeData.SHELL_SELECTED_FOREGROUND_COLOR, ThemeData.SHELL_SELECTED_BACKGROUND_COLOR, shellPreview));\r\n        tabbedPane.add(Translator.get(\"theme_editor.shell_history_tab\"),\r\n                       createConfigurationPanel(ThemeData.SHELL_HISTORY_FONT, ThemeData.SHELL_HISTORY_FOREGROUND_COLOR, ThemeData.SHELL_HISTORY_BACKGROUND_COLOR,\r\n                                                ThemeData.SHELL_HISTORY_SELECTED_FOREGROUND_COLOR, ThemeData.SHELL_HISTORY_SELECTED_BACKGROUND_COLOR, historyPreview));\r\n\r\n        tabbedPane.add(Translator.get(\"theme_editor.terminal_tab\"),\r\n                createConfigurationPanel(ThemeData.TERMINAL_FONT, ThemeData.TERMINAL_FOREGROUND_COLOR, ThemeData.TERMINAL_BACKGROUND_COLOR,\r\n                        ThemeData.TERMINAL_SELECTED_FOREGROUND_COLOR, ThemeData.TERMINAL_SELECTED_BACKGROUND_COLOR, shellPreview));\r\n\r\n        tabbedPane.addChangeListener(e -> {\r\n            setBackgroundColors();\r\n            setForegroundColors();\r\n            boolean shellMode = tabbedPane.getSelectedIndex() < 2;\r\n            historyPreview.setVisible(shellMode);\r\n            lblRun.setVisible(shellMode);\r\n            lblOutput.setVisible(shellMode);\r\n        });\r\n\r\n        JPanel mainPanel = new JPanel(new BorderLayout());\r\n        mainPanel.add(tabbedPane, BorderLayout.WEST);\r\n        mainPanel.add(previewPanel, BorderLayout.CENTER);\r\n\r\n        add(mainPanel, BorderLayout.NORTH);\r\n\r\n//        ThemeData.addDefaultValuesListener(new ThemeListener() {\r\n//\r\n//            @Override\r\n//            public void colorChanged(ColorChangedEvent event) {\r\n//System.out.println(event);\r\n//                setForegroundColors();\r\n//                setBackgroundColors();\r\n//            }\r\n//\r\n//            @Override\r\n//            public void fontChanged(FontChangedEvent event) {\r\n//            }\r\n//        });\r\n    }\r\n\r\n\r\n    public void propertyChange(final PropertyChangeEvent event) {\r\n        if (event.getPropertyName().equals(PreviewLabel.BACKGROUND_COLOR_PROPERTY_NAME)) {\r\n            setBackgroundColors();\r\n        } else if(event.getPropertyName().equals(PreviewLabel.FOREGROUND_COLOR_PROPERTY_NAME)) {\r\n            setForegroundColors();\r\n        }\r\n    }\r\n\r\n    private void setBackgroundColors() {\r\n        boolean shellMode = tabbedPane.getSelectedIndex() < 2;\r\n        if (shellMode) {\r\n        shellPreview.setBackground(themeData.getColor(ThemeData.SHELL_BACKGROUND_COLOR));\r\n        shellPreview.setSelectionColor(themeData.getColor(ThemeData.SHELL_SELECTED_BACKGROUND_COLOR));\r\n        historyPreview.setBackground(themeData.getColor(ThemeData.SHELL_HISTORY_BACKGROUND_COLOR));\r\n        historyPreview.setSelectionBackground(themeData.getColor(ThemeData.SHELL_HISTORY_SELECTED_BACKGROUND_COLOR));\r\n        } else {\r\n            shellPreview.setBackground(themeData.getColor(ThemeData.TERMINAL_BACKGROUND_COLOR));\r\n            shellPreview.setSelectionColor(themeData.getColor(ThemeData.TERMINAL_SELECTED_BACKGROUND_COLOR));\r\n        }\r\n    }\r\n\r\n    private void setForegroundColors() {\r\n        boolean shellMode = tabbedPane.getSelectedIndex() < 2;\r\n        if (shellMode) {\r\n            shellPreview.setForeground(themeData.getColor(ThemeData.SHELL_FOREGROUND_COLOR));\r\n            shellPreview.setSelectedTextColor(themeData.getColor(ThemeData.SHELL_SELECTED_FOREGROUND_COLOR));\r\n            shellPreview.setCaretColor(themeData.getColor(ThemeData.SHELL_FOREGROUND_COLOR));\r\n            historyPreview.setForeground(themeData.getColor(ThemeData.SHELL_HISTORY_FOREGROUND_COLOR));\r\n            historyPreview.setSelectionForeground(themeData.getColor(ThemeData.SHELL_HISTORY_SELECTED_FOREGROUND_COLOR));\r\n        } else {\r\n            shellPreview.setForeground(themeData.getColor(ThemeData.TERMINAL_FOREGROUND_COLOR));\r\n            shellPreview.setSelectedTextColor(themeData.getColor(ThemeData.TERMINAL_SELECTED_FOREGROUND_COLOR));\r\n            shellPreview.setCaretColor(themeData.getColor(ThemeData.TERMINAL_FOREGROUND_COLOR));\r\n        }\r\n    }\r\n\r\n\r\n    @Override\r\n    public void commit() {}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/theme/StatusBarPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.pref.theme;\n\nimport com.mucommander.utils.text.SizeFormat;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.border.MutableLineBorder;\nimport com.mucommander.ui.chooser.FontChooser;\nimport com.mucommander.ui.chooser.PreviewLabel;\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\nimport com.mucommander.ui.layout.ProportionalGridPanel;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.theme.ThemeData;\nimport com.mucommander.ui.theme.ThemeId;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.beans.PropertyChangeEvent;\nimport java.beans.PropertyChangeListener;\n\n/**\n * @author Nicolas Rinaudo, Maxence Bernard\n */\nclass StatusBarPanel extends ThemeEditorPanel implements PropertyChangeListener, ThemeId {\n    private static final int OK       = 0;\n    private static final int WARNING  = 1;\n    private static final int CRITICAL = 2;\n\n    private final static int WARNING_LEVEL_COLOR_IDS[] = {\n        STATUS_BAR_OK_COLOR,\n        STATUS_BAR_WARNING_COLOR,\n        STATUS_BAR_CRITICAL_COLOR\n    };\n\n    private final static String WARNING_LEVEL_LABELS[] = {\n        \"theme_editor.free_space.ok\",\n        \"theme_editor.free_space.warning\",\n        \"theme_editor.free_space.critical\"\n    };\n\n    private final static int VOLUME_INFO_SIZE_FORMAT    = SizeFormat.DIGITS_MEDIUM | SizeFormat.UNIT_SHORT | SizeFormat.INCLUDE_SPACE | SizeFormat.ROUND_TO_KB;\n    private final static long TOTAL_SIZE                = 85899345920L;\n    private final static long NORMAL_SIZE               = TOTAL_SIZE / 2;\n    private final static long WARNING_SIZE              = TOTAL_SIZE / 10;\n    private final static long CRITICAL_SIZE             = TOTAL_SIZE / 100;\n\n    private final static int WARNING_DRAW_PERCENTAGE[] = {50, 10, 1};\n\n    private final static String WARNING_LEVEL_TEXT[] = {\n        Translator.get(\"status_bar.volume_free\", SizeFormat.format(NORMAL_SIZE, VOLUME_INFO_SIZE_FORMAT) + \" / \" + SizeFormat.format(TOTAL_SIZE, VOLUME_INFO_SIZE_FORMAT)),\n        Translator.get(\"status_bar.volume_free\", SizeFormat.format(WARNING_SIZE, VOLUME_INFO_SIZE_FORMAT) + \" / \" + SizeFormat.format(TOTAL_SIZE, VOLUME_INFO_SIZE_FORMAT)),\n        Translator.get(\"status_bar.volume_free\", SizeFormat.format(CRITICAL_SIZE, VOLUME_INFO_SIZE_FORMAT) + \" / \" + SizeFormat.format(TOTAL_SIZE, VOLUME_INFO_SIZE_FORMAT))\n    };\n\n    private JLabel  normalPreview;\n    private Preview okPreview;\n    private Preview warningPreview;\n    private Preview criticalPreview;\n    \n\n    /**\n     * Creates a new file table editor.\n     * @param parent   dialog containing the panel.\n     * @param themeData themeData being edited.\n     */\n    StatusBarPanel(PreferencesDialog parent, ThemeData themeData) {\n        super(parent, Translator.get(\"theme_editor.statusbar_tab\"), themeData);\n        initUI();\n    }\n\n    private JPanel createGeneralPanel(FontChooser chooser, ColorButton foreground) {\n        // Initializes the color panel.\n        JPanel colorPanel = new ProportionalGridPanel(2);\n        colorPanel.add(createCaptionLabel(\"theme_editor.text\"));\n        colorPanel.add(foreground);\n\n        // Wraps the color panel in a flow layout.\n        JPanel flowPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        flowPanel.add(colorPanel);\n        flowPanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"theme_editor.colors\")));\n\n        // Creates the general panel.\n        YBoxPanel mainPanel = new YBoxPanel();\n        mainPanel.add(chooser);\n        mainPanel.addSpace(10);\n        mainPanel.add(flowPanel);\n\n        return mainPanel;\n    }\n\n    private JPanel createFreeSpacePanel(FontChooser chooser, ColorButton foreground, ColorButton background, ColorButton border) {\n        JPanel       colorPanel;\n        JPanel       flowPanel;\n        PreviewLabel previewLabel;\n\n        colorPanel = new ProportionalGridPanel(2);\n        colorPanel.add(new JLabel());\n        colorPanel.add(createCaptionLabel(\"theme_editor.color\"));\n        colorPanel.add(createCaptionLabel(\"theme_editor.background\"));\n        colorPanel.add(background);\n        colorPanel.add(createCaptionLabel(\"theme_editor.border\"));\n        colorPanel.add(border);\n\n        for(int i=0; i<3; i++) {\n            previewLabel = new PreviewLabel();\n            previewLabel.setOverlayUnderText(true);\n            previewLabel.setTextPainted(true);\n            foreground.addUpdatedPreviewComponent(previewLabel);\n            background.addUpdatedPreviewComponent(previewLabel);\n            border.addUpdatedPreviewComponent(previewLabel);\n            addFontChooserListener(chooser, previewLabel);\n\n            colorPanel.add(createCaptionLabel(WARNING_LEVEL_LABELS[i]));\n            colorPanel.add(new ColorButton(parent, themeData, WARNING_LEVEL_COLOR_IDS[i], PreviewLabel.OVERLAY_COLOR_PROPERTY_NAME, previewLabel));\n            previewLabel.addPropertyChangeListener(this);\n        }\n\n        flowPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        flowPanel.add(colorPanel);\n\n        return flowPanel;\n    }\n\n    private void addPreviewLabel(YBoxPanel panel, JLabel preview, String label, FontChooser chooser) {\n        JPanel wrapper;\n\n        panel.add(createCaptionLabel(label));\n\n        wrapper = new JPanel(new BorderLayout());\n        wrapper.add(preview, BorderLayout.NORTH);\n        panel.add(wrapper);\n\n        addFontChooserListener(chooser, preview);\n    }\n\n    private JPanel createPreviewPanel(FontChooser fontChooser) {\n        YBoxPanel previewPanel;\n        Insets    insets;\n\n        previewPanel = new YBoxPanel();\n\n        addPreviewLabel(previewPanel, normalPreview = new JLabel(Translator.get(\"status_bar.selected_files\", \"3\", \"14\")), \"theme_editor.normal\", fontChooser);\n        normalPreview.setForeground(themeData.getColor(ThemeData.STATUS_BAR_FOREGROUND_COLOR));\n\n        addPreviewLabel(previewPanel, okPreview       = new Preview(OK),       \"theme_editor.free_space.ok\", fontChooser);\n        addPreviewLabel(previewPanel, warningPreview  = new Preview(WARNING),  \"theme_editor.free_space.warning\", fontChooser);\n        addPreviewLabel(previewPanel, criticalPreview = new Preview(CRITICAL), \"theme_editor.free_space.critical\", fontChooser);\n\n        previewPanel.setBorder(BorderFactory.createTitledBorder(Translator.get(\"preview\")));\n\n        insets = previewPanel.getInsets();\n        previewPanel.setInsets(new Insets(insets.top, insets.left + 8, insets.bottom, insets.right + 6));\n\n        return previewPanel;\n    }\n\n    /**\n     * Initializes the panel's UI.\n     */\n    private void initUI() {\n        JPanel       mainPanel;\n        ColorButton  foreground;\n        ColorButton  background;\n        ColorButton  border;\n        PreviewLabel previewLabel;\n        PreviewLabel borderPreviewLabel;\n        FontChooser  fontChooser;\n\n        JTabbedPane tabbedPane;\n        fontChooser = createFontChooser(STATUS_BAR_FONT);\n\n        // Initializes the foreground color button.\n        foreground = new ColorButton(parent, themeData, STATUS_BAR_FOREGROUND_COLOR, PreviewLabel.FOREGROUND_COLOR_PROPERTY_NAME, previewLabel = new PreviewLabel());\n        previewLabel.setTextPainted(true);\n        addFontChooserListener(fontChooser, previewLabel);\n        previewLabel.addPropertyChangeListener(this);\n\n        // Initializes the background and border color buttons.\n        background = new ColorButton(parent, themeData, STATUS_BAR_BACKGROUND_COLOR, PreviewLabel.BACKGROUND_COLOR_PROPERTY_NAME, previewLabel = new PreviewLabel());\n        border     = new ColorButton(parent, themeData, STATUS_BAR_BORDER_COLOR, PreviewLabel.BORDER_COLOR_PROPERTY_NAME, borderPreviewLabel = new PreviewLabel());\n\n        // Initializes the background color preview.\n        previewLabel.setTextPainted(true);\n        foreground.addUpdatedPreviewComponent(previewLabel);\n        border.addUpdatedPreviewComponent(previewLabel);\n        addFontChooserListener(fontChooser, previewLabel);\n        previewLabel.addPropertyChangeListener(this);\n\n        // Initializes the border color preview.\n        borderPreviewLabel.setTextPainted(true);\n        foreground.addUpdatedPreviewComponent(borderPreviewLabel);\n        background.addUpdatedPreviewComponent(borderPreviewLabel);\n        addFontChooserListener(fontChooser, borderPreviewLabel);\n        borderPreviewLabel.addPropertyChangeListener(this);\n\n        tabbedPane   = new JTabbedPane();\n        tabbedPane.add(Translator.get(\"theme_editor.general\"), createGeneralPanel(fontChooser, foreground));\n        tabbedPane.add(Translator.get(\"theme_editor.free_space\"), createFreeSpacePanel(fontChooser, foreground, background, border));\n\n\n        // Main layout.\n        mainPanel = new JPanel(new BorderLayout());\n        mainPanel.add(tabbedPane, BorderLayout.CENTER);\n        mainPanel.add(createPreviewPanel(fontChooser), BorderLayout.EAST);\n\n        // Aligns everything north.\n        setLayout(new BorderLayout());\n        add(mainPanel, BorderLayout.NORTH);\n    }\n\n\n    // - Modification management ---------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    /**\n     * Ignored.\n     */\n    @Override\n    public void commit() {}\n\n\n\n    // - Property listening --------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    /**\n     * Refreshes the UI depending on the property event.\n     */\n    public void propertyChange(PropertyChangeEvent event) {\n        switch (event.getPropertyName()) {\n            // Repaints previews when the overlay or background color have been changed\n            case PreviewLabel.BACKGROUND_COLOR_PROPERTY_NAME:\n            case PreviewLabel.OVERLAY_COLOR_PROPERTY_NAME:\n                okPreview.repaint();\n                warningPreview.repaint();\n                criticalPreview.repaint();\n                break;\n            // Resets the preview labels' foreground color\n            case PreviewLabel.FOREGROUND_COLOR_PROPERTY_NAME:\n                okPreview.repaint();\n                warningPreview.repaint();\n                criticalPreview.repaint();\n                break;\n            // Resets the preview labels' borders\n            case PreviewLabel.BORDER_COLOR_PROPERTY_NAME:\n                okPreview.refreshBorder();\n                warningPreview.refreshBorder();\n                criticalPreview.refreshBorder();\n                break;\n        }\n    }\n\n\n\n    // - Preview labels ------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    private class Preview extends JLabel {\n        private MutableLineBorder border;\n        private int type;\n\n        Preview(int type) {\n            super(WARNING_LEVEL_TEXT[type]);\n            setOpaque(false);\n            setBorder(border = new MutableLineBorder(Color.BLACK, 1));\n            setHorizontalAlignment(CENTER);\n            setForeground(themeData.getColor(STATUS_BAR_FOREGROUND_COLOR));\n            this.type = type;\n        }\n\n        void refreshBorder() {\n            border.setLineColor(themeData.getColor(STATUS_BAR_BORDER_COLOR));\n            repaint();\n        }\n\n        @Override\n        public void paint(Graphics g) {\n            int width = ((getWidth() - 2) * WARNING_DRAW_PERCENTAGE[type]) / 100;\n\n            if (type == OK) {\n                g.setColor(themeData.getColor(STATUS_BAR_OK_COLOR));\n            } else if (type == WARNING) {\n                g.setColor(themeData.getColor(STATUS_BAR_WARNING_COLOR));\n            } else {\n                g.setColor(themeData.getColor(STATUS_BAR_CRITICAL_COLOR));\n            }\n            g.fillRect(1, 1, width + 1, getHeight() - 2);\n\n            g.setColor(themeData.getColor(STATUS_BAR_BACKGROUND_COLOR));\n            g.fillRect(width + 1, 1, getWidth() - width - 1, getHeight() - 2);\n\n            super.paint(g);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/theme/ThemeEditorDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.pref.theme;\n\nimport com.mucommander.conf.TcSnapshot;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.InformationDialog;\nimport com.mucommander.ui.dialog.QuestionDialog;\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\nimport com.mucommander.ui.dialog.pref.component.PrefComponent;\nimport com.mucommander.ui.theme.Theme;\nimport com.mucommander.ui.theme.ThemeData;\nimport com.mucommander.ui.theme.ThemeManager;\n\nimport java.awt.*;\n\n/**\n * Main dialog for the theme editor.\n * @author Nicolas Rinaudo\n */\npublic class ThemeEditorDialog extends PreferencesDialog {\n    private ThemeData data;\n    private Theme     theme;\n\tprivate Theme modifiedTheme;\n\n    /**\n     * Creates a new theme editor dialog.\n     * @param parent parent of the dialog.\n     * @param theme  theme to edit.\n     */\n    public ThemeEditorDialog(Dialog parent, Theme theme) {\n        super(parent, createTitle(theme));\n        initUI(theme);\n    }\n\n    /**\n     * Creates a new theme editor dialog.\n     * @param parent parent of the dialog.\n     * @param theme  theme to edit.\n     */\n    public ThemeEditorDialog(Frame parent, Theme theme) {\n        super(parent, createTitle(theme));\n        initUI(theme);\n    }\n\n    private static String createTitle(Theme theme) {\n        return Translator.get(\"theme_editor.title\") + \": \" + theme.getName();\n    }\n\n    private void initUI(Theme theme) {\n        this.theme = theme;\n        data = theme.cloneData();\n        addPreferencesPanel(new FolderPanePanel(this, data), false);\n        addPreferencesPanel(new LocationBarPanel(this, data));\n        addPreferencesPanel(new StatusBarPanel(this, data));\n        addPreferencesPanel(new ShellPanel(this, data));\n        addPreferencesPanel(new FileEditorPanel(this, data));\n        addPreferencesPanel(new QuickListPanel(this, data));\n\n        // Sets the dialog's size.\n        Dimension screenSize = TcSnapshot.getScreenSize();\n        Dimension minimumSize = new Dimension(580, 300);\n        Dimension maximumSize = new Dimension(screenSize);\n        if (screenSize.getWidth() >= 1024 && screenSize.getHeight() > 700) {\n            minimumSize.setSize(800, 480);\n        }\n        setMinimumSize(minimumSize);\n        setMaximumSize(maximumSize);\n    }\n\n    /**\n\t * Edits the theme specified at creation time and returns the actually modified theme. This is a new custom theme if\n\t * a pedefined theme was specified or else the theme itself.\n\t * \n\t * @return the actually modified theme it was modified by the user, null otherwise.\n\t */\n\tpublic Theme editTheme() {\n        showDialog();\n\t\treturn modifiedTheme;\n    }\n\n    @Override\n    public boolean checkCommit() {\n        super.checkCommit();\n\n\t\t// If the theme has been modified and is a predefined theme, asks the user to confirm\n\t\t// whether it's ok to duplicate it.\n        if (!theme.isIdentical(data) && !theme.canModify())\n            return new QuestionDialog(this, Translator.get(\"warning\"),\n                    Translator.get(\"theme_editor.theme_warning_predefined\"),\n                    this, new String[]{Translator.get(\"yes\"), Translator.get(\"no\")}, new int[]{0, 1}, 0).getActionValue() == 0;\n        return true;\n    }\n\n    @Override\n    public void commit() {\n        super.commit();\n        if (theme.isIdentical(data)) {\n            return;\n        }\n\n        try {\n\t\t\t// If the theme cannot be modified, create a new custom theme with the same name to save modifications.\n            if (!theme.canModify()) {\n\t\t\t\tmodifiedTheme = ThemeManager.duplicateTheme(theme);\n\t\t\t} else {\n\t\t\t\tmodifiedTheme = theme;\n            }\n\t\t\tmodifiedTheme.importData(data);\n\t\t\tThemeManager.writeTheme(modifiedTheme);\n\t\t\ttheme = modifiedTheme;\n        } catch (Exception exception) {\n\t\t\ttry {\n\t\t\t\tInformationDialog.showErrorDialog(this, Translator.get(\"write_error\"),\n\t\t\t\t        Translator.get(\"cannot_write_file\",\n\t\t\t\t                ThemeManager.getFile(theme.getType(), theme.getName()).getAbsolutePath()));\n\t\t\t\texception.printStackTrace();\n\t\t\t} catch (Exception e) {\n\t\t\t\te.printStackTrace();\n\t\t\t}\n        }\n    }\n\n    @Override\n    public void componentChanged(PrefComponent component) {\n\t\tsetCommitButtonsEnabled(!theme.isIdentical(data));\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/theme/ThemeEditorPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.pref.theme;\n\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.chooser.FontChooser;\nimport com.mucommander.ui.chooser.PreviewLabel;\nimport com.mucommander.ui.dialog.pref.PreferencesDialog;\nimport com.mucommander.ui.dialog.pref.PreferencesPanel;\nimport com.mucommander.ui.layout.ProportionalGridPanel;\nimport com.mucommander.ui.theme.ThemeData;\n\nimport javax.swing.*;\nimport javax.swing.event.ChangeEvent;\nimport javax.swing.event.ChangeListener;\nimport java.awt.*;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Base class for theme editor panels.\n * <p>\n * A <code>ThemeEditorPanel</code> is a {@link com.mucommander.ui.dialog.pref.PreferencesPanel} with some\n * theme specific features:\n * <ul>\n *   <li>Access to the {@link #themeData ThemeData} being edited.</li>\n *   <li>Helper methods for theme-specific layout creation.</li>\n * </ul>\n *\n * @author Maxence Bernard, Nicolas Rinaudo\n */\nabstract class ThemeEditorPanel extends PreferencesPanel {\n    /** Edited theme data. */\n    protected ThemeData themeData;\n\n    /** Holds references to listeners to prevent them from being garbage collected. */\n    private final List<ChangeListener> listenerReferences = new ArrayList<>();\n\n    /** Font used to display caption labels. */\n    private Font captionLabelFont;\n\n    /** Color used to display caption labels. */\n    private final Color captionTextColor = new Color(48, 48, 48);\n\n\n\n    /**\n     * Creates a new <code>ThemeEditorPanel</code>.\n     * @param parent    dialog in which the panel is stored.\n     * @param title     title of the panel.\n     * @param themeData data that is being edited.\n     */\n    ThemeEditorPanel(PreferencesDialog parent, String title, ThemeData themeData) {\n        super(parent, title);\n\n        this.themeData = themeData;\n\n        captionLabelFont = new JLabel().getFont();\n        captionLabelFont = captionLabelFont.deriveFont(Font.BOLD, captionLabelFont.getSize()-1.5f);\n    }\n\n\n\n    // - Caption label methods -----------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    /**\n     * Creates a caption label containing the specified localised entry.\n     * @param  dictionaryKey name of the dictionary entry to use in the label.\n     * @return               a caption label containing the specified localised entry.\n     */\n    JLabel createCaptionLabel(String dictionaryKey) {\n        JLabel captionLabel = new JLabel(Translator.get(dictionaryKey));\n        captionLabel.setFont(captionLabelFont);\n        captionLabel.setForeground(captionTextColor);\n\n        return captionLabel;\n    }\n\n    /**\n     * Creates a caption label containing the specified entry.\n     * @param title label title\n     * @return a caption label containing the specified entry.\n     */\n    JLabel createCaptionLabelWithTitle(String title) {\n        JLabel captionLabel = new JLabel(title);\n        captionLabel.setFont(captionLabelFont);\n        captionLabel.setForeground(captionTextColor);\n\n        return captionLabel;\n    }\n\n\n\n    /**\n     * Adds a row with standard color type labels.\n     * <p>\n     * This is a convenience method and is strictly equivalent to calling\n     * <code>{@link #addLabelRow(ProportionalGridPanel,boolean) addLabelRow}(pane, true)</code>.\n     *\n     * @param panel panel in which to add the label row.\n     */\n    void addLabelRow(ProportionalGridPanel panel) {addLabelRow(panel, true);}\n\n    /**\n     * Adds a row with standard color type labels.\n     * <p>\n     * The labels that will be created are:\n     * <pre>\n     *    &lt;EMPTY&gt; | Text | Background | (Preview)\n     * </pre>\n     *\n     * @param panel          panel in which to add the label row.\n     * @param includePreview whether to add the <code>preview</code> label.\n     */\n    void addLabelRow(ProportionalGridPanel panel, boolean includePreview) {\n        // Skips first column.\n        panel.add(new JLabel());\n\n        // Creates the standard labels.\n        panel.add(createCaptionLabel(\"theme_editor.text\"));\n        panel.add(createCaptionLabel(\"theme_editor.background\"));\n\n        // Adds the preview label if requested.\n        if (includePreview) {\n            panel.add(createCaptionLabel(\"preview\"));\n        }\n    }\n\n\n\n    // - Font chooser code ---------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    /**\n     * Creates a font chooser that will keep the specified font up-to-date in the current theme data.\n     * @param fontId identifier of the font this chooser will be editing.\n     */\n    FontChooser createFontChooser(int fontId) {\n        // Initializes the font chooser.\n        FontChooser fontChooser = new FontChooser(themeData.getFont(fontId));\n        fontChooser.setBorder(BorderFactory.createTitledBorder(Translator.get(\"theme_editor.font\")));\n        ChangeListener listener = new ThemeFontChooserListener(themeData, fontId, parent);\n        fontChooser.addChangeListener(listener);\n\n        // Hold a reference to this listener to prevent garbage collection\n        listenerReferences.add(listener);\n\n        return fontChooser;\n    }\n\n    /**\n     * Registers a listener on the specified font chooser.\n     * <p>\n     * The specified listener will receive calls to its <code>setFont</code> method whenever\n     * the font chooser has been updated.\n     *\n     * @param fontChooser      chooser to monitor.\n     * @param previewComponent component whose font should be tied to that of the chooser\n     */\n    void addFontChooserListener(FontChooser fontChooser, JComponent previewComponent) {\n        // Update button font when a new font has been chosen in the FontChooser\n        if (fontChooser != null) {\n            ChangeListener listener;\n            fontChooser.addChangeListener(listener = new PreviewFontChooserListener(previewComponent));\n            previewComponent.setFont(fontChooser.getCurrentFont());\n\n            // Hold a reference to this listener to prevent garbage collection\n            listenerReferences.add(listener);\n        }\n    }\n\n\n\n    // - Scroll pane methods -------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    /**\n     * Wraps the specified panel within a scroll pane.\n     * <p>\n     * The resulting scroll pane will have a vertical bar as needed, no horizontal scroll bar policy.\n     *\n     * @param panel panel to wrap in a <code>JScrollPane</code>.\n     */\n    JComponent createScrollPane(JPanel panel) {\n        JScrollPane scrollPane = new JScrollPane(panel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);\n        scrollPane.setBorder(null);\n\n        return scrollPane;\n    }\n\n\n    // - Color buttons methods -----------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    /**\n     * Adds color buttons to the specified panel.\n     * <p>\n     * This is a convenience method and is strictly equivalent to calling\n     * <code>addColorButtons(gridPanel, fontChooser, label, foregroundId, backgroundId, null)</code>.\n     *\n     * @param gridPanel    a 3 columns proportinal grid panel in which to add the buttons.\n     * @param fontChooser  used to decide which font to use in each color button's preview.\n     * @param label        label for the row.\n     * @param foregroundId identifier of the color to display in the foreground button.\n     * @param backgroundId identifier of the color to display in the background button.\n     */\n    PreviewLabel addColorButtons(ProportionalGridPanel gridPanel, FontChooser fontChooser, String label, int foregroundId, int backgroundId) {\n        return addColorButtons(gridPanel, fontChooser, label, foregroundId, backgroundId, null);\n    }\n\n    /**\n     * Adds color buttons to the specified panel.\n     * <p>\n     * This method will create a row containing the following items:\n     * <pre>\n     * LABEL | COLOR (foreground) | COLOR (background)\n     * </pre>\n     *\n     * @param gridPanel    a 3 columns proportional grid panel in which to add the buttons.\n     * @param fontChooser  used to decide which font to use in each color button's preview.\n     * @param label        label for the row.\n     * @param foregroundId identifier of the color to display in the foreground button.\n     * @param backgroundId identifier of the color to display in the background button.\n     * @param comp         component to register as a listener on the color buttons.\n     */\n    PreviewLabel addColorButtons(ProportionalGridPanel gridPanel, FontChooser fontChooser, String label, int foregroundId,\n                                 int backgroundId, JComponent comp) {\n        // Adds the row's caption label.\n        gridPanel.add(createCaptionLabel(label));\n\n        // Initializes the color buttons' preview label.\n        PreviewLabel previewLabel = new PreviewLabel();\n        previewLabel.setTextPainted(true);\n        addFontChooserListener(fontChooser, previewLabel);\n\n        // Creates the foreground color button.\n        if (foregroundId >= 0 ) {\n            ColorButton colorButton = new ColorButton(parent, themeData, foregroundId, PreviewLabel.FOREGROUND_COLOR_PROPERTY_NAME, previewLabel);\n            gridPanel.add(colorButton);\n            if (comp != null) {\n                colorButton.addUpdatedPreviewComponent(comp);\n            }\n        } else {\n            gridPanel.add(Box.createHorizontalGlue());\n        }\n\n        // Creates the background color button.\n        if (backgroundId >= 0) {\n            ColorButton colorButton = new ColorButton(parent, themeData, backgroundId, PreviewLabel.BACKGROUND_COLOR_PROPERTY_NAME, previewLabel);\n            gridPanel.add(colorButton);\n            if (comp != null) {\n                colorButton.addUpdatedPreviewComponent(comp);\n            }\n        } else {\n            gridPanel.add(Box.createHorizontalGlue());\n        }\n\n        return previewLabel;\n    }\n\n\n\n    // - Ad-hoc FontChooser listeners ----------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    /**\n     * Used to listen on <code>FontChoosers</code> and update theme data when the font is changed.\n     * @author Nicolas Rinaudo\n     */\n    private static class ThemeFontChooserListener implements ChangeListener {\n        /** Theme data in which to update the font when it changes. */\n        private final ThemeData data;\n        /** Identifier of the font we're listening on. */\n        private final int fontId;\n        /** Parent dialog of this panel **/\n        private final PreferencesDialog dialog;\n\n\n        /**\n         * Creates a new <code>ThemeFontChooserListener</code>.\n         * @param data   theme data to modify when change events are received.\n         * @param fontId identifier of the font that is being listened on.\n         */\n        ThemeFontChooserListener(ThemeData data, int fontId, PreferencesDialog dialog) {\n            this.data   = data;\n            this.fontId = fontId;\n            this.dialog = dialog;\n        }\n\n        /**\n         * Updates the theme data with the new font value.\n         */\n        public void stateChanged(ChangeEvent event) {\n        \tdata.setFont(fontId, ((FontChooser)event.getSource()).getCurrentFont());\n        \t// Inform the panel's parent dialog that a component in it was changed.\n        \tdialog.componentChanged(null);\n    \t}\n    }\n\n    /**\n     * Used to listen on <code>FontChoosers</code> and update preview components when the font is changed.\n     * @author Nicolas Rinaudo\n     */\n    private static class PreviewFontChooserListener implements ChangeListener {\n        /** Component to update when the font has changed. */\n        private final JComponent preview;\n\n\n        /**\n         * Creates a new instance of <code>PreviewFontChooserListener</code>.\n         * @param preview component to update when the font has changed.\n         */\n        PreviewFontChooserListener(JComponent preview) {\n            this.preview = preview;\n        }\n\n\n        /**\n         * Updates the preview component.\n         */\n        public void stateChanged(ChangeEvent event) {\n            preview.setFont(((FontChooser)event.getSource()).getCurrentFont());\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/pref/theme/package.html",
    "content": "<body>\n  UI component used to let users modify a theme.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/server/FTPPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.dialog.server;\n\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileProtocols;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.impl.ftp.FTPFile;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.DialogOwner;\nimport com.mucommander.ui.encoding.EncodingListener;\nimport com.mucommander.ui.encoding.EncodingSelectBox;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.net.MalformedURLException;\nimport java.text.ParseException;\n\n\n/**\n * This ServerPanel helps initiate FTP connections.\n *\n * @author Maxence Bernard\n */\npublic class FTPPanel extends ServerPanel implements ActionListener, EncodingListener {\n\n    private final static int STANDARD_PORT = FileURL.getRegisteredHandler(FileProtocols.FTP).getStandardPort();\n    private static final Credentials ANONYMOUS_CREDENTIALS = FileURL.getRegisteredHandler(FileProtocols.FTP).getGuestCredentials();\n\n    private final JTextField serverField;\n    private final JTextField usernameField;\n    private final JPasswordField passwordField;\n    private final JTextField initialDirField;\n    private final JSpinner portSpinner;\n    private final JSpinner nbRetriesSpinner;\n    private final JSpinner retryDelaySpinner;\n    private final EncodingSelectBox encodingSelectBox;\n    private final JCheckBox passiveCheckBox;\n    private final JCheckBox anonymousCheckBox;\n\t\n    private static String lastServer = \"\";\n    private static String lastUsername = \"\";\n    private static String lastInitialDir = \"/\";\n    private static int lastPort = STANDARD_PORT;\n    private static String lastEncoding = FTPFile.DEFAULT_ENCODING;\n    // Not static so that it is not remembered (for security reasons)\n    private String lastPassword = \"\";\n\n    /** Passive mode is enabled by default because of firewall restrictions */\n    private static boolean passiveMode = true;\n    private static boolean anonymousUser;\n\n\n    FTPPanel(final ServerConnectDialog dialog, MainFrame mainFrame) {\n        super(dialog, mainFrame);\n\n        // Server field, initialized to last server entered\n        serverField = new JTextField(lastServer);\n        serverField.selectAll();\n        addTextFieldListeners(serverField, true);\n        addRow(Translator.get(\"server_connect_dialog.server\"), serverField, 15);\n\n        // Username field, initialized to last username entered or 'anonymous' if anonymous user was previously selected\n        usernameField = new JTextField(anonymousUser?ANONYMOUS_CREDENTIALS.getLogin():lastUsername);\n        usernameField.selectAll();\n        usernameField.setEditable(!anonymousUser);\n        addTextFieldListeners(usernameField, false);\n        addRow(Translator.get(\"server_connect_dialog.username\"), usernameField, 5);\n\n        // Password field, initialized to \"\"\n        passwordField = new JPasswordField();\n        addTextFieldListeners(passwordField, false);\n        addRow(Translator.get(\"password\"), passwordField, 15);\n\n        // Initial directory field, initialized to \"/\"\n        initialDirField = new JTextField(lastInitialDir);\n        initialDirField.selectAll();\n        addTextFieldListeners(initialDirField, true);\n        addRow(Translator.get(\"server_connect_dialog.initial_dir\"), initialDirField, 5);\n\t\n        // Port field, initialized to last port (default is 21)\n        portSpinner = createPortSpinner(lastPort);\n        addRow(Translator.get(\"server_connect_dialog.port\"), portSpinner, 15);\n\n        // Encoding combo box\n        encodingSelectBox = new EncodingSelectBox(new DialogOwner(mainFrame.getJFrame()), lastEncoding);\n        encodingSelectBox.addEncodingListener(this);\n        addRow(Translator.get(\"encoding\"), encodingSelectBox, 15);\n\n        // Connection retries when server busy\n        nbRetriesSpinner = createIntSpinner(FTPFile.DEFAULT_NB_CONNECTION_RETRIES, 0, Integer.MAX_VALUE, 1);\n        addRow(Translator.get(\"ftp_connect.nb_connection_retries\"), nbRetriesSpinner, 5);\n\n        // Delay between two retries\n        retryDelaySpinner = createIntSpinner(FTPFile.DEFAULT_CONNECTION_RETRY_DELAY, 0, Integer.MAX_VALUE, 1);\n        addRow(Translator.get(\"ftp_connect.retry_delay\"), retryDelaySpinner, 15);\n\n        // Anonymous user checkbox\n        anonymousCheckBox = new JCheckBox(Translator.get(\"ftp_connect.anonymous_user\"), anonymousUser);\n        anonymousCheckBox.addActionListener(this);\n        addRow(\"\", anonymousCheckBox, 5);\n\n        // Passive mode checkbox\n        passiveCheckBox = new JCheckBox(Translator.get(\"ftp_connect.passive_mode\"), passiveMode);\n        passiveCheckBox.addActionListener(this);\n        addRow(\"\", passiveCheckBox, 0);\n    }\n\n\t\n    private void updateValues() {\n        lastServer = serverField.getText();\n        if(!anonymousUser) {\n            lastUsername = usernameField.getText();\n            lastPassword = new String(passwordField.getPassword());\n        }\n\n        lastInitialDir = initialDirField.getText();\n        lastPort = (Integer) portSpinner.getValue();\n    }\n\t\n\t\n    ////////////////////////////////\n    // ServerPanel implementation //\n    ////////////////////////////////\n\t\n    @Override\n    FileURL getServerURL() throws MalformedURLException {\n        updateValues();\n        if(!lastInitialDir.startsWith(\"/\"))\n            lastInitialDir = \"/\"+lastInitialDir;\n\t\t\t\n        FileURL url = FileURL.getFileURL(FileProtocols.FTP+\"://\"+lastServer+lastInitialDir);\n\n        if(anonymousUser)\n            url.setCredentials(new Credentials(ANONYMOUS_CREDENTIALS.getLogin(), new String(passwordField.getPassword())));\n        else\n            url.setCredentials(new Credentials(lastUsername, lastPassword));\n\n        // Set port\n        url.setPort(lastPort);\n\n        // Set passiveMode property to true (default) or false\n        url.setProperty(FTPFile.PASSIVE_MODE_PROPERTY_NAME, \"\"+passiveMode);\n\n        // Set FTP encoding property\n        url.setProperty(FTPFile.ENCODING_PROPERTY_NAME, encodingSelectBox.getSelectedEncoding());\n\n        // Set connection retry properties\n        url.setProperty(FTPFile.NB_CONNECTION_RETRIES_PROPERTY_NAME, \"\"+nbRetriesSpinner.getValue());\n        url.setProperty(FTPFile.CONNECTION_RETRY_DELAY_PROPERTY_NAME, \"\"+retryDelaySpinner.getValue());\n\n        return url;\n    }\n\n    @Override\n    boolean usesCredentials() {\n        return true;\n    }\n\n    @Override\n    public void dialogValidated() {\n        // Commits the current spinner value in case it was being edited and 'enter' was pressed\n        // (the spinner value would otherwise not be committed)\n        try {\n            portSpinner.commitEdit();\n        } catch(ParseException ignored) { }\n\n        updateValues();\n    }\n\n\n    ////////////////////////////\n    // ActionListener methods //\n    ////////////////////////////\n\t\n    public void actionPerformed(ActionEvent e) {\n        Object source = e.getSource();\n\t\t\n        if (source == passiveCheckBox) {\n            passiveMode = passiveCheckBox.isSelected();\n        } else if (source == anonymousCheckBox) {\n            updateValues();\n            anonymousUser = anonymousCheckBox.isSelected();\n            if (anonymousUser) {\n                usernameField.setEnabled(false);\n                usernameField.setText(ANONYMOUS_CREDENTIALS.getLogin());\n                passwordField.setEnabled(false);\n                passwordField.setText(ANONYMOUS_CREDENTIALS.getPassword());\n            } else {\n                usernameField.setEnabled(true);\n                usernameField.setText(lastUsername);\n                passwordField.setEnabled(true);\n                passwordField.setText(lastPassword);\n            }\n        }\n    }\n\n\n    public void encodingChanged(Object source, String oldEncoding, String newEncoding) {\n        lastEncoding = newEncoding;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/server/HDFSPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.dialog.server;\n\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileProtocols;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.impl.hadoop.HDFSFile;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.*;\nimport java.net.MalformedURLException;\nimport java.text.ParseException;\n\n\n/**\n * This ServerPanel helps initiate HDFS connections.\n *\n * @author Maxence Bernard\n */\npublic class HDFSPanel extends ServerPanel {\n\n    private final JTextField serverField;\n    private final JTextField usernameField;\n//    private JTextField groupField;\n    private final JTextField initialDirField;\n    private final JSpinner portSpinner;\n\n    private static String lastServer = \"\";\n    private static String lastUsername;\n//    private static String lastGroup = HDFSFile.getDefaultGroup();\n    private static String lastInitialDir = \"/\";\n    private static int lastPort = FileURL.getRegisteredHandler(FileProtocols.HDFS).getStandardPort();\n\n    static {\n        try {\n            lastUsername = HDFSFile.getDefaultUsername();\n        } catch (Throwable t) {\n            t.printStackTrace();\n        }\n    }\n\n    HDFSPanel(ServerConnectDialog dialog, final MainFrame mainFrame) {\n        super(dialog, mainFrame);\n\n        // Server field, initialized to last server entered\n        serverField = new JTextField(lastServer);\n        serverField.selectAll();\n        addTextFieldListeners(serverField, true);\n        addRow(Translator.get(\"server_connect_dialog.server\"), serverField, 15);\n\n        // Username field, initialized to last username\n        usernameField = new JTextField(lastUsername);\n        usernameField.selectAll();\n        addTextFieldListeners(usernameField, false);\n        addRow(Translator.get(\"server_connect_dialog.username\"), usernameField, 15);\n\n//        // Password field, initialized to \"\"\n//        groupField = new JTextField(lastGroup);\n//        groupField.selectAll();\n//        addTextFieldListeners(groupField, false);\n//        addRow(Translator.get(\"server_connect_dialog.group\"), groupField, 15);\n\n        // Initial directory field, initialized to \"/\"\n        initialDirField = new JTextField(lastInitialDir);\n        initialDirField.selectAll();\n        addTextFieldListeners(initialDirField, true);\n        addRow(Translator.get(\"server_connect_dialog.initial_dir\"), initialDirField, 5);\n\n        // Port field, initialized to last port\n        portSpinner = createPortSpinner(lastPort);\n        addRow(Translator.get(\"server_connect_dialog.port\"), portSpinner, 15);\n    }\n\n\n    private void updateValues() {\n        lastServer = serverField.getText();\n        lastUsername = usernameField.getText();\n//        lastGroup = groupField.getText();\n        lastInitialDir = initialDirField.getText();\n        lastPort = (Integer) portSpinner.getValue();\n    }\n\n\n    ////////////////////////////////\n    // ServerPanel implementation //\n    ////////////////////////////////\n\n    @Override\n    FileURL getServerURL() throws MalformedURLException {\n        updateValues();\n        if(!lastInitialDir.startsWith(\"/\"))\n            lastInitialDir = \"/\"+lastInitialDir;\n\n        FileURL url = FileURL.getFileURL(FileProtocols.HDFS+\"://\"+lastServer+lastInitialDir);\n\n        // Set user and group\n        url.setCredentials(new Credentials(lastUsername, \"\"));\n//        url.setProperty(HDFSFile.GROUP_PROPERTY_NAME, lastGroup);\n\n        // Set port\n        url.setPort(lastPort);\n\n        return url;\n    }\n\n    @Override\n    boolean usesCredentials() {\n        return true;\n    }\n\n    @Override\n    public void dialogValidated() {\n        // Commits the current spinner value in case it was being edited and 'enter' was pressed\n        // (the spinner value would otherwise not be committed)\n        try {\n            portSpinner.commitEdit();\n        } catch(ParseException ignored) { }\n\n        updateValues();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/server/HTTPPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.dialog.server;\n\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileProtocols;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.*;\nimport java.net.MalformedURLException;\n\n\n/**\n * This ServerPanel helps initiate HTTP connections. \n *\n * @author Maxence Bernard\n */\npublic class HTTPPanel extends ServerPanel {\n\n    private final JTextField urlField;\n    private final JTextField usernameField;\n    private final JPasswordField passwordField;\n\n    private static String lastURL = \"http://\";\n    private static String lastUsername = \"\";\n    // Not static so that it is not saved (for security reasons)\n    private String lastPassword = \"\";\n\n\t\n    HTTPPanel(ServerConnectDialog dialog, MainFrame mainFrame) {\n        super(dialog, mainFrame);\n\n        // Webserver (URL) field\n        urlField = new JTextField(lastURL);\n        urlField.selectAll();\n        addTextFieldListeners(urlField, true);\n        addRow(Translator.get(\"server_connect_dialog.http_url\"), urlField, 20);\n\n         // HTTP Basic authentication fields\n        addRow(new JLabel(Translator.get(\"http_connect.basic_authentication\")), 10);\n\n        // Username field\n        usernameField = new JTextField(lastUsername);\n        usernameField.selectAll();\n        addTextFieldListeners(usernameField, false);\n        addRow(Translator.get(\"server_connect_dialog.username\"), usernameField, 5);\n\n        // Password field\n        passwordField = new JPasswordField(lastPassword);\n        addTextFieldListeners(passwordField, false);\n        addRow(Translator.get(\"password\"), passwordField, 0);\n    }\n\n\n    private void updateValues() {\n        lastURL = urlField.getText();\n        lastUsername = usernameField.getText();\n        lastPassword = new String(passwordField.getPassword());\n    }\n\t\n    @Override\n    FileURL getServerURL() throws MalformedURLException {\n        updateValues();\n        \n        if (!(lastURL.toLowerCase().startsWith(FileProtocols.HTTP+\"://\") || lastURL.toLowerCase().startsWith(FileProtocols.HTTPS+\"://\"))) {\n            lastURL = FileProtocols.HTTP + \"://\" + lastURL;\n        }\n\n        FileURL fileURL = FileURL.getFileURL(lastURL);\n        fileURL.setCredentials(new Credentials(lastUsername, lastPassword));\n        return fileURL;\n    }\n\t\n    @Override\n    boolean usesCredentials() {\n        return true;\n    }\n\n    @Override\n    public void dialogValidated() {\n        updateValues();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/server/NFSPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.dialog.server;\n\nimport com.mucommander.commons.file.FileProtocols;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.impl.nfs.NFSFile;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.*;\nimport java.net.MalformedURLException;\nimport java.text.ParseException;\n\n\n/**\n * This ServerPanel helps initiate NFS connections.\n *\n * @author Maxence Bernard\n */\npublic class NFSPanel extends ServerPanel {\n\n    private final static int STANDARD_PORT = FileURL.getRegisteredHandler(FileProtocols.NFS).getStandardPort(); \n\n    private final JTextField serverField;\n    private final JTextField shareField;\n    private final JSpinner portSpinner;\n    private final JComboBox<String> nfsVersionComboBox;\n    private final JComboBox<String> nfsProtocolComboBox;\n\n    private static String lastServer = \"\";\n    private static String lastShare = \"\";\n    private static int lastPort = STANDARD_PORT;\n    private static String lastNfsVersion = NFSFile.DEFAULT_NFS_VERSION;\n    private static String lastNfsProtocol = NFSFile.DEFAULT_NFS_PROTOCOL;\n\n    NFSPanel(ServerConnectDialog dialog, MainFrame mainFrame) {\n        super(dialog, mainFrame);\n\n        // Server field, initialized to last value\n        serverField = new JTextField(lastServer);\n        serverField.selectAll();\n        addTextFieldListeners(serverField, true);\n        addRow(Translator.get(\"server_connect_dialog.server\"), serverField, 5);\n\n        // NFS share, initialized to \"\"\n        shareField = new JTextField(lastShare);\n        shareField.selectAll();\n        addTextFieldListeners(shareField, true);\n        addRow(Translator.get(\"server_connect_dialog.share\"), shareField, 15);\n\n        // Port field, initialized to last value (default is 2049)\n        portSpinner = createPortSpinner(lastPort);\n        addRow(Translator.get(\"server_connect_dialog.port\"), portSpinner, 15);\n\n        // NFS version, initialized to last value (default is NFSFile's default)\n        nfsVersionComboBox = new JComboBox<>();\n        nfsVersionComboBox.addItem(NFSFile.NFS_VERSION_2);\n        nfsVersionComboBox.addItem(NFSFile.NFS_VERSION_3);\n        nfsVersionComboBox.setSelectedItem(lastNfsVersion);\n        addRow(Translator.get(\"server_connect_dialog.nfs_version\"), nfsVersionComboBox, 5);\n\n        // NFS protocol, initialized to last value (default is NFSFile's default)\n        nfsProtocolComboBox = new JComboBox<>();\n        nfsProtocolComboBox.addItem(NFSFile.NFS_PROTOCOL_AUTO);\n        nfsProtocolComboBox.addItem(NFSFile.NFS_PROTOCOL_TCP);\n        nfsProtocolComboBox.addItem(NFSFile.NFS_PROTOCOL_UDP);\n        nfsProtocolComboBox.setSelectedItem(lastNfsProtocol);\n        addRow(Translator.get(\"server_connect_dialog.protocol\"), nfsProtocolComboBox, 15);\n    }\n\n\n    private void updateValues() {\n        lastServer = serverField.getText();\n        lastShare = shareField.getText();\n\n        lastPort = (Integer) portSpinner.getValue();\n\n        lastNfsVersion = (String)nfsVersionComboBox.getSelectedItem();\n        lastNfsProtocol = (String)nfsProtocolComboBox.getSelectedItem();\n    }\n\n\n    ////////////////////////////////\n    // ServerPanel implementation //\n    ////////////////////////////////\n\n    @Override\n    FileURL getServerURL() throws MalformedURLException {\n        updateValues();\n\n        FileURL url = FileURL.getFileURL(FileProtocols.NFS+\"://\"+lastServer+(lastShare.startsWith(\"/\")?\"\":\"/\")+lastShare);\n\n        // Set port\n        url.setPort(lastPort);\n        // Set NFS version\n        url.setProperty(NFSFile.NFS_VERSION_PROPERTY_NAME, lastNfsVersion);\n        // Set NFS protocol\n        url.setProperty(NFSFile.NFS_PROTOCOL_PROPERTY_NAME, lastNfsProtocol);\n\n        return url;\n    }\n\n    @Override\n    boolean usesCredentials() {\n        return false;\n    }\n\n    @Override\n    public void dialogValidated() {\n        // Commits the current spinner value in case it was being edited and 'enter' was pressed\n        // (the spinner value would otherwise not be committed)\n        try {\n            portSpinner.commitEdit();\n        } catch(ParseException ignored) { }\n\n        updateValues();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/server/S3Panel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.dialog.server;\n\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileProtocols;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.*;\nimport java.net.MalformedURLException;\nimport java.text.ParseException;\n\n\n/**\n * This ServerPanel helps initiate S3 connections.\n *\n * @author Maxence Bernard\n */\npublic class S3Panel extends ServerPanel {\n\n    private final JTextField serverField;\n    private final JTextField usernameField;\n    private final JPasswordField passwordField;\n    private final JTextField initialDirField;\n    private final JSpinner portSpinner;\n\n    private static String lastServer = \"s3.amazonaws.com\";\n    private static String lastUsername = \"\";\n    // Not static so that it is not saved (for security reasons)\n    private String lastPassword = \"\";\n    private static String lastInitialDir = \"/\";\n    private static int lastPort = FileURL.getRegisteredHandler(FileProtocols.S3).getStandardPort();\n\n\n    S3Panel(ServerConnectDialog dialog, final MainFrame mainFrame) {\n        super(dialog, mainFrame);\n\n        // Server field, initialized to last server entered (s3.amazonaws.com by default)\n        serverField = new JTextField(lastServer);\n        serverField.selectAll();\n        addTextFieldListeners(serverField, true);\n        addRow(Translator.get(\"server_connect_dialog.server\"), serverField, 15);\n\n        // Username field, initialized to last username\n        usernameField = new JTextField(lastUsername);\n        usernameField.selectAll();\n        addTextFieldListeners(usernameField, false);\n        // Not localized on purpose\n        addRow(\"Access ID Key\", usernameField, 5);\n\n        // Password field, initialized to \"\"\n        passwordField = new JPasswordField();\n        addTextFieldListeners(passwordField, false);\n        // Not localized on purpose\n        addRow(\"Secret Access Key\", passwordField, 15);\n\n        // Initial directory field, initialized to \"/\"\n        initialDirField = new JTextField(lastInitialDir);\n        initialDirField.selectAll();\n        addTextFieldListeners(initialDirField, true);\n        addRow(Translator.get(\"server_connect_dialog.initial_dir\"), initialDirField, 5);\n\n        // Port field, initialized to last port\n        portSpinner = createPortSpinner(lastPort);\n        addRow(Translator.get(\"server_connect_dialog.port\"), portSpinner, 15);\n    }\n\n\n    private void updateValues() {\n        lastServer = serverField.getText();\n        lastUsername = usernameField.getText();\n        lastPassword = new String(passwordField.getPassword());\n        lastInitialDir = initialDirField.getText();\n        lastPort = (Integer) portSpinner.getValue();\n    }\n\n\n    ////////////////////////////////\n    // ServerPanel implementation //\n    ////////////////////////////////\n\n    @Override\n    FileURL getServerURL() throws MalformedURLException {\n        updateValues();\n        if(!lastInitialDir.startsWith(\"/\"))\n            lastInitialDir = \"/\"+lastInitialDir;\n\n        FileURL url = FileURL.getFileURL(FileProtocols.S3+\"://\"+lastServer+lastInitialDir);\n        url.setCredentials(new Credentials(lastUsername, lastPassword));\n        url.setPort(lastPort);\n\n        return url;\n    }\n\n    @Override\n    boolean usesCredentials() {\n        return true;\n    }\n\n    @Override\n    public void dialogValidated() {\n        // Commits the current spinner value in case it was being edited and 'enter' was pressed\n        // (the spinner value would otherwise not be committed)\n        try {\n            portSpinner.commitEdit();\n        } catch(ParseException ignored) { }\n\n        updateValues();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/server/SFTPPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.dialog.server;\n\nimport java.awt.BorderLayout;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.net.MalformedURLException;\nimport java.nio.file.FileSystems;\nimport java.text.ParseException;\n\nimport javax.swing.JButton;\nimport javax.swing.JFileChooser;\nimport javax.swing.JPanel;\nimport javax.swing.JPasswordField;\nimport javax.swing.JSpinner;\nimport javax.swing.JTextField;\n\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileProtocols;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.impl.sftp.SFTPFile;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.main.MainFrame;\n\n\n/**\n * This ServerPanel helps initiate SFTP connections.\n *\n * @author Maxence Bernard, Vassil Dichev\n */\npublic class SFTPPanel extends ServerPanel {\n\n    private final static int STANDARD_PORT = FileURL.getRegisteredHandler(FileProtocols.SFTP).getStandardPort();\n\n    private final JTextField serverField;\n    private final JTextField privateKeyPathField;\n    private final JTextField usernameField;\n    private final JPasswordField passwordField;\n    private final JTextField initialDirField;\n    private final JSpinner portSpinner;\n\n    private static String lastServer = \"\";\n    private static String lastKeyPath = \"\";\n    private static String lastUsername = \"\";\n    // Not static so that it is not saved (for security reasons)\n    private String lastPassword = \"\";\n    private static String lastInitialDir = \"/\";\n    private static int lastPort = STANDARD_PORT;\n\n\n    SFTPPanel(ServerConnectDialog dialog, final MainFrame mainFrame) {\n        super(dialog, mainFrame);\n\n        // Server field, initialized to last server entered\n        serverField = new JTextField(lastServer);\n        serverField.selectAll();\n        addTextFieldListeners(serverField, true);\n        addRow(Translator.get(\"server_connect_dialog.server\"), serverField, 15);\n\n        // Username field, initialized to last username\n        usernameField = new JTextField(lastUsername);\n        usernameField.selectAll();\n        addTextFieldListeners(usernameField, false);\n        addRow(Translator.get(\"server_connect_dialog.username\"), usernameField, 5);\n\n        // Password field, initialized to \"\"\n        passwordField = new JPasswordField();\n        addTextFieldListeners(passwordField, false);\n        addRow(Translator.get(\"password\")+\"/\"+Translator.get(\"server_connect_dialog.passphrase\"), passwordField, 15);\n\n        // Key file field, initialized to last file\n        JPanel privateKeyChooser = new JPanel(new BorderLayout());\n\n        if (OsFamily.getCurrent().isUnixBased() && (lastKeyPath == null || lastKeyPath.trim().isEmpty())) {\n            lastKeyPath = System.getProperty(\"user.home\") + \"/.ssh/id_rsa\";\n        }\n        privateKeyPathField = new JTextField(lastKeyPath);\n        privateKeyPathField.selectAll();\n        addTextFieldListeners(privateKeyPathField, false);\n        privateKeyChooser.add(privateKeyPathField, BorderLayout.CENTER);\n\n        JButton chooseFileButton = new JButton(\"...\");\n        // Mac OS X: small component size\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            chooseFileButton.putClientProperty(\"JComponent.sizeVariant\", \"small\");\n        }\n\n        chooseFileButton.addActionListener(new ActionListener() {\n                final JFileChooser fc = new JFileChooser(System.getProperty(\"user.home\") + FileSystems.getDefault().getSeparator() + \".ssh\");\n                public void actionPerformed(ActionEvent e) {\n                    int returnVal = fc.showOpenDialog(mainFrame.getJFrame());\n                    if (returnVal == JFileChooser.APPROVE_OPTION) {\n                        privateKeyPathField.setText(fc.getSelectedFile().getAbsolutePath());\n                    }\n                }\n            }\n        );\n        privateKeyChooser.add(chooseFileButton, BorderLayout.EAST);\n\n        addRow(Translator.get(\"server_connect_dialog.private_key\"), privateKeyChooser, 15);\n\n        // Initial directory field, initialized to \"/\"\n        initialDirField = new JTextField(lastInitialDir);\n        initialDirField.selectAll();\n        addTextFieldListeners(initialDirField, true);\n        addRow(Translator.get(\"server_connect_dialog.initial_dir\"), initialDirField, 5);\n\n        // Port field, initialized to last port (default is 22)\n        portSpinner = createPortSpinner(lastPort);\n        addRow(Translator.get(\"server_connect_dialog.port\"), portSpinner, 15);\n    }\n\n\n    private void updateValues() {\n        lastKeyPath = privateKeyPathField.getText().trim();\n        lastServer = serverField.getText().trim();\n        lastUsername = usernameField.getText();\n        lastPassword = new String(passwordField.getPassword());\n        lastInitialDir = initialDirField.getText().trim();\n        lastPort = (Integer) portSpinner.getValue();\n    }\n\n\n    ////////////////////////////////\n    // ServerPanel implementation //\n    ////////////////////////////////\n\n    @Override\n    FileURL getServerURL() throws MalformedURLException {\n        updateValues();\n        if (!lastInitialDir.startsWith(\"/\")) {\n            lastInitialDir = \"/\" + lastInitialDir;\n        }\n\n        FileURL url = FileURL.getFileURL(FileProtocols.SFTP+\"://\"+lastServer+lastInitialDir);\n\n        // Set credentials\n        url.setCredentials(new Credentials(lastUsername, lastPassword));\n        if (!lastKeyPath.isEmpty()) {\n            url.setProperty(SFTPFile.PRIVATE_KEY_PATH_PROPERTY_NAME, lastKeyPath);\n        }\n\n        // Set port\n        url.setPort(lastPort);\n\n        return url;\n    }\n\n    @Override\n    boolean usesCredentials() {\n        return true;\n    }\n\n    @Override\n    public void dialogValidated() {\n        // Commits the current spinner value in case it was being edited and 'enter' was pressed\n        // (the spinner value would otherwise not be committed)\n        try {\n            portSpinner.commitEdit();\n        } catch(ParseException ignore) { }\n\n        updateValues();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/server/SMBPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.dialog.server;\n\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileProtocols;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.*;\nimport java.net.MalformedURLException;\n\n\n/**\n * This ServerPanel helps initiate SMB connections.\n *\n * @author Maxence Bernard\n */\npublic class SMBPanel extends ServerPanel {\n\n    private final JTextField domainField;\n    private final JTextField serverField;\n    private final JTextField shareField;\n    private final JTextField usernameField;\n    private final JPasswordField passwordField;\n\n    private static String lastDomain = \"\";\n    private static String lastServer = \"\";\n    private static String lastShare = \"\";\n    private static String lastUsername = \"\";\n    // Not static so that it is not saved (for security reasons)\n    private String lastPassword = \"\";\n\n\t\n    SMBPanel(ServerConnectDialog dialog, MainFrame mainFrame) {\n        super(dialog, mainFrame);\n\n        // Server field\n        serverField = new JTextField(lastServer);\n        serverField.selectAll();\n        addTextFieldListeners(serverField, true);\n        addRow(Translator.get(\"server_connect_dialog.server\"), serverField, 5);\n\n        // Share field\n        shareField = new JTextField(lastShare);\n        shareField.selectAll();\n        addTextFieldListeners(shareField, true);\n        addRow(Translator.get(\"server_connect_dialog.share\"), shareField, 15);\n\n        // Domain field\n        domainField = new JTextField(lastDomain);\n        domainField.selectAll();\n        addTextFieldListeners(domainField, true);\n        addRow(Translator.get(\"server_connect_dialog.domain\"), domainField, 15);\n\n        // Username field\n        usernameField = new JTextField(lastUsername);\n        usernameField.selectAll();\n        addTextFieldListeners(usernameField, false);\n        addRow(Translator.get(\"server_connect_dialog.username\"), usernameField, 5);\n\n        // Password field\n        passwordField = new JPasswordField();\n        addTextFieldListeners(passwordField, false);\n        addRow(Translator.get(\"password\"), passwordField, 0);\n    }\n\n\t\n    private void updateValues() {\n        lastServer = serverField.getText();\n        lastShare = shareField.getText();\n        lastDomain = domainField.getText();\n        lastUsername = usernameField.getText();\n        lastPassword = new String(passwordField.getPassword());\n    }\n\n\t\n    @Override\n    FileURL getServerURL() throws MalformedURLException {\n        updateValues();\n        FileURL url = FileURL.getFileURL(FileProtocols.SMB+\"://\"+lastServer+(lastShare.startsWith(\"/\")?\"\":\"/\")+lastShare);\n\n        // Insert the domain (if any) before the username, separated by a semicolon\n        String userInfo = lastUsername;\n        if (!lastDomain.isEmpty()) {\n            userInfo = lastDomain+\";\"+userInfo;\n        }\n        url.setCredentials(new Credentials(userInfo, lastPassword));\n\n        return url;\n    }\n\n    @Override\n    boolean usesCredentials() {\n        return true;\n    }\n\n    @Override\n    public void dialogValidated() {\n        updateValues();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/server/ServerConnectDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.dialog.server;\n\nimport com.mucommander.auth.CredentialsMapping;\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.FileProtocols;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.ConnectToServerAction;\nimport com.mucommander.ui.dialog.DialogToolkit;\nimport com.mucommander.ui.dialog.FocusDialog;\nimport com.mucommander.ui.dialog.InformationDialog;\nimport com.mucommander.ui.helper.FocusRequester;\nimport com.mucommander.ui.layout.XBoxPanel;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.*;\nimport javax.swing.event.ChangeEvent;\nimport javax.swing.event.ChangeListener;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.util.ArrayList;\nimport java.util.List;\n\n\n/**\n * Dialog that assists the user in connecting to a filesystem. It contains tabs and associated panels for each of the\n * supported protocols.\n *\n * @author Maxence Bernard\n */\npublic class ServerConnectDialog extends FocusDialog implements ActionListener, ChangeListener {\n\n    private final FolderPanel folderPanel;\n\t\n    private final JButton btnCancel;\n    private ServerPanel pnlCurrentServer;\n\n    private final JTabbedPane tabbedPane;\n    private final List<ServerPanel> serverPanels = new ArrayList<>();\n\n    private final JLabel lblUrl;\n    private final JCheckBox cbSaveCredentials;\n\n    // Dialog's width has to be at least 320\n    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(520, 0);\n\t\n    private static Class<? extends ServerPanel> lastPanelClass = FTPPanel.class;\n\n\n    /**\n     * Creates a new <code>ServerConnectDialog</code> that changes the current folder on the specified {@link FolderPanel}.\n     *\n     * @param folderPanel the panel on which to change the current folder\n     */\n    public ServerConnectDialog(FolderPanel folderPanel) {\n        this(folderPanel, lastPanelClass);\n    }\n\t\n\t\t\n    /**\n     * Creates a new <code>ServerConnectDialog</code> that changes the current folder on the specified {@link FolderPanel}.\n     * The specified panel is selected when the dialog appears.\n     *\n     * @param folderPanel the panel on which to change the current folder\n     * @param selectPanelClass class of the ServerPanel to select\n     */\n    public ServerConnectDialog(FolderPanel folderPanel, Class<? extends ServerPanel> selectPanelClass) {\n        super(folderPanel.getMainFrame().getJFrame(), ActionProperties.getActionLabel(ConnectToServerAction.Descriptor.ACTION_ID), folderPanel.getMainFrame().getJFrame());\n        this.folderPanel = folderPanel;\n        lastPanelClass = selectPanelClass;\n\n        MainFrame mainFrame = folderPanel.getMainFrame();\n        Container contentPane = getContentPane();\n\t\t\n        this.tabbedPane = new JTabbedPane(JTabbedPane.TOP);\n\n        addTab(FileProtocols.FTP, new FTPPanel(this, mainFrame), selectPanelClass);\n        addTab(FileProtocols.HDFS, new HDFSPanel(this, mainFrame), selectPanelClass);\n        addTab(FileProtocols.HTTP, new HTTPPanel(this, mainFrame), selectPanelClass);\n        addTab(FileProtocols.NFS, new NFSPanel(this, mainFrame), selectPanelClass);\n        addTab(FileProtocols.S3, new S3Panel(this, mainFrame), selectPanelClass);\n        addTab(FileProtocols.SFTP, new SFTPPanel(this, mainFrame), selectPanelClass);\n        addTab(FileProtocols.SMB, new SMBPanel(this, mainFrame), selectPanelClass);\n        addTab(FileProtocols.WEBDAV, new WebDAVPanel(this, mainFrame), selectPanelClass);\n        addTab(FileProtocols.VSPHERE, new VSpherePanel(this, mainFrame), selectPanelClass);\n\n        pnlCurrentServer = getCurrentServerPanel();\n\n        // Listen to tab change events\n        tabbedPane.addChangeListener(this);\n        contentPane.add(tabbedPane, BorderLayout.CENTER);\n\t\t\n        YBoxPanel yPanel = new YBoxPanel();\n        XBoxPanel xPanel = new XBoxPanel();\n        xPanel.add(new JLabel(i18n(\"server_connect_dialog.server_url\")+\":\"));\n        xPanel.addSpace(5);\n        lblUrl = new JLabel(\"\");\n        updateURLLabel();\n        xPanel.add(lblUrl);\n        yPanel.add(xPanel);\n\n        yPanel.addSpace(10);\n\n        this.cbSaveCredentials = new JCheckBox(i18n(\"auth_dialog.store_credentials\"), false);\n        // Enables 'save credentials' checkbox only if server panel/protocol uses credentials\n        cbSaveCredentials.setEnabled(pnlCurrentServer.usesCredentials());\n        yPanel.add(cbSaveCredentials);\n\n        JButton okButton = new JButton(i18n(\"server_connect_dialog.connect\"));\n        btnCancel = new JButton(i18n(\"cancel\"));\n        yPanel.add(DialogToolkit.createOKCancelPanel(okButton, btnCancel, getRootPane(), this));\n\n        contentPane.add(yPanel, BorderLayout.SOUTH);\n\t\t\n        // initial focus\n        setInitialFocusComponent(pnlCurrentServer);\n\t\t\n        setMinimumSize(MINIMUM_DIALOG_DIMENSION);\n    }\n\n\n    public void addTab(String protocol, ServerPanel serverPanel, Class<? extends ServerPanel> selectPanelClass) {\n        if (!FileFactory.isRegisteredProtocol(protocol)) {\n            return;\n        }\n\n        JPanel northPanel = new JPanel(new BorderLayout());\n        northPanel.add(serverPanel, BorderLayout.NORTH);\n        tabbedPane.addTab(protocol.toUpperCase(), northPanel);\n\n        if (selectPanelClass.equals(serverPanel.getClass())) {\n            tabbedPane.setSelectedComponent(northPanel);\n        }\n        serverPanels.add(serverPanel);\n    }\n\n\n    void updateURLLabel() {\n        try {\n            FileURL url = pnlCurrentServer.getServerURL();\n            lblUrl.setText(url == null ? \" \" : url.toString(false));\n        }\n        catch(MalformedURLException ex) {\n            lblUrl.setText(\" \");\n        }\n    }\n\n    private ServerPanel getCurrentServerPanel() {\n        return serverPanels.get(tabbedPane.getSelectedIndex());\n    }\n\n\t\n    public void actionPerformed(ActionEvent e) {\n        Object source = e.getSource();\n\n        if (source == btnCancel) {\n            dispose();\n            return;\n        }\n\n        try {\n            pnlCurrentServer.dialogValidated();\n\n            FileURL serverURL = pnlCurrentServer.getServerURL();\t// Can throw a MalformedURLException\n\n            // create a CredentialsMapping instance and pass to Folder so that it uses it to connect to the folder and\n            // adds to CredentialsManager once the folder has been successfully changed\n            Credentials credentials = serverURL.getCredentials();\n            CredentialsMapping credentialsMapping;\n            if (credentials != null) {\n                credentialsMapping = new CredentialsMapping(credentials, serverURL, cbSaveCredentials.isSelected());\n            } else {\n                credentialsMapping = null;\n            }\n\n            dispose();\n\n            // Change the current folder\n            folderPanel.tryChangeCurrentFolder(serverURL, credentialsMapping);\n        } catch(IOException ex) {\n            InformationDialog.showErrorDialog(this, i18n(\"table.folder_access_error_title\"), i18n(\"folder_does_not_exist\"));\n        }\n    }\n\t\n\n    public void stateChanged(ChangeEvent e) {\n        pnlCurrentServer = getCurrentServerPanel();\n        lastPanelClass = pnlCurrentServer.getClass();\n\n        // Enables 'save credentials' checkbox only if server panel/protocol uses credentials\n        cbSaveCredentials.setEnabled(pnlCurrentServer.usesCredentials());\n\n        updateURLLabel();\n        FocusRequester.requestFocus(pnlCurrentServer);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/server/ServerPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.dialog.server;\n\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.ui.layout.XAlignedComponentPanel;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.*;\nimport javax.swing.event.DocumentEvent;\nimport javax.swing.event.DocumentListener;\nimport java.awt.*;\nimport java.net.MalformedURLException;\n\n\n/**\n * This abstract class represents a panel that helps the user initiate a connection to a servers using a certain file\n * protocol. This class is agnostic with respect to the file protocol used -- subclasses implement a specific file \n * protocol.\n *\n * @author Maxence Bernard\n */\npublic abstract class ServerPanel extends XAlignedComponentPanel {\n\n    protected ServerConnectDialog dialog;\n    protected MainFrame mainFrame;\n\t\n\t\n    protected ServerPanel(ServerConnectDialog dialog, MainFrame mainFrame) {\n        // Add a 10-pixel gap label and text component\n        super(10);\n\t\t\n        this.dialog = dialog;\n        this.mainFrame = mainFrame;\n    }\n\t\n\t\n    @Override\n    public Insets getInsets() {\n        return new Insets(8, 6, 8, 6);\n    }\n\n    protected JSpinner createPortSpinner(int portValue) {\n        return createIntSpinner(portValue, 1, 65535, 1);\n    }\n\n    protected JSpinner createIntSpinner(int value, int minValue, int maxValue, int step) {\n        JSpinner spinner = new JSpinner(new SpinnerNumberModel(value, minValue, maxValue, step));\n\n        // Left-aligns the text within the text field, and use a simple decimal format with no thousand separator\n        JSpinner.NumberEditor editor = new JSpinner.NumberEditor(spinner, \"#####\");\n        editor.getTextField().setHorizontalAlignment(JTextField.LEADING);\n        spinner.setEditor(editor);\n\n        // Any changes made to the spinner will update the URL label\n        spinner.addChangeListener(e -> dialog.updateURLLabel());\n\n        return spinner;\n    }\n\t\n    protected void addTextFieldListeners(JTextField textField, boolean updateLabel) {\n        textField.addActionListener(dialog);\n        if (updateLabel) {\n            textField.getDocument().addDocumentListener(new DocumentListener() {\n                public void changedUpdate(DocumentEvent e) {\n                    dialog.updateURLLabel();\n                }\n\n                public void insertUpdate(DocumentEvent e) {\n                    dialog.updateURLLabel();\n                }\n\n                public void removeUpdate(DocumentEvent e) {\n                    dialog.updateURLLabel();\n                }\n            });\n        }\n    }\n\n\n    /** \n     * Returns the current server URL represented by this panel, <code>null</code> if it is not available.\n     * This method may be called at any time by {@link ServerConnectDialog}.\n     *\n     * @return the current server URL represented by this panel, <code>null</code> if it is not available\n     * @throws MalformedURLException if an exception was thrown while creating the FileURL instance\n     */\n    abstract FileURL getServerURL() throws MalformedURLException;\n\n    /**\n     * Returns <code>true</code> if this panel allows the user to specify credentials for the file protocol.\n     *\n     * @return <code>true</code> if this panel allows the user to specify credentials for the file protocol\n     */\n    abstract boolean usesCredentials();\n\n    /**\n     * This method is called by {@link ServerConnectDialog} when the dialog has been validated by the user\n     * ('OK' button or 'Enter' key pressed). This is where component values should be saved for when a\n     * new instance of the panel is created.\n     */\n    abstract void dialogValidated();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/server/ShowServerConnectionsDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.server;\n\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.connection.ConnectionHandler;\nimport com.mucommander.commons.file.connection.ConnectionPool;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.ShowServerConnectionsAction;\nimport com.mucommander.ui.dialog.FocusDialog;\nimport com.mucommander.ui.helper.MnemonicHelper;\nimport com.mucommander.ui.layout.XBoxPanel;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.*;\nimport java.awt.BorderLayout;\nimport java.awt.Container;\nimport java.awt.Dimension;\nimport java.awt.FlowLayout;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.util.List;\n\n/**\n * This dialog shows a list of server connections and allows the user to close/disconnect them.\n *\n * @author Maxence Bernard\n */\npublic class ShowServerConnectionsDialog extends FocusDialog implements ActionListener {\n\n    private final MainFrame mainFrame;\n\n    private final JList<String> connectionList;\n    private final List<ConnectionHandler> connections;\n\n    private final JButton btnDisconnect;\n    private final JButton btnGoto;\n    private final JButton btnClose;\n\n    // Dialog's size has to be at least 400x300\n    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(400,300);\n\n    // Dialog's size has to be at most 600x400\n    private final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(600,400);\n\n    \n    public ShowServerConnectionsDialog(MainFrame mainFrame) {\n        super(mainFrame.getJFrame(), ActionProperties.getActionLabel(ShowServerConnectionsAction.Descriptor.ACTION_ID), mainFrame.getJFrame());\n\n        this.mainFrame = mainFrame;\n\n        Container contentPane = getContentPane();\n\n        // Add the list of server connections\n        connections = ConnectionPool.getConnectionHandlersSnapshot();\n\n        connectionList = new JList<>(new AbstractListModel<>() {\n            public int getSize() {\n                return connections.size();\n            }\n\n            public String getElementAt(int i) {\n                ConnectionHandler connHandler = connections.get(i);\n                // Show login (but not password) in the URL\n                // Note: realm returned by ConnectionHandler does not contain credentials\n                FileURL clonedRealm = (FileURL) connHandler.getRealm().clone();\n                Credentials loginCredentials = new Credentials(connHandler.getCredentials().getLogin(), \"\");\n                clonedRealm.setCredentials(loginCredentials);\n\n                return clonedRealm.toString(true)\n                        + \" (\" + i18n(connHandler.isLocked() ? \"server_connections_dialog.connection_busy\" : \"server_connections_dialog.connection_idle\") + \")\";\n            }\n        });\n\n        // Only one list index can be selected at a time\n        connectionList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);\n\n        // Select the first connection in the list\n        boolean hasConnections = !connections.isEmpty();\n        if (hasConnections) {\n            connectionList.setSelectedIndex(0);\n        }\n\n        contentPane.add(\n                new JScrollPane(connectionList, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED),\n                BorderLayout.CENTER);\n\n        // Add buttons\n        XBoxPanel buttonsPanel = new XBoxPanel();\n        JPanel buttonGroupPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        MnemonicHelper mnemonicHelper = new MnemonicHelper();\n\n        // Disconnect button\n        btnDisconnect = new JButton(i18n(\"server_connections_dialog.disconnect\"));\n        btnDisconnect.setMnemonic(mnemonicHelper.getMnemonic(btnDisconnect));\n        btnDisconnect.setEnabled(hasConnections);\n        if (hasConnections) {\n            btnDisconnect.addActionListener(this);\n        }\n\n        buttonGroupPanel.add(btnDisconnect);\n\n        // Go to button\n        btnGoto = new JButton(i18n(\"go_to\"));\n        btnGoto.setMnemonic(mnemonicHelper.getMnemonic(btnGoto));\n        btnGoto.setEnabled(hasConnections);\n        if (hasConnections) {\n            btnGoto.addActionListener(this);\n        }\n        buttonGroupPanel.add(btnGoto);\n\n        buttonsPanel.add(buttonGroupPanel);\n\n        // Button that closes the window\n        btnClose = new JButton(i18n(\"close\"));\n        btnClose.setMnemonic(mnemonicHelper.getMnemonic(btnClose));\n        btnClose.addActionListener(this);\n\n        buttonsPanel.add(Box.createHorizontalGlue());\n        buttonsPanel.add(btnClose);\n\n        contentPane.add(buttonsPanel, BorderLayout.SOUTH);\n\n        // Connections list will receive initial focus\n        setInitialFocusComponent(connectionList);\n\n        // Selects 'Done' button when enter is pressed\n        getRootPane().setDefaultButton(btnClose);\n\n        // Packs dialog\n        setMinimumSize(MINIMUM_DIALOG_DIMENSION);\n        setMaximumSize(MAXIMUM_DIALOG_DIMENSION);\n\n        setDefaultCloseOperation(DISPOSE_ON_CLOSE);\n\n        showDialog();\n    }\n\n\n    public void actionPerformed(ActionEvent e) {\n        Object source = e.getSource();\n\n        // Disconnects the selected connection\n        if (source == btnDisconnect) {\n            int selectedIndex = connectionList.getSelectedIndex();\n\n            if (selectedIndex >= 0 && selectedIndex < connections.size()) {\n                final ConnectionHandler connHandler = connections.get(selectedIndex);\n\n                // Close connection in a separate thread as I/O can lock.\n                // Todo: Add a confirmation dialog if the connection is active as it will stop whatever the connection is currently doing\n                new Thread(connHandler::closeConnection).start();\n\n                // Remove connection from the list\n                connections.remove(selectedIndex);\n                connectionList.setSelectedIndex(Math.min(selectedIndex, connections.size()));\n                connectionList.repaint();\n\n                // Disable contextual buttons if there are no more connections\n                if (connections.isEmpty()) {\n                    btnDisconnect.setEnabled(false);\n                    btnGoto.setEnabled(false);\n                }\n            }\n        } else if (source == btnGoto)  {\n            // Goes to the selected connection\n            // Dispose the dialog first\n            dispose();\n\n            int selectedIndex = connectionList.getSelectedIndex();\n            if (selectedIndex>=0 && selectedIndex<connections.size()) {\n                mainFrame.getActivePanel().tryChangeCurrentFolder(connections.get(selectedIndex).getRealm());\n            }\n        } else if (source== btnClose)  {\n            dispose();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/server/VSpherePanel.java",
    "content": "package com.mucommander.ui.dialog.server;\r\n\r\nimport java.net.MalformedURLException;\r\n\r\nimport javax.swing.JPasswordField;\r\nimport javax.swing.JTextField;\r\n\r\nimport com.mucommander.commons.file.Credentials;\r\nimport com.mucommander.commons.file.FileProtocols;\r\nimport com.mucommander.commons.file.FileURL;\r\nimport com.mucommander.commons.file.impl.vsphere.VSphereFile;\r\nimport com.mucommander.commons.file.util.PathUtils;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\n/**\r\n * This ServerPanel helps initiate VSpherePanel connections.\r\n * \r\n * @author Yuval Kohavi, yuval@intigua.com\r\n */\r\npublic class VSpherePanel extends ServerPanel {\r\n\r\n\tprivate static final String IP_UUID_INSTANCE_UUID = \"IP\\\\UUID\\\\Instance UUID\";\r\n\r\n\tprivate static final long serialVersionUID = -3850165192515539062L;\r\n\t\r\n\tprivate final JTextField vsphereField;\r\n\tprivate final JTextField usernameField;\r\n\tprivate final JPasswordField passwordField;\r\n\r\n\tprivate final JTextField guestField;\r\n\tprivate final JTextField guestUsernameField;\r\n\tprivate final JPasswordField guestPasswordField;\r\n\r\n\tprivate final JTextField dirField;\r\n\tprivate static String lastVsphere = \"\";\r\n\tprivate static String lastGuest = \"\";\r\n\tprivate static String lastDir = \"\";\r\n\tprivate static String lastUsername = \"\";\r\n\tprivate static String lastGuestUsername = \"\";\r\n\r\n\tVSpherePanel(ServerConnectDialog dialog, MainFrame mainFrame) {\r\n\t\tsuper(dialog, mainFrame);\r\n\r\n\t\t// Server field\r\n\t\tvsphereField = new JTextField(lastVsphere);\r\n\t\tvsphereField.selectAll();\r\n\t\taddTextFieldListeners(vsphereField, true);\r\n\t\taddRow(Translator.get(\"server_connect_dialog.server\"), vsphereField, 5);\r\n\r\n\t\t// Username field\r\n\t\tusernameField = new JTextField(lastUsername);\r\n\t\tusernameField.selectAll();\r\n\t\taddTextFieldListeners(usernameField, false);\r\n\t\taddRow(Translator.get(\"server_connect_dialog.username\"), usernameField, 5);\r\n\r\n\t\t// Password field\r\n\t\tpasswordField = new JPasswordField();\r\n\t\taddTextFieldListeners(passwordField, false);\r\n\t\taddRow(Translator.get(\"password\"), passwordField, 0);\r\n\r\n\t\t// Server field\r\n\t\tguestField = new JTextField(lastGuest);\r\n\t\tguestField.selectAll();\r\n\t\taddTextFieldListeners(guestField, true);\r\n\t\taddRow(Translator.get(\"vsphere_connections_dialog.guest_server\", IP_UUID_INSTANCE_UUID), guestField, 5);\r\n\r\n\t\t// Username field\r\n\t\tguestUsernameField = new JTextField(lastGuestUsername);\r\n\t\tguestUsernameField.selectAll();\r\n\t\taddTextFieldListeners(guestUsernameField, false);\r\n\t\taddRow(Translator.get(\"vsphere_connections_dialog.guest_user\"), guestUsernameField, 5);\r\n\r\n\t\t// Password field\r\n\t\tguestPasswordField = new JPasswordField();\r\n\t\taddTextFieldListeners(guestPasswordField, false);\r\n\t\taddRow(Translator.get(\"vsphere_connections_dialog.guest_password\"), guestPasswordField, 0);\r\n\r\n\t\t// Share field\r\n\t\tdirField = new JTextField(lastDir);\r\n\t\tdirField.selectAll();\r\n\t\taddTextFieldListeners(dirField, true);\r\n\t\taddRow(Translator.get(\"server_connect_dialog.initial_dir\"), dirField,15);\r\n\r\n\t}\r\n\r\n\tprivate void updateValues() {\r\n\t\tlastVsphere = vsphereField.getText();\r\n\t\tlastUsername = usernameField.getText();\r\n\t\tlastGuest = guestField.getText();\r\n\t\tlastGuestUsername = guestUsernameField.getText();\r\n\t\tlastDir = dirField.getText();\r\n\t}\r\n\r\n\r\n\r\n\t@Override\r\n\tFileURL getServerURL() throws MalformedURLException {\r\n\t\tupdateValues();\r\n\t\tFileURL url = FileURL.getFileURL(FileProtocols.VSPHERE + \"://\"\r\n\t\t\t\t+ lastVsphere + \"/\" + lastGuest\r\n\t\t\t\t+ \"/\" + PathUtils.removeLeadingSeparator(lastDir));\r\n\t\turl.setCredentials(new Credentials(lastUsername,  new String(passwordField.getPassword())));\r\n\t\turl.setProperty(VSphereFile.GUEST_CREDENTIALS, lastGuestUsername + \":\" + new String(guestPasswordField.getPassword()));\r\n\r\n\t\treturn url;\r\n\t}\r\n\r\n\t@Override\r\n\tboolean usesCredentials() {\r\n\t\treturn true;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void dialogValidated() {\r\n\t\tupdateValues();\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/server/WebDAVPanel.java",
    "content": "package com.mucommander.ui.dialog.server;\n\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileProtocols;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.main.MainFrame;\nimport java.net.MalformedURLException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport javax.swing.JPasswordField;\nimport javax.swing.JTextField;\n\n/**\n *\n * @author Mathias\n */\npublic class WebDAVPanel extends ServerPanel {\n\n    private final JTextField serverField;\n    private final JTextField usernameField;\n    private final JPasswordField passwordField;\n\n    private static String lastServer = \"\";\n    private static String lastUsername = System.getProperty(\"user.name\");\n    private static String lastPassword = \"\";\n\n    WebDAVPanel(ServerConnectDialog dialog, final MainFrame mainFrame) {\n        super(dialog, mainFrame);\n\n        // Server field, initialized to last server entered\n        serverField = new JTextField(lastServer);\n        serverField.selectAll();\n        addTextFieldListeners(serverField, true);\n        addRow(Translator.get(\"server_connect_dialog.server\"), serverField, 15);\n\n        // Username field, initialized to last username\n        usernameField = new JTextField(lastUsername);\n        usernameField.selectAll();\n        addTextFieldListeners(usernameField, false);\n        addRow(Translator.get(\"server_connect_dialog.username\"), usernameField, 15);\n\n//        // Password field, initialized to \"\"\n        passwordField = new JPasswordField(\"\");\n        \n        passwordField.selectAll();\n        addTextFieldListeners(passwordField, false);\n        addRow(Translator.get(\"password\"), passwordField, 15);\n\n    }\n\n    private void updateValues() {\n        lastServer = serverField.getText();\n        lastUsername = usernameField.getText();\n        lastPassword = new String(passwordField.getPassword());\n    }\n\n    @Override\n    FileURL getServerURL() throws MalformedURLException {\n        updateValues();\n        \n        int port = FileURL.getRegisteredHandler(FileProtocols.WEBDAV).getStandardPort();\n        \n        String url = FileProtocols.WEBDAV + \"://\"+lastUsername+\":\"+lastPassword+\"@\" + lastServer;\n        \n        try {\n            URI uri = new URI(lastServer);\n            if (uri.getScheme() != null){\n                if (uri.getScheme().equalsIgnoreCase(\"http\")){\n                    port = 80;\n                } else if(uri.getScheme().equalsIgnoreCase(\"https\")){\n                    port = 443;\n                }\n                url = FileProtocols.WEBDAV + \"://\" + lastUsername + \":\" + lastPassword + \"@\" + uri.getHost() + \":\" + port + uri.getPath();\n            }\n        } catch (URISyntaxException ex) {\n            Logger.getLogger(WebDAVPanel.class.getName()).log(Level.SEVERE, null, ex);\n        }\n        \n        FileURL fileUrl = FileURL.getFileURL(url);\n\n        fileUrl.setCredentials(new Credentials(lastUsername, lastPassword));\n\n        fileUrl.setPort(port);\n\n        return fileUrl;\n    }\n\n    @Override\n    boolean usesCredentials() {\n        return true;\n    }\n\n    @Override\n    public void dialogValidated() {\n        updateValues();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/server/package.html",
    "content": "<body>\n  Components used to let users enter remote file systems information before connecting.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/shell/RunDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.shell;\n\nimport java.awt.BorderLayout;\nimport java.awt.Container;\nimport java.awt.Dimension;\nimport java.awt.FlowLayout;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.awt.event.KeyEvent;\nimport java.awt.event.KeyListener;\nimport java.awt.event.WindowAdapter;\nimport java.awt.event.WindowEvent;\nimport java.io.PrintStream;\n\nimport javax.swing.Box;\nimport javax.swing.JButton;\nimport javax.swing.JLabel;\nimport javax.swing.JPanel;\nimport javax.swing.JScrollPane;\nimport javax.swing.JTextArea;\n\nimport com.mucommander.commons.file.FileURL;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.FileProtocols;\nimport com.mucommander.process.AbstractProcess;\nimport com.mucommander.process.ProcessListener;\nimport com.mucommander.shell.Shell;\nimport com.mucommander.shell.ShellHistoryManager;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.RunCommandAction;\nimport com.mucommander.ui.dialog.DialogToolkit;\nimport com.mucommander.ui.dialog.FocusDialog;\nimport com.mucommander.ui.icon.SpinningDial;\nimport com.mucommander.ui.layout.XBoxPanel;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.theme.Theme;\nimport com.mucommander.ui.theme.ThemeManager;\n\n/**\n * Dialog used to execute a user-defined command.\n * <p>\n * Creates and displays a new dialog allowing the user to input a command which will be executed once the action is confirmed.\n * The command output of the user command is displayed in a text area\n * <p>\n * Note that even though this component is affected by themes, it's impossible to edit the current theme while it's being displayed.\n * For this reason, the RunDialog doesn't listen to theme modifications.\n *\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic class RunDialog extends FocusDialog implements ActionListener, ProcessListener, KeyListener {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(RunDialog.class);\n\t\n    /** Main frame this dialog depends on. */\n    private final MainFrame mainFrame;\n    /** Editable combo box used for shell input and history. */\n    private ShellComboBox inputCombo;\n    /** Run/stop button. */\n    private JButton btnRunStop;\n    /** Cancel button. */\n    private JButton btnCancel;\n    /** Clear shell history button. */\n    private JButton btnClear;\n    /** Text area used to display the shell output. */\n    private JTextArea outputTextArea;\n    /** Used to let the user known that the command is still running. */\n    private SpinningDial dial;\n\n\n\n    /** Stream used to send characters to the process' stdin process. */\n    private PrintStream     processInput;\n    /** Process currently running, <code>null</code> if none. */\n    private AbstractProcess currentProcess;\n\n\n\n    /** Minimum dimensions for the dialog. */\n    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(600, 400);\n\n\n\n    /**\n     * Creates the dialog's shell output area.\n     * @return a scroll pane containing the dialog's shell output area.\n     */\n    private JScrollPane createOutputArea() {\n        // Creates and initializes the output area.\n        outputTextArea = new JTextArea();\n        outputTextArea.setLineWrap(true);\n        outputTextArea.setCaretPosition(0);\n        outputTextArea.setRows(10);\n        outputTextArea.setEditable(false);\n        outputTextArea.addKeyListener(this);\n\n        // Applies the current theme to the shell output area.\n        outputTextArea.setForeground(ThemeManager.getCurrentColor(Theme.SHELL_FOREGROUND_COLOR));\n        outputTextArea.setCaretColor(ThemeManager.getCurrentColor(Theme.SHELL_FOREGROUND_COLOR));\n        outputTextArea.setBackground(ThemeManager.getCurrentColor(Theme.SHELL_BACKGROUND_COLOR));\n        outputTextArea.setSelectedTextColor(ThemeManager.getCurrentColor(Theme.SHELL_SELECTED_FOREGROUND_COLOR));\n        outputTextArea.setSelectionColor(ThemeManager.getCurrentColor(Theme.SHELL_SELECTED_BACKGROUND_COLOR));\n        outputTextArea.setFont(ThemeManager.getCurrentFont(Theme.SHELL_FONT));\n\n        // Creates a scroll pane on the shell output area.\n        return new JScrollPane(outputTextArea, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);\n    }\n\n    /**\n     * Creates the shell input part of the dialog.\n     * @return the shell input part of the dialog.\n     */\n    private YBoxPanel createInputArea() {\n        YBoxPanel mainPanel = new YBoxPanel();\n\n        // Adds a textual description:\n        // - if we're working in a local directory, 'init in current folder'.\n        // - if we're working on a non-standard FS, 'init in home folder'.\n        FileURL fileUrl = mainFrame.getActivePanel().getCurrentFolder().getURL();\n        boolean isFile = fileUrl.getScheme().equals(FileProtocols.FILE);\n        String label = i18n(isFile ? \"run_dialog.run_command_description\" : \"run_dialog.run_in_home_description\") + \":\";\n        mainPanel.add(new JLabel(label));\n\n        // Adds the shell input combo box.\n        mainPanel.add(inputCombo = new ShellComboBox(this));\n        inputCombo.setEnabled(true);\n\n        // Adds a textual description of the shell output area.\n        mainPanel.addSpace(10);\n\n        JPanel labelPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        labelPanel.add(new JLabel(i18n(\"run_dialog.command_output\")+\":\"));\n        labelPanel.add(new JLabel(dial = new SpinningDial()));\n        mainPanel.add(labelPanel);\n\n        return mainPanel;\n    }\n\n    /**\n     * Creates a panel containing the dialog's buttons.\n     * @return a panel containing the dialog's buttons.\n     */\n    private XBoxPanel createButtonsArea() {\n        XBoxPanel buttonsPanel = new XBoxPanel();\n\n        // 'Clear history' button.\n        buttonsPanel.add(btnClear = new JButton(i18n(\"run_dialog.clear_history\")));\n        btnClear.addActionListener(this);\n\n        // Separator.\n        buttonsPanel.add(Box.createHorizontalGlue());\n\n        // 'Run / stop' and 'Cancel' buttons.\n        buttonsPanel.add(DialogToolkit.createOKCancelPanel(\n                btnRunStop = new JButton(i18n(\"run_dialog.run\")),\n                btnCancel = new JButton(i18n(\"cancel\")),\n                getRootPane(),\n                this));\n\n        return buttonsPanel;\n    }\n\n    /**\n     * Creates and displays a new RunDialog.\n     * @param mainFrame the main frame this dialog is attached to.\n     */\n    public RunDialog(MainFrame mainFrame) {\n        super(mainFrame.getJFrame(), ActionProperties.getActionLabel(RunCommandAction.Descriptor.ACTION_ID), mainFrame.getJFrame());\n        this.mainFrame = mainFrame;\n\t\t\n        // Initializes the dialog's UI.\n        Container contentPane = getContentPane();\n        contentPane.add(createInputArea(), BorderLayout.NORTH);\n        contentPane.add(createOutputArea(), BorderLayout.CENTER);\n        contentPane.add(createButtonsArea(), BorderLayout.SOUTH);\n\n        // Sets default items.\n        setInitialFocusComponent(inputCombo);\n        getRootPane().setDefaultButton(btnRunStop);\n\n        // Makes sure that any running process will be killed when the dialog is closed.\n        addWindowListener(new WindowAdapter() {\n                @Override\n                public void windowClosed(WindowEvent e) {\n                    if (currentProcess != null) {\n                        processInput.close();\n                        currentProcess.destroy();\n                    }\n                }\n            });\n\n        // Sets the dialog's minimum size.\n        setMinimumSize(MINIMUM_DIALOG_DIMENSION);\n    }\n\n\n\n    /**\n     * Notifies the RunDialog that the current process has died.\n     * @param retValue process' return code (not used).\n     */\t\n    public void processDied(int retValue) {\n        LOGGER.debug(\"process exit, return value = {}\", retValue);\n        currentProcess = null;\n        if (processInput != null) {\n            processInput.close();\n            processInput = null;\n        }\n        switchToRunState();\n    }\t\n\n    /**\n     * Ignored.\n     */\n    public void processOutput(byte[] buffer, int offset, int length) {}\n\n    /**\n     * Notifies the RunDialog that the process has output some text.\n     * @param output contains the process' output.\n     */\n    public void processOutput(String output) {\n        addToTextArea(output);\n    }\n\n\n    /**\n     * Notifies the RunDialog that a key has been pressed.\n     * <p>\n     * This method will ignore all events while a process is not running. If a process is running:\n     * <ul>\n     *  <li><code>VK_ESCAPE</code> events are skipped and left to the <i>Cancel</i> button to handle.</li>\n     *  <li>Printable characters are passed to the process and consumed.</li>\n     *  <li>All other events are consumed.</li>\n     * </ul>\n     *\n     * <p>\n     * At the time of writing, <code>tab</code> characters do not seem to be caught.\n     * \n     * @param event describes the key event.\n     */\n    public void keyPressed(KeyEvent event) {\n        // Only handle keyPressed events when a process is running.\n        if (currentProcess == null) {\n            return;\n        }\n\n        // Ignores VK_ESCAPE events, as their behavior is a bit strange: they register\n        // as a printable character, and reacting to their being typed apparently consumes\n        // the event - preventing the dialog from being closed.\n        if (event.getKeyCode() != KeyEvent.VK_ESCAPE) {\n            char character = event.getKeyChar();\n\n            // Only printable key typed are passed to the shell.\n            if (character != KeyEvent.CHAR_UNDEFINED) {\n                processInput.print(character);\n                addToTextArea(String.valueOf(character));\n            }\n            event.consume();\n        }\n    }\n\n    /**\n     * Not used.\n     */\n    public void keyTyped(KeyEvent event) {}\n\n    /**\n     * Not used.\n     */\n    public void keyReleased(KeyEvent event) {}\n\n\n    private void clearHistory() {\n        ShellHistoryManager.clear();\n\n        // Sets the new focus depending on whether a process is currently running or not.\n        if (currentProcess == null) {\n            inputCombo.requestFocus();\n            outputTextArea.setText(\"\");\n        } else {\n            outputTextArea.requestFocus();\n            outputTextArea.getCaret().setVisible(true);\n        }\n    }\n\n    private void stopRunning() {\n        processInput.close();\n        currentProcess.destroy();\n        this.currentProcess = null;\n        switchToRunState();\n    }\n\n    /**\n     * Notifies the RunDialog that an action has been performed.\n     * @param e describes the action that occurred.\n     */\n    public void actionPerformed(ActionEvent e) {\n        Object source = e.getSource();\n\n        if (source == btnClear) {           // 'Clear shell history' has been pressed, clear shell history.\n            clearHistory();\n        } else if (source == btnRunStop) {  // 'Run / stop' button has been pressed.\n            // If we're not running a process, start a new one.\n            if (currentProcess == null) {\n                runCommand(inputCombo.getCommand());\n            } else {    // If we're running a process, kill it.\n                stopRunning();\n            }\n        } else if (source == btnCancel) {      // Cancel button disposes the dialog and kills the process\n            if (currentProcess != null) {\n                currentProcess.destroy();\n            }\n            dispose();\n        }\n    }\n\n\n\n    /**\n     * Switches the UI back to 'Run command' state.\n     */\n    private void switchToRunState() {\n        // Stops the spinning dial.\n        dial.setAnimated(false);\n\n        // Change 'Stop' button to 'Run'\n        this.btnRunStop.setText(i18n(\"run_dialog.run\"));\n\n        // Make command field active again\n        this.inputCombo.setEnabled(true);\n        inputCombo.requestFocus();\n\n        // Disables the caret in the process output area.\n        outputTextArea.getCaret().setVisible(false);\n\n        // Repaint this dialog\n        repaint();\n    }\t\n\n    /**\n     * Runs the specified command.\n     * @param command command to init.\n     */\n    void runCommand(String command) {\n        try {\n            // Starts the spinning dial.\n            dial.setAnimated(true);\n\n            // Change 'Run' button to 'Stop'\n            this.btnRunStop.setText(i18n(\"run_dialog.stop\"));\n\n            // Resets the process output area.\n            outputTextArea.setText(\"\");\n            outputTextArea.setCaretPosition(0);\n            outputTextArea.getCaret().setVisible(true);\n            outputTextArea.requestFocus();\n\n            // No new command can be entered while a process is running.\n            inputCombo.setEnabled(false);\n\n            currentProcess = Shell.execute(command, mainFrame.getActivePanel().getCurrentFolder(), this);\n            processInput   = new PrintStream(currentProcess.getOutputStream(), true);\n\n            // Repaints the dialog.\n            repaint();\n        } catch(Exception e) {\n            // Notifies the user that an error occurred and resets to normal state.\n            addToTextArea(i18n(\"generic_error\"));\n            switchToRunState();\n        }\n    }\n\n    /**\n     * Appends the specified string to the shell output area.\n     * @param s string to append to the shell output area.\n     */\n    private void addToTextArea(String s) {\n        outputTextArea.append(s);\n        outputTextArea.setCaretPosition(outputTextArea.getText().length());\n        outputTextArea.getCaret().setVisible(true);\n        outputTextArea.repaint();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/shell/ShellComboBox.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.shell;\n\nimport com.mucommander.shell.ShellHistoryListener;\nimport com.mucommander.shell.ShellHistoryManager;\nimport com.mucommander.ui.autocomplete.CompleterFactory;\nimport com.mucommander.ui.combobox.AutocompleteEditableCombobox;\nimport com.mucommander.ui.combobox.EditableComboBox;\nimport com.mucommander.ui.combobox.EditableComboBoxListener;\nimport com.mucommander.ui.combobox.SaneComboBox;\nimport com.mucommander.ui.theme.Theme;\nimport com.mucommander.ui.theme.ThemeManager;\n\nimport javax.swing.*;\nimport javax.swing.event.PopupMenuEvent;\nimport javax.swing.event.PopupMenuListener;\nimport java.util.Iterator;\n\n/**\n * Widget used for shell command input.\n * <p>\n * In addition to providing basic shell command input features, this widget interfaces with\n * the {@link com.mucommander.shell.ShellHistoryManager} to offer a history of shell commands\n * for the user to browse through.\n * <p>\n * Note that even though this component is affected by themes, it's impossible to edit the current theme while it's being displayed.\n * For this reason, the RunDialog doesn't listen to theme modifications.\n *\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic class ShellComboBox extends AutocompleteEditableCombobox<String> implements EditableComboBoxListener, ShellHistoryListener, PopupMenuListener {\n    /** Input field used to type in commands. */\n    private final JTextField input;\n    /** Where to init commands. */\n    private final RunDialog  parent;\n\n\n    /**\n     * Creates a new shell combo box.\n     * @param parent where to execute commands.\n     */\n    public ShellComboBox(RunDialog parent) {\n    \tsuper(CompleterFactory.getComboboxOptionsCompleter());\n    \t\n        this.parent = parent;\n\n        // Sets the combo box's editor.\n        this.input = getTextField();\n\n        addPopupMenuListener(this);\n\n        // Sets colors and font according to the current theme.\n        setForeground(ThemeManager.getCurrentColor(Theme.SHELL_HISTORY_FOREGROUND_COLOR));\n        setBackground(ThemeManager.getCurrentColor(Theme.SHELL_HISTORY_BACKGROUND_COLOR));\n        setSelectionForeground(ThemeManager.getCurrentColor(Theme.SHELL_HISTORY_SELECTED_FOREGROUND_COLOR));\n        setSelectionBackground(ThemeManager.getCurrentColor(Theme.SHELL_HISTORY_SELECTED_BACKGROUND_COLOR));\n        setFont(ThemeManager.getCurrentFont(Theme.SHELL_HISTORY_FONT));\n\n        // Fills the combo box with the current history.\n        populateHistory();\n        ShellHistoryManager.addListener(this);\n\n        // Select first item in the combo box (if any)\n        if (getItemCount() > 0) {\n            setSelectedIndex(0);\n        }\n\n        // Automatically update the text field's contents when an item is selected in this combo box\n        setComboSelectionUpdatesTextField(true);\n        \n        // Listener to actions fired by this EditableComboBox\n        addEditableComboBoxListener(this);\n    }\n\n    /**\n     * Fills the combo box with the current shell history.\n     */\n    private void populateHistory() {\n        // Empties the content of the combo box\n        removeAllItems();\n\n        // Iterates through all shell history elements.\n        Iterator<String> iterator = ShellHistoryManager.getHistoryIterator();\n        String command = null;\n        while (iterator.hasNext()) {\n            insertItemAt((command = iterator.next()), 0);\n        }\n\n        // If the list is not empty, initializes the input field on the last command.\n        if (command != null) {\n            input.setText(command);\n            input.setSelectionStart(0);\n            input.setSelectionEnd(command.length());\n        }\n    }\n\n\n\n    // - Misc. ----------------------------------------------------------------------\n    // ------------------------------------------------------------------------------\n\n    /**\n     * Overrides this method to ignore events received when this component is disabled.\n     */\n    @Override\n    public void setEnabled(boolean enabled) {\n        super.setEnabled(enabled);\n\n        if (enabled) {\n            input.setSelectionStart(0);\n            input.setSelectionEnd(input.getText().length());\n        }\n    }\n\n\n\n    // - EditableComboBoxListener implementation ------------------------------------\n    // ------------------------------------------------------------------------------\n\n    public void comboBoxSelectionChanged(SaneComboBox source) {}\n\n    public void textFieldValidated(EditableComboBox source) {parent.runCommand(input.getText());}\n\n    public void textFieldCancelled(EditableComboBox source) {parent.dispose();}\n\n\n    // - Shell listener code --------------------------------------------------------\n    // ------------------------------------------------------------------------------\n\n    public void historyChanged(String command) {\n    \tcommand = command.trim();\n\t\tinsertItemAt(command, 0);\n    }\n    \n    public void historyCleared() {\n        removeAllItems();\n        input.setText(\"\");\n    }\n\n\n\n    // - Popup menu listening -------------------------------------------------------\n    // ------------------------------------------------------------------------------\n    /**\n     * Ignored.\n     */\n    public void popupMenuCanceled(PopupMenuEvent e) {}\n\n    /**\n     * Makes sure the selection is always the first element in the list.\n     */\n    public void popupMenuWillBecomeVisible(PopupMenuEvent e) {\n        setComboSelectionUpdatesTextField(false);\n        if (getItemCount() > 0) {\n            setSelectedIndex(0);\n        }\n\n        setComboSelectionUpdatesTextField(true);\n    }\n\n    /**\n     * Ignored.\n     */\n    public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {}\n\n\n    // - Command handling -----------------------------------------------------------\n    // ------------------------------------------------------------------------------\n    /**\n     * Returns the current shell command.\n     * @return the current shell command.\n     */\n    public String getCommand() {return input.getText();}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/shell/package.html",
    "content": "<body>\n  Components used to run shell commands and browse the shell history.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/shutdown/QuitDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.shutdown;\n\nimport javax.swing.JCheckBox;\n\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.QuitAction;\nimport com.mucommander.ui.dialog.QuestionDialog;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.WindowManager;\n\n/**\n * Quit confirmation dialog invoked when the user asked the application to quit, which gives the user a chance\n * to cancel the operation in case the quit shortcut was hit by mistake.\n * \n * <p>A checkbox allows the user to disable this confirmation dialog for the next the application is quit. It can\n * later be re-enabled in the application preferences.\n *\n * @author Maxence Bernard\n */\npublic class QuitDialog extends QuestionDialog {\n    /**\n     * This flag used to prevent duplication of QuitDialog on macOS\n     */\n    private static boolean displayed = false;\n\n    /** True when quit confirmation button has been pressed by the user */\n    private final boolean quitConfirmed;\n\t\n    private final static int QUIT_ACTION = 0;\n    private final static int CANCEL_ACTION = 1;\n\n    \n    /**\n     * Creates a new instance of QuitDialog, displays the dialog and waits for a user's choice. This dialog\n     * doesn't quit the application when 'Quit' is confirmed, it is up to the method that invoked this dialog\n     * to perform that getTask, only if {@link #quitConfirmed()} returns <code>true</code>.\n     *\n     * <p>If 'Quit' is selected and the 'Show next time' checkbox is unchecked, the preference will be saved and\n     * {@link #confirmQuit()} will return <code>true</code>.\n     *\n     * @param mainFrame the parent MainFrame\n     */\n    public QuitDialog(MainFrame mainFrame) {\n        super(mainFrame.getJFrame(),\n              Translator.get(\"quit_dialog.title\"),\n              Translator.get(\"quit_dialog.desc\", \"\"+WindowManager.getMainFrames().size()),\n              mainFrame.getJFrame(),\n              new String[] {ActionProperties.getActionLabel(QuitAction.Descriptor.ACTION_ID), Translator.get(\"cancel\")},\n              new int[] {QUIT_ACTION, CANCEL_ACTION},\n              0);\n\t\t\n        JCheckBox showNextTimeCheckBox = new JCheckBox(Translator.get(\"quit_dialog.show_next_time\"), true);\n        addComponent(showNextTimeCheckBox);\n\t\t\n        this.quitConfirmed = getActionValue() == QUIT_ACTION;\n        if (quitConfirmed) {\n            // Remember user preference\n        \tTcConfigurations.getPreferences().setVariable(TcPreference.CONFIRM_ON_QUIT, showNextTimeCheckBox.isSelected());\n        }\n    }\n    \n    \n    /**\n     * Returns <code>true</code> if the user confirmed and pressed the Quit button.\n     *\n     * @return <code>true</code> if the user confirmed and pressed the Quit button\n     */\n    public boolean quitConfirmed() {\n        return quitConfirmed;\n    }\n    \n    \n    /**\n     * Returns <code>true</code> if quit confirmation hasn't been disabled in the preferences, and if there is at least\n     * one window to close.\n     *\n     * @return <code>true</code> if quit confirmation hasn't been disabled in the preferences\n     */\n    public static boolean confirmationRequired() {\n        return  !WindowManager.getMainFrames().isEmpty()     // May happen after an uncaught exception in the startup sequence\n             && TcConfigurations.getPreferences().getVariable(TcPreference.CONFIRM_ON_QUIT, TcPreferences.DEFAULT_CONFIRM_ON_QUIT);\n    }\n    \n    \n    /**\n     * Shows up a QuitDialog asking the user for confirmation to quit, and returns <code>true</code> if user confirmed\n     * the operation. The dialog will not be shown if quit confirmation has been disabled in the preferences.\n     * In this case, <code>true</code> will simply be returned.\n     *\n     * @return <code>true</code> if user confirmed the quit operation\n     */\n    public static synchronized boolean confirmQuit() {\n        if (displayed) {\n            return false;\n        }\n        displayed = true;\n        // Show confirmation dialog only if it hasn't been disabled in the preferences\n        if (confirmationRequired()) {\n            QuitDialog quitDialog = new QuitDialog(WindowManager.getCurrentMainFrame());\n            // Return true if user confirmed quit\n            displayed = false;\n            return quitDialog.quitConfirmed();\n        }\n        displayed = false;\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/shutdown/package.html",
    "content": "<body>\n  Dialogs displayed before shutdown.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/startup/CheckVersionDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.startup;\n\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.desktop.DesktopManager;\nimport com.mucommander.job.SelfUpdateJob;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.GoToWebsiteAction;\nimport com.mucommander.ui.dialog.InformationDialog;\nimport com.mucommander.ui.dialog.QuestionDialog;\nimport com.mucommander.ui.dialog.file.ProgressDialog;\nimport com.mucommander.ui.layout.InformationPane;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.updates.VersionChecker;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.net.URI;\nimport java.net.URL;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * This class takes care of retrieving the information about the latest muCommander version from a remote server and\n * displaying the result to the end user.\n *\n * @author Maxence Bernard\n */\npublic class CheckVersionDialog extends QuestionDialog {\n\tprivate static Logger logger;\n\t\n    /** Parent MainFrame instance */\n    private final MainFrame mainFrame;\n\n    /** true if the user manually clicked on the 'Check for updates' menu item,\n     * false if the update check was automatically triggered on startup */\n    private final boolean userInitiated;\n\n    /** Dialog's width has to be at least 240 */\n    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(320,0);\t\n\n    private final static int OK_ACTION = 0;\n    private final static int GO_TO_WEBSITE_ACTION = 1;\n    private final static int INSTALL_AND_RESTART_ACTION = 2;\n\n\t\n    /**\n     * Checks for updates and notifies the user of the outcome. The check itself is performed in a separate thread\n     * to prevent the app from waiting for the request's result.\n     *\n     * @param userInitiated true if the user manually clicked on the 'Check for updates' menu item,\n     * false if the update check was automatically triggered on startup. If the check was automatically triggered,\n     * the user won't be notified if there is no new version (current version is the latest).\n     */\n    public CheckVersionDialog(MainFrame mainFrame, VersionChecker versionChecker, boolean userInitiated) {\n        super(mainFrame.getJFrame(), \"\", mainFrame.getJFrame());\n        this.mainFrame = mainFrame;\n        this.userInitiated = userInitiated;\n\n        // Do all the hard work in a separate thread\n        //new Thread(this, \"com.mucommander.ui.dialog.startup.CheckVersionDialog's Thread\").start();\n        init(versionChecker);\n    }\n\t\n    \n    /**\n     * Checks for updates and notifies the user of the outcome.\n     */\n    public void init(VersionChecker version) {\n        Container contentPane = getContentPane();\n        contentPane.setLayout(new BorderLayout());\n        \n        String message;\n        String title;\n        URL downloadURL = null;\n        boolean downloadOption = false;\n        String jarURL = null;\n\n        try {\n            getLogger().debug(\"Checking for new version...\");\n\n            if (version == null) {\n                version = VersionChecker.getInstance();\n            }\n            //version = VersionChecker.getInstance();\n            // A newer version is available\n            if (version != null && version.isNewVersionAvailable()) {\n                getLogger().info(\"A new version is available!\");\n\n                title = i18n(\"version_dialog.new_version_title\");\n\n                // Checks if the current platform can open a new browser window\n                downloadURL = new URI(version.getDownloadURL()).toURL();\n                downloadOption = DesktopManager.isOperationSupported(DesktopManager.BROWSE, new Object[] {downloadURL});\n                \n                // If the platform is not capable of opening a new browser window,\n                // display the download URL.\n                message = downloadOption ? i18n(\"version_dialog.new_version\") : i18n(\"version_dialog.new_version_url\", downloadURL.toString());\n//message += version.getLatestVersion() + \"/ \" + RuntimeConstants.VERSION + \"(\" + RuntimeConstants.BUILD_NUMBER + \")\";\n                jarURL = version.getJarURL();\n            } else {    // We're already running latest version\n                getLogger().debug(\"No new version.\");\n\n                // If the version check was not initiated by the user (i.e. was automatic),\n                // we do not need to inform the user that he already has the latest version\n                if (!userInitiated) {\n                    dispose();\n                    return;\n                }\n                \n                title = i18n(\"version_dialog.no_new_version_title\");\n                message = i18n(\"version_dialog.no_new_version\");\n            }\n        } catch(Exception e) {\n            // If the version check was not initiated by the user (i.e. was automatic),\n            // we do not need to inform the user that the check failed\n            if (!userInitiated) {\n                dispose();\n                return;\n            }\n\n            title = i18n(\"version_dialog.not_available_title\");\n            message = i18n(\"version_dialog.not_available\");\n        }\n\n        // Set title\n        setTitle(title);\n\n        List<Integer> actionsV = new ArrayList<>();\n        List<String> labelsV = new ArrayList<>();\n\n        // 'OK' choice\n        actionsV.add(OK_ACTION);\n        labelsV.add(i18n(\"ok\"));\n\n        // 'Go to website' choice (if available)\n        if (downloadOption) {\n            actionsV.add(GO_TO_WEBSITE_ACTION);\n            labelsV.add(ActionProperties.getActionLabel(GoToWebsiteAction.Descriptor.ACTION_ID));\n        }\n\n        // 'Install and restart' choice (if available)\n        if (jarURL != null) {\n            actionsV.add(INSTALL_AND_RESTART_ACTION);\n            labelsV.add(i18n(\"version_dialog.install_and_restart\"));\n        }\n\n        // Turn the vectors into arrays\n        int nbChoices = actionsV.size();\n        int[] actions = new int[nbChoices];\n        String[] labels = new String[nbChoices];\n        for (int i = 0; i < nbChoices; i++) {\n            actions[i] = actionsV.get(i);\n            labels[i] = labelsV.get(i);\n        }\n\n        InformationPane pane = new InformationPane(message, null, Font.PLAIN, InformationPane.INFORMATION_ICON);\n        init(pane, labels, actions, 0);\n\t\t\t\n        JCheckBox cbShowNextTime = new JCheckBox(i18n(\"prefs_dialog.check_for_updates_on_startup\"),\n        \t\t\t\t\t\t\t\t\t\t\t\tTcConfigurations.getPreferences().getVariable(TcPreference.CHECK_FOR_UPDATE,\n                                                                                        TcPreferences.DEFAULT_CHECK_FOR_UPDATE));\n        addComponent(cbShowNextTime);\n\n        setMinimumSize(MINIMUM_DIALOG_DIMENSION);\n\t\t\n        // Show dialog and get user action\n        int action = getActionValue();\n        executeAction(downloadURL, jarURL, action);\n\n        // Remember user preference\n        TcConfigurations.getPreferences().setVariable(TcPreference.CHECK_FOR_UPDATE, cbShowNextTime.isSelected());\n    }\n\n    private void executeAction(URL downloadURL, String jarURL, int action) {\n        if (action == GO_TO_WEBSITE_ACTION) {\n            gotoWebSite(downloadURL);\n        } else if (action == INSTALL_AND_RESTART_ACTION) {\n            installAndRelaunch(jarURL);\n        }\n    }\n\n    private void installAndRelaunch(String jarURL) {\n        ProgressDialog progressDialog = new ProgressDialog(mainFrame, i18n(\"Installing new version\"));\n        SelfUpdateJob job = new SelfUpdateJob(progressDialog, mainFrame, FileFactory.getFile(jarURL));\n        progressDialog.start(job);\n    }\n\n    private void gotoWebSite(URL downloadURL) {\n        try {\n            DesktopManager.executeOperation(DesktopManager.BROWSE, new Object[] {downloadURL});\n        } catch(Exception e) {\n            InformationDialog.showErrorDialog(this);\n        }\n    }\n\n    private static Logger getLogger() {\n        if (logger == null) {\n            logger = LoggerFactory.getLogger(CheckVersionDialog.class);\n        }\n        return logger;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/startup/InitialSetupDialog.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2020 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dialog.startup;\n\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.util.Iterator;\n\nimport javax.swing.*;\n\nimport com.mucommander.RuntimeConstants;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.ui.combobox.TcComboBox;\nimport com.mucommander.ui.dialog.FocusDialog;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.theme.Theme;\nimport com.mucommander.ui.theme.ThemeManager;\n\nimport static com.mucommander.conf.TcPreference.*;\nimport static com.mucommander.conf.TcPreference.LOOK_AND_FEEL;\n\n/**\n * Dialog box allowing users to select misc. setup options for muCommander.\n * @author Nicolas Rinaudo\n */\npublic class InitialSetupDialog extends FocusDialog implements ActionListener {\n\n    /** All available look and feels. */\n    private UIManager.LookAndFeelInfo[] lfInfo;\n    /** Used to select a startup theme. */\n    private TcComboBox<Theme> cbTheme;\n    /** Used to select a look and feel. */\n    private TcComboBox<String> cbLookAndFeel;\n    private TcComboBox<String> cbEditorTheme;\n    /** Used to validate the user's choice. */\n    private JButton btnOk;\n\n\n    /**\n     * Creates the dialog's theme panel.\n     * @return the dialog's theme panel.\n     */\n    private JPanel createThemesPanel() {\n\t\tJPanel themePanel = new YBoxPanel();\n\t\tthemePanel.setAlignmentX(LEFT_ALIGNMENT);\n        themePanel.setBorder(BorderFactory.createTitledBorder(i18n(\"prefs_dialog.themes\")));\n\n\t\t// Adds the panel description.\n\t\tJPanel tempPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n\t\ttempPanel.add(new JLabel(i18n(\"setup.theme\") + ':'));\n\n\t\t// Adds the theme combo box.\n\t\tcbTheme = createThemeComboBox();\n\t\ttempPanel.add(cbTheme);\n\t\tthemePanel.add(tempPanel);\n\n\t\t// Adds the panel description.\n\t\ttempPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n\t\ttempPanel.add(new JLabel(i18n(\"prefs_dialog.syntax_themes\") + ':'));\n\n\t\tcbEditorTheme = new TcComboBox<>(ThemeManager.predefinedSyntaxThemeNames());\n\t\tcbEditorTheme.addActionListener(this);\n\n\t\ttempPanel.add(cbEditorTheme);\n\t\tthemePanel.add(tempPanel);\n\n\t\treturn themePanel;\n    }\n\n\tprivate TcComboBox<Theme> createThemeComboBox() {\n\t\tTcComboBox<Theme> themeComboBox = new TcComboBox<>();\n\t\tIterator<Theme> themes = ThemeManager.availableThemes();\n\n\t\tint index = 0;\t\t\t\t// Index of the currently analyzed theme.\n\t\tint selectedIndex = 0;\t\t// Index of the current theme in the combo box.\n\t\t// Adds all themes to the combo box.\n\t\twhile (themes.hasNext()) {\n\t\t\tTheme theme = themes.next();\n\t\t\tthemeComboBox.addItem(theme);\n\t\t\tif (ThemeManager.isCurrentTheme(theme)) {\n\t\t\t\tselectedIndex = index;\n\t\t\t}\n\t\t\tindex++;\n\t\t}\n\t\t// Selects the current theme.\n\t\tthemeComboBox.setSelectedIndex(selectedIndex);\n\t\tthemeComboBox.addActionListener(this);\n\n\t\treturn themeComboBox;\n\t}\n\n\t/**\n     * Creates the dialog's look and feel panel.\n     * @return the dialog's look and feel panel.\n     */\n    private JPanel createLookAndFeelPanel() {\n\t\t// Initializes the theme panel.\n\t\tJPanel lfPanel = new YBoxPanel();\n\t\tlfPanel.setAlignmentX(LEFT_ALIGNMENT);\n        lfPanel.setBorder(BorderFactory.createTitledBorder(i18n(\"prefs_dialog.look_and_feel\")));\n\n\t\t// Adds the panel description.\n\t\tJPanel tempPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n\t\ttempPanel.add(new JLabel(i18n(\"setup.look_and_feel\") + ':'));\n\t\tlfPanel.add(tempPanel);\n\n\t\t// Initialises the l&f combo box.\n\t\tcbLookAndFeel = new TcComboBox<>();\n\t\tlfInfo = UIManager.getInstalledLookAndFeels();\n\t\tString currentLf = UIManager.getLookAndFeel().getName();\n\t\tint selectedIndex = -1;\n\t\t// Goes through all available look&feels and selects the current one.\n        for (int i = 0; i < lfInfo.length; i++) {\n\t\t\tString buffer = lfInfo[i].getName();\n\n            // Tries to select current L&F\n            if (currentLf.equals(buffer)) {\n\t\t\t\tselectedIndex = i;\n\t\t\t}// Under Mac OS X, Mac L&F is either reported as 'MacOS' or 'MacOS Adaptative'\n            // so we need this test\n            else if (selectedIndex == -1 && (currentLf.startsWith(buffer) || buffer.startsWith(currentLf))) {\n\t\t\t\tselectedIndex = i;\n\t\t\t}\n            cbLookAndFeel.addItem(buffer);\n        }\n\n        // If no match, selects first one\n        if (selectedIndex == -1) {\n\t\t\tselectedIndex = 0;\n\t\t}\n        cbLookAndFeel.setSelectedIndex(selectedIndex);\n\t\tcbLookAndFeel.addActionListener(this);\n\t\tlfPanel.add(cbLookAndFeel);\n\n\t\treturn lfPanel;\n    }\n\n    /**\n     * Creates the dialog's main panel.\n     * @return the dialog's main panel.\n     */\n    private JPanel createMainPanel() {\n\t\tYBoxPanel mainPanel = new YBoxPanel();\n\t\tmainPanel.add(new JLabel(i18n(\"setup.intro\")));\n\t\tmainPanel.addSpace(10);\n\t\tmainPanel.add(createThemesPanel());\n\t\tmainPanel.addSpace(10);\n\t\tmainPanel.add(createLookAndFeelPanel());\n\t\tmainPanel.addSpace(10);\n\n\t\tJPanel okPanel = new JPanel();\n\t\tokPanel.setLayout(new FlowLayout(FlowLayout.TRAILING));\n\t\tokPanel.add(btnOk = new JButton(i18n(\"ok\")));\n\t\tbtnOk.addActionListener(this);\n\n\t\tmainPanel.add(okPanel);\n\n\t\treturn mainPanel;\n    }\n\n\n\t/**\n     * Creates a new InitialSetupDialog.\n     * @param owner dialog's owner.\n     */\n    public InitialSetupDialog(Frame owner) {\n\t\tsuper(owner, i18n(\"setup.title\"), owner);\n\t\tpreInitDefault();\n\n\t\tgetContentPane().add(createMainPanel(), BorderLayout.CENTER);\n\t\tpack();\n\t\tsetResizable(false);\n        setInitialFocusComponent(cbTheme);\n\t\tsetKeyboardDisposalEnabled(false);\n        getRootPane().setDefaultButton(btnOk);\n    }\n\n\n\t@Override\n    public void actionPerformed(ActionEvent e) {\n    \tObject src = e.getSource();\n\t\tif (src == cbTheme) {\n\t\t\tThemeManager.setCurrentTheme((Theme) cbTheme.getSelectedItem());\n\t\t} else if (src == cbLookAndFeel) {\n\t\t\tsetVariable(LOOK_AND_FEEL, lfInfo[cbLookAndFeel.getSelectedIndex()].getClassName());\n\t\t} else if (src == cbEditorTheme) {\n\t\t\tsetVariable(SYNTAX_THEME_NAME, cbEditorTheme.getSelectedIndex());\n\t\t} else if (src == btnOk) {\n\t\t\tThemeManager.setCurrentTheme((Theme) cbTheme.getSelectedItem());\n\t\t\tsetVariable(LOOK_AND_FEEL, lfInfo[cbLookAndFeel.getSelectedIndex()].getClassName());\n\t\t\tpostInitDefault();\n\t\t\tdispose();\n\t\t}\n    }\n\n\tprivate void preInitDefault() {\n    \tinitFileGroups();\n    \tboolean isGnome = OsFamily.LINUX.isCurrent() && new com.mucommander.desktop.gnome.GuessedGnomeDesktopAdapter().isAvailable();\n    \tif (isGnome && RuntimeConstants.DISPLAY_4K) {\n\t\t\tsetVariable(TOOLBAR_ICON_SCALE, 2.0f);\n\t\t\tsetVariable(COMMAND_BAR_ICON_SCALE, 2.0f);\n\t\t\tsetVariable(TABLE_ICON_SCALE, 3.0f);\n\t\t}\n\t}\n\n\tprivate static void initFileGroups() {\n\t\tsetVariable(FILE_GROUP_1_MASK, \"*.png,*.jpg,*.gif,*.bmp,*.pcx,*.ico,*.jpeg,*.psd\");\n\t\tsetVariable(FILE_GROUP_2_MASK, \"*.java,*.pas,*.cpp,*.hpp,*.h,*.c,*.m,*.kt,*.s,*.asm,*.inc,*.go,*.rs,*.art\");\n\t\tsetVariable(FILE_GROUP_3_MASK, \"*.htm,*.html,*.css\");\n\t\tsetVariable(FILE_GROUP_4_MASK, \"*.py,*.php,*.js,*.gradle\");\n\t\tsetVariable(FILE_GROUP_5_MASK, \"*.xml,*.svg\");\n\t\tsetVariable(FILE_GROUP_6_MASK, \"*.doc,*.rtf,*.txt,*.pdf,*.djvu\");\n\t\tsetVariable(FILE_GROUP_7_MASK, \"*.mpg,*.mp4,*.avi\");\n\t\tsetVariable(FILE_GROUP_8_MASK, \"\");\n\t\tsetVariable(FILE_GROUP_9_MASK, \"\");\n\t\tsetVariable(FILE_GROUP_10_MASK, \"\");\n\t}\n\n\tprivate void postInitDefault() {\n\t}\n\n\tprivate static void setVariable(TcPreference preference, String value) {\n\t\tTcConfigurations.getPreferences().setVariable(preference, value);\n\t}\n\n\tprivate static void setVariable(TcPreference preference, int value) {\n\t\tTcConfigurations.getPreferences().setVariable(preference, value);\n\t}\n\n\tprivate static void setVariable(TcPreference preference, float value) {\n\t\tTcConfigurations.getPreferences().setVariable(preference, value);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/startup/package.html",
    "content": "<body>\n  Dialogs displayed when muCommander is starting up.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/symlink/CreateSymLinkDialog.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.dialog.symlink;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.util.SymLinkUtils;\nimport com.mucommander.job.ui.UserInputHelper;\nimport com.mucommander.ui.dialog.DialogToolkit;\nimport com.mucommander.ui.dialog.FocusDialog;\nimport com.mucommander.ui.dialog.QuestionDialog;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.text.FilePathField;\n\nimport javax.swing.JButton;\nimport javax.swing.JLabel;\nimport javax.swing.JTextField;\nimport java.awt.BorderLayout;\nimport java.awt.Container;\nimport java.awt.Dimension;\nimport java.awt.Frame;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.io.IOException;\nimport java.nio.file.AccessDeniedException;\nimport java.nio.file.FileAlreadyExistsException;\n\n/**\n * @author Oleg Trifonov\n * Created on 09/06/14.\n */\npublic class CreateSymLinkDialog extends FocusDialog implements ActionListener {\n\n    /**\n     * Dialog size constraints\n     */\n    private static final Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(320, 0);\n    /**\n     * Dialog width should not exceed 360, height is not an issue (always the same)\n     */\n    private static final Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(1024, 320);\n\n    private static final int RETRY_ACTION = 1;\n    private static final int CANCEL_ACTION = 0;\n\n    private final Frame mainFrame;\n    private final FilePathField edtTarget;\n    private final JTextField edtName;\n    private final JButton btnOk;\n    private final JButton btnCancel;\n\n\n\n\n    /**\n     *\n     * @param mainFrame\n     * @param linkPath path of root directory for created symbolic link or symbolic link itself\n     * @param targetFile existing filename (filename symlink will point to)\n     */\n    public CreateSymLinkDialog(Frame mainFrame, AbstractFile linkPath, AbstractFile targetFile) {\n        super(mainFrame, i18n(\"symboliclinkeditor.create\"), null);\n        this.mainFrame = mainFrame;\n\n        Container contentPane = getContentPane();\n\n        YBoxPanel yPanel = new YBoxPanel(10);\n\n        yPanel.add(new JLabel(i18n(\"symboliclinkeditor.target_file_create\") + ':'));\n        edtTarget = new FilePathField();\n        yPanel.add(edtTarget);\n        yPanel.addSpace(10);\n\n        yPanel.add(new JLabel(i18n(\"symboliclinkeditor.link_name\") + ':'));\n        edtName = new FilePathField();\n        yPanel.add(edtName);\n\n        contentPane.add(yPanel, BorderLayout.NORTH);\n\n        btnOk = new JButton(i18n(\"ok\"));\n        btnCancel = new JButton(i18n(\"cancel\"));\n        contentPane.add(DialogToolkit.createOKCancelPanel(btnOk, btnCancel, getRootPane(), this), BorderLayout.SOUTH);\n\n        // Path field will receive initial focus\n        setInitialFocusComponent(edtTarget);\n\n        setMinimumSize(MINIMUM_DIALOG_DIMENSION);\n        setMaximumSize(MAXIMUM_DIALOG_DIMENSION);\n\n        edtName.setText(linkPath.getAbsolutePath() + targetFile.getName());\n        edtTarget.setText(targetFile.getAbsolutePath());\n    }\n\n    @Override\n    public void actionPerformed(ActionEvent e) {\n        if (e.getSource() == btnOk) {\n            new Thread(this::execute).start();\n        } else if (e.getSource() == btnCancel) {\n            dispose();\n        }\n    }\n\n\n    private void execute() {\n        QuestionDialog dialog = null;\n        final String targetPath = edtTarget.getText();\n        final String linkPath = edtName.getText();\n        String errorMessage;\n        while (true) {\n            try {\n                SymLinkUtils.createSymlink(linkPath, targetPath);\n                // success\n                break;\n            } catch (FileAlreadyExistsException e) {\n                errorMessage = i18n(\"cannot_write_symlink_already_exists\", linkPath);\n            } catch (AccessDeniedException e) {\n                errorMessage = i18n(\"cannot_write_symlink_access_denied\", linkPath);\n            } catch (IOException e) {\n                errorMessage = i18n(\"cannot_write_symlink\", linkPath);\n            }\n\n            if (dialog == null) {\n                dialog = new QuestionDialog(mainFrame,\n                        i18n(\"error\"),\n                        errorMessage,\n                        mainFrame,\n                        new String[] {i18n(\"retry\"), i18n(\"cancel\")},\n                        new int[] {RETRY_ACTION, CANCEL_ACTION},\n                        0);\n            }\n\n            UserInputHelper jobUserInput = new UserInputHelper(null, dialog);\n            int action = (Integer)jobUserInput.getUserInput();\n            if (action < 0 || action == CANCEL_ACTION) {\n                break;\n            }\n        }\n        cancel();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/symlink/EditSymlinkDialog.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.dialog.symlink;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.util.SymLinkUtils;\nimport com.mucommander.ui.dialog.DialogToolkit;\nimport com.mucommander.ui.dialog.FocusDialog;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.text.FilePathField;\n\nimport javax.swing.JButton;\nimport javax.swing.JLabel;\nimport java.awt.BorderLayout;\nimport java.awt.Container;\nimport java.awt.Dimension;\nimport java.awt.Frame;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\n\n/**\n * Created on 23/06/14.\n */\npublic class EditSymlinkDialog extends FocusDialog implements ActionListener {\n\n    private final Frame mainFrame;\n    private final AbstractFile linkPath;\n    private final FilePathField edtTarget;\n    private final JButton btnOk;\n\n    /**\n     * Dialog size constraints\n     */\n    private static final Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(320, 0);\n    /**\n     * Dialog width should not exceed 360, height is not an issue (always the same)\n     */\n    private static final Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(1024, 320);\n\n\n    public EditSymlinkDialog(Frame mainFrame, AbstractFile linkPath) {\n        super(mainFrame, i18n(\"symboliclinkeditor.edit\"), null);\n        this.mainFrame = mainFrame;\n        this.linkPath = linkPath;\n\n        Container contentPane = getContentPane();\n\n        YBoxPanel yPanel = new YBoxPanel(10);\n\n        edtTarget = new FilePathField();\n        edtTarget.setDefaultLocation(linkPath.getParent());\n        String hint = String.format(i18n(\"symboliclinkeditor.target_file_edit\"), linkPath.getBaseName())+ ':';\n        yPanel.add(new JLabel(hint));\n        yPanel.add(edtTarget);\n\n        edtTarget.setText(SymLinkUtils.getTargetPath(linkPath));\n        edtTarget.addActionListener(this);\n\n        yPanel.addSpace(10);\n\n        contentPane.add(yPanel, BorderLayout.NORTH);\n\n        btnOk = new JButton(i18n(\"ok\"));\n        JButton btnCancel = new JButton(i18n(\"cancel\"));\n        contentPane.add(DialogToolkit.createOKCancelPanel(btnOk, btnCancel, getRootPane(), this), BorderLayout.SOUTH);\n        btnOk.addActionListener(this);\n\n        // Path field will receive initial focus\n        setInitialFocusComponent(edtTarget);\n\n        setMinimumSize(MINIMUM_DIALOG_DIMENSION);\n        setMaximumSize(MAXIMUM_DIALOG_DIMENSION);\n    }\n\n    @Override\n    public void actionPerformed(ActionEvent e) {\n        if (e.getSource() == btnOk || e.getSource() == edtTarget) {\n            SymLinkUtils.editSymlink(linkPath, edtTarget.getText());\n        }\n        cancel();\n    }\n\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dialog/tab/TabTitleDialog.kt",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.dialog.tab\n\nimport com.mucommander.ui.action.ActionProperties\nimport com.mucommander.ui.action.impl.SetTabTitleAction\nimport com.mucommander.ui.dialog.DialogToolkit\nimport com.mucommander.ui.dialog.FocusDialog\nimport com.mucommander.ui.layout.XBoxPanel\nimport com.mucommander.ui.main.FolderPanel\nimport com.mucommander.ui.main.MainFrame\nimport com.mucommander.ui.text.SizeConstrainedDocument\nimport java.awt.BorderLayout\nimport java.awt.Dimension\nimport java.awt.event.ActionEvent\nimport java.awt.event.ActionListener\nimport javax.swing.*\n\n/**\n * This dialog allow the user to enter a title for the currently selected tab.\n * Empty title means that the tab title will be based on the current location\n * presented in the tab.\n * \n * @author Arik Hadas\n */\nclass TabTitleDialog(\n    mainFrame: MainFrame,\n    /** The FolderPanel to which this tab belongs  */\n    private val folderPanel: FolderPanel\n) : FocusDialog(\n    mainFrame.jFrame,\n    ActionProperties.getActionLabel(SetTabTitleAction.Descriptor.ACTION_ID),\n    folderPanel.panel\n), ActionListener {\n    private val okButton = JButton(i18n(\"ok\"))\n\n    /** The text field in which the title is entered  */\n    private val titleTextField = JTextField().apply {\n        setDocument(SizeConstrainedDocument(31))\n        text = folderPanel.tabs.getCurrentTab().getTitle()\n        selectAll()\n    }\n\n    init {\n        val cancelButton = JButton(i18n(\"cancel\"))\n\n        contentPane.apply {\n            setLayout(BorderLayout())\n            // Add customization panel\n            add(createInnerPanel(), BorderLayout.CENTER)\n            // Aligns the button panel to the right.\n            add(DialogToolkit.createOKCancelPanel(okButton, cancelButton, rootPane, this@TabTitleDialog), BorderLayout.SOUTH)\n        }\n\n        minimumSize = MINIMUM_SIZE\n    }\n\n    private fun createInnerPanel(): JPanel =\n        XBoxPanel().apply {\n            add(JLabel(i18n(\"title\") + \":\"))\n            addSpace(10)\n            add(titleTextField) //, BorderLayout.CENTER);\n            setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5))\n        }\n\n    private fun changeTabTitle() {\n        val title = titleTextField.getText().trim()\n        folderPanel.tabs.setTitle(title.ifEmpty { null })\n    }\n\n    override fun actionPerformed(e: ActionEvent) {\n        dispose()\n\n        if (e.getSource() === okButton) {\n            changeTabTitle()\n        }\n    }\n\n    companion object {\n        /** Ensure the dialog width is at least 300  */\n        private val MINIMUM_SIZE = Dimension(250, 0)\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dnd/ClipboardNotifier.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dnd;\n\nimport java.awt.datatransfer.DataFlavor;\nimport java.awt.datatransfer.FlavorEvent;\nimport java.awt.datatransfer.FlavorListener;\n\nimport javax.swing.Action;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * ClipboardNotifier allows an action to be dynamically enabled when the clipboard contains files, and disabled otherwise.\n *\n * <p>ClipboardNotifier requires Java 1.5 and does not work under Mac OS X (tested under Tiger with Java 1.5.0_06).\n *\n * @author Maxence Bernard\n */\npublic class ClipboardNotifier implements FlavorListener {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(ClipboardNotifier.class);\n\t\n    /** The action to dynamically enable/disable */\n    private Action action;\n\n    /**\n     * Starts monitoring the clipboard for files and dynamically enable/disable the specified action accordingly.\n     * The action is initially enabled if the clipboard contains files.\n     *\n     * @param action the action to dynamically enable/disable when files are present/not present\n     */\n    public ClipboardNotifier(Action action) {\n        this.action = action;\n\n        // Toggle initial state\n        toggleActionState();\n\n        // Monitor clipboard changes\n        ClipboardSupport.getClipboard().addFlavorListener(this);\n    }\n\n\n    /**\n     * Toggle the action depending on the clipboard contents.\n     */\n    private void toggleActionState() {\n        try {\n            action.setEnabled(ClipboardSupport.getClipboard().isDataFlavorAvailable(DataFlavor.javaFileListFlavor));\n        }\n        catch(Exception e) {\n            // Works around \"java.lang.IllegalStateException: cannot open system clipboard\" thrown when the clipboard\n            // is currently unavailable (ticket #164).\n\n            LOGGER.debug(\"Caught an exception while querying the clipboard for files\", e);\n        }\n    }\n\n    ///////////////////////////////////\n    // FlavorListener implementation //\n    ///////////////////////////////////\n\n    public void flavorsChanged(FlavorEvent event) {\n        toggleActionState();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dnd/ClipboardOperations.java",
    "content": "\npackage com.mucommander.ui.dnd;\n\n/**\n *\n * @author Kezides\n */\n\n//List of possible operations to be made with the clipboard.\npublic enum ClipboardOperations {\n    CUT, COPY, ARCHIVE\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dnd/ClipboardSupport.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dnd;\n\nimport com.mucommander.commons.file.util.FileSet;\n\nimport java.awt.*;\nimport java.awt.datatransfer.Clipboard;\nimport java.awt.datatransfer.Transferable;\n\n/**\n * This class provides methods to more easily interact with the system clipboard.\n *\n * @author Maxence Bernard\n */\npublic class ClipboardSupport {\n\n    private static ClipboardOperations operation;\n        \n    public static ClipboardOperations getOperation(){\n        return operation;\n    }\n    \n    public static void setOperation(ClipboardOperations operation){\n        ClipboardSupport.operation = operation;\n    }\n\n    /**\n     * Returns the system clipboard's contents as a <code>Transferable</code>, <code>null</code>\n     * if it currently has no contents.\n     */\n    private static Transferable getClipboardContents() {\n        try {\n            return getClipboard().getContents(null);\n        } catch(IllegalStateException e) {\n            return null;\n        }\n    }\n\n    /**\n     * Sets the contents of the system clipboard.\n     *\n     * @param transferable the data to transfer to the clipboard\n     */\n    public static void setClipboardContents(Transferable transferable) {\n        try {\n            getClipboard().setContents(transferable, null);\n        } catch(IllegalStateException ignored) {}\n    }\n\n\n    /**\n     * Returns the files contained by the system clipboard as a {@link com.mucommander.commons.file.util.FileSet}, <code>null</code>\n     * if it currently has no contents or if the item(s) contained are not files.\n     */\n    public static FileSet getClipboardFiles() {\n        Transferable transferable = getClipboardContents();\n        // May return null if no file could be retrieved from the transferable instance\n        return transferable == null ? null : TransferableFileSet.getTransferFiles(transferable);\n    }\n\n    /**\n     * Transfers the files contained in the specified {@link com.mucommander.commons.file.util.FileSet} to the system clipboard.\n     * The data will be transferred as a {@link TransferableFileSet}.\n     *\n     * @param fileSet the files to transfer to the system clipboard.\n     */\n    public static void setClipboardFiles(FileSet fileSet) {\n        TransferableFileSet tfs = new TransferableFileSet(fileSet);\n\n        // Disable FileSetDataFlavor support which would otherwise throw an exception because the data is not serializable\n        tfs.setFileSetDataFlavorSupported(false);\n\n        setClipboardContents(tfs);\n    }\n\n\n    /**\n     * Returns an instance of the system clipboard.\n     */\n    public static Clipboard getClipboard() {\n        return Toolkit.getDefaultToolkit().getSystemClipboard();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dnd/DnDContext.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dnd;\n\nimport com.mucommander.ui.main.FolderPanel;\n\n/**\n * This class gives information about the context in which a drag-and-drop operation is being performed.\n * The getters are static since only one drag-and-drop operation can be performed at the same time. The information\n * returned by the getters is meaningful only when a drag-and-drop is being carried out.\n *\n * @see FileDragSourceListener\n * @author Maxence Bernard\n */\nclass DnDContext {\n\n    /** Has the drag operation been initiated by muCommander ? */\n    private static boolean dragInitiatedByMucommander;\n\n    /** FolderPanel instance which initiated the drag */\n    private static FolderPanel dragInitiator;\n\n    /** Current drag gesture modifiers */\n    private static int dragGestureModifiersEx;\n\n\n    /**\n     * Returns <code>true<code> if the current drag has been initiated by muCommander, i.e. *not* by another application.\n     * The returned value has a meaning only if a drag operation is currently being performed.\n     */\n    static boolean isDragInitiatedByMucommander() {\n        return dragInitiatedByMucommander;\n    }\n\n    /**\n     * This method is called by {@link FileDragSourceListener}.\n     */\n    static void setDragInitiatedByMucommander(boolean b) {\n        dragInitiatedByMucommander = b;\n    }\n\n\n    /**\n     * Returns the {@link FolderPanel} instance that initiated the drag operation.\n     * This method returns <code>null</code> if the current drag has not been initiated by muCommander.\n     */\n    static FolderPanel getDragInitiator() {\n        return dragInitiator;\n    }\n\n    /**\n     * This method is called by {@link FileDragSourceListener}.\n     */\n    static void setDragInitiator(FolderPanel fp) {\n        dragInitiator = fp;\n    }\n\n\n    /**\n     * Returns the extended modifiers that are currently pressed while dragging.\n     * This method returns <code>0</code> if the current drag has not been initiated by muCommander.\n     */\n    static int getDragGestureModifiersEx() {\n        return dragGestureModifiersEx;\n    }\n    \n    /**\n     * This method is called by {@link FileDragSourceListener}.\n     */\n    static void setDragGestureModifiersEx(int modifiersEx) {\n        dragGestureModifiersEx = modifiersEx;\n\n//        AppLogger.finest(\"gestureModifiersEx=\"+modifiersEx);\n//        AppLogger.finest(\"getModifiersExText=\"+ InputEvent.getModifiersExText(modifiersEx));\n//        AppLogger.finest(\"is shift down=\"+((modifiersEx&InputEvent.SHIFT_DOWN_MASK)!=0));\n//        AppLogger.finest(\"is ctrl down=\"+((modifiersEx&InputEvent.CTRL_DOWN_MASK)!=0));\n//        AppLogger.finest(\"is alt down=\"+((modifiersEx&InputEvent.ALT_DOWN_MASK)!=0));\n//        AppLogger.finest(\"is meta down=\"+((modifiersEx&InputEvent.META_DOWN_MASK)!=0));\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dnd/FileDragSourceListener.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dnd;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.main.table.FileTable;\nimport com.mucommander.ui.main.table.views.BaseFileTableModel;\n\nimport java.awt.*;\nimport java.awt.dnd.*;\nimport java.awt.event.InputEvent;\n\n\n/**\n * This class adds 'drag' support to components that are registered using the {@link #enableDrag(java.awt.Component)}\n * method.\n *\n * <p>A {@link com.mucommander.ui.main.FolderPanel} instance has to be specified at creation time, this instance will be\n * used to retrieve the list of selected/marked file(s) that are dragged, whenever a drag operation is initiated on\n * of the registered components.\n *\n * @author Maxence Bernard\n */\npublic class FileDragSourceListener implements DragGestureListener, DragSourceListener {\n\n    /** the FolderPanel instance used to retrieve dragged files */\n    private final FolderPanel folderPanel;\n\n\n    /**\n     * Creates a new FileDragSourceListener using the specified FolderPanel that will be used to retrieve the dragged files\n     * based on the current file selection.\n     *\n     * @param folderPanel the FolderPanel used to retrieve the list of selected/marked file(s) that are dragged\n     */\n    public FileDragSourceListener(FolderPanel folderPanel) {\n        this.folderPanel = folderPanel;\n    }\n\n\n    /**\n     * Enables drag operations on the specified component. This class will be notified whenever drag operations\n     * are performed on the component.\n     *\n     * @param c the component for which to add 'drag' support\n     */\n    public void enableDrag(Component c) {\n        DragSource dragSource = DragSource.getDefaultDragSource();\n        dragSource.createDefaultDragGestureRecognizer(c, DnDConstants.ACTION_COPY|DnDConstants.ACTION_MOVE|DnDConstants.ACTION_LINK, this);\n    }\n\n\n//    /**\n//     * Creates a custom DragGestureEvent instance re-using the information contained in the given DragGestureEvent, but\n//     * overridding the actions with the specified actions bitwise mask.\n//     * When used with <code>DragSource.startDrag</code>, this allows to start a drag operation with a different source\n//     * action set from the one specified in the <code>DragGestureRecognizer</code>, based on the current state and\n//     * contents of the FolderPanel.\n//     */\n//    private DragGestureEvent createCustomDragGestureEvent(DragGestureEvent originalDGE, int actions) {\n//        Vector eventList = new Vector();\n//        Iterator eventIterator = originalDGE.iterator();\n//\n//        while(eventIterator.hasNext())\n//            eventList.add(eventIterator.next());\n//\n//        DragGestureRecognizer dragGestureRecognizer = originalDGE.getSourceAsDragGestureRecognizer();\n//        dragGestureRecognizer.setSourceActions(actions);\n//\n//        return new DragGestureEvent(dragGestureRecognizer,\n//                actions,\n//                originalDGE.getDragOrigin(),\n//                eventList);\n//    }\n\n\n    /////////////////////////////////\n    // DragGestureListener methods //\n    /////////////////////////////////\n\n    public void dragGestureRecognized(DragGestureEvent event) {\n        if (folderPanel.getMainFrame().getNoEventsMode())\n            return;\n\n        FileTable fileTable = folderPanel.getFileTable();\n        BaseFileTableModel tableModel = fileTable.getFileTableModel();\n\n        // Return (do not initiate drag) if mouse button2 or button3 was used\n        if ((event.getTriggerEvent().getModifiersEx() & (InputEvent.BUTTON2_DOWN_MASK|InputEvent.BUTTON3_DOWN_MASK)) != 0)\n            return;\n\n// Do not use that to retrieve the current selected file as it is inaccurate: the selection could have changed since the mouse was clicked.\n//        AbstractFile selectedFile = fileTable.getSelectedFile(false);\n//        // Return if selected file is null (could happen if '..' is selected)\n//        if(selectedFile==null)\n//            return;\n\n        // Find out which row was clicked\n        int clickedRow = fileTable.rowAtPoint(event.getDragOrigin());\n        int clickedCol = fileTable.columnAtPoint(event.getDragOrigin());\n        int index = tableModel.getFileIndexAt(clickedRow, clickedCol);\n        // Return (do not initiate drag) if the selected file is the parent folder '..'\n        if (index < 0 || fileTable.isParentFolder(index)) {\n            return;\n        }\n\n        // Retrieve the file corresponding to the clicked row\n        AbstractFile selectedFile = tableModel.getFileAt(index);\n\n        // Find out which files are to be dragged, based on the selected file and currenlty marked files.\n        // If there are some files marked, drag marked files only if the selected file is one of the marked files.\n        // In any other case, only drag the selected file.\n        FileSet markedFiles;\n        FileSet draggedFiles;\n        if (tableModel.getNbMarkedFiles() > 0 && (markedFiles = fileTable.getSelectedFiles()).contains(selectedFile)) {\n            draggedFiles = markedFiles;\n        } else {\n            draggedFiles = new FileSet(folderPanel.getCurrentFolder(), selectedFile);\n        }\n\n        // Set initial DnDContext information\n        DnDContext.setDragInitiatedByMucommander(true);\n        DnDContext.setDragInitiator(folderPanel);\n        DnDContext.setDragGestureModifiersEx(event.getTriggerEvent().getModifiersEx());\n\n        // Start dragging\n        DragSource.getDefaultDragSource().startDrag(event, null, new TransferableFileSet(draggedFiles), this);\n//        DragSource.getDefaultDragSource().startDrag(createCustomDragGestureEvent(event, DnDConstants.ACTION_MOVE), null, new TransferableFileSet(draggedFiles), this);\n    }\n\n\n    ///////////////////////////////////////\n    // DragSourceListener implementation //\n    ///////////////////////////////////////\n\n    public void dragEnter(DragSourceDragEvent event) {\n        // Update drag gesture modifiers\n        DnDContext.setDragGestureModifiersEx(event.getGestureModifiersEx());\n    }\n\n    public void dragOver(DragSourceDragEvent event) {\n    }\n\n    public void dropActionChanged(DragSourceDragEvent event) {\n        // Update drag gesture modifiers\n        DnDContext.setDragGestureModifiersEx(event.getGestureModifiersEx());\n    }\n\n    public void dragExit(DragSourceEvent event) {\n    }\n\n    public void dragDropEnd(DragSourceDropEvent event) {\n        // Reset DnDContext information\n        DnDContext.setDragInitiatedByMucommander(false);\n        DnDContext.setDragInitiator(null);\n        DnDContext.setDragGestureModifiersEx(0);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dnd/FileDropTargetListener.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dnd;\n\nimport java.awt.Cursor;\nimport java.awt.datatransfer.DataFlavor;\nimport java.awt.dnd.DnDConstants;\nimport java.awt.dnd.DragSource;\nimport java.awt.dnd.DropTargetDragEvent;\nimport java.awt.dnd.DropTargetDropEvent;\nimport java.awt.dnd.DropTargetEvent;\nimport java.awt.dnd.DropTargetListener;\nimport java.awt.event.InputEvent;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.job.CopyJob;\nimport com.mucommander.job.MoveJob;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.file.FileCollisionDialog;\nimport com.mucommander.ui.dialog.file.ProgressDialog;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.main.MainFrame;\n\n/**\n * Provides file(s) 'drop' support to components that add a <code>DropTarget</code> using this <code>DropTargetListener</code>.\n * A {@link com.mucommander.ui.main.FolderPanel} instance has to be specified at creation time, this instance will be\n * used to change the current folder, or copy/move files to the current folder.\n *\n * <p>There are 2 different modes this class can operate in. The mode to be used has to be specified when this class is\n * instantiated.\n *\n * <p>In 'folder change mode', when a file or string representing a file path is dropped, the associated FolderPanel's\n * current folder is changed:\n * <ul>\n * <li>If the file is a directory, the current folder is changed to that directory\n * <li>For any other file kind (archive, regular file...), current folder is changed to the file's parent folder\n * and the file is selected\n * </ul>\n * If more than one file (or file path) is dropped, only the first one is taken into account.\n *\n * <p>In the normal mode, files (or file paths) that are dropped can also be moved or copied to the associated FolderPanel's\n * current folder, on top of the change current folder action. The actual drop action performed (move, copy or change current folder)\n * depends on the keyboard modifiers typed by the user when dragging the files.\n * When the mouse cursor enters the drop-enabled component's area, it is changed to symbolize the action to be performed.\n * The default drop action (when no modifier is down) is copy. \n *\n * <p>Drop events originating from the same FolderPanel are on purpose not accepted as spring-loaded folders are not\n * (yet) supported which would make the drop operation ambiguous and confusing.\n *\n * @author Maxence Bernard\n */\npublic class FileDropTargetListener implements DropTargetListener {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(FileDropTargetListener.class);\n\t\n    /** the FolderPanel instance used to change the current folder when a file is dropped */\n    private final FolderPanel folderPanel;\n\n    /** Mode that specifies what to do when files are dropped */\n    private final boolean changeFolderOnlyMode;\n\n    /** Drop action (copy or move) currenlty specified by the user */\n    private int currentDropAction;\n\n    /** Has DropTargetDragEvent event been accepted ? */\n    private boolean dragAccepted;\n\n    /**\n     * Extended modifiers which must be down while dragging for the drop action to be a MOVE and not a COPY (default):\n     * <code>InputEvent.META_DOWN_MASK</code> under Mac OS X, <code>InputEvent.ALT_DOWN_MASK</code> under any other\n     * platform.\n     */\n    private final static int MOVE_ACTION_MODIFIERS_EX = OsFamily.MAC_OS_X.isCurrent()?\n            InputEvent.META_DOWN_MASK\n            :InputEvent.ALT_DOWN_MASK;\n\n\n    /**\n     * Creates a new FileDropTargetListener using the provided FolderPanel that will be used to either change the\n     * current folder or copy/move when files are dropped, depending on the specified operating mode and drop action.\n     *\n     * @param folderPanel the FolderPanel instance used to change the current folder or copy/move when files are dropped\n     * @param changeFolderOnlyMode if <code>true</code>, the FolderPanel's current folder can only be changed when file(s)\n     * are dropped, files cannot be copied or moved.\n     */\n    public FileDropTargetListener(FolderPanel folderPanel, boolean changeFolderOnlyMode) {\n        this.folderPanel = folderPanel;\n        this.changeFolderOnlyMode = changeFolderOnlyMode;\n    }\n\n\n    /**\n     * Returns a mouse <code>Cursor<code> that symbolizes the given drop action and 'accepted' status.\n     * The given action must one of the following:\n     * <ul>\n     *  <li>DnDConstants.ACTION_COPY\n     *  <li>DnDConstants.ACTION_MOVE\n     *  <li>DnDConstants.ACTION_LINK\n     * </ul>\n     * If the action has any other value, the default Cursor is returned. \n     */\n    private Cursor getDragActionCursor(int dropAction, boolean dragAccepted) {\n        switch(dropAction) {\n            case DnDConstants.ACTION_COPY:\n                return dragAccepted?DragSource.DefaultCopyDrop:DragSource.DefaultCopyNoDrop;\n\n            case DnDConstants.ACTION_MOVE:\n                return dragAccepted?DragSource.DefaultMoveDrop:DragSource.DefaultMoveNoDrop;\n\n            case DnDConstants.ACTION_LINK:\n                return dragAccepted?DragSource.DefaultLinkDrop:DragSource.DefaultLinkNoDrop;\n\n            default:\n                return Cursor.getDefaultCursor();\n        }\n    }\n\n\n    /**\n     * Accepts or rejects the specified <code>DropTargetDragEvent</code> and changes the mouse cursor to match the\n     * current drop action.\n     * The drag event will be accepted it supports at least one of the supported DataFlavors and one of the two\n     * following conditions are true:\n     * <ul>\n     * <li>the event originates from one of muCommander's {@link FolderPanel} for which the current folder is not the\n     * same as the FolderPanel associated with this <code>FileDropTargetListener</code>\n     * <li>the event does not originate from muCommander\n     * </ul>\n     *\n     * <p>This method overrides the default drop action for drag-and-drop operations within muCommander to make it\n     * <code>DnDConstants.ACTION_COPY</code> instead of <code>DnDConstants.ACTION_MOVE</code>.\n     * For a move action to be performed when the mouse is released, the modifiers defined by\n     * {@link #MOVE_ACTION_MODIFIERS_EX} must be down.\n     *\n     * @return <code>true</code> if the event was accepted, false otherwise\n     */\n    private boolean acceptOrRejectDragEvent(DropTargetDragEvent event) {\n        this.currentDropAction = event.getDropAction();\n\n        this.dragAccepted = event.isDataFlavorSupported(TransferableFileSet.getFileSetDataFlavor())\n                || event.isDataFlavorSupported(DataFlavor.javaFileListFlavor)\n                || event.isDataFlavorSupported(DataFlavor.getTextPlainUnicodeFlavor());\n\n        if(dragAccepted && DnDContext.isDragInitiatedByMucommander()) {\n            FolderPanel dragInitiator = DnDContext.getDragInitiator();\n\n            if(dragInitiator==folderPanel || dragInitiator.getCurrentFolder().equalsCanonical(folderPanel.getCurrentFolder())) {\n                // Refuse drag if the drag was initiated by the same FolderPanel, or if its current folder is the same\n                // as this one\n                this.dragAccepted = false;\n            }\n            else {\n                // Change the default drop action to DnDConstants.ACTION_COPY instead of DnDConstants.ACTION_MOVE,\n                // if the move extended modifiers are not currently down.\n                int dragModifiers = DnDContext.getDragGestureModifiersEx();\n\n                if(currentDropAction==DnDConstants.ACTION_MOVE\n                        && (dragModifiers&MOVE_ACTION_MODIFIERS_EX)==0\n                        && (event.getSourceActions()&DnDConstants.ACTION_COPY)!=0) {\n                    LOGGER.debug(\"changing default action, was: DnDConstants.ACTION_MOVE, now: DnDConstants.ACTION_COPY\");\n                    currentDropAction = DnDConstants.ACTION_COPY;\n                }\n            }\n        }\n\n        LOGGER.trace(\"dragAccepted=\"+dragAccepted+\" dropAction=\"+currentDropAction);\n\n        if (dragAccepted) {\n            // Accept the drag event with our drop action\n            event.acceptDrag(currentDropAction);\n        } else {\n            // Reject the drag event\n            event.rejectDrag();\n        }\n\n        LOGGER.trace(\"cursor=\"+getDragActionCursor(currentDropAction, dragAccepted));\n\n        // Change the mouse cursor on this FolderPanel and child components\n        folderPanel.getPanel().setCursor(getDragActionCursor(currentDropAction, dragAccepted));\n\n        return dragAccepted;\n    }\n\n\n    @Override\n    public void dragEnter(DropTargetDragEvent event) {\n        acceptOrRejectDragEvent(event);\n    }\n\n\n    public void dragOver(DropTargetDragEvent event) {\n        // Although it doesn't look necessary, cursor needs to be set each time this method is called otherwise\n        // it returns to the default one (at least under Mac OS X w/ Java 1.5)\n        acceptOrRejectDragEvent(event);\n    }\n\n\n    public void dropActionChanged(DropTargetDragEvent event) {\n        acceptOrRejectDragEvent(event);\n    }\n\n\n    public void dragExit(DropTargetEvent event) {\n        // Restore default cursor\n        folderPanel.getPanel().setCursor(Cursor.getDefaultCursor());\n    }\n\n\n    public void drop(DropTargetDropEvent event) {\n        // Restore default cursor, no matter what\n        folderPanel.getPanel().setCursor(Cursor.getDefaultCursor());\n\n        // The drop() method is called even if a DropTargetDropEvent was rejected before,\n        // so this test is really necessary\n        if (!dragAccepted) {\n            event.rejectDrop();\n            return;\n        }\n\n        // Accept drop event\n        event.acceptDrop(currentDropAction);\n\n        // Retrieve the files contained by the transferable as a FileSet (takes care of handling the different DataFlavors)\n        FileSet droppedFiles = TransferableFileSet.getTransferFiles(event.getTransferable());\n\n        // Stop and report failure if no file could not be retrieved\n        if (droppedFiles == null || droppedFiles.size() == 0) {\n            // Report drop failure\n            event.dropComplete(false);\n            return;\n        }\n\n        // If in 'change folder mode' or if the drop action is 'ACTION_LINK' in normal mode:\n        // change the FolderPanel's current folder to the dropped file/folder :\n        // - If the file is a directory, the current folder is changed to that directory\n        // - For any other file kind (archive, regular file...), current folder is changed to the file's parent folder and the file is selected\n        // If more than one file is dropped, only the first one is used\n        if (changeFolderOnlyMode || currentDropAction == DnDConstants.ACTION_LINK) {\n            AbstractFile file = droppedFiles.elementAt(0);\n\n            // If file is a directory, change current folder to that directory\n            if (file.isDirectory())\n                folderPanel.tryChangeCurrentFolder(file);\n            // For any other file kind (archive, regular file...), change directory to the file's parent folder\n            // and select the file\n            else\n                folderPanel.tryChangeCurrentFolder(file.getParent(), file, false);\n\n            // Request focus on the FolderPanel\n            folderPanel.getPanel().requestFocus();\n        }\n        // Normal mode: copy or move dropped files to the FolderPanel's current folder\n        else {\n            MainFrame mainFrame = folderPanel.getMainFrame();\n            AbstractFile destFolder = folderPanel.getCurrentFolder();\n            if (currentDropAction == DnDConstants.ACTION_MOVE) {\n                // Start moving files\n                ProgressDialog progressDialog = new ProgressDialog(mainFrame, Translator.get(\"move_dialog.moving\"));\n                MoveJob moveJob = new MoveJob(progressDialog, mainFrame, droppedFiles, destFolder, null, FileCollisionDialog.ASK_ACTION, false);\n                progressDialog.start(moveJob);\n            }\n            else {\n                // Start copying files\n                ProgressDialog progressDialog = new ProgressDialog(mainFrame, Translator.get(\"copy_dialog.copying\"));\n                CopyJob job = new CopyJob(progressDialog, mainFrame, droppedFiles, destFolder, null, CopyJob.Mode.COPY, FileCollisionDialog.ASK_ACTION);\n                progressDialog.start(job);\n            }\n        }\n\n        // Report that the drop event has been successfully handled\n        event.dropComplete(true);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dnd/TransferableFileSet.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.dnd;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.FileProtocols;\nimport com.mucommander.commons.file.util.FileSet;\nimport lombok.Setter;\nimport org.jetbrains.annotations.NotNull;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.awt.datatransfer.DataFlavor;\nimport java.awt.datatransfer.Transferable;\nimport java.awt.datatransfer.UnsupportedFlavorException;\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Vector;\n\n/**\n * This class represents a Transferable file set and is used for Drag and Drop transfers initiated by muCommander\n * (dragged from a muCommander UI component).\n *\n * <p>The actual file set data can be fetched using one of those 3 DataFlavors :\n * <ul>\n *   <li>FileSetDataFlavor (as returned by {@link #getFileSetDataFlavor()}): data returned as a {@link com.mucommander.commons.file.util.FileSet}.\n * This flavor is used for local file transfers (within the application) only. In particular, this DataFlavor cannot\n * be used to transfer data to the clipboard because the data (FileSet) cannot be serialized.\n * In this case, the {@link #setFileSetDataFlavorSupported(boolean)} method should be used to disable FileSet DataFlavor.\n *   </li>\n *   <li>DataFlavor.javaFileListFlavor : data returned as a java.util.Vector of <code>java.io.File</code> files.\n * This flavor is used for file transfers to and from external applications.\n *   </li>\n *   <li>text/uri-list (RFC 2483): an alternative flavor supported by Gnome and KDE where the data is returned as\n * a String containing file URIs separated by '\\r\\n'. This flavor is as of today the only one supported by GNOME and\n * KDE. See bug #45 and http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4899516 .\n *   </li>\n *   <li>DataFlavor.stringFlavor: data returned as a String containing files paths separated by \\n characters.\n * This other alternative flavor is used for file transfers to and from external applications that do not support\n * either of DataFlavor.javaFileListFlavor and text/uri-list but text only (plain text editors for example).\n *   </li>\n * </ul>\n *\n * @author Maxence Bernard, Xavi Miró\n */\npublic class TransferableFileSet implements Transferable {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(TransferableFileSet.class);\n\t\n    /** Transferred FileSet */\n    private final FileSet fileSet;\n\n    /** Is FileSet DataFlavor supported ? */\n    private boolean fileSetFlavorSupported = true;\n\n    /** Is DataFlavor.javaFileListFlavor supported ? */\n    private boolean javaFileListFlavorSupported = true;\n\n    /** Is DataFlavor.stringFlavor supported ? */\n    private boolean stringFlavorSupported = true;\n\n    /** Is text/uri-list (RFC 2483) flavor supported ?\n     * -- SETTER --\n     *  Sets whether the <code>text/uri-list</code> (RFC 2483) should be supported by this Transferable\n     *  (supported by default).\n     */\n    @Setter\n    private boolean textUriFlavorSupported = true;\n\n    /** Does DataFlavor.stringFlavor transfer the files' full paths or filenames only ? */\n    private boolean stringFlavourTransfersFilename = false;\n\n    /** Does DataFlavor.stringFlavor transfer the files' filenames with extension or without ? */\n    private boolean stringFlavourTransfersFileBaseName = false;\n    \n\t/** DataFlavor used for GNOME/KDE transfers */\n    private static DataFlavor TEXT_URI_FLAVOR;\n\n    /** Custom FileSet DataFlavor used for local transfers */\n    private static DataFlavor FILE_SET_DATA_FLAVOR;\n\n    static {\n        // create a single custom DataFlavor instance that designates the FileSet class to transfer data\n        try {\n            FILE_SET_DATA_FLAVOR = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType+\";class=\"+FileSet.class.getName());\n\t        TEXT_URI_FLAVOR =  new DataFlavor(\"text/uri-list;class=\"+String.class.getName());\n        }\n        catch(ClassNotFoundException e) {\n            // That should never happen\n            LOGGER.debug(\"FileSet DataFlavor could not be instantiated\", e);\n        }\n    }\n\n\n    /**\n     * Creates a new Transferable file set with support for all DataFlavors enabled.\n     *\n     * @param fileSet the files to be transferred\n     */\n    public TransferableFileSet(FileSet fileSet) {\n        this.fileSet = fileSet;\n    }\n\n\n    /**\n     * Sets whether the FileSet <code>DataFlavor</code> (as returned by {@link #getFileSetDataFlavor()}\n     * should be supported by this Transferable (supported by default).\n     *\n     * @param supported <code>true</code> to support the flavor\n     */\n    public void setFileSetDataFlavorSupported(boolean supported) {\n        this.fileSetFlavorSupported = supported;\n    }\n\n    /**\n     * Sets whether the <code>DataFlavor.javaFileListFlavor</code> should be supported by this Transferable\n     * (supported by default).\n     *\n     * @param supported <code>true</code> to support the flavor\n     */\n    public void setJavaFileListDataFlavorSupported(boolean supported) {\n        this.javaFileListFlavorSupported = supported;\n    }\n\n    /**\n     * Sets whether the <code>DataFlavor.stringFlavor</code> should be supported by this Transferable\n     * (supported by default).\n     *\n     * @param supported <code>true</code> to support the flavor\n     */\n    public void setStringDataFlavorSupported(boolean supported) {\n        this.stringFlavorSupported = supported;\n    }\n\n    /**\n     * Sets whether the files' full path or just the filenames should be returned when\n     * {@link #getTransferData(java.awt.datatransfer.DataFlavor)} is called with <code>DataFlavor.stringFlavor</code>.\n     * (*not* enabled by default)\n     *\n     * @param b if <code>true</code>, DataFlavor.stringFlavor returns filenames only, full file paths otherwise.\n     */\n    public void setStringDataFlavourTransfersFilename(boolean b) {\n        this.stringFlavourTransfersFilename = b;\n    }\n\n    /**\n     * Returns whether the files' full path or just the filenames will be returned when\n     * {@link #getTransferData(java.awt.datatransfer.DataFlavor)} is called with <code>DataFlavor.stringFlavor</code>.\n     * Returns <code>false</code> unless {@link #setStringDataFlavourTransfersFilename(boolean)} has been called.\n     *\n     * @return whether the files' full path or just the filenames will be returned when\n     * {@link #getTransferData(java.awt.datatransfer.DataFlavor)} is called with <code>DataFlavor.stringFlavor</code>\n     */\n    public boolean getStringDataFlavourTransfersFilename() {\n        return this.stringFlavourTransfersFilename;\n    }\n\n    /**\n     * Sets whether the files' base name (without file extension) should be returned when\n     * {@link #getTransferData(java.awt.datatransfer.DataFlavor)} is called with <code>DataFlavor.stringFlavor</code>.\n     * (*not* enabled by default)\n     *\n     * @param b if <code>true</code>, DataFlavor.stringFlavor returns filenames without extension, full file name otherwise.\n     */\n\tpublic void setStringDataFlavourTransfersFileBaseName(boolean b) {\n\t\tthis.stringFlavourTransfersFileBaseName = b;\n\t}\n\n    /**\n     * Returns whether the files' base name (without file extension) should be returned when\n     * {@link #getTransferData(java.awt.datatransfer.DataFlavor)} is called with <code>DataFlavor.stringFlavor</code>.\n     * Returns <code>false</code> unless {@link #setStringDataFlavourTransfersFileBaseName(boolean)} has been called.\n     *\n     * @return whether the files'  base name (without file extension) should be returned when\n     * {@link #getTransferData(java.awt.datatransfer.DataFlavor)} is called with <code>DataFlavor.stringFlavor</code>\n     */\n    public boolean getStringDataFlavourTransfersFileBaseName() {\n\t\treturn stringFlavourTransfersFileBaseName;\n\t}\n\n    /**\n     * Returns an instance of the custom FileSet DataFlavor used to transfer files locally.\n     *\n     * @return an instance of the custom FileSet DataFlavor used to transfer files locally\n     */\n    static DataFlavor getFileSetDataFlavor() {\n        return FILE_SET_DATA_FLAVOR;\n    }\n\n\n    /**\n     * Returns the files contained by the specified Transferable as a {@link com.mucommander.commons.file.util.FileSet},\n     * or <code>null</code> if no file was present in the Transferable or if an error occurred.\n     *\n     * <p>3 types of dropped data flavors are supported and used in this order of priority:\n     * <ul>\n     * <li>FileSet: the local DataFlavor used when files are transferred from muCommander\n     * <li>File list: used when files are transferred from an external application\n     * <li>File paths: alternate flavor used when some text representing one or several file paths is dragged\n     * from an external application\n     * </ul>\n     *\n     * @param transferable a Transferable instance that contains the files to be retrieved\n     * @return the files contained by the specified Transferable as a FileSet, or <code>null</code> if no file\n     * was present or if an error occurred\n     */\n    static FileSet getTransferFiles(Transferable transferable) {\n        FileSet files;\n        AbstractFile file;\n\n        try {\n            // FileSet DataFlavor\n            if (transferable.isDataFlavorSupported(FILE_SET_DATA_FLAVOR)) {\n                files = (FileSet)transferable.getTransferData(FILE_SET_DATA_FLAVOR);\n            }\n            // File list DataFlavor\n            else if(transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {\n                @SuppressWarnings(\"unchecked\")\n                List<File> fileList = (List<File>)transferable.getTransferData(DataFlavor.javaFileListFlavor);\n\n                files = new FileSet();\n                for (File aFileList : fileList) {\n                    file = FileFactory.getFile(aFileList.getAbsolutePath());\n\n                    if (file != null) {\n                        files.add(file);\n                    }\n                }\n            }\n            // Text plain DataFlavor: assume that lines designate file paths\n            else if (transferable.isDataFlavorSupported(DataFlavor.stringFlavor)) {\n                try (BufferedReader br = new BufferedReader(DataFlavor.getTextPlainUnicodeFlavor().getReaderForText(transferable))) {\n                    // Read input line by line and try to create AbstractFile instances\n                    files = new FileSet();\n                    String path;\n                    while( (path = br.readLine()) != null) {\n                        // Try to create an AbstractFile instance, returned instance may be null\n                        file = FileFactory.getFile(path);\n\n                        // Safety precaution: if at least one line doesn't resolve as a file, stop reading\n                        // and return null. This is to avoid any nasty effect that could arise if a random\n                        // piece of text (let's say an email contents) was inadvertently pasted or dropped to muCommander.\n                        if (file == null) {\n                            return null;\n                        }\n\n                        files.add(file);\n                    }\n\n                }\n            } else {\n                return null;\n            }\n        } catch (Exception e) {\n            // Catch UnsupportedFlavorException, IOException\n            LOGGER.debug(\"Caught exception while processing transferable\", e);\n\n            return  null;\n        }\n\n        return files;\n    }\n\n\n    @Override\n    public DataFlavor[] getTransferDataFlavors() {\n        List<DataFlavor> supportedDataFlavorsV = new ArrayList<>();\n\n        if(fileSetFlavorSupported)\n            supportedDataFlavorsV.add(FILE_SET_DATA_FLAVOR);\n\n        if(javaFileListFlavorSupported)\n            supportedDataFlavorsV.add(DataFlavor.javaFileListFlavor);\n\n        if(stringFlavorSupported)\n            supportedDataFlavorsV.add(DataFlavor.stringFlavor);\n\n        if(textUriFlavorSupported)\n            supportedDataFlavorsV.add(TEXT_URI_FLAVOR);\n\n        DataFlavor[] supportedDataFlavors = new DataFlavor[supportedDataFlavorsV.size()];\n        supportedDataFlavorsV.toArray(supportedDataFlavors);\n\n        return supportedDataFlavors;\n    }\n\n    @Override\n    public boolean isDataFlavorSupported(DataFlavor dataFlavor) {\n        if(dataFlavor.equals(FILE_SET_DATA_FLAVOR))\n            return fileSetFlavorSupported;\n        else if(dataFlavor.equals(DataFlavor.javaFileListFlavor))\n            return javaFileListFlavorSupported;\n        else if(dataFlavor.equals(DataFlavor.stringFlavor))\n            return stringFlavorSupported;\n        else if(dataFlavor.equals(TEXT_URI_FLAVOR))\n            return textUriFlavorSupported;\n\n        return false;\n    }\n\n    @NotNull\n    @Override\n    public Object getTransferData(DataFlavor dataFlavor) throws UnsupportedFlavorException {\n        int nbFiles = fileSet.size();\n\n        // Return files stored in a FileSet instance (the one that was passed to the constructor)\n        if(dataFlavor.equals(FILE_SET_DATA_FLAVOR) && fileSetFlavorSupported) {\n            return fileSet;\n        }\n        // Return files stored in a java.util.Vector instance\n        else if(dataFlavor.equals(DataFlavor.javaFileListFlavor) && javaFileListFlavorSupported) {\n            List<File> fileList = new Vector<>(nbFiles);\n\n            for(int i=0; i<nbFiles; i++) {\n                AbstractFile file = fileSet.elementAt(i);\n                fileList.add(new File(file.getAbsolutePath()));\n            }\n            return fileList;\n        }\n//        // Return an InputStream formatted in a specified Unicode charset that contains file paths separated by '\\n' characters\n//        else if(dataFlavor.equals(DataFlavor.getTextPlainUnicodeFlavor())) {\n//            String mimeType = dataFlavor.getMimeType();\n//            String charset = mimeType.substring(mimeType.indexOf(\"charset=\")+8, mimeType.length());\n//\n//            StringBuilder sb = new StringBuilder();\n//            for(int i=0; i<nbFiles; i++) {\n//                sb.append(fileSet.fileAt(i).getAbsolutePath());\n//                if(i!=nbFiles-1)\n//                    sb.append('\\n');\n//            }\n//\n//            return new ByteArrayInputStream(sb.toString().getBytes(charset));\n//        }\n        // Return a String with file paths or names\n        else if(dataFlavor.equals(DataFlavor.stringFlavor) && stringFlavorSupported) {\n            StringBuilder sb = new StringBuilder();\n            AbstractFile file;\n            for(int i=0; i<nbFiles; i++) {\n                file = fileSet.elementAt(i);\n                // Check if to return string with filenames\n                if (stringFlavourTransfersFilename) {\n                \t// Append just base name or file name with extension\n                \tsb.append(stringFlavourTransfersFileBaseName ? file.getBaseName() : file.getName());\n                } else {\n                \tsb.append(file.getAbsolutePath());\n                }\n                                \t\n                if(i!=nbFiles-1)\n                    sb.append('\\n');\n            }\n\n            return sb.toString();\n        }\n        // Return a String with file URLs, as specified by RFC 2483\n        else if(dataFlavor.equals(TEXT_URI_FLAVOR) && textUriFlavorSupported) {\n            StringBuilder sb = new StringBuilder();\n            AbstractFile file;\n            for(int i = 0; i < nbFiles; i++) {\n                file = fileSet.elementAt(i);\n                String url = file.getURL().getScheme().equals(FileProtocols.FILE)\n                    // Use java.io.File.toURI() for local files (e.g. file:/mnt/music), this is the format expected by\n                    // Gnome/Nautilus.\n                    ?new File(file.getAbsolutePath()).toURI().toString()\n                    // Use standard URL format (e.g. smb://host/share/file) for other file types\n                    :file.getAbsolutePath();\n\n                sb.append(url);\n                if (i != nbFiles-1){\n                    sb.append(\"\\r\\n\");\n                }\n            }\n\n            return sb.toString();\n        }\n        // Any other requested DataFlavor will thrown an UnsupportedFlavorException\n        else {\n            throw new UnsupportedFlavorException(dataFlavor);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/dnd/package.html",
    "content": "<body>\n  Drag and Drop API.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/encoding/EncodingListener.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.encoding;\n\n/**\n * Interface to be implemented by classes that wish to be notified of character encoding selections that occurred\n * in an {@link EncodingMenu}.\n *\n * @author Maxence Bernard\n * @see EncodingMenu\n */\npublic interface EncodingListener {\n\n    /**\n     * Called when the currently selected encoding has changed.\n     *\n     * @param source component in which the event occurred\n     * @param oldEncoding previously selected encoding\n     * @param newEncoding newly selected encoding\n     */\n    void encodingChanged(Object source, String oldEncoding, String newEncoding);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/encoding/EncodingMenu.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.encoding;\n\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.DialogOwner;\nimport ru.trolsoft.ui.TMenuSeparator;\nimport ru.trolsoft.ui.TRadioButtonMenuItem;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.util.WeakHashMap;\n\n/**\n * This menu lets the user choose a character encoding among a list of {@link EncodingPreferences#getPreferredEncodings()\n * preferred encodings}.\n * The menu contains a checkbox menu item for each of the preferred encodings, and a special item that invokes a dialog\n * that allows the list of preferred encodings to be customized.\n *\n * @see EncodingPreferences\n * @author Maxence Bernard\n */\npublic class EncodingMenu extends JMenu {\n\n    /** Contains all registered encoding listeners, stored as weak references */\n    protected final WeakHashMap<EncodingListener, ?> listeners = new WeakHashMap<>();\n\n    /** the dialog/frame that owns this component */\n    private final DialogOwner dialogOwner;\n\n    /** The encoding that is currently selected, may be null */\n    private String selectedEncoding;\n\n\n    /**\n     * Creates a new <code>EncodingMenu</code> with no encoding selected.\n     *\n     * @param dialogOwner the frame that owns this menu\n     */\n    public EncodingMenu(DialogOwner dialogOwner) {\n        this(dialogOwner, null);\n    }\n\n    /**\n     * Creates a new <code>EncodingMenu</code> with the specified encoding initially selected (may be <code>null</code>).\n     * If the encoding is not one of the preferred encodings, it is added as the first encoding in the menu.\n     *\n     * @param dialogOwner the frame that owns this menu\n     * @param selectedEncoding the encoding initially selected, <code>null</code> for none\n     */\n    public EncodingMenu(final DialogOwner dialogOwner, String selectedEncoding) {\n        super(Translator.get(\"encoding\"));\n\n        this.dialogOwner = dialogOwner;\n        this.selectedEncoding = selectedEncoding;\n\n        populateMenu();\n    }\n\n\n    /**\n     * Adds a checkbox menu item for each of the preferred encodings, and a special item that invokes a dialog\n     * that allows the list of preferred encodings to be customized.\n     */\n    private void populateMenu() {\n        java.util.List<String> encodings = EncodingPreferences.getPreferredEncodings();\n\n        // Add the current encoding if it is not in the list of preferred encodings\n        if (selectedEncoding != null && !encodings.contains(selectedEncoding)) {\n            encodings.add(0, selectedEncoding);\n        }\n\n        addPreferredEncodings(encodings);\n        add(new TMenuSeparator());\n        addCustomizeMenu();\n\n    }\n\n\n    private void addPreferredEncodings(java.util.List<String> encodings) {\n        ButtonGroup group = new ButtonGroup();\n        for (String enc: encodings) {\n            JMenuItem item = new TRadioButtonMenuItem(enc);\n\n            // Select the current encoding, if there is one\n            if (selectedEncoding != null && selectedEncoding.equals(enc)) {\n                item.setSelected(true);\n            }\n\n            // Listen to checkbox actions\n            item.addActionListener(e -> {\n                String oldEncoding = selectedEncoding;\n                selectedEncoding = ((JMenuItem)e.getSource()).getText();\n                if (!oldEncoding.equals(selectedEncoding)) {\n                    // Notify listeners of the new encoding\n                    fireEncodingListener(oldEncoding, EncodingMenu.this.selectedEncoding);\n                }\n            });\n\n            group.add(item);\n            add(item);\n        }\n    }\n\n    private void addCustomizeMenu() {\n        // 'Customize' menu item\n        JMenuItem customizeItem = new JMenuItem(Translator.get(\"customize\") + \"...\");\n        customizeItem.addActionListener(e -> {\n            Window owner = dialogOwner.getOwner();\n            if (owner instanceof Frame) {\n                new PreferredEncodingsDialog((Frame) owner).showDialog();\n            } else {\n                new PreferredEncodingsDialog((Dialog) owner).showDialog();\n            }\n\n            removeAll();\n            populateMenu();\n        });\n        add(customizeItem);\n    }\n\n    /**\n     * Returns the encoding that is currently selected, <code>null</code> if none is selected.\n     *\n     * @return the encoding that is currently selected, <code>null</code> if none is selected.\n     */\n    public String getSelectedEncoding() {\n        return selectedEncoding;\n    }\n\n\n    public void addEncodingListener(EncodingListener listener) {\n        synchronized (listeners) {\n            listeners.put(listener, null);\n        }\n    }\n\n    public void removeEncodingListener(EncodingListener listener) {\n        synchronized(listeners) {\n            listeners.remove(listener);\n        }\n    }\n\n    private void fireEncodingListener(String oldEncoding, String newEncoding) {\n        synchronized(listeners) {\n            for (EncodingListener listener : listeners.keySet()) {\n                listener.encodingChanged(this, oldEncoding, newEncoding);\n            }\n        }\n\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/encoding/EncodingPreferences.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.encoding;\n\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\n\nimport java.nio.charset.Charset;\nimport java.util.List;\nimport java.util.Vector;\n\n/**\n * This class allows to retrieve and set character encoding preferences. It is used by UI components that let the user\n * choose an encoding, thus limiting this list to a reasonable amount of choices instead of displaying all supported\n * character encodings.\n *\n * @see EncodingMenu\n * @see EncodingSelectBox\n * @author Maxence Bernard\n */\npublic class EncodingPreferences {\n\n    /** Default list of preferred encodings, comma-separated. */\n    public static final String[] DEFAULT_PREFERRED_ENCODINGS = new String[] {\n        \"UTF-8\",\n        \"UTF-16\",\n        \"ISO-8859-1\",\n        \"windows-1251\",\n        \"windows-1252\",\n        \"KOI8-R\",\n        \"Big5\",\n        \"GB18030\",\n        \"EUC-KR\",\n        \"Shift_JIS\",\n        \"ISO-2022-JP\",\n        \"EUC-JP\",\n        \"cp866\"\n    };\n\n\n    /**\n     * Returns a user-defined list of preferred encodings.\n     *\n     * @return a user-defined list of preferred encodings.\n     */\n    public static List<String> getPreferredEncodings() {\n        List<String> vector = TcConfigurations.getPreferences().getListVariable(TcPreference.PREFERRED_ENCODINGS, \",\");\n        if(vector==null) {\n            vector = getDefaultPreferredEncodings();\n            TcConfigurations.getPreferences().setVariable(TcPreference.PREFERRED_ENCODINGS, vector, \",\");\n        }\n\n        return vector;\n    }\n\n    /**\n     * Returns a default list of preferred encodings, containing some of the most popular encodings.\n     *\n     * @return a default list of preferred encodings.\n     */\n    public static List<String> getDefaultPreferredEncodings() {\n        List<String> encodingsV = new Vector<>();\n        for (String encoding : DEFAULT_PREFERRED_ENCODINGS) {\n            // Ensure that the encoding is supported before adding it\n            if (Charset.isSupported(encoding))\n                encodingsV.add(encoding);\n        }\n\n        return encodingsV;\n    }\n\n    /**\n     * Sets the user-defined list of preferred encodings.\n     *\n     * @param encodings the user-defined list of preferred encodings\n     */\n    public static void setPreferredEncodings(List<String> encodings) {\n    \tTcConfigurations.getPreferences().setVariable(TcPreference.PREFERRED_ENCODINGS, encodings, \",\");\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/encoding/EncodingSelectBox.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.encoding;\r\n\r\nimport java.awt.BorderLayout;\r\nimport java.awt.Dialog;\r\nimport java.awt.Frame;\r\nimport java.awt.Window;\r\nimport java.util.List;\r\nimport java.util.WeakHashMap;\r\n\r\nimport javax.swing.JButton;\r\nimport javax.swing.JPanel;\r\n\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.ui.combobox.SaneComboBox;\r\nimport com.mucommander.ui.dialog.DialogOwner;\r\n\r\n/**\r\n * This compound component lets the user choose a character encoding among a list of {@link EncodingPreferences#getPreferredEncodings()\r\n * preferred encodings} using a combo box, and customize the list of preferred encodings when the 'customize' button\r\n * is pressed.\r\n\r\n * @author Maxence Bernard\r\n */\r\npublic class EncodingSelectBox extends JPanel {\r\n\r\n    /** Allows the encoding to be selected */\r\n    protected SaneComboBox<String> comboBox;\r\n\r\n    /** Button that invokes the dialog that allows to customize the list of preferred encodings */\r\n    protected JButton customizeButton;\r\n\r\n    /** Contains all registered encoding listeners, stored as weak references */\r\n    protected final WeakHashMap<EncodingListener, ?> listeners = new WeakHashMap<>();\r\n\r\n    /** The encoding that is currently selected, may be null */\r\n    protected String currentEncoding;\r\n\r\n\r\n    /**\r\n     * Creates a new <code>EncodingSelectBox</code> with no specific encoding initially selected.\r\n     *\r\n     * @param dialogOwner the dialog/frame that owns this component\r\n     */\r\n    public EncodingSelectBox(DialogOwner dialogOwner) {\r\n        this(dialogOwner, null);\r\n    }\r\n\r\n    /**\r\n     * Creates a new <code>EncodingSelectBox</code> with the specified encoding initially selected.\r\n     * The encoding must be one of the preferred encodings, or <code>null</code>. In the latter case, the first encoding\r\n     * will be selected. \r\n     *\r\n     * @param dialogOwner the dialog/frame that owns this component\r\n     * @param selectedEncoding the encoding that will be initially selected, <code>null</code> for the first preferred\r\n     * encoding\r\n     */\r\n    public EncodingSelectBox(final DialogOwner dialogOwner, String selectedEncoding) {\r\n        super(new BorderLayout());\r\n\r\n        comboBox = new SaneComboBox<>();\r\n        populateComboBox(selectedEncoding);\r\n\r\n        comboBox.addActionListener(e -> {\r\n                String oldEncoding = currentEncoding;\r\n                currentEncoding = (String)comboBox.getSelectedItem();\r\n\r\n            if (currentEncoding == null || !currentEncoding.equals(oldEncoding)) {\r\n                    // Notify listeners of the new encoding\r\n                    fireEncodingListener(oldEncoding, EncodingSelectBox.this.currentEncoding);\r\n                }\r\n        });\r\n\r\n        add(comboBox, BorderLayout.CENTER);\r\n\r\n        // Customize button\r\n        customizeButton = new JButton(\"...\");\r\n        // Mac OS X: small component size\r\n        if (OsFamily.MAC_OS_X.isCurrent()) {\r\n            customizeButton.putClientProperty(\"JComponent.sizeVariant\", \"small\");\r\n        }\r\n\r\n        customizeButton.addActionListener(e -> {\r\n            String selectedEncoding1 = getSelectedEncoding();\r\n                Window owner = dialogOwner.getOwner();\r\n            if (owner instanceof Frame) {\r\n                new PreferredEncodingsDialog((Frame) owner).showDialog();\r\n            } else {\r\n                new PreferredEncodingsDialog((Dialog) owner).showDialog();\r\n            }\r\n\r\n                comboBox.removeAllItems();\r\n            populateComboBox(selectedEncoding1);\r\n        });\r\n\r\n        add(customizeButton, BorderLayout.EAST);\r\n    }\r\n\r\n    /**\r\n     * Adds a checkbox menu item for each of the preferred encodings, and a special item that invokes a dialog\r\n     * that allows the list of preferred encodings to be customized.\r\n     *\r\n     * @param selectEncoding the encoding that will be selected, <code>null</code> for the first one\r\n     */\r\n    protected void populateComboBox(String selectEncoding) {\r\n        List<String> encodings = EncodingPreferences.getPreferredEncodings();\r\n\r\n        // Ignore the specified encoding if it is not in the list of preferred encodings\r\n        if (selectEncoding != null && !encodings.contains(selectEncoding)) {\r\n            selectEncoding = null;\r\n        }\r\n\r\n        // Add preferred encodings to the combo box\r\n        for (String encoding : encodings) {\r\n            comboBox.addItem(encoding);\r\n        }\r\n\r\n        if (selectEncoding != null) {\r\n            comboBox.setSelectedItem(selectEncoding);\r\n        } else if (!encodings.isEmpty()) {\r\n            comboBox.setSelectedItem(encodings.get(0));\r\n        }\r\n        currentEncoding = selectEncoding;\r\n    }\r\n\r\n    /**\r\n     * Returns the encoding that is currently selected, <code>null</code> if none is selected.\r\n     *\r\n     * @return the encoding that is currently selected, <code>null</code> if none is selected.\r\n     */\r\n    public String getSelectedEncoding() {\r\n        int index = comboBox.getSelectedIndex();\r\n        return index < 0 ? null : comboBox.getItemAt(index);\r\n    }\r\n    \r\n\r\n    public void addEncodingListener(EncodingListener listener) {\r\n        synchronized(listeners) {\r\n            listeners.put(listener, null);\r\n        }\r\n    }\r\n\r\n    public void removeEncodingListener(EncodingListener listener) {\r\n        synchronized(listeners) {\r\n            listeners.remove(listener);\r\n        }\r\n    }\r\n\r\n    protected void fireEncodingListener(String oldEncoding, String newEncoding) {\r\n        synchronized(listeners) {\r\n            for (EncodingListener listener : listeners.keySet())\r\n                listener.encodingChanged(this, oldEncoding, newEncoding);\r\n        }\r\n\r\n    }\r\n\r\n    @Override\r\n    public void setEnabled(boolean enabled) {\r\n        comboBox.setEnabled(enabled);\r\n        customizeButton.setEnabled(enabled);\r\n\r\n        super.setEnabled(enabled);\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/encoding/PreferredEncodingsDialog.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.encoding;\n\nimport java.awt.BorderLayout;\nimport java.awt.Container;\nimport java.awt.Dialog;\nimport java.awt.Dimension;\nimport java.awt.FlowLayout;\nimport java.awt.Frame;\nimport java.awt.event.WindowAdapter;\nimport java.awt.event.WindowEvent;\nimport java.nio.charset.Charset;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Vector;\n\nimport javax.swing.JButton;\nimport javax.swing.JCheckBox;\nimport javax.swing.JLabel;\nimport javax.swing.JPanel;\nimport javax.swing.JScrollPane;\n\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.ui.dialog.FocusDialog;\nimport com.mucommander.ui.layout.YBoxPanel;\n\n/**\n * This dialog allows the list of preferred character encodings to be modified by the end user. Each of the supported\n * encodings are represented as a checkbox and can individually be selected/unselected. A 'revert to defaults' button\n * allows the {@link EncodingPreferences#getDefaultPreferredEncodings() default preferred encodings} to be used.\n *\n * @see EncodingPreferences \n * @author Maxence Bernard\n */\npublic class PreferredEncodingsDialog extends FocusDialog {\n\n    /** Contains all the checkbox added to this dialog */\n    private List<JCheckBox> checkboxes;\n\n    /** Minimum dimensions of this dialog */\n    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(300, 0);\n\n    /** Maximum dimensions of this dialog */\n    private final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(550, 400);\n\n    /**\n     * Creates a new PreferredEncodingsDialog, without showing it on screen.\n     *\n     * @param owner the frame that invoked this dialog\n     */\n    PreferredEncodingsDialog(Frame owner) {\n        super(owner, i18n(\"preferred_encodings\"), owner);\n        init();\n    }\n\n    /**\n     * Creates a new PreferredEncodingsDialog, without showing it on screen.\n     *\n     * @param owner the dialog that invoked this dialog\n     */\n    public PreferredEncodingsDialog(Dialog owner) {\n        super(owner, i18n(\"preferred_encodings\"), owner);\n        init();\n    }\n\n\n    protected void init() {\n        // Mac OS X: small window borders\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            getRootPane().putClientProperty(\"Window.style\", \"small\");\n        }\n\n        Container contentPane = getContentPane();\n\n        // Label\n        JLabel label = new JLabel(i18n(\"preferred_encodings\")+\":\");\n\n        // Mac OS X: small component size\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            label.putClientProperty(\"JComponent.sizeVariant\", \"small\");\n        }\n\n        contentPane.add(label, BorderLayout.NORTH);\n\n        // Checkboxes\n        YBoxPanel yPanel = new YBoxPanel();\n\n        checkboxes = new Vector<>();\n        for (String enc : Charset.availableCharsets().keySet()) {\n            JCheckBox checkbox = new JCheckBox(enc);\n            // Mac OS X: component size\n            if (OsFamily.MAC_OS_X.isCurrent()) {\n                checkbox.putClientProperty(\"JComponent.sizeVariant\", \"small\");\n            }\n\n            checkboxes.add(checkbox);\n            yPanel.add(checkbox);\n        }\n\n        selectCheckboxes(EncodingPreferences.getPreferredEncodings());\n\n        JScrollPane scrollPane = new JScrollPane(yPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);\n        contentPane.add(scrollPane, BorderLayout.CENTER);\n\n        // 'Revert to defaults' button\n\n        JButton defaultsButton = new JButton(i18n(\"reset\"));\n        // Mac OS X: component size\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            defaultsButton.putClientProperty(\"JComponent.sizeVariant\", \"small\");\n        }\n\n        defaultsButton.addActionListener(e -> selectCheckboxes(EncodingPreferences.getDefaultPreferredEncodings()));\n\n        JPanel flowPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING));\n        flowPanel.add(defaultsButton);\n        contentPane.add(flowPanel, BorderLayout.SOUTH);\n\n        setMinimumSize(MINIMUM_DIALOG_DIMENSION);\n        setMaximumSize(MAXIMUM_DIALOG_DIMENSION);\n\n        addWindowListener(new WindowAdapter() {\n            @Override\n            public void windowClosing(WindowEvent e) {\n                List<String> preferredEncodings = new ArrayList<>();\n\n                for (JCheckBox checkbox : checkboxes) {\n                    if (checkbox.isSelected())\n                        preferredEncodings.add(checkbox.getText());\n                }\n\n                EncodingPreferences.setPreferredEncodings(preferredEncodings);\n            }\n        });\n    }\n\n    /**\n     * Selects all the checkboxes which correspond to an encoding that is present in the given vector.\n     *\n     * @param selectedEncodings list of encodings to select\n     */\n    private void selectCheckboxes(List<String> selectedEncodings) {\n        for (JCheckBox checkbox : checkboxes) {\n            checkbox.setSelected(selectedEncodings.contains(checkbox.getText()));\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/event/ActivePanelListener.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.event;\n\nimport com.mucommander.ui.main.FolderPanel;\n\n\n/**\n * Interface to be implemented by classes that wish to be notified of active table changes on a particular MainFrame.\n * Those classes need to be registered to receive those events, this can be done by calling\n * {@link com.mucommander.ui.main.MainFrame#addActivePanelListener(ActivePanelListener)}.\n *\n * @see com.mucommander.ui.main.MainFrame\n * @author Maxence Bernard\n */\npublic interface ActivePanelListener {\n\n    /**\n     * This method is invoked when the currently active (i.e. that has focus) folder panel has changed on the MainFrame.\n     *\n     * @param folderPanel the new active FolderPanel.\n     */\n    void activePanelChanged(FolderPanel folderPanel);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/event/LocationAdapter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.event;\n\n/**\n * An abstract adapter class for receiving location events.\n * The methods in this class are empty. This class exists as\n * convenience for creating listener objects.\n * \n * @author Arik Hadas\n */\npublic abstract class LocationAdapter implements LocationListener {\n\n\t/**\n     * {@inheritDoc}\n     */\n\tpublic void locationChanging(LocationEvent locationEvent) { }\n\n\t/**\n     * {@inheritDoc}\n     */\n\tpublic void locationChanged(LocationEvent locationEvent) { }\n\n\t/**\n     * {@inheritDoc}\n     */\n\tpublic void locationCancelled(LocationEvent locationEvent) { }\n\n\t/**\n     * {@inheritDoc}\n     */\n\tpublic void locationFailed(LocationEvent locationEvent) { }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/event/LocationEvent.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.event;\n\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.ui.main.FolderPanel;\n\n\n/**\n * Event used to indicate that a folder change is or has occurred. This event is passed to to every LocationListener\n * that registered to receive those events on a particular FolderPanel.\n *\n * @author Maxence Bernard\n */\npublic class LocationEvent {\n\n    /** FolderPanel where location has or is being changed */\n    private final FolderPanel folderPanel;\n\n    /** URL of the folder that has or is being changed */\n    private final FileURL folderURL;\n\n    /**\n     * Creates a new LocationEvent.\n     *\n     * @param folderPanel FolderPanel where location has or is being changed.\n     * @param folderURL url of the folder that has or is being changed\n     */\n    public LocationEvent(FolderPanel folderPanel, FileURL folderURL) {\n        this.folderPanel = folderPanel;\n        this.folderURL = folderURL;\n    }\n\n    /**\n     * Returns the FolderPanel instance where location has or is being changed.\n     */\n    public FolderPanel getFolderPanel() {\n        return folderPanel;\n    }\n\n    /**\n     * Returns the URL to the folder that has or is being changed.\n     */\n    public FileURL getFolderURL() {\n        return folderURL;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/event/LocationListener.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.event;\n\n\n/**\n * Interface to be implemented by classes that wish to be notified of location changes on a particular\n * FolderPanel. Those classes need to be registered to receive those events, this can be done by calling\n * {@link LocationManager#addLocationListener(LocationListener)}.\n *\n * @see com.mucommander.ui.main.FolderPanel\n * @author Maxence Bernard\n */\npublic interface LocationListener {\n\t\n    /**\n     * This method is invoked when the current folder is being changed.\n     *\n     * <p>A call to either {@link #locationChanged(LocationEvent)}, {@link #locationCancelled(LocationEvent)} or\n     * {@link #locationFailed(LocationEvent)} will always follow to indicate the outcome of the folder change. \n     *\n     * @param locationEvent describes the location change event\n     */\n    void locationChanging(LocationEvent locationEvent);\n\n\n    /**\n     * This method is invoked when the current folder has changed.\n     *\n     * @param locationEvent describes the location change event\n     */\n    void locationChanged(LocationEvent locationEvent);\n\n\n    /**\n     * This method is invoked when the current folder has been cancelled by the user.\n     *\n     * @param locationEvent describes the location change event\n     */\n    void locationCancelled(LocationEvent locationEvent);\n\n\n    /**\n     * This method is invoked when the current folder could not be changed, as a result\n     * of the folder not existing or failing to list its contents.\n     *\n     * @param locationEvent describes the location change event\n     */\n    void locationFailed(LocationEvent locationEvent);\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/event/LocationManager.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.event;\n\nimport java.util.Map;\nimport java.util.WeakHashMap;\n\nimport com.mucommander.commons.file.filter.FileFilter;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.core.FolderChangeMonitor;\nimport com.mucommander.core.GlobalLocationHistory;\nimport com.mucommander.ui.main.ConfigurableFolderFilter;\nimport com.mucommander.ui.main.FolderPanel;\n\n/**\n * @author Maxence Bernard\n */\npublic class LocationManager {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(LocationManager.class);\n\n    /** Contains all registered location listeners, stored as weak references */\n    private final Map<LocationListener, ?> locationListeners = new WeakHashMap<>();\n\n    /** The FolderPanel instance this LocationManager manages location events for */\n    private final FolderPanel folderPanel;\n\n    /** Current location presented in the FolderPanel */\n    private AbstractFile currentFolder;\n\n    /** Filters out unwanted files when listing folder contents */\n\tprivate final ConfigurableFolderFilter configurableFolderFilter = new ConfigurableFolderFilter();\n\n\tprivate FolderChangeMonitor folderChangeMonitor;\n\n    /**\n     * Creates a new LocationManager that manages location events listeners and broadcasts for the specified FolderPanel.\n     *\n     * @param folderPanel the FolderPanel instance this LocationManager manages location events for\n     */\n    public LocationManager(FolderPanel folderPanel) {\n        this.folderPanel = folderPanel;\n        \n        addLocationListener(GlobalLocationHistory.getInstance());\n    }\n\n    /**\n     * Set the given {@link AbstractFile} as the folder presented in the {@link FolderPanel}.\n     * This method saves the given {@link AbstractFile}, and notify the {@link LocationListener}s that\n     * the location was changed to it.\n     * \n     * @param folder the {@link AbstractFile} that is going to be presented in the {@link FolderPanel}\n     */\n    public void setCurrentFolder(AbstractFile folder, AbstractFile fileToSelect, boolean changeLockedTab) {\n        AbstractFile[] children = safeLs(folder, configurableFolderFilter);\n\n        folderPanel.setCurrentFolder(folder, children, fileToSelect, changeLockedTab);\n\n    \tthis.currentFolder = folder;\n\n    \t// Notify listeners that the location has changed\n    \tfireLocationChanged(folder.getURL());\n\n    \t// After the initial folder is set, initialize the monitoring thread\n    \tif (folderChangeMonitor == null) {\n            folderChangeMonitor = new FolderChangeMonitor(folderPanel);\n        }\n    }\n\n    private static AbstractFile[] safeLs(AbstractFile folder, FileFilter filter) {\n        LOGGER.trace(\"calling ls()\");\n        try {\n            return folder.ls(filter);\n        } catch (Exception e) {\n            LOGGER.error(\"Couldn't ls children of \" + folder.getAbsolutePath() + \", error: \" + e.getMessage());\n            return new AbstractFile[0];\n        }\n    }\n\n    /**\n     * Return the folder presented in the {@link FolderPanel}\n     * \n     * @return the {@link AbstractFile} presented in the {@link FolderPanel}\n     */\n    public AbstractFile getCurrentFolder() {\n    \treturn currentFolder;\n    }\n\n    public FolderChangeMonitor getFolderChangeMonitor() {\n        return folderChangeMonitor;\n    }\n\n    /**\n     * Registers a LocationListener to receive notifications whenever the current folder of the associated FolderPanel\n     * has or is being changed.\n     *\n     * <p>Listeners are stored as weak references so {@link #removeLocationListener(LocationListener)}\n     * doesn't need to be called for listeners to be garbage collected when they're not used anymore.\n     *\n     * @param listener the LocationListener to register\n     */\n    public synchronized void addLocationListener(LocationListener listener) {\n        locationListeners.put(listener, null);\n    }\n\n    /**\n     * Removes the LocationListener from the list of listeners that receive notifications when the current folder of the\n     * associated FolderPanel has or is being changed.\n     *\n     * @param listener the LocationListener to remove\n     */\n    public synchronized void removeLocationListener(LocationListener listener) {\n        locationListeners.remove(listener);\n    }\n\n    /**\n     * Notifies all registered listeners that the current folder has changed on associated FolderPanel.\n     *\n     * @param folderURL url of the new current folder in the associated FolderPanel\n     */\n    private synchronized void fireLocationChanged(FileURL folderURL) {\n        for (LocationListener listener : locationListeners.keySet()) {\n            listener.locationChanged(new LocationEvent(folderPanel, folderURL));\n        }\n    }\n\n    /**\n     * Notifies all registered listeners that the current folder is being changed on the associated FolderPanel.\n     *\n     * @param folderURL url of the folder that will become the new location if the folder change is successful\n     */\n    public synchronized void fireLocationChanging(FileURL folderURL) {\n        for (LocationListener listener : locationListeners.keySet()) {\n            listener.locationChanging(new LocationEvent(folderPanel, folderURL));\n        }\n    }\n\n    /**\n     * Notifies all registered listeners that the folder change as notified by {@link #fireLocationChanging(FileURL)}\n     * has been cancelled by the user.\n     *\n     * @param folderURL url of the folder for which a failed attempt was made to make it the current folder\n     */\n    public synchronized void fireLocationCancelled(FileURL folderURL) {\n        for (LocationListener listener : locationListeners.keySet()) {\n            listener.locationCancelled(new LocationEvent(folderPanel, folderURL));\n        }\n    }\n\n    /**\n     * Notifies all registered listeners that the folder change as notified by {@link #fireLocationChanging(FileURL)}\n     * could not be changed, as a result of the folder not existing or failing to list its contents.\n     *\n     * @param folderURL url of the folder for which a failed attempt was made to make it the current folder\n     */\n    public synchronized void fireLocationFailed(FileURL folderURL) {\n        for (LocationListener listener : locationListeners.keySet()) {\n            listener.locationFailed(new LocationEvent(folderPanel, folderURL));\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/event/TableSelectionListener.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.event;\n\nimport com.mucommander.ui.main.table.FileTable;\n\n/**\n * Interface to be implemented by classes that wish to be notified of selection changes on a particular\n * FileTable. Those classes need to be registered to receive those events, this can be done by calling\n * {@link com.mucommander.ui.main.table.FileTable#addTableSelectionListener(TableSelectionListener) FileTable.addTableSelectionListener()}.\n *\n * @see com.mucommander.ui.main.table.FileTable\n * @author Maxence Bernard\n */\npublic interface TableSelectionListener {\n\n    /**\n     * This method is invoked when the selected file has changed on the specified FileTable .\n     *\n     * @param source the {@link com.mucommander.ui.main.table.FileTable} instance on which the file selection has changed\n     */\n    void selectedFileChanged(FileTable source);\n\n\n    /**\n     * This method is invoked when the files marked have changed on the specified FileTable.\n     *\n     * @param source the {@link com.mucommander.ui.main.table.FileTable} instance on which the files marked have changed\n     */\n    void markedFilesChanged(FileTable source);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/helper/FocusRequester.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.helper;\n\nimport java.awt.*;\n\nimport javax.swing.*;\n\nimport com.mucommander.ui.PreloadedJFrame;\nimport com.mucommander.ui.main.MainFrame;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n\n/**\n * The sole purpose of this class is to provide a way to request focus on a component after all currently queued\n * Swing events have been processed. This is useful for components that are not eligible to receive focus at the time\n * they request it, for instance when they are not visible yet.\n *\n * @author Maxence Bernard\n */\npublic class FocusRequester implements Runnable {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(FocusRequester.class);\n\t\n    /** The component on which to request focus */\n    private Component component;\n\n    /** If true, focus will be requested using Component#requestFocusInWindow() instead of Component#requestFocus() */\n    private final boolean requestFocusInWindow;\n\t\n    private FocusRequester(Component c, boolean requestFocusInWindow) {\n        this.component = c;\n        this.requestFocusInWindow = requestFocusInWindow;\n    }\n\t\n    /**\n     * Requests focus on the given component using {@link java.awt.Component#requestFocus()}, after all currently queued\n     * Swing events have been processed.\n     *\n     * <p>This method can typically be used when a component has been added to the screen but is not yet visible.\n     * In that case, calling {@link Component#requestFocus()} would have no effect.\n     *\n     * @param c the component on which to request focus\n     * @see java.awt.Component#requestFocus()\n     */\n    public static synchronized void requestFocus(Component c) {\n        if (c == null) {\n            LOGGER.debug(\">>>>>>>>>>>>>>>>> Component is null, returning!\");\n            return;\n        }\n        SwingUtilities.invokeLater(new FocusRequester(c, false));\n    }\n\n    /**\n     * Requests focus on the given component, after all currently queued Swing events have been processed.\n     *\n     * <p>This method can typically be used when a component has been added to the screen but is not yet visible.\n     * In that case, calling {@link java.awt.Component#requestFocusInWindow()} would have no effect.\n     *\n     * @param c the component on which to request focus\n     * @see java.awt.Component#requestFocusInWindow()\n     */\n    public static synchronized void requestFocusInWindow(Component c) {\n        if (c == null) {\n            LOGGER.debug(\">>>>>>>>>>>>>>>>>> Component is null, returning!\");\n            return;\n        }\n        SwingUtilities.invokeLater(new FocusRequester(c, true));\n    }\n\n    @Override\n    public void run() {\n        // Request focus on the component\n        if (requestFocusInWindow) {\n            component.requestFocusInWindow();\n        } else {\n            component.requestFocus();\n        }\n        if (component instanceof Frame f) {\n            f.toFront();\n        }\n        if (component instanceof PreloadedJFrame preloadedJFrame) {\n            var frame = preloadedJFrame.getMainFrameObject();\n            if (frame instanceof MainFrame mainFrame) {\n                mainFrame.getActiveTable().requestFocus();\n            }\n        }\n\n        LOGGER.debug(\"focus requested on {}\", (component.getClass().getName()));\n\n        this.component = null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/helper/MenuToolkit.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.helper;\n\nimport java.awt.event.ActionListener;\nimport java.awt.event.KeyEvent;\n\nimport javax.swing.*;\nimport javax.swing.event.MenuListener;\n\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.menu.JScrollMenu;\nimport ru.trolsoft.ui.TCheckBoxMenuItem;\nimport ru.trolsoft.ui.TRadioButtonMenuItem;\n\n\n\n/**\n * MenuToolkit provides convenient methods that make life easier\n * when creating menus.\n *\n * @author Maxence Bernard\n */\npublic class MenuToolkit {\n\n    private static final int TYPE_ITEM = 0;\n    private static final int TYPE_CHECKBOX = 1;\n    private static final int TYPE_RADIOBUTTON = 2;\n\n\n    private MenuToolkit() {\n\n    }\n\n    /**\n     * Creates and returns a new JMenu.\n     *\n     * @param title          title of the menu\n     * @param mnemonicHelper an optional (can be null) mnemonic helper which will be used along with\n     *                       the title to set a mnemonic to the menu.\n     * @param menuListener   an optional (can be null) menu listener which will listen to the events triggered by the menu.\n     */\n    public static JMenu addMenu(String title, MnemonicHelper mnemonicHelper, MenuListener menuListener) {\n        JMenu menu = new JMenu(title);\n        initMenu(menu, title, mnemonicHelper, menuListener);\n        return menu;\n    }\n\n    /**\n     * Creates and returns a new JScrollMenu.\n     *\n     * @param title          title of the menu\n     * @param mnemonicHelper an optional (can be null) mnemonic helper which will be used along with\n     *                       the title to set a mnemonic to the menu.\n     * @param menuListener   an optional (can be null) menu listener which will listen to the events triggered by the menu.\n     */\n    public static JScrollMenu addScrollableMenu(String title, MnemonicHelper mnemonicHelper, MenuListener menuListener) {\n        final JScrollMenu menu = new JScrollMenu(title);\n        initMenu(menu, title, mnemonicHelper, menuListener);\n        return menu;\n    }\n\n    private static void initMenu(JMenu menu, String title, MnemonicHelper mnemonicHelper, MenuListener menuListener) {\n        setupMnemonic(title, mnemonicHelper, menu);\n        if (menuListener != null) {\n            menu.addMenuListener(menuListener);\n        }\n    }\n\n    /**\n     * Creates a new JMenuItem and adds it to the given JMenu.\n     *\n     * @param menu           menu to add the menu item to.\n     * @param text           text used by the menu item.\n     * @param mnemonicHelper an optional (can be null) mnemonic helper which will be used along with\n     *                       the item's text to set a mnemonic to the menu.\n     * @param accelerator    an optional (can be null) keyboard shortcut used by the menu item.\n     * @param actionListener an optional (can be null) action listener which will listen to the events triggered by the menu item.\n     */\n    public static JMenuItem addMenuItem(JMenu menu, String text, MnemonicHelper mnemonicHelper, KeyStroke accelerator, ActionListener actionListener) {\n        return addMenuItem(menu, text, mnemonicHelper, accelerator, actionListener, TYPE_ITEM);\n    }\n\n    /**\n     * Creates a new JCheckBoxMenuItem initially unselected and adds it to the given JMenu.\n     *\n     * @param menu           menu to add the menu item to.\n     * @param text           text used by the menu item.\n     * @param mnemonicHelper an optional (can be null) mnemonic helper which will be used along with\n     *                       the item's text to set a mnemonic to the menu.\n     * @param accelerator    an optional (can be null) keyboard shortcut used by the menu item.\n     * @param actionListener an optional (can be null) action listener which will listen to the events triggered by the menu item.\n     */\n    public static JCheckBoxMenuItem addCheckBoxMenuItem(JMenu menu, String text, MnemonicHelper mnemonicHelper, KeyStroke accelerator, ActionListener actionListener) {\n        return (JCheckBoxMenuItem) addMenuItem(menu, text, mnemonicHelper, accelerator, actionListener, TYPE_CHECKBOX);\n    }\n\n    /**\n     * Creates a new JRadioButtonMenuItem initially unselected and adds it to the given JMenu.\n     *\n     * @param menu           menu to add the menu item to.\n     * @param text           text used by the menu item.\n     * @param mnemonicHelper an optional (can be null) mnemonic helper which will be used along with\n     *                       the item's text to set a mnemonic to the menu.\n     * @param accelerator    an optional (can be null) keyboard shortcut used by the menu item.\n     * @param actionListener an optional (can be null) action listener which will listen to the events triggered by the menu item.\n     */\n    public static JRadioButtonMenuItem addRadioButtonMenuItem(JMenu menu, String text, MnemonicHelper mnemonicHelper,\n                                                              KeyStroke accelerator, ActionListener actionListener, ButtonGroup group) {\n        JRadioButtonMenuItem item = (JRadioButtonMenuItem) addMenuItem(menu, text, mnemonicHelper, accelerator, actionListener,\n                TYPE_RADIOBUTTON);\n        if (group != null) {\n            group.add(item);\n        }\n        return item;\n    }\n\n\n    /**\n     * Creates a new JMenuItem or JCheckBoxMenuItem and adds it to the given JMenu.\n     *\n     * @param menu           menu to add the menu item to.\n     * @param text           text used by the menu item.\n     * @param mnemonicHelper an optional (can be null) mnemonic helper which will be used along with\n     *                       the item's text to set a mnemonic to the menu.\n     * @param accelerator    an optional (can be null) keyboard shortcut used by the menu item.\n     * @param actionListener an optional (can be null) action listener which will listen to the events triggered by the menu item.\n     * @param menuType       specifies whether the menu item to be created is a JCheckBoxMenuItem, JRadioButtonMenuItem or just a regular JMenuItem.\n     */\n    private static JMenuItem addMenuItem(JMenu menu, String text, MnemonicHelper mnemonicHelper, KeyStroke accelerator, ActionListener actionListener, int menuType) {\n        final JMenuItem menuItem = construct(menuType, text);\n        setupMnemonic(text, mnemonicHelper, menuItem);\n\n        if (accelerator != null) {\n            menuItem.setAccelerator(accelerator);\n        }\n\n        if (actionListener != null) {\n            menuItem.addActionListener(actionListener);\n        }\n\n        menu.add(menuItem);\n\n        return menuItem;\n    }\n\n    private static void setupMnemonic(String text, MnemonicHelper mnemonicHelper, JMenuItem menuItem) {\n        if (mnemonicHelper != null) {\n            char mnemonic = mnemonicHelper.getMnemonic(text);\n            if (mnemonic != 0) {\n                menuItem.setMnemonic(mnemonic);\n            }\n        }\n    }\n\n    /**\n     * Does things that should be done to all menu items created from\n     * <code>MuAction</code>s.\n     * <ol>\n     * <li>If the provided action has an icon, it would by default get displayed in the menu item.\n     * Since icons have nothing to do in menus, let's make sure the menu item has no icon.</li>\n     * <li>If the action has a keyboard shortcut that conflicts with the menu's internal ones\n     * (enter, space and escape), they will not be used.</li>\n     * </ol>\n     *\n     * @param item menu item to take care of.\n     */\n    public static void configureActionMenuItem(JMenuItem item) {\n        item.setIcon(null);\n\n        if (isInvalidAccelerator(item.getAccelerator())) {\n            item.setAccelerator(null);\n        }\n    }\n\n    private static boolean isInvalidAccelerator(KeyStroke stroke) {\n        return stroke != null && stroke.getModifiers() == 0 &&\n                (stroke.getKeyCode() == KeyEvent.VK_ENTER || stroke.getKeyCode() == KeyEvent.VK_SPACE || stroke.getKeyCode() == KeyEvent.VK_ESCAPE);\n    }\n\n    public static JMenuItem addMenuItem(JMenu menu, TcAction action, MnemonicHelper mnemonicHelper) {\n        return addMenuItem(menu, action, mnemonicHelper, TYPE_ITEM);\n    }\n\n    public static JCheckBoxMenuItem addCheckBoxMenuItem(JMenu menu, TcAction action, MnemonicHelper mnemonicHelper) {\n        return (JCheckBoxMenuItem) addMenuItem(menu, action, mnemonicHelper, TYPE_CHECKBOX);\n    }\n\n    public static JRadioButtonMenuItem addRadioButtonMenuItem(JMenu menu, TcAction action, MnemonicHelper mnemonicHelper) {\n        return (JRadioButtonMenuItem) addMenuItem(menu, action, mnemonicHelper, TYPE_RADIOBUTTON);\n    }\n\n    private static JMenuItem addMenuItem(JMenu menu, TcAction action, MnemonicHelper mnemonicHelper, int menuType) {\n        final JMenuItem menuItem = construct(menuType, action);\n\n\n        if (mnemonicHelper != null && action != null) {\n            char mnemonic = mnemonicHelper.getMnemonic(action.getLabel());\n            if (mnemonic != 0) {\n                menuItem.setMnemonic(mnemonic);\n            }\n        }\n\n        // If the provided action has an icon, it would by default get displayed in the menu item.\n        // Since icons have nothing to do in menus, let's make sure the menu item has no icon.\n        menuItem.setIcon(null);\n\n        menu.add(menuItem);\n\n        return menuItem;\n    }\n\n    private static JMenuItem construct(int type, String text) {\n        switch (type) {\n            case TYPE_CHECKBOX:\n                return new TCheckBoxMenuItem(text);\n            case TYPE_RADIOBUTTON:\n                return new TRadioButtonMenuItem(text);\n            default:\n                return new JMenuItem(text);\n        }\n    }\n\n    private static JMenuItem construct(int type, Action action) {\n        switch (type) {\n            case TYPE_CHECKBOX:\n                return new TCheckBoxMenuItem(action);\n            case TYPE_RADIOBUTTON:\n                return new TRadioButtonMenuItem(action);\n            default:\n                return new JMenuItem(action);\n        }\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/helper/MnemonicHelper.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.helper;\n\nimport javax.swing.*;\nimport java.util.ArrayList;\nimport java.util.List;\n\n\n/**\n * MnemonicHelper provides a way to easily set mnemonics to UI components, without having to bother\n * with remembering which ones have already been assigned to another component.\n *\n * <p>To use it: simply create a new instance and keep calling {@link #getMnemonic(String)}\n * to get mnemonics from the giving pieces of text.\n * \n * @author Maxence Bernard\n */\npublic class MnemonicHelper {\n\n    /** Current list of previously assigned mnemonics */\n    private final List<Character> takenMnemonics;\n\t\n\t\n    /**\n     * Creates a new blank MnemonicHelper.\n     */\n    public MnemonicHelper() {\n        takenMnemonics = new ArrayList<>();\n    }\n\t\n\t\n    /**\n     * Finds and returns first character in the given string that's not already as a mnemonic.\n     *\n     * <p>Returned mnemonic will be added to current internal list of taken mnemonics\n     * and won't ever be used again by this instance.\n     *\n     * @return the character to be used as a mnemonic, always in lower case, 0 if no\n     * mnemonic was available for this piece of text. 0 is returned if a <code>null</code> string is passed.\n     * @param text text to get a mnemonic from.\n     */\n    public char getMnemonic(String text) {\n        // Returns 0 in case of null string\n        if (text == null || text.isEmpty()) {\n            return 0;\n        }\n\t\t\n        // Find first letter available for mnemonic (keyboard shortcut)\n        int mnemonicPos = 0;\n        text = text.toLowerCase();\n        int textLength = text.length();\n        do {\n            char mnemonic = text.charAt(mnemonicPos++);\n            if (!isMnemonicUsed(mnemonic)) {\n                takenMnemonics.add(mnemonic);\n                return mnemonic;\n            }\n        } while (mnemonicPos < textLength);\n\n        return 0;\n    }\n\n\n    /**\n     * Convenience method that returns a mnemonic for the specified button.\n     * Yields to the same result as if {@link #getMnemonic(String)} were called with JButton.getText().\n     *\n     * @param button the button to get a mnemonic for\n     * @return the character to be used as a mnemonic, always in lower case, 0 if no\n     * mnemonic was available for this piece of text. 0 is returned if a <code>null</code> string is passed.\n     */\n    public char getMnemonic(JButton button) {\n        return getMnemonic(button.getText());\n    }\n\n\n    /**\n     * Returns <code>true</code> if the specified character has already been previously\n     * used as a mnemonic, returned by {@link #getMnemonic(String)}.\n     *\n     * @param ch the character which will be tested for an existing mnemonic.\n     * @return whether the character is already used in the mnemonics array.\n     */\n    private boolean isMnemonicUsed(char ch) {\n        return takenMnemonics.contains(ch);\n    }\n\n\n    /**\n     * Clears any previously registered mnemonics by {@link #getMnemonic(String)}.\n     */\n    public void clear() {\n        takenMnemonics.clear();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/helper/ScreenServices.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.helper;\n\nimport com.mucommander.conf.TcSnapshot;\n\nimport java.awt.Dimension;\nimport java.awt.Frame;\nimport java.awt.Insets;\nimport java.awt.Rectangle;\nimport java.awt.Toolkit;\nimport java.awt.Window;\n\n/**\n * This class offers screen related services\n * \n * @author Arik Hadas\n */\npublic class ScreenServices {\n\n\t/**\n     * Computes the screen's insets for the specified window and returns them.\n     * <p>\n     * While this might seem strange, screen insets can change from one window\n     * to another. For example, on X11 windowing systems, there is no guarantee that\n     * a window will be displayed on the same screen, let alone computer, as the one\n     * the application is running on.\n     *\n     * @param window the window for which screen insets should be computed.\n     * @return the screen's insets for the specified window\n     */\n    public static Insets getScreenInsets(Window window) {\n        return Toolkit.getDefaultToolkit().getScreenInsets(window.getGraphicsConfiguration());\n    }\n\t\n\t\n    /**\n     * Checks whether the specified frame can be moved to the specified coordinates and still\n     * be fully visible.\n     * <p>\n     * If <code>x</code> (resp. <code>y</code>) is <code>null</code>, this method won't test\n     * whether the frame is within horizontal (resp. vertical) bounds.\n     *\n     * @param frame frame who's visibility should be tested.\n     * @param x     horizontal coordinate of the upper-leftmost corner of the area to check for.\n     * @param y     vertical coordinate of the upper-leftmost corner of the area to check for.\n     * @return      <code>true</code> if the frame can be moved at the specified location,\n     *              <code>false</code> otherwise.\n     */\n    public static boolean isInsideUsableScreen(Frame frame, int x, int y) {\n        Insets screenInsets = getScreenInsets(frame);\n        Dimension screenSize = TcSnapshot.getScreenSize();\n\n        return (x < 0 || (x >= screenInsets.left && x < screenSize.width - screenInsets.right))\n            && (y < 0 || (y >= screenInsets.top && y < screenSize.height - screenInsets.bottom));\n    }\n\t\n\t\n    /**\n     * Returns the maximum dimensions for a full-screen window.\n     *\n     * @param window window who's full screen size should be computed.\n     * @return the maximum dimensions for a full-screen window\n     */\n    public static Rectangle getFullScreenBounds(Window window) {\n        Toolkit toolkit = Toolkit.getDefaultToolkit();\n        Dimension screenSize = toolkit.getScreenSize();\n\n        Insets screenInsets = toolkit.getScreenInsets(window.getGraphicsConfiguration());\n        return new Rectangle(screenInsets.left, screenInsets.top, screenSize.width-screenInsets.left-screenInsets.right, screenSize.height-screenInsets.top-screenInsets.bottom);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/helper/package.html",
    "content": "<body>\n  Various Swing helper classes.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/icon/AnimatedIcon.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.icon;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.awt.geom.AffineTransform;\nimport java.lang.ref.WeakReference;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * <code>javax.swing.Icon</code> implementation that manages animation.\n * <p>\n * This heavily borrows code from Technomage's <code>furbelow</code> package, distributed\n * under the GNU Lesser General Public License.<br>\n * The original source code can be found <a href=\"http://furbelow.svn.sourceforge.net/viewvc/furbelow/trunk/src/furbelow\">here</a>.\n *\n * @author twall, Nicolas Rinaudo\n */\npublic abstract class AnimatedIcon implements Icon, AutoCloseable {\n    /** Default number of frames per animation. */\n    public static final int DEFAULT_FRAME_COUNT = 8;\n    /** Default number of milliseconds between each frame. */\n    public static final int DEFAULT_FRAME_DELAY = 1000 / DEFAULT_FRAME_COUNT;\n\n\n\n    /** All tracked components. */\n    private final Set<TrackedComponent> components = new HashSet<>();\n    /** Timer used to take the animation from one frame to the next. */\n    private final Timer   timer;\n    /** Index of the current frame. */\n    private int     currentFrame;\n    /** Total number of frames in the animation. */\n    private int     frameCount;\n    /** Whether the animation should be running. */\n    private boolean animate;\n\n    private boolean disposed = false;\n\n\n    /**\n     * Creates a new animated icon.\n     * <p>\n     * This is a convenience constructor and is strictly equivalent to calling\n     * <code>{@link #AnimatedIcon(int,int)}({@link #DEFAULT_FRAME_COUNT}, {@link #DEFAULT_FRAME_DELAY});</code>\n     */\n    public AnimatedIcon() {\n        this(DEFAULT_FRAME_COUNT, DEFAULT_FRAME_DELAY);\n    }\n\n    /**\n     * Creates a new animated icon with the specified number of frames.\n     * <p>\n     * This is a convenience constructor and is strictly equivalent to calling\n     * <code>{@link #AnimatedIcon(int,int)}(frameCount, {@link #DEFAULT_FRAME_DELAY});</code>\n     *\n     * @param frameCount number of frames in the animation.\n     */\n    public AnimatedIcon(int frameCount) {\n        this(frameCount, DEFAULT_FRAME_DELAY);\n    }\n\n    /**\n     * Creates a new animated icon with the specified number of frames and repaint delay.\n     * @param frameCount   number of frames in the animation.\n     * @param repaintDelay number of milliseconds to sleep between each frame.\n     */\n    public AnimatedIcon(int frameCount, int repaintDelay) {\n        // Initializes the animation timer.\n        timer = new Timer(repaintDelay, new AnimationUpdater(this));\n        timer.setRepeats(true);\n\n        // Initializes frame control.\n        setFrameCount(frameCount);\n        setFrameDelay(repaintDelay);\n    }\n\n\n    /**\n     * Returns the icon's width.\n     * @return the icon's width.\n     */\n    public abstract int getIconWidth();\n\n    /**\n     * Returns the icon's height.\n     * @return the icon's height.\n     */\n    public abstract int getIconHeight();\n\n    /**\n     * Paints the current frame.\n     * @param c component in which the frame is being painted.\n     * @param g graphics in which to paint the frame.\n     * @param x horizontal coordinate at which to paint the frame.\n     * @param y vertical coordinate at which to paint the frame.\n     */\n    protected abstract void paintFrame(Component c, Graphics g, int x, int y);\n\n\n\n    /**\n     * Sets the total number of frames in the animation.\n     * @param count total number of frames in the animation.\n     */\n    public synchronized void setFrameCount(int count) {\n        this.frameCount = count;\n    }\n\n    /**\n     * Returns the total number of frames in the animation.\n     * @return the total number of frames in the animation.\n     */\n    public synchronized int getFrameCount() {\n        return frameCount;\n    }\n\n    /**\n     * Returns the index of the current frame in the animation.\n     * @return the index of the current frame in the animation.\n     */\n    public synchronized int getFrame() {\n        return currentFrame;\n    }\n\n    /**\n     * Sets the index of the current frame in the animation.\n     * <p>\n     * If the method does actually change the current frame, it will trigger a repaint.\n     *\n     * @param frame index of the current frame in the animation.\n     */\n    public synchronized void setFrame(int frame) {\n        if (frame != currentFrame) {\n            if (frame == 0) {\n                currentFrame = 0;\n            } else {\n                currentFrame = frame % frameCount;\n            }\n            repaint();\n        }\n    }\n\n    /**\n     * Takes the animation to its next frame.\n     * <p>\n     * This is a convenience method and is strictly equivalent to calling\n     * <code>{@link #setFrame(int) setFrame}({@link #getFrame() getFrame()} + 1)</code>.\n     *\n     */\n    public synchronized void nextFrame() {setFrame(currentFrame + 1);}\n\n    /**\n     * Sets the number of milliseconds the animation will sleep between each frame.\n     * <p>\n     * If set to 0, the animation will stop.\n     *\n     * @param delay number of milliseconds the animation will sleep between each frame.\n     */\n    public synchronized void setFrameDelay(int delay) {timer.setDelay(delay);}\n\n    /**\n     * Starts / stops the animation.\n     * @param a whether the animation should be started or stopped.\n     */\n    public synchronized void setAnimated(boolean a) {\n        // Starts the animation if necessary.\n        if (a) {\n            if (!timer.isRunning()) {\n                timer.restart();\n            }\n        }\n\n        // Stops the animation if necessary.\n        else if (timer.isRunning()) {\n            timer.stop();\n        }\n        animate = a;\n    }\n\n    /**\n     * Returns <code>true</code> if the animation is currently running.\n     * <p>\n     * Note that this method will return <code>true</code> if the animation is <b>meant</b> to be running,\n     * for example if the icon is not visible but would be animated if it was.\n     *\n     * @return <code>true</code> if the animation is currently running, <code>false</code>.\n     */\n    public synchronized boolean isAnimated() {return animate;}\n\n    /**\n     * Returns the number of milliseconds the animation will sleep between each frame.\n     * @return the number of milliseconds the animation will sleep between each frame.\n     */\n    public synchronized int getFrameDelay() {return timer.getDelay();}\n\n\n\n    /**\n     * Paints the icon's current frame.\n     * @param c component in which to paint the icon.\n     * @param g graphic context in which to paint the icon.\n     * @param x horizontal coordinate at which to paint the icon.\n     * @param y vertical coordinate at which to paint the icon.\n     */\n    public synchronized void paintIcon(Component c, Graphics g, int x, int y) {\n        // Paints the current frame.\n        paintFrame(c, g, x, y);\n\n        // Stores the component and starts / restarts the timer if necessary.\n        if (c != null) {\n            AffineTransform transform;\n\n            if (g instanceof Graphics2D g2d) {\n                transform = g2d.getTransform();\n            } else {\n                transform = new AffineTransform();\n            }\n            components.add(new TrackedComponent(c, x, y, (int)(getIconWidth() * transform.getScaleX()), (int)(getIconHeight() * transform.getScaleY())));\n\n            // Restarts the timer if necessary.\n            if (!timer.isRunning() && animate) {\n                timer.restart();\n            }\n        }\n    }\n\n    /**\n     * Forces the icon to repaint.\n     */\n    protected synchronized void repaint() {\n        // If the component list is empty, we can stop the timer.\n        if (components.isEmpty()) {\n            timer.stop();\n        } else {    // Repaints all pending components.\n            for (TrackedComponent comp : components) {\n                comp.repaint();\n            }\n            components.clear();\n        }\n    }\n\n\n    @Override\n    public void close() throws Exception {\n        if (!disposed) {\n            timer.stop();\n            components.clear();\n            disposed = true;\n        }\n    }\n    /**\n     * Used to keep track of the various components in which an animated icon is being painted.\n     * @author twall, Nicolas Rinaudo\n     */\n    private static class TrackedComponent {\n        /** Component in which the icon must be painted. */\n        private final Component component;\n        /** Horizontal coordinate at which the icon should be painted. */\n        private final int       x;\n        /** Vertical coordinate at which the icon should be painted. */\n        private final int       y;\n        /** Width of the icon (used for clipping). */\n        private final int       width;\n        /** Height of the icon (used for clipping). */\n        private final int       height;\n        /** Component's hashcode. */\n        private final int       hashCode;\n\n        /**\n         * Creates a new tracked component.\n         * @param c      component in which to paint the icon.\n         * @param x      horizontal coordinate at which to paint the icon.\n         * @param y      vertical coordinate at which to paint the icon.\n         * @param width  width of the icon.\n         * @param height height of the icon.\n         */\n        public TrackedComponent(Component c, int x, int y, int width, int height) {\n            Component ancestor = findNonRendererAncestor(c);\n\n            // Identifies the component that displays the icon.\n            if (ancestor != c) {\n                Point pt = SwingUtilities.convertPoint(c, x, y, ancestor);\n                c = ancestor;\n                x = pt.x;\n                y = pt.y;\n            }\n\n            // Stores all the necessary information and computes the tracked component's hashcode.\n            component   = c;\n            this.x      = x;\n            this.y      = y;\n            this.width  = width;\n            this.height = height;\n            int code = x;\n            code = 31 * code + y;\n            code = 31 * code + System.identityHashCode(c);\n            this.hashCode = code;\n        }\n\n        public int hashCode() {\n            return hashCode;\n        }\n\n        /**\n         * Finds the specified component's first non-renderer ancestor.\n         * @param c component whose ancestors should be explored.\n         */\n        private Component findNonRendererAncestor(Component c) {\n            Component ancestor = SwingUtilities.getAncestorOfClass(CellRendererPane.class, c);\n            if (ancestor != null && ancestor != c && ancestor.getParent() != null)\n                c = findNonRendererAncestor(ancestor.getParent());\n            return c;\n        }\n\n        /**\n         * Forces the tracked component to repaint the animated icon.\n         */\n        public void repaint() {\n            component.repaint(x, y, width, height);\n        }\n    }\n\n\n\n    /**\n     * Receives timer events and notifies the icon.\n     * @author twall, Nicolas Rinaudo\n     */\n    private static class AnimationUpdater implements ActionListener {\n        /** Weak reference to the animation. */\n        private final WeakReference<AnimatedIcon> icon;\n\n        /**\n         * Creates a new animation updater on the specified icon.\n         * @param icon animation to update.\n         */\n        public AnimationUpdater(AnimatedIcon icon) {\n            this.icon = new WeakReference<>(icon);\n        }\n\n        /**\n         * Notifies the icon that it should update.\n         * @param event ignored.\n         */\n        public void actionPerformed(ActionEvent event) {\n            AnimatedIcon i = icon.get();\n\n            // Makes sure the animation hasn't been garbage collected.\n            if (i != null) {\n                i.nextFrame();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/icon/CustomFileIconProvider.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.icon;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileProtocols;\nimport com.mucommander.commons.file.icon.FileIconProvider;\n\nimport javax.swing.Icon;\nimport javax.swing.ImageIcon;\nimport java.awt.Dimension;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * This {@link com.mucommander.commons.file.icon.FileIconProvider} returns icons from the\n * {@link com.mucommander.ui.icon.IconManager.IconSet#FILE IconManager's custom file icon set}, based on files' extension and type.\n * No caching is performed by this class as {@link IconManager} already takes care of this. \n *\n * @author Maxence Bernard\n */\npublic class CustomFileIconProvider implements FileIconProvider {\n\n    /** Has init() method already been called? */\n    private static boolean initialized;\n\n    /** Hashtable that associates file extensions with icon names */\n    private static Map<String, String> extensionMap;\n\n    /** Icon for directories */\n    public final static String FOLDER_ICON_NAME = \"folder.png\";\n\n    /** Default icon for files without a known extension */\n    public final static String FILE_ICON_NAME = \"file.png\";\n\n    /** Icon for supported archives (browsable) */\n    public final static String ARCHIVE_ICON_NAME = \"archive_supported.png\";\n\n    /** Icon for parent folder (..) */\n    public final static String PARENT_FOLDER_ICON_NAME = \"parent.png\";\n\n    /** Transparent icon symbolizing symlinks, painted over an existing icon */\n    public final static String SYMLINK_ICON_NAME = \"link.png\";\n\n    /** Icon for Mac OS X's applications */\n    public final static String MAC_OS_X_APP_ICON_NAME = \"executable_osx.png\";\n\n    /** Icon for the root of remote (non-local) locations */\n    public final static String NETWORK_ICON_NAME = \"network.png\";\n\n    /** Icon for the not accessible remote locations */\n    public final static String DISCONNECTED_ICON_NAME = \"disconnect.png\";\n\n    /** Icon for android ADB FS locations */\n    public final static String ANDROID_ICON_NAME = \"android.png\";\n\n    /** Icon for not accessible files (used for quick-lists) **/\n    public final static String NOT_ACCESSIBLE_FILE = \"not_accessible.png\";\n\n    /** Icon for bookmarks */\n    public final static String BOOKMARK_ICON_NAME = \"bookmark.png\";\n\n    /** File icon <-> extensions association map. For information about specific file extensions, refer to:\n     * <ul>\n     *  <li><a href=\"http://en.wikipedia.org/wiki/File_format\">http://en.wikipedia.org/wiki/File_format</a></li>\n     *  <li><a href=\"http://filext.com/\">http://filext.com/</a></li>\n     *\t<li><a href=\"http://whatis.techtarget.com/fileFormatP/\">http://whatis.techtarget.com/fileFormatP/</a></li>\n     *  <li><a href=\"http://www.fileinfo.net\">http://www.fileinfo.net</a></li>\n     * </ul>\n     */\n    final static String[][] ICON_EXTENSIONS = {\n        {\"archive_unsupported.png\", \"7z\", \"ace\", \"arj\", \"bin\", \"bz\", \"cab\", \"dmg\", \"hqx\", \"ipk\", \"lha\", \"lzh\", \"lzx\", \"msi\", \"mpkg\", \"pak\", \"pkg\", \"pq6\", \"rar\", \"rk\", \"rz\", \"sea\", \"sit\", \"sitx\", \"sqx\", \"z\", \"zoo\"},\t// Unsupported archive formats (no native support), see http://en.wikipedia.org/wiki/Archive_formats\n        {\"audio.png\", \"aac\", \"aif\", \"aiff\", \"aifc\", \"amr\", \"ape\", \"au\", \"cda\", \"mp3\", \"mpa\", \"mp2\", \"mpc\", \"m3u\", \"m4a\", \"m4b\", \"m4p\", \"nap\", \"ogg\", \"pls\", \"ra\", \"ram\", \"wav\", \"wave\", \"flac\", \"wma\", \"mid\", \"midi\", \"smf\", \"mod\", \"mtm\", \"xm\", \"s3m\", \"mka\"},\t// Audio formats, see http://en.wikipedia.org/wiki/Audio_file_format\n        {\"cd_image.png\", \"iso\", \"nrg\"},\t// CD/DVD image\n        {\"certificate.png\", \"cer\", \"crt\", \"key\"},\t// Certificate file\n        {\"configuration.png\", \"cnf\", \"conf\", \"config\", \"inf\", \"ini\", \"pif\", \"prefs\", \"prf\"},\t// Configuration file\n        {\"database.png\", \"myi\", \"myd\", \"frm\", \"sql\", \"sqc\", \"sqr\", \"mdb\", \"mde\", \"mdn\", \"mdt\", \"accdb\", \"accde\", \"accdr\", \"accdt\"},\t// Database file\n        {\"executable_windows.png\", \"bat\", \"com\", \"exe\"},\t// Windows executables\n        {\"feed.png\", \"rdf\", \"rss\"},\t// RSS/RDF feed\n        {\"font.png\", \"fnt\", \"fon\", \"otf\"},\t// Non-TrueType font\n        {\"font_truetype.png\", \"ttc\", \"ttf\"},\t// TrueType font\n        {\"image_bitmap.png\", \"exif\", \"ico\", \"gif\", \"j2k\", \"jpg\", \"jpeg\", \"jpg2\", \"jp2\", \"bmp\", \"ico\", \"iff\", \"mng\", \"pcd\", \"pic\", \"pict\", \"png\", \"psd\", \"psp\", \"pbm\", \"pgm\", \"ppm\", \"raw\", \"tga\", \"tiff\", \"tif\", \"wbmp\", \"xbm\", \"xcf\", \"xpm\"},\t// Bitmap image formats, see http://en.wikipedia.org/wiki/Graphics_file_format and http://en.wikipedia.org/wiki/Image_file_formats\n        {\"image_vector.png\", \"ai\", \"cgm\", \"dpx\", \"dxf\", \"eps\", \"emf\", \"ps\", \"svg\", \"svgz\", \"wmf\", \"xar\"},\t// Vector image formats, http://en.wikipedia.org/wiki/Graphics_file_format\n        {\"library.png\", \"dylib\", \"la\", \"o\", \"so\"},\t// Libraries\n        {\"linux.png\", \"deb\", \"rpm\"},\t// Linux packages\n        {\"macromedia_actionscript.png\", \"as\"},\t// Macromedia Actionscript\n        {\"macromedia_flash.png\", \"swf\", \"swd\", \"swa\", \"swc\", \"fla\", \"flv\", \"flp\", \"jsfl\"},\t// Macromedia Flash\n        {\"macromedia_freehand.png\", \"fh\", \"fhd\"},\t// Macromedia Freehand\n        {\"ms_excel.png\", \"xls\", \"xla\", \"xlb\", \"xlc\", \"xld\", \"xlk\", \"xll\", \"xlm\", \"xlr\", \"xlt\", \"xlv\", \"xlw\", \"xlshtml\", \"xlsmhtml\", \"xlthtml\", \"xlsx\", \"xltx\", \"xlsm\", \"xltm\", \"xlam\", \"xlsb\"},\t// Microsoft Excel\n        {\"ms_word.png\", \"doc\", \"wbk\", \"wiz\", \"wpg\", \"wpk\", \"wpm\", \"wpt\", \"wrs\", \"wwl\", \"docx\", \"dotx\", \"docm\", \"dotm\"},\t// Microsoft Word\n        {\"ms_powerpoint.png\", \"pcb\", \"pot\", \"ppa\", \"ppi\", \"pps\", \"ppt\", \"pwz\", \"pptx\", \"potx\", \"ppsx\", \"pptm\", \"potm\", \"ppsm\"},\t// Microsoft Office (Powerpoint)\n        {\"ms_visualstudio.png\",\t\"atp\", \"dbp\", \"hxc\", \"ncb\", \"pch\", \"pdb\", \"sln\", \"suo\", \"srf\", \"vaf\", \"vam\", \"vbg\", \"vbp\", \"vbproj\", \"vcproj\", \"vdp\", \"vdproj\", \"vip\", \"vmx\", \"vsdir\", \"vsmacros\",\t\"vsmproj\", \"vup\"},\t// Microsoft Visual Studio\n        {\"ms_windows_shortcut.png\", \"lnk\"},\t// MS Windows .lnk shortcut files\n        {\"pdf.png\", \"pdf\"},\t\t// Adobe Acrobat / PDF\n        {\"source.png\", \"asm\", \"asp\", \"bas\", \"bcp\", \"cbl\", \"cob\", \"f\", \"fpp\", \"inc\", \"js\", \"lsp\", \"m4\", \"pas\", \"pl\", \"py\", \"src\", \"vb\", \"vbe\", \"vbs\", \"x\"},\t// Languages for which there is no special icon (generic source icon)\n        {\"source.png\", \"awk\", \"csh\", \"esh\", \"sh\", \"ksh\", \"ws\", \"wsf\"},\t// Shell scripts\n        {\"source_c.png\", \"c\", \"cc\"},\t// C source\n        {\"source_c_header.png\", \"h\", \"hh\", \"hhh\"},\t// C header\n        {\"source_cplusplus.png\", \"cpp\", \"c++\"},\t// C++ source\n        {\"source_csharp.png\", \"c#=\", \"cs\"},\t// C# source\n        {\"source_java.png\", \"java\", \"jsp\"},\t// Java source\n        {\"source_php.png\", \"php\", \"php3\", \"php4\", \"php5\", \"phtm\", \"phtml\"},\t// PHP source\n        {\"source_ruby.png\", \"rb\", \"rbx\", \"rhtml\"},\t// Ruby source\n        {\"source_web.png\", \"html\", \"htm\", \"xhtml\", \"wml\", \"wmlc\", \"wmls\", \"wmlsc\", \"hdml\", \"xhdml\", \"chtml\", \"vrml\", \"torrent\", \"url\", \"css\"},\t// Web formats\n        {\"source_xml.png\", \"xml\", \"dtd\", \"xfd\", \"xfdl\", \"xmap\", \"xmi\", \"xsc\", \"xsd\", \"xsl\", \"xslt\", \"xtd\", \"xul\", \"rss\", \"jnlp\", \"plist\"},\t// XML-based formats\n        {\"text.png\", \"1st\", \"ans\", \"asc\", \"ascii\", \"diz\", \"err\", \"faq\", \"latex\", \"log\", \"man\", \"msg\", \"nfo\", \"readme\", \"rtf\", \"sig\", \"tex\", \"text\", \"txt\"},\t// Text formats\n        {\"vcard.png\", \"vcf\"},\t// vCard\n        {\"video.png\", \"3g2\", \"3gp\", \"3gp2\", \"3gpp\", \"asf\", \"asx\", \"avi\", \"dir\", \"dv\", \"dxr\", \"m1v\", \"m4e\", \"m4u\", \"moov\", \"mov\", \"movie\", \"mp4\", \"mpe\", \"mpeg\", \"mpg\", \"mpv2\", \"qt\", \"rm\", \"rmvb\", \"rts\", \"vob\", \"wmv\", \"divx\", \"mkv\"}\t\t// Video formats\n    };\n\n\n    /**\n     * Initializes the file extension map.\n     */\n    private static void init() {\n        // Map known file extensions to icon names\n        extensionMap = new HashMap<>();\n        for (String[] iconExt : ICON_EXTENSIONS) {\n            String iconName = iconExt[0];\n            for (int j = 1; j < iconExt.length; j++)\n                extensionMap.put(iconExt[j], iconName);\n        }\n\n        initialized = true;\n    }\n\n    /**\n     * Returns an icon symbolizing a symlink to the given target icon.\n     *\n     * @param targetIcon the icon representing the symlink's target\n     * @return an icon symbolizing a symlink to the given target\n     */\n    private static ImageIcon getSymlinkIcon(Icon targetIcon) {\n        return IconManager.getCompositeIcon(targetIcon, IconManager.getIcon(IconManager.IconSet.FILE, SYMLINK_ICON_NAME));\n    }\n\n\n    /////////////////////////////////////\n    // FileIconProvider implementation //\n    /////////////////////////////////////\n\n    public Icon getFileIcon(AbstractFile file, Dimension preferredResolution) {\n        // Call init, if not done already\n        if (!initialized) {\n            init();\n        }\n\n        if (file == null) {\n            return IconManager.getIcon(IconManager.IconSet.FILE, DISCONNECTED_ICON_NAME);\n        }\n\n        // If file is a symlink, get the linked file's icon and paint a semi-transparent symbolic icon on top of it\n        boolean isSymlink = file.isSymlink();\n        if (isSymlink) {\n            file = file.getCanonicalFile();\n        }\n\n        ImageIcon icon;\n        // Retrieve the file's extension, null if the file has no extension\n        String fileExtension = file.getExtension();\n\n        if (!file.exists()) {\n        \ticon = IconManager.getIcon(IconManager.IconSet.FILE, DISCONNECTED_ICON_NAME);\n        } else if (FileProtocols.ADB.equals(file.getURL().getScheme()) && file.isRoot()) {\n            icon = IconManager.getIcon(IconManager.IconSet.FILE, ANDROID_ICON_NAME);\n        }\n        // Special icon for the root of remote (non-local) locations\n        else if (!FileProtocols.FILE.equals(file.getURL().getScheme()) && file.isRoot()) {\n            icon = IconManager.getIcon(IconManager.IconSet.FILE, NETWORK_ICON_NAME);\n        }\n        // If file is a directory, use folder icon. One exception is made for 'app' extension under MAC OS\n        else if (file.isDirectory()) {\n            // Mac OS X application are directories with the .app extension and have a dedicated icon\n            icon = IconManager.getIcon(IconManager.IconSet.FILE, \"app\".equals(fileExtension) ? MAC_OS_X_APP_ICON_NAME : FOLDER_ICON_NAME);\n        }\n        // If the file is browsable (supported archive or other), use an icon symbolizing an archive\n        else if (file.isBrowsable()) {\n            icon = IconManager.getIcon(IconManager.IconSet.FILE, ARCHIVE_ICON_NAME);\n        }\n        // Regular file icon\n        else {\n            // Determine if the file's extension has an associated icon\n            if (fileExtension == null)\n                // File has no extension, use default file icon\n                icon = IconManager.getIcon(IconManager.IconSet.FILE, FILE_ICON_NAME);\n            else {\n                // Compare extension against lower-cased extensions\n                String iconName = extensionMap.get(fileExtension.toLowerCase());\n                if (iconName == null)    // No icon associated to extension, use default file icon\n                    icon = IconManager.getIcon(IconManager.IconSet.FILE, FILE_ICON_NAME);\n                else {\n                    // Retrieves the cached (or freshly loaded if not in cache already) ImageIcon instance corresponding to the icon's name\n                    icon = IconManager.getIcon(IconManager.IconSet.FILE, iconName);\n                    // Returned IconImage should never be null, but if it is (icon file missing), return default file icon\n                    if (icon == null) {\n                        return IconManager.getIcon(IconManager.IconSet.FILE, FILE_ICON_NAME);\n                    }\n                }\n            }\n        }\n\n        // If file is a symlink, paint a semi-transparent symbolic icon over the linked file's icon\n        if (isSymlink) {\n            return getSymlinkIcon(icon);\n        }\n\n        return icon;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/icon/EmptyIcon.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.icon;\n\nimport java.awt.Component;\nimport java.awt.Graphics;\n\nimport javax.swing.Icon;\n\n/**\n * Empty {@link Icon} in the specified size that can be used as a place holder\n * \n * @author Arik Hadas\n */\npublic class EmptyIcon implements Icon {\n\n\tprivate final int width;\n\tprivate final int height;\n\n\tpublic EmptyIcon(int size) {\n\t\tthis.width = size;\n\t\tthis.height = size;\n\t}\n\n\tpublic EmptyIcon(int width, int height) {\n\t\tthis.width = width;\n\t\tthis.height = height;\n\t}\n\n\tpublic int getIconHeight() {\n\t\treturn height;\n\t}\n\n\tpublic int getIconWidth() {\n\t\treturn width;\n\t}\n\n\tpublic void paintIcon(Component c, Graphics g, int x, int y) {\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/icon/FileIcons.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.icon;\n\nimport java.awt.Dimension;\nimport java.awt.Image;\n\nimport javax.swing.Icon;\nimport javax.swing.ImageIcon;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.icon.FileIconProvider;\nimport com.mucommander.commons.file.impl.adb.AdbFile;\nimport com.mucommander.commons.file.impl.ftp.FTPFile;\nimport com.mucommander.commons.file.impl.http.HTTPFile;\nimport com.mucommander.commons.file.impl.sftp.SFTPFile;\nimport com.mucommander.commons.runtime.OsFamily;\n\n/**\n * <code>FileIcons</code> provides several methods to retrieve file icons for a given file:\n * <ul>\n *  <li>{@link #getSystemFileIcon(AbstractFile)}: returns a system icon, provided by the underlying OS/desktop manager.\n *   Under supported platforms, those file icons are the same as the ones displayed in the default file manager.</li>\n *  <li>{@link #getCustomFileIcon(AbstractFile)}: returns a custom icon, fetched from the muCommander icon set and\n * based on the file's kind (archive, folder...) and extension.</li>\n *  <li>{@link #getFileIcon(AbstractFile)} returns either a system icon or a custom icon, depending on the current\n * system icons policy. The default policy is {@link #DEFAULT_SYSTEM_ICONS_POLICY} and can be changed using\n * {@link #setSystemIconsPolicy(String)}.</li>\n * </ul>\n * Icons can be requested indifferently for any type of {@link AbstractFile} files: local files, remote files,\n * archives entries... \n *\n * <p>It is important to note that not all platforms have proper support for system file icons.\n * The {@link #hasProperSystemIcons()} method can be used to determine if the current platform properly supports system\n * icons. Non-supported platforms may return no icon (<code>null</code> values), or icons that do not resemble the\n * system ones.\n *\n * @author Maxence Bernard\n */\npublic class FileIcons {\n\n    /** Never use system file icons */\n    public final static String USE_SYSTEM_ICONS_NEVER = \"never\";\n    /** Use system file icons only for applications */\n    public final static String USE_SYSTEM_ICONS_APPLICATIONS = \"applications\";\n    /** Always use system file icons */\n    public final static String USE_SYSTEM_ICONS_ALWAYS = \"always\";\n\n    /** Default policy for system icons */\n    private final static String DEFAULT_SYSTEM_ICONS_POLICY = OsFamily.MAC_OS_X.isCurrent() ? USE_SYSTEM_ICONS_ALWAYS : USE_SYSTEM_ICONS_APPLICATIONS;\n\n    /** Default icon scale factor (no rescaling) */\n    private final static float DEFAULT_SCALE_FACTOR = 1.0f;\n\n    /** Base width and height of icons for a scale factor of 1 */\n    private final static int BASE_ICON_DIMENSION = 16;\n\n    /** Controls if and when system file icons should be used instead of custom icons */\n    private static String systemIconsPolicy = DEFAULT_SYSTEM_ICONS_POLICY;\n\n    /** Current icon scale factor */\n    private static float scaleFactor = DEFAULT_SCALE_FACTOR;\n\n    /** FileIconProvider instance for custom icons */\n    private static FileIconProvider customFileIconProvider;\n\n    /** FileIconProvider instance for system icons */\n    private static FileIconProvider systemFileIconProvider;\n\n    /** Current dimension of returned file icons */\n    private static Dimension iconDimension = new Dimension((int)(BASE_ICON_DIMENSION * DEFAULT_SCALE_FACTOR), (int)(BASE_ICON_DIMENSION * DEFAULT_SCALE_FACTOR));\n\n\n    /*\n     * Initializes the system and custom file icon providers.\n     */\n    static {\n        setCustomFileIconProvider(new CustomFileIconProvider());\n        setSystemFileIconProvider(FileFactory.getDefaultFileIconProvider());\n    }\n\n\n    /**\n     * Shorthand for {@link #getFileIcon(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)} called with the\n     * icon dimension returned by {@link #getIconDimension()}.\n     *\n     * @param file the AbstractFile instance for which an icon will be returned\n     * @return an icon for the given file\n     * @see #getSystemIconsPolicy()\n     */\n    public static Icon getFileIcon(AbstractFile file) {\n        return getFileIcon(file, iconDimension);\n    }\n\n    /**\n     * Returns an icon for the given file and of the specified dimension.\n     * The returned icon will either be a system icon, or one from the custom icon set, depending on the current\n     * {@link #getSystemIconsPolicy() system icons policy}.\n     * If a system icon should have been returned for the specified file but could not be resolved\n     * ({@link #getSystemFileIcon(AbstractFile, Dimension)} returned <code>null</code>), an icon from the\n     * custom icon set will be returned instead. Therefore, this method never returns <code>null</code>.\n     *\n     * @param file the AbstractFile instance for which an icon will be returned\n     * @param iconDimension the icon's dimension\n     * @return an icon for the given file\n     * @see #getSystemIconsPolicy()\n     */\n    public static Icon getFileIcon(AbstractFile file, Dimension iconDimension) {\n        boolean systemIcon = USE_SYSTEM_ICONS_ALWAYS.equals(systemIconsPolicy);\n\n        if (USE_SYSTEM_ICONS_APPLICATIONS.equals(systemIconsPolicy)) {\n            systemIcon = com.mucommander.desktop.DesktopManager.isApplication(file);\n        }\n        if (file instanceof AdbFile && file.isRoot()) {\n            return IconManager.getIcon(IconManager.IconSet.FILE, \"android.png\");\n        } else if (file instanceof FTPFile || file instanceof HTTPFile || file instanceof SFTPFile) {\n            return IconManager.getIcon(IconManager.IconSet.FILE, CustomFileIconProvider.NETWORK_ICON_NAME);\n        }\n\n        if (systemIcon) {\n            Icon icon = getSystemFileIcon(file, iconDimension);\n            if (icon != null) {\n                return icon;\n            }\n            // If the system icon could not be resolved, return a custom file icon\n        }\n\n        return getCustomFileIcon(file, iconDimension);\n    }\n\n\n    /**\n     * Shorthand for {@link #getCustomFileIcon(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)} called with the\n     * icon dimension returned by {@link #getIconDimension()}.\n     *\n     * @param file the file for which an icon is to be returned\n     * @return a custom icon for the given file\n     */\n    private static Icon getCustomFileIcon(AbstractFile file) {\n        return getCustomFileIcon(file, iconDimension);\n    }\n\n    /**\n     * Returns an icon of the specified dimension for the given file. The icon is provided by the\n     * {@link #getCustomFileIconProvider() custom file icon provider}. This method is guaranteed to never return\n     * <code>null</code>.\n     *\n     * @param file the file for which an icon is to be returned\n     * @param iconDimension the icon's dimension\n     * @return a custom icon for the given file\n     * @see #getCustomFileIconProvider()\n     */\n    private static Icon getCustomFileIcon(AbstractFile file, Dimension iconDimension) {\n        return getFileProviderIcon(customFileIconProvider, file, iconDimension);\n    }\n\n    /**\n     * Shorthand for {@link #getSystemFileIcon(com.mucommander.commons.file.AbstractFile, java.awt.Dimension)} called with the\n     * icon dimension returned by {@link #getIconDimension()}.\n     *\n     * @param file the file for which an icon is to be returned\n     * @return a system icon for the given file\n     */\n    public static Icon getSystemFileIcon(AbstractFile file) {\n        return getSystemFileIcon(file, iconDimension);\n    }\n\n    /**\n     * Returns an icon of the specified dimension for the given file. The returned icon is provided by the\n     * underlying OS/desktop manager, using the {@link com.mucommander.commons.file.icon.FileIconProvider} currently set.\n     * Returns <code>null</code> if the icon couldn't be retrieved, either because the file doesn't exist or for\n     * any other reason.\n     *\n     * @param file the file for which an icon is to be returned\n     * @param iconDimension the icon's dimension\n     * @return a system icon for the given file\n     */\n    private static Icon getSystemFileIcon(AbstractFile file, Dimension iconDimension) {\n        return getFileProviderIcon(systemFileIconProvider, file, iconDimension);\n    }\n\n    /**\n     * Returns an icon of the specified dimension for the given file. The return icon is provided by the specified\n     * {@link FileIconProvider}. This method takes care of up/down-scaling the icon returned by the provider if it\n     * doesn't match the specified dimension.\n     *\n     * @param fip the FileIconProvider from which to fetch the icon\n     * @param file the file for which an icon is to be returned\n     * @param iconDimension the icon's dimension \n     * @return an icon for the specified file\n     */\n    private static Icon getFileProviderIcon(FileIconProvider fip, AbstractFile file, Dimension iconDimension) {\n        Icon icon = fip.getFileIcon(file, iconDimension);\n        if (icon == null) {\n            return null;\n        }\n\n        if (iconDimension.width == icon.getIconWidth() && iconDimension.height == icon.getIconHeight()) {\n            return icon;    // the icon already has the right dimension\n        }\n\n        // Scale the icon to the target dimension\n        ImageIcon imageIcon = IconManager.getImageIcon(icon);\n        return new ImageIcon(imageIcon.getImage().getScaledInstance(iconDimension.width, iconDimension.height, Image.SCALE_AREA_AVERAGING));\n    }\n\n\n    /**\n     * Returns the {@link com.mucommander.commons.file.icon.FileIconProvider} instance that provides 'custom' file icons.\n     *\n     * @return the FileIconProvider instance that provides 'custom' file icons.\n     */\n    private static FileIconProvider getCustomFileIconProvider() {\n        return customFileIconProvider;\n    }\n\n    /**\n     * Sets the {@link com.mucommander.commons.file.icon.FileIconProvider} instance that provides 'custom' file icons.\n     *\n     * @param fip the FileIconProvider instance that provides 'custom' file icons\n     */\n    private static void setCustomFileIconProvider(FileIconProvider fip) {\n        customFileIconProvider = fip;\n    }\n\n    /**\n     * Returns the {@link com.mucommander.commons.file.icon.FileIconProvider} instance that provides 'system' file icons.\n     *\n     * @return the FileIconProvider instance that provides 'custom' file icons.\n     */\n    public static FileIconProvider getSystemFileIconProvider() {\n        return systemFileIconProvider;\n    }\n\n    /**\n     * Sets the {@link com.mucommander.commons.file.icon.FileIconProvider} instance that provides 'custom' file icons.\n     *\n     * @param fip the FileIconProvider instance that provides 'custom' file icons\n     */\n    private static void setSystemFileIconProvider(FileIconProvider fip) {\n        systemFileIconProvider = fip;\n    }\n\n\n    /**\n     * Returns the dimension of file icons currently returned by this class, which is the base icon dimension (16x16)\n     * multiplied by the current scale factor.\n     *\n     * @return the dimension of file icons currently returned by this class\n     */\n    public static Dimension getIconDimension() {\n        return iconDimension;\n    }\n\n\t\n    /**\n     * Returns the current icon scale factor, initialized by default to {@link #DEFAULT_SCALE_FACTOR}.\n     *\n     * @return the current icon scale factor\n     */\n    public static float getScaleFactor() {\n        return scaleFactor;\n    }\n\n    /**\n     * Sets the current icon scale factor. The given value must be greater than 0.\n     *\n     * @param factor the new icon scale factor to use\n     * @throws IllegalArgumentException if factor is lower or equal to 0\n     */\n    public static void setScaleFactor(float factor) {\n        if (scaleFactor <= 0) {\n            throw new IllegalArgumentException(\"Scale factor must be greater than 0, (\" + factor + \")\");\n        }\n        scaleFactor = factor;\n        iconDimension = new Dimension((int)(BASE_ICON_DIMENSION *scaleFactor), (int)(BASE_ICON_DIMENSION*scaleFactor));\n    }\n\n\n    /**\n     * Returns the current system icons policy, controlling when system file icons should be used instead\n     * of custom file icons, see constant fields for possible values. The system icons policy is by default initialized\n     * to {@link #DEFAULT_SYSTEM_ICONS_POLICY}.\n     *\n     * @return the current system icons policy\n     */\n    public static String getSystemIconsPolicy() {\n        return systemIconsPolicy;\n    }\n\n\n    /**\n     * Sets the system icons policy, controlling when system file icons should be used instead of custom file icons.\n     * See constants fields for allowed values.\n     *\n     * @param policy the new system icons policy to use\n     */\n    public static void setSystemIconsPolicy(String policy) {\n        systemIconsPolicy = policy;\n    }\n\n\n    /**\n     * Returns <code>true</code> if the current platform is able to retrieve system icons that match the ones used in\n     * the OS's default file manager. If <code>false</code> is returned and {@link #getSystemFileIcon(com.mucommander.commons.file.AbstractFile)}\n     * is used or {@link #getFileIcon(com.mucommander.commons.file.AbstractFile)} together with a system policy different from\n     * {@link #USE_SYSTEM_ICONS_NEVER}, the returned icon will probably look very bad. \n     *\n     * @return true if the current platform is able to retrieve system icons that match the ones used in the OS's\n     * default file manager\n     */\n    public static boolean hasProperSystemIcons() {\n        return OsFamily.MAC_OS_X.isCurrent() || OsFamily.WINDOWS.isCurrent();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/icon/IconManager.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.icon;\n\nimport java.awt.*;\nimport java.awt.image.BufferedImage;\nimport java.net.MalformedURLException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.util.Hashtable;\nimport java.util.Map;\n\nimport javax.swing.Icon;\nimport javax.swing.ImageIcon;\n\nimport lombok.Getter;\nimport lombok.extern.slf4j.Slf4j;\n\nimport com.mucommander.commons.file.util.ResourceLoader;\nimport ru.trolsoft.macosx.RetinaImageIcon;\n\n/**\n * IconManager takes care of loading, caching, rescaling the icons contained inside the application's JAR file.\n *\n * @author Maxence Bernard\n */\n@Slf4j\npublic class IconManager {\n    public enum IconSet {\n        /** Designates the file icon set */\n        FILE(\"file\"),\n        /** Designates the action icon set */\n        ACTION(\"action\"),\n        /** Designates the toolbar icon set */\n        STATUS_BAR(\"status_bar\"),\n        /** Designates the table icon set */\n        COMMON(\"common\"),\n        /** Designates the preferences icon set */\n        PREFERENCES(\"preferences\"),\n        /** Designates the progress icon set */\n        PROGRESS(\"progress\"),\n        /** Designates the language icon set */\n        LANGUAGE(\"language\"),\n        /** Designates the trolcommander icon set */\n        TROLCOMMANDER(\"trolcommander\"),\n        /** Other images */\n        MISC(\"misc\");\n\n        /** Base folder of all images */\n        private final static String BASE_IMAGE_FOLDER = \"/images/\";\n\n        /** Icon sets folders within the application's JAR file\n         * -- GETTER --\n         *  Returns the path to the folder that contains the image resource files of the given icon set.\n         *  The returned path is relative to the application JAR file's root and contains a trailing slash.\n         */\n        @Getter\n        private final String folder;\n\n        /** Caches for the different icon sets */\n        private final Map<String, ImageIcon> cache = new Hashtable<>();\n\n        IconSet(String folder) {\n            this.folder = BASE_IMAGE_FOLDER + folder + '/';\n        }\n\n        private Map<String, ImageIcon> getCache() {\n            return cache;\n        }\n    }\n\n    /**\n     * Creates a new instance of IconManager.\n     */\n    private IconManager() {}\n\n\n    /**\n     * Creates and returns an ImageIcon instance using the specified icon path and scale factor. No caching.\n     *\n     * @param iconPath path of the icon resource inside the application's JAR file\n     * @param scaleFactor the icon scale factor, <code>1.0f</code> to have the icon in its original size (no rescaling)\n     */\n    public static ImageIcon getIcon(String iconPath, float scaleFactor) {\n        URL resourceURL = ResourceLoader.getResourceAsURL(iconPath);\n        if (resourceURL == null) {\n            log.debug(\"Warning: attempt to load non-existing icon: \" + iconPath + \" , icon missing ?\");\n            return null;\n        }\n        ImageIcon icon;\n        if (RetinaImageIcon.IS_RETINA) {\n            icon = new RetinaImageIcon(getRetinaUrl(resourceURL));\n            if (icon.getImageLoadStatus() == MediaTracker.ERRORED) {\n                icon = new ImageIcon(resourceURL);\n            }\n        } else {\n            icon = new ImageIcon(resourceURL);\n        }\n        //ImageIcon icon = new ImageIcon(resourceURL);\n        return scaleFactor == 1.0f ? icon : getScaledIcon(icon, scaleFactor);\n    }\n\n    private static URL getRetinaUrl(URL url) {\n        String path = url.toString();\n        if (path.endsWith(\".png\") && !path.contains(\"@\")) {\n            try {\n                return new URI(path.replace(\".png\", \"@2x.png\")).toURL();\n            } catch (MalformedURLException | URISyntaxException e) {\n                log.error(\"getRetinaUrl error\", e);\n            }\n        }\n        return url;\n    }\n\n\n    /**\n     * Convenience method, calls and returns the result of {@link #getIcon(String, float) getIcon(iconPath, scaleFactor)}\n     * with a scale factor of 1.0f (no rescaling).\n     */\n    public static ImageIcon getIcon(String iconPath) {\n        return getIcon(iconPath, 1.0f);\n    }\n\n\n    /**\n     * Returns a scaled version of the given ImageIcon instance, using the specified scale factor.\n     *\n     * @param icon the icon to scale.\n     * @param scaleFactor the icon scale factor, <code>1.0f</code> to have the icon in its original size (no rescaling)\n     */\n    public static ImageIcon getScaledIcon(ImageIcon icon, float scaleFactor) {\n        if (scaleFactor == 1.0f || icon == null) {\n            return icon;\n        }\n\n        Image image = icon.getImage();\n        return new ImageIcon(image.getScaledInstance((int)(scaleFactor*image.getWidth(null)), (int)(scaleFactor*image.getHeight(null)), Image.SCALE_AREA_AVERAGING));\n    }\n\n    /**\n     * Returns a 'composite' icon made by composing the two given icons: the <code>backgroundIcon</code> is painted\n     * first, and the <code>foregroundIcon</code> is superposed, letting its non-transparent pixels reveal the\n     * background icon.\n     * For this method to provide a meaningful result, the two icons should have the same dimensions and the\n     * <code>foreground</code> should have some transparent pixels.\n     *\n     * @param backgroundIcon the icon that is painted first\n     * @param foregroundIcon the icon that is superposed above backgroundIcon, should use transparency\n     * @return a 'composite' icon made by composing the two given icons\n     */\n    public static ImageIcon getCompositeIcon(Icon backgroundIcon, Icon foregroundIcon) {\n        BufferedImage bi = new BufferedImage(backgroundIcon.getIconWidth(), backgroundIcon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);\n\n        Graphics g = bi.getGraphics();\n        backgroundIcon.paintIcon(null, g, 0, 0);\n        foregroundIcon.paintIcon(null, g, 0, 0);\n\n        return new ImageIcon(bi);\n    }\n\n\n    /**\n     * Returns an icon in the specified icon set and with the given name. If a scale factor other than 1.0f is passed,\n     * the return icon will be scaled accordingly.\n     *\n     * <p>If the icon set has a cache, first looks for an existing instance in the cache, and if it couldn't be found, \n     * create an instance and store it in the cache for future access. Note that the cached icon is unscaled, i.e.\n     * the scaled icon is not cached.\n     *\n     * @param iconSet an icon set (see public constants for possible values)\n     * @param iconName filename of the icon to retrieve\n     * @param scaleFactor the icon scale factor, <code>1.0f</code> to have the icon in its original size (no rescaling)\n     * @return an ImageIcon instance corresponding to the specified icon set, name and scale factor,\n     * <code>null</code> if the image wasn't found or couldn't be loaded\n     */\n    public static ImageIcon getIcon(IconSet iconSet, String iconName, float scaleFactor) {\n        ImageIcon icon = getIcon(iconSet, iconName);\n        return icon == null || scaleFactor == 1.0f ? icon : getScaledIcon(icon, scaleFactor);\n    }\n\n\n    /**\n     * Convenience method, calls and returns the result of {@link #getIcon(IconSet, String, float) getIcon(iconSet, iconName, scaleFactor)}\n     * with a scale factor of 1.0f (no rescaling).\n     */\n    public static ImageIcon getIcon(IconSet iconSet, String iconName) {\n        Map<String, ImageIcon> cache = iconSet.getCache();\n        if (cache == null) {\n            // No caching, simply create the icon\n            return getIcon(iconSet.getFolder() + iconName);\n        }\n        // Look for the icon in the cache\n        ImageIcon icon = cache.get(iconName);\n        if (icon == null) {\n            // Icon is not in the cache, let's create it\n            icon = getIcon(iconSet.getFolder()+iconName);\n            // and add it to the cache if icon exists\n            if (icon != null) {\n                cache.put(iconName, icon);\n            }\n        }\n\n        return icon;\n    }\n\n\n    /**\n     * Returns an icon made of the specified icon and some transparent space around it.\n     *\n     * @param icon the original icon, will be painted at the center of the new icon\n     * @param insets specifies the dimensions of the transparent space around the returned icon\n     * @return an icon made of the specified icon and some transparent space around it\n     */\n    public static ImageIcon getPaddedIcon(ImageIcon icon, Insets insets) {\n        if (icon instanceof RetinaImageIcon) {\n            return RetinaImageIcon.buildPaddedIcon(icon, insets);\n        }\n        BufferedImage bi = new BufferedImage(\n                icon.getIconWidth() + insets.left + insets.right,\n                icon.getIconHeight() + insets.top + insets.bottom,\n                BufferedImage.TYPE_INT_ARGB);\n\n        Graphics g = bi.getGraphics();\n        g.drawImage(icon.getImage(), insets.left, insets.top, null);\n\n        return new ImageIcon(bi);\n    }\n\n\n\n    /**\n     * Creates and returns an ImageIcon with the same content and dimensions. This method is useful when an ImageIcon\n     * is needed and only an Icon is available.\n     *\n     * <p>If the given Icon is already an ImageIcon, the same instance is returned. If it is not, a new ImageIcon is \n     * created and returned.\n     */\n    public static ImageIcon getImageIcon(Icon icon) {\n        if (icon instanceof ImageIcon) {\n            return (ImageIcon) icon;\n        }\n\n        BufferedImage bi = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);\n        icon.paintIcon(null, bi.getGraphics(), 0, 0);\n\n        return new ImageIcon(bi);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/icon/SpinningDial.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.icon;\n\nimport java.awt.*;\nimport java.awt.image.BufferedImage;\nimport java.util.Arrays;\n\n/**\n * Animated icon of a spinning dial used to notify users that an application is performing a getTask.\n * <p>\n * This behaves as any animated icon except for one thing: when the animation is stopped using\n * {@link #setAnimated(boolean)}, the dial won't be displayed anymore until the animation is\n * resumed.\n *\n * <p>\n * This heavily borrows code from Technomage's <code>furbelow</code> package, distributed\n * under the GNU Lesser General Public License.<br>\n * The original source code can be found <a href=\"http://furbelow.svn.sourceforge.net/viewvc/furbelow/trunk/src/furbelow\">here</a>.\n *\n * @author twall, Nicolas Rinaudo\n */\npublic class SpinningDial extends AnimatedIcon {\n    /** Default creation animation status. */\n    public  static final boolean DEFAULT_ANIMATE    = false;\n    /** Dial's default color. */\n    public  static final Color DEFAULT_COLOR        = Color.BLACK;\n    /** Minimum alpha-transparency value that must be applied to the dial's color as it fades out. */\n    private static final int   MIN_ALPHA            = 32;\n    /** Icon's default width and height. */\n    public  static final int   DEFAULT_SIZE         = 16;\n    /** Default number of spokes in the dial. */\n    public  static final int   DEFAULT_SPOKES       = 12;\n    /** Dial's full size, will be scaled down at paint time. */\n    private static final int   FULL_SIZE            = 256;\n    /** Width of each of the dial's strokes. */\n    private static final float DEFAULT_STROKE_WIDTH = FULL_SIZE / 10f;\n    /** Scale down factor for the dial. */\n    private static final float FRACTION             = 0.6f;\n\n\n\n    /** Icon's width. */\n    private final int     width;\n    /** Icon's height. */\n    private final int     height;\n    /** All images that compose the spinning dial. */\n    private final Image[] frames;\n    /** Color used to paint the dial. */\n    private Color   color;\n    /** Width of each stroke. */\n    private float   strokeWidth;\n\n\n\n    /**\n     * Creates a new spinning dial.\n     * <p>\n     * The new instance will be initialized using default values:\n     * <ul>\n     *   <li>{@link #DEFAULT_SIZE} for its width and height.</li>\n     *   <li>{@link #DEFAULT_COLOR} for its color.</li>\n     *   <li>{@link #DEFAULT_SPOKES} for its number of spokes.</li>\n     * </ul>\n     *\n     * <p>\n     * A dial created that way will not be displayed until {@link #setAnimated(boolean)} is\n     * called to animate it.\n     */\n    public SpinningDial() {this(DEFAULT_SIZE, DEFAULT_SIZE);}\n\n    /**\n     * Creates a new spinning dial.\n     * <p>\n     * The new instance will be initialized using default values:\n     * <ul>\n     *   <li>{@link #DEFAULT_SIZE} for its width and height.</li>\n     *   <li>{@link #DEFAULT_COLOR} for its color.</li>\n     *   <li>{@link #DEFAULT_SPOKES} for its number of spokes.</li>\n     * </ul>\n     *\n     * @param animate whether to animate the dial immediately or not.\n     */\n    public SpinningDial(boolean animate) {this(DEFAULT_SIZE, DEFAULT_SIZE, animate);}\n\n    /**\n     * Creates a new spinning dial with the specified color.\n     * <p>\n     * The new instance will be initialized using default values:\n     * <ul>\n     *   <li>{@link #DEFAULT_SIZE} for its width and height.</li>\n     *   <li>{@link #DEFAULT_SPOKES} for its number of spokes.</li>\n     * </ul>\n     *\n     * <p>\n     * A dial created that way will not be displayed until {@link #setAnimated(boolean)} is\n     * called to animate it.\n     *\n     * @param c color in which to paint the dial.\n     */\n    public SpinningDial(Color c) {this(DEFAULT_SIZE, DEFAULT_SIZE, c);}\n\n    /**\n     * Creates a new spinning dial with the specified color.\n     * <p>\n     * The new instance will be initialized using default values:\n     * <ul>\n     *   <li>{@link #DEFAULT_SIZE} for its width and height.</li>\n     *   <li>{@link #DEFAULT_SPOKES} for its number of spokes.</li>\n     * </ul>\n     *\n     * @param c       color in which to paint the dial.\n     * @param animate whether to animate the dial immediately or not.\n     */\n    public SpinningDial(Color c, boolean animate) {this(DEFAULT_SIZE, DEFAULT_SIZE, c, animate);}\n\n    /**\n     * Creates a new spinning dial with the specified dimensions.\n     * <p>\n     * The new instance will be initialized using default values:\n     * <ul>\n     *   <li>{@link #DEFAULT_COLOR} for its color.</li>\n     *   <li>{@link #DEFAULT_SPOKES} for its number of spokes.</li>\n     * </ul>\n     *\n     * <p>\n     * A dial created that way will not be displayed until {@link #setAnimated(boolean)} is\n     * called to animate it.\n     *\n     * @param w width of the icon.\n     * @param h height of the icon.\n     */\n    public SpinningDial(int w, int h) {this(w, h, DEFAULT_SPOKES);}\n\n    /**\n     * Creates a new spinning dial with the specified dimensions.\n     * <p>\n     * The new instance will be initialized using default values:\n     * <ul>\n     *   <li>{@link #DEFAULT_COLOR} for its color.</li>\n     *   <li>{@link #DEFAULT_SPOKES} for its number of spokes.</li>\n     * </ul>\n     *\n     * @param w       width of the icon.\n     * @param h       height of the icon.\n     * @param animate whether to animate the dial immediately or not.\n     */\n    public SpinningDial(int w, int h, boolean animate) {this(w, h, DEFAULT_SPOKES, animate);}\n\n    /**\n     * Creates a new spinning dial with the specified dimensions and color.\n     * <p>\n     * The new instance will use {@link #DEFAULT_SPOKES} for its number of spokes.\n     *\n     * <p>\n     * A dial created that way will not be displayed until {@link #setAnimated(boolean)} is\n     * called to animate it.\n     *\n     * @param w width of the icon.\n     * @param h height of the icon.\n     * @param c color in which to paint the dial.\n     */\n    public SpinningDial(int w, int h, Color c) {this(w, h, DEFAULT_SPOKES, c);}\n\n    /**\n     * Creates a new spinning dial with the specified dimensions and color.\n     * <p>\n     * The new instance will use {@link #DEFAULT_SPOKES} for its number of spokes.\n     *\n     * @param w       width of the icon.\n     * @param h       height of the icon.\n     * @param c       color in which to paint the dial.\n     * @param animate whether to animate the dial immediately or not.\n     */\n    public SpinningDial(int w, int h, Color c, boolean animate) {this(w, h, DEFAULT_SPOKES, c, animate);}\n\n    /**\n     * Creates a new spinning dial with the specified dimensions and number of spokes.\n     * <p>\n     * The new instance will use {@link #DEFAULT_COLOR} for its color.\n     *\n     * <p>\n     * A dial created that way will not be displayed until {@link #setAnimated(boolean)} is\n     * called to animate it.\n     *\n     * @param w      width of the icon.\n     * @param h      height of the icon.\n     * @param spokes number of spokes that compose the dial.\n     */\n    public SpinningDial(int w, int h, int spokes) {this(w, h, spokes, DEFAULT_COLOR);}\n\n    /**\n     * Creates a new spinning dial with the specified dimensions and number of spokes.\n     * <p>\n     * The new instance will use {@link #DEFAULT_COLOR} for its color.\n     *\n     * @param w       width of the icon.\n     * @param h       height of the icon.\n     * @param spokes  number of spokes that compose the dial.\n     * @param animate whether to animate the dial immediately or not.\n     */\n    public SpinningDial(int w, int h, int spokes, boolean animate) {this(w, h, spokes, DEFAULT_COLOR, animate);}\n\n    /**\n     * Creates a new spinning dial with the specified characteristics.\n     * <p>\n     * A dial created that way will not be displayed until {@link #setAnimated(boolean)} is\n     * called to animate it.\n     *\n     * @param w      width of the icon.\n     * @param h      height of the icon.\n     * @param spokes number of spokes that compose the dial.\n     * @param c      color in which to paint the dial.\n     */\n    public SpinningDial(int w, int h, int spokes, Color c) {this(w, h, spokes, c, DEFAULT_ANIMATE);}\n\n    /**\n     * Creates a new spinning dial with the specified characteristics.\n     * @param w       width of the icon.\n     * @param h       height of the icon.\n     * @param spokes  number of spokes that compose the dial.\n     * @param c       color in which to paint the dial.\n     * @param animate whether to animate the dial immediately or not.\n     */\n    public SpinningDial(int w, int h, int spokes, Color c, boolean animate) {\n        super(spokes, 1000 / spokes);\n\n        // Initializes the icon.\n        width       = w;\n        height      = h;\n        color       = c;\n        frames      = new Image[getFrameCount()];\n        strokeWidth = DEFAULT_STROKE_WIDTH;\n\n        // Animates the icon if necessary.\n        if(animate)\n            setAnimated(true);\n    }\n\n    /**\n     * Sets the width of the strokes used to paint each of the dial's spokes.\n     * @param width width of the strokes used to paint each of the dial's spokes.\n     */\n    public synchronized void setStrokeWidth(float width) {strokeWidth = width;}\n\n    /**\n     * Returns the width of the strokes used to paint each of the dial's spokes.\n     * @return the width of the strokes used to paint each of the dial's spokes.\n     */\n    public synchronized float getStrokeWidth() {return strokeWidth;}\n\n\n\n    /**\n     * Sets the color used to draw the dial.\n     * @param c color in which to paint the dial.\n     */\n    public synchronized void setColor(Color c) {\n        // Ignores calls that don't actually change anything.\n        if (!color.equals(c)) {\n            color = c;\n\n            // Resets stored images to make sure they get repainted with the right color.\n            Arrays.fill(frames, null);\n        }\n    }\n\n    /**\n     * Returns the color used to paint the dial.\n     * @return the color used to paint the dial.\n     */\n    public synchronized Color getColor() {return color;}\n\n    /**\n     * Computes the dial color according to the specified alpha-transparency value.\n     * @param alpha transparency value that must be applied to the dial's color.\n     */\n    protected Color getSpokeColor(int alpha) {return new Color(color.getRed(), color.getGreen(), color.getBlue(), Math.max(MIN_ALPHA, alpha));}\n\n\n\n    /**\n     * Returns the icon's height.\n     * @return the icon's height.\n     */\n    @Override\n    public int getIconHeight() {\n        return height;\n    }\n\n    /**\n     * Returns the icon's width.\n     * @return the icon's width.\n     */\n    @Override\n    public int getIconWidth() {\n        return width;\n    }\n\n\n\n    /**\n     * Initializes graphics for painting one of the dial's frames.\n     * @param graphics graphics instance to initialize.\n     */\n    private void initializeGraphics(Graphics2D graphics) {\n        float scale = (float)Math.min(width, height) / FULL_SIZE;\n\n        graphics.setComposite(AlphaComposite.Clear);\n        graphics.fillRect(0, 0, width, height);\n        graphics.setComposite(AlphaComposite.Src);\n        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);\n        graphics.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));\n        graphics.translate((float)width / 2, (float)height / 2);\n        graphics.scale(scale, scale);\n    }\n\n    /**\n     * Paints the current frame on the specified component.\n     * @param c        component on which to paint the dial.\n     * @param graphics graphic context to use when painting the dial.\n     * @param x        horizontal coordinate at which to paint the dial.\n     * @param y        vertical coordinate at which to paint the dial.\n     */\n    @Override\n    public synchronized void paintFrame(Component c, Graphics graphics, int x, int y) {\n        int currentFrame;\n\n        // Ignores paint calls while not animated.\n        if (isAnimated()) {\n            // Checks whether the current frame has already been generated or not, generates\n            // it if not.\n            if ((frames[currentFrame = getFrame()]) == null) {\n                // Initializes the frame.\n                // Note: getGraphicsConfiguration() returns null if the component has not yet been added to a container\n                GraphicsConfiguration gc = c != null ? c.getGraphicsConfiguration() : null;\n                Image frame = gc != null ? gc.createCompatibleImage(width, height, Transparency.TRANSLUCENT)\n                        : new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);\n\n                // Initializes the frame's g.\n                Graphics2D g = (Graphics2D)frame.getGraphics();\n                initializeGraphics(g);\n\n                // Draws each spoke in the dial.\n                int alpha  = 255;\n                int radius = FULL_SIZE / 2 - 1 - (int)(strokeWidth / 2);\n                for (int i = 0; i < getFrameCount(); i++) {\n                    double cos = Math.cos((Math.PI * 2) - (Math.PI * 2 * (i - currentFrame)) / getFrameCount());\n                    double sin = Math.sin((Math.PI * 2) - (Math.PI * 2 * (i - currentFrame)) / getFrameCount());\n\n                    g.setColor(getSpokeColor(alpha));\n                    g.drawLine((int)(radius * FRACTION * cos), (int)(radius * FRACTION * sin),\n                               (int)(radius * cos), (int)(radius * sin));\n                    alpha = Math.max(MIN_ALPHA, (alpha * 3) / 4);\n                }\n                g.dispose();\n\n                // Stores the newly generated frame.\n                frames[currentFrame] = frame;\n            }\n\n            // Draws the current frame.\n            graphics.drawImage(frames[currentFrame], x, y, null);\n        }\n    }\n\n    /**\n     * Starts / stops the spinning dial.\n     * <p>\n     * If <code>a</code> is <code>false</code>, the animation will stop and the\n     * dial won't be displayed anymore until the animation resumes.\n     *\n     * @param a whether to start or stop the animation.\n     */\n    @Override\n    public void setAnimated(boolean a) {\n        super.setAnimated(a);\n\n        // Makes sure the dial disappears when the animation is stopped.\n        if (!a) {\n            repaint();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/layout/AsyncPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.layout;\n\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.icon.SpinningDial;\nimport ru.trolsoft.ui.ZxSpectrumLoadPane;\n\nimport javax.swing.*;\nimport javax.swing.border.EmptyBorder;\nimport javax.swing.event.AncestorEvent;\nimport javax.swing.event.AncestorListener;\nimport java.awt.*;\n\n/**\n * <code>AsyncPanel</code> is a <code>JPanel</code> aimed at components that potentially take a long time to\n * initialize. It allows to deport their initialization in a separate thread as to not lock the event thread.\n * It works as follows:\n * <ol>\n *  <li>Initially, AsyncPanel displays a 'please wait component' that symbolizes the fact that the contents of the\n *      panel is being loaded.</li>\n *  <li>Then the initialization of the component will be executed in background thread by the method {@link #initTargetComponent()}</li>\n *  <li>When AsyncPanel becomes visible on screen, the {@link #getTargetComponent(Exception)} method is called to trigger the\n *      initialization of the real component to display.</li>\n *  <li>As soon as the method returns, the wait component is removed and the target component added to AsyncPanel.\n *      If AsyncPanel is the child of a <code>java.awt.Window</code>, the window is repacked to take into account the\n *      new size of this panel.</li>\n * </ol>\n *\n * <p>This panel tries to be as 'transparent' as possible for the target component: the borders of this panel are empty\n * and its layout is a <code>BorderLayout</code> where the target component is added to the center.\n *\n * @author Maxence Bernard\n */\npublic abstract class AsyncPanel extends ZxSpectrumLoadPane {\n\n    private class InitWorker extends SwingWorker<Void, Void> {\n        private Exception exception;\n        volatile boolean finished = false;\n\n        @Override\n        protected Void doInBackground() {\n            try {\n                initTargetComponent();\n            } catch (Exception e) {\n                exception = e;\n            }\n            return null;\n        }\n\n        @Override\n        protected void done() {\n            if (finished) {\n                return;\n            }\n            finished = true;\n            finish();\n        }\n\n        private void finish() {\n            JComponent targetComponent = getTargetComponent(exception);\n            remove(waitComponent);\n            setBorder(new EmptyBorder(0, 0, 0, 0));\n            add(targetComponent, BorderLayout.CENTER);\n            updateLayout();\n            // Force update viewer/editor window on Windows 8.1\n            if (OsFamily.WINDOWS.isCurrent()) {\n                setSize(getSize());\n            }\n            stop();\n        }\n    }\n\n    /** The component displayed while the target component is being loaded */\n    private final JComponent waitComponent;\n\n    /** This field becomes true when this panel has become visible on screen. */\n    private boolean visibleOnScreen;\n\n    private InitWorker worker;\n\n    /**\n     * Creates a new <code>AsyncPanel</code> with the default wait component.\n     */\n    protected AsyncPanel() {\n        this(getDefaultWaitComponent());\n    }\n\n    /**\n     * Creates a new <code>AsyncPanel</code> that displays the given component while the target component is being\n     * loaded.\n     *\n     * @param waitComponent the component to display while the target component is being loaded\n     */\n    private AsyncPanel(JComponent waitComponent) {\n        super(new BorderLayout());\n        start();\n        this.waitComponent = waitComponent;\n        add(waitComponent, BorderLayout.CENTER);\n\n        // Starts loading the component when this panel has become visible on screen\n        addAncestorListener(new AncestorListener() {\n\n            public void ancestorAdded(AncestorEvent e) {\n                if (visibleOnScreen) {\n                    return;\n                }\n\n                visibleOnScreen = true;\n                removeAncestorListener(this);\n\n//                loadTargetComponent();\n                worker = new InitWorker();\n                worker.execute();\n            }\n\n            public void ancestorRemoved(AncestorEvent event) {}\n\n            public void ancestorMoved(AncestorEvent event) {}\n        });\n\n    }\n\n\n    protected void cancel() {\n        stop();\n        if (worker != null) {\n            worker.cancel(true);\n        }\n    }\n\n    /*\n     * Loads the target component by calling {@link #getTargetComponent()} and replace the wait component by it.\n     */\n\n//    private void loadTargetComponent() {\n//        new Thread() {\n//            @Override\n//            public void init() {\n//                JComponent targetComponent = getTargetComponent();\n//\n//                remove(waitComponent);\n//                setBorder(new EmptyBorder(0, 0, 0, 0));\n//\n//                add(targetComponent, BorderLayout.CENTER);\n//\n//                updateLayout();\n//            }\n//        }.start();\n//    }\n\n    /**\n     * Returns the default component to be displayed while the target component is being loaded.\n     *\n     * @return the default component to be displayed while the target component is being loaded\n     */\n    private static JComponent getDefaultWaitComponent() {\n        JLabel label = new JLabel(Translator.get(\"loading\"));\n        label.setIcon(new SpinningDial(24, 24, true));\n\n        // Center the label both horizontally and vertically\n        JPanel tempPanel = new JPanel(new GridBagLayout());\n\n        GridBagConstraints gbc = new GridBagConstraints();\n        gbc.gridx = 0;\n        gbc.gridy = 0;\n        gbc.fill = GridBagConstraints.BOTH;\n\n        tempPanel.add(label, gbc);\n        return tempPanel;\n    }\n\n    \n    /**\n     * Packs the parent Window that contains this component, if any. This method is called once the target component\n     * has been made initialized and added to this panel. This method can be overridden by subclasses if additional work\n     * needs to be done to update the layout.\n     */\n    protected void updateLayout() {\n    }\n\n\n    public abstract JComponent getTargetComponent(Exception e);\n\n    public abstract void initTargetComponent() throws Exception;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/layout/CompareImagesPanel.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.layout;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.icon.IconManager;\nimport com.mucommander.ui.icon.SpinningDial;\n\nimport javax.imageio.ImageIO;\nimport javax.swing.*;\nimport java.awt.Dimension;\nimport java.awt.Graphics;\nimport java.awt.Image;\nimport java.io.IOException;\n\n/**\n * Created on 24/01/14.\n */\npublic class CompareImagesPanel extends JPanel {\n\n    private static final int MAX_PREVIEW_WIDTH = 150;\n    private static final int MAX_PREVIEW_HEIGHT = 100;\n\n    private final Image[] images = new Image[2];\n    private final int[] imgWidth = new int[2];\n    private final int[] imgHeight = new int[2];\n    private final int[] imgSrcWidth = new int[2];\n    private final int[] imgSrcHeight = new int[2];\n\n    private final JLabel[] lblImageSize = new JLabel[2];\n\n    private final JDialog parent;\n    private final ImageIcon arrow;\n    private final SpinningDial[] dials = new SpinningDial[2];\n\n    private class LoadImagesTask extends SwingWorker<Void, Void> {\n        private final AbstractFile file;\n        private final int index;\n\n        public LoadImagesTask(AbstractFile file, int index) {\n            super();\n            this.file = file;\n            this.index = index;\n        }\n\n        @Override\n        protected Void doInBackground() {\n            if (file == null) {\n                return null;\n            }\n            try {\n                Image image = loadImage(file);\n                imgSrcWidth[index] = image.getWidth(null);\n                imgSrcHeight[index] = image.getHeight(null);\n                image = scaleImage(image);\n                images[index] = image;\n                imgWidth[index] = image.getWidth(null);\n                imgHeight[index] = image.getHeight(null);\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n            return null;\n        }\n\n        @Override\n        protected void done() {\n            synchronized (CompareImagesPanel.this) {\n                if (lblImageSize[index] != null) {\n                    lblImageSize[index].setText(imgSrcWidth[index] + \" x \" + imgSrcHeight[index]);\n                }\n                int panelWidth = 10 + imgWidth[0] + imgWidth[1] + arrow.getIconWidth();\n                int panelHeight = Math.max(imgHeight[0], imgHeight[1]);\n                panelHeight = Math.max(panelHeight, arrow.getIconHeight());\n                panelWidth = Math.max(panelWidth, getWidth());\n                setPreferredSize(new Dimension(panelWidth, panelHeight));\n                if (dials[index] != null) {\n                    dials[index].setAnimated(false);\n                }\n                repaint();\n                CompareImagesPanel.this.revalidate();\n                parent.revalidate();\n                parent.pack();\n            }\n        }\n    }\n\n    public CompareImagesPanel(AbstractFile file1, AbstractFile file2, JDialog parent, JLabel lblSize1, JLabel lblSize2) {\n        super();\n        this.parent = parent;\n        this.lblImageSize[0] = lblSize1;\n        this.lblImageSize[1] = lblSize2;\n        new LoadImagesTask(file1, 0).execute();\n        new LoadImagesTask(file2, 1).execute();\n\n        dials[0] = new SpinningDial();\n        dials[0].setAnimated(true);\n\n\n        if (file2 != null) {\n            dials[1] = new SpinningDial();\n            dials[1].setAnimated(true);\n        }\n        arrow = IconManager.getIcon(IconManager.IconSet.MISC, \"replace_arrow.png\");\n    }\n\n\n    @Override\n    public void paint(Graphics g) {\n        super.paint(g);\n        if (images[0] != null) {\n            g.drawImage(images[0], (MAX_PREVIEW_WIDTH-imgWidth[0])/2, (getHeight() - imgHeight[0])/2, null);\n        } else {\n            dials[0].paintIcon(this, g, (MAX_PREVIEW_WIDTH-dials[0].getIconWidth())/2, (getHeight() - dials[0].getIconHeight())/2);\n        }\n        if (images[1] != null) {\n            g.drawImage(images[1], getWidth() - (MAX_PREVIEW_WIDTH+imgWidth[1])/2, (getHeight() - imgHeight[1])/2, null);\n        } else if (dials[1] != null){\n            dials[1].paintIcon(this, g, getWidth() - (MAX_PREVIEW_WIDTH+dials[1].getIconWidth())/2, (getHeight() - dials[1].getIconHeight())/2);\n        }\n        if (lblImageSize[1] != null) {\n            arrow.paintIcon(this, g, (getWidth() - arrow.getIconWidth())/2, (getHeight() - arrow.getIconHeight())/2);\n        }\n    }\n\n    private static Image loadImage(AbstractFile file) throws IOException {\n        return ImageIO.read(file.getInputStream());\n    }\n\n    private static Image scaleImage(Image src) {\n        final int srcWidth = src.getWidth(null);\n        final int srcHeight = src.getHeight(null);\n        if (srcWidth <= MAX_PREVIEW_HEIGHT && srcHeight <= MAX_PREVIEW_HEIGHT) {\n            return src;\n        }\n        final double zoomX = 1.0 * srcWidth / MAX_PREVIEW_WIDTH;\n        final double zoomY = 1.0 * srcHeight / MAX_PREVIEW_HEIGHT;\n        final double zoom = Math.max(zoomX, zoomY);\n        int outWidth = (int)(1.0 * srcWidth / zoom);\n        int outHeight = (int)(1.0 * srcHeight / zoom);\n\n        return src.getScaledInstance(outWidth, outHeight, Image.SCALE_FAST);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/layout/FluentPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.layout;\n\nimport java.awt.Component;\nimport java.awt.LayoutManager;\n\nimport javax.swing.JPanel;\n\n/**\n * This panel should be used instead of creating temporary panels only for layout purpose. \n * Using Fluent Interface technique, the add component calls can be chained and the resulting\n * code becomes more readable.\n * \n * Note: The caller to {@link #add(Component)} should be very careful not to assume\n * the returned object is the object that was just added (as in {@link JPanel#add(Component)})\n * \n * @author Arik Hadas\n */\npublic class FluentPanel extends JPanel {\n\n\tpublic FluentPanel(LayoutManager layoutManager) {\n\t\tsuper(layoutManager);\n\t}\n\t\n\t@Override\n\tpublic FluentPanel add(Component comp) {\n\t\tsuper.add(comp);\n\n\t\treturn this;\n\t}\n\n\tpublic FluentPanel add(Component comp, String constraints) {\n\t\tsuper.add(comp, constraints);\n\t\t\n\t\treturn this;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/layout/InformationPane.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.layout;\n\nimport com.mucommander.ui.text.MultiLineLabel;\nimport org.jetbrains.annotations.Nullable;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n/**\n * InformationPane is a panel which is suitable for use in dialogs, to give information about the action that the\n * dialog is about to perform.<br>\n * It is made of 3 components:\n * <ul>\n *  <li>A 'main' label which provides a brief description of the action to be performed.\n * By default, this label uses the standard label's font size and a bold style.\n *  <li>A 'caption' label which is displayed under the main label and provides additional information about the action\n * to be performed. This label uses a smaller font size and plain style.\n *  <li>An optional icon displayed to the left of labels. InformationPane makes it easy to use one of the standard\n * <code>JOptionPane</code> icons: error, information, warning, question.\n * </ul>\n *\n * <p>Here is a textual representation of the layout, all components are vertically aligned to the top:\n * <pre>\n *\n * --------------------------\n * | [ICON] | Main label    |\n * |        | Caption label |\n * |________|_______________|\n * \n * </pre>\n\n *\n * @author Maxence Bernard\n */\npublic class InformationPane extends JPanel {\n\n    /** Label used to display the icon, can be null if no icon is used */\n    private JLabel iconLabel;\n    /** Label used to display the main message */\n    private final MultiLineLabel mainLabel;\n    /** Label used to display the caption message */\n    private final MultiLineLabel captionLabel;\n\n    /** Designates the 'error' predefined icon */\n    public final static int ERROR_ICON = 1;\n    /** Designates the 'information' predefined icon */\n    public final static int INFORMATION_ICON = 2;\n    /** DesignateS the 'warning' predefined icon */\n    public final static int WARNING_ICON = 3;\n    /** Designates the 'question' predefined icon */\n    public final static int QUESTION_ICON = 4;\n\n    /**\n     * Creates a new InformationPane with no main and caption message.\n     */\n    public InformationPane() {\n        this(\"\", \"\");\n    }\n\n    /**\n     * Creates a new InformationPane using the given main and caption messages and no icon. The font style for the main\n     * label is set to <code>Font.BOLD</code>.\n     *\n     * @param mainMessage the message to display in the main label\n     * @param captionMessage the message to display in the caption label\n     */\n    public InformationPane(String mainMessage, String captionMessage) {\n        this(mainMessage, captionMessage, Font.BOLD, null);\n    }\n\n    /**\n     * Creates a new InformationPane using the given main message, caption message, main label font style and\n     * predefined icon.\n     *\n     * @param mainMessage the message to display in the main label\n     * @param captionMessage the message to display in the caption label\n     * @param mainMessageFontStyle the font style to use in the main label\n     * @param predefinedIconId an id designating the predefined icon to display, see constant fields for allowed values\n     */\n    public InformationPane(String mainMessage, String captionMessage, int mainMessageFontStyle, int predefinedIconId) {\n        this(mainMessage, captionMessage, mainMessageFontStyle, getPredefinedIcon(predefinedIconId));\n    }\n\n    /**\n     * Creates a new InformationPane using the given main message, caption message, main label font style and icon.\n     *\n     * @param mainMessage the message to display in the main label\n     * @param captionMessage the message to display in the caption label\n     * @param mainMessageFontStyle the font style to use in the main label\n     * @param icon an icon to display, null for no icon\n     */\n    public InformationPane(String mainMessage, String captionMessage, int mainMessageFontStyle, Icon icon) {\n        super(new BorderLayout());\n\n        if (icon != null) {\n            setIcon(icon);\n        }\n\n        YBoxPanel yPanel = new YBoxPanel(5);\n\n        mainLabel = new MultiLineLabel(mainMessage);\n        if (mainMessageFontStyle != Font.PLAIN) {\n            setMainLabelFontStyle(mainMessageFontStyle);\n        }\n        yPanel.add(mainLabel);\n\n        yPanel.addSpace(5);\n\n        captionLabel = new MultiLineLabel(captionMessage);\n        Font labelFont = mainLabel.getFont();\n        captionLabel.setFont(labelFont.deriveFont(Font.PLAIN, labelFont.getSize()-2));\n        yPanel.add(captionLabel);\n\n        add(yPanel, BorderLayout.CENTER);\n    }\n\n    /**\n     * Returns an <code>Icon</code> instance corresponding to the given predefined icon id.\n     *\n     * @param predefinedIconId an id designating a predefined icon, see constant fields for allowed values\n     * @return an Icon instance corresponding to the given predefined icon id \n     */\n    public static Icon getPredefinedIcon(int predefinedIconId) {\n        String optionPaneIcon = getPredefinedIconName(predefinedIconId);\n        if (optionPaneIcon == null) {\n            return null;\n        }\n        return UIManager.getIcon(\"OptionPane.\" + optionPaneIcon);\n    }\n\n    @Nullable\n    private static String getPredefinedIconName(int predefinedIconId) {\n        switch (predefinedIconId) {\n            case ERROR_ICON:\n                return \"errorIcon\";\n            case INFORMATION_ICON:\n                return \"informationIcon\";\n            case WARNING_ICON:\n                return \"warningIcon\";\n            case QUESTION_ICON:\n                return \"questionIcon\";\n            default:\n                return null;\n        }\n    }\n\n    /**\n     * Returns the main label displayed in this InformationPane.\n     *\n     * @return the main label displayed in this InformationPane\n     */\n    public MultiLineLabel getMainLabel() {\n        return mainLabel;\n    }\n\n    /**\n     * Returns the caption label displayed in this InformationPane.\n     *\n     * @return the caption label displayed in this InformationPane\n     */\n    public MultiLineLabel getCaptionLabel() {\n        return captionLabel;\n    }\n\n    /**\n     * Changes the icon displayed in this InformationPane to the given one. If <code>null</code> is specified, this\n     * InformationPane will not display any icon.\n     *\n     * @param icon the new icon to display, null for no icon\n     */\n    public void setIcon(Icon icon) {\n        if(icon==null) {\n            if(iconLabel!=null) {\n                remove(iconLabel);\n                iconLabel = null;\n            }\n        }\n        else {\n            if(iconLabel==null) {\n                iconLabel = new JLabel(\" \");\n                add(iconLabel, BorderLayout.WEST);\n            }\n\n            iconLabel.setIcon(icon);\n        }\n    }\n\n    /**\n     * Changes the font style used by the main label. Allowed values are <code>Font.PLAIN</code>,\n     * <code>Font.BOLD</code> and <code>Font.ITALIC</code> or a mix of those combined as a bitwise mask.\n     *\n     * @param fontStyle the new font style for the main label\n     */\n    public void setMainLabelFontStyle(int fontStyle) {\n        Font labelFont = mainLabel.getFont();\n        mainLabel.setFont(labelFont.deriveFont(fontStyle, labelFont.getSize()));\n    }\n\n    /**\n     * Returns the font style used by the main label as a bitwise mask.\n     *\n     * @return the font style used by the main label as a bitwise mask\n     */\n    public int getMainLabelFontStyle() {\n        return mainLabel.getFont().getStyle();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/layout/ProportionalGridPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.layout;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n/**\n * @author Maxence Bernard\n */\npublic class ProportionalGridPanel extends JPanel {\n\n    private final int nbColumns;\n    private final GridBagConstraints gbc;\n\n    public ProportionalGridPanel(int nbColumns) {\n        super(new GridBagLayout());\n        this.nbColumns = nbColumns;\n\n        gbc = new GridBagConstraints();\n        gbc.gridx = 0;\n        gbc.gridy = 0;\n        gbc.insets = new Insets(2, 3, 2, 3);\n\n//        gbc.gridwidth = 1;\n//        gbc.gridheight = 1;\n//        gbc.ipadx = 0;\n//        gbc.ipady = 0;\n//        gbc.weightx = 0;\n//        gbc.weighty = 0;\n//        gbc.fill = GridBagConstraints.NONE;\n\n        gbc.anchor = GridBagConstraints.WEST;\n    }\n\n\n    @Override\n    public Component add(Component component) {\n        add(component, gbc);\n\n        if (gbc.gridx < nbColumns-1) {\n            gbc.gridx++;\n        } else {\n            gbc.gridy++;\n            gbc.gridx = 0;\n        }\n\n        return component;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/layout/ProportionalSplitPane.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.layout;\n\nimport com.mucommander.desktop.DesktopManager;\nimport lombok.Getter;\n\nimport javax.swing.*;\nimport javax.swing.plaf.basic.BasicSplitPaneDivider;\nimport javax.swing.plaf.basic.BasicSplitPaneUI;\nimport java.awt.*;\nimport java.awt.event.ComponentEvent;\nimport java.awt.event.ComponentListener;\nimport java.awt.event.MouseEvent;\nimport java.awt.event.MouseListener;\n\n/**\n * ProportionalSplitPane is a JSplitPane that is able to maintain the divider's location constant proportionally when\n * the window it is attached to is resized, or when its orientation is changed.\n *\n * <p>Another added feature is the ability to restore a split ratio of 0.5f (same size for both panels) when\n * the divider component is double-clicked.\n *\n * @author Maxence Bernard\n */\npublic class ProportionalSplitPane extends JSplitPane implements ComponentListener, MouseListener {\n    /** Last known width of the window this split pane is attached to. */\n    private int windowWidth;\n\n    /** Last known absolute divider location */\n    private int lastDividerLocation = -1;\n\n    /** Current proportional divider location, initially 0.5f (same size for both panels)\n     * -- GETTER --\n     *  Returns current pane split ratio.\n     */\n    @Getter\n    private float splitRatio = 0.5f;\n\n    /** Window this split pane is attached to */\n    private Window window;\n\n\n    public ProportionalSplitPane(Window window) {\n        super();\n        init(window, null, null);\n    }\n\n    public ProportionalSplitPane(Window window, int orientation) {\n        super(orientation);\n        init(window, null, null);\n    }\n\n    public ProportionalSplitPane(Window window, int orientation, boolean continuousLayout) {\n        super(orientation, continuousLayout);\n        init(window, null, null);\n    }\n\n    public ProportionalSplitPane(Window window, int orientation, JComponent leftComponent, JComponent rightComponent) {\n        super(orientation, leftComponent, rightComponent);\n        init(window, leftComponent, rightComponent);\n    }\n\n    public ProportionalSplitPane(Window window, int orientation, boolean continuousLayout, JComponent leftComponent, JComponent rightComponent) {\n        super(orientation, continuousLayout, leftComponent, rightComponent);\n        init(window, leftComponent, rightComponent);\n    }\n\n    private void init(Window window, JComponent leftComponent, JComponent rightComponent) {\n        this.window = window;\n        window.addComponentListener(this);\n\n        BasicSplitPaneDivider divider = getDividerComponent();\n        divider.addComponentListener(this);\n        divider.addMouseListener(this);\n\n        // Set null minimum size for both components so that divider can be moved all the way left/up and right/down\n        Dimension nullDimension = new Dimension(0,0);\n        if (leftComponent != null) {\n            leftComponent.setMinimumSize(nullDimension);\n        }\n        if (rightComponent != null) {\n            rightComponent.setMinimumSize(nullDimension);\n        }\n    }\n\n\n    /**\n     * Updates the divider component's location to keep the current proportional divider location. \n     */\n    public void updateDividerLocation() {\n        if (!window.isVisible()) {\n            return;\n        }\n\n        // Reset the last divider location to make sure that the next call to moveComponent doesn't\n        // needlessly recalculate the split ratio.\n        lastDividerLocation = -1;\n        //setDividerLocation((int)(splitRatio*(getOrientation()==HORIZONTAL_SPLIT?getWidth():getHeight())));\n        setDividerLocation(splitRatio);\n    }\n\n\n    /**\n     * Sets the constant, proportional divider's location. The given float but be comprised between 0 and 1, 0 meaning\n     * completely left (or top), 1 right completely (or bottom).   \n     *\n     * @param splitRatio the proportional divider's location, comprised between 0 and 1.\n     */\n    public void setSplitRatio(float splitRatio) {\n        this.splitRatio = splitRatio;\n        updateDividerLocation();\n    }\n\n\n    /**\n     * Returns the split pane divider component.\n     */\n    public BasicSplitPaneDivider getDividerComponent() {\n        return ((BasicSplitPaneUI)getUI()).getDivider();\n    }\n\n\n    /**\n     * Disables all the JSplitPane accessibility shortcuts that are registered by default:\n     * <ul>\n     * <li>Navigate in - Tab\n     * <li>Navigate out - Ctrl+Tab, Ctrl+Shift+Tab\n     * <li>Navigate between - Tab, F6\n     * <li>Give focus splitter bar - F8\n     * <li>Change size - Arrow keys, home, and end (moves the pane splitter appropriately)\n     * </ul>\n     */\n    public void disableAccessibilityShortcuts() {\n        InputMap inputMap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);\n        inputMap.clear();\n        inputMap.setParent(null);\n    }\n\n\n    @Override\n    public void setOrientation(int orientation) {\n        super.setOrientation(orientation);\n        // Maintain the divider's proportional location\n        updateDividerLocation();\n    }\n\n\n    /**\n     * Sets the divider location when the ContentPane has been resized so that it stays at the\n     * same proportional (not absolute) location.\n     */\n    @Override\n    public void componentResized(ComponentEvent e) {\n        Object source = e.getSource();\n\n        if (source == window) {\n            // Note: the window/split pane may not be visible when this method is called for the first time\n\n            // Makes sure that windowWidth is never 0 in #componentMoved.\n            windowWidth = window.getWidth();\n            updateDividerLocation();\n        }\n    }\n\n    @Override\n    public void componentMoved(ComponentEvent e) {\n        if (e.getSource() == getDividerComponent()) {\n            // Ignore this event if the divider's location hasn't changed, or if the initial divider's location\n            // hasn't been set yet\n            if (lastDividerLocation == -1) {\n                lastDividerLocation = getDividerLocation();\n                return;\n            } else if (lastDividerLocation == getDividerLocation()) {\n                return;\n            }\n\n            // This is a bit tricky: we want to ignore events triggered by the divider moving because the window was\n            // resized in such a way that it didn't have a choice (window width smaller than current divider location).\n            // Such events are managed by the componentResized method.\n            if (windowWidth != window.getWidth()) {\n                windowWidth = window.getWidth();\n                return;\n            }\n\n            // Divider has been moved, calculate new split ratio\n            lastDividerLocation = getDividerLocation();\n            splitRatio = lastDividerLocation / (float)(getOrientation() == HORIZONTAL_SPLIT?getWidth() : getHeight());\n        }\n    }\n\n\n    @Override\n    public void componentShown(ComponentEvent e) {\n        // Called when the window is made visible\n        if (e.getSource() == window) {\n            // Set initial divider's location\n            updateDividerLocation();\n        }\n    }\n\n    @Override\n    public void componentHidden(ComponentEvent e) {\n    }\n\n\n    @Override\n    public void mouseClicked(MouseEvent mouseEvent) {\n        if (DesktopManager.isLeftMouseButton(mouseEvent) && mouseEvent.getClickCount() == 2) {\n            setSplitRatio(0.5f);\n            doLayout();\n        }\n    }\n\n    @Override\n    public void mousePressed(MouseEvent mouseEvent) {\n    }\n\n    @Override\n    public void mouseReleased(MouseEvent mouseEvent) {\n    }\n\n    @Override\n    public void mouseEntered(MouseEvent mouseEvent) {\n    }\n\n    @Override\n    public void mouseExited(MouseEvent mouseEvent) {\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/layout/SpringUtilities.java",
    "content": "/*\n * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *   - Redistributions of source code must retain the above copyright\n *     notice, this list of conditions and the following disclaimer.\n *\n *   - Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *\n *   - Neither the name of Oracle or the names of its\n *     contributors may be used to endorse or promote products derived\n *     from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n * IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */ \n\npackage com.mucommander.ui.layout;\n\nimport javax.swing.Spring;\nimport javax.swing.SpringLayout;\nimport java.awt.Component;\nimport java.awt.Container;\n\n/**\n * A 1.4 file that provides utility methods for\n * creating form- or grid-style layouts with SpringLayout.\n * These utilities are used by several programs, such as\n * SpringBox and SpringCompactGrid.\n */\npublic class SpringUtilities {\n    /**\n     * A debugging utility that prints to stdout the component's\n     * minimum, preferred, and maximum sizes.\n     */\n    public static void printSizes(Component c) {\n        System.out.println(\"minimumSize = \" + c.getMinimumSize());\n        System.out.println(\"preferredSize = \" + c.getPreferredSize());\n        System.out.println(\"maximumSize = \" + c.getMaximumSize());\n    }\n\n    /**\n     * Aligns the first <code>rows</code> * <code>cols</code>\n     * components of <code>parent</code> in\n     * a grid. Each component is as big as the maximum\n     * preferred width and height of the components.\n     * The parent is made just big enough to fit them all.\n     *\n     * @param rows number of rows\n     * @param cols number of columns\n     * @param initialX x location to start the grid at\n     * @param initialY y location to start the grid at\n     * @param xPad x padding between cells\n     * @param yPad y padding between cells\n     */\n    public static void makeGrid(Container parent,\n                                int rows, int cols,\n                                int initialX, int initialY,\n                                int xPad, int yPad) {\n        SpringLayout layout;\n        try {\n            layout = (SpringLayout)parent.getLayout();\n        } catch (ClassCastException exc) {\n            System.err.println(\"The first argument to makeGrid must use SpringLayout.\");\n            return;\n        }\n\n        Spring xPadSpring = Spring.constant(xPad);\n        Spring yPadSpring = Spring.constant(yPad);\n        Spring initialXSpring = Spring.constant(initialX);\n        Spring initialYSpring = Spring.constant(initialY);\n        int max = rows * cols;\n\n        //Calculate Springs that are the max of the width/height so that all\n        //cells have the same size.\n        Spring maxWidthSpring = layout.getConstraints(parent.getComponent(0)).\n                                    getWidth();\n        Spring maxHeightSpring = layout.getConstraints(parent.getComponent(0)).\n                                    getHeight();\n        for (int i = 1; i < max; i++) {\n            SpringLayout.Constraints cons = layout.getConstraints(\n                                            parent.getComponent(i));\n\n            maxWidthSpring = Spring.max(maxWidthSpring, cons.getWidth());\n            maxHeightSpring = Spring.max(maxHeightSpring, cons.getHeight());\n        }\n\n        //Apply the new width/height Spring. This forces all the\n        //components to have the same size.\n        for (int i = 0; i < max; i++) {\n            SpringLayout.Constraints cons = layout.getConstraints(\n                                            parent.getComponent(i));\n\n            cons.setWidth(maxWidthSpring);\n            cons.setHeight(maxHeightSpring);\n        }\n\n        //Then adjust the x/y constraints of all the cells so that they\n        //are aligned in a grid.\n        SpringLayout.Constraints lastCons = null;\n        SpringLayout.Constraints lastRowCons = null;\n        for (int i = 0; i < max; i++) {\n            SpringLayout.Constraints cons = layout.getConstraints(\n                                                 parent.getComponent(i));\n            if (i % cols == 0) { //start of new row\n                lastRowCons = lastCons;\n                cons.setX(initialXSpring);\n            } else { //x position depends on previous component\n                cons.setX(Spring.sum(lastCons.getConstraint(SpringLayout.EAST),\n                                     xPadSpring));\n            }\n\n            if (i / cols == 0) { //first row\n                cons.setY(initialYSpring);\n            } else { //y position depends on previous row\n                cons.setY(Spring.sum(lastRowCons.getConstraint(SpringLayout.SOUTH),\n                                     yPadSpring));\n            }\n            lastCons = cons;\n        }\n\n        //Set the parent's size.\n        SpringLayout.Constraints pCons = layout.getConstraints(parent);\n        pCons.setConstraint(SpringLayout.SOUTH,\n                            Spring.sum(\n                                Spring.constant(yPad),\n                                lastCons.getConstraint(SpringLayout.SOUTH)));\n        pCons.setConstraint(SpringLayout.EAST,\n                            Spring.sum(\n                                Spring.constant(xPad),\n                                lastCons.getConstraint(SpringLayout.EAST)));\n    }\n\n    /* Used by makeCompactGrid. */\n    private static SpringLayout.Constraints getConstraintsForCell(\n                                                int row, int col,\n                                                Container parent,\n                                                int cols) {\n        SpringLayout layout = (SpringLayout) parent.getLayout();\n        Component c = parent.getComponent(row * cols + col);\n        return layout.getConstraints(c);\n    }\n\n    /**\n     * Aligns the first <code>rows</code> * <code>cols</code>\n     * components of <code>parent</code> in\n     * a grid. Each component in a column is as wide as the maximum\n     * preferred width of the components in that column;\n     * height is similarly determined for each row.\n     * The parent is made just big enough to fit them all.\n     *\n     * @param rows number of rows\n     * @param cols number of columns\n     * @param initialX x location to start the grid at\n     * @param initialY y location to start the grid at\n     * @param xPad x padding between cells\n     * @param yPad y padding between cells\n     */\n    public static void makeCompactGrid(Container parent,\n                                       int rows, int cols,\n                                       int initialX, int initialY,\n                                       int xPad, int yPad) {\n        SpringLayout layout;\n        try {\n            layout = (SpringLayout)parent.getLayout();\n        } catch (ClassCastException exc) {\n            System.err.println(\"The first argument to makeCompactGrid must use SpringLayout.\");\n            return;\n        }\n\n        //Align all cells in each column and make them the same width.\n        Spring x = Spring.constant(initialX);\n        for (int c = 0; c < cols; c++) {\n            Spring width = Spring.constant(0);\n            for (int r = 0; r < rows; r++) {\n                width = Spring.max(width,\n                                   getConstraintsForCell(r, c, parent, cols).\n                                       getWidth());\n            }\n            for (int r = 0; r < rows; r++) {\n                SpringLayout.Constraints constraints =\n                        getConstraintsForCell(r, c, parent, cols);\n                constraints.setX(x);\n                constraints.setWidth(width);\n            }\n            x = Spring.sum(x, Spring.sum(width, Spring.constant(xPad)));\n        }\n\n        //Align all cells in each row and make them the same height.\n        Spring y = Spring.constant(initialY);\n        for (int r = 0; r < rows; r++) {\n            Spring height = Spring.constant(0);\n            for (int c = 0; c < cols; c++) {\n                height = Spring.max(height,\n                                    getConstraintsForCell(r, c, parent, cols).\n                                        getHeight());\n            }\n            for (int c = 0; c < cols; c++) {\n                SpringLayout.Constraints constraints =\n                        getConstraintsForCell(r, c, parent, cols);\n                constraints.setY(y);\n                constraints.setHeight(height);\n            }\n            y = Spring.sum(y, Spring.sum(height, Spring.constant(yPad)));\n        }\n\n        //Set the parent's size.\n        SpringLayout.Constraints pCons = layout.getConstraints(parent);\n        pCons.setConstraint(SpringLayout.SOUTH, y);\n        pCons.setConstraint(SpringLayout.EAST, x);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/layout/XAlignedComponentPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.layout;\n\nimport javax.swing.*;\nimport javax.swing.text.JTextComponent;\nimport java.awt.*;\n\n\n/**\n * XAlignedComponentPanel is a panel which makes life easier when having\n * to display several rows of label + text field.\n * <p>On each row, labels are right-aligned and text fields left-aligned,\n * looking something like this:<br>\n * <pre>\n *       Label1 [Component1]<br>\n * LongerLabel2 [Component2]<br>\n *       Label3 [Component3]<br>\n * </pre>\n *\n * <p>Vertical space between labels and components, and horizontal space between rows can both be specified.\n *\n * @author Maxence Bernard\n */\npublic class XAlignedComponentPanel extends JPanel {\n\n    /** Gridbag layout constraints */\n    private final GridBagConstraints c;\n\n    /** Number of pixels between labels and components */\n    private final int xSpace;\n\n    /**\n     * First component in this panel, which will be given the focus when focus is requested on this panel using\n     * {@link #requestFocus()}.\n     */\n    private JComponent firstComponent;\n\n\n    public final static int DEFAULT_XSPACE = 5;\n\n\n    /**\n     * Creates an initially empty panel, using the default vertical space as defined by {@link #DEFAULT_XSPACE} to\n     * separate labels and components for all rows added later.\n     */\n    public XAlignedComponentPanel() {\n        this(DEFAULT_XSPACE);\n    }\n\n\n    /**\n     * Creates an initially empty panel, using the given vertical space to separate labels and components for\n     * all rows added later.\n     *\n     * @param xSpace number of pixels to be inserted between labels and components.\n     */\n    public XAlignedComponentPanel(int xSpace) {\n        // Set grid bag layout\n        setLayout(new GridBagLayout());\n\n        setAlignmentX(LEFT_ALIGNMENT);\n\n        // Number of pixels between labels and components\n        this.xSpace = xSpace;\n\t\t\n        // Init gridbag constraints.\n        this.c = new GridBagConstraints();\n        this.c.anchor = GridBagConstraints.EAST;\n    }\n\n    public void setLabelLeftAligned(boolean aligned) {\n        this.c.anchor = aligned ? GridBagConstraints.WEST : GridBagConstraints.EAST;\n    }\n\n    /**\n     * Adds a new row with the given label and component, the component taking all the horizontal space left\n     * by the widest label in this XAlignedComponentPanel.\n     *\n     * @param label text that describes the component\n     * @param component JComponent instance, will take all remaining width space\n     * @param ySpaceAfter number of pixels to be inserted after this row\n     */\n    public void addRow(String label, JComponent component, int ySpaceAfter) {\n        addRow(new JLabel(label), component, ySpaceAfter);\n    }\n\n\n    /**\n     * Adds a new row with the given label and component, the component taking all the horizontal space left\n     * by the widest label in this XAlignedComponentPanel.\n     *\n     * @param label the label component that describes the component\n     * @param component JComponent instance that will take all remaining width space\n     * @param ySpaceAfter number of pixels to be inserted after this row\n     */\n    public void addRow(JComponent label, JComponent component, int ySpaceAfter) {\n        if (firstComponent == null) {\n            firstComponent = component;\n        }\n\t\t\n        // Prepare grid bag constraints for label\n        c.gridwidth = GridBagConstraints.RELATIVE;\n        c.fill = GridBagConstraints.NONE;\n        c.weightx = 0.0;\n        c.insets = new Insets(0, 0, ySpaceAfter, xSpace);\n\t\t\n        add(label, c);\n\n        // Prepare grid bag constraints for component\n        c.gridwidth = GridBagConstraints.REMAINDER;\n        c.fill = GridBagConstraints.HORIZONTAL;\n        c.weightx = 1.0;\n        c.insets = new Insets(0, 0, ySpaceAfter, 0);\n\n        add(component, c);\n    }\n\n\n    /**\n     * Adds a new row with the specified component left-aligned and taking all available width space.\n     *\n     * @param component JComponent instance that will take all available width space\n     * @param ySpaceAfter number of pixels to be inserted after this row\n     */\n    public void addRow(JComponent component, int ySpaceAfter) {\n        // Prepare grid bag constraints\t\t\n        c.gridwidth = GridBagConstraints.REMAINDER;\n        c.fill = GridBagConstraints.HORIZONTAL;\n        c.weightx = 1.0;\n        c.insets = new Insets(0, 0, ySpaceAfter, 0);\n\t\n        add(component, c);\n    }\n\t\n\t\n\t\n    /**\n     * Overrides JPanel#requestFocus() method to request focus on the first component\n     * and select its contents if it is an instance of JTextComponent.\n     */\n    @Override\n    public void requestFocus() {\n        if (firstComponent == null)\n            super.requestFocus();\n        else {\n            if (firstComponent instanceof JTextComponent) {\n                JTextComponent textComponent = (JTextComponent) firstComponent;\n                String text = textComponent.getText();\n                if(!text.isEmpty()) {\n                    textComponent.setSelectionStart(0);\n                    textComponent.setSelectionEnd(text.length());\n                }\n            }\t\t\t\n            firstComponent.requestFocus();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/layout/XBoxPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.layout;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n\n/**\n * Convenience class to make panels with a horizontal BoxLayout easier to use.\n *\n * @author Maxence Bernard\n */\npublic class XBoxPanel extends JPanel {\n\n    /** Custom insets, can be null if custom insets haven't been specified with {@link #setInsets(Insets)} */\n    private Insets insets;\n\t\n\t\n    /**\n     * Creates a new JPanel with a vertical BoxLayout (BoxLayout.X_AXIS).\n     */\n    public XBoxPanel() {\n        setLayout(new BoxLayout(this, BoxLayout.X_AXIS));\n    }\n\t\n\n    /**\n     * Creates a new JPanel with a vertical BoxLayout (BoxLayout.X_AXIS) and\n     * adds some initial space to the panel.\n     */\n    public XBoxPanel(int nbPixels) {\n        setLayout(new BoxLayout(this, BoxLayout.X_AXIS));\n        add(Box.createRigidArea(new Dimension(nbPixels, 0)));\n    }\n\n\t\n    /**\n     * Aligns the given component on the left and adds it to this panel.\n     */\n    @Override\n    public Component add(Component comp) {\n        if (comp instanceof JComponent) {\n            ((JComponent) comp).setAlignmentX(LEFT_ALIGNMENT);\n        }\n\n        return super.add(comp);\n    }\n\n    /**\n     * Adds a vertical separation of the given size to this panel.\n     */\n    public void addSpace(int nbPixels) {\n        add(Box.createRigidArea(new Dimension(nbPixels, 0)));\n    }\n\t\n\t\n    /**\n     * Sets this panel's insets.\n     */\n    public void setInsets(Insets insets) {\n        this.insets = insets;\n    }\n\t\n    /**\n     * Returns this panel's insets.\n     */\n    @Override\n    public Insets getInsets() {\n        return insets == null ? super.getInsets() : insets;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/layout/YBoxPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.layout;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n\n/**\n * Convenience class to make use of panels with a vertical BoxLayout simpler.\n *\n * @author Maxence Bernard\n */\npublic class YBoxPanel extends JPanel {\n\n    /** Custom insets, can be null if custom insets haven't been specified with {@link #setInsets(Insets)} */\n    private Insets insets;\n\t\n\t\n    /**\n     * Creates a new JPanel with a vertical BoxLayout (BoxLayout.Y_AXIS).\n     */\n    public YBoxPanel() {\n        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));\n    }\n\t\n\n    /**\n     * Creates a new JPanel with a vertical BoxLayout (BoxLayout.Y_AXIS) and\n     * adds some initial space to the panel.\n     */\n    public YBoxPanel(int nbVertSpace) {\n        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));\n        add(Box.createRigidArea(new Dimension(0, nbVertSpace)));\n    }\n\n\t\n    /**\n     * Aligns the given component on the left and adds it to this panel.\n     */\n    @Override\n    public Component add(Component comp) {\n        if (comp instanceof JComponent) {\n            ((JComponent) comp).setAlignmentX(LEFT_ALIGNMENT);\n        }\n\n        return super.add(comp);\n    }\n\n\t\n    /**\n     * Adds a vertical separation of the given size to this panel.\n     */\n    public void addSpace(int nbVertSpace) {\n        add(Box.createRigidArea(new Dimension(0, nbVertSpace)));\n    }\n\t\n\t\n    /**\n     * Adds the given component to this panel, inserting the specified amount of horizontal\n     * space before the component.\n     */\n    @Override\n    public Component add(Component component, int nbHorizSpace) {\n        JPanel tempPanel = new XBoxPanel(nbHorizSpace);\n        tempPanel.add(component);\n\n        return add(tempPanel);\n    }\n\n\n    /**\n     * Sets this panel's insets.\n     */\n    public void setInsets(Insets insets) {\n        this.insets = insets;\n    }\n\t\n    /**\n     * Returns this panel's insets.\n     */\n    @Override\n    public Insets getInsets() {\n        return insets == null ? super.getInsets() : insets;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/layout/package.html",
    "content": "<body>\n  Utility classes meant to help with Swing component layout.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/list/DynamicHorizontalWrapList.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.list;\r\n\r\nimport com.mucommander.commons.collections.AlteredVector;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.*;\r\n\r\n/**\r\n * This class represent a DynamicList with a following horizontal-wrap layout:\r\n * <pre>\r\n *     1   2   3\r\n *     4   5   6\r\n *     7   8   9\r\n *     10..\r\n * </pre>\r\n *   \r\n * In which the number of items per-row is dynamically determined by the \r\n * width of the list and the given item-width parameter.\r\n *  \r\n * @author Arik Hadas\r\n */\r\npublic class DynamicHorizontalWrapList<E> extends DynamicList<E> {\r\n\r\n\t// The width of each item in the list\r\n\tprivate final int itemWidth;\r\n\t// Saves the last width of the parent container to detect if there\r\n\t// should be a change in the number of items per-row.\r\n\tprivate int lastParentWidth;\r\n\t\r\n\tpublic DynamicHorizontalWrapList(AlteredVector<E> items, int itemWidth) {\r\n\t\tthis(items, itemWidth, 0);\r\n\t}\r\n\t\r\n\tpublic DynamicHorizontalWrapList(AlteredVector<E> items, int itemWidth, int horizontalPadding) {\r\n\t\tsuper(items);\r\n\t\t\r\n\t\tthis.itemWidth = itemWidth + horizontalPadding;\r\n\t\tsetLayoutOrientation(JList.HORIZONTAL_WRAP);\r\n\t}\r\n\r\n\t@Override\r\n    public void repaint() {\r\n\t\tContainer parent = getParent();\r\n\t\tif (parent != null) {\r\n\t\t\tRectangle parentBounds = parent.getBounds();\r\n\t\t\tint parentWidth = parentBounds.width;\r\n\r\n\t\t\tif (lastParentWidth != parentWidth) {\r\n\t\t\t\tlastParentWidth = parentWidth;\r\n\r\n\t\t\t\tint itemsPerRow = parentWidth / itemWidth;\r\n\t\t\t\tsetFixedCellWidth(itemWidth + ((parentWidth - itemWidth * itemsPerRow)/ itemsPerRow));\r\n\t\t\t\tsetVisibleRowCount(getComponentCount() / itemsPerRow);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tsuper.repaint();\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/list/DynamicList.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.list;\n\nimport com.mucommander.commons.collections.AlteredVector;\nimport com.mucommander.commons.collections.VectorChangeListener;\nimport com.mucommander.utils.text.Translator;\n\nimport javax.swing.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.KeyEvent;\n\n/**\n * DynamicList extends JList to work with an {@link AlteredVector} of items which values can be dynamically modified\n * and automatically reflected in the list, also keeping the current selection consistent.\n *\n * <p>It also provides actions to:\n * <ul>\n * <li>move the currently selected item up (mapped to 'Shift+UP' and 'Shift+LEFT')\n * <li>move the currently selected item down (mapped to 'Shift+DOWN' and 'Shift+RIGHT')\n * <li>remove the currently selected item and selects the previous one (if any) (mapped to 'DELETE' and 'BACKSPACE')\n * </ul>\n *\n * <p>This list only works in 'single selection mode', that means only one item can be selected at a time.\n *\n * @author Maxence Bernard\n */\npublic class DynamicList<E> extends JList<E> {\n\n    /** Items displayed in the JList */\n    private final AlteredVector<E> items;\n\n    /** Custom ListModel that handles modifications made to the AlteredVector */\n    private final DynamicListModel model;\n\n    /** Action instance which moves the currently selected item up when triggered */\n    private final MoveUpAction moveUpAction;\n\n    /** Action instance which moves the currently selected item down when triggered */\n    private final MoveDownAction moveDownAction;\n\n    /** Action instance which, when triggered, removes the currently selected item from the list\n     * and selects the previous item (if any). */\n    private final RemoveAction removeAction;\n\n\n    /**\n     * Custom ListModel that handles modifications made to the AlteredVector and reflect them changes in the JList.\n     */\n    private class DynamicListModel extends AbstractListModel<E> implements VectorChangeListener {\n\n        public int getSize() {\n            return items.size();\n        }\n\n        public E getElementAt(int i) {\n            if (i < 0 || i >= items.size()) {\n                return null;\n            }\n\n            return items.elementAt(i);\n        }\n\n        private void notifyAdded(int fromIndex, int toIndex) {\n            fireIntervalAdded(this, fromIndex, toIndex);\n        }\n\n        private void notifyRemoved(int fromIndex, int toIndex) {\n            fireIntervalRemoved(this, fromIndex, toIndex);\n        }\n\n        private void notifyModified(int index) {\n            fireContentsChanged(this, index, index);\n        }\n\n        //////////////////////////\n        // VectorChangeListener //\n        //////////////////////////\n\n        public void elementsAdded(int startIndex, int nbAdded) {\n            model.notifyAdded(startIndex, startIndex+nbAdded-1);\n        }\n\n        public void elementsRemoved(int startIndex, int nbRemoved) {\n            model.notifyRemoved(startIndex, startIndex+nbRemoved-1);\n        }\n\n        public void elementChanged(int index) {\n            model.notifyModified(index);\n        }\n    }\n\n\n    /**\n     * Action which moves the currently selected item up when triggered.\n     */\n    private class MoveUpAction extends AbstractAction {\n\n        private MoveUpAction() {\n        }\n\n        public void actionPerformed(ActionEvent actionEvent) {\n            moveItem(getSelectedIndex(), true);\n\n            // Request focus back on the list\n            requestFocus();\n        }\n    }\n\n\n    /**\n     * Action which moves the currently selected item down when triggered.\n     */\n    private class MoveDownAction extends AbstractAction {\n\n        private MoveDownAction() {\n        }\n\n        public void actionPerformed(ActionEvent actionEvent) {\n            moveItem(getSelectedIndex(), false);\n\n            // Request focus back on the list\n            requestFocus();\n        }\n    }\n\n\n    /**\n     * Action which, when triggered, removes the currently selected item from the list and selects the previous item (if any).\n     */\n    private class RemoveAction extends AbstractAction {\n\n        private RemoveAction() {\n            putValue(Action.NAME, Translator.get(\"delete\"));\n        }\n\n        public void actionPerformed(ActionEvent actionEvent) {\n            int selectedIndex = getSelectedIndex();\n\n            if (!isIndexValid(selectedIndex))\n                return;\n\n            items.removeElementAt(selectedIndex);\n\n            // Select previous item (if there is one) and make sure it is visible.\n            int nbItems = items.size();\n            if (nbItems > 0)\n                selectAndScroll(Math.min(selectedIndex, nbItems-1));\n            \n            // Request focus back on the list\n            requestFocus();\n        }\n    }\n\n\n    /**\n     * Creates a new DynamicList using the items stored in the given {@link AlteredVector}.\n     * These items (if any) will be visible whenever this list is visible, and the first item (if any) will be selected.\n     *\n     * <p>Any change made to the AlteredVector will be automatically reflected in the list, except for changes\n     * made to the item instances themselves for which {@link #itemModified(int, boolean)} will need to\n     * be called explicitly.\n     *\n     * @param items items to add to the list\n     */\n    public DynamicList(AlteredVector<E> items) {\n        this.items = items;\n\n        // Use a custom ListModel\n        this.model = new DynamicListModel();\n\n        setModel(model);\n        \n        // Listen to changes made to the Vector\n        this.items.addVectorChangeListener(model);\n\n        // Allow only one item to be selected at a time\n        setSelectionMode(ListSelectionModel.SINGLE_SELECTION);\n\n        // Select first item, if there is at least one\n        if (!items.isEmpty()) {\n            setSelectedIndex(0);\n        }\n\n        // create action instances\n        this.moveUpAction = new MoveUpAction();\n        this.moveDownAction = new MoveDownAction();\n        this.removeAction = new RemoveAction();\n\n        InputMap inputMap = getInputMap();\n        ActionMap actionMap = getActionMap();\n\n        // Map 'Delete' and 'Backspace' to RemoveAction\n        Class<? extends Action> actionClass = removeAction.getClass();\n        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), actionClass);\n        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), actionClass);\n        actionMap.put(actionClass, removeAction);\n\n        // Map 'Shift+Up'/'Meta+Up' and 'Shift+Left'/'Meta+Left' to MoveUpAction\n        actionClass = moveUpAction.getClass();\n        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.SHIFT_DOWN_MASK), actionClass);\n        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.META_DOWN_MASK), actionClass);\n        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, KeyEvent.SHIFT_DOWN_MASK), actionClass);\n        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, KeyEvent.META_DOWN_MASK), actionClass);\n        actionMap.put(actionClass, moveUpAction);\n\n        // Map 'Shift+Down'/'Meta+Down' and 'Shift+Right'/'Meta+Right' to MoveDownAction\n        actionClass = moveDownAction.getClass();\n        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.SHIFT_DOWN_MASK), actionClass);\n        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.META_DOWN_MASK), actionClass);\n        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.SHIFT_DOWN_MASK), actionClass);\n        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.META_DOWN_MASK), actionClass);\n        actionMap.put(actionClass, moveDownAction);\n    }\n\n\n    /**\n     * Returns the items displayed by this DynamicList.\n     */\n    public AlteredVector<E> getItems() {\n        return items;\n    }\n\n\n    /**\n     * Selects the item located at the given index and if necessary scrolls the list to make sure\n     * that the new selection is visible within the viewport.\n     *\n     * @param index index of the item to select\n     */\n    public void selectAndScroll(int index) {\n        setSelectedIndex(index);\n        ensureIndexIsVisible(index);\n    }\n\n\n    /**\n     * Returns true if the given index is without the bounds of the items Vector.\n     *\n     * @param index index to test\n     * @return true if the given index is without the bounds of the items.\n     */\n    public boolean isIndexValid(int index) {\n        return index >= 0 && index < items.size();\n    }\n\n\n    /**\n     * This method should be called whenever an item in the items vector has been modified in order to properly\n     * repaint the list and reflect the change.\n     *\n     * @param index index of the item in the Vector that has been modified\n     * @param selectItem if true, the modified item will be selected\n     */\n    public void itemModified(int index, boolean selectItem) {\n        // Make sure that the given index is not out of bounds\n        if (!isIndexValid(index)) {\n            return;\n        }\n\n        // Notify ListModel in order to properly repaint list\n        model.notifyModified(index);\n    }\n\n\n    /**\n     * Moves the item located at the given index up or down, swapping its place with the previous or next item.\n     *\n     * @param index the item to move\n     * @param moveUp if true the item at the given index will be moved up, if not moved down\n     */\n    private void moveItem(int index, boolean moveUp) {\n        // Make sure that the given index is not out of bounds\n        if (!isIndexValid(index)) {\n            return;\n        }\n\n        int newIndex;\n\n        // Calculate the new index for the item to move\n        if (moveUp)  {\n            // Item is already at the top, do nothing\n            if (index < 1) {\n                return;\n            }\n\n            newIndex = index-1;\n        }\n        else {\n            // Item is already at the bottom, do nothing\n            if (index >= items.size()-1) {\n                return;\n            }\n\n            newIndex = index+1;\n        }\n\n        // Swap values in the Vector\n        E tmp = items.elementAt(index);\n        items.setElementAt(items.elementAt(newIndex), index);\n        items.setElementAt(tmp, newIndex);\n\n        // Select moved item and make sure it is visible\n        selectAndScroll(newIndex);\n    }\n\n\n    /**\n     * Returns an Action that can be used for instance in a JButton to\n     * move the item currently selected item up, swapping it with the previous item.\n     *\n     * @return an Action that moves the currently selected item up.\n     */\n    Action getMoveUpAction() {\n        return moveUpAction;\n    }\n\n    /**\n     * Returns an Action that can be used for instance in a JButton to\n     * move the item currently selected item down, swapping it with the following item.\n     *\n     * @return an Action that moves the currently selected item down.\n     */\n    Action getMoveDownAction() {\n        return moveDownAction;\n    }\n\n\n    /**\n     * Returns an Action that can be used for instance in a JButton to\n     * remove the currently selected item.\n     *\n     * @return an Action that removes the currently selected item.\n     */\n    public Action getRemoveAction() {\n        return removeAction;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/list/FileList.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.list;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.util.FileSet;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n/**\n * FileList is a <code>JList</code> that displays information about a list of files: each row displays a file's name\n * and icon.\n *\n * <p>Since all <code>AbstractFile</code> methods are I/O bound and potentially lock-prone, it is not a good idea to\n * call them on request from the main event thread. To work around this, the constructor can preload all the information\n * subsequently needed by this list. This has a cost since all files will have to queried at init time, even if some\n * are not used (displayed) afterwards. On the other hand, navigation throughout the list will be faster.\n * Preloading can be enabled or disabled in the constructor but it should always enabled unless it is\n * known for certain that the underlying files are not I/O bound and cannot lock.\n *\n * @author Maxence Bernard\n */\npublic class FileList extends JList<AbstractFile> {\n\n    /** Files to display */\n    protected FileSet files;\n    /** True if file attribute preloading has been enabled */\n    protected boolean fileAttributesPreloaded;\n\n    /** Preloaded filenames, null if preloading is not enabled */\n    protected String[] filenames;\n    /** Preloaded file icons, null if preloading is not enabled */\n    protected Icon[] icons;\n\n    /** Custom font by the JLabel */\n    protected Font customFont;\n\n\n    /**\n     * Creates a new FileList where each file in the given {@link FileSet} is displayed on a separate row. \n     *\n     * @param files the set of files to display\n     * @param preloadFileAttributes enables/disables file attribute preloading. It should always enabled unless it is known\n     * for certain that the underlying files are not I/O bound and cannot lock.\n     */\n    public FileList(final FileSet files, boolean preloadFileAttributes) {\n        this.files = files;\n\n        int nbFiles = files.size();\n\n        // Very important: allows the JList to operate in fixed cell height mode, which makes it substantially faster\n        // to initialize when there is a large number of rows.\n        if (nbFiles > 0) {\n            setPrototypeCellValue(files.elementAt(0));\n        }\n\n        if (preloadFileAttributes) {\n            filenames = new String[nbFiles];\n            icons = new Icon[nbFiles];\n            AbstractFile file;\n            for(int i=0; i<nbFiles; i++) {\n                file = files.elementAt(i);\n                filenames[i] = file.getName();\n                icons[i] = file.getIcon();\n            }\n\n            this.fileAttributesPreloaded = true;\n        }\n\n        // Use a custom ListModel\n        setModel(new AbstractListModel<AbstractFile>() {\n            public int getSize() {\n                return files.size();\n            }\n\n            public AbstractFile getElementAt(int index) {\n                return files.elementAt(index);\n            }\n        });\n\n        customFont = new JLabel().getFont();\n        customFont = customFont.deriveFont(customFont.getStyle(), customFont.getSize()-2);\n\n        // Use a custom ListCellRenderer\n        setCellRenderer(new DefaultListCellRenderer() {\n\n            @Override\n            public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {\n                JLabel label = (JLabel)super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);\n                label.setFont(customFont);\n\n                if (FileList.this.fileAttributesPreloaded) {\n                    label.setText(filenames[index]);\n                    label.setIcon(icons[index]);\n                } else {\n                    AbstractFile file = (AbstractFile)value;\n                    label.setText(file.getName());\n                    label.setIcon(file.getIcon());\n                }\n\n                return label;\n            }\n        });\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/list/SortableListPanel.kt",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2026 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.list\n\nimport com.mucommander.commons.collections.AlteredVector\nimport com.mucommander.ui.button.ArrowButton\nimport com.mucommander.utils.text.Translator\nimport java.awt.BorderLayout\nimport java.awt.Color\nimport java.awt.Dimension\nimport java.awt.GridLayout\nimport javax.swing.JPanel\nimport javax.swing.JScrollPane\n\n/**\n * SortableListPanel is a JPanel which contains a scrollable [DynamicList] in the center and two buttons\n * 'Move up' and 'Move down' buttons on the right side of the list which allow to move the items up and down and\n * easily reorder them within the list.\n * \n * @author Maxence Bernard\n */\nclass SortableListPanel<E>(\n    items: AlteredVector<E>\n) : JPanel(BorderLayout()) {\n    /**\n     * Returns the [DynamicList] used by this SortableListPanel.\n     */\n    @JvmField\n    val dynamicList: DynamicList<E> = DynamicList<E>(items)\n\n\n    /**\n     * Creates a new SortableListPanel with a [DynamicList] that uses the provided items [AlteredVector].\n     */\n    init {\n        add(\n            JScrollPane(dynamicList), BorderLayout.CENTER\n        )\n\n        val buttonPanel = JPanel(GridLayout(2, 1)).apply {\n            add(\n                ArrowButton(dynamicList.getMoveUpAction(), ArrowButton.Direction.UP).apply {\n                    preferredSize = Dimension(19, 0) // Constrain the button's size which by default is huge under Windows/Java 1.5\n                    setFocusable(false) // Make the button non-focusable so that it doesn't steal focus from the list\n                    setToolTipText(Translator.get(\"sortable_list.move_up\"))\n                }\n            )\n            add(\n                ArrowButton(dynamicList.getMoveDownAction(), ArrowButton.Direction.DOWN).apply {\n                    preferredSize = Dimension(19, 0) // Constrain the button's size which by default is huge under Windows/Java 1.5\n                    setFocusable(false) // Make the button non-focusable so that it doesn't steal focus from the list\n                    setToolTipText(Translator.get(\"sortable_list.move_down\"))\n                }\n            )\n        }\n\n        add(buttonPanel, BorderLayout.EAST)\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/list/package.html",
    "content": "<body>\n  Contains various components used to deal with JList.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/macosx/AppleScript.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.macosx;\n\nimport java.io.OutputStreamWriter;\nimport java.io.UnsupportedEncodingException;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.commons.runtime.OsVersion;\nimport com.mucommander.process.AbstractProcess;\nimport com.mucommander.process.ProcessListener;\nimport com.mucommander.process.ProcessRunner;\n\n/**\n * This class allows to init AppleScript code under Mac OS X, relying on the <code>osacript</code> command available\n * that comes with any install of Mac OS X. This command is used instead of the Cocoa-Java library which has been\n * deprecated by Apple.<br>\n * Calls to {@link #execute(String, StringBuilder)} on any OS other than Mac OS X will always fail.\n *\n * <p>\n * <b>Important notes about character encoding</b>:\n * <ul>\n *   <li>AppleScript 1.10- (Mac OS X 10.4 or lower) expects <i>MacRoman</i> encoding, not <i>UTF-8</i>. <b>That\n *       means the script should only contain characters that are part of the MacRoman charset</b>; any character\n *       that cannot be expressed in MacRoman will not be properly interpreted.\n *       The only way to pass Unicode text to a script is by reading it from a file.\n *       See <a href=\"http://www.satimage.fr/software/en/unicode_and_applescript.html\">http://www.satimage.fr/software/en/unicode_and_applescript.html</a>\n *       for more information on how to do so.\n *   </li>\n *   <li>AppleScript 2.0+ (Mac OS X 10.5 and up) is fully Unicode-aware and will properly interpret any Unicode\n *       character: \"AppleScript is now entirely Unicode-based. Comments and text constants in scripts may contain\n *       any Unicode characters, and all text processing is done in Unicode\".\n *       See <a href=\"http://www.apple.com/applescript/features/unicode.html\">http://www.apple.com/applescript/features/unicode.html</a>\n *       for more information.\n *   </li>\n * </ul>\n *\n * @author Maxence Bernard\n */\npublic class AppleScript {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(AppleScript.class);\n\t\n    /** The UTF-8 encoding */\n    public final static String UTF8 = \"UTF-8\";\n\n    /** The MacRoman encoding */\n    private final static String MACROMAN = \"MacRoman\";\n\n\n    /**\n     * Executes the given AppleScript and returns <code>true</code> if it completed its execution normally, i.e. without\n     * any error.\n     * The script's output is accumulated in the given <code>StringBuilder</code>. If the script completed its execution\n     * normally, the buffer will contain the script's standard output. If the script failed because of an error in it,\n     * the buffer will contain details about the error.\n     *\n     * <p>If the caller is not interested in the script's output, a <code>null</code> value can be passed which will\n     * speed the execution up a little.\n     *\n     * @param appleScript the AppleScript to execute\n     * @param outputBuffer the StringBuilder that will hold the script's output, <code>null</code> for no output\n     * @return true if the script was successfully executed, false if the\n     */\n    public static boolean execute(String appleScript, StringBuilder outputBuffer) {\n        return execute(appleScript, outputBuffer, null);\n    }\n\n\n    public static boolean execute(String appleScript, StringBuilder outputBuffer, AbstractFile currentDirectory) {\n        // No point in going any further if the current OS is not Mac OS X\n        if (!OsFamily.MAC_OS_X.isCurrent()) {\n            return false;\n        }\n\n        LOGGER.debug(\"Executing AppleScript: {}\", appleScript);\n\n        // Use the 'osascript' command to execute the AppleScript. The '-s o' flag tells osascript to print errors to\n        // stdout rather than stderr. The AppleScript is piped to the process instead of passing it as an argument\n        // ('-e' flag), for better control over the encoding and to remove any limitations on the maximum script size.\n        String[] tokens = new String[] {\n            \"osascript\",\n            \"-s\",\n            \"o\",\n        };\n\n        try {\n            // Execute the osascript command.\n            ProcessListener processListener = outputBuffer == null ? null : new ScriptOutputListener(outputBuffer, AppleScript.getScriptEncoding());\n            AbstractProcess process = ProcessRunner.execute(tokens, currentDirectory, processListener, null);\n            // Pipe the script to the osascript process.\n            try (OutputStreamWriter pout  = new OutputStreamWriter(process.getOutputStream(), getScriptEncoding())) {\n                pout.write(appleScript);\n            }\n\n            // Wait for the process to die\n            int returnCode = process.waitFor();\n\n            LOGGER.debug(\"osascript returned code={}, output={}\", returnCode, outputBuffer);\n\n            if (returnCode != 0) {\n            \tLOGGER.debug(\"osascript terminated abnormally\");\n                return false;\n            }\n\n            return true;\n        } catch(Exception e) {        // IOException, InterruptedException\n            // Shouldn't normally happen\n        \tLOGGER.debug(\"Unexcepted exception while executing AppleScript\", e);\n            return false;\n        }\n    }\n\n    /**\n     * Returns the encoding that AppleScript uses on the current runtime environment:\n     * <ul>\n     *   <li>{@link #UTF8} for AppleScript 2.0+ (Mac OS X 10.5 and up)</li>\n     *   <li>{@link #MACROMAN} for AppleScript 1.10- (Mac OS X 10.4 or lower)</li>\n     * </ul>\n     *\n     * If {@link #MACROMAN} is used, the scripts passed to {@link #execute(String, StringBuilder)} should not contain\n     * characters that are not part of the <i>MacRoman</i> charset or they will not be properly interpreted.\n     *\n     * @return the encoding that AppleScript uses on the current runtime environment\n     */\n    public static String getScriptEncoding() {\n        // - AppleScript 2.0+ (Mac OS X 10.5 and up) is fully Unicode-aware and expects a script in UTF-8 encoding.\n        // - AppleScript 1.3- (Mac OS X 10.4 or lower) expects MacRoman encoding, not UTF-8.\n        return OsVersion.MAC_OS_X_10_5.isCurrentOrHigher() ? UTF8 : MACROMAN;\n    }\n\n\n    /**\n     * This ProcessListener accumulates the output of the 'osascript' command and suppresses the trailing '\\n' character\n     * from the script's output.\n     */\n    private static class ScriptOutputListener implements ProcessListener {\n\n        private final StringBuilder outputBuffer;\n        private final String outputEncoding;\n\n        private ScriptOutputListener(StringBuilder outputBuffer, String outputEncoding) {\n            this.outputBuffer = outputBuffer;\n            this.outputEncoding = outputEncoding;\n        }\n\n        @Override\n        public void processOutput(byte[] buffer, int offset, int length) {\n            try {\n                outputBuffer.append(new String(buffer, offset, length, outputEncoding));\n            } catch(UnsupportedEncodingException e) {\n                // The encoding is necessarily supported\n            }\n        }\n\n        @Override\n        public void processOutput(String s) {\n        }\n\n        @Override\n        public void processDied(int returnValue) {\n            // Remove the trailing \"\\n\" character that osascript returns.\n            int len = outputBuffer.length();\n            if (len > 0 && outputBuffer.charAt(len-1) == '\\n') {\n                outputBuffer.setLength(len - 1);\n            }\n        }\n    }\n\n\n    // The following commented method executes an AppleScript using the deprecated Cocoa-Java library.\n    // We're now using the 'osascript' command instead, but this method is kept for the record in case Apple one day\n    // decides to un-deprecate the Cocoa-Java library.\n\n//    /**\n//     * Executes the given AppleScript and returns the script's output if it was successfully executed, <code>null</code>\n//     * if the script couldn't be compiled or if an error occurred while executing it.\n//     * An empty string <code>\"\"</code> is returned if the script doesn't output anything.\n//     *\n//     * @param appleScript the AppleScript to compile and execute\n//     * @return the script's output, null if an error occurred while compiling or executing the script\n//     */\n//    private static String executeAppleScript(String appleScript) {\n//        AppLogger.finer(\"Executing AppleScript \"+appleScript);\n//\n//        int pool = -1;\n//\n//        try {\n//            // Quote from Apple Cocoa-Java doc:\n//            // An autorelease pool is used to manage Foundation’s autorelease mechanism for Objective-C objects.\n//            // NSAutoreleasePool provides Java applications access to autorelease pools. Typically it is not\n//            // necessary for Java applications to use NSAutoreleasePools since Java manages garbage collection.\n//            // However, some situations require an autorelease pool; for instance, if you start off a thread that\n//            // calls Cocoa, there won’t be a top-level pool.\n//            pool = NSAutoreleasePool.push();\n//\n//            NSMutableDictionary errorInfo = new NSMutableDictionary();\n//            NSAppleEventDescriptor eventDescriptor = new NSAppleScript(appleScript).execute(errorInfo);\n//            if(eventDescriptor==null) {\n//                AppLogger.fine(\"Caught AppleScript error: \"+errorInfo.objectForKey(NSAppleScript.AppleScriptErrorMessage));\n//\n//                return null;\n//            }\n//\n//            String output = eventDescriptor.stringValue();  // Returns null if the script didn't output anything\n//            AppLogger.finer(\"AppleScript output=\"+output);\n//\n//            return output==null?\"\":output;\n//        }\n//        catch(Error e) {\n//            // Can happen if Cocoa-java is not in the classpath\n//            AppLogger.fine(\"Unexcepted error while executing AppleScript (cocoa-java not available?)\", e);\n//\n//            return null;\n//        }\n//        catch(Exception e) {\n//            // Try block is not supposed to throw any exception, but this is low-level stuff so just to be safe\n//            AppLogger.fine(\"Unexcepted exception while executing AppleScript\", e);\n//\n//            return null;\n//        }\n//        finally {\n//            if(pool!=-1)\n//                NSAutoreleasePool.pop(pool);\n//        }\n//    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/macosx/AppleScriptBuilder.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2018 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.macosx;\n\npublic class AppleScriptBuilder {\n    private final StringBuilder sb = new StringBuilder();\n\n    public AppleScriptBuilder append(String s) {\n        sb.append(s);\n        return this;\n    }\n\n    public AppleScriptBuilder nl() {\n        sb.append('\\n');\n        return this;\n    }\n\n    public AppleScriptBuilder appendQuoted(String s) {\n        sb.append('\"');\n        sb.append(s);\n        sb.append('\"');\n        return this;\n    }\n\n    @Override\n    public String toString() {\n        return sb.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/macosx/EAWTHandler.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2025 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.macosx;\n\nimport com.mucommander.TrolCommander;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.main.WindowManager;\n\nimport java.awt.*;\nimport java.awt.desktop.*;\nimport java.io.File;\n\n\n/**\n * This class registers the About, Preferences and Quit handlers using the com.apple.eawt API available\n */\nclass EAWTHandler {\n\n    EAWTHandler() {\n        Desktop desktop = Desktop.getDesktop();\n        desktop.setAboutHandler(this::showAbout);\n        desktop.setPreferencesHandler(this::showPreferences);\n        desktop.setQuitHandler(this::handleQuitRequestWith);\n        desktop.setOpenFileHandler(this::openFiles);\n    }\n\n    private void showAbout(AboutEvent e) {\n        OSXIntegration.showAbout();\n    }\n\n    private void showPreferences(PreferencesEvent e) {\n        OSXIntegration.showPreferences();\n    }\n\n    private void handleQuitRequestWith(QuitEvent quitEvent, QuitResponse quitResponse) {\n        if (OSXIntegration.doQuit()) {\n            quitResponse.performQuit();\n        } else {\n            quitResponse.cancelQuit();\n        }\n    }\n\n    public void openFiles(OpenFilesEvent openFilesEvent) {\n        // Wait until the application has been launched. This step is required to properly handle the case where the\n        // application is launched with a file to open, for instance when drag-n-dropping a file to the Dock icon\n        // when trolCommander is not started yet. In this case, this method is called while Launcher is still busy\n        // launching the application (no mainframe exists yet).\n        TrolCommander.waitUntilLaunched();\n        for (File f : openFilesEvent.getFiles()) {\n            AbstractFile file = FileFactory.getFile(f.toString());\n            if (file == null) {\n                continue;\n            }\n            FolderPanel activePanel = WindowManager.getCurrentMainFrame().getActivePanel();\n            if (file.isBrowsable()) {\n                activePanel.tryChangeCurrentFolder(file);\n            } else {\n                activePanel.tryChangeCurrentFolder(file.getParent(), file, false);\n            }\n        }\n\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/macosx/IMacOsWindow.java",
    "content": "package com.mucommander.ui.macosx;\n\nimport com.mucommander.commons.runtime.OsVersion;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\n\n\nimport javax.swing.*;\n\n\npublic interface IMacOsWindow extends RootPaneContainer {\n\n    default void initLookAndFeel() {\n        if (OsVersion.MAC_OS_X_10_4.isCurrentOrLower() || OsVersion.MAC_OS_X_10_13.isCurrentOrHigher()) {\n            getRootPane().putClientProperty(\"apple.awt.brushMetalLook\",\n                    TcConfigurations.getPreferences().getVariable(TcPreference.USE_BRUSHED_METAL, TcPreferences.DEFAULT_USE_BRUSHED_METAL));\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/macosx/OSXIntegration.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.macosx;\n\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.commons.runtime.OsVersion;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.ui.action.ActionManager;\nimport com.mucommander.ui.dialog.about.AboutDialog;\nimport com.mucommander.ui.dialog.shutdown.QuitDialog;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.WindowManager;\n\n/**\n * This class handles Mac OS X specifics when muCommander is started:\n * <ul>\n *  <li>Turns on/off brush metal based on preferences (default is on)\n *  <li>Turns screen menu bar based on preferences (default is on, no GUI for that pref)\n *  <li>Registers handlers for the 'About', 'Preferences' and 'Quit' application menu items\n * </ul>\n *\n * <p>The com.apple.eawt API is used to handle 'About', 'Preferences' and 'Quit' events and report back to the OS.\n *\n * @see EAWTHandler\n * @author Maxence Bernard\n */\npublic class OSXIntegration {\n\n    public OSXIntegration() {\n        if (!OsFamily.MAC_OS_X.isCurrent()) {\n            return;\n        }\n        // At the time of writing, the 'brushed metal' look causes the JVM to crash randomly under Leopard (10.5)\n        // so we disable brushed metal on that OS version but leave it for earlier versions where it works fine.\n        // See http://www.mucommander.com/forums/viewtopic.php?f=4&t=746 for more info about this issue.\n        if (OsVersion.MAC_OS_X_10_4.isCurrentOrLower() || OsVersion.MAC_OS_X_10_13.isCurrentOrHigher()) {\n            // Turn on/off brush metal look (default is off because still buggy when scrolling and panning dialog windows) :\n            //  \"Allows you to display your main windows with the 'textured' Aqua window appearance.\n            //   This property should be applied only to the primary application window,\n            //   and should not affect supporting windows like dialogs or preference windows.\"\n            System.setProperty(\"apple.awt.brushMetalLook\",\n                \"\"+ TcConfigurations.getPreferences().getVariable(TcPreference.USE_BRUSHED_METAL, TcPreferences.DEFAULT_USE_BRUSHED_METAL));\n        }\n\n        // Enables/Disables screen menu bar (default is on) :\n        //  \"if you are using the Aqua look and feel, this property puts Swing menus in the Mac OS X menu bar.\"\n        System.setProperty(\"apple.laf.useScreenMenuBar\", \"\"+ TcConfigurations.getPreferences().getVariable(TcPreference.USE_SCREEN_MENU_BAR,\n                                                                                             TcPreferences.DEFAULT_USE_SCREEN_MENU_BAR));\n\n        // Catch 'About', 'Preferences' and 'Quit' events\n        try {\n            new EAWTHandler();\n        } catch (Throwable t) {\n            t.printStackTrace();\n        }\n    }\n\n    /**\n     * Shows the 'About' dialog.\n     */\n    static void showAbout() {\n        MainFrame mainFrame = WindowManager.getCurrentMainFrame();\n        \n        // Do nothing (return) when in 'no events mode'\n        if (mainFrame.getNoEventsMode()) {\n            return;\n        }\n\n        new AboutDialog(mainFrame).showDialog();\n    }\n\n    /**\n     * Shows the 'Preferences' dialog.\n     */\n    static void showPreferences() {\n        MainFrame mainFrame = WindowManager.getCurrentMainFrame();\n\n        // Do nothing (return) when in 'no events mode'\n        if (mainFrame == null || mainFrame.getNoEventsMode()) {\n            return;\n        }\n\n        ActionManager.performAction(com.mucommander.ui.action.impl.ShowPreferencesAction.Descriptor.ACTION_ID, mainFrame);\n    }\n\n    /**\n     * Quits the application after displaying a confirmation dialog if it hasn't been disabled\n     * in the preferences. Return <code>true</code> if the operation has been aborted by user.\n     */\n    static boolean doQuit() {\n        // Ask the user for confirmation and abort if user refused to quit.\n        if (!QuitDialog.confirmQuit()) {\n            return false;\n        }\n\n        // We got a green -> quit!\n        WindowManager.quit();\n                \n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/macosx/TabbedPaneUICustomizer.java",
    "content": "package com.mucommander.ui.macosx;\n\nimport java.awt.Graphics;\nimport java.awt.Insets;\n\nimport javax.swing.JTabbedPane;\nimport javax.swing.plaf.TabbedPaneUI;\n\nimport com.apple.laf.AquaTabbedPaneContrastUI;\nimport com.apple.laf.AquaTabbedPaneUI;\n\npublic class TabbedPaneUICustomizer {\n\n\n\tprivate static final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0);\n\tprivate static final Insets CONTENT_BORDER_INSETS = new Insets(5, 0, 0, 0);\n\t\n\tpublic static void customizeTabbedPaneUI(JTabbedPane tabbedPane) {\n\t\tTabbedPaneUI tabbedPaneUI = tabbedPane.getUI();\n\n        if (tabbedPaneUI instanceof AquaTabbedPaneContrastUI) {\n\t\t\ttabbedPane.setUI(new CompactAquaTabbedPaneContrastUI());\n\t\t} else if (tabbedPaneUI instanceof AquaTabbedPaneUI) {\n\t\t\ttabbedPane.setUI(new CompactAquaTabbedPaneUI());\n\t\t}\n\t}\n\t\n\tprivate static class CompactAquaTabbedPaneUI extends AquaTabbedPaneUI {\n\n\t\t@Override\n\t\tprotected Insets getContentBorderInsets(final int arg0) {\n\t\t\treturn CONTENT_BORDER_INSETS;\n\t\t}\n\t\t\n\t\t@Override\n\t\tprotected Insets getTabAreaInsets(final int arg0) {\n\t\t\treturn EMPTY_INSETS;\n\t\t}\n\t\t\n\t\t@Override\n\t\tprotected Insets getContentDrawingInsets(final int arg0) {\n\t\t\treturn EMPTY_INSETS;\n\t\t}\n\n\t\t/**\n\t\t * No content border\n\t\t */\n\t\t@Override\n\t\tprotected void paintContentBorder(final Graphics g, final int tabPlacement, final int selectedIndex) {\n\t\t}\n\t}\n\t\n\tprivate static class CompactAquaTabbedPaneContrastUI extends AquaTabbedPaneContrastUI {\n\n\t\t@Override\n\t\tprotected Insets getContentBorderInsets(int arg0) {\n\t\t\treturn CONTENT_BORDER_INSETS;\n\t\t}\n\n\t\t@Override\n\t\tprotected Insets getTabAreaInsets(int arg0) {\n\t\t\treturn EMPTY_INSETS;\n\t\t}\n\n\t\t@Override\n\t\tprotected Insets getContentDrawingInsets(int arg0) {\n\t\t\treturn EMPTY_INSETS;\n\t\t}\n\n\t\t/**\n\t\t * No content border\n\t\t */\n\t\t@Override\n\t\tprotected void paintContentBorder(final Graphics g, final int tabPlacement, final int selectedIndex) {\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/macosx/package.html",
    "content": "<body>\n  OSX UI integration.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/BreadcrumbBar.kt",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/trolcommander\n * Copyright (C) 2014-2026 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify it under the terms of the GNU General\n * Public License as published by the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied\n * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License* along with this program.\n * If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.main\n\nimport com.mucommander.commons.file.AbstractFile\nimport com.mucommander.ui.theme.*\nimport java.awt.Cursor\nimport java.awt.FlowLayout\nimport java.awt.event.MouseAdapter\nimport java.awt.event.MouseEvent\nimport java.util.*\nimport javax.swing.JLabel\nimport javax.swing.JTextField\n\n/**\n * Renders the current directory as a horizontal row of hyperlink-style labels separated by `›` glyphs.\n * Clicking a label navigates the owning [FolderPanel] to the corresponding ancestor directory.\n *\n *\n * Extends [JTextField] (rather than `JPanel`) so that the look-and-feel paints the correct native border automatically.\n * On macOS Aqua the border renderer checks `instanceof JTextComponent`; a plain `JPanel` would not receive the beveled\n * round-rect treatment.\n * * [.paintComponent] is overridden to suppress text rendering and just fill the interior with the background color.\n *\n * Uses [AbstractFile.getParent] to walk the hierarchy, so it works uniformly for local paths (Windows, Unix) and remote file systems (SFTP, FTP…).\n */\ninternal class BreadcrumbBar(private val folderPanel: FolderPanel) : JTextField(), ThemeListener {\n    init {\n        isEditable = false\n        setFocusable(false)\n        setLayout(FlowLayout(FlowLayout.LEFT, 0, 0))\n        setBackground(ThemeManager.getCurrentColor(Theme.LOCATION_BAR_BACKGROUND_COLOR))\n        ThemeManager.addCurrentThemeListener(this)\n    }\n\n    /**\n     * Rebuilds the breadcrumb labels for the given [AbstractFile].\n     * Walks up via [AbstractFile.getParent] to collect all ancestors, then renders them root-first.\n     */\n    fun setFile(file: AbstractFile?) {\n        removeAll()\n\n        if (file == null) {\n            revalidate()\n            repaint()\n            return\n        }\n\n        // Collect ancestors from current directory up to the root\n        val stack: Deque<AbstractFile> = ArrayDeque<AbstractFile>()\n        var f: AbstractFile? = file\n        while (f != null) {\n            stack.push(f) // push → top of deque is the root after the loop\n            f = f.getParent()\n        }\n\n        var first = true\n        for (ancestor in stack) {\n            if (!first) {\n                add(makeSeparatorLabel())\n            }\n            first = false\n\n            val isLast = stack.peekLast() === ancestor\n            if (isLast) {\n                add(makePlainLabel(ancestor.getName()))\n            } else {\n                add(makeLinkLabel(ancestor.getName(), ancestor.absolutePath))\n            }\n        }\n\n        revalidate()\n        repaint()\n    }\n\n    /** A label that looks and behaves like a hyperlink.  */\n    private fun makeLinkLabel(text: String, targetPath: String?): JLabel {\n        val escapedText = escapeHtml(text)\n        val normalContent = \"<html>$escapedText</html>\"\n        val hoverContent = \"<html><u>$escapedText</u></html>\"\n\n        return JLabel(normalContent).apply {\n            setForeground(ThemeManager.getCurrentColor(Theme.LOCATION_BAR_FOREGROUND_COLOR))\n            setFont(ThemeManager.getCurrentFont(Theme.LOCATION_BAR_FONT))\n            setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR))\n            setBackground(ThemeManager.getCurrentColor(Theme.LOCATION_BAR_BACKGROUND_COLOR))\n            addMouseListener(object : MouseAdapter() {\n                override fun mousePressed(e: MouseEvent?) {\n                    folderPanel.tryChangeCurrentFolder(targetPath)\n                }\n\n                override fun mouseEntered(e: MouseEvent?) =\n                    setText(hoverContent)\n\n                override fun mouseExited(e: MouseEvent?) =\n                    setText(normalContent)\n            })\n        }\n    }\n\n    /** A plain (non-clickable) label for the current directory segment.  */\n    private fun makePlainLabel(text: String): JLabel =\n        JLabel(\"<html>\" + escapeHtml(text) + \"</html>\").apply {\n            setForeground(ThemeManager.getCurrentColor(Theme.LOCATION_BAR_FOREGROUND_COLOR))\n            setFont(ThemeManager.getCurrentFont(Theme.LOCATION_BAR_FONT))\n            setBackground(ThemeManager.getCurrentColor(Theme.LOCATION_BAR_BACKGROUND_COLOR))\n        }\n\n    private fun makeSeparatorLabel(): JLabel =\n        JLabel(SEPARATOR_GLYPH).apply {\n            setForeground(ThemeManager.getCurrentColor(Theme.LOCATION_BAR_FOREGROUND_COLOR))\n            setFont(ThemeManager.getCurrentFont(Theme.LOCATION_BAR_FONT))\n            setBackground(ThemeManager.getCurrentColor(Theme.LOCATION_BAR_BACKGROUND_COLOR))\n        }\n\n    /** Escapes the minimal HTML special characters that can appear in file names.  */\n    private fun escapeHtml(text: String): String {\n        return text.replace(\"&\", HTML_AMP)\n            .replace(\"<\", HTML_LT)\n            .replace(\">\", HTML_GT)\n    }\n\n    override fun colorChanged(event: ColorChangedEvent) {\n        if (event.colorId == Theme.LOCATION_BAR_BACKGROUND_COLOR) {\n            setBackground(event.color)\n        }\n    }\n\n    override fun fontChanged(event: FontChangedEvent?) {\n    }\n\n    companion object {\n        /** The `›` glyph rendered between path segments.  */\n        private const val SEPARATOR_GLYPH = \" \\u203A \"\n\n        private const val HTML_AMP = \"&amp;\"\n        private const val HTML_LT = \"&lt;\"\n        private const val HTML_GT = \"&gt;\"\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/ConfigurableFolderFilter.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main;\r\n\r\nimport com.mucommander.commons.conf.ConfigurationEvent;\r\nimport com.mucommander.commons.conf.ConfigurationListener;\r\nimport com.mucommander.commons.file.filter.AndFileFilter;\r\nimport com.mucommander.commons.file.filter.AttributeFileFilter;\r\nimport com.mucommander.commons.file.filter.AttributeFileFilter.FileAttribute;\r\nimport com.mucommander.commons.file.filter.FileFilter;\r\nimport com.mucommander.conf.TcConfigurations;\r\nimport com.mucommander.conf.TcPreferences;\r\nimport com.mucommander.conf.TcPreferencesAPI;\r\nimport com.mucommander.ui.main.tree.FoldersTreePanel;\r\n\r\nimport static com.mucommander.conf.TcPreference.*;\r\n\r\n/**\r\n * Filters out files that are unwanted when displaying a folder, based on user preferences.\r\n * <p>\r\n * This class is used for displaying the {@link FolderPanel} main folder panel and the\r\n * {@link FoldersTreePanel folder tree view}.\r\n *\r\n * @author Maxence Bernard, Mariusz Jakubowski\r\n */\r\npublic class ConfigurableFolderFilter extends AndFileFilter implements ConfigurationListener {\r\n    \r\n    private final FileFilter hiddenFileFilter = new AttributeFileFilter(FileAttribute.HIDDEN, true);\r\n    private final FileFilter dsFileFilter = new DSStoreFileFilter();\r\n    /** Filter used to IMAGE_FILTER out system files and folders that should not be displayed to inexperienced users. */\r\n    private final FileFilter systemFileFilter = new AttributeFileFilter(FileAttribute.SYSTEM, true);\r\n    \r\n\r\n    public ConfigurableFolderFilter() {\r\n        configureFilters();\r\n        TcConfigurations.addPreferencesListener(this);\r\n    }\r\n\r\n    private void configureFilters() {\r\n        final TcPreferencesAPI pref = TcConfigurations.getPreferences();\r\n        // Filters out hidden files, null when 'show hidden files' option is enabled\r\n        if (!pref.getVariable(SHOW_HIDDEN_FILES, TcPreferences.DEFAULT_SHOW_HIDDEN_FILES)) {\r\n            // This IMAGE_FILTER is inverted and matches non-hidden files\r\n            addFileFilter(hiddenFileFilter);\r\n        }\r\n\r\n        // Filters out Mac OS X .DS_Store files, null when 'show DS_Store files' option is enabled\r\n        if (!pref.getVariable(SHOW_DS_STORE_FILES, TcPreferences.DEFAULT_SHOW_DS_STORE_FILES))\r\n            addFileFilter(dsFileFilter);\r\n\r\n        if (!pref.getVariable(SHOW_SYSTEM_FOLDERS, TcPreferences.DEFAULT_SHOW_SYSTEM_FOLDERS))\r\n            addFileFilter(systemFileFilter);\r\n    }\r\n\r\n\r\n    /**\r\n     * Adds or removes filters based on configuration changes.\r\n     */\r\n    @Override\r\n    public void configurationChanged(ConfigurationEvent event) {\r\n        // Show or hide hidden files\r\n        switch (event.getVariable()) {\r\n            case TcPreferences.SHOW_HIDDEN_FILES:\r\n                if (event.getBooleanValue()) {\r\n                    removeFileFilter(hiddenFileFilter);\r\n                } else {\r\n                    addFileFilter(hiddenFileFilter);\r\n                }\r\n                break;\r\n            // Show or hide .DS_Store files (Mac OS X option)\r\n            case TcPreferences.SHOW_DS_STORE_FILES:\r\n                if (event.getBooleanValue()) {\r\n                    removeFileFilter(dsFileFilter);\r\n                } else {\r\n                    addFileFilter(dsFileFilter);\r\n                }\r\n                break;\r\n            // Show or hide system folders (Mac OS X option)\r\n            case TcPreferences.SHOW_SYSTEM_FOLDERS:\r\n                if (event.getBooleanValue()) {\r\n                    removeFileFilter(systemFileFilter);\r\n                } else {\r\n                    addFileFilter(systemFileFilter);\r\n                }\r\n                break;\r\n        }\r\n\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/DSStoreFileFilter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main;\n\nimport com.mucommander.commons.file.filter.AbstractFilenameFilter;\nimport com.mucommander.commons.file.filter.FilenameFilter;\n\n\n/**\n * <code>DSStoreFileFilter</code> is a {@link FilenameFilter} that matches Mac OS X '.DS_Store' files.\n *\n * @author Maxence Bernard\n */\npublic class DSStoreFileFilter extends AbstractFilenameFilter {\n\n    public boolean accept(String filename) {\n        return !\".DS_Store\".equals(filename);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/DrivePopupButton.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main;\n\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.net.MalformedURLException;\nimport java.util.*;\nimport java.util.List;\nimport java.util.regex.PatternSyntaxException;\n\nimport javax.swing.*;\nimport javax.swing.filechooser.FileSystemView;\n\nimport com.mucommander.adb.AndroidMenu;\nimport com.mucommander.adb.AdbUtils;\nimport com.mucommander.bonjour.BonjourDirectory;\nimport com.mucommander.utils.FileIconsCache;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport com.mucommander.bonjour.BonjourMenu;\nimport com.mucommander.bonjour.BonjourService;\nimport com.mucommander.bookmark.Bookmark;\nimport com.mucommander.bookmark.BookmarkListener;\nimport com.mucommander.bookmark.BookmarkManager;\nimport com.mucommander.commons.conf.ConfigurationEvent;\nimport com.mucommander.commons.conf.ConfigurationListener;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.FileProtocols;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.filter.PathFilter;\nimport com.mucommander.commons.file.filter.RegexpPathFilter;\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.action.impl.OpenLocationAction;\nimport com.mucommander.ui.button.PopupButton;\nimport com.mucommander.ui.dialog.server.FTPPanel;\nimport com.mucommander.ui.dialog.server.HTTPPanel;\nimport com.mucommander.ui.dialog.server.NFSPanel;\nimport com.mucommander.ui.dialog.server.SFTPPanel;\nimport com.mucommander.ui.dialog.server.SMBPanel;\nimport com.mucommander.ui.dialog.server.ServerConnectDialog;\nimport com.mucommander.ui.dialog.server.ServerPanel;\nimport com.mucommander.ui.event.LocationEvent;\nimport com.mucommander.ui.event.LocationListener;\nimport com.mucommander.ui.helper.MnemonicHelper;\nimport com.mucommander.ui.icon.CustomFileIconProvider;\nimport com.mucommander.ui.icon.FileIcons;\nimport com.mucommander.ui.icon.IconManager;\nimport ru.trolsoft.ui.TMenuSeparator;\n\n\n/**\n * <code>DrivePopupButton</code> is a button which, when clicked, pops up a menu with a list of volumes items that be used\n * to change the current folder.\n *\n * @author Maxence Bernard\n */\n@Slf4j\npublic class DrivePopupButton extends PopupButton implements BookmarkListener, ConfigurationListener, LocationListener {\n    /**\n     * FolderPanel instance that contains this button\n     */\n    private final FolderPanel folderPanel;\n\n    /**\n     * Current volumes\n     */\n    private static AbstractFile[] volumes;\n\n    /**\n     * static FileSystemView instance, has a (non-null) value only under Windows\n     */\n    private static FileSystemView fileSystemView;\n\n    /**\n     * Caches extended drive names, has a (non-null) value only under Windows\n     */\n    private static Map<AbstractFile, String> extendedNameCache;\n\n    /**\n     * Caches drive icons\n     */\n    private static final Map<AbstractFile, Icon> iconCache = new HashMap<>();\n\n\n    /**\n     * Filters out volumes from the list based on the exclude regexp defined in the configuration, null if the regexp\n     * is not defined.\n     */\n    private static PathFilter volumeFilter;\n\n\n    static {\n        if (OsFamily.WINDOWS.isCurrent()) {\n            fileSystemView = FileSystemView.getFileSystemView();\n            extendedNameCache = new HashMap<>();\n        }\n\n        try {\n            String excludeRegexp = TcConfigurations.getPreferences().getVariable(TcPreference.VOLUME_EXCLUDE_REGEXP);\n            if (excludeRegexp != null) {\n                volumeFilter = new RegexpPathFilter(excludeRegexp, true);\n                volumeFilter.setInverted(true);\n            }\n        } catch (PatternSyntaxException e) {\n            log.info(\"Invalid regexp for conf variable \" + TcPreferences.VOLUME_EXCLUDE_REGEXP, e);\n        }\n\n        // Initialize the volumes list\n        volumes = getDisplayableVolumes();\n    }\n\n\n    /**\n     * Creates a new <code>DrivePopupButton</code> which is to be added to the given FolderPanel.\n     *\n     * @param folderPanel the FolderPanel instance this button will be added to\n     */\n    DrivePopupButton(FolderPanel folderPanel) {\n        this.folderPanel = folderPanel;\n\n        // Listen to location events to update the button when the current folder changes\n        folderPanel.getLocationManager().addLocationListener(this);\n\n        // Listen to bookmark changes to update the button if a bookmark corresponding to the current folder\n        // has been added/edited/removed\n        BookmarkManager.addBookmarkListener(this);\n\n        // Listen to configuration changes to update the button if the system file icons policy has changed\n        TcConfigurations.addPreferencesListener(this);\n\n        // Use new JButton decorations introduced in Mac OS X 10.5 (Leopard)\n        //if (OsFamily.MAC_OS_X.isCurrent() && OsVersion.MAC_OS_X_10_5.isCurrentOrHigher()) {\n        //setMargin(new Insets(6,8,6,8));\n        //putClientProperty(\"JComponent.sizeVariant\", \"small\");\n        //putClientProperty(\"JComponent.sizeVariant\", \"large\");\n        //putClientProperty(\"JButton.buttonType\", \"textured\");\n        //}\n    }\n\n\n    /**\n     * Updates the button's label and icon to reflect the current folder and match one of the current volumes:\n     * <<ul>\n     * <li>If the specified folder corresponds to a bookmark, the bookmark's name will be displayed\n     * <li>If the specified folder corresponds to a local file, the enclosing volume's name will be displayed\n     * <li>If the specified folder corresponds to a remote file, the protocol's name will be displayed\n     * </ul>\n     * The button's icon will be the current folder's one.\n     */\n    private void updateButton() {\n        AbstractFile currentFolder = folderPanel.getCurrentFolder();\n\n        setText(buildLabel(currentFolder));\n//        setToolTipText(newToolTip);\n        // Set the folder icon based on the current system icons policy\n        setIcon(FileIcons.getFileIcon(currentFolder));\n    }\n\n    private String buildLabel(AbstractFile currentFolder) {\n        String currentPath = currentFolder != null ? currentFolder.getAbsolutePath() : null;\n        FileURL currentURL = currentFolder != null ? currentFolder.getURL() : null;\n\n//        String newToolTip = null;\n\n        // First tries to find a bookmark matching the specified folder\n        List<Bookmark> bookmarks = BookmarkManager.getBookmarks();\n\n        String newLabel = null;\n        for (Bookmark b : bookmarks) {\n            if (currentPath != null && currentPath.equals(b.getLocation())) {\n                // Note: if several bookmarks match current folder, the first one will be used\n                newLabel = b.getName();\n                break;\n            }\n        }\n        if (newLabel != null) {\n            return newLabel;\n        }\n\n        // If no bookmark matched current folder\n        String protocol = currentURL != null ? currentURL.getScheme() : null;\n        if (!FileProtocols.FILE.equals(protocol)) {\n            // Remote file, use the protocol's name\n            return protocol != null ? protocol.toUpperCase() : \"\";\n        } else {\n            // Local file, use volume's name\n\n            // Patch for Windows UNC network paths (weakly characterized by having a host different from 'localhost'):\n            // display 'SMB' which is the underlying protocol\n            if (OsFamily.WINDOWS.isCurrent() && !FileURL.LOCALHOST.equals(currentURL.getHost())) {\n                return \"SMB\";\n            } else {\n                // getCanonicalPath() must be avoided under Windows for the following reasons:\n                // a) it is not necessary, Windows doesn't have symlinks\n                // b) it triggers the dreaded 'No disk in drive' error popup dialog.\n                // c) when network drives are present but not mounted (e.g. X:\\ mapped onto an SMB share),\n                // getCanonicalPath which is I/O bound will take a looooong time to execute\n\n                int bestIndex = getBestIndex(getVolumePath(currentFolder));\n                return volumes[bestIndex].getName();\n\n                // Not used because the call to FileSystemView is slow\n//                    if(fileSystemView!=null)\n//                        newToolTip = getWindowsExtendedDriveName(volumes[bestIndex]);\n            }\n        }\n    }\n\n    private int getBestIndex(String currentPath) {\n        int bestLength = -1;\n        int bestIndex = 0;\n        for (int i = 0; i < volumes.length; i++) {\n            String volumePath = getVolumePath(volumes[i]).toLowerCase();\n            int len = volumePath.length();\n            if (currentPath.startsWith(volumePath) && len > bestLength) {\n                bestIndex = i;\n                bestLength = len;\n            }\n        }\n        return bestIndex;\n    }\n\n    @NotNull\n    private String getVolumePath(AbstractFile file) {\n        if (OsFamily.WINDOWS.isCurrent()) {\n            return file.getAbsolutePath(false);\n        } else {\n            return file.getCanonicalPath(false);\n        }\n    }\n\n\n    /**\n     * Returns the extended name of the given local file, e.g. \"Local Disk (C:)\" for C:\\. The returned value is\n     * interesting only under Windows. This method is I/O bound and very slow so it should not be called from the main\n     * event thread.\n     *\n     * @param localFile the file for which to return the extended name\n     * @return the extended name of the given local file\n     */\n    private static String getExtendedDriveName(AbstractFile localFile) {\n        // Note: fileSystemView.getSystemDisplayName(java.io.File) is unfortunately very very slow\n        String name = fileSystemView.getSystemDisplayName((java.io.File) localFile.getUnderlyingFileObject());\n\n        if (name == null || name.isEmpty()) {   // This happens for CD/DVD drives when they don't contain any disc\n            return localFile.getName();\n        }\n\n        return name;\n    }\n\n\n    /**\n     * Returns the list of volumes to be displayed in the popup menu.\n     *\n     * <p>The raw list of volumes is fetched using {@link LocalFile#getVolumes()} and then\n     * filtered using the regexp defined in the {@link TcPreferences#VOLUME_EXCLUDE_REGEXP} configuration variable\n     * (if defined).\n     *\n     * @return the array of volumes to be displayed in the popup menu\n     */\n    private static AbstractFile[] getDisplayableVolumes() {\n        AbstractFile[] volumes = LocalFile.getVolumes();\n\n        if (volumeFilter != null) {\n            return volumeFilter.filter(volumes);\n        }\n\n        return volumes;\n    }\n\n    @Override\n    public JPopupMenu getPopupMenu() {\n        JPopupMenu popupMenu = new JPopupMenu();\n\n        // Update the list of volumes in case new ones were mounted\n        volumes = getDisplayableVolumes();\n\n        // Add volumes\n        final MainFrame mainFrame = folderPanel.getMainFrame();\n\n        MnemonicHelper mnemonicHelper = new MnemonicHelper();   // Provides mnemonics and ensures uniqueness\n\n        addVolumes(popupMenu, mainFrame, mnemonicHelper);\n        popupMenu.add(new TMenuSeparator());\n\n        addBookmarks(popupMenu, mainFrame, mnemonicHelper);\n        popupMenu.add(new TMenuSeparator());\n\n        // Add 'Network shares' shortcut\n        if (FileFactory.isRegisteredProtocol(FileProtocols.SMB)) {\n            TcAction action = new CustomOpenLocationAction(mainFrame, new Bookmark(Translator.get(\"drive_popup.network_shares\"), \"smb:///\", null));\n            action.setIcon(IconManager.getIcon(IconManager.IconSet.FILE, CustomFileIconProvider.NETWORK_ICON_NAME));\n            setMnemonic(popupMenu.add(action), mnemonicHelper);\n        }\n\n        if (BonjourDirectory.isActive()) {\n            // Add Bonjour services menu\n            setMnemonic(popupMenu.add(new BonjourMenu() {\n                @Override\n                public TcAction getMenuItemAction(BonjourService bs) {\n                    return new CustomOpenLocationAction(mainFrame, bs);\n                }\n            }), mnemonicHelper);\n        }\n\n        addAdbDevices(popupMenu, mainFrame, mnemonicHelper);\n        popupMenu.add(new TMenuSeparator());\n\n        // Add 'connect to server' shortcuts\n        setMnemonic(popupMenu.add(new ServerConnectAction(\"SMB...\", SMBPanel.class)), mnemonicHelper);\n        setMnemonic(popupMenu.add(new ServerConnectAction(\"FTP...\", FTPPanel.class)), mnemonicHelper);\n        setMnemonic(popupMenu.add(new ServerConnectAction(\"SFTP...\", SFTPPanel.class)), mnemonicHelper);\n        setMnemonic(popupMenu.add(new ServerConnectAction(\"HTTP...\", HTTPPanel.class)), mnemonicHelper);\n        setMnemonic(popupMenu.add(new ServerConnectAction(\"NFS...\", NFSPanel.class)), mnemonicHelper);\n\n        return popupMenu;\n    }\n\n\n    private void addVolumes(JPopupMenu popupMenu, MainFrame mainFrame, MnemonicHelper mnemonicHelper) {\n        boolean useExtendedDriveNames = fileSystemView != null;\n        List<JMenuItem> itemsV = new ArrayList<>();\n\n        int nbVolumes = volumes.length;\n        for (int i = 0; i < nbVolumes; i++) {\n            TcAction action = new CustomOpenLocationAction(mainFrame, volumes[i]);\n            String volumeName = volumes[i].getName();\n\n            // If several volumes have the same filename, use the volume's path for the action's label instead of the\n            // volume's path, to disambiguate\n            for (int j = 0; j < nbVolumes; j++) {\n                if (j != i && volumes[j].getName().equalsIgnoreCase(volumeName)) {\n                    action.setLabel(volumes[i].getAbsolutePath());\n                    break;\n                }\n            }\n\n            JMenuItem item = popupMenu.add(action);\n            setMnemonic(item, mnemonicHelper);\n\n            // Set icon from cache\n            Icon icon = iconCache.get(volumes[i]);\n            if (icon != null) {\n                item.setIcon(icon);\n            }\n\n            if (useExtendedDriveNames) {\n                // Use the last known value (if any) while we update it in a separate thread\n                String previousExtendedName = extendedNameCache.get(volumes[i]);\n                if (previousExtendedName != null) {\n                    item.setText(previousExtendedName);\n                }\n\n            }\n            itemsV.add(item);   // JMenu offers no way to retrieve a particular JMenuItem, so we have to keep them\n        }\n\n        new RefreshDriveNamesAndIcons(popupMenu, itemsV).start();\n    }\n\n    private void addBookmarks(JPopupMenu popupMenu, MainFrame mainFrame, MnemonicHelper mnemonicHelper) {\n        // Add bookmarks\n        List<Bookmark> bookmarks = BookmarkManager.getBookmarks();\n        if (!bookmarks.isEmpty()) {\n            addBookmarksGroup(popupMenu, mainFrame, mnemonicHelper, bookmarks, null);\n        } else {\n            // No bookmark : add a disabled menu item saying there is no bookmark\n            popupMenu.add(Translator.get(\"bookmarks_menu.no_bookmark\")).setEnabled(false);\n        }\n    }\n\n    private void addBookmarksGroup(JComponent parentMenu, MainFrame mainFrame, MnemonicHelper mnemonicHelper,\n                                   List<Bookmark> bookmarks, String parent) {\n        for (Bookmark b : bookmarks) {\n            if ((b.getParent() == null && parent == null) || (parent != null && parent.equals(b.getParent()))) {\n                if (b.getName().equals(BookmarkManager.BOOKMARKS_SEPARATOR) && b.getLocation().isEmpty()) {\n                    parentMenu.add(new TMenuSeparator());\n                    continue;\n                }\n                if (b.getLocation().isEmpty()) {\n                    JMenu groupMenu = new JMenu(b.getName());\n                    parentMenu.add(groupMenu);\n                    addBookmarksGroup(groupMenu, mainFrame, mnemonicHelper, bookmarks, b.getName());\n                    setMnemonic(groupMenu, mnemonicHelper);\n                } else {\n                    JMenuItem item = createBookmarkMenuItem(parentMenu, mainFrame, b);\n                    setMnemonic(item, mnemonicHelper);\n                }\n            }\n        }\n    }\n\n    private JMenuItem createBookmarkMenuItem(JComponent parentMenu, MainFrame mainFrame, Bookmark b) {\n        JMenuItem item;\n        if (parentMenu instanceof JPopupMenu) {\n            item = ((JPopupMenu) parentMenu).add(new CustomOpenLocationAction(mainFrame, b));\n        } else {\n            item = ((JMenu) parentMenu).add(new CustomOpenLocationAction(mainFrame, b));\n        }\n        //JMenuItem item = popupMenu.add(new CustomOpenLocationAction(mainFrame, b));\n        String location = b.getLocation();\n        if (!location.contains(\"://\")) {\n            AbstractFile file = FileFactory.getFile(location);\n            if (file != null) {\n                Icon icon = FileIconsCache.getInstance().getIcon(file);\n                if (icon != null) {\n                    item.setIcon(icon);\n                }\n//                        Image image = FileIconsCache.getInstance().getImageIcon(file);\n//                        if (image != null) {\n//                            item.setIcon(new ImageIcon(image));\n//                        }\n            }\n        } else if (location.startsWith(\"ftp://\") || location.startsWith(\"sftp://\") || location.startsWith(\"http://\")) {\n            item.setIcon(IconManager.getIcon(IconManager.IconSet.FILE, CustomFileIconProvider.NETWORK_ICON_NAME));\n        } else if (location.startsWith(\"adb://\")) {\n            item.setIcon(IconManager.getIcon(IconManager.IconSet.FILE, CustomFileIconProvider.ANDROID_ICON_NAME));\n        }\n        return item;\n    }\n\n\n    private void addAdbDevices(JPopupMenu popupMenu, MainFrame mainFrame, MnemonicHelper mnemonicHelper) {\n        if (AdbUtils.checkAdb()) {\n            setMnemonic(popupMenu.add(new AndroidMenu() {\n                @Override\n                public TcAction getMenuItemAction(String deviceSerial) {\n                    FileURL url = getDeviceURL(deviceSerial);\n                    return new CustomOpenLocationAction(mainFrame, url);\n                }\n\n                @Nullable\n                private FileURL getDeviceURL(String deviceSerial) {\n                    try {\n                        return FileURL.getFileURL(\"adb://\" + deviceSerial);\n                    } catch (MalformedURLException e) {\n                        log.error(\"Device URL error\", e);\n                        return null;\n                    }\n                }\n            }), mnemonicHelper);\n        }\n    }\n\n\n    /**\n     * Calls to getExtendedDriveName(String) are very slow, so they are performed in a separate thread\n     * to not lock the main even thread. The popup menu gets first displayed with the short drive names, and\n     * then refreshed with the extended names as they are retrieved.\n     */\n    private static class RefreshDriveNamesAndIcons extends Thread {\n\n        private final JPopupMenu popupMenu;\n        private final List<JMenuItem> items;\n\n        RefreshDriveNamesAndIcons(JPopupMenu popupMenu, List<JMenuItem> items) {\n            super(\"RefreshDriveNamesAndIcons\");\n            this.popupMenu = popupMenu;\n            this.items = items;\n        }\n\n        @Override\n        public void run() {\n            final boolean useExtendedDriveNames = fileSystemView != null;\n            for (int i = 0; i < items.size(); i++) {\n                final JMenuItem item = items.get(i);\n                final String extendedName = getExtendedDriverName(useExtendedDriveNames, volumes[i]);\n                final Icon icon = getIcon(volumes[i]);\n\n                SwingUtilities.invokeLater(() -> {\n                    if (useExtendedDriveNames) {\n                        item.setText(extendedName);\n                    }\n                    if (icon != null) {\n                        item.setIcon(icon);\n                    }\n                });\n\n            }\n\n            // Re-calculate the popup menu's dimensions\n            SwingUtilities.invokeLater(() -> {\n                popupMenu.invalidate();\n                popupMenu.pack();\n            });\n        }\n\n        @Nullable\n        private Icon getIcon(AbstractFile file) {\n            // Set system icon for volumes, only if system icons are available on the current platform\n            final Icon icon = FileIcons.hasProperSystemIcons() ? FileIcons.getSystemFileIcon(file) : null;\n            if (icon != null) {\n                iconCache.put(file, icon);\n            }\n            return icon;\n        }\n\n        @Nullable\n        private String getExtendedDriverName(boolean useExtendedDriveNames, AbstractFile file) {\n            if (useExtendedDriveNames) {\n                // Under Windows, show the extended drive name (e.g. \"Local Disk (C:)\" instead of just \"C:\") but use\n                // the simple drive name for the mnemonic (i.e. 'C' instead of 'L').\n                String extendedName = getExtendedDriveName(file);\n\n                // Keep the extended name for later (see above)\n                extendedNameCache.put(file, extendedName);\n                return extendedName;\n            }\n            return null;\n        }\n\n    }\n\n\n    /**\n     * Convenience method that sets a mnemonic to the given JMenuItem, using the specified MnemonicHelper.\n     *\n     * @param menuItem       the menu item for which to set a mnemonic\n     * @param mnemonicHelper the MnemonicHelper instance to be used to determine the mnemonic's character.\n     */\n    private void setMnemonic(JMenuItem menuItem, MnemonicHelper mnemonicHelper) {\n        menuItem.setMnemonic(mnemonicHelper.getMnemonic(menuItem.getText()));\n    }\n\n\n    @Override\n    public void bookmarksChanged() {\n        // Refresh label in case a bookmark with the current location was changed\n        updateButton();\n    }\n\n\n    /**\n     * Listens to certain configuration variables.\n     */\n    @Override\n    public void configurationChanged(ConfigurationEvent event) {\n        String var = event.getVariable();\n\n        // Update the button's icon if the system file icons policy has changed\n        if (var.equals(TcPreferences.USE_SYSTEM_FILE_ICONS)) {\n            updateButton();\n        }\n    }\n\n    @Override\n    public Dimension getPreferredSize() {\n        // Limit button's maximum width to something reasonable and leave enough space for location field,\n        // as bookmarks name can be as long as users want them to be.\n        // Note: would be better to use JButton.setMaximumSize() but it doesn't seem to work\n        Dimension d = super.getPreferredSize();\n        if (d.width > 160) {\n            d.width = 160;\n        }\n        return d;\n    }\n\n\n    /**\n     * This action pops up {@link com.mucommander.ui.dialog.server.ServerConnectDialog} for a specified protocol.\n     */\n    private class ServerConnectAction extends AbstractAction {\n        private final Class<? extends ServerPanel> serverPanelClass;\n\n        private ServerConnectAction(String label, Class<? extends ServerPanel> serverPanelClass) {\n            super(label);\n            this.serverPanelClass = serverPanelClass;\n        }\n\n        public void actionPerformed(ActionEvent actionEvent) {\n            new ServerConnectDialog(folderPanel, serverPanelClass).showDialog();\n        }\n    }\n\n\n    /**\n     * This modified {@link OpenLocationAction} changes the current folder on the {@link FolderPanel} that contains\n     * this button, instead of the currently active {@link FolderPanel}.\n     */\n    private class CustomOpenLocationAction extends OpenLocationAction {\n\n        CustomOpenLocationAction(MainFrame mainFrame, Bookmark bookmark) {\n            super(mainFrame, new HashMap<>(), bookmark);\n        }\n\n        CustomOpenLocationAction(MainFrame mainFrame, AbstractFile file) {\n            super(mainFrame, new HashMap<>(), file);\n        }\n\n        CustomOpenLocationAction(MainFrame mainFrame, BonjourService bs) {\n            super(mainFrame, new HashMap<>(), bs);\n        }\n\n        CustomOpenLocationAction(MainFrame mainFrame, FileURL url) {\n            super(mainFrame, new HashMap<>(), url);\n        }\n\n        @Override\n        protected FolderPanel getFolderPanel() {\n            return folderPanel;\n        }\n    }\n\n\n    @Override\n    public void locationChanged(LocationEvent e) {\n        // Update the button's label to reflect the new current folder\n        updateButton();\n    }\n\n    public void locationChanging(LocationEvent locationEvent) {\n    }\n\n    public void locationCancelled(LocationEvent locationEvent) {\n    }\n\n    public void locationFailed(LocationEvent locationEvent) {\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/FolderPanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main;\n\nimport com.mucommander.auth.CredentialsMapping;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.core.FolderChangeMonitor;\nimport com.mucommander.core.LocalLocationHistory;\nimport com.mucommander.core.LocationChanger;\nimport com.mucommander.core.LocationChanger.ChangeFolderThread;\nimport com.mucommander.ui.PreloadedJFrame;\nimport com.mucommander.ui.action.ActionKeymap;\nimport com.mucommander.ui.action.ActionManager;\nimport com.mucommander.ui.action.impl.FocusNextAction;\nimport com.mucommander.ui.action.impl.FocusPreviousAction;\nimport com.mucommander.ui.dnd.FileDragSourceListener;\nimport com.mucommander.ui.dnd.FileDropTargetListener;\nimport com.mucommander.ui.event.LocationManager;\nimport com.mucommander.ui.event.TableSelectionListener;\nimport com.mucommander.ui.main.quicklist.*;\nimport com.mucommander.ui.main.table.FileTable;\nimport com.mucommander.ui.main.table.views.full.FileTableConfiguration;\nimport com.mucommander.ui.main.tabs.ConfFileTableTab;\nimport com.mucommander.ui.main.tabs.FileTableTab;\nimport com.mucommander.ui.main.tabs.FileTableTabs;\nimport com.mucommander.ui.main.tree.FoldersTreePanel;\nimport com.mucommander.ui.quicklist.QuickList;\nimport com.mucommander.ui.quicklist.QuickListContainer;\nimport com.mucommander.ui.tabs.ActiveTabListener;\nimport lombok.Getter;\nimport lombok.extern.slf4j.Slf4j;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.dnd.DropTarget;\nimport java.awt.event.FocusEvent;\nimport java.awt.event.FocusListener;\nimport java.awt.event.KeyEvent;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * Folder pane that contains the table that displays the contents of the current directory and allows navigation, the\n * drive button, and the location field.\n *\n * @author Maxence Bernard, Arik Hadas\n */\n@Slf4j\npublic class FolderPanel implements FocusListener, QuickListContainer, ActiveTabListener {\n    @Getter private final JPanel panel;\n    @Getter private boolean previewMode;\n\n    /** The following constants are used to identify the left and right folder panels */\n\tpublic enum FolderPanelType { LEFT, RIGHT }\n\n    /**\n     * -- GETTER --\n     *  Returns the MainFrame that contains this panel.\n     */\n    @Getter\n    final MainFrame mainFrame;\n\n    /**\n     * -- GETTER --\n     *  Returns the LocationManager instance that notifies registered listeners of location changes that occur in this FolderPanel.\n     */\n    @Getter\n    private final LocationManager locationManager = new LocationManager(this);\n\n    /**\n     * -- GETTER --\n     *  Returns the DrivePopupButton contained by this panel.\n     */\n    /*  We're NOT using JComboBox anymore because of its strange behavior:\n           it calls actionPerformed() each time an item is highlighted with the arrow (UP/DOWN) keys,\n           so there is no way to tell if it's the final selection (ENTER) or not.\n       */\n    @Getter\n    private final DrivePopupButton driveButton;\n    /**\n     * -- GETTER --\n     *  Returns the LocationTextField contained by this panel.\n     */\n    @Getter\n    private final LocationTextField locationTextField;\n    /**\n     * -- GETTER --\n     *  Returns the FileTable contained by this panel.\n     */\n    @Getter\n    private final FileTable fileTable;\n    /**\n     * -- GETTER --\n     *  Returns the FileTable tabs contained by this panel.\n     */\n    @Getter\n    private final FileTableTabs tabs;\n    /**\n     * -- GETTER --\n     *  Returns a panel with a folders tree.\n     */\n    @Getter\n    private final FoldersTreePanel foldersTreePanel;\n    private final JSplitPane treeSplitPane;\n\n    @Getter\n    private final FileDragSourceListener fileDragSourceListener;\n\n    private final LocationChanger locationChanger;\n\n    /** Is directory tree visible\n     * -- GETTER --\n     *  Returns true if a directory tree is visible.\n     */\n    @Getter\n    private boolean treeVisible = false;\n\n    /** Saved width of a directory tree (when it's not visible) */ \n    private int oldTreeWidth = 150;\n\n    /** Array of all the existing pop ups for this panel's FileTable **/\n    private QuickList[] fileTablePopups;\n\n    private PreviewPanel previewPanel;\n    private final JPanel locationPanel;\n\n    private final TableSelectionListener previewTableSelectionListener = new TableSelectionListener() {\n        @Override\n        public void selectedFileChanged(FileTable source) {\n            if (previewMode) {\n                AbstractFile file = source.getFolderPanel().getFileTable().getSelectedFile();\n                previewPanel.loadFile(file);\n            }\n        }\n\n        @Override\n        public void markedFilesChanged(FileTable source) {\n        }\n    };\n\n\n    /* TODO branch private boolean branchView; */\n\n    FolderPanel(MainFrame mainFrame, ConfFileTableTab[] initialTabs, int indexOfSelectedTab, FileTableConfiguration conf) {\n        panel = PreloadedJFrame.getJPanel(new BorderLayout());\n        log.trace(\" initialTabs:\");\n        for (FileTableTab tab:initialTabs) {\n            log.trace(\"\\t{}\", tab.getLocation() != null ? tab.getLocation().toString() : null);\n        }\n        \t\t\n        this.mainFrame = mainFrame;\n        \n        // No decoration for this panel\n        panel.setBorder(null);\n\n        locationPanel = PreloadedJFrame.getJPanel(new GridBagLayout());\n        GridBagConstraints c = new GridBagConstraints();\n        c.fill = GridBagConstraints.HORIZONTAL;\n        c.gridy = 0;\n\n        // create and add drive button\n        this.driveButton = new DrivePopupButton(this);\n        c.weightx = 0;\n        c.gridx = 0;        \n        locationPanel.add(driveButton, c);\n\n        // Create location text field and wrap it in a LocationBar that can\n        // alternate between the text field and a breadcrumb view (Ctrl key).\n        this.locationTextField = new LocationTextField(this);\n        LocationBar locationBar = new LocationBar(this, locationTextField);\n\n        // Give location field all the remaining space until the PoupupsButton\n        c.weightx = 1;\n        c.gridx = 1;\n        // Add some space between drive button and location combo box (none by default)\n        c.insets = new Insets(0, 4, 0, 0);\n        locationPanel.add(locationBar, c);\n\n        panel.add(locationPanel, BorderLayout.NORTH);\n\n        // create the FileTable\n        fileTable = new FileTable(mainFrame, this, conf);\n\n        locationChanger = new LocationChanger(mainFrame, this, locationManager);\n\n        // create the Tabs (Must be called after the fileTable was created and current folder was set)\n        tabs = new FileTableTabs(mainFrame, this, initialTabs);\n\n\t\t// Select the tab that was previously selected on last init\n\t\ttabs.selectTab(indexOfSelectedTab);\n\t\t\n\t\ttabs.addActiveTabListener(this);\n\n        // create folders tree on a JSplitPane \n        foldersTreePanel = new FoldersTreePanel(this);\n        foldersTreePanel.setVisible(false);\n        treeSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, foldersTreePanel.getPanel(), tabs);\n        treeSplitPane.setDividerSize(0);\n        treeSplitPane.setDividerLocation(0);\n        // Remove default border\n        treeSplitPane.setBorder(null);\n        panel.add(treeSplitPane, BorderLayout.CENTER);\n\n        // Disable Ctrl+Tab and Shift+Ctrl+Tab focus traversal keys\n        disableCtrlFocusTraversalKeys(locationTextField);\n        disableCtrlFocusTraversalKeys(foldersTreePanel.getTree());\n        disableCtrlFocusTraversalKeys(fileTable);\n        disableCtrlFocusTraversalKeys(tabs);\n        registerCycleThruFolderPanelAction(locationTextField);\n        registerCycleThruFolderPanelAction(foldersTreePanel.getTree());\n        // No need to register cycle actions for FileTable, they already are \n\n        // Listen to focus event in order to notify MainFrame of changes of the current active panel/table\n        fileTable.addFocusListener(this);\n        locationTextField.addFocusListener(this);\n        tabs.addFocusListener(this);\n\n        // Drag and Drop support\n\n        // Enable drag support on the FileTable\n        this.fileDragSourceListener = new FileDragSourceListener(this);\n        fileDragSourceListener.enableDrag(fileTable);\n\n        // Allow the location field to change the current directory when a file/folder is dropped on it\n        FileDropTargetListener dropTargetListener = new FileDropTargetListener(this, true);\n        locationTextField.setDropTarget(new DropTarget(locationTextField, dropTargetListener));\n        driveButton.setDropTarget(new DropTarget(driveButton, dropTargetListener));\n    }\n\n\n    /**\n     * Removes the Control+Tab and Shift+Control+Tab focus traversal keys from the given component so that those\n     * shortcuts can be used for other purposes.\n     *\n     * @param component the component for which to remove the Control+Tab and Shift+Control+Tab focus traversal keys\n     */\n    private void disableCtrlFocusTraversalKeys(Component component) {\n        // Remove Ctrl+Tab from forward focus traversal keys\n        Set<AWTKeyStroke> keyStrokeSet = new HashSet<>();\n        keyStrokeSet.add(AWTKeyStroke.getAWTKeyStroke(KeyEvent.VK_TAB, 0));\n        component.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, keyStrokeSet);\n\n        // Remove Shift+Ctrl+Tab from backward focus traversal keys\n        keyStrokeSet = new HashSet<>();\n        keyStrokeSet.add(AWTKeyStroke.getAWTKeyStroke(KeyEvent.VK_TAB, java.awt.event.InputEvent.SHIFT_DOWN_MASK));\n        component.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, keyStrokeSet);\n    }\n\n    /**\n     * Registers the {@link FocusNextAction} and {@link FocusPreviousAction} actions onto the given component's\n     * input map.\n     *  \n     * @param component the component for which to register the cycle actions\n     */\n    private void registerCycleThruFolderPanelAction(JComponent component) {\n        ActionKeymap.registerActionAccelerators(\n                ActionManager.getActionInstance(FocusNextAction.Descriptor.ACTION_ID, mainFrame),\n                component,\n                JComponent.WHEN_FOCUSED);\n\n        ActionKeymap.registerActionAccelerators(\n                ActionManager.getActionInstance(FocusPreviousAction.Descriptor.ACTION_ID, mainFrame),\n                component,\n                JComponent.WHEN_FOCUSED);\n    }\n\n\n    /**\n     * Returns the visited folders history, wrapped in a FolderHistory object.\n     *\n     * @return the visited folders history, wrapped in a FolderHistory object\n     */\n    public LocalLocationHistory getFolderHistory() {\n        return getTabs().getCurrentTab().getLocationHistory();\n    }\n\n    /**\n     * Allows the user to easily change the current folder and type a new one: requests focus \n     * on the location field and selects the folder string.\n     */\n    public void changeCurrentLocation() {\n    \tlocationTextField.selectAll();\n    \tlocationTextField.requestFocus();\n    }\n\t\n    /**\n     * Returns the FolderChangeMonitor which monitors changes in the current folder and automatically refreshes it.\n     *\n     * @return the FolderChangeMonitor which monitors changes in the current folder and automatically refreshes it\n     */\n    public FolderChangeMonitor getFolderChangeMonitor() {\n        return locationManager.getFolderChangeMonitor();\n    }\n    \n    public void setProgressValue(int value) {\n        SwingUtilities.invokeLater(() -> locationTextField.setProgressValue(value));\n    }\n\n    public void tryChangeCurrentFolderInternal(FileURL folderURL, Runnable callback) {\n    \tlocationChanger.tryChangeCurrentFolderInternal(folderURL, callback);\n    }\n\n    public ChangeFolderThread tryChangeCurrentFolder(AbstractFile folder) {\n    \treturn locationChanger.tryChangeCurrentFolder(folder, false);\n    }\n\n    public ChangeFolderThread tryChangeCurrentFolder(FileURL folderURL, AbstractFile selectThisFileAfter, boolean findWorkableFolder) {\n    \treturn locationChanger.tryChangeCurrentFolder(FileFactory.getFile(folderURL), selectThisFileAfter, findWorkableFolder, false);\n    }\n\n    public ChangeFolderThread tryChangeCurrentFolder(AbstractFile folder, AbstractFile selectThisFileAfter, boolean findWorkableFolder) {\n    \treturn locationChanger.tryChangeCurrentFolder(folder, selectThisFileAfter, findWorkableFolder, false);\n    }\n\n    public ChangeFolderThread tryChangeCurrentFolder(String folderPath) {\n    \treturn locationChanger.tryChangeCurrentFolder(folderPath);\n    }\n\n    public ChangeFolderThread tryChangeCurrentFolder(FileURL folderURL) {\n    \treturn locationChanger.tryChangeCurrentFolder(folderURL);\n    }\n\n    public ChangeFolderThread tryChangeCurrentFolder(FileURL folderURL, CredentialsMapping credentialsMapping) {\n    \treturn locationChanger.tryChangeCurrentFolder(folderURL, credentialsMapping, false);\n    }\n\n    public ChangeFolderThread tryRefreshCurrentFolder() {\n    \treturn locationChanger.tryRefreshCurrentFolder();\n    }\n\n    public ChangeFolderThread tryRefreshCurrentFolder(AbstractFile selectThisFileAfter) {\n        return locationChanger.tryRefreshCurrentFolder(selectThisFileAfter);\n    }\n\n    public ChangeFolderThread getChangeFolderThread() {\n        return locationChanger.getChangeFolderThread();\n    }\n\n    public long getLastFolderChangeTime() {\n        return locationChanger.getLastFolderChangeTime();\n    }\n\n    /**\n     * Returns the folder that is currently being displayed by this panel.\n     *\n     * @return the folder that is currently being displayed by this panel\n     */\n    public AbstractFile getCurrentFolder() {\n        return locationManager.getCurrentFolder();\n    }\n\n    /**\n     * This method updates the UI with the given folder \n     * If the currently selected tab is locked and the given flag \"changeLockedTab\" is off, \n     * then a new tab is opened with the given folder,\n     * otherwise, the currently presented folder is replaces with the given folder\n     * \n     * @param folder - the folder to be set\n     * @param children - the children of the given folder\n     * @param fileToSelect - the file that would be selected after changing the folder\n     * @param changeLockedTab - flag that indicates whether to change the presented folder in \n     * the currently selected tab although it's locked (used when switching tabs)\n     */\n    public void setCurrentFolder(AbstractFile folder, AbstractFile[] children, AbstractFile fileToSelect, boolean changeLockedTab) {\n        // Change the current folder in the table and select the given file if not null\n        if (fileToSelect == null) {\n            fileTable.setCurrentFolder(folder, children);\n        } else {\n            fileTable.setCurrentFolder(folder, children, fileToSelect);\n        }\n    }\n\n    /**\n     * Shows the popup which is located the given index in fileTablePopups.\n     * \n     * @param index - index of the FileTablePopup in fileTablePopups.\n     */\n    public void showQuickList(int index) {\n        if (fileTablePopups == null) {\n            // Initialize quick lists\n            fileTablePopups = new QuickList[] {\n                    new ParentFoldersQL(this),\n                    new RecentLocationsQL(this),\n                    new RecentExecutedFilesQL(this),\n                    new BookmarksQL(this),\n                    new RootFoldersQL(this),\n                    new TabsQL(this),\n                    new RecentViewedQL(this),\n                    new RecentEditedQL(this),\n                    new EditorBookmarksQL(this)\n            };\n        }\n    \tfileTablePopups[index].show();\n    }\n\n    /**\n     * Returns width of a folders tree.\n     * @return a width of a folders tree\n     */\n    public int getTreeWidth() {\n        return treeVisible ? treeSplitPane.getDividerLocation() : oldTreeWidth;\n    }\n\n    /**\n     * Sets a width of a folders tree.\n     * @param width new width\n     */\n    void setTreeWidth(int width) {\n        if (!treeVisible) {\n            oldTreeWidth = width;\n        } else {\n        \ttreeSplitPane.setDividerLocation(width);\n        \ttreeSplitPane.doLayout();\n        }\n    }\n\n    /**\n     * Enables/disables a directory tree visibility. Invoked by {@link com.mucommander.ui.action.impl.ToggleTreeAction}.\n     */\n    public void setTreeVisible(boolean treeVisible) {\n    \tif (this.treeVisible != treeVisible) {\n\t        this.treeVisible = treeVisible;\n\t        if (!treeVisible) {\n\t            // save width of a tree panel\n\t            oldTreeWidth = treeSplitPane.getDividerLocation();\n\t        }\n\t        foldersTreePanel.setVisible(treeVisible);\n\t        // hide completely divider if a tree isn't visible\n\t        treeSplitPane.setDividerLocation(treeVisible ? oldTreeWidth : 0);\n\t        treeSplitPane.setDividerSize(treeVisible ? 5 : 0);\n\t        foldersTreePanel.requestFocus();\n    \t}\n    }\n\n    \n    @Override\n    public String toString() {\n        return getClass().getName()+\"@\"+hashCode() +\" currentFolder=\"+getCurrentFolder()+\" hasFocus=\"+panel.hasFocus();\n    }\n\n\n    @Override\n    public void focusGained(FocusEvent e) {\n        // Notify MainFrame that we are in control now! (our table/location field is active)\n        mainFrame.setActiveTable(fileTable);\n    }\n\n    @Override\n    public void focusLost(FocusEvent e) {\n        if (!TcConfigurations.getPreferences().getVariable(TcPreference.SHOW_QUICK_SEARCH_MATCHES_FIRST, TcPreferences.DEFAULT_SHOW_QUICK_SEARCH_MATCHES_FIRST)) {\n            fileTable.getQuickSearch().stop();\n        }\n    }\n\n\n    @Override\n    public Point calcQuickListPosition(Dimension dim) {\n    \treturn new Point(\n    \t\t\tMath.max((getWidth() - (int)dim.getWidth()) / 2, 0),\n    \t\t\tgetLocationTextField().getHeight() + Math.max((panel.getHeight() - (int)dim.getHeight()) / 3, 0)\n    \t\t\t);\n\t}\n\n    @Override\n\tpublic Component containerComponent() {\n\t\treturn panel;\n\t}\n\n    @Override\n\tpublic Component nextFocusableComponent() {\n\t\treturn fileTable;\n\t}\n\n\n    @Override\n\tpublic void activeTabChanged() {\n\t\tboolean isCurrentTabLocked = tabs.getCurrentTab().isLocked();\n\t\t\n\t\tlocationTextField.setEnabled(!isCurrentTabLocked);\n\t\tdriveButton.setEnabled(!isCurrentTabLocked);\n\t}\n\n\n    public void setPreviewMode(boolean previewMode) {\n        this.previewMode = previewMode;\n        if (previewMode) {\n            panel.remove(treeSplitPane);\n            locationPanel.setVisible(false);\n            if (previewPanel == null) {\n                previewPanel = new PreviewPanel();\n            }\n            panel.add(previewPanel, BorderLayout.CENTER);\n            mainFrame.getActivePanel().getFileTable().addTableSelectionListener(previewTableSelectionListener);\n            previewPanel.loadFile(mainFrame.getActiveTable().getSelectedFile());\n        } else {\n            panel.remove(previewPanel);\n            panel.add(treeSplitPane, BorderLayout.CENTER);\n            locationPanel.setVisible(true);\n            mainFrame.getActivePanel().getFileTable().removeTableSelectionListener(previewTableSelectionListener);\n        }\n        panel.doLayout();\n        panel.repaint();\n    }\n\n    @Override\n    public int getWidth() {\n        return panel.getWidth();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/LocationBar.kt",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/trolcommander\n * Copyright (C) 2014-2026 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify it under the terms of the GNU General\n * Public License as published by the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied\n * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License* along with this program.\n * If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.main\n\nimport java.awt.*\nimport java.awt.event.*\nimport javax.swing.JPanel\nimport javax.swing.SwingUtilities\nimport javax.swing.Timer\n\n/**\n * A wrapper panel that sits in place of the location text field in each FolderPanel.\n * It hosts two cards in a [CardLayout]:\n *  - The regular [LocationTextField] (always visible by default).\n *  - A [BreadcrumbBar] that renders the current path as a row of clickable hyperlink-style labels, one per directory\n *  segment, separated by `›` glyphs.\n *\n * When the Ctrl key (or Meta on macOS) is held for {@value #BREADCRUMB_SHOW_DELAY_MS}ms *and* the location text field\n * does not have keyboard focus (i.e. the user is not actively editing the path), the breadcrumb card is shown.\n * The delayed appearance prevents the breadcrumb from flickering during quick keyboard shortcuts (Ctrl+C, Ctrl+V, etc.).\n *\n * To reduce visual noise, the breadcrumb is only shown for the panel where the mouse is currently hovering.\n * If the mouse is not over either folder panel, breadcrumbs are shownin both panels. The breadcrumb dynamically follows\n * the mouse - if the user moves the mouse from one panel to another while still holding Ctrl, the breadcrumb switches\n * accordingly.\n * The breadcrumb is immediately hidden when Ctrl is released, or if any other key is pressedor the mouse is clicked\n * before the delay expires.\n */\nclass LocationBar(private val folderPanel: FolderPanel, private val locationTextField: LocationTextField) : JPanel() {\n    private val breadcrumbBar: BreadcrumbBar\n    private val cardLayout: CardLayout = CardLayout()\n\n    /** Timer that delays showing the breadcrumb to avoid noise from quick keyboard shortcuts  */\n    private val showBreadcrumbTimer: Timer\n\n    /** Tracks whether Ctrl/Meta is currently being held down  */\n    private var modifierHeld = false\n\n    init {\n        setLayout(cardLayout)\n        setOpaque(false)\n\n        breadcrumbBar = BreadcrumbBar(folderPanel)\n\n        add(locationTextField, CARD_TEXT_FIELD)\n        add(breadcrumbBar, CARD_BREADCRUMB)\n\n        // On macOS the menu shortcut key is Cmd (META); on all other platforms it is Ctrl.\n        // We want Ctrl to always work, and Cmd to also work on macOS.\n        val menuShortcutMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()\n        val isMac = menuShortcutMask == InputEvent.META_DOWN_MASK\n\n        // Initialize the timer that delays showing the breadcrumb\n        showBreadcrumbTimer = Timer(BREADCRUMB_SHOW_DELAY_MS) { _: ActionEvent? ->\n            // Only show breadcrumb if:\n            // 1. Text field is currently visible (not already showing breadcrumb)\n            // 2. Mouse is over this panel OR mouse is over neither panel\n            if (locationTextField.isShowing() && shouldShowBreadcrumbForMousePosition()) {\n                breadcrumbBar.setFile(folderPanel.currentFolder)\n                cardLayout.show(this@LocationBar, CARD_BREADCRUMB)\n            }\n        }\n        showBreadcrumbTimer.isRepeats = false\n\n        // Listen for mouse events globally\n        Toolkit.getDefaultToolkit().addAWTEventListener({ event: AWTEvent? ->\n            if (event is MouseEvent) {\n                when (event.getID()) {\n                    MouseEvent.MOUSE_PRESSED -> cancelBreadcrumbTimer()\n                    MouseEvent.MOUSE_MOVED -> if (modifierHeld) {\n                        updateBreadcrumbVisibility()\n                    }\n                }\n            }\n        }, AWTEvent.MOUSE_EVENT_MASK or AWTEvent.MOUSE_MOTION_EVENT_MASK)\n\n        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher { e: KeyEvent? ->\n            val isCtrl = e!!.getKeyCode() == KeyEvent.VK_CONTROL\n            val isMeta = isMac && e.getKeyCode() == KeyEvent.VK_META\n\n            if (e.getID() == KeyEvent.KEY_PRESSED) {\n                if (isCtrl || isMeta) {\n                    // Ctrl/Meta pressed - start the timer to show breadcrumb after delay\n                    if (!locationTextField.hasFocus() && !showBreadcrumbTimer.isRunning) {\n                        modifierHeld = true\n                        showBreadcrumbTimer.restart()\n                    }\n                } else {\n                    // Any other key pressed - cancel the timer to prevent breadcrumb from showing\n                    // This filters out keyboard shortcuts like Ctrl+C, Ctrl+V, etc.\n                    cancelBreadcrumbTimer()\n                }\n            } else if (e.getID() == KeyEvent.KEY_RELEASED) {\n                if (isCtrl || isMeta) {\n                    // Ctrl/Meta released - cancel timer and hide breadcrumb if showing\n                    cancelBreadcrumbTimer()\n\n                    // Hide breadcrumb only when neither trigger modifier remains held\n                    val ctrlStillHeld = (e.modifiersEx and InputEvent.CTRL_DOWN_MASK) != 0\n                    val metaStillHeld = isMac && (e.modifiersEx and InputEvent.META_DOWN_MASK) != 0\n                    if (!ctrlStillHeld && !metaStillHeld) {\n                        modifierHeld = false\n                        SwingUtilities.invokeLater {\n                            cardLayout.show(this@LocationBar, CARD_TEXT_FIELD)\n                        }\n                    }\n                }\n            }\n            false // never consume — other Ctrl shortcuts must keep working\n        }\n    }\n\n    /**\n     * Cancels the breadcrumb show timer if it's running.\n     */\n    private fun cancelBreadcrumbTimer() {\n        if (showBreadcrumbTimer.isRunning) {\n            showBreadcrumbTimer.stop()\n        }\n    }\n\n    /**\n     * Updates the breadcrumb visibility based on current mouse position.\n     * Shows breadcrumb if mouse is over this panel or neither panel.\n     * Hides breadcrumb if mouse is over the other panel.\n     * Called when mouse moves while Ctrl/Meta is held.\n     */\n    private fun updateBreadcrumbVisibility() {\n        SwingUtilities.invokeLater {\n            val shouldShow = shouldShowBreadcrumbForMousePosition()\n            val isShowingBreadcrumb = breadcrumbBar.isShowing()\n            if (shouldShow && !isShowingBreadcrumb) {\n                // Mouse moved to this panel or neutral area - show breadcrumb\n                breadcrumbBar.setFile(folderPanel.currentFolder)\n                cardLayout.show(this@LocationBar, CARD_BREADCRUMB)\n            } else if (!shouldShow && isShowingBreadcrumb) {\n                // Mouse moved to other panel - hide breadcrumb\n                cardLayout.show(this@LocationBar, CARD_TEXT_FIELD)\n            }\n        }\n    }\n\n    /**\n     * Determines whether the breadcrumb should be shown based on the current mouse position.\n     * Returns true if:\n     * - Mouse is over this panel, OR\n     * - Mouse is over neither panel (show in both)\n     * Returns false if:\n     * - Mouse is over the other panel (don't show noise in the non-hovered panel)\n     */\n    private fun shouldShowBreadcrumbForMousePosition(): Boolean {\n        val pointerInfo = MouseInfo.getPointerInfo() ?: return true\n        val mouseLocation = pointerInfo.location\n\n        // Check if mouse is over this panel\n        val thisPanel = folderPanel.panel\n        if (isMouseOverComponent(thisPanel, mouseLocation)) {\n            return true\n        }\n\n        // Check if mouse is over the other panel\n        val mainFrame = folderPanel.getMainFrame()\n        val leftPanel = mainFrame.leftPanel\n        val rightPanel = mainFrame.rightPanel\n        val otherPanel = if (folderPanel === leftPanel) rightPanel else leftPanel\n\n        if (otherPanel != null) {\n            val otherPanelComponent = otherPanel.panel\n            if (isMouseOverComponent(otherPanelComponent, mouseLocation)) {\n                return false // Mouse is over the other panel, don't show breadcrumb here\n            }\n        }\n\n        return true // Mouse is over neither panel, show breadcrumb in both\n    }\n\n    /**\n     * Checks if the mouse at the given screen location is over the specified component.\n     */\n    private fun isMouseOverComponent(component: Component?, screenLocation: Point): Boolean {\n        if (component == null || !component.isShowing()) {\n            return false\n        }\n\n        val componentLocation = Point(screenLocation)\n        SwingUtilities.convertPointFromScreen(componentLocation, component)\n        return component.contains(componentLocation)\n    }\n\n    /**\n     * Always report the text field's preferred size so that switching cards does\n     * not cause the location bar row to grow or shrink.\n     */\n    override fun getPreferredSize(): Dimension? {\n        return locationTextField.getPreferredSize()\n    }\n\n    override fun getMinimumSize(): Dimension? {\n        return locationTextField.getMinimumSize()\n    }\n\n    companion object {\n        private const val CARD_TEXT_FIELD = \"textField\"\n        private const val CARD_BREADCRUMB = \"breadcrumb\"\n\n        /** Delay in milliseconds before showing breadcrumb when Ctrl/Meta is held  */\n        private const val BREADCRUMB_SHOW_DELAY_MS = 50\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/LocationTextField.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main;\r\n\r\nimport java.awt.event.FocusEvent;\r\nimport java.awt.event.FocusListener;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.regex.Matcher;\r\nimport java.util.regex.Pattern;\r\n\r\nimport com.mucommander.bookmark.Bookmark;\r\nimport com.mucommander.bookmark.BookmarkManager;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileProtocols;\r\nimport com.mucommander.commons.file.FileURL;\r\nimport com.mucommander.commons.file.impl.local.LocalFile;\r\nimport com.mucommander.commons.file.impl.local.UNCFile;\r\nimport com.mucommander.commons.file.util.PathUtils;\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.ui.autocomplete.AutocompleterTextComponent;\r\nimport com.mucommander.ui.autocomplete.CompleterFactory;\r\nimport com.mucommander.ui.autocomplete.TextFieldCompletion;\r\nimport com.mucommander.ui.event.LocationEvent;\r\nimport com.mucommander.ui.event.LocationListener;\r\nimport com.mucommander.ui.progress.ProgressTextField;\r\nimport com.mucommander.ui.theme.ColorChangedEvent;\r\nimport com.mucommander.ui.theme.FontChangedEvent;\r\nimport com.mucommander.ui.theme.Theme;\r\nimport com.mucommander.ui.theme.ThemeListener;\r\nimport com.mucommander.ui.theme.ThemeManager;\r\n\r\n/**\r\n * A TextField which is located on each panel and used to display the location presented in the panel's file-table,\r\n * and for letting the user change this location.\r\n * \r\n * This TextField support:\r\n * - auto-completion\r\n * - location changing progress indicator\r\n * - Theme settings\r\n * \r\n * @author Maxence Bernard, Arik Hadas\r\n */\r\n\r\npublic class LocationTextField extends ProgressTextField implements LocationListener, FocusListener, ThemeListener {\r\n\t/** FolderPanel this text field is displayed in */\r\n    private final FolderPanel folderPanel;\r\n\r\n    /** True while a folder is being changed after a path was entered in the location field and validated by the user */\r\n    private boolean folderChangeInitiatedByLocationField;\r\n\r\n    /** Used to save the path that was entered by the user after validation of the location textfield */\r\n    private String locationFieldTextSave;\r\n\r\n    /** For windows path, regex that finds trailing space characters at the end of a path */\r\n    private static final Pattern windowsTrailingSpacePattern;\r\n\r\n    static {\r\n        if (OsFamily.WINDOWS.isCurrent()) {\r\n            windowsTrailingSpacePattern = Pattern.compile(\"[ ]+[\\\\\\\\]*$\");\r\n        } else {\r\n            windowsTrailingSpacePattern = null;\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Creates a new LocationTextField for use in the given FolderPanel.\r\n     *\r\n     * @param folderPanel FolderPanel this text field is displayed in\r\n     */\r\n    LocationTextField(FolderPanel folderPanel) {\r\n        // Use a custom text field that can display loading progress when changing folders\r\n        super(0, ThemeManager.getCurrentColor(Theme.LOCATION_BAR_PROGRESS_COLOR));\r\n\r\n        this.folderPanel = folderPanel;\r\n\r\n    \t// Applies theme values.\r\n        setFont(ThemeManager.getCurrentFont(Theme.LOCATION_BAR_FONT));\r\n        setDisabledTextColor(ThemeManager.getCurrentColor(Theme.LOCATION_BAR_FOREGROUND_COLOR));\r\n        setForeground(ThemeManager.getCurrentColor(Theme.LOCATION_BAR_FOREGROUND_COLOR));\r\n        setBackground(ThemeManager.getCurrentColor(Theme.LOCATION_BAR_BACKGROUND_COLOR));\r\n        setSelectedTextColor(ThemeManager.getCurrentColor(Theme.LOCATION_BAR_SELECTED_FOREGROUND_COLOR));\r\n        setSelectionColor(ThemeManager.getCurrentColor(Theme.LOCATION_BAR_SELECTED_BACKGROUND_COLOR));\r\n\r\n        // Listen to location changes to update popup menu choices and disable this component while the location is\r\n        // being changed\r\n        folderPanel.getLocationManager().addLocationListener(this);\r\n\r\n        // Listen to focus events to temporarily disable the MainFrame's JMenuBar when this component has the keyboard focus.\r\n        // Not doing so would trigger unwanted menu bar actions when typing.\r\n        addFocusListener(this);\r\n        \r\n        enableAutoCompletion();\r\n        \r\n        ThemeManager.addCurrentThemeListener(this);\r\n    }\r\n    \r\n    /**\r\n     * Adds auto-completion capabilities to this text field.\r\n     */\r\n    private void enableAutoCompletion() {\r\n    \tnew TextFieldCompletion(new AutocompleterTextComponent(this) {\r\n\r\n\t\t\t@Override\r\n            public void OnEnterPressed(KeyEvent keyEvent) {\r\n\t\t\t\tif (textFieldValidated()) // if a malformed url was entered.\r\n\t\t\t\t\tfolderChangeCompleted(false);\t\t        \r\n\t\t\t\t\r\n\t\t\t\t// /!\\ Consume the event so to prevent JTextField from firing an ActionEvent\r\n\t\t\t\tkeyEvent.consume();\r\n\t\t\t}\r\n\r\n\t\t\t@Override\r\n            public void OnEscPressed(KeyEvent keyEvent) {\r\n\t\t\t\ttextFieldCancelled();\r\n\t\t\t}\r\n        }, CompleterFactory.getLocationCompleter());\r\n    }\r\n\r\n    /**\r\n     * Re-enable this text field after a folder change was completed, cancelled by the user or has failed.\r\n     *\r\n     * <p>If the folder change was the result of the user manually entering a path in the location field and the folder\r\n     * change failed or was cancelled, keeps the path intact and request focus on the text field so the user can modify it.\r\n     */\r\n    private void folderChangeCompleted(boolean folderChangedSuccessfully) {\r\n        if (folderChangedSuccessfully || !folderChangeInitiatedByLocationField) {\r\n            // Set the location field's contents to the new current folder's path\r\n            setText(folderPanel.getCurrentFolder().getAbsolutePath());\r\n        }\r\n\r\n        // Re-enable this text field\r\n        setEnabled(true);\r\n\r\n        // If the location was entered and validated in the location field and the folder change failed or was cancelled...\r\n        if (!folderChangedSuccessfully && folderChangeInitiatedByLocationField) {\r\n            // Restore the text that was entered by the user\r\n            setText(locationFieldTextSave);\r\n            // Select the text to grab user's attention and make it easier to modify\r\n            selectAll();\r\n            // Request focus (focus was on FileTable)\r\n            requestFocus();\r\n        }\r\n\r\n        // Reset field for next folder change\r\n        folderChangeInitiatedByLocationField = false;\r\n    }\r\n\r\n    @Override\r\n    public void locationChanging(LocationEvent e) {\r\n        // Change the location field's text to the folder being changed, only if the folder change was not initiated\r\n        // by the location field (to preserve the path entered by the user while the folder is being changed) \r\n        if (!folderChangeInitiatedByLocationField) {\r\n            FileURL folderURL = e.getFolderURL();\r\n\r\n            String locationText;\r\n            if (folderURL.getScheme().equals(FileProtocols.FILE)) {\r\n                locationText = urlToFileLocationString(folderURL);\r\n            }\r\n            // Display the full URL for protocols other than 'file'\r\n            else {\r\n                locationText = folderURL.toString(false);\r\n            }\r\n            setText(locationText);\r\n        }\r\n\r\n        // Disable component until the folder has been changed, canceled or failed.\r\n        // Note: if the focus currently is in the location field, the focus manager will release focus and give it\r\n        // to the next component (i.e. FileTable)\r\n        setEnabled(false);\r\n    }\r\n\r\n    private String urlToFileLocationString(FileURL folderURL) {\r\n        String locationText;\r\n        if (FileURL.LOCALHOST.equals(folderURL.getHost())) {\r\n            // Do not display the URL's scheme & host for local files\r\n            locationText = folderURL.getPath();\r\n            // Under for OSes with 'root drives' (Windows, OS/2), remove the leading '/' character\r\n            if (LocalFile.hasRootDrives()) {\r\n                locationText = PathUtils.removeLeadingSeparator(locationText, \"/\");\r\n            }\r\n        } else {\r\n            // For network files with FILE scheme display the URL in UNC format\r\n            locationText = \"\\\\\\\\\" + folderURL.getHost() + folderURL.getPath().replace('/', '\\\\');\r\n            if (!locationText.endsWith(UNCFile.SEPARATOR)) {\r\n                locationText += UNCFile.SEPARATOR;\r\n            }\r\n        }\r\n        return locationText;\r\n    }\r\n\r\n    public void locationChanged(LocationEvent e) {\r\n        // Re-enable component and change the location field's text to the new current folder's path\r\n        folderChangeCompleted(true);\r\n    }\r\n\r\n    public void locationCancelled(LocationEvent e) {\r\n        // Re-enable component and change the location field's text to the new current folder's path.\r\n        // If the path was entered in the location field, keep the path to give the user a chance to correct it.\r\n        folderChangeCompleted(false);\r\n    }\r\n\r\n    public void locationFailed(LocationEvent e) {\r\n        // Re-enable component and change the location field's text to the new current folder's path.\r\n        // If the path was entered in the location field, keep the path to give the user a chance to correct it.\r\n        folderChangeCompleted(false);\r\n    }\r\n\r\n    /**\r\n     * \r\n     * @return true if a malformed url was entered, false otherwise.\r\n     */\r\n    private boolean textFieldValidated() {\r\n        String location = getText().trim();\r\n\r\n        // Under Windows, trim the entered path for the following reason.\r\n        // If a file 'A' (e.g. \"C:\\temp\") exists and 'A ' (e.g. \"C:\\temp \") is requested, the java.io.File will resolve\r\n        // (file.exists() will  return true), but this file will be a strange one, listing bogus children files with\r\n        // weird attributes (in the case of a directory).\r\n        // Windows (or java.io.File under Windows) is somehow space-tolerant but then unable to deal with\r\n        // those files properly. So if a path ends with space characters, we remove them to prevent those weirdnesses.\r\n        // Note that Win32 doesn't allow creating files with trailing spaces (in Explorer, command prompt...), but\r\n        // those files can still be manually crafted and thus exist on one's hard drive.\r\n        // Mucommander should in theory be able to access such files without any problem but this hasn't been tested.\r\n        if (OsFamily.WINDOWS.isCurrent() && location.indexOf(\":\\\\\") == 1) {\r\n            // Looks for trailing spaces and if some \r\n            Matcher matcher = windowsTrailingSpacePattern.matcher(location);\r\n            if (matcher.find()) {\r\n                location = location.substring(0, matcher.start());\r\n            }\r\n        }\r\n                \r\n        // Save the path that was entered in case the location change fails or is cancelled \r\n        locationFieldTextSave = location;\r\n        \r\n        // Indicate we search for location corresponding to the given string. \r\n        // it will be false we'll find one.\r\n        boolean tryToInterpretEnteredString = true;\r\n\r\n        // Look for a bookmark which name is the entered string (case insensitive)\r\n        Bookmark b = BookmarkManager.getBookmark(location);\r\n        if (b != null) {\r\n        \t// Change the current folder to the bookmark's location\r\n        \tsetText(location = b.getLocation());\r\n        \ttryToInterpretEnteredString = false;\r\n        }\r\n\r\n        // Look for a volume whose name is the entered string (case insensitive)\r\n        AbstractFile[] volumes = LocalFile.getVolumes();\r\n        for (int i = 0; tryToInterpretEnteredString && i < volumes.length; i++) {\r\n            if (volumes[i].getName().equalsIgnoreCase(location)) {\r\n                // Change the current folder to the volume folder\r\n            \tsetText(location = volumes[i].getAbsolutePath());\r\n            \ttryToInterpretEnteredString = false;\r\n            }\r\n        }\r\n\r\n        // Todo: replace this by env:// filesystem ?\r\n        // Look for a system variable which name is the entered string (case insensitive)\r\n        if (tryToInterpretEnteredString && location.startsWith(\"$\")) {\r\n        \tString variableKey = location.substring(1);\r\n        \tString variableValue = System.getenv(variableKey);\r\n        \tif (variableValue != null) {\r\n                setText(location = variableValue);\r\n            }\r\n        }        \r\n        \r\n        // Remember that the folder change was initiated by the location field\r\n        folderChangeInitiatedByLocationField = true;\r\n        \r\n        // Change folder\r\n        return folderPanel.tryChangeCurrentFolder(location) == null;\r\n    }\r\n\r\n    private void textFieldCancelled() {\r\n        AbstractFile currentFolder = folderPanel.getCurrentFolder();\r\n        setText(currentFolder != null ? currentFolder.getAbsolutePath() : \"\");\r\n        transferFocus();\r\n    }\r\n\r\n\r\n    @Override\r\n    public void focusGained(FocusEvent e) {\r\n        // Disable menu bar when this component has gained focus\r\n        folderPanel.getMainFrame().getJFrame().getJMenuBar().setEnabled(false);\r\n    }\r\n\r\n    @Override\r\n    public void focusLost(FocusEvent e) {\r\n//    \t// If we are not in the middle of a folder change, and focus has been\r\n//    \t// lost then ensure location field's text is set to the current directory.\r\n//    \tif (!folderPanel.isFolderChanging())\r\n//    \t\tlocationField.setText(folderPanel.getCurrentFolder().getAbsolutePath());\r\n    \t\r\n        // Enable menu bar when this component has lost focus\r\n        folderPanel.getMainFrame().getJFrame().getJMenuBar().setEnabled(true);\r\n    }\r\n\r\n\r\n\r\n    // - Theme listening -------------------------------------------------------------\r\n    // -------------------------------------------------------------------------------\r\n    /**\r\n     * Receives theme color changes notifications.\r\n     */\r\n    public void colorChanged(ColorChangedEvent event) {\r\n        switch (event.getColorId()) {\r\n            case Theme.LOCATION_BAR_PROGRESS_COLOR:\r\n                setProgressColor(event.getColor());\r\n                break;\r\n\r\n            case Theme.LOCATION_BAR_FOREGROUND_COLOR:\r\n                setDisabledTextColor(event.getColor());\r\n                setForeground(event.getColor());\r\n                break;\r\n\r\n            case Theme.LOCATION_BAR_BACKGROUND_COLOR:\r\n                setBackground(event.getColor());\r\n                break;\r\n\r\n            case Theme.LOCATION_BAR_SELECTED_FOREGROUND_COLOR:\r\n                setSelectedTextColor(event.getColor());\r\n                break;\r\n\r\n            case Theme.LOCATION_BAR_SELECTED_BACKGROUND_COLOR:\r\n                setSelectionColor(event.getColor());\r\n                break;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Receives theme font changes notifications.\r\n     */\r\n    public void fontChanged(FontChangedEvent event) {\r\n        if (event.getFontId() == Theme.LOCATION_BAR_FONT) {\r\n            setFont(event.getFont());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/MainFrame.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main;\n\nimport com.apple.eawt.FullScreenUtilities;\nimport com.mucommander.commons.file.AbstractArchiveEntryFile;\nimport com.mucommander.commons.file.AbstractArchiveFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileProtocols;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.commons.runtime.OsVersion;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.conf.TcSnapshot;\nimport com.mucommander.desktop.DesktopManager;\nimport com.mucommander.ui.PreloadedJFrame;\nimport com.mucommander.ui.action.ActionKeymap;\nimport com.mucommander.ui.action.ActionManager;\nimport com.mucommander.ui.action.impl.CloseWindowAction;\nimport com.mucommander.ui.button.ToolbarMoreButton;\nimport com.mucommander.ui.event.ActivePanelListener;\nimport com.mucommander.ui.event.LocationEvent;\nimport com.mucommander.ui.event.LocationListener;\nimport com.mucommander.ui.icon.IconManager;\nimport com.mucommander.ui.layout.ProportionalSplitPane;\nimport com.mucommander.ui.layout.YBoxPanel;\nimport com.mucommander.ui.main.commandbar.CommandBar;\nimport com.mucommander.ui.main.menu.MainMenuBar;\nimport com.mucommander.ui.main.statusbar.StatusBar;\nimport com.mucommander.ui.main.table.Column;\nimport com.mucommander.ui.main.table.FileTable;\nimport com.mucommander.ui.main.table.views.full.FileTableConfiguration;\nimport com.mucommander.ui.main.table.SortInfo;\nimport com.mucommander.ui.main.tabs.ConfFileTableTab;\nimport com.mucommander.ui.main.toolbar.ToolBar;\nimport com.mucommander.ui.terminal.TcTerminal;\nimport lombok.Getter;\n\nimport javax.swing.*;\nimport javax.swing.table.TableColumnModel;\nimport java.awt.*;\nimport java.awt.event.WindowAdapter;\nimport java.awt.event.WindowEvent;\nimport java.util.*;\nimport java.util.List;\n\n/**\n * This is the main frame, which contains all other UI components visible on a mucommander window.\n * \n * @author Maxence Bernard\n */\npublic class MainFrame implements LocationListener {\n    private final JFrame frameInstance;\n    /**\n     * -- GETTER --\n     *  Returns the ProportionalSplitPane component that splits the two panels.\n     */\n    @Getter\n    private ProportionalSplitPane splitPane;\n\n    private FolderPanel leftFolderPanel;\n    private FolderPanel rightFolderPanel;\n\t\n    private FileTable leftTable;\n    private FileTable rightTable;\n    \n    /** Active table in the MainFrame\n     * -- GETTER --\n     *  Returns the currently active table.\n     *  <p>The returned table doesn't necessarily have focus, the focus can be in some other component\n     *  of the active\n     * , or nowhere in the MainFrame if it is currently not in the foreground.\n     *  <p>Use\n     *  to test if the table currently has focus.\n     *\n     */\n    @Getter\n    private FileTable activeTable;\n\n    private TcTerminal tcTerminal;\n    private JSplitPane terminalSplitPane;\n\n    /** Toolbar panel */\n    private JPanel toolbarPanel;\n\n    /** Toolbar component */\n    private ToolBar toolbar;\n\n    /** Status bar instance\n     * -- GETTER --\n     *  Returns the status bar, where information about selected files and volume are displayed.\n     *  Note that a non-null instance of\n     *  is returned even if it is currently hidden.\n     */\n    @Getter\n    private StatusBar statusBar;\n\t\n    /** Command bar instance\n     * -- GETTER --\n     *  Returns the\n     * , i.e. the component that contains shortcuts to certains actions such as\n     *  View, Edit, Copy, Move, etc...\n     *  Note that a non-null instance of\n     *  is returned even if it is currently hidden.\n     */\n    @Getter\n    private CommandBar commandBar;\n\t\n    /** Is no events mode enabled ? */\n    private boolean noEventsMode;\n\n    /** Is this MainFrame active in the foreground ?\n     * -- GETTER --\n     *  Returns <code>true</code> if this MainFrame is currently active in the foreground.\n     */\n    @Getter\n    private boolean foregroundActive;\n\n    /** Is single panel view?\n     * -- GETTER --\n     *  Returns <code>true</code> if only one panel is show\n     */\n    @Getter\n    private boolean singlePanel;\n\n    /** Contains all registered ActivePanelListener instances, stored as weak references */\n    private final Map<ActivePanelListener, ?> activePanelListeners = Collections.synchronizedMap(new WeakHashMap<>());\n\n    private JPanel insetsPane;\n\n    public MainFrame(ConfFileTableTab leftTab, FileTableConfiguration leftTableConf,\n                     ConfFileTableTab rightTab, FileTableConfiguration rightTableConf) {\n        this(new ConfFileTableTab[] {leftTab}, 0, leftTableConf, new ConfFileTableTab[] {rightTab}, 0, rightTableConf);\n    }\n\n\n    /**\n     * Creates a new main frame set to the given initial folders.\n     *\n     * @param leftTabs left panel tabs configuration\n     * @param indexOfLeftSelectedTab index of left selected tab\n     * @param leftTableConf left table configuration\n     * @param rightTabs right panel tabs configuration\n     * @param indexOfRightSelectedTab index of right selected tab\n     * @param rightTableConf right table configuration\n     */\n    public MainFrame(ConfFileTableTab[] leftTabs, int indexOfLeftSelectedTab, FileTableConfiguration leftTableConf,\n                     ConfFileTableTab[] rightTabs, int indexOfRightSelectedTab, FileTableConfiguration rightTableConf) {\n        frameInstance = PreloadedJFrame.getJFrame(this);\n        FolderPanel leftPanel = new FolderPanel(this, leftTabs, indexOfLeftSelectedTab, leftTableConf);\n        FolderPanel rightPanel = new FolderPanel(this, rightTabs, indexOfRightSelectedTab, rightTableConf);\n        init(leftPanel, rightPanel);\n\n        for (boolean isLeft = true; ; isLeft = false) {\n            FileTable fileTable = isLeft ? leftTable : rightTable;\n            fileTable.sortBy(Column.valueOf(TcConfigurations.getSnapshot().getVariable(TcSnapshot.getFileTableSortByVariable(0, isLeft), TcSnapshot.DEFAULT_SORT_BY).toUpperCase()),\n                    !TcConfigurations.getSnapshot().getVariable(TcSnapshot.getFileTableSortOrderVariable(0, isLeft), TcSnapshot.DEFAULT_SORT_ORDER).equals(TcSnapshot.SORT_ORDER_DESCENDING));\n\n            FolderPanel folderPanel = isLeft ? leftFolderPanel : rightFolderPanel;\n            folderPanel.setTreeWidth(TcConfigurations.getSnapshot().getVariable(TcSnapshot.getTreeWidthVariable(0, isLeft), 150));\n            folderPanel.setTreeVisible(TcConfigurations.getSnapshot().getVariable(TcSnapshot.getTreeVisiblityVariable(0, isLeft), false));\n\n            if (!isLeft)\n                break;\n        }\n    }\n\n    /**\n     * Copy constructor\n     */\n    public MainFrame(MainFrame mainFrame) {\n        frameInstance = PreloadedJFrame.getJFrame(this);\n        FolderPanel leftFolderPanel = mainFrame.getLeftPanel();\n        FolderPanel rightFolderPanel = mainFrame.getRightPanel();\n        FileTable leftFileTable = leftFolderPanel.getFileTable();\n        FileTable rightFileTable = rightFolderPanel.getFileTable();\n\n        init(new FolderPanel(this,\n                        new ConfFileTableTab[] {\n                            new ConfFileTableTab(leftFolderPanel.getCurrentFolder().getURL())\n                        }, 0, leftFileTable.getConfiguration()),\n                        new FolderPanel(this, new ConfFileTableTab[] {\n                            new ConfFileTableTab(rightFolderPanel.getCurrentFolder().getURL())\n                        }, 0, rightFileTable.getConfiguration()));\n\n        // TODO: Sorting should be part of the FileTable configuration\n        this.leftTable.sortBy(leftFileTable.getSortInfo());\n        this.rightTable.sortBy(rightFileTable.getSortInfo());\n    }\n\n    private void init(FolderPanel leftFolderPanel, FolderPanel rightFolderPanel) {\n        // Set the window icon\n        setWindowIcon();\n\n        DesktopManager.customizeMainFrame(frameInstance);\n\n        //initLookAndFeel();\n\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            FullScreenUtilities.setWindowCanFullScreen(frameInstance, true);    // Lion Fullscreen support\n        }\n\n        // Enable window resize\n        frameInstance.setResizable(true);\n\n        // The toolbar should have no inset, this is why it is left out of the insetsPane\n        JPanel contentPane = new JPanel(new BorderLayout());\n        frameInstance.setContentPane(contentPane);\n\n        // Initializes the folder panels and file tables.\n        this.leftFolderPanel = leftFolderPanel;\n        this.rightFolderPanel = rightFolderPanel;\n        leftTable = leftFolderPanel.getFileTable();\n        rightTable = rightFolderPanel.getFileTable();\n        activeTable  = leftTable;\n\n        // create the toolbar and corresponding panel wrapping it, and show it only if it hasn't been disabled in the preferences.\n        // Note: Toolbar.setVisible() has to be called no matter if Toolbar is visible or not, in order for it to be properly initialized\n        this.toolbar = new ToolBar(this);\n        this.toolbarPanel = ToolbarMoreButton.wrapToolBar(toolbar);\n        this.toolbarPanel.setVisible(TcConfigurations.getPreferences().getVariable(TcPreference.TOOLBAR_VISIBLE, TcPreferences.DEFAULT_TOOLBAR_VISIBLE));\n        contentPane.add(toolbarPanel, BorderLayout.NORTH);\n\n        insetsPane = new JPanel(new BorderLayout()) {\n            // Add an x=3,y=3 gap around content pane\n            @Override\n            public Insets getInsets() {\n                return new Insets(0, 3, 3, 3);      // No top inset\n            }\n        };\n\n        // Below the toolbar there is the pane with insets\n        contentPane.add(insetsPane, BorderLayout.CENTER);\n\n        // Listen to location change events to display the current folder in the window's title\n        leftFolderPanel.getLocationManager().addLocationListener(this);\n        rightFolderPanel.getLocationManager().addLocationListener(this);\n\n        // create menu bar (has to be created after toolbar)\n        MainMenuBar menuBar = new MainMenuBar(this);\n        frameInstance.setJMenuBar(menuBar);\n\n        // create the split pane that separates folder panels and allows to resize how much space is allocated to the\n        // both of them. The split orientation is loaded from and saved to the preferences.\n        // Note: the vertical/horizontal terminology used in muCommander is just the opposite of the one used\n        // in JSplitPane which is anti-natural / confusing.\n        int splitOrientation = TcConfigurations.getSnapshot().getVariable(TcSnapshot.getSplitOrientation(0), TcSnapshot.DEFAULT_SPLIT_ORIENTATION).equals(TcSnapshot.VERTICAL_SPLIT_ORIENTATION) ?\n                JSplitPane.HORIZONTAL_SPLIT : JSplitPane.VERTICAL_SPLIT;\n        splitPane = new ProportionalSplitPane(frameInstance, splitOrientation,false,\n                MainFrame.this.leftFolderPanel.getPanel(),\n                MainFrame.this.rightFolderPanel.getPanel()) {\n        };\n        // Remove any default border the split pane has\n        splitPane.setBorder(null);\n\n        // Adds buttons that allow to collapse and expand the split pane in both directions\n        splitPane.setOneTouchExpandable(true);\n\n        // Disable all the JSPlitPane accessibility shortcuts that are registered by default, as some of them\n        // conflict with default trolCommander action shortcuts (e.g. F6 and F8)\n        splitPane.disableAccessibilityShortcuts();\n\n        // Split pane will be given any extra space\n        insetsPane.add(splitPane, BorderLayout.CENTER);\n\n        // Add a 2-pixel gap between the file table and status bar\n        YBoxPanel southPanel = new YBoxPanel();\n//        southPanel.addSpace(2);\n\n        // Add status bar\n        this.statusBar = new StatusBar(this);\n        southPanel.add(statusBar);\n\n        // Show command bar only if it hasn't been disabled in the preferences\n        this.commandBar = new CommandBar(this);\n        // Note: CommandBar.setVisible() has to be called no matter if CommandBar is visible or not, in order for it to be properly initialized\n        this.commandBar.setVisible(TcConfigurations.getPreferences().getVariable(TcPreference.COMMAND_BAR_VISIBLE, TcPreferences.DEFAULT_COMMAND_BAR_VISIBLE));\n        southPanel.add(commandBar);\n        insetsPane.add(southPanel, BorderLayout.SOUTH);\n\n        // Perform CloseAction when the user asked the window to close\n        frameInstance.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);\n        frameInstance.addWindowListener(new WindowAdapter() {\n            @Override\n            public void windowClosing(WindowEvent e) {\n                ActionManager.performAction(CloseWindowAction.Descriptor.ACTION_ID, MainFrame.this);\n            }\n        });\n\n        ActionKeymap.registerActions(this);\n\n        // Fire table change events on registered ActivePanelListener instances, to notify of the initial active table.\n        fireActivePanelChanged(activeTable.getFolderPanel());\n\n        // Set the custom FocusTraversalPolicy that manages focus for both FolderPanel and their subcomponents.\n        frameInstance.setFocusTraversalPolicy(new CustomFocusTraversalPolicy());\n    }\n\n\n\n    /**\n     * Sets the window icon, using the best method (Java 1.6's Window#setIconImages when available, Window#setIconImage\n     * otherwise) and icon resolution(s) (OS-dependent).\n     */\n    private void setWindowIcon() {\n        // TODO: this code should probably be moved to the desktop API\n\n        // - Mac OS X completely ignores calls to #setIconImage/setIconImages, no need to waste time\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            return;\n        }\n\n        // Use Java 1.6 's new Window#setIconImages(List<Image>) when available\n        List<Image> icons = new ArrayList<>();\n\n        // Start by adding a 16x16 image with 1-bit transparency, any OS should support that.\n        icons.add(IconManager.getIcon(IconManager.IconSet.TROLCOMMANDER, \"icon16_8.png\").getImage());\n\n        // - Windows XP messes up 8-bit PNG transparency.\n        // We would be better off with the .ico of the launch4j exe (which has 8-bit alpha transparency) but there\n        // seems to be no way to keep it when in 'dontWrapJar' mode (separate exe and jar files).\n        if (OsFamily.WINDOWS.isCurrent() && OsVersion.WINDOWS_XP.isCurrentOrLower()) {\n            icons.add(IconManager.getIcon(IconManager.IconSet.TROLCOMMANDER, \"icon48_8.png\").getImage());\n        }\n        // - Windows Vista supports 8-bit transparency and icon resolutions up to 256x256.\n        // - GNOME and KDE support 8-bit transparency.\n        else {\n            // Add PNG 24 images (8-bit transparency)\n            icons.add(IconManager.getIcon(IconManager.IconSet.TROLCOMMANDER, \"icon16_24.png\").getImage());\n            icons.add(IconManager.getIcon(IconManager.IconSet.TROLCOMMANDER, \"icon32_24.png\").getImage());\n            icons.add(IconManager.getIcon(IconManager.IconSet.TROLCOMMANDER, \"icon48_24.png\").getImage());\n            icons.add(IconManager.getIcon(IconManager.IconSet.TROLCOMMANDER, \"icon128_24.png\").getImage());\n            icons.add(IconManager.getIcon(IconManager.IconSet.TROLCOMMANDER, \"icon256_24.png\").getImage());\n        }\n\n        frameInstance.setIconImages(icons);\n    }\n\n\n\n    /**\n     * Registers the given ActivePanelListener to receive events when the active table changes.\n     *\n     * @param activePanelListener the ActivePanelListener to add\n     */\n    public void addActivePanelListener(ActivePanelListener activePanelListener) {\n        activePanelListeners.put(activePanelListener, null);\n    }\n\n    /**\n     * Unregisters the given ActivePanelListener so that it no longer receives events when the active table changes.\n     *\n     * @param activePanelListener the ActivePanelListener to remove\n     */\n    public void removeActivePanelListener(ActivePanelListener activePanelListener) {\n        activePanelListeners.remove(activePanelListener);\n    }\n\n    /**\n     * Fires table change events on all registered ActivePanelListener instances.\n     *\n     * @param folderPanel the new active panel\n     */\n    private void fireActivePanelChanged(FolderPanel folderPanel) {\n        for (ActivePanelListener listener : activePanelListeners.keySet()) {\n            listener.activePanelChanged(folderPanel);\n        }\n    }\n\n\n    /**\n     * Returns <code>true</code> if 'no events mode' is currently enabled.\n     *\n     * @return <code>true</code> if 'no events mode' is currently enabled\n     */\n    public boolean getNoEventsMode() {\n        return this.noEventsMode;\n    }\n\t\n    /**\n     * Enables/disables the 'no events mode' which prevents mouse and keyboard events from being received\n     * by the application (MainFrame, its subcomponents and the menu bar).\n     *\n     * @param enabled <code>true</code> to enable 'no events mode', <code>false</code> to disable it\n     */\n    public void setNoEventsMode(boolean enabled) {\n        // Piece of code used in 0.8 beta1 and removed after because it's way too slow, kept here for the record \n        //\t\t// Glass pane has empty mouse and key adapters (created in the constructor)\n        //\t\t// which will catch all mouse and keyboard events \n        //\t\tgetGlassPane().setVisible(enabled);\n        //\t\tgetJMenuBar().setEnabled(!enabled);\n        //\t\t// Remove focus from whatever component in FolderPanel which had focus\n        //\t\tgetGlassPane().requestFocus();\n\n        this.noEventsMode = enabled;\n    }\n\n\n    /**\n     * Returns the {@link ToolBar} where shortcut buttons (go back, go forward, ...) are.\n     * Note that a non-null instance of {@link ToolBar} is returned even if it is currently hidden.\n     *\n     * @return the toolbar component\n     */\n    public ToolBar getToolBar() {\n        return toolbar;\n    }\n\n    /**\n     * Returns the panel where the {@link ToolBar} component is.\n     * Note that a non-null instance of {@link ToolBar} is returned even if it is currently hidden.\n     *\n     * @return the toolbar component\n     */\n    public JPanel getToolBarPanel() {\n        return toolbarPanel;\n    }\n\n\n    /**\n     * Returns the currently active panel.\n     *\n     * <p>The returned panel doesn't necessarily have focus, for example if the MainFrame is currently not in the\n     * foreground.\n     *\n     * @return the currently active panel\n     */\n    public FolderPanel getActivePanel() {\n        return activeTable.getFolderPanel();\n    }\n\n    /**\n     * Sets the currently active FileTable. This method is to be called by FolderPanel only.\n     *\n     * @param table the currently active FileTable\n     */\n    void setActiveTable(FileTable table) {\n        boolean activeTableChanged = activeTable != table;\n\n        if (activeTableChanged) {\n            this.activeTable = table;\n\n            // Update window title to reflect new active table\n            updateWindowTitle();\n\n            // Fire table change events on registered ActivePanelListener instances.\n            fireActivePanelChanged(table.getFolderPanel());\n        }\n    }\n\n\t\n    /**\n     * Returns the inactive table, i.e. the complement of {@link #getActiveTable()}.\n     *\n     * @return the inactive table\n     */\n    public FileTable getInactiveTable() {\n        return activeTable == leftTable ? rightTable : leftTable;\n    }\n    \n    /**\n     * Returns the inactive panel, i.e. the complement of {@link #getActivePanel()}.\n     *\n     * @return the inactive panel\n     */\n    public FolderPanel getInactivePanel() {\n        return getInactiveTable().getFolderPanel();\n    }\n\n    /**\n     * Returns the FolderPanel instance corresponding to the left panel.\n     *\n     * @return the FolderPanel instance corresponding to the left panel\n     */\n    public FolderPanel getLeftPanel() {\n        return leftFolderPanel;\n    }\n\n    /**\n     * Returns the FolderPanel instance corresponding to the right panel.\n     *\n     * @return the FolderPanel instance corresponding to the right panel\n     */\n    public FolderPanel getRightPanel() {\n        return rightFolderPanel;\n    }\n\n\n    /**\n     * Specifies how folder panels are split: if true is passed, the folder panels will be split vertically\n     * (default), horizontally otherwise.\n     *\n     * @param vertical if true, the folder panels will be split horizontally (default), vertically otherwise.\n     */\n    public void setSplitPaneOrientation(boolean vertical) {\n        // Note: the vertical/horizontal terminology used in muCommander is just the opposite of the one used\n        // in JSplitPane which is anti-natural / confusing\n        splitPane.setOrientation(vertical ? JSplitPane.HORIZONTAL_SPLIT : JSplitPane.VERTICAL_SPLIT);\n    }\n\n    /**\n     * Returns how folder panels are currently split: if <code>true</code> is returned, panels are split vertically\n     * (default), horizontally otherwise.\n     *\n     * @return <code>true</code> if folder panels are split vertically\n     */\n    public boolean getSplitPaneOrientation() {\n        // Note: the vertical/horizontal terminology used in muCommander is just the opposite of the one used\n        // in JSplitPane which is anti-natural / confusing\n        return splitPane.getOrientation() == JSplitPane.HORIZONTAL_SPLIT;\n    }\n\n\n    /**\n     * Swaps the two FolderPanel instances: after a call to this method, the left FolderPanel will be the right one and\n     * vice-versa.\n     */\n    public void swapFolders() {\n        splitPane.remove(leftFolderPanel.getPanel());\n        splitPane.remove(rightFolderPanel.getPanel());\n\n        // Swaps the folder panels.\n        FolderPanel tempPanel = leftFolderPanel;\n        leftFolderPanel = rightFolderPanel;\n        rightFolderPanel = tempPanel;\n\n        // swaps folders trees\n        int tempTreeWidth = leftFolderPanel.getTreeWidth();\n        leftFolderPanel.setTreeWidth(rightFolderPanel.getTreeWidth());\n        rightFolderPanel.setTreeWidth(tempTreeWidth);\n        boolean tempTreeVisible = leftFolderPanel.isTreeVisible();\n        leftFolderPanel.setTreeVisible(rightFolderPanel.isTreeVisible());\n        rightFolderPanel.setTreeVisible(tempTreeVisible);\n        \n\n        // Resets the tables.\n        FileTable tempTable = leftTable;\n        leftTable = rightTable;\n        rightTable = tempTable;\n\n        // Preserve the sort order and columns visibility.\n        TableColumnModel model = leftTable.getColumnModel();\n        leftTable.setColumnModel(rightTable.getColumnModel());\n        rightTable.setColumnModel(model);\n\n        SortInfo sortInfo = leftTable.getSortInfo().clone();\n\n        leftTable.sortBy(rightTable.getSortInfo());\n        leftTable.updateColumnsVisibility();\n\n        rightTable.sortBy(sortInfo);\n        rightTable.updateColumnsVisibility();\n\n        // Do the swap and update the split pane\n        splitPane.setLeftComponent(leftFolderPanel.getPanel());\n        splitPane.setRightComponent(rightFolderPanel.getPanel());\n\n        splitPane.doLayout();\n\n        // Update split pane divider's location\n        splitPane.updateDividerLocation();\n\n        activeTable.requestFocus();\n    }\n\n    /**\n     * Makes both folders the same, choosing the one which is currently active. \n     */\n    public void setSameFolder() {\n        (activeTable == leftTable ? rightTable : leftTable).getFolderPanel().tryChangeCurrentFolder(activeTable.getFolderPanel().getCurrentFolder());\n    }\n\n    /**\n     * Sets whether this MainFrame is currently active in the foreground. This method is to be called by WindowManager\n     * only.\n     *\n     * @param foregroundActive true if this MainFrame is currently active in the foreground\n     */\n    void setForegroundActive(boolean foregroundActive) {\n        this.foregroundActive = foregroundActive;\n    }\n\n    /**\n     * Forces a refresh of the frame's folder panel.\n     */\n    public void tryRefreshCurrentFolders() {\n        leftFolderPanel.tryRefreshCurrentFolder();\n        rightFolderPanel.tryRefreshCurrentFolder();\n    }\n\n\n    /**\n     * Returns <code>true</code> if this MainFrame is active, or is an ancestor of a Window that is currently active.\n     *\n     * @return <code>true</code> if this MainFrame is active, or is an ancestor of a Window that is currently active\n     */\n    public boolean isAncestorOfActiveWindow() {\n        if (frameInstance.isActive()) {\n            return true;\n        }\n\n        for (Window w : frameInstance.getOwnedWindows()) {\n            if (w.isActive()) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Updates this window's title to show currently active folder and window number.\n     * This method is called by this class and WindowManager.\n     */\n    public void updateWindowTitle() {\n        // Update window title\n        AbstractFile currentFolder = activeTable.getFolderPanel().getCurrentFolder();\n        String title = currentFolder != null ? currentFolder.getAbsolutePath() : \"\";\n\n\t// Add the application name to window title on all OSs except MAC\n        if (!OsFamily.MAC_OS_X.isCurrent()) {\n            title += \" - trolCommander\";\n        }\n\n        List<MainFrame> mainFrames = WindowManager.getMainFrames();\n        if (mainFrames.size() > 1) {\n            title += \" [\" + (mainFrames.indexOf(this) + 1) + \"]\";\n        }\n        frameInstance.setTitle(title);\n\n        // Use new Window decorations introduced in Mac OS X 10.5 (Leopard)\n        if (OsFamily.MAC_OS_X.isCurrent() && OsVersion.MAC_OS_X_10_5.isCurrentOrHigher() && currentFolder != null) {\n            // Displays the document icon in the window title bar, works only for local files\n            Object javaIoFile;\n            if (currentFolder.getURL().getScheme().equals(FileProtocols.FILE)) {\n                // If the current folder is an archive entry, display the archive file, this is the closest we can get\n                // with a java.io.File\n                if (currentFolder.hasAncestor(AbstractArchiveEntryFile.class)) {\n                    AbstractArchiveFile parent = currentFolder.getParentArchive();\n                    javaIoFile = parent != null ? parent.getUnderlyingFileObject() : null;\n                } else {\n                    javaIoFile = currentFolder.getUnderlyingFileObject();\n                }\n            } else {\n                // If the current folder is not a local file, use the special /Network directory which is sort of\n                // 'Network Neighborhood'.\n                javaIoFile = new java.io.File(\"/Network\");\n            }\n\n            // Note that for some strange reason (looks like a bug), setting the property to null won't remove\n            // the previous icon.\n            frameInstance.getRootPane().putClientProperty(\"Window.documentFile\", javaIoFile);\n        }\n    }\n\n\n    /**\n     * Toggles single panel view state and returns new one\n     *\n     * @return new state for singlePanel boolean\n     */\n    public boolean toggleSinglePanel() {\n        singlePanel = !singlePanel;\n        return singlePanel;\n    }\n\n    /**\n     * Overrides <code>java.awt.Window#toFront</code> to have the window return to a normal state if it is minimized.\n     */\n    public void toFront() {\n        if ( (frameInstance.getExtendedState()&Frame.ICONIFIED) != 0) {\n            frameInstance.setExtendedState(Frame.NORMAL);\n        }\n        frameInstance.toFront();\n    }\n\n\n    /**\n     * Manages focus for both FolderPanel and their subcomponents.\n     *\n     * @author Maxence Bernard\n     */\n    protected class CustomFocusTraversalPolicy extends FocusTraversalPolicy {\n\n        @Override\n        public Component getComponentAfter(Container container, Component component) {\n            if (component == leftFolderPanel.getFoldersTreePanel().getTree())\n                return leftTable;\n            if (component == rightFolderPanel.getFoldersTreePanel().getTree())\n                return rightTable;\n            if (component == leftFolderPanel.getLocationTextField())\n                return leftTable;\n            if (component == leftTable)\n                return rightTable;\n            if (component == rightFolderPanel.getLocationTextField())\n                return rightTable;\n            // otherwise (component==table2)\n            return leftTable;\n        }\n\n        @Override\n        public Component getComponentBefore(Container container, Component component) {\n            // Completely symmetrical with getComponentAfter\n            return getComponentAfter(container, component);\n       }\n\n        @Override\n        public Component getFirstComponent(Container container) {\n            return leftTable;\n        }\n\n        @Override\n        public Component getLastComponent(Container container) {\n            return rightTable;\n        }\n\n        @Override\n        public Component getDefaultComponent(Container container) {\n            return getActiveTable();\n        }\n    }\n\n    public boolean isAutoSizeColumnsEnabled() {\n        return leftTable.isAutoSizeColumnsEnabled();\n    }\n\n    public void setAutoSizeColumnsEnabled(boolean b) {\n        leftTable.setAutoSizeColumnsEnabled(b);\n        rightTable.setAutoSizeColumnsEnabled(b);\n    }\n    \n\n    @Override\n    public void locationChanged(LocationEvent e) {\n        // Update window title to reflect the new current folder\n        updateWindowTitle();\n    }\n\n    @Override\n\tpublic void locationChanging(LocationEvent locationEvent) { }\n\n    @Override\n\tpublic void locationCancelled(LocationEvent locationEvent) { }\n\n    @Override\n\tpublic void locationFailed(LocationEvent locationEvent) { }\n\n\n\n    public void showTerminalPanel(boolean show) {\n        if (show) {\n            if (tcTerminal == null) {\n                tcTerminal = new TcTerminal(this);\n                terminalSplitPane = new JSplitPane();\n                terminalSplitPane.setBorder(null);\n                terminalSplitPane.setOrientation(JSplitPane.VERTICAL_SPLIT);\n            }\n\n            terminalSplitPane.setTopComponent(splitPane);\n            terminalSplitPane.setBottomComponent(tcTerminal.getComponent());\n\n            int height = tcTerminal.loadHeight();\n            if (height < 0) {\n                height = frameInstance.getHeight()/2;\n            }\n            terminalSplitPane.setDividerLocation(height);\n\n            tcTerminal.show(true);\n            insetsPane.remove(splitPane);\n            insetsPane.add(terminalSplitPane, BorderLayout.CENTER);\n            tcTerminal.updateTitle();\n            frameInstance.revalidate();\n            tcTerminal.getComponent().requestFocusInWindow();\n        } else if (tcTerminal != null) {\n            tcTerminal.storeHeight(terminalSplitPane.getDividerLocation());\n            insetsPane.remove(terminalSplitPane);\n            insetsPane.add(splitPane, BorderLayout.CENTER);\n            tcTerminal.show(false);\n            frameInstance.revalidate();\n            activeTable.requestFocus();\n            updateWindowTitle();\n        }\n    }\n\n\n    public void toggleTerminalPanel() {\n        JComponent term = tcTerminal != null ? tcTerminal.getComponent() : null;\n\n        if (term != null && !term.hasFocus() && term.getParent().getParent() != null) {\n            tcTerminal.getComponent().getComponent(0).requestFocus();\n        } else {\n            showTerminalPanel(tcTerminal == null || !tcTerminal.getComponent().isVisible());\n        }\n    }\n\n\n    public void closeTerminalSession() {\n        showTerminalPanel(false);\n        tcTerminal = null;\n        terminalSplitPane = null;\n    }\n\n\n    public void setTerminalPanelHeight(int height) {\n        if (height > terminalSplitPane.getHeight()) {\n            height = terminalSplitPane.getHeight();\n        }\n        terminalSplitPane.setDividerLocation(terminalSplitPane.getHeight() - height);\n    }\n\n    public int getTerminalPanelHeight() {\n        return terminalSplitPane.getHeight() - terminalSplitPane.getDividerLocation();\n    }\n\n    public JFrame getJFrame() {\n        return frameInstance;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/PreviewPanel.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.main;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.theme.Theme;\nimport com.mucommander.ui.theme.ThemeManager;\nimport com.mucommander.ui.viewer.FileViewer;\nimport com.mucommander.ui.viewer.UserCancelledException;\nimport com.mucommander.ui.viewer.ViewerRegistrar;\nimport com.mucommander.ui.viewer.text.TextArea;\nimport com.mucommander.ui.viewer.text.TextViewer;\nimport net.sf.jftp.gui.tasks.ImageViewer;\n\nimport javax.swing.JPanel;\nimport java.awt.BorderLayout;\nimport java.awt.Color;\nimport java.awt.Font;\nimport java.awt.event.KeyEvent;\nimport java.io.IOException;\n\n/**\n * @author Oleg Trifonov\n *\n * Created on 26/09/2016.\n */\nclass PreviewPanel extends JPanel {\n\n    private TextArea textArea;\n\n    private TextViewer textViewer;\n    private ImageViewer imageViewer;\n\n    PreviewPanel() {\n        super(new BorderLayout());\n\n        // No decoration for this panel\n        setBorder(null);\n    }\n\n\n    private void setupTextArea() {\n        textArea.setCurrentLineHighlightColor(ThemeManager.getCurrentColor(Theme.EDITOR_CURRENT_BACKGROUND_COLOR));\n        textArea.setAntiAliasingEnabled(true);\n        textArea.setEditable(false);\n        //textArea.addKeyListener(textAreaKeyListener);\n\n        try {\n            ThemeManager.readEditorTheme(ThemeManager.getCurrentSyntaxThemeName()).apply(textArea);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        //textArea.setCodeFoldingEnabled(true);\n\n        // Use theme colors and font\n        textArea.setForeground(ThemeManager.getCurrentColor(Theme.EDITOR_FOREGROUND_COLOR));\n        textArea.setCaretColor(ThemeManager.getCurrentColor(Theme.EDITOR_FOREGROUND_COLOR));\n        Color background = ThemeManager.getCurrentColor(Theme.EDITOR_BACKGROUND_COLOR);\n        textArea.setBackground(background);\n\n        for (int i = 1; i <= textArea.getSecondaryLanguageCount(); i++) {\n            textArea.setSecondaryLanguageBackground(i, background);\n        }\n        textArea.setSelectedTextColor(ThemeManager.getCurrentColor(Theme.EDITOR_SELECTED_FOREGROUND_COLOR));\n        textArea.setSelectionColor(ThemeManager.getCurrentColor(Theme.EDITOR_SELECTED_BACKGROUND_COLOR));\n        textArea.setFont(ThemeManager.getCurrentFont(Theme.EDITOR_FONT));\n        textArea.setCodeFoldingEnabled(true);\n\n        textArea.setWrapStyleWord(true);\n\n        textArea.addMouseWheelListener(e -> {\n            boolean isCtrlPressed = (e.getModifiersEx() & KeyEvent.CTRL_DOWN_MASK) != 0;\n            if (isCtrlPressed) {\n                Font currentFont = textArea.getFont();\n                int currentFontSize = currentFont.getSize();\n                boolean rotationUp = e.getWheelRotation() < 0;\n                if (rotationUp || currentFontSize > 1) {\n                    Font newFont = new Font(currentFont.getName(), currentFont.getStyle(), currentFontSize + (rotationUp ? 1 : -1));\n                    textArea.setFont(newFont);\n                }\n            } else {\n                textArea.getParent().dispatchEvent(e);\n            }\n        });\n\n        //textArea.addCaretListener(caretListener);\n    }\n\n\n    void loadFile(AbstractFile file) {\n        if (file == null) {\n            clearPreviewArea();\n            doLayout();\n            repaint();\n        } else if (file.isDirectory()) {\n            clearPreviewArea();\n            doLayout();\n            repaint();\n        } else {\n            previewFile(file);\n        }\n\n    }\n\n\n    private void previewFile(AbstractFile file) {\n        try {\n            FileViewer viewer = ViewerRegistrar.createFileViewer(file, null, null);\n            clearPreviewArea();\n            if (viewer != null) {\n                viewer.open(file);\n                add(viewer);\n            }\n        } catch (UserCancelledException | IOException e) {\n            e.printStackTrace();\n        }\n    }\n\n\n    private void clearPreviewArea() {\n        while (getComponents().length > 0) {\n            remove(0);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/QuickLists.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main;\n\n/**\n * Enum of quick lists implemented in muCommander\n * \n * @author Arik Hadas\n */\npublic enum QuickLists {\n\tPARENT_FOLDERS,\n    RECENT_ACCESSED_LOCATIONS,\n    RECENT_EXECUTED_FILES,\n    BOOKMARKS,\n    ROOTS,\n    TABS,\n    RECENT_VIEWED_FILES,\n    RECENT_EDITED_FILES,\n    EDITOR_BOOKMARKS\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/SplashScreen.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main;\n\nimport com.mucommander.RuntimeConstants;\nimport com.mucommander.commons.file.util.ResourceLoader;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.ui.dialog.DialogToolkit;\nimport com.mucommander.ui.icon.IconManager;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.font.FontRenderContext;\nimport java.awt.geom.Rectangle2D;\n\n\n/**\n * Splash screen that gets displayed on muCommander startup.\n *\n * <p>The splash screen is made of a logo image on top of which is displayed muCommander version number (in the top right corner)\n * and a loading message (in the lower left corner) which is updated by {@link com.mucommander.TrolCommander} to show startup progress.\n * It is then closed by {@link com.mucommander.TrolCommander} when muCommander is fully started and ready for use.\n *\n * @author Maxence Bernard\n */\npublic class SplashScreen extends JWindow {\n\n    /** trolCommander version displayed on this splash screen */\n    private final String version;\n\n    /** Current loading message displayed on this splash screen */\n    private String loadingMessage;\n\n    /** Font used to display version and loading message on this splash screen */\n    private final Font customFont;\n\n    /** Path to the splash screen wo image within the JAR file */\n    private final static String SPLASH_IMAGE_PATH = IconManager.IconSet.TROLCOMMANDER.getFolder() + \"splash.png\";\n\n    /** Color of the text displayed on this splash screen */ \n    private final static Color TEXT_COLOR = new Color(192, 238, 241);\n    private final static Color SHADOW_TEXT_COLOR = new Color(0, 86, 117);\n\n    /** Number of pixels between the loading message and the left side of the splash image */\n    private final static int LOADING_MSG_MARGIN_X = 4;\n    /** Number of pixels between the loading message and the bottom of the splash image */\n    private final static int LOADING_MSG_MARGIN_Y = 6;\n\n    /** Number of pixels between the version information and the right side of the splash image */\n    private final static int VERSION_MARGIN_X = 5;\n    /** Number of pixels between the version information and the top of the splash image */\n    private final static int VERSION_MARGIN_Y = 3;\n\n\n    /**\n     * Creates and displays a new SplashScreen, with the given version string and initial loading message.\n     *\n     * @param version trolCommander version string which will be displayed in the top right corner\n     * @param loadingMessage initial loading message, displayed in the lower left corner\n     */\n    public SplashScreen(String version, String loadingMessage) {\n        this.version = version;\n        this.loadingMessage = loadingMessage;\n\n        // create a custom font\n        int fontSize = OsFamily.getCurrent() == OsFamily.LINUX && RuntimeConstants.DISPLAY_4K ? 24 : 11;\n        this.customFont = new Font(\"Courier\", Font.BOLD, fontSize);\n        ImageIcon imageIcon = loadImageIcon();\n\n        setContentPane(new JLabel(imageIcon));\n\t\t\n        setSizeFromImage(imageIcon);\n        DialogToolkit.centerOnScreen(this);\n\n        // Display the splash screen\n        setVisible(true);\n    }\n\n    private ImageIcon loadImageIcon() {\n        // Resolve the URL of the splash logo image within the JAR file and create an ImageIcon\n        // Note: DO NOT use IconManager to load the icon as it would trigger ConfigurationManager's initialization\n        // and we don't want that, we want SplashScreen to be displayed as soon as possible\n        ImageIcon imageIcon = new ImageIcon(ResourceLoader.getResourceAsURL(SPLASH_IMAGE_PATH));\n\n        // Wait for the image to be fully loaded\n        MediaTracker mediaTracker = new MediaTracker(this);\n        mediaTracker.addImage(imageIcon.getImage(), 0);\n        try {\n            mediaTracker.waitForID(0);\n        } catch(InterruptedException e) {\n            e.printStackTrace();\n        }\n        return imageIcon;\n    }\n\n    private void setSizeFromImage(ImageIcon imageIcon) {\n        // Set size manually instead of using pack(), because of a bug under 1.3.1/Win32 which\n        // eats a 1-pixel row of the image\n        int width = imageIcon.getIconWidth();\n        int height = imageIcon.getIconHeight();\n        setSize(width, height);\n    }\n\n\n    /**\n     * Repaints this SplashScreen to display the new given loading message, replacing the previous one. \n     *\n     * @param msg the new loading message to be displayed\n     */\n    public void setLoadingMessage(String msg) {\n        this.loadingMessage = msg;\n        repaint();\n    }\n\n\n    /**\n     * Overridden paint method.\n     */\n    @Override\n    public void paint(Graphics g) {\n        super.paint(g);\n\n        g.setFont(customFont);\n\n        // Display loading message in the lower left corner\n        int textX = LOADING_MSG_MARGIN_X;\n        int textY = getHeight() - LOADING_MSG_MARGIN_Y;\n\n        g.setColor(SHADOW_TEXT_COLOR);\n        g.drawString(loadingMessage, textX-1, textY-1);\n\n        g.setColor(TEXT_COLOR);\n        g.drawString(loadingMessage, textX, textY);\n\n        // Display version in the top right corner\n        // Get FontRenderContext instance to calculate text width and height\n        FontRenderContext fontRenderContext = ((Graphics2D)g).getFontRenderContext();\n        Rectangle2D textBounds = new java.awt.font.TextLayout(version, customFont, fontRenderContext).getBounds();\n\n        textX = getWidth()-(int)textBounds.getWidth()-VERSION_MARGIN_X;\n        textY = (int)textBounds.getHeight()+VERSION_MARGIN_Y;\n\n        g.setColor(SHADOW_TEXT_COLOR);\n        g.drawString(version, textX-1, textY-1);\n\n        g.setColor(TEXT_COLOR);\n        g.drawString(version, textX, textY);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/SystemFileFilter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.filter.AbstractFileFilter;\n\n/**\n * Filter used to filter out system files and folders that should not be displayed to inexperienced users.\n *\n * <p>At the moment, this filter only supports Mac OS X top-level system folders (those hidden by Finder)\n * and thus this filter should only be used under Mac OS X.\n *\n * @author Maxence Bernard\n */\npublic class SystemFileFilter extends AbstractFileFilter {\n\n    /**\n     * Top-level Mac OS X system folders hidden by Finder. For more info about those files:\n     * http://www.westwind.com/reference/OS-X/invisibles.html\n     */\n    private final static String[] SYSTEM_FOLDERS = {\n        // Mac OS X system folders\n        \"/.Trashes\",\n        \"/.vol\",\n        \"/dev\",\n        \"/automount\",\n        \"/bin\",\n        \"/cores\",\n        \"/etc\",\n        \"/lost+found\",    \n        \"/Network\",\n        \"/private\",\n        \"/sbin\",\n        \"/tmp\",\n        \"/usr\",\n        \"/var\",\n//        \"/Volumes\",\n        \"/mach.sym\",\n        \"/mach_kernel\",\n        \"/mach\",\n        \"/Desktop DB\",\n        \"/Desktop DF\",\n        \"/File Transfer Folder\",\n        \"/.hotfiles.btree\",\n        \"/.Spotlight-V100\",\n        \"/.hidden\",     // Used by Mac OS X up to 10.3, not in 10.4\n        System.getProperty(\"user.home\")+\"/.Trash\",  // User trash folder\n        // Mac OS 9 system folders \n        \"/AppleShare PDS\",\n        \"/Cleanup At Startup\",\n        \"/Desktop Folder\",\n        \"/Network Trash Folder\",\n        \"/Shutdown Check\",\n        \"/Temporary Items\",\n        System.getProperty(\"user.home\")+\"/Temporary Items\",  // User trash folder\n        \"/TheFindByContentFolder\",\n        \"/TheVolumeSettingsFolder\",\n        \"/Trash\",\n        \"/VM Storage\"\n    };\n\n\n    ///////////////////////////////\n    // FileFilter implementation //\n    ///////////////////////////////\n\n    public boolean accept(AbstractFile file) {\n        String path = file.getAbsolutePath(false);\n\n        for (String SYSTEM_FOLDER : SYSTEM_FOLDERS) {\n            if (path.equals(SYSTEM_FOLDER)) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/WindowManager.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main;\n\nimport java.awt.Frame;\nimport java.awt.event.WindowEvent;\nimport java.awt.event.WindowListener;\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Vector;\n\nimport javax.swing.LookAndFeel;\nimport javax.swing.MenuSelectionManager;\nimport javax.swing.SwingUtilities;\nimport javax.swing.UIManager;\n\nimport com.mucommander.ui.PreloadedJFrame;\nimport com.mucommander.ui.dialog.FocusDialog;\nimport com.mucommander.ui.viewer.FileFrame;\nimport lombok.Getter;\nimport lombok.extern.slf4j.Slf4j;\n\nimport com.mucommander.launcher.ShutdownHook;\nimport com.mucommander.commons.conf.ConfigurationEvent;\nimport com.mucommander.commons.conf.ConfigurationListener;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.extension.ExtensionManager;\nimport com.mucommander.ui.main.commandbar.CommandBar;\nimport com.mucommander.ui.main.frame.MainFrameBuilder;\nimport com.mucommander.ui.helper.FocusRequester;\n\n/**\n * Window Manager is responsible for creating, disposing, switching,\n * in other words managing :) muCommander windows.\n *\n * @author Maxence Bernard, Arik Hadas\n */\n//public class WindowManager implements ActionListener, WindowListener, ActivePanelListener, LocationListener, ConfigurationListener {\n@Slf4j\npublic class WindowManager implements WindowListener, ConfigurationListener {\n    // The following constants are used to compute the proper position of a new MainFrame.\n\n    /** MainFrame (main trolCommander window) instances */\n    private final List<MainFrame> mainFrames = new Vector<>();\n    \n    /** MainFrame currently being used (that has focus),\n     * or last frame to have been used if muCommander doesn't have focus */\t\n    private MainFrame currentMainFrame;\n\n    /**\n     * -- GETTER --\n     *  Returns the sole instance of WindowManager.\n     */\n    @Getter\n    private static final WindowManager instance = new WindowManager();\n\n\n    /**\n     * Installs all custom look and feels.\n     */\n    private static void installCustomLookAndFeels() {\n        // Get all available custom look and feels\n        List<String> plafs = TcConfigurations.getPreferences().getListVariable(TcPreference.CUSTOM_LOOK_AND_FEELS, TcPreferences.CUSTOM_LOOK_AND_FEELS_SEPARATOR);\n\n        // Tries to retrieve the custom look and feels list.\n        if (plafs == null) {\n            return;\n        }\n\n        // Goes through the list and install every custom look and feel we could find.\n        // Look and feels that aren't supported under the current platform are ignored.\n        for (String plaf : plafs) {\n            try {\n                installLookAndFeel(plaf);\n            } catch(Throwable e) {\n                log.info(\"Failed to install Look&Feel {}\", plaf, e);\n            }\n        }\n    }\n\n    /**\n     * Creates a new instance of WindowManager.\n     */\n    private WindowManager() {\n        // Notifies Swing that look&feels must be loaded as extensions.\n        // This is necessary to ensure that look and feels placed in the extensions folder\n        // are accessible.\n        UIManager.getDefaults().put(\"ClassLoader\", ExtensionManager.getClassLoader());\n\n        // Installs all custom look and feels.\n        installCustomLookAndFeels();\n        \n        TcConfigurations.addPreferencesListener(this);\n    }\n\n    public static void setupAfterCreation() {\n        // Sets custom lookAndFeel if different from current lookAndFeel\n        String lnfName = TcConfigurations.getPreferences().getVariable(TcPreference.LOOK_AND_FEEL);\n        if (lnfName != null && !lnfName.equals(UIManager.getLookAndFeel().getName())) {\n            setLookAndFeel(lnfName);\n        }\n\n        if (lnfName == null) {\n            log.debug(\"Could load look'n feel from preferences\");\n        }\n    }\n\n\n    /**\n     * Returns the <code>MainFrame</code> instance that was last active. Note that the returned <code>MainFrame</code>\n     * may or may not be currently active.\n     *\n     * @return the <code>MainFrame</code> instance that was last active\n     */\n    public static MainFrame getCurrentMainFrame() {\n        return instance.currentMainFrame;\n    }\n\t\n    /**\n     * Returns a <code>Vector</code> of all <code>MainFrame</code> instances currently displaying.\n     *\n     * @return a <code>Vector</code> of all <code>MainFrame</code> instances currently displaying\n     */\n    public static List<MainFrame> getMainFrames() {\n        return instance.mainFrames;\n    }\n\n    /**\n     * Refreshes all panels in all frames in an asynchronous manner.\n     */\n    public static void tryRefreshCurrentFolders() {\n        // Starts with the main frame to make sure that results are immediately\n        // visible to the user.\n    \tinstance.currentMainFrame.tryRefreshCurrentFolders();\n        for (MainFrame mainFrame : instance.mainFrames) {\n            if (mainFrame != instance.currentMainFrame) {\n                mainFrame.tryRefreshCurrentFolders();\n            }\n        }\n    }\n\n    /**\n     * Creates a new MainFrame and makes it visible on the screen, on top of any other frames.\n     *\n     * @param mainFrameBuilder MainFrameBuilder class\n     */\n    public static synchronized void createNewMainFrame(MainFrameBuilder mainFrameBuilder) {\n        MainFrame[] newMainFrames = mainFrameBuilder.build();\n\n        // To catch user window closing actions\n        for (MainFrame frame : newMainFrames) {\n            frame.getJFrame().addWindowListener(instance);\n        }\n\n        // Adds the new MainFrame to the vector\n        instance.mainFrames.addAll(Arrays.asList(newMainFrames));\n\n        // Set new window's title. Window titles show window number only if there is more than one window.\n        // So if a second window was just created, we update first window's title so that it shows window number (#1).\n        for (MainFrame frame : instance.mainFrames) {\n            frame.updateWindowTitle();\n        }\n\n        // Make frames visible\n        for (MainFrame frame : newMainFrames) {\n            frame.getJFrame().setVisible(true);\n        }\n\n        if (!instance.mainFrames.isEmpty()) {\n        \tint previouslySelectedMainFrame = mainFrameBuilder.getSelectedFrame();\n        \tinstance.mainFrames.get(previouslySelectedMainFrame).toFront();\n        }\n    }\n\n    /**\n     * Disposes all opened windows, ending with the one that is currently active if there is one, \n     * or the last one which was activated.\t\n     */\n    public static synchronized void quit() {\n        // Dispose all MainFrames, ending with the currently active one.\n        int nbFrames = instance.mainFrames.size();\n        if (nbFrames > 0) {            // If an uncaught exception occurred in the startup sequence, there is no MainFrame to dispose\n            // Retrieve current MainFrame's index\n            int currentMainFrameIndex = getCurrentWindowIndex();\n            \n            // Dispose all MainFrames but the current one\n            for (int i = 0; i < nbFrames; i++) {\n                if (i != currentMainFrameIndex) {\n                    instance.mainFrames.get(i).getJFrame().dispose();\n                }\n            }\n\n            // Dispose current MainFrame last so that its attributes (last folders, window position...) are saved last\n            // in the preferences\n            if (currentMainFrameIndex >= 0) {\n                instance.mainFrames.get(currentMainFrameIndex).getJFrame().dispose();\n            }\n        }\n\n        // Dispose all other frames (viewers, editors...)\n        for (Frame frame : Frame.getFrames()) {\n            if (frame.isShowing()) {\n                log.debug(\"disposing frame {}\", frame.getTitle());\n                frame.dispose();\n            }\n        }\n\n        // Initiate shutdown sequence.\n        // Important note: we cannot rely on windowClosed() triggering the shutdown sequence as\n        // Quit under OS X shuts down the app as soon as this method returns and as a result, \n        // windowClosed() events are never dispatched to the MainFrames\n        ShutdownHook.initiateShutdown();\n    }\n\t\n    /**\n     * Returns the index of the currently selected window\n     * @return index of currently selected window\n     */\n    public static int getCurrentWindowIndex() {\n    \treturn instance.mainFrames.indexOf(instance.currentMainFrame);\n    }\n\t\n    /**\n     * Switches to the next MainFrame, in the order of which they were created.\n     */\n    public static void switchToNextWindow() {\n        int frameIndex = getCurrentWindowIndex();\n        MainFrame mainFrame = instance.mainFrames.get((frameIndex+1) % instance.mainFrames.size());\n        mainFrame.toFront();\n    }\n\n    /**\n     * Switches to previous MainFrame, in the order of which they were created.\n     */\n    public static void switchToPreviousWindow() {\n        int frameIndex = getCurrentWindowIndex();\n        int nbFrames = instance.mainFrames.size();\n        MainFrame mainFrame = instance.mainFrames.get((frameIndex-1+nbFrames) % nbFrames);\n        mainFrame.toFront();\n    }\n\n    public static void installLookAndFeel(String className) throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException {\n        LookAndFeel plaf = (LookAndFeel)Class.forName(className, true, ExtensionManager.getClassLoader()).getEnclosingConstructor().newInstance();\n        if (plaf.isSupportedLookAndFeel()) {\n            UIManager.installLookAndFeel(plaf.getName(), plaf.getClass().getName());\n        }\n    }\n\n    /**\n     * Changes LooknFeel to the given one, updating the UI of each MainFrame.\n     *\n     * @param lnfName name of the new LooknFeel to use\n     */\n    private static void setLookAndFeel(String lnfName) {\n        try {\n            // Initializes class loading.\n            // This is necessary due to Swing's UIDefaults.LazyProxyValue behavior that just\n            // won't use the right ClassLoader instance to load resources.\n            Thread currentThread = Thread.currentThread();\n            ClassLoader oldLoader = currentThread.getContextClassLoader();\n            currentThread.setContextClassLoader(ExtensionManager.getClassLoader());\n\n            UIManager.setLookAndFeel((LookAndFeel)Class.forName(lnfName, true, ExtensionManager.getClassLoader()).getDeclaredConstructor().newInstance());\n\n            // Restores the contextual ClassLoader.\n            currentThread.setContextClassLoader(oldLoader);\n\n            for (MainFrame mainFrame : instance.mainFrames) {\n                SwingUtilities.updateComponentTreeUI(mainFrame.getJFrame());\n            }\n        } catch(Throwable e) {\n            log.debug(\"Exception caught\", e);\n        }\n    }\n\n    @Override\n    public void windowActivated(WindowEvent e) {\n        Object source = e.getSource();\n\n        // Return if event doesn't originate from a MainFrame/PreloadedJFrame (e.g. ViewerFrame or EditorFrame)\n        if(!(source instanceof PreloadedJFrame))\n            return;\n\n        currentMainFrame = (MainFrame) ((PreloadedJFrame)source).getMainFrameObject();\n        // Let MainFrame know that it is active in the foreground\n        currentMainFrame.setForegroundActive(true);\n\n        // Resets shift mode to false, since keyReleased events may have been lost during window switching\n        CommandBar commandBar = currentMainFrame.getCommandBar();\n        if (commandBar != null) {\n            commandBar.setAlternateActionsMode(false);\n        }\n    }\n\n    @Override\n    public void windowDeactivated(WindowEvent e) {\n        Object source = e.getSource();\n\n        // Workaround for JRE bug #4841881 (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4841881) /\n        // which causes Alt+Tab to focus the menu bar under certain L&F.\n        // This bug has also been reported as trolCommmander bug #89.\n        MenuSelectionManager.defaultManager().clearSelectedPath();\n\n        // Return if event doesn't originate from a MainFrame (e.g. ViewerFrame or EditorFrame)\n        if (source instanceof PreloadedJFrame pf) {\n            // Let MainFrame know that it is not active anymore\n            ((MainFrame)(pf).getMainFrameObject()).setForegroundActive(false);\n        }\n    }\n\n    @Override\n    public void windowClosing(WindowEvent e) {\n    }\n    \n    /**\n     * windowClosed is synchronized so that it doesn't get called while quit() is executing.\n     */\n    @Override\n    public synchronized void windowClosed(WindowEvent e) {\n        log.trace(\"called\");\n        Object source = e.getSource();\n\n        if (source instanceof PreloadedJFrame pfSource) {\n            var mainFrame = (MainFrame) (pfSource).getMainFrameObject();\n            closeMainFrame(mainFrame);\n        } else if (source instanceof FileFrame fileFrame) {\n            if (fileFrame.getReturnFocusTo() != null) {\n                return;\n            }\n        } else if (source instanceof FocusDialog focusDialog) {\n            if (focusDialog.getReturnFocusTo() != null) {\n                return;\n            }\n        }\n\n        // Test if there is at least one MainFrame still showing\n        if (!mainFrames.isEmpty()) {\n            FocusRequester.requestFocus(currentMainFrame.getJFrame());\n        } else if (!hasMoreActiveFrames()) {\n            ShutdownHook.initiateShutdown();\n        }\n    }\n\n\n    /**\n     * Remove disposed MainFrame from the MainFrame list\n     */\n    private void closeMainFrame(MainFrame mainFrame) {\n        int frameIndex = mainFrames.indexOf(mainFrame);\n\n        mainFrames.remove(mainFrame);\n\n        // Update following windows titles to reflect the MainFrame's disposal.\n        // Window titles show window number only if there is more than one window.\n        // So if there is only one window left, we update first window's title so that it removes window number (#1).\n        int nbFrames = mainFrames.size();\n        if (nbFrames == 1) {\n            mainFrames.getFirst().updateWindowTitle();\n        } else {\n            if (frameIndex >= 0) {\n                for (int i = frameIndex; i < nbFrames; i++) {\n                    mainFrames.get(i).updateWindowTitle();\n                }\n            }\n        }\n    }\n\n    /**\n     * Test if there is at least one window (viewer, editor...) still showing\n     */\n    private boolean hasMoreActiveFrames() {\n        Frame[] frames = Frame.getFrames();\n        for (Frame frame : frames) {\n            if (frame.isShowing()) {\n                log.debug(\"found active frame\");\n                return true;\n            }\n        }\n        return false;\n    }\n\n    @Override\n    public void windowIconified(WindowEvent e) {\n    }\n\n    @Override\n    public void windowDeiconified(WindowEvent e) {\n    }\n\n    @Override\n    public void windowOpened(WindowEvent e) {\n    }\n\n\n    /**\n     * Listens to certain configuration variables.\n     */\n    @Override\n    public void configurationChanged(ConfigurationEvent event) {\n    \tString var = event.getVariable();\n\n    \t// /!\\ font.size is set after font.family in AppearancePrefPanel\n    \t// that's why we only listen to this one in order not to change Font twice\n    \tif (TcPreferences.LOOK_AND_FEEL.equals(var)) {\n    \t\tString lnfName = event.getValue();\n\n    \t\tif (!UIManager.getLookAndFeel().getClass().getName().equals(lnfName)) {\n                setLookAndFeel(lnfName);\n            }\n    \t}\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/commandbar/CommandBar.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.commandbar;\r\n\r\nimport com.mucommander.desktop.DesktopManager;\r\nimport com.mucommander.ui.action.ActionManager;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.awt.event.KeyListener;\r\nimport java.awt.event.MouseEvent;\r\nimport java.awt.event.MouseListener;\r\n\r\n/**\r\n * CommandBar is the button bar that sits at the bottom of the main window and provides access to\r\n * main commander actions (view, edit, copy, move...).\r\n *\r\n * @author Maxence Bernard, Arik Hadas\r\n */\r\npublic class CommandBar extends JPanel implements KeyListener, MouseListener, CommandBarAttributesListener {\r\n\r\n    /** Parent MainFrame instance */\r\n    private final MainFrame mainFrame;\r\n\r\n    /** True when modifier key is pressed */\r\n    private boolean modifierDown;\r\n\r\n    /** Command bar buttons */\r\n    private CommandBarButton[] buttons;\r\n    \r\n    /** Command bar actions */\r\n    private static String[] actionIds;\r\n    \r\n    /** Command bar alternate actions */\r\n    private static String[] alternateActionIds;\r\n    \r\n    /** Modifier key that triggers the display of alternate actions when pressed */\r\n    private static KeyStroke modifier;\r\n\r\n    /**\r\n     * Creates a new CommandBar instance associated with the given MainFrame.\r\n     */\r\n    public CommandBar(MainFrame mainFrame) {\r\n        this.mainFrame = mainFrame;\r\n\r\n        // Listen to modifier key events to display alternate actions\r\n        mainFrame.getLeftPanel().getFileTable().addKeyListener(this);\r\n        mainFrame.getRightPanel().getFileTable().addKeyListener(this);\r\n\r\n        // Listen to mouse events to popup a menu when command bar is right clicked\r\n        addMouseListener(this);\r\n\r\n        actionIds = CommandBarAttributes.getActions();\r\n\t\talternateActionIds = CommandBarAttributes.getAlternateActions();\r\n        modifier = CommandBarAttributes.getModifier();\r\n        \r\n        addButtons();\r\n        \r\n        CommandBarAttributes.addCommandBarAttributesListener(this);\r\n    }\r\n    \r\n    /**\r\n     * Add buttons and separators to the command-bar panel according to the actions array.\r\n     * \r\n     * actions array must be initialized before this function is called.\r\n     */\r\n    private void addButtons() {\r\n    \tsetLayout(new GridLayout(0, actionIds.length));\r\n    \t\r\n    \t// create buttons and add them to this command bar\r\n        int nbButtons = actionIds.length;\r\n        buttons = new CommandBarButton[nbButtons];\r\n        for (int i = 0; i < nbButtons; i++) {\r\n        \tbuttons[i] = CommandBarButton.create(actionIds[i], mainFrame);\r\n            if (buttons[i] != null) {\r\n                buttons[i].addMouseListener(this);\r\n                add(buttons[i]);\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Displays/hides alternate actions: buttons that have an alternate action show it when the command bar's\r\n     * modifier is pressed (Shift by default).\r\n     */\r\n    public void setAlternateActionsMode(boolean on) {\r\n        // Do nothing if command bar is not currently visible\r\n        if (!isVisible()) {\r\n            return;\r\n        }\r\n\r\n        if (this.modifierDown != on) {\r\n            this.modifierDown = on;\r\n\r\n            int nbButtons = buttons.length;\r\n            for (int i=0; i<nbButtons; i++) {\r\n                if (buttons[i] != null) {\r\n                    buttons[i].setButtonAction(\r\n                            on && alternateActionIds[i] != null\r\n                                    ? alternateActionIds[i]\r\n                                    : actionIds[i],\r\n                            mainFrame);\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    /////////////////////////\r\n    // KeyListener methods //\r\n    /////////////////////////\r\n\r\n    public void keyPressed(KeyEvent e) {\r\n        // Display alternate actions when the modifier key is pressed\r\n        if (e.getKeyCode() == modifier.getKeyCode()) {\r\n            setAlternateActionsMode(true);\r\n    }\r\n    }\r\n\r\n    public void keyReleased(KeyEvent e) {\r\n        // Display regular actions when the modifier key is released\r\n        if (e.getKeyCode() == modifier.getKeyCode()) {\r\n            setAlternateActionsMode(false);\r\n    }\r\n    }\r\n\r\n    public void keyTyped(KeyEvent e) {\r\n    }\r\n\r\n    ///////////////////////////\r\n    // MouseListener methods //\r\n    ///////////////////////////\r\n\r\n    public void mouseClicked(MouseEvent e) {\r\n        // Right clicking on the toolbar brings up a popup menu\r\n        if (DesktopManager.isRightMouseButton(e)) {\r\n            //\t\tif (e.isPopupTrigger()) {\t// Doesn't work under Mac OS X (CTRL+click doesn't return true)\r\n            JPopupMenu popupMenu = new JPopupMenu();\r\n            popupMenu.add(ActionManager.getActionInstance(com.mucommander.ui.action.impl.ToggleCommandBarAction.Descriptor.ACTION_ID, mainFrame));\r\n            popupMenu.add(ActionManager.getActionInstance(com.mucommander.ui.action.impl.CustomizeCommandBarAction.Descriptor.ACTION_ID, mainFrame));\r\n\t\t\t// Get the click location in  the CommandBar's coordinate system. \r\n\t\t\t// The location returned by the MouseEvent is in the source component (button) coordinate system. it's converted using SwingUtilities to the CommandBar's coordinate system.\r\n\t\t\tPoint clickLocation = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), this);\r\n            popupMenu.show(this, clickLocation.x, clickLocation.y);\r\n            popupMenu.setVisible(true);\r\n        }\r\n    }\r\n\r\n    public void mouseReleased(MouseEvent e) {\r\n    }\r\n\r\n    public void mousePressed(MouseEvent e) {\r\n    }\r\n\r\n    public void mouseEntered(MouseEvent e) {\r\n    }\r\n\r\n    public void mouseExited(MouseEvent e) {\r\n    }\r\n\r\n    \r\n    //////////////////////////////////////////\r\n    // CommandBarAttributesListener methods //\r\n    //////////////////////////////////////////\r\n    \r\n    public void commandBarAttributeChanged() {\r\n\t\tactionIds = CommandBarAttributes.getActions();\r\n\t\talternateActionIds = CommandBarAttributes.getAlternateActions();\r\n\t\tmodifier = CommandBarAttributes.getModifier();\r\n\t\tremoveAll();\r\n\t\taddButtons();\r\n\t\tdoLayout();\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/commandbar/CommandBarAttributes.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.commandbar;\r\n\r\nimport com.mucommander.ui.action.ActionKeymap;\r\nimport com.mucommander.ui.action.impl.*;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.Arrays;\r\nimport java.util.WeakHashMap;\r\n\r\n/**\r\n * This class is responsible to handle the attributes of CommandBars - their actions, alternate actions and modifier.\r\n * Every CommandBar should get its attributes from this class, and register in it for receiving attributes modifications.  \r\n * \r\n * @author Arik Hadas\r\n */\r\npublic class CommandBarAttributes {\r\n\r\n\t/** Command bar actions */\r\n    private static String[] actionIds;\r\n    /** Command bar alternate actions */\r\n    private static String[] alternateActionIds;\r\n    /** Modifier key that triggers the display of alternate actions when pressed */\r\n    private static KeyStroke modifier;\r\n    \r\n    /** Command bar default actions */\r\n    private static final String[] DEFAULT_ACTION_IDS =\r\n    {\r\n        TerminalAction.Descriptor.ACTION_ID,\r\n    \tViewAction.Descriptor.ACTION_ID,\r\n    \tEditAction.Descriptor.ACTION_ID,\r\n    \tCopyAction.Descriptor.ACTION_ID,\r\n    \tMoveAction.Descriptor.ACTION_ID,\r\n    \tMkdirAction.Descriptor.ACTION_ID,\r\n    \tDeleteAction.Descriptor.ACTION_ID,\r\n    \tRefreshAction.Descriptor.ACTION_ID,\r\n    \tCloseWindowAction.Descriptor.ACTION_ID\r\n    };\r\n    /** Command bar default alternate actions */\r\n    private static final String[] DEFAULT_ALTERNATE_ACTION_IDS =\r\n    {\r\n        TerminalPanelAction.Descriptor.ACTION_ID,\r\n        ViewAsAction.Descriptor.ACTION_ID,\r\n        EditAsAction.Descriptor.ACTION_ID,\r\n    \tLocalCopyAction.Descriptor.ACTION_ID,\r\n    \tRenameAction.Descriptor.ACTION_ID,\r\n    \tMkfileAction.Descriptor.ACTION_ID,\r\n    \tPermanentDeleteAction.Descriptor.ACTION_ID,\r\n    \tnull,\r\n    \tnull\r\n    };\r\n    /** Default modifier key that triggers the display of alternate actions when pressed */\r\n    private static final KeyStroke DEFAULT_MODIFIER = KeyStroke.getKeyStroke(KeyEvent.VK_SHIFT, 0);\r\n    \r\n    /** Contains all registered command-bar attributes listeners, stored as weak references */\r\n    private static final WeakHashMap<CommandBarAttributesListener, ?> listeners = new WeakHashMap<>();\r\n\r\n    /**\r\n     * This method restore the default command-bar attributes.\r\n     * The attributes are updated only if they are not already equal to the default attributes.\r\n     */\r\n    static void restoreDefault() {\r\n        String[] defaultActions = null;\r\n        String[] alternateActions = null;\r\n        for (int i = 0; i < DEFAULT_ACTION_IDS.length; i++) {\r\n            String id1 = DEFAULT_ACTION_IDS[i];\r\n            KeyStroke key1 = ActionKeymap.getAccelerator(id1);\r\n\r\n            if (key1 != null && key1.getModifiers() != 0) {\r\n                String id2 = DEFAULT_ALTERNATE_ACTION_IDS[i];\r\n                KeyStroke key2 = ActionKeymap.getAccelerator(id2);\r\n                if (key2 == null || key2.getModifiers() == 0) {\r\n                    // swap keys\r\n                    if (defaultActions == null) {\r\n                        defaultActions = Arrays.copyOf(DEFAULT_ACTION_IDS, DEFAULT_ACTION_IDS.length);\r\n                        alternateActions = Arrays.copyOf(DEFAULT_ALTERNATE_ACTION_IDS, DEFAULT_ALTERNATE_ACTION_IDS.length);\r\n                    }\r\n                    defaultActions[i] = id2;\r\n                    alternateActions[i] = id1;\r\n                }\r\n            }\r\n        }\r\n        if (defaultActions == null) {\r\n            defaultActions = DEFAULT_ACTION_IDS;\r\n        }\r\n        if (alternateActions == null) {\r\n            alternateActions = DEFAULT_ALTERNATE_ACTION_IDS;\r\n        }\r\n    \tsetAttributes(defaultActions, alternateActions, DEFAULT_MODIFIER);\r\n    }\r\n    \r\n    /**\r\n     * @return true if command-bar attributes equal to the default attributes.\r\n     */\r\n    static boolean areDefaultAttributes() {\r\n    \tif (actionIds != DEFAULT_ACTION_IDS) {\r\n    \t\tint nbActions = actionIds.length;\r\n    \t\t\r\n    \t\tif (nbActions != DEFAULT_ACTION_IDS.length)\r\n    \t\t\treturn false;\r\n    \t\t\r\n    \t\tfor (int i=0; i<nbActions; ++i) {\r\n                if (!equals(actionIds[i], DEFAULT_ACTION_IDS[i])) {\r\n                    return false;\r\n                }\r\n            }\r\n    \t}\r\n    \t\r\n    \tif (alternateActionIds != DEFAULT_ALTERNATE_ACTION_IDS) {\r\n    \t\tint nbAlternateActions = alternateActionIds.length;\r\n    \t\t\r\n    \t\tif (nbAlternateActions != DEFAULT_ALTERNATE_ACTION_IDS.length)\r\n    \t\t\treturn false;\r\n    \t\t\r\n    \t\tfor (int i=0; i<nbAlternateActions; ++i)\r\n    \t\t\tif (!equals(alternateActionIds[i], DEFAULT_ALTERNATE_ACTION_IDS[i]))\r\n    \t\t\t\treturn false;\r\n    \t}\r\n    \t\r\n    \treturn DEFAULT_MODIFIER == modifier || DEFAULT_MODIFIER.equals(modifier);\r\n    }\r\n    \r\n    private static boolean equals(Object action1, Object action2) {\r\n    \tif (action1 == null) {\r\n    \t\treturn action2 == null;\r\n        }\r\n    \treturn action1.equals(action2);\r\n    }\r\n\r\n    /**\r\n     * This method sets command bar actions and modifiers.\r\n     * \r\n     * @param actionIds          standard command-bar actions.\r\n     * @param alternateActionIds alternate command-bar actions.\r\n     * @param modifier           command-bar modifier.\r\n     */\r\n    public static void setAttributes(String[] actionIds, String[] alternateActionIds, KeyStroke modifier) {\r\n    \tCommandBarAttributes.actionIds = actionIds;\r\n    \tCommandBarAttributes.alternateActionIds = alternateActionIds;\r\n    \tCommandBarAttributes.modifier = modifier;\r\n    \tfireAttributesChanged();\r\n    }\r\n    \r\n\r\n    public static String[] getActions() {\r\n        return actionIds;\r\n    }\r\n    \r\n    public static String[] getAlternateActions() {\r\n        return alternateActionIds;\r\n    }\r\n    \r\n    public static KeyStroke getModifier() {\r\n        return modifier;\r\n    }\r\n    \r\n    \r\n    // - Listeners -------------------------------------------------------------\r\n    // -------------------------------------------------------------------------\r\n    static void addCommandBarAttributesListener(CommandBarAttributesListener listener) {\r\n    \tsynchronized(listeners) {listeners.put(listener, null);}\r\n    }\r\n    \r\n    public static void removeCommandBarAttributesListener(CommandBarAttributesListener listener) {\r\n    \tsynchronized(listeners) {\r\n    \t    listeners.remove(listener);\r\n    \t}\r\n    }\r\n    \r\n    private static void fireAttributesChanged() {\r\n    \tsynchronized(listeners) {\r\n            // Iterate on all listeners\r\n            for (CommandBarAttributesListener listener : listeners.keySet()) {\r\n                listener.commandBarAttributeChanged();\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/commandbar/CommandBarAttributesListener.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.commandbar;\r\n\r\n/**\r\n * This is an interface that each class that should listen to CommandBar's attributes modifications need to implement.\r\n * \r\n * @author Arik Hadas\r\n */\r\npublic interface CommandBarAttributesListener {\r\n\t/**\r\n     * This method is invoked when command-bar's actions\\alternate actions\\modifier have been modified.\r\n     */\r\n    void commandBarAttributeChanged();\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/commandbar/CommandBarButton.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.commandbar;\r\n\r\nimport java.awt.Dimension;\r\nimport java.awt.Insets;\r\n\r\nimport com.mucommander.commons.conf.ConfigurationEvent;\r\nimport com.mucommander.commons.conf.ConfigurationListener;\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.commons.runtime.OsVersion;\r\nimport com.mucommander.conf.TcConfigurations;\r\nimport com.mucommander.conf.TcPreference;\r\nimport com.mucommander.conf.TcPreferences;\r\nimport com.mucommander.ui.action.ActionManager;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.button.NonFocusableButton;\r\nimport com.mucommander.ui.icon.IconManager;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport ru.trolsoft.macosx.RetinaImageIcon;\r\n\r\n/**\r\n * Button that located in command-bar.\r\n * Button in command-bar are associated with an MuAction and could have an alternative action,\r\n * i.e an action that would replace the regular button's action when a modifier key is pressed.\r\n * \r\n * @author Arik Hadas\r\n */\r\npublic class CommandBarButton extends NonFocusableButton implements ConfigurationListener {\r\n\r\n\t/** ID of the button's action */\r\n\tprivate final String actionId;\r\n\t\r\n\t/** Current icon scale factor */\r\n    // The math.max(1.0f, ...) part is to workaround a bug which cause(d) this value to be set to 0.0 in the configuration file.\r\n    static float scaleFactor = Math.max(1.0f, TcConfigurations.getPreferences().getVariable(TcPreference.COMMAND_BAR_ICON_SCALE,\r\n                                                                        TcPreferences.DEFAULT_COMMAND_BAR_ICON_SCALE));\r\n\t\r\n    public static CommandBarButton create(String actionId, MainFrame mainFrame) {\r\n\t\treturn actionId == null ? null : new CommandBarButton(actionId, mainFrame);\r\n\t}\r\n\t\r\n\tCommandBarButton(String actionId, MainFrame mainFrame) {\r\n\t\t\r\n\t\t// Use new JButton decorations introduced in Mac OS X 10.5 (Leopard)\r\n        if (OsFamily.MAC_OS_X.isCurrent() && OsVersion.MAC_OS_X_10_5.isCurrentOrHigher()) {\r\n            //putClientProperty(\"JComponent.sizeVariant\", \"small\");\r\n            //putClientProperty(\"JButton.buttonType\", \"textured\");\r\n        } else {\r\n            setMargin(new Insets(3,4,3,4));\r\n        }\r\n        \r\n        this.actionId = actionId;\r\n        \r\n        setButtonAction(actionId, mainFrame);\r\n        \r\n        // For Mac OS X whose default minimum width for buttons is enormous\r\n        setMinimumSize(new Dimension(40, (int) getPreferredSize().getHeight()));\r\n        \r\n        // Listen to configuration changes to reload command bar buttons when icon size has changed\r\n        TcConfigurations.addPreferencesListener(this);\r\n\t}\r\n\t\r\n\t/**\r\n     * Sets the given button's action, custom label showing the accelerator and icon taking into account the scale factor.\r\n     */\r\n\tpublic void setButtonAction(String actionId, MainFrame mainFrame) {\r\n    \tTcAction action = ActionManager.getActionInstance(actionId, mainFrame);\r\n    \t\r\n    \tsetAction(action);\r\n    \t\r\n        // Append the action's shortcut to the button's label\r\n        String label;\r\n        label = action.getLabel();\r\n        if (action.getAcceleratorText() != null) {\r\n            label += \" [\" + action.getAcceleratorText() + ']';\r\n        }\r\n        setText(label);\r\n\r\n        // Scale icon if scale factor is different from 1.0\r\n        if (scaleFactor != 1.0f) {\r\n            setIcon(IconManager.getScaledIcon(action.getIcon(), scaleFactor));\r\n        }\r\n        if (RetinaImageIcon.IS_RETINA && getIcon() instanceof RetinaImageIcon) {\r\n            setDisabledIcon(((RetinaImageIcon) getIcon()).buildDisabledIcon());\r\n            setPressedIcon(getIcon());\r\n        }\r\n\r\n    }\r\n\t\r\n    /**\r\n     * Return the id of the button's action\r\n     * \r\n     * @return id of the button's action\r\n     */\r\n    public String getActionId() {\r\n    \treturn actionId;\r\n    }\r\n    \r\n    ///////////////////////////////////\r\n    // ConfigurationListener methods //\r\n    ///////////////////////////////////\r\n\r\n    /**\r\n     * Listens to certain configuration variables.\r\n     */\r\n    public void configurationChanged(ConfigurationEvent event) {\r\n        String var = event.getVariable();\r\n\r\n        // Reload butons icon if the icon scale factor has changed\r\n        if (var.equals(TcPreferences.COMMAND_BAR_ICON_SCALE)) {\r\n            scaleFactor = event.getFloatValue();\r\n\r\n            // Change the button's icon but NOT the action's icon which has to remain in its original non-scaled size\r\n            setIcon(IconManager.getScaledIcon(((TcAction) getAction()).getIcon(), scaleFactor));\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/commandbar/CommandBarButtonForDisplay.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.commandbar;\n\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.icon.IconManager;\nimport com.mucommander.ui.main.MainFrame;\n\nimport java.awt.*;\n\n/**\n * CommandBarButton that used for display purpose only\n * \n * @author Arik Hadas\n */\npublic class CommandBarButtonForDisplay extends CommandBarButton {\n\t\n\t/** The preferred size of display button */\n\tpublic static final Dimension PREFERRED_SIZE = new Dimension(130, 30);\n\t\n\tpublic static CommandBarButtonForDisplay create(String actionId) {\n\t\treturn actionId == null ? null : new CommandBarButtonForDisplay(actionId);\n\t}\n\t\n\tprivate CommandBarButtonForDisplay(String actionId) {\n\t\tsuper(actionId, null);\n\t\tsetEnabled(true);\n\t\tsetPreferredSize(PREFERRED_SIZE);\n\t}\n\t\n\t@Override\n    public void setButtonAction(String actionId, MainFrame mainFrame) {\n        // Use the action's label as the button's label\n        String label = ActionProperties.getActionLabel(actionId);\n        setText(label);\n\n        // Set the button's tooltip to the action's tooltip if it has one,\n        // to the action's label otherwise (the label may be too long for being displayed fully)\n        String tooltipText = ActionProperties.getActionTooltip(actionId);\n        setToolTipText(tooltipText==null?label:tooltipText);\n\n        setIcon(IconManager.getScaledIcon(ActionProperties.getActionIcon(actionId), scaleFactor));\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/commandbar/CommandBarIO.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.commandbar;\r\n\r\nimport java.io.File;\r\nimport java.io.FileNotFoundException;\r\nimport java.io.IOException;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.xml.sax.helpers.DefaultHandler;\r\n\r\nimport com.mucommander.PlatformManager;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileFactory;\r\n\r\n/**\r\n * This class contains the common things for reading and writing the command-bar actions and modifier.\r\n * \r\n * @author Arik Hadas\r\n */\r\npublic abstract class CommandBarIO extends DefaultHandler {\r\n\tprivate static Logger logger;\r\n\t\r\n\t/* Variables used for XML parsing */\r\n\t/** Root element */\r\n\tprotected static final String ROOT_ELEMENT = \"command_bar\";\r\n\t/** Attribute containing the last muCommander version that was used to create the file */\r\n\tprotected static final String VERSION_ATTRIBUTE  = \"version\";\r\n\tprotected static final String MODIFIER_ATTRIBUTE = \"modifier\";\r\n    /** Element describing one of the button in the list */\r\n\tprotected static final String BUTTON_ELEMENT = \"button\";\r\n    /** Attribute containing the action class associated with the button */\r\n\tprotected static final String ACTION_ATTRIBUTE = \"action\";\r\n\t/** Attribute containing the alternative action class associated with the button */\r\n\tprotected static final String ALT_ACTION_ATTRIBUTE = \"alt_action\";\r\n\t/** Attribute containing the action id associated with the button */\r\n\tprotected static final String ACTION_ID_ATTRIBUTE = \"action_id\";\r\n\t/** Attribute containing the alternative action id associated with the button */\r\n\tprotected static final String ALT_ACTION_ID_ATTRIBUTE = \"alt_action_id\";\r\n \r\n\t/** Default command bar descriptor filename */\r\n\tprotected final static String DEFAULT_COMMAND_BAR_FILE_NAME = \"command_bar.xml\";\r\n\r\n    /** Path to the command bar descriptor resource file within the application JAR file */\r\n\tprotected final static String COMMAND_BAR_RESOURCE_PATH = \"/\" + DEFAULT_COMMAND_BAR_FILE_NAME;\r\n\r\n    /** Command bar descriptor file used when calling {@link #loadCommandBar()} */\r\n\tprotected static AbstractFile commandBarFile;\r\n\t\r\n\t/** CommandBarWriter instance */\r\n\tprivate static CommandBarWriter commandBarWriter;\r\n\t\r\n\t/** Whether the command-bar has been modified and should be saved */\r\n    protected static boolean wasCommandBarModified;\r\n\t\r\n\t/**\r\n     * Parses the XML file describing the command bar's buttons and associated actions.\r\n     * If the file doesn't exist yet, it is copied from the default resource file within the JAR.\r\n     *\r\n     * This method must be called before instantiating CommandBar for the first time.\r\n     */\r\n    public static void loadCommandBar() throws Exception {\r\n    \t// Load user's file if exist\r\n    \tAbstractFile commandBarFile = getDescriptionFile();\r\n    \tif (commandBarFile != null && commandBarFile.exists()) {\r\n    \t\tCommandBarReader reader = new CommandBarReader(commandBarFile);\r\n    \t\tCommandBarAttributes.setAttributes(reader.getActionsRead(), reader.getAlternateActionsRead(), reader.getModifierRead());\r\n    \t} else {\r\n    \t\tCommandBarAttributes.restoreDefault();\r\n    \t\tgetLogger().debug(DEFAULT_COMMAND_BAR_FILE_NAME + \" was not found, using defaults\");\r\n    \t}\r\n    \t\r\n    \t// initialize the writer after setting the command-bar initial attributes:\r\n    \tcommandBarWriter = CommandBarWriter.create();\r\n    }\r\n    \r\n    /**\r\n     * Mark that actions were modified and therefore should be saved.\r\n     */\r\n    public static void setModified() {\r\n\t\twasCommandBarModified = true;\r\n\t}\r\n    \r\n    /**\r\n     * Writes the current command bar to the user's command bar file.\r\n     * @throws IOException \r\n     * @throws IOException\r\n     */\r\n    public static void saveCommandBar() throws IOException {\r\n    \tif (CommandBarAttributes.areDefaultAttributes()) {\r\n    \t\tAbstractFile commandBarFile = getDescriptionFile();\r\n        \tif(commandBarFile != null && commandBarFile.exists()) {\r\n\t\t\t\tgetLogger().info(\"Command bar use default settings, removing descriptor file\");\r\n        \t\tcommandBarFile.delete();\r\n        \t} else {\r\n\t\t\t\tgetLogger().debug(\"Command bar not modified, not saving\");\r\n\t\t\t}\r\n    \t} else if (commandBarWriter != null) {\r\n    \t\tif (wasCommandBarModified) {\r\n\t\t\t\tcommandBarWriter.write();\r\n\t\t\t} else {\r\n\t\t\t\tgetLogger().debug(\"Command bar not modified, not saving\");\r\n\t\t\t}\r\n    \t} else {\r\n\t\t\tgetLogger().warn(\"Could not save command bar. writer is null\");\r\n\t\t}\r\n    }\r\n\t\r\n\t/**\r\n     * Sets the path to the command bar description file to be loaded when calling {@link #loadCommandBar()}.\r\n     * By default, this file is {@link #DEFAULT_COMMAND_BAR_FILE_NAME} within the preferences folder.\r\n     * @param  path                  path to the command bar descriptor file\r\n     * @throws FileNotFoundException if the specified file is not accessible.\r\n     */\r\n    public static void setDescriptionFile(String path) throws FileNotFoundException {\r\n        AbstractFile file  = FileFactory.getFile(path);\r\n\r\n        if (file == null) {\r\n\t\t\tsetDescriptionFile(new File(path));\r\n\t\t} else {\r\n\t\t\tsetDescriptionFile(file);\r\n\t\t}\r\n    }\r\n\r\n    /**\r\n     * Sets the path to the command bar description file to be loaded when calling {@link #loadCommandBar()}.\r\n     * By default, this file is {@link #DEFAULT_COMMAND_BAR_FILE_NAME} within the preferences folder.\r\n     * @param  file                  path to the command bar descriptor file\r\n     * @throws FileNotFoundException if the specified file is not accessible.\r\n     */\r\n    public static void setDescriptionFile(File file) throws FileNotFoundException {\r\n\t\tsetDescriptionFile(FileFactory.getFile(file.getAbsolutePath()));\r\n\t}\r\n\r\n    /**\r\n     * Sets the path to the command bar description file to be loaded when calling {@link #loadCommandBar()}.\r\n     * By default, this file is {@link #DEFAULT_COMMAND_BAR_FILE_NAME} within the preferences folder.\r\n     * @param  file                  path to the command bar descriptor file\r\n     * @throws FileNotFoundException if the specified file is not accessible.\r\n     */\r\n    public static void setDescriptionFile(AbstractFile file) throws FileNotFoundException {\r\n        // Makes sure file can be used as a commandbar description file.\r\n        if (file.isBrowsable()) {\r\n\t\t\tthrow new FileNotFoundException(\"Not a valid file: \" + file.getAbsolutePath());\r\n\t\t}\r\n        commandBarFile = file;\r\n    }\r\n\r\n    public static AbstractFile getDescriptionFile() throws IOException {\r\n        if (commandBarFile == null) {\r\n\t\t\treturn PlatformManager.getPreferencesFolder().getChild(DEFAULT_COMMAND_BAR_FILE_NAME);\r\n\t\t}\r\n        return commandBarFile;\r\n    }\r\n\r\n\tprivate static Logger getLogger() {\r\n\t\tif (logger == null) {\r\n\t\t\tlogger = LoggerFactory.getLogger(CommandBarIO.class);\r\n\t\t}\r\n\t\treturn logger;\r\n\t}\r\n}\r\n\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/commandbar/CommandBarReader.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.commandbar;\r\n\r\nimport com.mucommander.RuntimeConstants;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.io.backup.BackupInputStream;\r\nimport com.mucommander.ui.action.ActionManager;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.xml.sax.Attributes;\r\nimport org.xml.sax.SAXException;\r\n\r\nimport javax.swing.KeyStroke;\r\nimport javax.xml.parsers.ParserConfigurationException;\r\nimport javax.xml.parsers.SAXParserFactory;\r\nimport java.io.IOException;\r\nimport java.io.InputStream;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\n/**\r\n * This class parses the XML file describing the command bar's buttons and associated actions.\r\n *\r\n * @author Maxence Bernard, Arik Hadas\r\n */\r\nclass CommandBarReader extends CommandBarIO {\r\n\tprivate static Logger logger;\r\n\t\r\n    /** Temporarily used for XML parsing */\r\n    private List<String> actionsIdsV;\r\n    /** Temporarily used for XML parsing */\r\n    private List<String> alternateActionsIdsV;\r\n    /** Temporarily used for XML parsing */\r\n    private KeyStroke modifier;\r\n\r\n    /** Parsed file */\r\n    private final AbstractFile file;\r\n\r\n    /**\r\n     * Starts parsing the XML description file.\r\n     * \r\n     * @throws SAXException\r\n     * @throws IOException\r\n     * @throws ParserConfigurationException\r\n     */\r\n    CommandBarReader(AbstractFile file) throws SAXException, IOException, ParserConfigurationException {\r\n    \tthis.file = file;\r\n\r\n        try (InputStream in = new BackupInputStream(file)) {\r\n            SAXParserFactory.newInstance().newSAXParser().parse(in, this);\r\n        }\r\n    }\r\n    \r\n    \r\n    String[] getActionsRead() {\r\n    \tint nbActions = actionsIdsV.size();\r\n    \tString[] actionIds = new String[nbActions];\r\n        actionsIdsV.toArray(actionIds);\r\n        return actionIds;\r\n    }\r\n    \r\n    String[] getAlternateActionsRead() {\r\n    \tint nbActions = alternateActionsIdsV.size();\r\n    \tString[] alternateActionIds = new String[nbActions];\r\n        alternateActionsIdsV.toArray(alternateActionIds);\r\n        return alternateActionIds;\r\n    }\r\n    \r\n    KeyStroke getModifierRead() {\r\n    \treturn modifier;\r\n    }\r\n    \r\n    ////////////////////////////\r\n    // ContentHandler methods //\r\n    ////////////////////////////\r\n\r\n    @Override\r\n    public void startDocument() {\r\n        getLogger().trace(file.getAbsolutePath()+\" parsing started\");\r\n\r\n        actionsIdsV = new ArrayList<>();\r\n        alternateActionsIdsV = new ArrayList<>();\r\n        modifier = null;\r\n    }\r\n\r\n    @Override\r\n    public void endDocument() {\r\n        getLogger().trace(file.getAbsolutePath()+\" parsing finished\");\r\n    }\r\n\r\n    @Override\r\n    public void startElement(String uri, String localName, String qName, Attributes attributes) {\r\n        if(qName.equals(BUTTON_ELEMENT)) {\r\n        \t// Resolve action id\r\n        \tString actionIdAttribute = attributes.getValue(ACTION_ID_ATTRIBUTE);\r\n        \tif (actionIdAttribute != null) {\r\n        \t\tif (ActionManager.isActionExist(actionIdAttribute)) {\r\n        \t\t\tactionsIdsV.add(actionIdAttribute);\r\n\r\n        \t\t\t// Resolve alternate action id (if any)\r\n        \t\t\tactionIdAttribute = attributes.getValue(ALT_ACTION_ID_ATTRIBUTE);\r\n        \t\t\talternateActionsIdsV.add(ActionManager.isActionExist(actionIdAttribute) ? actionIdAttribute : null);\r\n        \t\t}\r\n        \t}\r\n        \telse {\r\n        \t\t// Resolve action class\r\n        \t\tString actionClassAttribute = attributes.getValue(ACTION_ATTRIBUTE);\r\n        \t\tif (actionClassAttribute != null) {\r\n        \t\t\tString actionId = ActionManager.extrapolateId(actionClassAttribute);\r\n        \t\t\tif (ActionManager.isActionExist(actionId)) {\r\n        \t\t\t\tactionsIdsV.add(actionId);\r\n\r\n        \t\t\t\t// Resolve alternate action class (if any)\r\n        \t\t\t\tactionClassAttribute = attributes.getValue(ALT_ACTION_ATTRIBUTE);\r\n        \t\t\t\tif(actionClassAttribute == null)\r\n        \t\t\t\t\talternateActionsIdsV.add(null);\r\n        \t\t\t\telse {\r\n        \t\t\t\t\tactionId = ActionManager.extrapolateId(actionClassAttribute);\r\n        \t\t\t\t\tif (ActionManager.isActionExist(actionId))\r\n        \t\t\t\t\t\talternateActionsIdsV.add(actionId);\r\n        \t\t\t\t\telse {\r\n        \t\t\t\t\t\tgetLogger().warn(\"Error in \"+DEFAULT_COMMAND_BAR_FILE_NAME+\": action id for \" + actionClassAttribute + \" not found\");\r\n        \t\t\t\t\t\talternateActionsIdsV.add(null);\r\n        \t\t\t\t\t}\r\n        \t\t\t\t}\r\n        \t\t\t}\r\n        \t\t\telse\r\n                        getLogger().warn(\"Error in \"+DEFAULT_COMMAND_BAR_FILE_NAME+\": action id for \" + actionClassAttribute + \" not found\");\r\n        \t\t}\r\n        \t}\r\n        }\r\n        else if(qName.equals(ROOT_ELEMENT)) {\r\n        \t// Retrieve modifier key (shift by default)\r\n        \tmodifier = KeyStroke.getKeyStroke(attributes.getValue(MODIFIER_ATTRIBUTE));\r\n            \r\n        \t// Note: early 0.8 beta3 nightly builds did not have version attribute, so the attribute may be null\r\n            String fileVersion = attributes.getValue(VERSION_ATTRIBUTE);\r\n            \r\n            // if the file's version is not up-to-date, update the file to the current version at quitting.\r\n    \t\tif (!RuntimeConstants.VERSION.equals(fileVersion))\r\n    \t\t\tsetModified();\r\n        }\r\n    }\r\n\r\n    private static Logger getLogger() {\r\n        if (logger == null) {\r\n            logger = LoggerFactory.getLogger(CommandBarReader.class);\r\n        }\r\n        return logger;\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/commandbar/CommandBarWriter.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.commandbar;\r\n\r\nimport java.io.IOException;\r\nimport java.io.OutputStream;\r\n\r\nimport javax.swing.KeyStroke;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport com.mucommander.RuntimeConstants;\r\nimport com.mucommander.io.backup.BackupOutputStream;\r\nimport com.mucommander.ui.text.KeyStrokeUtils;\r\nimport com.mucommander.utils.xml.XmlAttributes;\r\nimport com.mucommander.utils.xml.XmlWriter;\r\n\r\n/**\r\n * This class is responsible for writing the command-bar attributes (actions and modifier).\r\n * \r\n * @author Arik Hadas\r\n */\r\nclass CommandBarWriter extends CommandBarIO {\r\n\tprivate static Logger logger;\r\n\t\r\n\t// - Singleton -------------------------------------------------------\r\n    // -------------------------------------------------------------------\r\n\tprivate static CommandBarWriter instance;\r\n\t\r\n\tpublic static CommandBarWriter create() {\r\n\t\tif (instance == null)\r\n\t\t\tinstance = new CommandBarWriter();\r\n\t\treturn instance;\r\n\t}\r\n\t\r\n\tprivate CommandBarWriter() {}\r\n\t\r\n\tvoid write() {\r\n\t\tString[] commandBarActionIds = CommandBarAttributes.getActions();\r\n\t\tString[] commandBarAlterativeActionIds = CommandBarAttributes.getAlternateActions();\r\n\t\tKeyStroke commandBarModifier = CommandBarAttributes.getModifier();\r\n\r\n\t\ttry (BackupOutputStream bos = new BackupOutputStream(getDescriptionFile())) {\r\n\t\t\tnew Writer(bos).write(commandBarActionIds, commandBarAlterativeActionIds, commandBarModifier);\r\n\t\t\twasCommandBarModified = false;\r\n\t\t} catch (IOException e) {\r\n\t\t\tgetLogger().debug(\"Caught exception\", e);\r\n\t\t}\r\n\t}\r\n\t\r\n\tprivate static class Writer {\r\n\t\tprivate XmlWriter writer;\r\n\t\t\r\n\t\tprivate Writer(OutputStream stream) throws IOException {\r\n    \t\tthis.writer = new XmlWriter(stream);\r\n    \t}\r\n\t\t\r\n\t\tprivate void write(String[] actionIds, String[] alternativeActionIds, KeyStroke modifier) throws IOException {\r\n\t\t\ttry {\r\n\t\t\t\twriter.writeCommentLine(\"See http://trac.mucommander.com/wiki/CommandBar for information on how to customize this file\");\r\n\t\t\t\t\r\n\t\t\t\tXmlAttributes rootElementAttributes = new XmlAttributes();\r\n\t\t\t\trootElementAttributes.add(MODIFIER_ATTRIBUTE, KeyStrokeUtils.getKeyStrokeRepresentation(modifier));\r\n\t\t\t\trootElementAttributes.add(VERSION_ATTRIBUTE, RuntimeConstants.VERSION);\r\n\r\n    \t\t\twriter.startElement(ROOT_ELEMENT, rootElementAttributes, true);    \t\t\t\r\n    \t\t\t\r\n    \t\t\tint nbCommandBarActions = actionIds.length;\r\n    \t\t\tfor (int i=0; i<nbCommandBarActions; ++i)\r\n    \t\t\t\twrite(actionIds[i], alternativeActionIds[i]);\r\n\r\n    \t\t} finally {\r\n    \t\t\twriter.endElement(ROOT_ELEMENT);\r\n    \t\t}\r\n\t\t}\r\n\t\t\r\n\t\tprivate void write(String actionId, String alternativeActionId) throws IOException {\r\n\t\t\tXmlAttributes attributes = new XmlAttributes();\r\n\t\t\tattributes.add(ACTION_ID_ATTRIBUTE, actionId);\r\n\t\t\tif (alternativeActionId != null)\r\n\t\t\t\tattributes.add(ALT_ACTION_ID_ATTRIBUTE, alternativeActionId);\r\n\r\n\t\t\tgetLogger().trace(\"Writing button: action_id = \"  + attributes.getValue(ACTION_ID_ATTRIBUTE) + \", alt_action_id = \" + attributes.getValue(ALT_ACTION_ID_ATTRIBUTE));\r\n\t\t\t\r\n\t\t\twriter.writeStandAloneElement(BUTTON_ELEMENT, attributes);\r\n\t\t}\r\n\t}\r\n\r\n\tprivate static Logger getLogger() {\r\n\t\tif (logger == null) {\r\n\t\t\tlogger = LoggerFactory.getLogger(CommandBarWriter.class);\r\n\t\t}\r\n\t\treturn logger;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/frame/ClonedMainFrameBuilder.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.frame;\n\nimport java.awt.Dimension;\nimport java.awt.Frame;\nimport java.awt.Insets;\nimport java.awt.Rectangle;\nimport java.awt.Toolkit;\nimport java.awt.Window;\n\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.WindowManager;\n\n/**\n * \n * @author Arik Hadas\n */\npublic class ClonedMainFrameBuilder extends MainFrameBuilder {\n\t\n\t/** Number of pixels a new MainFrame will be moved to the left from its parent. */\n    private static final int X_OFFSET = 22;\n    /** Number of pixels a new MainFrame will be moved down from its parent. */\n    private static final int Y_OFFSET = 22;\n\n    public int getSelectedFrame() {\n    \treturn WindowManager.getMainFrames().size()-1;\n    }\n\n\t@Override\n\tpublic MainFrame[] build() {\n\t\tMainFrame currentMainFrame = WindowManager.getCurrentMainFrame();\n\t\t\n\t\tMainFrame mainFrame = new MainFrame(currentMainFrame);\n\t\t\n\t\t// If this is a cloned window, use the same dimensions as the previous MainFrame, with\n        // a slight horizontal and vertical offset to make sure we keep both of them visible.\n\t\t\n\t\tDimension screenSize   = Toolkit.getDefaultToolkit().getScreenSize();\n        var frame = currentMainFrame.getJFrame();\n\t\tint x      = frame.getX() + X_OFFSET;\n        int y      = frame.getY() + Y_OFFSET;\n        int width  = frame.getWidth();\n        int height = frame.getHeight();\n\n        // Make sure we're still within the screen.\n        // Note that while the width and height tests look redundant, they are required. Some\n        // window managers, such as Gnome, return rather peculiar results.\n        if (!isInsideUsableScreen(frame, x + width, -1)) {\n            x = 0;\n        }\n        if (!isInsideUsableScreen(frame, -1, y + height)) {\n            y = 0;\n        }\n        if (width + x > screenSize.width) {\n            width = screenSize.width - x;\n        }\n        if (height + y > screenSize.height) {\n            height = screenSize.height - y;\n        }\n        \n        mainFrame.getJFrame().setBounds(new Rectangle(x, y, width, height));\n        \n\t\treturn new MainFrame[] { mainFrame };\n\t}\n\t\n\t// - Screen handling --------------------------------------------------------\n    // --------------------------------------------------------------------------\n    /**\n     * Computes the screen's insets for the specified window and returns them.\n     * <p>\n     * While this might seem strange, screen insets can change from one window\n     * to another. For example, on X11 windowing systems, there is no guarantee that\n     * a window will be displayed on the same screen, let alone computer, as the one\n     * the application is running on.\n     *\n     * @param window the window for which screen insets should be computed.\n     * @return the screen's insets for the specified window\n     */\n    private static Insets getScreenInsets(Window window) {\n        return Toolkit.getDefaultToolkit().getScreenInsets(window.getGraphicsConfiguration());\n    }\n\t\n\t\n    /**\n     * Checks whether the specified frame can be moved to the specified coordinates and still\n     * be fully visible.\n     * <p>\n     * If <code>x</code> (resp. <code>y</code>) is <code>null</code>, this method won't test\n     * whether the frame is within horizontal (resp. vertical) bounds.\n     *\n     * @param frame frame whose visibility should be tested.\n     * @param x     horizontal coordinate of the upper-leftmost corner of the area to check for.\n     * @param y     vertical coordinate of the upper-leftmost corner of the area to check for.\n     * @return      <code>true</code> if the frame can be moved at the specified location,\n     *              <code>false</code> otherwise.\n     */\n    private static boolean isInsideUsableScreen(Frame frame, int x, int y) {\n        Insets screenInsets = getScreenInsets(frame);\n        Dimension screenSize   = Toolkit.getDefaultToolkit().getScreenSize();\n\n        return (x < 0 || (x >= screenInsets.left && x < screenSize.width - screenInsets.right))\n            && (y < 0 || (y >= screenInsets.top && y < screenSize.height - screenInsets.bottom));\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/frame/CommandLineMainFrameBuilder.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.frame;\n\nimport java.io.File;\nimport java.util.LinkedList;\nimport java.util.List;\n\nimport com.mucommander.auth.CredentialsManager;\nimport com.mucommander.auth.CredentialsMapping;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.AuthException;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.ui.dialog.auth.AuthDialog;\nimport com.mucommander.ui.main.FolderPanel.FolderPanelType;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.WindowManager;\nimport com.mucommander.ui.main.tabs.ConfFileTableTab;\n\n/**\n * \n * @author Arik Hadas\n */\npublic class CommandLineMainFrameBuilder extends MainFrameBuilder {\n\n\tprivate final List<MainFrame> mainFrames = new LinkedList<>();\n\t\n\tpublic CommandLineMainFrameBuilder(String[] folders) {\n\t\tfor (int i = 0; i < folders.length; i += 2) {\n\t\t\tmainFrames.add(new MainFrame(\n\t\t\t\t\tnew ConfFileTableTab(getInitialAbstractPaths(folders[i], FolderPanelType.LEFT)),\n\t\t\t\t\tgetFileTableConfiguration(FolderPanelType.LEFT, mainFrames.size()),\n\t\t\t\t\tnew ConfFileTableTab(getInitialAbstractPaths(i < folders.length - 1 ? folders[i + 1] : null, FolderPanelType.RIGHT)),\n\t\t\t\t\tgetFileTableConfiguration(FolderPanelType.RIGHT, mainFrames.size())));\n        }\n\t}\n\n\t@Override\n\tpublic MainFrame[] build() {\n\t\treturn mainFrames.toArray(new MainFrame[0]);\n\t}\n\t\n\t/**\n     * Returns a valid initial abstract path for the specified frame.\n     * <p>\n     * This method does its best to interpret <code>path</code> properly, or to fail\n     * politely if it can't. This means that:<br/>\n     * - we first try to see whether <code>path</code> is a legal, existing URI.<br/>\n     * - if it's not, we check whether it might be a legal local, existing file path.<br/>\n     * - if it's not, we'll just use the default initial path for the frame.<br/>\n     * - if <code>path</code> is browsable (eg directory, archive, ...), use it as is.<br/>\n     * - if it's not, use its parent.<br/>\n     * - if it does not have a parent, use the default initial path for the frame.<br/>\n     *\n     * @param  path  path to the folder we want to open in <code>frame</code>.\n     * @param  folderPanelType identifier of the panel we want to compute the path for (either {@link com.mucommander.ui.main.FolderPanel.FolderPanelType#LEFT} or\n     *               {@link #@link com.mucommander.ui.main.FolderPanel.FolderPanelType.RIGHT}).\n     * @return       our best shot at what was actually requested.\n     */\n    private FileURL getInitialAbstractPaths(String path, FolderPanelType folderPanelType) {\n        // This is one of those cases where a null value actually has a proper meaning.\n        if (path == null) {\n            return getHomeFolder().getURL();\n        }\n\n        // Tries the specified path as-is.\n        AbstractFile file;\n        CredentialsMapping newCredentialsMapping;\n\n        while(true) {\n            try {\n                file = FileFactory.getFile(path, true);\n                if (file != null && !file.exists()) {\n                    file = null;\n                }\n                break;\n            }\n            // If an AuthException occurred, gets login credential from the user.\n            catch(Exception e) {\n                if(e instanceof AuthException) {\n                    // Prompts the user for a login and password.\n                    AuthException authException = (AuthException)e;\n                    FileURL url = authException.getURL();\n                    AuthDialog authDialog = new AuthDialog(WindowManager.getCurrentMainFrame(), url, true, authException.getMessage());\n                    authDialog.showDialog();\n                    newCredentialsMapping = authDialog.getCredentialsMapping();\n                    if(newCredentialsMapping !=null) {\n                        // Use the provided credentials\n                        CredentialsManager.authenticate(url, newCredentialsMapping);\n                        path = url.toString(true);\n                    }\n                    // If the user cancels, we fall back to the default path.\n                    else {\n                        return getHomeFolder().getURL();\n                    }\n                }\n                else {\n                    file = null;\n                    break;\n                }\n            }\n        }\n\n        // If the specified path does not work out,\n        if (file == null) {\n            // Tries the specified path as a relative path.\n            if ((file = FileFactory.getFile(new File(path).getAbsolutePath())) == null || !file.exists()) {\n                // Defaults to home.\n                return getHomeFolder().getURL();\n            }\n        }\n\n        // If the specified path is a non-browsable, uses its parent.\n        if (!file.isBrowsable()) {\n            // This is just playing things safe, as I doubt there might ever be a case of\n            // a file without a parent directory.\n            if ((file = file.getParent()) == null) {\n                return getHomeFolder().getURL();\n            }\n        }\n\n        return file.getURL();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/frame/DefaultMainFramesBuilder.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.frame;\n\nimport java.awt.Dimension;\nimport java.awt.Rectangle;\nimport java.awt.Toolkit;\nimport java.net.MalformedURLException;\n\nimport com.mucommander.commons.conf.Configuration;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.conf.TcSnapshot;\nimport com.mucommander.ui.main.FolderPanel.FolderPanelType;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.tabs.ConfFileTableTab;\n\n/**\n * \n * @author Arik Hadas\n */\npublic class DefaultMainFramesBuilder extends MainFrameBuilder {\n\t//private static final Logger LOGGER = LoggerFactory.getLogger(DefaultMainFramesBuilder.class);\n\t\n\tprivate final Configuration snapshot = TcConfigurations.getSnapshot();\n\t\n\tpublic DefaultMainFramesBuilder() { }\n\n\t@Override\n\tpublic int getSelectedFrame() {\n\t\treturn Math.max(snapshot.getIntegerVariable(TcSnapshot.getSelectedWindow()), 0);\n\t}\n\n\t@Override\n\tpublic MainFrame[] build() {\n\t\tint nbFrames = snapshot.getIntegerVariable(TcSnapshot.getWindowsCount());\n\n        // if there is no window saved in the snapshot file or custom folders are set, open one window with default settings\n\t\tif (nbFrames == 0 || isCustomStartupFolders()) {\n\t\t\tMainFrame mainFrame = new MainFrame(\n\t\t\t\t\tnew ConfFileTableTab(getInitialPath(FolderPanelType.LEFT)),\n\t\t\t\t\tgetFileTableConfiguration(FolderPanelType.LEFT, -1),\n\t\t\t\t\tnew ConfFileTableTab(getInitialPath(FolderPanelType.RIGHT)),\n\t\t\t\t\tgetFileTableConfiguration(FolderPanelType.RIGHT, -1));\n\t\t\t\n\t\t\tDimension screenSize   = Toolkit.getDefaultToolkit().getScreenSize();\n\t        // Full screen bounds are not reliable enough, in particular under Linux+Gnome\n\t        // so we simply make the initial window 4/5 of screen's size, and center it.\n\t        // This should fit under any window manager / platform\n\t        int x      = screenSize.width / 10;\n\t        int y      = screenSize.height / 10;\n\t        int width  = (int)(screenSize.width * 0.8);\n\t        int height = (int)(screenSize.height * 0.8);\n\n\t        mainFrame.getJFrame().setBounds(new Rectangle(x, y, width, height));\n\n\t        return new MainFrame[] {mainFrame};\n\t\t}\n\t\telse {\n\t\t\tMainFrame[] mainFrames = new MainFrame[nbFrames];\n\t\t\tfor (int i = 0; i < mainFrames.length; ++i) {\n\t\t\t\tmainFrames[i] = createMainFrame(i);\n\t\t\t}\n\n\t\t\treturn mainFrames;\n\t\t}\n\t}\n\n\tprivate boolean isCustomStartupFolders() {\n\t\treturn TcConfigurations.getPreferences().getVariable(TcPreference.STARTUP_FOLDERS).equals(TcPreferences.STARTUP_FOLDERS_CUSTOM);\n\t}\n\n\tprivate MainFrame createMainFrame(int index) {\n\t\tint nbTabsInLeftPanel = snapshot.getIntegerVariable(TcSnapshot.getTabsCountVariable(index, true));\n\t\tConfFileTableTab[] leftTabs = new ConfFileTableTab[nbTabsInLeftPanel];\n\t\tfor (int i = 0; i < nbTabsInLeftPanel; ++i) {\n\t\t\tleftTabs[i] = new ConfFileTableTab(\n\t\t\t\t\tsnapshot.getBooleanVariable(TcSnapshot.getTabLockedVariable(index, true, i)),\n\t\t\t\t\trestoreFileURL(snapshot.getVariable(TcSnapshot.getTabLocationVariable(index, true, i))),\n\t\t\t\t\tsnapshot.getVariable(TcSnapshot.getTabTitleVariable(index, true, i)));\n\t\t}\n\n\t\tint nbTabsInRightPanel = snapshot.getIntegerVariable(TcSnapshot.getTabsCountVariable(index, false));\n\t\tConfFileTableTab[] rightTabs = new ConfFileTableTab[nbTabsInRightPanel];\n\t\tfor (int i = 0; i < nbTabsInRightPanel; ++i) {\n            rightTabs[i] = new ConfFileTableTab(\n                    snapshot.getBooleanVariable(TcSnapshot.getTabLockedVariable(index, false, i)),\n                    restoreFileURL(snapshot.getVariable(TcSnapshot.getTabLocationVariable(index, false, i))),\n                    snapshot.getVariable(TcSnapshot.getTabTitleVariable(index, false, i)));\n        }\n\n\t\tMainFrame mainFrame = new MainFrame(\n\t\t\t\tleftTabs,\n\t\t\t\tgetInitialSelectedTab(FolderPanelType.LEFT, index),\n\t\t\t\tgetFileTableConfiguration(FolderPanelType.LEFT, index),\n\t\t\t\trightTabs,\n\t\t\t\tgetInitialSelectedTab(FolderPanelType.RIGHT, index),\n\t\t\t\tgetFileTableConfiguration(FolderPanelType.RIGHT, index));\n\n\t\t// Retrieve last saved window bounds\n\t\tDimension screenSize   = Toolkit.getDefaultToolkit().getScreenSize();\n        int x      = TcConfigurations.getSnapshot().getIntegerVariable(TcSnapshot.getX(index));\n        int y      = TcConfigurations.getSnapshot().getIntegerVariable(TcSnapshot.getY(index));\n        int width  = TcConfigurations.getSnapshot().getIntegerVariable(TcSnapshot.getWidth(index));\n        int height = TcConfigurations.getSnapshot().getIntegerVariable(TcSnapshot.getHeight(index));\n\n        // Retrieves the last known size of the screen.\n        int lastScreenWidth  = TcConfigurations.getSnapshot().getIntegerVariable(TcSnapshot.SCREEN_WIDTH);\n        int lastScreenHeight = TcConfigurations.getSnapshot().getIntegerVariable(TcSnapshot.SCREEN_HEIGHT);\n\n        // If no previous location was saved, or if the resolution has changed,\n        // reset the window's dimensions to their default values.\n        if (x == -1 || y == -1 || width == -1 || height == -1 ||\n           screenSize.width != lastScreenWidth ||  screenSize.height != lastScreenHeight\n           || width + x > screenSize.width + 5 || height + y > screenSize.height + 5) {\n\n            // Full screen bounds are not reliable enough, in particular under Linux+Gnome\n            // so we simply make the initial window 4/5 of screen's size, and center it.\n            // This should fit under any window manager / platform\n            x      = screenSize.width / 10;\n            y      = screenSize.height / 10;\n            width  = (int)(screenSize.width * 0.8);\n            height = (int)(screenSize.height * 0.8);\n        }\n\n        mainFrame.getJFrame().setBounds(new Rectangle(x, y, width, height));\n        \n        return mainFrame;\n\t}\n\t\n    private int getInitialSelectedTab(FolderPanelType folderPanelType, int window) {\n    \t// Checks which kind of initial path we're dealing with.\n    \tboolean isCustom = TcConfigurations.getPreferences().getVariable(TcPreference.STARTUP_FOLDERS, TcPreferences.DEFAULT_STARTUP_FOLDERS).equals(TcPreferences.STARTUP_FOLDERS_CUSTOM);\n    \t\n    \treturn isCustom ? \n    \t\t0 :\n    \t\tTcConfigurations.getSnapshot().getIntegerVariable(TcSnapshot.getTabsSelectionVariable(window, folderPanelType == FolderPanelType.LEFT));\n    }\n    \n    private FileURL restoreFileURL(String url) {\n    \ttry {\n\t\t\treturn FileURL.getFileURL(url);\n\t\t} catch (MalformedURLException e) {\n\t\t\treturn getHomeFolder().getURL();\n\t\t}\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/frame/MainFrameBuilder.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.frame;\n\nimport java.util.LinkedList;\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.conf.Configuration;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.conf.TcPreferencesAPI;\nimport com.mucommander.conf.TcSnapshot;\nimport com.mucommander.ui.main.FolderPanel.FolderPanelType;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.Column;\nimport com.mucommander.ui.main.table.views.full.FileTableConfiguration;\n\n/**\n *\n * @author Arik Hadas\n */\npublic abstract class MainFrameBuilder {\n    private static Logger logger;\n\n    public abstract MainFrame[] build();\n\n    public int getSelectedFrame() {\n        return 0;\n    }\n\n    /**\n     * Retrieves the user's initial path for the specified frame.\n     * <p>\n     * If the path found in preferences is either illegal or does not exist, this method will\n     * return the user's home directory - we assume this will always exist, which might be a bit\n     * of a leap of faith.\n     *\n     * @param folderPanelType panel for which the initial path should be returned (either {@link com.mucommander.ui.main.FolderPanel.FolderPanelType#LEFT} or\n     *                        {@link com.mucommander.ui.main.FolderPanel.FolderPanelType#RIGHT}).\n     * @return the user's initial path for the specified frame.\n     */\n    protected AbstractFile[] getInitialPaths(FolderPanelType folderPanelType, int window) {\n        boolean isCustom;    // Whether the initial path is a custom one or the last used folder.\n        String[] folderPaths; // Paths to the initial folders.\n\n        // Snapshot configuration\n        Configuration snapshot = TcConfigurations.getSnapshot();\n        // Preferences configuration\n        TcPreferencesAPI preferences = TcConfigurations.getPreferences();\n\n        // Checks which kind of initial path we're dealing with.\n        isCustom = preferences.getVariable(TcPreference.STARTUP_FOLDERS, TcPreferences.DEFAULT_STARTUP_FOLDERS).equals(TcPreferences.STARTUP_FOLDERS_CUSTOM);\n\n        // Handles custom initial paths.\n        if (isCustom) {\n            folderPaths = new String[]{(folderPanelType == FolderPanelType.LEFT ? preferences.getVariable(TcPreference.LEFT_CUSTOM_FOLDER) :\n                    preferences.getVariable(TcPreference.RIGHT_CUSTOM_FOLDER))};\n        }\n        // Handles \"last folder\" initial paths.\n        else {\n            // Set initial path to each tab\n            int nbFolderPaths = snapshot.getIntegerVariable(TcSnapshot.getTabsCountVariable(window, folderPanelType == FolderPanelType.LEFT));\n            folderPaths = new String[nbFolderPaths];\n            for (int i = 0; i < nbFolderPaths; ++i) {\n                folderPaths[i] = snapshot.getVariable(TcSnapshot.getTabLocationVariable(window, folderPanelType == FolderPanelType.LEFT, i));\n            }\n        }\n\n        List<AbstractFile> initialFolders = new LinkedList<>(); // Initial folders\n        AbstractFile folder;\n\n        for (String folderPath : folderPaths) {\n            // TODO: consider whether to search for workable path in case the folder doesn't exist\n            if (folderPath != null && (folder = FileFactory.getFile(folderPath)) != null && folder.exists())\n                initialFolders.add(folder);\n        }\n\n        // If the initial path is not legal or does not exist, defaults to the user's home.\n        AbstractFile[] results = initialFolders.isEmpty() ?\n                new AbstractFile[]{FileFactory.getFile(System.getProperty(\"user.home\"))} :\n                initialFolders.toArray(new AbstractFile[0]);\n\n        getLogger().debug(\"initial folders:\");\n        for (AbstractFile result : results) {\n            getLogger().debug(\"\\t{}\", result);\n        }\n\n        return results;\n    }\n\n    /**\n     * Retrieves the user's initial path for the specified frame.\n     * <p>\n     * If the path found in preferences is either illegal or does not exist, this method will\n     * return the user's home directory - we assume this will always exist, which might be a bit\n     * of a leap of faith.\n     *\n     * @param folderPanelType panel for which the initial path should be returned (either {@link com.mucommander.ui.main.FolderPanel.FolderPanelType#LEFT} or\n     *                        {@link #@link com.mucommander.ui.main.FolderPanel.FolderPanelType.RIGHT}).\n     * @return the user's initial path for the specified frame.\n     */\n    FileURL getInitialPath(FolderPanelType folderPanelType) {\n        // Preferences configuration\n        TcPreferencesAPI preferences = TcConfigurations.getPreferences();\n\n        // Checks which kind of initial path we're dealing with.\n        boolean isCustom = preferences.getVariable(TcPreference.STARTUP_FOLDERS, TcPreferences.DEFAULT_STARTUP_FOLDERS).equals(TcPreferences.STARTUP_FOLDERS_CUSTOM);\n\n        String customPath = null;\n        // Handles custom initial paths.\n        if (isCustom) {\n            customPath = (folderPanelType == FolderPanelType.LEFT ?\n                    preferences.getVariable(TcPreference.LEFT_CUSTOM_FOLDER)\n                    : preferences.getVariable(TcPreference.RIGHT_CUSTOM_FOLDER));\n        }\n\n        AbstractFile result;\n        if (customPath == null || (result = FileFactory.getFile(customPath)) == null || !result.exists()) {\n            result = getHomeFolder();\n        }\n\n        getLogger().debug(\"initial folder: {}\", result);\n\n        return result.getURL();\n    }\n\n    FileTableConfiguration getFileTableConfiguration(FolderPanelType folderPanelType, int window) {\n        FileTableConfiguration conf = new FileTableConfiguration();\n\n        // Loop on columns\n        for (Column c : Column.values()) {\n            if (c != Column.NAME) {       // Skip the special name column (always visible, width automatically calculated)\n                // Sets the column's initial visibility.\n                conf.setEnabled(c,\n                        TcConfigurations.getSnapshot().getVariable(\n                                TcSnapshot.getShowColumnVariable(window, c, folderPanelType == FolderPanelType.LEFT),\n                                c.showByDefault()\n                        )\n                );\n\n                // Sets the column's initial width.\n                conf.setWidth(c, TcConfigurations.getSnapshot().getIntegerVariable(TcSnapshot.getColumnWidthVariable(window, c, folderPanelType == FolderPanelType.LEFT)));\n            }\n\n            // Sets the column's initial order\n            conf.setPosition(c, TcConfigurations.getSnapshot().getVariable(\n                    TcSnapshot.getColumnPositionVariable(window, c, folderPanelType == FolderPanelType.LEFT),\n                    c.ordinal())\n            );\n        }\n\n        return conf;\n    }\n\n    AbstractFile getHomeFolder() {\n        return FileFactory.getFile(System.getProperty(\"user.home\"));\n    }\n\n\n    private static Logger getLogger() {\n        if (logger == null) {\n            logger = LoggerFactory.getLogger(MainFrameBuilder.class);\n        }\n        return logger;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/menu/MainMenuBar.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.menu;\r\n\r\nimport com.mucommander.bonjour.BonjourMenu;\r\nimport com.mucommander.bonjour.BonjourService;\r\nimport com.mucommander.bookmark.Bookmark;\r\nimport com.mucommander.bookmark.BookmarkManager;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.impl.local.LocalFile;\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.conf.TcConfigurations;\r\nimport com.mucommander.conf.TcPreference;\r\nimport com.mucommander.conf.TcPreferences;\r\nimport com.mucommander.desktop.DesktopManager;\r\nimport com.mucommander.ui.action.ActionManager;\r\nimport com.mucommander.ui.action.ActionParameters;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.action.impl.*;\r\nimport com.mucommander.ui.dialog.InformationDialog;\r\nimport com.mucommander.ui.dialog.pref.theme.ThemeEditorDialog;\r\nimport com.mucommander.ui.helper.MenuToolkit;\r\nimport com.mucommander.ui.helper.MnemonicHelper;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.WindowManager;\r\nimport com.mucommander.ui.main.table.Column;\r\nimport com.mucommander.ui.main.table.FileTable;\r\nimport com.mucommander.ui.theme.Theme;\r\nimport com.mucommander.ui.theme.ThemeManager;\r\nimport com.mucommander.ui.viewer.FileFrame;\r\nimport com.mucommander.utils.text.Translator;\r\nimport ru.trolsoft.ui.TCheckBoxMenuItem;\r\nimport ru.trolsoft.ui.TMenuSeparator;\r\n\r\nimport javax.swing.*;\r\nimport javax.swing.event.MenuEvent;\r\nimport javax.swing.event.MenuListener;\r\nimport java.awt.*;\r\nimport java.awt.event.ActionEvent;\r\nimport java.awt.event.ActionListener;\r\nimport java.util.*;\r\nimport java.util.List;\r\n\r\n\r\n/**\r\n * This class is the main menu bar. It takes care of displaying menu and menu items and triggering\r\n * the proper actions.\r\n *\r\n * <p><b>Implementation note</b>: for performance reasons, some menu items are created/enabled/disabled when corresponding menus\r\n * are selected, instead of monitoring the MainFrame's state and unnecessarily creating/enabling/disabling menu items\r\n * when they are not visible. However, this prevents keyboard shortcuts from being managed by the menu bar for those\r\n * dynamic items.\r\n *\r\n * @author Maxence Bernard\r\n */\r\npublic class MainMenuBar extends JMenuBar implements ActionListener, MenuListener {\r\n    private final MainFrame mainFrame;\r\n\r\n    // View menu\r\n    private final JMenu viewMenu;\r\n    private final JMenu themesMenu;\r\n    private final JCheckBoxMenuItem[] cbSortByItems = new TCheckBoxMenuItem[Column.values().length];\r\n    private final JMenu tableModeMenu;\r\n    private final JCheckBoxMenuItem[] cbTableModeItems = new TCheckBoxMenuItem[3];\r\n    private final JMenu columnsMenu;\r\n    private final JCheckBoxMenuItem[] cbToggleColumnItems = new TCheckBoxMenuItem[Column.values().length];\r\n    private final JCheckBoxMenuItem cbToggleToggleAutoSizeItem;\r\n    private final JCheckBoxMenuItem cbToggleShowFoldersFirstItem;\r\n    private final JCheckBoxMenuItem cbToggleFoldersAlwaysAlphabeticalItem;\r\n    private final JCheckBoxMenuItem cbToggleShowHiddenFilesItem;\r\n    private final JCheckBoxMenuItem cbToggleTreeItem;\r\n    private final JCheckBoxMenuItem cbToggleSinglePanel;\r\n    private final OpenWithMenu openWithMenu;\r\n    private final OpenAsMenu openAsMenu;\r\n    /* TODO branch private JCheckBoxMenuItem toggleBranchView; */\r\n\r\n\r\n    // Go menu\r\n    private final JMenu goMenu;\r\n    private final int volumeOffset;\r\n\r\n    // Bookmark menu\r\n    private final JMenu bookmarksMenu;\r\n    private final int bookmarksOffset;  // Index of the first bookmark menu item\r\n\r\n    private JMenu ejectDrivesMenu;\r\n\r\n    // Window menu\r\n    private final JMenu windowMenu;\r\n    private final int windowOffset; // Index of the first window menu item\r\n    private final JCheckBoxMenuItem splitHorizontallyItem;\r\n    private final JCheckBoxMenuItem splitVerticallyItem;\r\n\r\n    /** Maps window menu items onto weakly-referenced frames */\r\n    private WeakHashMap<JMenuItem, Frame> windowMenuFrames;\r\n\r\n\r\n    private final static String[] RECALL_WINDOW_ACTION_IDS = {\r\n        RecallWindow1Action.Descriptor.ACTION_ID,\r\n        RecallWindow2Action.Descriptor.ACTION_ID,\r\n        RecallWindow3Action.Descriptor.ACTION_ID,\r\n        RecallWindow4Action.Descriptor.ACTION_ID,\r\n        RecallWindow5Action.Descriptor.ACTION_ID,\r\n        RecallWindow6Action.Descriptor.ACTION_ID,\r\n        RecallWindow7Action.Descriptor.ACTION_ID,\r\n        RecallWindow8Action.Descriptor.ACTION_ID,\r\n        RecallWindow9Action.Descriptor.ACTION_ID,\r\n        RecallWindow10Action.Descriptor.ACTION_ID\r\n    };\r\n\r\n\r\n    /**\r\n     * Creates a new MenuBar for the given MainFrame.\r\n     */\r\n    public MainMenuBar(MainFrame mainFrame) {\r\n        this.mainFrame = mainFrame;\r\n        // Disable menu bar (NOT menu item) mnemonics under Mac OS X because of a bug: when screen menu bar is enabled\r\n        // and a menu is triggered by a mnemonic, the menu pops up where it would appear with a regular menu bar\r\n        // (i.e. with screen menu bar disabled).\r\n        MnemonicHelper menuMnemonicHelper = OsFamily.MAC_OS_X.isCurrent() ? null : new MnemonicHelper();\r\n\r\n        MnemonicHelper menuItemMnemonicHelper = new MnemonicHelper();\r\n        MnemonicHelper menuItemMnemonicHelper2 = new MnemonicHelper();\r\n\r\n        // File menu\r\n        JMenu fileMenu = MenuToolkit.addMenu(Translator.get(\"file_menu\"), menuMnemonicHelper, this);\r\n        MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(NewWindowAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(AddTabAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n\r\n        fileMenu.add(new TMenuSeparator());\r\n        MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(OpenAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(OpenNativelyAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        openWithMenu = new OpenWithMenu(mainFrame, null);\r\n        fileMenu.add(openWithMenu);\r\n        openAsMenu = new OpenAsMenu(mainFrame);\r\n        fileMenu.add(openAsMenu);\r\n        MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(OpenInNewTabAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(OpenInOtherPanelAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(OpenInBothPanelsAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(RevealInDesktopAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n\r\n        fileMenu.add(new TMenuSeparator());\r\n        MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(PackAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(UnpackAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(EmailAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(BatchRenameAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(SplitFileAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(CombineFilesAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(CreateSymlinkAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n\r\n        fileMenu.add(new TMenuSeparator());\r\n        MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(ShowFilePropertiesAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(CalculateChecksumAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(ChangePermissionsAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(ChangeDateAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(ChangeReplicationAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n\r\n        // Under Mac OS X, 'Preferences' already appears in the application (muCommander) menu, do not display it again\r\n        if (!OsFamily.MAC_OS_X.isCurrent()) {\r\n            fileMenu.add(new TMenuSeparator());\r\n            MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(ShowPreferencesAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        }\r\n\r\n        fileMenu.add(new TMenuSeparator());\r\n        MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(CloseWindowAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        // Under Mac OS X, 'Quit' already appears in the application (muCommander) menu, do not display it again\r\n        if (!OsFamily.MAC_OS_X.isCurrent()) {\r\n            MenuToolkit.addMenuItem(fileMenu, ActionManager.getActionInstance(QuitAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        }\r\n        add(fileMenu);\r\n\r\n        // Mark menu\r\n        menuItemMnemonicHelper.clear();\r\n        JMenu markMenu = MenuToolkit.addMenu(Translator.get(\"mark_menu\"), menuMnemonicHelper, this);\r\n        MenuToolkit.addMenuItem(markMenu, ActionManager.getActionInstance(MarkSelectedFileAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(markMenu, ActionManager.getActionInstance(MarkGroupAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(markMenu, ActionManager.getActionInstance(UnmarkGroupAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(markMenu, ActionManager.getActionInstance(MarkAllAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(markMenu, ActionManager.getActionInstance(UnmarkAllAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(markMenu, ActionManager.getActionInstance(MarkExtensionAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(markMenu, ActionManager.getActionInstance(MarkEmptyFilesAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(markMenu, ActionManager.getActionInstance(InvertSelectionAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n\r\n        markMenu.add(new TMenuSeparator());\r\n        MenuToolkit.addMenuItem(markMenu, ActionManager.getActionInstance(CopyFilesToClipboardAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(markMenu, ActionManager.getActionInstance(CopyFileNamesAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(markMenu, ActionManager.getActionInstance(CopyFileBaseNamesAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(markMenu, ActionManager.getActionInstance(CopyFilePathsAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(markMenu, ActionManager.getActionInstance(PasteClipboardFilesAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n\r\n        markMenu.add(new TMenuSeparator());\r\n        MenuToolkit.addMenuItem(markMenu, ActionManager.getActionInstance(CompareFoldersAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(markMenu, ActionManager.getActionInstance(CompareFolderFilesAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n\r\n        add(markMenu);\r\n\r\n        // View menu\r\n        menuItemMnemonicHelper.clear();\r\n        viewMenu = MenuToolkit.addMenu(Translator.get(\"view_menu\"), menuMnemonicHelper, this);\r\n        MenuToolkit.addMenuItem(viewMenu, ActionManager.getActionInstance(SwapFoldersAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(viewMenu, ActionManager.getActionInstance(SetSameFolderAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n\r\n        viewMenu.add(new TMenuSeparator());\r\n\r\n        tableModeMenu = MenuToolkit.addMenu(Translator.get(\"view_menu.table_mode\"), null, this);\r\n\r\n        cbTableModeItems[0] = MenuToolkit.addCheckBoxMenuItem(tableModeMenu, ActionManager.getActionInstance(ToggleTableViewModeFullAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper2);\r\n        cbTableModeItems[1] = MenuToolkit.addCheckBoxMenuItem(tableModeMenu, ActionManager.getActionInstance(ToggleTableViewModeCompactAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper2);\r\n        cbTableModeItems[2] = MenuToolkit.addCheckBoxMenuItem(tableModeMenu, ActionManager.getActionInstance(ToggleTableViewModeShortAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper2);\r\n        ButtonGroup groupViewMode = new ButtonGroup();\r\n        for (JCheckBoxMenuItem checkBoxMenuItem : cbTableModeItems) {\r\n            groupViewMode.add(checkBoxMenuItem);\r\n        }\r\n//        tableModeMenu.addMenuListener(new MenuListener() {\r\n//            @Override\r\n//            public void menuSelected(MenuEvent e) {\r\n//                int mode = mainFrame.getActiveTable().getViewMode().ordinal();\r\n//                cbTableModeItems[mode].setSelected(true);\r\n//            }\r\n//\r\n//            @Override\r\n//            public void menuDeselected(MenuEvent e) { }\r\n//\r\n//            @Override\r\n//            public void menuCanceled(MenuEvent e) { }\r\n//        });\r\n        tableModeMenu.add(new TMenuSeparator());\r\n        MenuToolkit.addMenuItem(tableModeMenu, ActionManager.getActionInstance(TogglePanelPreviewModeAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper2);\r\n        viewMenu.add(tableModeMenu);\r\n        viewMenu.add(new TMenuSeparator());\r\n\r\n        cbToggleShowFoldersFirstItem = MenuToolkit.addCheckBoxMenuItem(viewMenu, ActionManager.getActionInstance(ToggleShowFoldersFirstAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        cbToggleFoldersAlwaysAlphabeticalItem = MenuToolkit.addCheckBoxMenuItem(viewMenu, ActionManager.getActionInstance(ToggleFoldersAlwaysAlphabeticalAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        cbToggleShowHiddenFilesItem = MenuToolkit.addCheckBoxMenuItem(viewMenu, ActionManager.getActionInstance(ToggleHiddenFilesAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        cbToggleTreeItem = MenuToolkit.addCheckBoxMenuItem(viewMenu, ActionManager.getActionInstance(ToggleTreeAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        cbToggleSinglePanel = MenuToolkit.addCheckBoxMenuItem(viewMenu, ActionManager.getActionInstance(ToggleSinglePanelAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n\r\n        /* TODO branch toggleBranchView = MenuToolkit.addCheckBoxMenuItem(viewMenu, ActionManager.getActionInstance(ToggleBranchViewAction.class, mainFrame), menuItemMnemonicHelper); */\r\n\r\n        MenuToolkit.addMenuItem(viewMenu, ActionManager.getActionInstance(ShowFoldersSizeAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n\r\n        viewMenu.add(new TMenuSeparator());\r\n        ButtonGroup buttonGroup = new ButtonGroup();\r\n        for (Column c : Column.values()) {\r\n            buttonGroup.add(cbSortByItems[c.ordinal()] = MenuToolkit.addCheckBoxMenuItem(viewMenu, ActionManager.getActionInstance(c.getSortByColumnActionId(), mainFrame), menuItemMnemonicHelper));\r\n        }\r\n\r\n        MenuToolkit.addMenuItem(viewMenu, ActionManager.getActionInstance(ReverseSortOrderAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n\r\n        viewMenu.add(new TMenuSeparator());\r\n\r\n        // Toggle columns submenu\r\n        columnsMenu = MenuToolkit.addMenu(Translator.get(\"view_menu.show_hide_columns\"), null, this);\r\n        menuItemMnemonicHelper2.clear();\r\n        for (Column c : Column.values()) {\r\n            if (c == Column.NAME) {\r\n                continue;\r\n            }\r\n\r\n            cbToggleColumnItems[c.ordinal()] = MenuToolkit.addCheckBoxMenuItem(columnsMenu, ActionManager.getActionInstance(c.getToggleColumnActionId(), mainFrame), menuItemMnemonicHelper2);\r\n        }\r\n        viewMenu.add(columnsMenu);\r\n\r\n        cbToggleToggleAutoSizeItem = MenuToolkit.addCheckBoxMenuItem(viewMenu, ActionManager.getActionInstance(ToggleAutoSizeAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n\r\n        viewMenu.add(new TMenuSeparator());\r\n        MenuToolkit.addMenuItem(viewMenu, ActionManager.getActionInstance(ToggleToolBarAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(viewMenu, ActionManager.getActionInstance(ToggleStatusBarAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(viewMenu, ActionManager.getActionInstance(ToggleCommandBarAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(viewMenu, ActionManager.getActionInstance(CustomizeCommandBarAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n\r\n        add(viewMenu);\r\n\r\n        // Go menu\r\n        menuItemMnemonicHelper.clear();\r\n        goMenu = MenuToolkit.addMenu(Translator.get(\"go_menu\"), menuMnemonicHelper, this);\r\n\r\n        MenuToolkit.addMenuItem(goMenu, ActionManager.getActionInstance(GoBackAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(goMenu, ActionManager.getActionInstance(GoForwardAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n\r\n        goMenu.add(new TMenuSeparator());\r\n\r\n        MenuToolkit.addMenuItem(goMenu, ActionManager.getActionInstance(GoToParentAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(goMenu, ActionManager.getActionInstance(GoToParentInOtherPanelAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(goMenu, ActionManager.getActionInstance(GoToParentInBothPanelsAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(goMenu, ActionManager.getActionInstance(GoToRootAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n\r\n        goMenu.add(new TMenuSeparator());\r\n        MenuToolkit.addMenuItem(goMenu, ActionManager.getActionInstance(ChangeLocationAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(goMenu, ActionManager.getActionInstance(ConnectToServerAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(goMenu, ActionManager.getActionInstance(ShowServerConnectionsAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n\r\n        // Quick lists\r\n        goMenu.add(new TMenuSeparator());\r\n        JMenu quickListMenu = MenuToolkit.addMenu(Translator.get(\"quick_lists_menu\"), menuMnemonicHelper, this);\r\n        menuItemMnemonicHelper2.clear();\r\n        MenuToolkit.addMenuItem(quickListMenu, ActionManager.getActionInstance(ShowParentFoldersQLAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper2);\r\n        MenuToolkit.addMenuItem(quickListMenu, ActionManager.getActionInstance(ShowRecentLocationsQLAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper2);\r\n        MenuToolkit.addMenuItem(quickListMenu, ActionManager.getActionInstance(ShowRecentExecutedFilesQLAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper2);\r\n        MenuToolkit.addMenuItem(quickListMenu, ActionManager.getActionInstance(ShowBookmarksQLAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper2);\r\n        MenuToolkit.addMenuItem(quickListMenu, ActionManager.getActionInstance(ShowRootFoldersQLAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper2);\r\n        MenuToolkit.addMenuItem(quickListMenu, ActionManager.getActionInstance(ShowTabsQLAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper2);\r\n        MenuToolkit.addMenuItem(quickListMenu, ActionManager.getActionInstance(ShowRecentViewedFilesQLAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper2);\r\n        MenuToolkit.addMenuItem(quickListMenu, ActionManager.getActionInstance(ShowRecentEditedFilesQLAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper2);\r\n        MenuToolkit.addMenuItem(quickListMenu, ActionManager.getActionInstance(ShowEditorBookmarksQLAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper2);\r\n        goMenu.add(quickListMenu);\r\n\r\n        // Add Bonjour services menu\r\n        goMenu.add(new TMenuSeparator());\r\n        BonjourMenu bonjourMenu = new BonjourMenu() {\r\n            @Override\r\n            public TcAction getMenuItemAction(BonjourService bs) {\r\n                return new OpenLocationAction(MainMenuBar.this.mainFrame, new HashMap<>(), bs);\r\n            }\r\n        };\r\n        char mnemonic = menuItemMnemonicHelper.getMnemonic(bonjourMenu.getName());\r\n        if (mnemonic != 0) {\r\n            bonjourMenu.setMnemonic(mnemonic);\r\n        }\r\n        bonjourMenu.setIcon(null);\r\n        goMenu.add(bonjourMenu);\r\n\r\n        // Volumes will be added when the menu is selected\r\n        goMenu.add(new TMenuSeparator());\r\n        volumeOffset = goMenu.getItemCount();\r\n\r\n        add(goMenu);\r\n\r\n        // Tools menu\r\n        menuItemMnemonicHelper.clear();\r\n        JMenu toolsMenu = MenuToolkit.addMenu(Translator.get(\"tools_menu\"), menuMnemonicHelper, this);\r\n\r\n        MenuToolkit.addMenuItem(toolsMenu, ActionManager.getActionInstance(UserMenuAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(toolsMenu, ActionManager.getActionInstance(FindFileAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(toolsMenu, ActionManager.getActionInstance(CalculatorAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(toolsMenu, ActionManager.getActionInstance(RunCommandAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        if (OsFamily.MAC_OS_X.isCurrent()) {\r\n            ejectDrivesMenu = MenuToolkit.addMenu(Translator.get(\"eject_menu\"), menuMnemonicHelper, this);\r\n            toolsMenu.add(ejectDrivesMenu);\r\n        }\r\n        if (CompareFilesAction.supported()) {\r\n            MenuToolkit.addMenuItem(toolsMenu, ActionManager.getActionInstance(CompareFilesAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        }\r\n        toolsMenu.add(new TMenuSeparator());\r\n        MenuToolkit.addMenuItem(toolsMenu, ActionManager.getActionInstance(EditCommandsAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n\r\n        add(toolsMenu);\r\n        //toolsOffset = toolsMenu.getItemCount();\r\n\r\n        // Bookmark menu, menu items will be added when the menu gets selected\r\n        menuItemMnemonicHelper.clear();\r\n        bookmarksMenu = MenuToolkit.addMenu(Translator.get(\"bookmarks_menu\"), menuMnemonicHelper, this);\r\n        //bookmarksMenu = MenuToolkit.addScrollableMenu(Translator.get(\"bookmarks_menu\"), menuMnemonicHelper, this);\r\n\r\n        MenuToolkit.addMenuItem(bookmarksMenu, ActionManager.getActionInstance(AddBookmarkAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(bookmarksMenu, ActionManager.getActionInstance(EditBookmarksAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(bookmarksMenu, ActionManager.getActionInstance(ExploreBookmarksAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        bookmarksMenu.add(new TMenuSeparator());\r\n        MenuToolkit.addMenuItem(bookmarksMenu, ActionManager.getActionInstance(EditCredentialsAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        bookmarksMenu.add(new TMenuSeparator());\r\n\r\n        // Save the first bookmark menu item's offset for later (bookmarks will be added when menu becomes visible)\r\n        this.bookmarksOffset = bookmarksMenu.getItemCount();\r\n\r\n        add(bookmarksMenu);\r\n\r\n        \r\n        // Window menu\r\n        menuItemMnemonicHelper.clear();\r\n\r\n        windowMenu = MenuToolkit.addMenu(Translator.get(\"window_menu\"), menuMnemonicHelper, this);\r\n\r\n        // If running Mac OS X, add 'Minimize' and 'Zoom' items\r\n        if (OsFamily.MAC_OS_X.isCurrent()) {\r\n            MenuToolkit.addMenuItem(windowMenu, ActionManager.getActionInstance(MinimizeWindowAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n            MenuToolkit.addMenuItem(windowMenu, ActionManager.getActionInstance(MaximizeWindowAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n            windowMenu.add(new TMenuSeparator());\r\n        }\r\n\r\n        MenuToolkit.addMenuItem(windowMenu, ActionManager.getActionInstance(SplitEquallyAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        buttonGroup = new ButtonGroup();\r\n        buttonGroup.add(splitVerticallyItem = MenuToolkit.addCheckBoxMenuItem(windowMenu, ActionManager.getActionInstance(SplitVerticallyAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper));\r\n        buttonGroup.add(splitHorizontallyItem = MenuToolkit.addCheckBoxMenuItem(windowMenu, ActionManager.getActionInstance(SplitHorizontallyAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper));\r\n\r\n        windowMenu.add(new TMenuSeparator());\r\n        themesMenu = MenuToolkit.addMenu(Translator.get(\"prefs_dialog.themes\"), null, this);\r\n        // Theme menu items will be added when the themes menu is selected\r\n        windowMenu.add(themesMenu);\r\n\r\n        windowMenu.add(new TMenuSeparator());\r\n        MenuToolkit.addMenuItem(windowMenu, ActionManager.getActionInstance(RecallPreviousWindowAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(windowMenu, ActionManager.getActionInstance(RecallNextWindowAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(windowMenu, ActionManager.getActionInstance(BringAllToFrontAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        // All other window menu items will be added when the menu gets selected\r\n        windowMenu.add(new TMenuSeparator());\r\n\r\n        // Save the first window menu item's offset for later\r\n        this.windowOffset = windowMenu.getItemCount();\r\n\r\n        add(windowMenu);\r\n\r\n        // Help menu\r\n        menuItemMnemonicHelper.clear();\r\n        JMenu helpMenu = MenuToolkit.addMenu(Translator.get(\"help_menu\"), menuMnemonicHelper, null);\r\n\r\n        MenuToolkit.addMenuItem(helpMenu, ActionManager.getActionInstance(GoToDocumentationAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(helpMenu, ActionManager.getActionInstance(ShowKeyboardShortcutsAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        MenuToolkit.addMenuItem(helpMenu, ActionManager.getActionInstance(ShowDebugConsoleAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n\r\n        // Links to website, only shows for OS/Window manager that can launch the default browser to open URLs\r\n        if (DesktopManager.canBrowse()) {\r\n            helpMenu.add(new TMenuSeparator());\r\n            MenuToolkit.addMenuItem(helpMenu, ActionManager.getActionInstance(GoToWebsiteAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n            //MenuToolkit.addMenuItem(helpMenu, ActionManager.getActionInstance(GoToForumsAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n            //MenuToolkit.addMenuItem(helpMenu, ActionManager.getActionInstance(ReportBugAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n            //MenuToolkit.addMenuItem(helpMenu, ActionManager.getActionInstance(DonateAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n\r\n            helpMenu.add(new TMenuSeparator());\r\n            MenuToolkit.addMenuItem(helpMenu, ActionManager.getActionInstance(CheckForUpdatesAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        }\r\n\t\t\r\n        // Under Mac OS X, 'About' already appears in the application (muCommander) menu, do not display it again\r\n        if (!OsFamily.MAC_OS_X.isCurrent()) {\r\n            helpMenu.add(new TMenuSeparator());\r\n            MenuToolkit.addMenuItem(helpMenu, ActionManager.getActionInstance(ShowAboutAction.Descriptor.ACTION_ID, mainFrame), menuItemMnemonicHelper);\r\n        }\r\n\t\t\r\n        add(helpMenu);\r\n    }\r\n\t\r\n\r\n    @Override\r\n    public void actionPerformed(ActionEvent e) {\r\n        // Discard action events while in 'no events mode'\r\n        if (mainFrame.getNoEventsMode()) {\r\n            return;\r\n        }\r\n\r\n        // Bring the frame corresponding to the clicked menu item to the front\r\n        JMenuItem source = (JMenuItem) e.getSource();\r\n        windowMenuFrames.get(source).toFront();\r\n    }\r\n\r\n\r\n    @Override\r\n    public void menuSelected(MenuEvent e) {\r\n        Object source = e.getSource();\r\n\r\n        if (source == viewMenu) {\r\n            updateViewMenu();\r\n        } else if (source == columnsMenu) {\r\n            updateShowHideColumnsMenu();\r\n        } else if (source == goMenu) {\r\n            updateGoMenu();\r\n        } else if (source == ejectDrivesMenu) {\r\n            updateEjectDriveMenu();\r\n        } else if (source == bookmarksMenu) {\r\n            updateBookmarksMenu();\r\n        } else if (source == windowMenu) {\r\n            updateWindowsMenu();\r\n        } else if (source == themesMenu) {\r\n            updateThemesMenu();\r\n        } else if (source == tableModeMenu) {\r\n            int mode = mainFrame.getActiveTable().getViewMode().ordinal();\r\n            cbTableModeItems[mode].setSelected(true);\r\n        } else if (source == openWithMenu) {\r\n            openWithMenu.populate(mainFrame.getActiveTable().getSelectedFile());\r\n        } else if (source == openAsMenu) {\r\n            final AbstractFile selectedFile = mainFrame.getActiveTable().getSelectedFile();\r\n            openAsMenu.setEnabled(selectedFile != null && !selectedFile.isDirectory());\r\n        }\r\n    }\r\n\r\n    private void updateViewMenu() {\r\n        FileTable activeTable = mainFrame.getActiveTable();\r\n\r\n        // Select the 'sort by' criterion currently in use in the active table\r\n        cbSortByItems[activeTable.getSortInfo().getCriterion().ordinal()].setSelected(true);\r\n\r\n        boolean foldersFirst = activeTable.getSortInfo().getFoldersFirst();\r\n        cbToggleShowFoldersFirstItem.setSelected(foldersFirst);\r\n        cbToggleFoldersAlwaysAlphabeticalItem.setEnabled(foldersFirst);\r\n        cbToggleFoldersAlwaysAlphabeticalItem.setSelected(foldersFirst && activeTable.getSortInfo().getFoldersAlwaysAlphabetical());\r\n        cbToggleShowHiddenFilesItem.setSelected(TcConfigurations.getPreferences().getVariable(TcPreference.SHOW_HIDDEN_FILES, TcPreferences.DEFAULT_SHOW_HIDDEN_FILES));\r\n        cbToggleTreeItem.setSelected(activeTable.getFolderPanel().isTreeVisible());\r\n        cbToggleToggleAutoSizeItem.setSelected(mainFrame.isAutoSizeColumnsEnabled());\r\n        cbToggleSinglePanel.setSelected(mainFrame.isSinglePanel());\r\n        // TODO branch toggleBranchView.setSelected(activeTable.getFolderPanel().isBranchView());\r\n    }\r\n\r\n    private void updateShowHideColumnsMenu() {\r\n        // Update the selected and enabled state of each column menu item.\r\n        FileTable activeTable = mainFrame.getActiveTable();\r\n        for (Column c : Column.values()) {\r\n            if (c == Column.NAME) {    // Name column doesn't have a menu item as it cannot be disabled\r\n                continue;\r\n            }\r\n\r\n            JCheckBoxMenuItem item = cbToggleColumnItems[c.ordinal()];\r\n            item.setSelected(activeTable.isColumnEnabled(c));\r\n            item.setEnabled(activeTable.isColumnDisplayable(c));\r\n            // Override the action's label to a shorter one\r\n            item.setText(c.getLabel());\r\n        }\r\n    }\r\n\r\n    private void updateGoMenu() {\r\n        // Remove any previous volumes from the Go menu\r\n        // as they might have changed since menu was last selected\r\n        for (int i = goMenu.getItemCount(); i > volumeOffset; i--) {\r\n            goMenu.remove(volumeOffset);\r\n        }\r\n\r\n        AbstractFile[] volumes = LocalFile.getVolumes();\r\n        for (AbstractFile volume : volumes) {\r\n            goMenu.add(new OpenLocationAction(mainFrame, new Hashtable<>(), volume));\r\n        }\r\n    }\r\n\r\n    private void updateEjectDriveMenu() {\r\n        // Remove any previous drives menu items from menu\r\n        // as there might have changed since menu was last selected\r\n        ejectDrivesMenu.removeAll();\r\n\r\n        AbstractFile[] volumes = LocalFile.getVolumes();\r\n        boolean empty = true;\r\n        for (AbstractFile volume : volumes) {\r\n            if (volume != null && !volume.isSymlink() && !volume.getPath().toLowerCase().startsWith(\"/users/\")) {\r\n                MenuToolkit.addMenuItem(ejectDrivesMenu, volume.getName(), null, null, event -> {\r\n                    EjectDriveAction.eject(mainFrame, volume);\r\n                    mainFrame.tryRefreshCurrentFolders();\r\n                });\r\n                empty = false;\r\n            }\r\n        }\r\n        if (empty) {\r\n            JMenuItem menuItem = new JMenuItem(Translator.get(\"eject.no_mounted_devices\"));\r\n            menuItem.setEnabled(false);\r\n            ejectDrivesMenu.add(menuItem);\r\n        }\r\n    }\r\n\r\n    private void updateBookmarksMenu() {\r\n        // Remove any previous bookmarks menu items from menu\r\n        // as bookmarks might have changed since menu was last selected\r\n        for (int i = bookmarksMenu.getItemCount(); i > bookmarksOffset; i--) {\r\n            bookmarksMenu.remove(bookmarksOffset);\r\n        }\r\n\r\n        // Add bookmarks menu items\r\n        List<Bookmark> bookmarks = BookmarkManager.getBookmarks();\r\n        if (!bookmarks.isEmpty()) {\r\n            addBookmarksForGroup(bookmarksMenu, bookmarks, null);\r\n        } else {\r\n            // Show 'No bookmark' as a disabled menu item instead showing nothing\r\n            JMenuItem noBookmarkItem = MenuToolkit.addMenuItem(bookmarksMenu, Translator.get(\"bookmarks_menu.no_bookmark\"), null, null, null);\r\n            noBookmarkItem.setEnabled(false);\r\n        }\r\n    }\r\n\r\n    private void addBookmarksForGroup(JMenu menu, List<Bookmark> bookmarks, String parent) {\r\n        for (Bookmark bookmark : bookmarks) {\r\n            if ((bookmark.getParent() == null && parent == null) || (parent != null && parent.equals(bookmark.getParent()))) {\r\n                if (bookmark.getLocation().isEmpty() && !bookmark.getName().equals(BookmarkManager.BOOKMARKS_SEPARATOR)) {\r\n                    JMenu groupMenu = MenuToolkit.addMenu(bookmark.getName(), null, null);\r\n                    menu.add(groupMenu);\r\n                    addBookmarksForGroup(groupMenu, bookmarks, bookmark.getName());\r\n                } else {\r\n                    MenuToolkit.addMenuItem(menu, new OpenLocationAction(mainFrame, new HashMap<>(), bookmark), null);\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n    private void updateWindowsMenu() {\r\n        // Select the split orientation currently in use\r\n        if (mainFrame.getSplitPaneOrientation()) {\r\n            splitVerticallyItem.setSelected(true);\r\n        } else {\r\n            splitHorizontallyItem.setSelected(true);\r\n        }\r\n\r\n        // Removing any window menu item previously added\r\n        // Note: menu item cannot be removed by menuDeselected() as actionPerformed() will be called after\r\n        // menu has been deselected.\r\n        for (int i = windowMenu.getItemCount(); i > windowOffset; i--) {\r\n            windowMenu.remove(windowOffset);\r\n        }\r\n\r\n        // This WeakHashMap maps menu items to frame instances. It has to be a weakly referenced hash map\r\n        // and not a regular hash map, since it will not (and cannot) be emptied when the menu has been deselected\r\n        // and we really do not want this hash map to prevent the frames to be GCed\r\n        windowMenuFrames = new WeakHashMap<>();\r\n\r\n        // create a menu item for each of the MainFrame instances, that displays the MainFrame's path\r\n        // and a keyboard accelerator to recall the frame (for the first 10 frames only).\r\n        List<MainFrame> mainFrames = WindowManager.getMainFrames();\r\n        int nbFrames = mainFrames.size();\r\n        for (int i = 0; i < nbFrames; i++) {\r\n            MainFrame mainFrame = mainFrames.get(i);\r\n            JCheckBoxMenuItem checkBoxMenuItem = new TCheckBoxMenuItem();\r\n\r\n            // If frame number is less than 10, use the corresponding action class (accelerator will be displayed in the menu item)\r\n            TcAction recallWindowAction;\r\n            if (i < 10) {\r\n                recallWindowAction = ActionManager.getActionInstance(RECALL_WINDOW_ACTION_IDS[i], this.mainFrame);\r\n            } else {    // Else use the generic RecallWindowAction\r\n                Map<String, Object> actionProps = new HashMap<>();\r\n                // Specify the window number using the dedicated property\r\n                actionProps.put(RecallWindowAction.WINDOW_NUMBER_PROPERTY_KEY, \"\"+(i+1));\r\n                recallWindowAction = ActionManager.getActionInstance(new ActionParameters(RecallWindowAction.Descriptor.ACTION_ID, actionProps), this.mainFrame);\r\n            }\r\n\r\n            checkBoxMenuItem.setAction(recallWindowAction);\r\n\r\n            // Replace the action's label and use the MainFrame's current folder path instead\r\n            checkBoxMenuItem.setText((i+1)+\" \"+mainFrame.getActiveTable().getFolderPanel().getCurrentFolder().getAbsolutePath());\r\n\r\n            // Use the action's label as a tooltip\r\n            checkBoxMenuItem.setToolTipText(recallWindowAction.getLabel());\r\n\r\n            // Check current MainFrame (the one this menu bar belongs to)\r\n            checkBoxMenuItem.setSelected(mainFrame == this.mainFrame);\r\n\r\n            windowMenu.add(checkBoxMenuItem);\r\n        }\r\n\r\n        // Add 'other' (non-MainFrame) windows : viewer and editor frames, no associated accelerator\r\n        Frame[] frames = Frame.getFrames();\r\n        nbFrames = frames.length;\r\n        boolean firstFrame = true;\r\n        for (int i = 0; i < nbFrames; i++) {\r\n            Frame frame = frames[i];\r\n            // Test if Frame is not hidden (disposed), Frame.getFrames() returns both active and disposed frames\r\n            if (frame.isShowing() && (frame instanceof FileFrame)) {\r\n                // Add a separator before the first non-MainFrame frame to mark a separation between MainFrames\r\n                // and other frames\r\n                if (firstFrame) {\r\n                    windowMenu.add(new TMenuSeparator());\r\n                    firstFrame = false;\r\n                }\r\n                // Use frame's window title\r\n                JMenuItem menuItem = new JMenuItem(frame.getTitle());\r\n                menuItem.addActionListener(this);\r\n                windowMenu.add(menuItem);\r\n                windowMenuFrames.put(menuItem, frame);\r\n            }\r\n        }\r\n    }\r\n\r\n    private void updateThemesMenu() {\r\n        // Remove all previous theme items, create new ones for each available theme and select the current theme\r\n        themesMenu.removeAll();\r\n        ButtonGroup buttonGroup = new ButtonGroup();\r\n        Iterator<Theme> themes = ThemeManager.availableThemes();\r\n        themesMenu.add(new JMenuItem(new EditCurrentThemeAction()));\r\n        themesMenu.add(new TMenuSeparator());\r\n        while (themes.hasNext()) {\r\n            Theme theme = themes.next();\r\n            JCheckBoxMenuItem item = new TCheckBoxMenuItem(new ChangeCurrentThemeAction(theme));\r\n            buttonGroup.add(item);\r\n            if (ThemeManager.isCurrentTheme(theme)) {\r\n                item.setSelected(true);\r\n            }\r\n\r\n            themesMenu.add(item);\r\n        }\r\n    }\r\n\r\n\r\n    public void menuDeselected(MenuEvent e) {\r\n    }\r\n\t \r\n    public void menuCanceled(MenuEvent e) {\r\n    }\r\n\r\n\r\n    /**\r\n     * Action that changes the current theme to the specified in the constructor.\r\n     */\r\n    private class ChangeCurrentThemeAction extends AbstractAction {\r\n\r\n        private final Theme theme;\r\n\r\n        ChangeCurrentThemeAction(Theme theme) {\r\n            super(theme.getName());\r\n            this.theme = theme;\r\n        }\r\n\r\n        public void actionPerformed(ActionEvent actionEvent) {\r\n            try {\r\n                ThemeManager.setCurrentTheme(theme);\r\n            } catch(IllegalArgumentException e) {\r\n                InformationDialog.showErrorDialog(mainFrame.getJFrame(), Translator.get(\"theme_could_not_be_loaded\"));\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Actions that edits the current theme.\r\n     */\r\n    private class EditCurrentThemeAction extends AbstractAction {\r\n        EditCurrentThemeAction() {\r\n            super(Translator.get(\"prefs_dialog.edit_current_theme\"));\r\n        }\r\n\r\n        public void actionPerformed(ActionEvent actionEvent) {\r\n            new ThemeEditorDialog(mainFrame.getJFrame(), ThemeManager.getCurrentTheme()).editTheme();\r\n        }\r\n    }\r\n\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/menu/OpenAsMenu.java",
    "content": "package com.mucommander.ui.main.menu;\n\nimport com.mucommander.commons.file.ArchiveFormatProvider;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.ui.action.ActionManager;\nimport com.mucommander.ui.action.ActionParameters;\nimport com.mucommander.ui.action.impl.OpenAsAction;\nimport com.mucommander.ui.helper.MenuToolkit;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.utils.text.Translator;\n\nimport javax.swing.*;\nimport java.util.*;\n\npublic class OpenAsMenu extends JMenu {\n\n    private final static List<String> EXTENSIONS = new ArrayList<>();\n    private final MainFrame mainFrame;\n\n    static {\n        for (Iterator<ArchiveFormatProvider> it = FileFactory.archiveFormats(); it.hasNext(); ) {\n            ArchiveFormatProvider provider = it.next();\n            EXTENSIONS.addAll(Arrays.asList(provider.getFileExtensions()));\n        }\n        Collections.sort(EXTENSIONS);\n    }\n\n\n    OpenAsMenu(MainFrame mainFrame) {\n        super(Translator.get(\"file_menu.open_as\") + \"...\");\n        this.mainFrame = mainFrame;\n        populate();\n    }\n\n    /**\n     * Refreshes the content of the menu.\n     */\n    private void populate() {\n        for (String extension : EXTENSIONS) {\n            Map<String, Object> params = Collections.singletonMap(\"extension\", extension);\n            Action action = ActionManager.getActionInstance(new ActionParameters(OpenAsAction.Descriptor.ACTION_ID, params), mainFrame);\n            action.putValue(Action.NAME, extension.substring(1));\n            add(action);\n        }\n    }\n\n    @Override\n    public final JMenuItem add(Action a) {\n        JMenuItem item = super.add(a);\n        MenuToolkit.configureActionMenuItem(item);\n        return item;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/menu/OpenWithMenu.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.menu;\n\nimport javax.swing.Action;\nimport javax.swing.JMenu;\nimport javax.swing.JMenuItem;\n\nimport com.mucommander.command.Command;\nimport com.mucommander.command.CommandManager;\nimport com.mucommander.command.CommandType;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.action.ActionManager;\nimport com.mucommander.ui.helper.MenuToolkit;\nimport com.mucommander.ui.main.MainFrame;\n\n\n/**\n * Open with menu.\n * <p>\n * Note that this class doesn't yet monitor modifications to the command list.\n *\n * @author Nicolas Rinaudo\n */\npublic class OpenWithMenu extends JMenu {\n    private final MainFrame mainFrame;\n\n    /**\n     * Creates a new Open With menu.\n     */\n    OpenWithMenu(MainFrame frame, AbstractFile clickedFile) {\n        super(Translator.get(\"file_menu.open_with\") + \"...\");\n        this.mainFrame = frame;\n        populate(clickedFile);\n    }\n\n    /**\n     * Refreshes the content of the menu.\n     */\n    public void populate(AbstractFile clickedFile) {\n        for (Command command : CommandManager.commands()) {\n            if (command.getType() != CommandType.NORMAL_COMMAND) {\n                continue;\n            }\n            if (clickedFile != null && !CommandManager.checkFileMask(command, clickedFile)) {\n                continue;\n            }\n            add(ActionManager.getActionInstance(command, mainFrame));\n        }\n        if (getItemCount() == 0) {\n            setEnabled(false);\n        }\n    }\n\n    @Override\n    public final JMenuItem add(Action a) {\n    \tJMenuItem item = super.add(a);\n    \tMenuToolkit.configureActionMenuItem(item);\n    \treturn item;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/menu/TablePopupMenu.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.main.menu;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.filter.MountedDriveFilter;\nimport com.mucommander.commons.file.util.FileSet;\nimport com.mucommander.desktop.DesktopManager;\nimport com.mucommander.ui.action.impl.CompareFilesAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.popup.TcActionsPopupMenu;\nimport com.mucommander.utils.text.Translator;\n\n/**\n * Contextual popup menu invoked by FileTable when right-clicking on a file or a group of files.\n * <p>\n * The following items are displayed (see constructor code for conditions) :\n * <p>\n * Open\n * Open in new tab\n * Open natively\n * Open with...\n * Open as...\n * Rename\n * Reveal in Finder\n * ----\n * Copy name(s)\n * Copy path(s)\n * ----\n * Mark all\n * Unmark all\n * Mark / Unmark\n * ----\n * Delete\n * ----\n * Properties\n * Change permission...\n * Change date...\n * ----\n * Eject    [Mac OS only]\n * Compare files [Mac OS only]\n * \n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic class TablePopupMenu extends TcActionsPopupMenu {\n\n    /**\n     * Creates a new TablePopupMenu.\n     *\n     * @param mainFrame parent MainFrame instance\n     * @param currentFolder current folder in table\n     * @param clickedFile right-clicked file, can be null if user clicked on the folder table background\n     * @param parentFolderClicked true if user right-clicked on the parent '..' folder\n     * @param markedFiles list of marked files, can be empty but never null\n     */\n    public TablePopupMenu(MainFrame mainFrame, AbstractFile currentFolder, AbstractFile clickedFile, boolean parentFolderClicked, FileSet markedFiles) {\n        super(mainFrame);\n        \n        // 'Open ...' actions displayed if a single file was clicked\n        addOpenActions(mainFrame, clickedFile, parentFolderClicked);\n\n        // 'Reveal in desktop' displayed only if clicked file is a local file and the OS is capable of doing this\n        addRevealInDesktopAction(currentFolder);\n\n        addSeparator();\n\n        // 'Copy name(s)' and 'Copy path(s)' are displayed only if a single file was clicked or files are marked\n        addCopyActions(clickedFile, markedFiles);\n\n        // Those following items are displayed in all cases\n        addMarkActions();\n\n        addSeparator();\n\n        // 'Rename' displayed if a single file was clicked\n        if (clickedFile != null) {\n            addAction(com.mucommander.ui.action.impl.RenameAction.Descriptor.ACTION_ID);\n        }\n        addAction(com.mucommander.ui.action.impl.DeleteAction.Descriptor.ACTION_ID);\n        addAction(com.mucommander.ui.action.impl.CreateSymlinkAction.Descriptor.ACTION_ID);\n        if (clickedFile != null && clickedFile.isSymlink()) {\n            addAction(com.mucommander.ui.action.impl.LocateSymlinkAction.Descriptor.ACTION_ID);\n        }\n\n        addSeparator();\n\n        addAction(com.mucommander.ui.action.impl.ShowFilePropertiesAction.Descriptor.ACTION_ID);\n        addAction(com.mucommander.ui.action.impl.ChangePermissionsAction.Descriptor.ACTION_ID);\n        addAction(com.mucommander.ui.action.impl.ChangeDateAction.Descriptor.ACTION_ID);\n\n        if (new MountedDriveFilter().accept(clickedFile)) {\n            addSeparator();\n            addAction(com.mucommander.ui.action.impl.EjectDriveAction.Descriptor.ACTION_ID);\n        }\n\n        addCompareSelectedFilesAction(markedFiles);\n    }\n\n    private void addMarkActions() {\n        addAction(com.mucommander.ui.action.impl.MarkAllAction.Descriptor.ACTION_ID);\n        addAction(com.mucommander.ui.action.impl.UnmarkAllAction.Descriptor.ACTION_ID);\n        addAction(com.mucommander.ui.action.impl.MarkSelectedFileAction.Descriptor.ACTION_ID);\n    }\n\n    private void addCopyActions(AbstractFile clickedFile, FileSet markedFiles) {\n        if (clickedFile != null || !markedFiles.isEmpty()) {\n            addAction(com.mucommander.ui.action.impl.CopyFilesToClipboardAction.Descriptor.ACTION_ID);\n            addAction(com.mucommander.ui.action.impl.CopyFileNamesAction.Descriptor.ACTION_ID);\n            addAction(com.mucommander.ui.action.impl.CopyFileBaseNamesAction.Descriptor.ACTION_ID);\n            addAction(com.mucommander.ui.action.impl.CopyFilePathsAction.Descriptor.ACTION_ID);\n\n            addSeparator();\n        }\n    }\n\n    private void addRevealInDesktopAction(AbstractFile currentFolder) {\n        if (DesktopManager.canOpenInFileManager(currentFolder)) {\n            addAction(com.mucommander.ui.action.impl.RevealInDesktopAction.Descriptor.ACTION_ID);\n        }\n    }\n\n    private void addOpenActions(MainFrame mainFrame, AbstractFile clickedFile, boolean parentFolderClicked) {\n        if (clickedFile != null || parentFolderClicked) {\n            addAction(com.mucommander.ui.action.impl.OpenAction.Descriptor.ACTION_ID);\n            addAction(com.mucommander.ui.action.impl.OpenNativelyAction.Descriptor.ACTION_ID);\n            add(new OpenWithMenu(mainFrame, clickedFile));\n            if (clickedFile != null && !clickedFile.isDirectory()) {\n                add(new OpenAsMenu(mainFrame));\n            }\n\n            addAction(com.mucommander.ui.action.impl.OpenInNewTabAction.Descriptor.ACTION_ID);\n        }\n    }\n\n    private void addCompareSelectedFilesAction(FileSet markedFiles) {\n        if (markedFiles.size() != 2 || !CompareFilesAction.supported()) {\n            return;\n        }\n        AbstractFile f1 = markedFiles.get(0);\n        AbstractFile f2 = markedFiles.get(1);\n        if (f1.isDirectory() || f2.isDirectory() || !f1.isLocalFile() || !f2.isLocalFile()) {\n            return;\n        }\n        add(Translator.get(\"CompareFiles.label\")).addActionListener(\n                e -> CompareFilesAction.compareTwoFiles(f1.getAbsolutePath(), f2.getAbsolutePath())\n        );\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/menu/UserPopupMenu.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2018 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.main.menu;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.desktop.macos.OSXTerminal;\nimport com.mucommander.process.ExecutorUtils;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.EditAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.menu.usermenu.UserMenuItem;\nimport com.mucommander.ui.viewer.EditorRegistrar;\nimport com.mucommander.utils.text.Translator;\nimport lombok.extern.slf4j.Slf4j;\n\nimport javax.swing.*;\nimport javax.swing.event.PopupMenuEvent;\nimport javax.swing.event.PopupMenuListener;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.awt.event.KeyEvent;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n@Slf4j\npublic class UserPopupMenu extends JPopupMenu implements ActionListener, PopupMenuListener {\n    private final MainFrame mainFrame;\n    private final AbstractFile menuFile;\n    private final Map<JMenuItem, UserMenuItem> propertiesMap = new HashMap<>();\n    private JMenuItem firstItem;\n\n\n    public UserPopupMenu(MainFrame mainFrame, AbstractFile file) {\n        this.mainFrame = mainFrame;\n        this.menuFile = file;\n\n        addPopupMenuListener(this);\n    }\n\n    public JMenuItem add(JMenu parent, String name, UserMenuItem properties) {\n        JMenuItem item = new JMenuItem(name);\n        if (parent == null) {\n            add(item);\n        } else {\n            parent.add(item);\n        }\n        propertiesMap.put(item, properties);\n        item.addActionListener(this);\n        if (firstItem == null) {\n            firstItem = item;\n        }\n        return item;\n    }\n\n    private void selectFirstItem() {\n        if (firstItem != null) {\n            SwingUtilities.invokeLater(() -> MenuSelectionManager.defaultManager().setSelectedPath(\n                    new MenuElement[] {this, firstItem})\n            );\n        }\n    }\n\n    @Override\n    public void actionPerformed(ActionEvent e) {\n        Object menuItem = e.getSource();\n        if (menuItem instanceof JMenuItem) {\n            performCommand(propertiesMap.get(menuItem));\n        }\n    }\n\n    private void performCommand(UserMenuItem properties) {\n        if (properties.command == null) {\n            mainFrame.getStatusBar().setStatusInfo(Translator.get(\"UserMenu.command_not_defined\"));\n            return;\n        }\n        switch (properties.console) {\n            case NONE:\n                executeInBackground(properties);\n                break;\n            case SHOW:\n            case HIDE:\n                executeInTerminal(properties);\n                break;\n            case APPEND:\n                executeInNewTerminalTabs(properties);\n                break;\n        }\n    }\n\n    private void executeInNewTerminalTabs(UserMenuItem properties) {\n        if (properties.command.isSingle()) {\n            OSXTerminal.addNewTabWithCommands(menuFile.getParent(), properties.command.singleCommand);\n        } else {\n            for (List<String> group : properties.command.commandsList) {\n                String[] list = new String[group.size()];\n                String[] commands = group.toArray(list);\n                executeInNewTerminalTabs(menuFile.getParent(), commands);\n            }\n        }\n    }\n\n\n    private static void sleep(long ms) {\n        try {\n            Thread.sleep(ms);\n        } catch (InterruptedException ignore) {\n        }\n    }\n\n\n    private void executeInBackground(UserMenuItem properties) {\n        final AbstractFile folder = mainFrame.getActivePanel().getCurrentFolder();\n        UserMenuItem.Command cmd = properties.command;\n        if (cmd.isSingle()) {\n            new Thread(() -> {\n                try {\n                    ExecutorUtils.execute(cmd.singleCommand, folder);\n                } catch (IOException | InterruptedException e) {\n                    log.error(\"Single command error\", e);\n                }\n            }).start();\n        } else {\n            for (List<String> group : cmd.commandsList) {\n                new Thread(() -> {\n                    try {\n                        for (String command : group) {\n                            ExecutorUtils.execute(command, folder);\n                        }\n                    } catch (IOException | InterruptedException e) {\n                        log.error(\"Multiple commands error\", e);\n                    }\n                }).start();\n            }\n        }\n    }\n\n    private void executeInTerminal(UserMenuItem properties) {\n        AbstractFile home = menuFile.getParent();\n        if (properties.command.isSingle()) {\n            executeInNewTerminalWindow(home, properties.command.singleCommand);\n        } else {\n            for (List<String> group : properties.command.commandsList) {\n                String[] list = new String[group.size()];\n                String[] commands = group.toArray(list);\n                executeInNewTerminalWindow(home, commands);\n            }\n        }\n    }\n\n    private static String getDefaultTerminalCommand() {\n        return switch (OsFamily.getCurrent()) {\n            case WINDOWS -> \"cmd /c start cmd.exe /K \\\"cd /d $p\\\"\";\n            case LINUX -> \"\";\n            case MAC_OS_X -> \"open -a Terminal .\";\n            default -> \"\";\n        };\n    }\n\n    private static void executeInNewTerminalTabs(AbstractFile home, String[] commands) {\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            OSXTerminal.addNewTabWithCommands(home, commands);\n        } else {\n            // TODO\n            log.error(\"Operation not supported for {}\", OsFamily.getCurrent());\n        }\n        sleep(200);\n    }\n\n    private static void executeInNewTerminalWindow(AbstractFile home, String... commands) {\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            OSXTerminal.openNewWindowAndRun(home, commands);\n        } else {\n            // TODO\n            log.error(\"Operation not supported for {}\", OsFamily.getCurrent());\n        }\n        sleep(200);\n    }\n\n\n\n//    private static String getConsoleCommand(AbstractFile folder) {\n//        String cmd = getTerminalCommand();\n//        return cmd.replace(\"$p\", folder.getAbsolutePath());\n//    }\n//\n//    private static String getTerminalCommand() {\n//        switch (getTerminalType()) {\n//            case TERMINAL_DEFAULT:\n//                return getDefaultTerminalCommand();\n//            case TERMINAL_CUSTOM:\n//                return getCustomExternalTerminal();\n//            case TERMINAL_ITERM:\n//                return \"open -a iTerm .\"\n//        }\n//    }\n\n    private static String getCustomExternalTerminal() {\n        return TcConfigurations.getPreferences().getVariable(TcPreference.CUSTOM_EXTERNAL_TERMINAL);\n    }\n\n    private static int getTerminalType() {\n        return TcConfigurations.getPreferences().getVariable(TcPreference.EXTERNAL_TERMINAL_TYPE, TcPreferences.DEFAULT_TERMINAL_TYPE);\n    }\n\n\n\n    @Override\n    public void processKeyEvent(KeyEvent e, MenuElement[] path, MenuSelectionManager manager) {\n        if (e.getKeyCode() == KeyEvent.VK_F4 && e.getModifiersEx() == 0 && e.getID() == KeyEvent.KEY_PRESSED) {\n            openEditor();\n            e.consume();\n            return;\n        }\n        super.processKeyEvent(e, path, manager);\n    }\n\n\n    public void show(Component invoker) {\n        Dimension size = getPreferredSize();\n        int x = (invoker.getWidth() - size.width)/2;\n        int y = (invoker.getHeight() - size.height)/2;\n        show(invoker, x, y);\n        selectFirstItem();\n        requestFocus();\n    }\n\n    private void openEditor() {\n        EditorRegistrar.createEditorFrame(mainFrame, menuFile, ActionProperties.getActionIcon(EditAction.Descriptor.ACTION_ID).getImage());\n    }\n\n    @Override\n    public void popupMenuWillBecomeVisible(PopupMenuEvent e) {\n        mainFrame.getStatusBar().setStatusInfo(Translator.get(\"UserMenu.press_f4_to_edit_menu\"));\n    }\n\n    @Override\n    public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {\n        mainFrame.getStatusBar().activePanelChanged(mainFrame.getActivePanel());\n    }\n\n    @Override\n    public void popupMenuCanceled(PopupMenuEvent e) {\n\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/menu/package.html",
    "content": "<body>\n  Various menus used by muCommander.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/menu/usermenu/LoadUserMenuException.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/trolcommander\n * Copyright (C) 2014-2026 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.main.menu.usermenu;\n\nimport lombok.Getter;\n\npublic class LoadUserMenuException extends Exception {\n    @Getter\n    private final int line;\n\n    @Getter\n    private final int column;\n\n    public LoadUserMenuException(String message) {\n        super(message);\n        line = parseLine(message);\n        column = parseColumn(message);\n    }\n\n    private static int parseLine(String message) {\n        return parseInt(message, \", line \");\n    }\n\n    private static int parseColumn(String message) {\n        return parseInt(message, \", column \");\n    }\n\n    private static int parseInt(String message, String attrName) {\n        int indexStart = message.indexOf(attrName);\n        if (indexStart < 0) {\n            return -1;\n        }\n        indexStart += attrName.length();\n        int indexEnd1 = message.indexOf(',', indexStart);\n        int indexEnd2 = message.indexOf(':', indexStart);\n        int indexEnd = indexEnd1 > 0 && indexEnd1 < indexEnd2 ? indexEnd1 : indexEnd2;\n        if (indexEnd < 0) {\n            indexEnd = message.length() - 1;\n        }\n        try {\n            return Integer.parseInt(message.substring(indexStart, indexEnd));\n        } catch (Exception e) {\n            return -1;\n        }\n\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/menu/usermenu/UserMenuItem.java",
    "content": "package com.mucommander.ui.main.menu.usermenu;\n\nimport java.util.List;\n\npublic class UserMenuItem {\n\n    public enum ConsoleType {\n        SHOW,\n        NONE,\n        HIDE,\n        APPEND;\n\n        public static ConsoleType fromStr(String s) {\n            for (ConsoleType c : values()) {\n                if (c.name().equalsIgnoreCase(s)) {\n                    return c;\n                }\n            }\n            return HIDE;\n        }\n    }\n\n    public static class Command {\n        public final List<List<String>> commandsList;\n        public final String singleCommand;\n\n        public Command(String singleCommand) {\n            this.singleCommand = singleCommand;\n            this.commandsList = null;\n        }\n\n        public Command(List<List<String>> commandsList) {\n            this.singleCommand = null;\n            this.commandsList = commandsList;\n        }\n\n        public boolean isSingle() {\n            return singleCommand != null;\n        }\n    }\n\n    public final Command command;\n    public final ConsoleType console;\n\n    UserMenuItem(Command command, ConsoleType console) {\n        this.command = command;\n        this.console = console;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/menu/usermenu/UserPopupMenuLoader.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2018 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.main.menu.usermenu;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.helper.MnemonicHelper;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.menu.UserPopupMenu;\nimport org.jetbrains.annotations.Nullable;\nimport org.yaml.snakeyaml.Yaml;\nimport ru.trolsoft.ui.TMenuSeparator;\n\nimport javax.swing.*;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\n\npublic class UserPopupMenuLoader {\n\n    public static UserPopupMenu loadMenu(MainFrame mainFrame, AbstractFile file) throws IOException, LoadUserMenuException {\n        try (InputStream is = file.getInputStream()) {\n            Yaml yaml = new Yaml();\n            Map<String, Object> root = yaml.load(is);\n            if (root == null || !root.containsKey(\"menu\")) {\n                throw new LoadUserMenuException(\"Invalid YAML structure: 'menu' key not found\");\n            }\n            @SuppressWarnings(\"unchecked\")\n            List<Object> items = (List<Object>) root.get(\"menu\");\n            MnemonicHelper mnemonicHelper = new MnemonicHelper();\n            UserPopupMenu menu = new UserPopupMenu(mainFrame, file);\n            loadMenu(menu, null, items, mnemonicHelper);\n            return menu;\n        } catch (LoadUserMenuException e) {\n            throw e;\n        } catch (Exception e) {\n            throw new LoadUserMenuException(e.getMessage());\n        }\n    }\n\n    private static void loadMenu(UserPopupMenu menu, JMenu parent, List<Object> items, MnemonicHelper mnemonicHelper) throws LoadUserMenuException {\n        if (items == null) {\n            return;\n        }\n        for (Object obj : items) {\n            if (obj instanceof String objs && objs.equalsIgnoreCase(\"separator\")) {\n                menu.add(new TMenuSeparator());\n            } else if (obj instanceof Map) {\n                @SuppressWarnings(\"unchecked\")\n                Map<String, Object> item = (Map<String, Object>) obj;\n                String name = getItemProp(item, \"name\");\n                Object subItems = item.get(\"items\");\n                String key = getItemProp(item, \"key\");\n                if (name != null && subItems != null) { // Submenu\n                    JMenu submenu = new JMenu(name);\n                    if (key != null && !key.isEmpty()) {\n                        submenu.setMnemonic(KeyStroke.getKeyStroke(key).getKeyCode());\n                    } else {\n                        submenu.setMnemonic(mnemonicHelper.getMnemonic(name));\n                    }\n                    if (parent == null) {\n                        menu.add(submenu);\n                    } else {\n                        parent.add(submenu);\n                    }\n                    @SuppressWarnings(\"unchecked\")\n                    List<Object> subItemsList = (List<Object>) subItems;\n                    loadMenu(menu, submenu, subItemsList, new MnemonicHelper());\n                } else if (name != null) {\n                    UserMenuItem.Command command = getItemCommand(item);\n                    String console = getItemProp(item, \"console\");\n                    UserMenuItem properties = new UserMenuItem(command, UserMenuItem.ConsoleType.fromStr(console));\n                    JMenuItem mi = menu.add(parent, name, properties);\n                    mi.setMnemonic(mnemonicHelper.getMnemonic(name));\n                    if (key != null) {\n                        KeyStroke keyStroke = KeyStroke.getKeyStroke(key);\n                        mi.setAccelerator(keyStroke);\n                    }\n                } else {\n                    throw new LoadUserMenuException(\"Invalid item type at index: \" + items.indexOf(obj) + \", '\" + obj + \"'\");\n                }\n            } else {\n                throw new LoadUserMenuException(\"Invalid item type at index: \" + items.indexOf(obj) + \", '\" + obj + \"'\");\n            }\n        }\n    }\n\n    private static UserMenuItem.Command getItemCommand(Map<String, Object> item) {\n        if (!item.containsKey(\"command\")) {\n            return null;\n        }\n        Object cmd = item.get(\"command\");\n        if (cmd instanceof String) {\n            return new UserMenuItem.Command((String) cmd);\n        } else if (cmd instanceof List) {\n            List<List<String>> result = new ArrayList<>();\n            @SuppressWarnings(\"unchecked\")\n            List<Object> cmdList = (List<Object>) cmd;\n\n            boolean containsArrays = listContainsLists(cmdList);\n            if (containsArrays) {\n                for (Object o : cmdList) {\n                    List<String> group = new ArrayList<>();\n                    result.add(group);\n                    if (o instanceof List) {\n                        @SuppressWarnings(\"unchecked\")\n                        List<Object> groupList = (List<Object>) o;\n                        for (Object c : groupList) {\n                            if (c instanceof String) {\n                                group.add((String) c);\n                            } else {\n                                throw new RuntimeException(\"invalid command type: \" + item);\n                            }\n                        }\n                    }\n                }\n            } else {\n                List<String> group = new ArrayList<>();\n                result.add(group);\n                for (Object c : cmdList) {\n                    if (c instanceof String) {\n                        group.add((String) c);\n                    } else {\n                        throw new RuntimeException(\"invalid command type: \" + item);\n                    }\n                }\n            }\n            return new UserMenuItem.Command(result);\n        }\n        throw new RuntimeException(\"invalid command type: \" + item);\n    }\n\n    private static boolean listContainsLists(List<Object> list) {\n        for (Object o : list) {\n            if (o instanceof List) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    @Nullable\n    private static String getItemProp(Map<String, Object> item, String name) {\n        Object value = item.get(name);\n        return value != null ? value.toString() : null;\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/package.html",
    "content": "<body>\n  Main muCommander UI components.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/quicklist/BookmarksQL.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.quicklist;\r\n\r\nimport javax.swing.Icon;\r\n\r\nimport com.mucommander.bookmark.Bookmark;\r\nimport com.mucommander.bookmark.BookmarkListener;\r\nimport com.mucommander.bookmark.BookmarkManager;\r\nimport com.mucommander.commons.collections.AlteredVector;\r\nimport com.mucommander.commons.file.FileFactory;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.action.ActionProperties;\r\nimport com.mucommander.ui.action.impl.ShowBookmarksQLAction;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.quicklist.QuickListWithIcons;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\n/**\r\n * This quick list shows existing bookmarks.\r\n * \r\n * @author Arik Hadas\r\n */\r\npublic class BookmarksQL extends QuickListWithIcons<Bookmark> implements BookmarkListener {\r\n\tprivate Bookmark[] cachedBookmarks;\r\n\tprivate final FolderPanel folderPanel;\r\n\r\n\tpublic BookmarksQL(FolderPanel folderPanel) {\r\n\t\tsuper(folderPanel, ActionProperties.getActionLabel(ShowBookmarksQLAction.Descriptor.ACTION_ID), Translator.get(\"bookmarks_menu.no_bookmark\"));\r\n\t\t\r\n\t\tthis.folderPanel = folderPanel;\r\n\t\t\r\n\t\tbookmarksChanged();\r\n\t\tBookmarkManager.addBookmarkListener(this);\r\n\t}\r\n\r\n\t@Override\r\n    protected void acceptListItem(Bookmark item) {\r\n\t\tfolderPanel.tryChangeCurrentFolder(item.getLocation()); //change with text validate\r\n\t}\r\n\r\n\t@Override\r\n    protected Bookmark[] getData() {\r\n\t\treturn cachedBookmarks;\r\n\t}\r\n\t\r\n\t@Override\r\n    protected Icon itemToIcon(Bookmark item) {\r\n\t\treturn getIconOfFile(FileFactory.getFile(item.getLocation()));\r\n\t}\r\n\r\n\tpublic void bookmarksChanged() {\r\n\t\tcachedBookmarks = prepareBookmarks();\r\n\t}\r\n\r\n\tprivate static Bookmark[] prepareBookmarks() {\r\n\t\tList<Bookmark> outList = new ArrayList<>();\r\n\t\tAlteredVector<Bookmark> bookmarks = BookmarkManager.getBookmarks();\r\n\t\tfor (Bookmark bookmark : bookmarks) {\r\n\t\t\tif (!bookmark.getLocation().isEmpty() && !bookmark.getName().equals(BookmarkManager.BOOKMARKS_SEPARATOR)) {\r\n\t\t\t\toutList.add(bookmark);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tBookmark[] result = new Bookmark[outList.size()];\r\n\t\toutList.toArray(result);\r\n\t\treturn result;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/quicklist/EditAsQL.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2019 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.main.quicklist;\n\nimport com.mucommander.command.Command;\nimport com.mucommander.command.CommandManager;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.process.ProcessRunner;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.EditAction;\nimport com.mucommander.ui.action.impl.EditAsAction;\nimport com.mucommander.ui.dialog.InformationDialog;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.quicklist.QuickListWithDataList;\nimport com.mucommander.ui.quicklist.item.QuickListDataList;\nimport com.mucommander.ui.viewer.EditorFactory;\nimport com.mucommander.ui.viewer.EditorRegistrar;\nimport com.mucommander.ui.viewer.FileEditor;\nimport com.mucommander.ui.viewer.WarnUserException;\n\nimport java.awt.*;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class EditAsQL extends QuickListWithDataList<EditorFactory> {\n\n    private class CommandEditFactory implements EditorFactory {\n\n        private final Command cmd;\n\n        CommandEditFactory(Command cmd) {\n            this.cmd = cmd;\n        }\n\n        @Override\n        public boolean canEditFile(AbstractFile file) {\n            return CommandManager.checkFileMask(cmd, file);\n        }\n\n        @Override\n        public FileEditor createFileEditor() {\n            return null;\n        }\n\n        @Override\n        public String getName() {\n            return cmd.getDisplayName();// + \" (\" + cmd.getCommand() + \")\";\n        }\n\n        @Override\n        public String toString() {\n            return getName();\n        }\n        private void editFile(AbstractFile file) {\n            try {\n                ProcessRunner.execute(cmd.getTokens(file), file);\n            } catch(Exception e) {\n                InformationDialog.showErrorDialog(mainFrame.getJFrame());\n            }\n        }\n    }\n\n    private final AbstractFile file;\n    private final MainFrame mainFrame;\n\n    public EditAsQL(MainFrame mainFame, AbstractFile file) {\n        super(mainFame.getActivePanel(), ActionProperties.getActionLabel(EditAsAction.Descriptor.ACTION_ID), \"\");\n        this.file = file;\n        this.mainFrame = mainFame;\n    }\n\n\n    @Override\n    protected EditorFactory[] getData() {\n        if (file == null) {\n            return new EditorFactory[0];\n        }\n        // Builtin viewers\n        List<EditorFactory> factories = EditorRegistrar.getAllEditors(file);\n        List<EditorFactory> result = new ArrayList<>(factories.size());\n        for (final EditorFactory factory : factories) {\n            result.add(new EditorFactory() {\n\n                @Override\n                public boolean canEditFile(AbstractFile file) throws WarnUserException {\n                    return factory.canEditFile(file);\n                }\n\n                @Override\n                public FileEditor createFileEditor() {\n                    return factory.createFileEditor();\n                }\n\n                @Override\n                public String getName() {\n                    return factory.getName();\n                }\n\n                @Override\n                public String toString() {\n                    return getName();\n                }\n            });\n        }\n        // View commands\n        for (Command cmd : CommandManager.getCommands(CommandManager.EDITOR_ALIAS)) {\n            if (CommandManager.checkFileMask(cmd, file)) {\n                result.add(new EditAsQL.CommandEditFactory(cmd));\n            }\n        }\n        EditorFactory[] resultArray = new EditorFactory[result.size()];\n        resultArray = result.toArray(resultArray);\n        return resultArray;\n    }\n\n    @Override\n    protected void acceptListItem(EditorFactory item) {\n        if (item instanceof EditAsQL.CommandEditFactory) {\n            ((EditAsQL.CommandEditFactory) item).editFile(file);\n            return;\n        }\n        Image icon = ActionProperties.getActionIcon(EditAction.Descriptor.ACTION_ID).getImage();\n        EditorRegistrar.createEditorFrame(mainFrame, file, icon);//, item, null);\n    }\n\n    @Override\n    protected QuickListDataList<EditorFactory> getList() {\n        return new QuickListDataList<>(getData());\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/quicklist/EditorBookmarksQL.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2018 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.main.quicklist;\n\nimport com.mucommander.cache.TextHistory;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.action.impl.EditAction;\nimport com.mucommander.ui.action.impl.ShowEditorBookmarksQLAction;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.quicklist.QuickListWithIcons;\nimport com.mucommander.ui.viewer.EditorRegistrar;\nimport org.jetbrains.annotations.NotNull;\n\nimport javax.swing.*;\nimport java.awt.event.KeyEvent;\nimport java.io.IOException;\nimport java.util.LinkedList;\nimport java.util.List;\n\npublic class EditorBookmarksQL extends QuickListWithIcons<AbstractFile> {\n\n    private static final LinkedList<AbstractFile> LIST = new LinkedList<>();\n\n    private static final int MAX_FILES_IN_LIST = 500;\n\n    private final MainFrame mainFrame;\n\n\n    public EditorBookmarksQL(FolderPanel folderPanel)  {\n        super(folderPanel, ActionProperties.getActionLabel(ShowEditorBookmarksQLAction.Descriptor.ACTION_ID),\n                i18n(\"editor_bookmarks_quick_list.empty_message\"));\n        mainFrame = folderPanel.getMainFrame();\n    }\n\n    @Override\n    protected Icon itemToIcon(AbstractFile item) {\n        return TcAction.getStandardIcon(EditAction.class);\n    }\n\n    @Override\n    protected AbstractFile[] getData() {\n        List<String> list = TextHistory.getInstance().getList(TextHistory.Type.EDITOR_BOOKMARKS);\n        return buildFilesArray(list);\n    }\n\n    @NotNull\n    private static AbstractFile[] buildFilesArray(List<String> list) {\n        AbstractFile[] result = new AbstractFile[list.size()];\n        for (int i = 0; i < list.size(); i++) {\n            result[i] = FileFactory.getFile(list.get(i));\n        }\n        return result;\n    }\n\n    @Override\n    protected void acceptListItem(AbstractFile item) {\n        if (item != null && item.exists()) {\n            openFileInEditor(item);\n        } else {\n            mainFrame.getStatusBar().setStatusInfo(i18n(\"editor_bookmarks_quick_list.file_not_found\"));\n        }\n    }\n\n    @Override\n    protected void onShow() {\n        super.onShow();\n        mainFrame.getStatusBar().setStatusInfo(i18n(\"editor_bookmarks_quick_list.press_f4_to_edit_list\"));\n    }\n\n    protected void onHide() {\n        super.onHide();\n        mainFrame.getStatusBar().activePanelChanged(mainFrame.getActivePanel());\n    }\n\n    private void openFileInEditor(AbstractFile file) {\n        EditorRegistrar.createEditorFrame(mainFrame, file, ActionProperties.getActionIcon(EditAction.Descriptor.ACTION_ID).getImage());\n    }\n\n    public static void addFile(AbstractFile file) {\n        if (!LIST.remove(file) && LIST.size() > MAX_FILES_IN_LIST) {\n            LIST.removeLast();\n        }\n        LIST.addFirst(file);\n    }\n\n    @Override\n    public void keyPressed(KeyEvent e) {\n        if (e.getKeyCode() == KeyEvent.VK_F4 && e.getModifiersEx() == 0) {\n            e.consume();\n            openBookmarkFileInEditor();\n        }\n        super.keyPressed(e);\n    }\n\n    private void openBookmarkFileInEditor() {\n        try {\n            AbstractFile file = TextHistory.getHistoryFile(TextHistory.Type.EDITOR_BOOKMARKS);\n            setVisible(false);\n            SwingUtilities.invokeLater(() -> openFileInEditor(file));\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/quicklist/ParentFoldersQL.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2014 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.quicklist;\r\n\r\nimport java.util.List;\r\nimport java.util.LinkedList;\r\n\r\nimport javax.swing.Icon;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.action.ActionProperties;\r\nimport com.mucommander.ui.action.impl.ShowParentFoldersQLAction;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.quicklist.QuickListWithIcons;\r\n\r\n/**\r\n * This quick list shows the parent folders of the current location in the FileTable.\r\n * \r\n * @author Arik Hadas\r\n */\r\npublic class ParentFoldersQL extends QuickListWithIcons<AbstractFile> {\r\n\r\n\tprivate final FolderPanel folderPanel;\r\n\t\r\n\tpublic ParentFoldersQL(FolderPanel folderPanel) {\r\n\t\tsuper(folderPanel,\r\n                ActionProperties.getActionLabel(ShowParentFoldersQLAction.Descriptor.ACTION_ID),\r\n                Translator.get(\"parent_folders_quick_list.empty_message\"));\r\n\t\t\r\n\t\tthis.folderPanel = folderPanel;\r\n\t}\r\n\t\r\n\t@Override\r\n    protected void acceptListItem(AbstractFile item) {\r\n\t\tfolderPanel.tryChangeCurrentFolder(item);\r\n\t}\r\n\t\r\n\r\n\t@Override\r\n    public AbstractFile[] getData() {\r\n        List<AbstractFile> abstractFiles = populateParentFolders(folderPanel.getCurrentFolder());\r\n        return abstractFiles.toArray(new AbstractFile[0]);\r\n\t}\r\n\r\n\t@Override\r\n    protected Icon itemToIcon(AbstractFile item) {\r\n\t\treturn getIconOfFile(item);\r\n\t}\r\n\r\n    protected List<AbstractFile> populateParentFolders(AbstractFile folder) {\r\n        List<AbstractFile> parents = new LinkedList<>();\r\n        while ((folder = folder.getParent()) != null) {\r\n            parents.add(folder);\r\n        }\r\n        return parents;\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/quicklist/RecentEditedQL.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2014 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.main.quicklist;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.DummyFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.action.impl.EditAction;\nimport com.mucommander.ui.action.impl.ShowRecentEditedFilesQLAction;\nimport com.mucommander.ui.action.impl.ViewAction;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.quicklist.QuickListWithIcons;\nimport com.mucommander.ui.viewer.EditorRegistrar;\nimport com.mucommander.ui.viewer.text.TextFilesHistory;\n\nimport javax.swing.Icon;\nimport java.util.LinkedList;\nimport java.util.List;\n\n/**\n * @author Oleg Trifonov\n * Created on 01/07/14.\n */\npublic class RecentEditedQL extends QuickListWithIcons<AbstractFile> {\n\n    private static final LinkedList<AbstractFile> list = new LinkedList<>();\n\n    private static final int MAX_FILES_IN_LIST = 100;\n\n    private final MainFrame mainFrame;\n\n\n    public RecentEditedQL(FolderPanel folderPanel)  {\n        super(folderPanel, ActionProperties.getActionLabel(ShowRecentEditedFilesQLAction.Descriptor.ACTION_ID), Translator.get(\"recent_edited_files_quick_list.empty_message\"));\n        mainFrame = folderPanel.getMainFrame();\n    }\n\n    @Override\n    protected Icon itemToIcon(AbstractFile item) {\n        return TcAction.getStandardIcon(EditAction.class);\n    }\n\n    @Override\n    protected AbstractFile[] getData() {\n        List<AbstractFile> list = TextFilesHistory.getInstance().getLastList(MAX_FILES_IN_LIST);\n        return list.toArray(new AbstractFile[0]);\n    }\n\n    @Override\n    protected void acceptListItem(AbstractFile item) {\n        if (item instanceof DummyFile) {\n            item = FileFactory.getFile(item.getURL());\n        }\n        if (item != null && item.exists()) {\n            EditorRegistrar.createEditorFrame(mainFrame, item, ActionProperties.getActionIcon(ViewAction.Descriptor.ACTION_ID).getImage());\n        } else {\n            // TODO error message\n        }\n    }\n\n    public static void addFile(AbstractFile file) {\n        if (!list.remove(file) && list.size() > MAX_FILES_IN_LIST) {\n            list.removeLast();\n        }\n        list.addFirst(file);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/quicklist/RecentExecutedFilesQL.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.quicklist;\r\n\r\nimport java.io.IOException;\r\nimport java.util.LinkedList;\r\n\r\nimport javax.swing.Icon;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileProtocols;\r\nimport com.mucommander.commons.file.impl.local.LocalFile;\r\nimport com.mucommander.desktop.DesktopManager;\r\nimport com.mucommander.job.TempExecJob;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.action.ActionProperties;\r\nimport com.mucommander.ui.action.impl.ShowRecentExecutedFilesQLAction;\r\nimport com.mucommander.ui.dialog.file.ProgressDialog;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.WindowManager;\r\nimport com.mucommander.ui.quicklist.QuickListWithIcons;\r\n\r\n/**\r\n * This quick list shows recently executed files.\r\n * \r\n * @author Arik Hadas\r\n */\r\n\r\npublic class RecentExecutedFilesQL extends QuickListWithIcons<AbstractFile> {\r\n\tprivate static final LinkedList<AbstractFile> list = new LinkedList<>();\r\n\tprivate static final int MAX_NUM_OF_ELEMENTS = 10;\r\n//\tprivate FolderPanel folderPanel;\r\n\t\r\n\tpublic RecentExecutedFilesQL(FolderPanel folderPanel) {\r\n\t\tsuper(folderPanel, ActionProperties.getActionLabel(ShowRecentExecutedFilesQLAction.Descriptor.ACTION_ID), Translator.get(\"recent_executed_files_quick_list.empty_message\"));\r\n\t\t\r\n//\t\tthis.folderPanel = folderPanel;\r\n\t}\r\n\t\r\n\t@Override\r\n    protected void acceptListItem(AbstractFile item) {\r\n\t\tMainFrame mainFrame = WindowManager.getCurrentMainFrame();\r\n\r\n\t\tif (item.getURL().getScheme().equals(FileProtocols.FILE) && (item.hasAncestor(LocalFile.class))) {\r\n            try {\r\n                DesktopManager.open(item);\r\n            } catch(IOException e) {\r\n                e.printStackTrace();\r\n            }\r\n        } else {\r\n            // Copies non-local file in a temporary local file and opens them using their native association.\r\n            ProgressDialog progressDialog = new ProgressDialog(mainFrame, Translator.get(\"copy_dialog.copying\"));\r\n            TempExecJob job = new TempExecJob(progressDialog, mainFrame, item);\r\n            progressDialog.start(job);\r\n        }\r\n\t}\r\n\t\r\n\tpublic static void addFile(AbstractFile file) {\r\n\t\tif (!list.remove(file) && list.size() > MAX_NUM_OF_ELEMENTS) {\r\n            list.removeLast();\r\n        }\r\n\t\tlist.addFirst(file);\r\n\t}\r\n\r\n\t@Override\r\n    protected AbstractFile[] getData() {\r\n\t\treturn list.toArray(new AbstractFile[0]);\r\n\t}\r\n\r\n\t@Override\r\n    protected Icon itemToIcon(AbstractFile item) {\r\n\t\treturn getIconOfFile(item);\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/quicklist/RecentLocationsQL.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.quicklist;\r\n\r\nimport java.util.LinkedList;\r\nimport java.util.List;\r\n\r\nimport javax.swing.Icon;\r\n\r\nimport com.mucommander.commons.file.FileFactory;\r\nimport com.mucommander.commons.file.FileProtocols;\r\nimport com.mucommander.commons.file.FileURL;\r\nimport com.mucommander.commons.file.impl.local.LocalFile;\r\nimport com.mucommander.core.GlobalLocationHistory;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.action.ActionProperties;\r\nimport com.mucommander.ui.action.impl.ShowRecentLocationsQLAction;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.quicklist.QuickListWithIcons;\r\n\r\n/**\r\n * This quick list shows recently accessed locations.\r\n *\r\n * @author Arik Hadas\r\n */\r\npublic class RecentLocationsQL extends QuickListWithIcons<RecentLocationsQL.RecentLocation> {\r\n\r\n    private final FolderPanel folderPanel;\r\n\r\n    public RecentLocationsQL(FolderPanel folderPanel) {\r\n        super(folderPanel,\r\n                ActionProperties.getActionLabel(ShowRecentLocationsQLAction.Descriptor.ACTION_ID),\r\n                Translator.get(\"recent_locations_quick_list.empty_message\"));\r\n\r\n        this.folderPanel = folderPanel;\r\n    }\r\n\r\n    @Override\r\n    protected void acceptListItem(RecentLocation item) {\r\n        folderPanel.tryChangeCurrentFolder(item.url);\r\n    }\r\n\r\n    @Override\r\n    public RecentLocation[] getData() {\r\n        List<RecentLocation> list = new LinkedList<>();\r\n        for (FileURL url : GlobalLocationHistory.getInstance().getHistory()) {\r\n            // Don't include the currently presented location in the list\r\n            if (url.equals(folderPanel.getCurrentFolder().getURL())) {\r\n                continue;\r\n            }\r\n\r\n            list.add(0, new RecentLocation(url));\r\n        }\r\n\r\n        return list.toArray(new RecentLocation[0]);\r\n    }\r\n\r\n    @Override\r\n    protected Icon itemToIcon(RecentLocation item) {\r\n        return getIconOfFile(FileFactory.getFile(item.url));\r\n    }\r\n\r\n    class RecentLocation {\r\n        private final FileURL url;\r\n\r\n        RecentLocation(FileURL url) {\r\n            this.url = url;\r\n        }\r\n\r\n        @Override\r\n        public String toString() {\r\n            if (!FileProtocols.FILE.equals(url.getScheme())) {\r\n                return url.toString();\r\n            }\r\n\r\n            String path = url.getPath();\r\n            if (LocalFile.USES_ROOT_DRIVES && !path.isEmpty()) {\r\n                path = path.substring(1);\r\n            }\r\n\r\n            return path;\r\n        }\r\n\r\n        @Override\r\n        public boolean equals(Object obj) {\r\n            if (obj instanceof RecentLocation) {\r\n                return url.equals(((RecentLocation) obj).url);\r\n            }\r\n            return false;\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/quicklist/RecentViewedQL.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2014 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.main.quicklist;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.DummyFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.action.impl.ShowRecentViewedFilesQLAction;\nimport com.mucommander.ui.action.impl.ViewAction;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.quicklist.QuickListWithIcons;\nimport com.mucommander.ui.viewer.ViewerRegistrar;\nimport com.mucommander.ui.viewer.text.TextFilesHistory;\n\nimport javax.swing.Icon;\nimport java.util.List;\n\n/**\n * @author Oleg Trifonv\n * Created on 01/07/14.\n */\npublic class RecentViewedQL extends QuickListWithIcons<AbstractFile> {\n\n    private static final int MAX_FILES_IN_LIST = 50;\n\n    private final MainFrame mainFrame;\n\n    public RecentViewedQL(FolderPanel folderPanel) {\n        super(folderPanel, ActionProperties.getActionLabel(ShowRecentViewedFilesQLAction.Descriptor.ACTION_ID), Translator.get(\"recent_viewed_files_quick_list.empty_message\"));\n        this.mainFrame = folderPanel.getMainFrame();\n    }\n\n    @Override\n    protected Icon itemToIcon(AbstractFile item) {\n        return TcAction.getStandardIcon(ViewAction.class);\n    }\n\n    @Override\n    protected AbstractFile[] getData() {\n        List<AbstractFile> list = TextFilesHistory.getInstance().getLastList(MAX_FILES_IN_LIST);\n        return list.toArray(new AbstractFile[0]);\n    }\n\n    @Override\n    protected void acceptListItem(AbstractFile item) {\n        if (item instanceof DummyFile) {\n            item = FileFactory.getFile(item.getURL());\n        }\n        if (item.exists()) {\n            ViewerRegistrar.createViewerFrame(mainFrame, item, ActionProperties.getActionIcon(ViewAction.Descriptor.ACTION_ID).getImage());\n        } else {\n            // TODO error message\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/quicklist/RootFoldersQL.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.quicklist;\n\nimport javax.swing.Icon;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.ShowRootFoldersQLAction;\nimport com.mucommander.ui.icon.FileIcons;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.quicklist.QuickListWithIcons;\n\n/**\n * This quick list shows roots of partitions.\n * \n * @author Arik Hadas\n */\npublic class RootFoldersQL extends QuickListWithIcons<AbstractFile> {\n\t\n\tprivate final FolderPanel folderPanel;\n\t\n\tpublic RootFoldersQL(FolderPanel folderPanel) {\n\t\tsuper(folderPanel, ActionProperties.getActionLabel(ShowRootFoldersQLAction.Descriptor.ACTION_ID), Translator.get(\"roots_quick_list.empty_message\"));\n\t\t\n\t\tthis.folderPanel = folderPanel;\n\t}\n\t\n\t@Override\n\tprotected Icon itemToIcon(AbstractFile item) {\n\t\treturn FileIcons.hasProperSystemIcons()?FileIcons.getSystemFileIcon(item):null;\n\t}\n\n\t@Override\n\tprotected AbstractFile[] getData() {\n\t\treturn LocalFile.getVolumes();\n\t}\n\n\t@Override\n\tprotected void acceptListItem(AbstractFile item) {\n\t\tfolderPanel.tryChangeCurrentFolder(item);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/quicklist/TabsQL.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.quicklist;\n\nimport java.awt.Dimension;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\n\nimport javax.swing.Icon;\n\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.ShowTabsQLAction;\nimport com.mucommander.ui.icon.EmptyIcon;\nimport com.mucommander.ui.icon.IconManager;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.main.tabs.FileTableTab;\nimport com.mucommander.ui.main.tabs.FileTableTabHeader;\nimport com.mucommander.ui.main.tabs.PrintableFileTableTabFactory;\nimport com.mucommander.ui.quicklist.QuickListWithIcons;\nimport com.mucommander.ui.tabs.TabFactory;\n\n/**\n * This quick list shows the tabs contained in the FolderPanel.\n * \n * @author Arik Hadas\n */\npublic class TabsQL extends QuickListWithIcons<FileTableTab> {\n\n\t/** The FolderPanel that contains the tabs */\n\tprivate final FolderPanel folderPanel;\n\t\n\tprivate final TabFactory<FileTableTab, FileTableTab> tabsFactory = new PrintableFileTableTabFactory();\n\t\n\tprivate final Icon lockedTabIcon = IconManager.getIcon(IconManager.IconSet.COMMON, FileTableTabHeader.LOCKED_ICON_NAME);\n\tprivate final Icon unlockedTabIcon = new EmptyIcon(8, 9);\n\t\n\tpublic TabsQL(FolderPanel folderPanel) {\n\t\tsuper(folderPanel, ActionProperties.getActionLabel(ShowTabsQLAction.Descriptor.ACTION_ID), Translator.get(\"tabs_quick_list.empty_message\"));\n\t\t\n\t\tthis.folderPanel = folderPanel;\n\t}\n\n\t@Override\n\tprotected Icon getImageIconOfItemImp(final FileTableTab item,  final Dimension preferredSize) {\n\t\treturn itemToIcon(item);\n\t}\n\n\t@Override\n\tprotected Icon itemToIcon(FileTableTab item) {\n\t\treturn item.isLocked() ? lockedTabIcon : unlockedTabIcon;\n\t}\n\n\tprotected FileTableTab[] getData() {\n\t\tList<FileTableTab> tabsList = new ArrayList<>();\n\t\tIterator<FileTableTab> tabsIterator = folderPanel.getTabs().iterator();\n\t\t\n\t\twhile (tabsIterator.hasNext()) {\n\t\t\ttabsList.add(tabsFactory.createTab(tabsIterator.next()));\n\t\t}\n\t\t\n\t\t// Remove the selected tab from the list\n\t\ttabsList.remove(folderPanel.getTabs().getSelectedIndex());\n\t\t\n\t\treturn tabsList.toArray(new FileTableTab[0]);\n\t}\n\n\t@Override\n\tprotected void acceptListItem(FileTableTab item) {\n\t\tfolderPanel.getTabs().selectTab(item);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/quicklist/ViewAsQL.java",
    "content": "/*\r\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\r\n * Copyright (C) 2013-2019 Oleg Trifonov\r\n *\r\n * trolCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * trolCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\npackage com.mucommander.ui.main.quicklist;\r\n\r\nimport com.mucommander.command.Command;\r\nimport com.mucommander.command.CommandManager;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.process.ProcessRunner;\r\nimport com.mucommander.ui.action.ActionProperties;\r\nimport com.mucommander.ui.action.impl.ViewAction;\r\nimport com.mucommander.ui.action.impl.ViewAsAction;\r\nimport com.mucommander.ui.dialog.InformationDialog;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.quicklist.QuickListWithDataList;\r\nimport com.mucommander.ui.quicklist.item.QuickListDataList;\r\nimport com.mucommander.ui.viewer.FileViewer;\r\nimport com.mucommander.ui.viewer.ViewerFactory;\r\nimport com.mucommander.ui.viewer.ViewerRegistrar;\r\nimport com.mucommander.ui.viewer.WarnUserException;\r\n\r\nimport java.awt.Image;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\n/**\r\n * @author Oleg Trifonov\r\n * Created on 02/07/14.\r\n */\r\npublic class ViewAsQL extends QuickListWithDataList<ViewerFactory> {\r\n\r\n    private class CommandViewFactory implements ViewerFactory {\r\n\r\n        private final Command cmd;\r\n\r\n        CommandViewFactory(Command cmd) {\r\n            this.cmd = cmd;\r\n        }\r\n\r\n        @Override\r\n        public boolean canViewFile(AbstractFile file) {\r\n            return CommandManager.checkFileMask(cmd, file);\r\n        }\r\n\r\n        @Override\r\n        public FileViewer createFileViewer() {\r\n            return null;\r\n        }\r\n\r\n        @Override\r\n        public String getName() {\r\n            return cmd.getDisplayName();// + \" (\" + cmd.getCommand() + \")\";\r\n        }\r\n\r\n        @Override\r\n        public String toString() {\r\n            return getName();\r\n        }\r\n\r\n        private void viewFile(AbstractFile file) {\r\n            try {\r\n                ProcessRunner.execute(cmd.getTokens(file), file);\r\n            } catch(Exception e) {\r\n                InformationDialog.showErrorDialog(mainFrame.getJFrame());\r\n            }\r\n        }\r\n    }\r\n\r\n    private final AbstractFile file;\r\n    private final MainFrame mainFrame;\r\n\r\n    public ViewAsQL(MainFrame mainFame, AbstractFile file) {\r\n        super(mainFame.getActivePanel(), ActionProperties.getActionLabel(ViewAsAction.Descriptor.ACTION_ID), \"\");\r\n        this.file = file;\r\n        this.mainFrame = mainFame;\r\n    }\r\n\r\n\r\n    @Override\r\n    protected ViewerFactory[] getData() {\r\n        if (file == null) {\r\n            return new ViewerFactory[0];\r\n        }\r\n        // Builtin viewers\r\n        List<ViewerFactory> factories = ViewerRegistrar.getAllViewers(file);\r\n        List<ViewerFactory> result = new ArrayList<>(factories.size());\r\n        for (final ViewerFactory factory : factories) {\r\n            result.add(new ViewerFactory() {\r\n\r\n                @Override\r\n                public boolean canViewFile(AbstractFile file) throws WarnUserException {\r\n                    return factory.canViewFile(file);\r\n                }\r\n\r\n                @Override\r\n                public FileViewer createFileViewer() {\r\n                    return factory.createFileViewer();\r\n                }\r\n\r\n                @Override\r\n                public String getName() {\r\n                    return factory.getName();\r\n                }\r\n\r\n                @Override\r\n                public String toString() {\r\n                    return getName();\r\n                }\r\n            });\r\n        }\r\n        // View commands\r\n        for (Command cmd : CommandManager.getCommands(CommandManager.VIEWER_ALIAS)) {\r\n            if (CommandManager.checkFileMask(cmd, file)) {\r\n                result.add(new CommandViewFactory(cmd));\r\n            }\r\n        }\r\n        ViewerFactory[] resultArray = new ViewerFactory[result.size()];\r\n        resultArray = result.toArray(resultArray);\r\n        return resultArray;\r\n    }\r\n\r\n    @Override\r\n    protected void acceptListItem(ViewerFactory item) {\r\n        if (item instanceof CommandViewFactory) {\r\n            ((CommandViewFactory) item).viewFile(file);\r\n            return;\r\n        }\r\n        Image icon = ActionProperties.getActionIcon(ViewAction.Descriptor.ACTION_ID).getImage();\r\n        ViewerRegistrar.createViewerFrame(mainFrame, file, icon, item, null);\r\n    }\r\n\r\n    @Override\r\n    protected QuickListDataList<ViewerFactory> getList() {\r\n        return new QuickListDataList<>(getData());\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/quicklist/ViewedAndEditedFilesQL.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.main.quicklist;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.quicklist.QuickListContainer;\nimport com.mucommander.ui.quicklist.QuickListWithIcons;\nimport com.mucommander.ui.viewer.FileFrame;\nimport com.mucommander.ui.viewer.FileViewersList;\n\nimport javax.swing.Icon;\nimport java.awt.event.KeyAdapter;\nimport java.awt.event.KeyEvent;\nimport java.util.List;\n\n/**\n * @author Oleg Trifonov\n * Created on 28/07/16.\n */\npublic class ViewedAndEditedFilesQL extends QuickListWithIcons<AbstractFile> {\n\n    private final List<FileViewersList.FileRecord> files = FileViewersList.getFiles();\n    private final AbstractFile currentFile;\n    private int currentFileIndex = -1;\n\n    public ViewedAndEditedFilesQL(QuickListContainer container, AbstractFile currentFile) {\n        super(container, Translator.get(\"file_editor.files.list\"), \"\");\n        this.currentFile = currentFile;\n\n        dataList.addKeyListener(new KeyAdapter() {\n            @Override\n            public void keyPressed(KeyEvent e) {\n                if (e.getKeyCode() == KeyEvent.VK_TAB) {\n                    selectNext();\n                    e.consume();\n                }\n            }\n\n            @Override\n            public void keyReleased(KeyEvent e) {\n                int mask = OsFamily.MAC_OS_X.isCurrent() ? KeyEvent.VK_ALT : KeyEvent.VK_CONTROL;\n                if (e.getKeyCode() == mask) {\n                    setVisible(false);\n                    acceptListItem(dataList.getSelectedValue());\n                }\n            }\n        });\n    }\n\n    @Override\n    protected Icon itemToIcon(AbstractFile item) {\n        for (FileViewersList.FileRecord rec : files) {\n            if (rec.fileName.equals(item.getAbsolutePath())) {\n                return rec.getIcon();\n            }\n        }\n        return null;\n    }\n\n    @Override\n    protected AbstractFile[] getData() {\n        AbstractFile[] result = new AbstractFile[files.size()];\n        for (int i = 0 ; i < files.size(); i++) {\n            result[i] = FileFactory.getFile(files.get(i).fileName);\n            if (result[i].equals(currentFile)) {\n                currentFileIndex = i;\n            }\n        }\n        return result;\n    }\n\n    @Override\n    public void show() {\n        super.show();\n        if (currentFileIndex >= 0) {\n            dataList.setSelectedIndex(currentFileIndex);\n            selectNext();\n        }\n    }\n\n\n    @Override\n    protected void acceptListItem(AbstractFile item) {\n        if (item == dataList.getSelectedValue()) {\n            FileFrame frame = files.get(dataList.getSelectedIndex()).fileFrameRef.get();\n            if (frame != null) {\n                frame.toFront();\n                return;\n            }\n        }\n        // theoretically these next code will execute newer\n        for (FileViewersList.FileRecord rec : files) {\n            if (rec.fileName.equals(item.getAbsolutePath())) {\n                FileFrame frame = rec.fileFrameRef.get();\n                if (frame != null) {\n                    frame.toFront();\n                }\n                return;\n            }\n        }\n    }\n\n\n    private void selectNext() {\n        int selected = dataList.getSelectedIndex();\n        selected++;\n        if (selected >= dataList.getModel().getSize()) {\n            selected = 0;\n        }\n        dataList.setSelectedIndex(selected);\n    }\n\n\n    public boolean isEmpty() {\n        return files.isEmpty();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/statusbar/FileWindowsListButton.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.main.statusbar;\n\nimport com.jidesoft.swing.JideSplitButton;\nimport com.mucommander.RuntimeConstants;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.WindowManager;\nimport com.mucommander.ui.text.FontUtils;\nimport com.mucommander.ui.viewer.FileViewersList;\nimport com.mucommander.utils.FileIconsCache;\n\nimport javax.swing.Icon;\nimport javax.swing.JFrame;\nimport javax.swing.JMenuItem;\nimport javax.swing.SwingUtilities;\n\nimport java.awt.Window;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static com.mucommander.ui.viewer.FileViewersList.FileRecord;\n\n/**\n * Created on 09/06/16.\n * @author Oleg Trifonov\n */\npublic class FileWindowsListButton extends JideSplitButton {\n    private long lastUpdateTime;\n    private FileRecord selectedRecord;\n    private MainFrame selectedMainFrame;\n    private final boolean includeMainFrames;\n\n\n    public FileWindowsListButton(boolean includeMainFrames) {\n        super();\n        this.includeMainFrames = includeMainFrames;\n        if (OsFamily.getCurrent() == OsFamily.LINUX && RuntimeConstants.DISPLAY_4K) {\n            FontUtils.scaleFont(this, 18, 22);\n        }\n        updateList();\n        addActionListener(e -> showSelectedFile());\n    }\n\n    FileWindowsListButton() {\n        this(false);\n    }\n\n    private void showSelectedFile() {\n        if (selectedRecord == null) {\n            if (selectedMainFrame != null) {\n                setText(mainFrameName(selectedMainFrame));\n                setIcon(getIconFrom(selectedMainFrame));\n                selectedMainFrame.toFront();\n            }\n            return;\n        }\n        JFrame fileFrame = selectedRecord.fileFrameRef.get();\n        if (fileFrame != null) {\n            fileFrame.toFront();\n        }\n    }\n\n\n    private void selectFile(FileRecord fileRecord) {\n        setText(fileRecord.shortName);\n        setIcon(getIconFrom(fileRecord));\n        selectedRecord = fileRecord;\n        showSelectedFile();\n    }\n\n\n    private void updateList() {\n        removeAll();\n        if (includeMainFrames) {\n            List<MainFrame> mainFrames = WindowManager.getMainFrames();\n            for (MainFrame mainFrame : mainFrames) {\n                String name = mainFrameName(mainFrame);\n                JMenuItem menuItem = new JMenuItem(name, getIconFrom(mainFrame));\n                menuItem.addActionListener(e -> mainFrame.toFront());\n                add(menuItem);\n            }\n\n        }\n        boolean containsSelected = false;\n        List<FileRecord> list = new ArrayList<>(FileViewersList.getFiles());\n        for (FileRecord fr : list) {\n            Window excludedFrame = SwingUtilities.getWindowAncestor(this);\n            if (fr.fileFrameRef.get() == excludedFrame) {\n                continue;\n            }\n            JMenuItem menuItem = new JMenuItem(fr.fileName, getIconFrom(fr));\n            menuItem.addActionListener(e -> selectFile(fr));\n            add(menuItem);\n\n            if (fr == selectedRecord) {\n                containsSelected = true;\n            }\n        }\n\n        if (!containsSelected) {\n            selectedRecord = null;\n        }\n\n        if (getMenuComponentCount() > 0) {\n            if (selectedRecord == null) {\n                if (includeMainFrames) {\n                    selectedRecord = null;\n                    selectedMainFrame = WindowManager.getCurrentMainFrame();\n                    setText(mainFrameName(selectedMainFrame));\n                    setIcon(getIconFrom(selectedMainFrame));\n                } else {\n                    selectedRecord = FileViewersList.getFiles().get(0);\n                    setText(selectedRecord.shortName);\n                    setIcon(getIconFrom(selectedRecord));\n                }\n            }\n            setVisible(true);\n        } else {\n            setVisible(false);\n            selectedRecord = null;\n        }\n        lastUpdateTime = System.currentTimeMillis();\n    }\n\n    private static String mainFrameName(MainFrame mainFrame) {\n        return mainFrame.getLeftPanel().getCurrentFolder().getName() + \" : \" + mainFrame.getRightPanel().getCurrentFolder().getName();\n    }\n\n    private Icon getIconFrom(MainFrame mainFrame) {\n        return FileIconsCache.getInstance().getIcon(mainFrame.getActivePanel().getCurrentFolder());\n    }\n\n    private Icon getIconFrom(FileRecord fileRecord) {\n        return fileRecord == null ? null : fileRecord.getIcon();\n    }\n\n    @Override\n    public boolean isVisible() {\n        if (lastUpdateTime < FileViewersList.getLastUpdateTime()) {\n            SwingUtilities.invokeLater(this::updateList);\n        }\n        return super.isVisible();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/statusbar/HeapIndicator.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.statusbar;\n\nimport com.mucommander.cache.TextHistory;\nimport com.mucommander.cache.WindowsStorage;\nimport com.mucommander.ui.border.MutableLineBorder;\nimport com.mucommander.ui.theme.*;\nimport com.mucommander.utils.FileIconsCache;\n\nimport javax.swing.JLabel;\nimport javax.swing.Timer;\nimport java.awt.Color;\nimport java.awt.Dimension;\nimport java.awt.Graphics;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.awt.event.MouseEvent;\nimport java.awt.event.MouseListener;\n\n/**\n * @author Oleg Trifonov\n * Created on 13/11/14.\n */\npublic class HeapIndicator extends JLabel implements ActionListener, ThemeListener, MouseListener {\n\n    private Timer timer;\n    private int refreshInterval;\n\n    private long usedMem;\n    private long totalMem;\n    private static final Color COLOR_BORDER = new Color(0x555555);\n    private static final Color COLOR_FOREGROUND = new Color(0x8888ff);\n\n    HeapIndicator() {\n        super(\"\");\n        setHorizontalAlignment(CENTER);\n        setRefreshInterval(1000*10);\n        update();\n//        setMinimumSize(new Dimension(80, 0));\n//        setMaximumSize(new Dimension(80, 100));\n        addMouseListener(this);\n        setBorder(new MutableLineBorder(ThemeManager.getCurrentColor(Theme.STATUS_BAR_BORDER_COLOR)));\n    }\n\n    @Override\n    public Dimension getPreferredSize() {\n        Dimension d = super.getPreferredSize();\n        return new Dimension(d.width+4, d.height+2);\n    }\n\n    @Override\n    public void paint(Graphics g) {\n        int width = getWidth();\n        int height = getHeight();\n\n        g.setColor(COLOR_BORDER);\n        g.drawRect(0, 0, width-1, height-1);\n        g.setColor(COLOR_FOREGROUND);\n        int x2 = (int)(width*((float)usedMem/(float)totalMem));\n        g.fillRect(1, 1, x2-1, height-2);\n\n        super.paint(g);\n    }\n\n    private static long bytesToKb(long bytes) {\n        return bytes / 1024L;\n    }\n\n    private static long bytesToMb(long bytes) {\n        return bytes / 1024L / 1024L;\n    }\n\n    /**\n     * Updates heap memory information.\n     */\n    private void update() {\n        totalMem = Runtime.getRuntime().totalMemory();\n        usedMem = totalMem - Runtime.getRuntime().freeMemory();\n        int percent = (int)(100*usedMem/totalMem);\n        //setText(\" \" + bytesToMb(usedMem) + \" MB \");\n        setText(\" \" + bytesToMb(totalMem) + \" MB \");\n        setToolTipText(\"Memory used \" + bytesToMb(usedMem) + \"MB  from \" + bytesToMb(totalMem) + \"MB  \" + percent + \"%\");\n    }\n\n    private void installTimer(int interval) {\n        if (timer == null) {\n            timer = new Timer(interval, this);\n        } else {\n            timer.stop();\n            timer.setDelay(interval);\n        }\n        timer.start();\n    }\n\n    private void uninstallTimer() {\n        if (timer != null) {\n            timer.stop();\n            timer.removeActionListener(this);\n            timer = null;\n        }\n    }\n\n    private void setRefreshInterval(int interval) {\n        this.refreshInterval = interval;\n        installTimer(interval);\n    }\n\n    @Override\n    public void actionPerformed(ActionEvent e) {\n        update();\n        repaint();\n    }\n\n    @Override\n    public void setVisible(boolean visible) {\n        if (visible) {\n            installTimer(refreshInterval);\n        } else {\n            uninstallTimer();\n        }\n        super.setVisible(visible);\n    }\n\n    @Override\n    public void colorChanged(ColorChangedEvent event) {\n\n    }\n\n    @Override\n    public void fontChanged(FontChangedEvent event) {\n\n    }\n\n    @Override\n    public void mouseClicked(MouseEvent e) {\n        TextHistory.getInstance().clear();\n        WindowsStorage.getInstance().clear();\n        FileIconsCache.getInstance().clear();\n        System.gc();\n        update();\n        repaint();\n    }\n\n    @Override\n    public void mousePressed(MouseEvent e) {\n\n    }\n\n    @Override\n    public void mouseReleased(MouseEvent e) {\n\n    }\n\n    @Override\n    public void mouseEntered(MouseEvent e) {\n\n    }\n\n    @Override\n    public void mouseExited(MouseEvent e) {\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/statusbar/StatusBar.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.statusbar;\n\nimport com.mucommander.cache.FastLRUCache;\nimport com.mucommander.cache.LRUCache;\nimport com.mucommander.commons.conf.ConfigurationEvent;\nimport com.mucommander.commons.conf.ConfigurationListener;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.impl.CachedFile;\nimport com.mucommander.commons.file.impl.ftp.FTPFile;\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport com.mucommander.commons.file.impl.sftp.SFTPFile;\nimport com.mucommander.commons.file.util.SymLinkUtils;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.desktop.DesktopManager;\nimport com.mucommander.utils.text.SizeFormat;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.action.ActionManager;\nimport com.mucommander.ui.event.ActivePanelListener;\nimport com.mucommander.ui.event.LocationEvent;\nimport com.mucommander.ui.event.LocationListener;\nimport com.mucommander.ui.event.TableSelectionListener;\nimport com.mucommander.ui.icon.SpinningDial;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.table.FileTable;\nimport com.mucommander.ui.main.table.views.BaseFileTableModel;\nimport com.mucommander.ui.theme.*;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport ru.trolsoft.utils.ImageSizeDetector;\nimport ru.trolsoft.utils.JavaClassVersionDetector;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.ComponentEvent;\nimport java.awt.event.ComponentListener;\nimport java.awt.event.MouseEvent;\nimport java.awt.event.MouseListener;\nimport java.awt.image.BufferedImage;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n\n/**\n * StatusBar is the component that sits at the bottom of each MainFrame, between the folder panels and command bar.\n * There is one and only one StatusBar per MainFrame, created by the associated MainFrame. It can be hidden,\n * but the instance will always remain, until the MainFrame is disposed.\n *\n * <p>StatusBar is used to display info about the total/selected number of files in the current folder and current volume's\n * free/total space. When a folder is being changed, a waiting message is displayed. When quick search is being used,\n * the current quick search string is displayed.\n *\n * <p>StatusBar receives LocationListener events when the folder has or is being changed, and automatically updates\n * selected files and volume info, and display the waiting message when the folder is changing. Quick search info\n * is set by FileTable.QuickSearch.\n *\n * <p>When StatusBar is visible, a Thread runs in the background to periodically update free/total space volume info.\n * This thread stops when the StatusBar is hidden.\n *\n * @author Maxence Bernard\n */\npublic class StatusBar extends JPanel implements Runnable, MouseListener, ActivePanelListener, TableSelectionListener, LocationListener, ComponentListener, ThemeListener {\n    private static final Logger LOGGER = LoggerFactory.getLogger(StatusBar.class);\n\n    private final MainFrame mainFrame;\n\n    /**\n     * Label that displays info about current selected file(s)\n     */\n    private final JLabel selectedFilesLabel;\n\n    /**\n     * Icon used while loading is in progress.\n     */\n    private final SpinningDial dial;\n\n    /**\n     * Label that displays info about current volume (free/total space)\n     */\n    private final VolumeSpaceLabel volumeSpaceLabel;\n\n    private final TaskPanel taskPanel;\n\n    private final JProgressBar progressBar;\n\n    private final Component progressGlue;\n\n    /**\n     * Thread which auto updates volume info\n     */\n    private Thread autoUpdateThread;\n\n    /**\n     * Number of volume info strings that can be temporarily cached\n     */\n    private final static int VOLUME_INFO_CACHE_CAPACITY = 50;\n\n    /**\n     * Number of milliseconds before cached volume info strings expire\n     */\n    private final static int VOLUME_INFO_TIME_TO_LIVE = 60000;\n\n    /**\n     * Number of milliseconds between each volume info update by auto-update thread\n     */\n    private final static int AUTO_UPDATE_PERIOD = 60_000;\n\n    /**\n     * Caches volume info strings (free/total space) for a while, since this information is expensive to retrieve\n     * (I/O bound). This map uses folders' volume path as its key.\n     */\n    private static final LRUCache<String, Long[]> volumeInfoCache = new FastLRUCache<>(VOLUME_INFO_CACHE_CAPACITY);\n\n    /**\n     * Icon that is displayed when folder is changing\n     */\n    public final static String WAITING_ICON = \"waiting.png\";\n\n    /**\n     * Listens to configuration changes and updates static fields accordingly\n     */\n    private final static ConfigurationListener CONFIGURATION_ADAPTER;\n\n    /**\n     * SizeFormat format used to create the selected file(s) size string\n     */\n    private static int selectedFileSizeFormat;\n\n    private final static ExtensionFilenameFilter SUPPORTED_IMAGE_FILTER = new ExtensionFilenameFilter(new String[]{\n            \".png\", \".gif\", \".jpg\", \".jpeg\", \".bmp\", \".tga\", \".tiff\", \".tif\"});\n\n    private final static ExtensionFilenameFilter JAVA_CLASS_FILTER = new ExtensionFilenameFilter(\".class\");\n\n\n    static {\n        // Initialize the size column format based on the configuration\n        setSelectedFileSizeFormat(TcConfigurations.getPreferences().getVariable(TcPreference.DISPLAY_COMPACT_FILE_SIZE,\n                TcPreferences.DEFAULT_DISPLAY_COMPACT_FILE_SIZE));\n\n        // Listens to configuration changes and updates static fields accordingly.\n        // Note: a reference to the listener must be kept to prevent it from being garbage-collected.\n        CONFIGURATION_ADAPTER = new ConfigurationListener() {\n            public synchronized void configurationChanged(ConfigurationEvent event) {\n                String var = event.getVariable();\n\n                if (var.equals(TcPreferences.DISPLAY_COMPACT_FILE_SIZE)) {\n                    setSelectedFileSizeFormat(event.getBooleanValue());\n                }\n            }\n        };\n        TcConfigurations.addPreferencesListener(CONFIGURATION_ADAPTER);\n    }\n\n\n    /**\n     * Sets the SizeFormat format used to create the selected file(s) size string.\n     *\n     * @param compactSize true to use a compact size format, false for full size in bytes\n     */\n    private static void setSelectedFileSizeFormat(boolean compactSize) {\n        if (compactSize) {\n            selectedFileSizeFormat = SizeFormat.DIGITS_MEDIUM | SizeFormat.UNIT_SHORT | SizeFormat.ROUND_TO_KB;\n        } else {\n            selectedFileSizeFormat = SizeFormat.DIGITS_FULL | SizeFormat.UNIT_LONG;\n        }\n\n        selectedFileSizeFormat |= SizeFormat.INCLUDE_SPACE;\n    }\n\n\n    /**\n     * Creates a new StatusBar instance.\n     */\n    public StatusBar(MainFrame mainFrame) {\n        // create and add status bar\n        setLayout(new BoxLayout(this, BoxLayout.X_AXIS));\n\n        this.mainFrame = mainFrame;\n\n        progressBar = new JProgressBar();\n        add(progressBar);\n        //progressBar.setVisible(false);\n        progressGlue = Box.createHorizontalGlue();\n        add(progressGlue);\n        showProgress(-1);\n\n        selectedFilesLabel = new JLabel(\"\");\n        dial = new SpinningDial();\n        add(selectedFilesLabel);\n\n        add(Box.createHorizontalGlue());\n\n        FileWindowsListButton fileWindowsListButton = new FileWindowsListButton();\n        add(fileWindowsListButton);\n\n        taskPanel = new TaskPanel();\n        add(taskPanel);\n        add(Box.createRigidArea(new Dimension(2, 0)));\n\n        HeapIndicator heapIndicator = new HeapIndicator();\n        add(heapIndicator);\n        add(Box.createRigidArea(new Dimension(2, 0)));\n\n        // Add a button for interacting with the trash, only if the current platform has a trash implementation\n        if (DesktopManager.getTrash() != null) {\n            TrashPopupButton trashButton = new TrashPopupButton(mainFrame);\n            trashButton.setPopupMenuLocation(SwingConstants.TOP);\n\n            add(trashButton);\n            add(Box.createRigidArea(new Dimension(2, 0)));\n        }\n\n        volumeSpaceLabel = new VolumeSpaceLabel();\n        add(volumeSpaceLabel);\n\n        // Show/hide this status bar based on user preferences\n        // Note: setVisible has to be called even with true for the auto-update thread to be initialized\n        setVisible(shouldBeVisible());\n\n        // Catch location events to update status bar info when folder is changed\n        FolderPanel leftPanel = mainFrame.getLeftPanel();\n        leftPanel.getLocationManager().addLocationListener(this);\n\n        FolderPanel rightPanel = mainFrame.getRightPanel();\n        rightPanel.getLocationManager().addLocationListener(this);\n\n        // Catch table selection change events to update the selected files info when the selected files have changed on\n        // one of the file tables\n        leftPanel.getFileTable().addTableSelectionListener(this);\n        rightPanel.getFileTable().addTableSelectionListener(this);\n\n        // Catch active panel change events to update status bar info when current table has changed\n        mainFrame.addActivePanelListener(this);\n\n        // Catch mouse events to pop up a menu on right-click\n        selectedFilesLabel.addMouseListener(this);\n        volumeSpaceLabel.addMouseListener(this);\n        addMouseListener(this);\n\n        // Catch component events to be notified when this component is made visible\n        // and update status info\n        addComponentListener(this);\n\n        // Initializes theme.\n        selectedFilesLabel.setFont(ThemeManager.getCurrentFont(Theme.STATUS_BAR_FONT));\n        selectedFilesLabel.setForeground(ThemeManager.getCurrentColor(Theme.STATUS_BAR_FOREGROUND_COLOR));\n        volumeSpaceLabel.setFont(ThemeManager.getCurrentFont(Theme.STATUS_BAR_FONT));\n        volumeSpaceLabel.setForeground(ThemeManager.getCurrentColor(Theme.STATUS_BAR_FOREGROUND_COLOR));\n        ThemeManager.addCurrentThemeListener(this);\n    }\n\n    private static boolean shouldBeVisible() {\n        return TcConfigurations.getPreferences().getVariable(TcPreference.STATUS_BAR_VISIBLE, TcPreferences.DEFAULT_STATUS_BAR_VISIBLE);\n    }\n\n\n    /**\n     * Updates info displayed on the status bar: currently selected files and volume info.\n     */\n    private void updateStatusInfo() {\n        // No need to waste precious cycles if status bar is not visible\n        if (!isVisible()) {\n            return;\n        }\n        updateSelectedFilesInfo();\n        updateVolumeInfo();\n    }\n\n\n    /**\n     * Updates info about currently selected files ((nb of selected files, combined size), displayed on the left-side of this status bar.\n     */\n// Making this method synchronized creates a deadlock with FileTable\n//    public synchronized void updateSelectedFilesInfo() {\n    public void updateSelectedFilesInfo() {\n        // No need to waste precious cycles if status bar is not visible\n        if (!isVisible()) {\n            return;\n        }\n\n        FileTable currentFileTable = mainFrame.getActiveTable();\n\n        // Currently select file, can be null\n        AbstractFile selectedFile = currentFileTable.getSelectedFile(false, true);\n        BaseFileTableModel tableModel = currentFileTable.getFileTableModel();\n        // Number of marked files, can be 0\n        int nbMarkedFiles = tableModel.getNbMarkedFiles();\n        // Combined size of marked files, 0 if no file has been marked\n        long markedTotalSize = tableModel.getTotalMarkedSize();\n        // number of files in folder\n        int fileCount = tableModel.getFileCountWithoutParent();\n\n        // Update files info based on marked files if there are some, or currently selected file otherwise\n        int nbSelectedFiles = nbMarkedFiles == 0 && selectedFile != null ? 1 : nbMarkedFiles;\n\n        StringBuilder filesInfo = new StringBuilder();\n\n        if (fileCount == 0) {\n            // Set status bar to a space character, not an empty string otherwise it will disappear\n            filesInfo.append(' ');\n        } else {\n            filesInfo.append(Translator.get(\"status_bar.selected_files\", String.valueOf(nbSelectedFiles), String.valueOf(fileCount)));\n\n            if (nbMarkedFiles > 0) {\n                filesInfo.append(\" - \");\n                filesInfo.append(SizeFormat.format(markedTotalSize, selectedFileSizeFormat));\n            }\n\n            if (selectedFile != null) {\n                appendSelectedFileInfo(filesInfo, selectedFile);\n            }\n        }\n\n        // Update label\n        setStatusInfo(\"<html>\" + filesInfo);\n    }\n\n    private void appendSelectedFileInfo(StringBuilder filesInfo, AbstractFile selectedFile) {\n        filesInfo.append(\" - \");\n        filesInfo.append(\"<b>\");\n        filesInfo.append(selectedFile.getName());\n        filesInfo.append(\"</b>\");\n        if (selectedFile.isSymlink()) {\n            String target = getFileLink(selectedFile);\n            if (target != null) {\n                filesInfo.append(\" -> \");\n                filesInfo.append(target);\n            }\n        }\n        boolean local = selectedFile.getAncestor() instanceof LocalFile;\n        if (selectedFile.isDirectory()) {\n            if (local) {\n                filesInfo.append(\" (\");\n                try {\n                    filesInfo.append(selectedFile.ls().length);\n                } catch (IOException ignored) {\n                }\n                filesInfo.append(' ');\n                filesInfo.append(Translator.get(\"files\"));\n                filesInfo.append(')');\n            }\n        } else {\n            filesInfo.append(\" (\");\n            filesInfo.append(SizeFormat.format(selectedFile.getSize(), SizeFormat.DIGITS_FULL | SizeFormat.UNIT_LONG | SizeFormat.INCLUDE_SPACE));\n\n            if (local && SUPPORTED_IMAGE_FILTER.accept(selectedFile)) {\n                // Show image size\n                try (InputStream is = selectedFile.getInputStream()) {\n                    ImageSizeDetector detector = new ImageSizeDetector(is);\n                    if (detector.getType() != null) {\n                        filesInfo.append(\", \");\n                        filesInfo.append(detector.getWidth());\n                        filesInfo.append(\" x \");\n                        filesInfo.append(detector.getHeight());\n                    }\n                } catch (FileNotFoundException ignore) {\n                    // etc. if file was moved\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            } else if (JAVA_CLASS_FILTER.accept(selectedFile)) {\n                try (InputStream is = selectedFile.getPushBackInputStream(16)) {\n                    JavaClassVersionDetector detector = new JavaClassVersionDetector(is);\n\n                    if (detector.getVersion() != JavaClassVersionDetector.Version.UNKNOWN) {\n                        filesInfo.append(\", Java v\").append(detector.getVersion().name);\n                    } else if (detector.getVersion() != JavaClassVersionDetector.Version.WRONG_FORMAT) {\n                        filesInfo.append(\", Java major = \").append(detector.getMajor()).append(\", minor = \").append(detector.getMinor());\n                    }\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n\n            filesInfo.append(\")\");\n        }\n    }\n\n\n    private static String getFileLink(AbstractFile file) {\n        AbstractFile f;\n        if (file instanceof CachedFile) {\n            f = ((CachedFile) file).getProxiedFile();\n        } else {\n            f = file;\n        }\n        if (f instanceof LocalFile) {\n            return SymLinkUtils.getTargetPath(file);\n        } else if (f instanceof FTPFile) {\n            return ((FTPFile) f).getLink();\n        } else if (f instanceof SFTPFile) {\n            return ((SFTPFile) f).getLink();\n        }\n        return null;\n    }\n\n\n    /**\n     * Updates info about current volume (free space, total space), displayed on the right-side of this status bar.\n     */\n    private synchronized void updateVolumeInfo() {\n        // No need to waste precious cycles if status bar is not visible\n        if (!isVisible()) {\n            return;\n        }\n\n        final AbstractFile currentFolder = mainFrame.getActivePanel().getCurrentFolder();\n        // Resolve the current folder's volume and use its path as a key for the volume info cache\n        final String volumePath = currentFolder != null && currentFolder.exists() ? currentFolder.getVolume().getAbsolutePath(true) : \"\";\n\n        Long[] cachedVolumeInfo = volumeInfoCache.get(volumePath);\n        if (cachedVolumeInfo != null) {\n            LOGGER.debug(\"Cache hit!\");\n            volumeSpaceLabel.setVolumeSpace(cachedVolumeInfo[0], cachedVolumeInfo[1]);\n        } else {\n            // Retrieves free and total volume space.\n            // Perform volume info retrieval in a separate thread as this method may be called\n            // by the event thread and it can take a while, we want to return as soon as possible\n            new Thread(\"StatusBar.updateVolumeInfo\") {\n                @Override\n                public void run() {\n                    // Free space on current volume, -1 if this information is not available \n                    long volumeFree;\n                    // Total space on current volume, -1 if this information is not available \n                    long volumeTotal;\n\n                    // Folder is a local file and Java version is 1.5: call getVolumeInfo() instead of\n                    // separate calls to getFreeSpace() and getTotalSpace() as it is twice as fast.\n//                    if (currentFolder instanceof LocalFile && JavaVersion.JAVA_1_5.isCurrentOrLower()) {\n//                        try {\n//                            long volumeInfo[] = ((LocalFile)currentFolder).getVolumeInfo();\n//                            volumeTotal = volumeInfo[0];\n//                            volumeFree = volumeInfo[1];\n//                        } catch (IOException e) {\n//                            volumeTotal = -1;\n//                            volumeFree = -1;\n//                        }\n//                    }\n                    // Java 1.6 and up or any other file type\n//                    else {\n                    try {\n                        volumeFree = currentFolder != null ? currentFolder.getFreeSpace() : -1;\n                    } catch (IOException e) {\n                        volumeFree = -1;\n                    }\n\n                    try {\n                        volumeTotal = currentFolder != null ? currentFolder.getTotalSpace() : -1;\n                    } catch (IOException e) {\n                        volumeTotal = -1;\n                    }\n//                    }\n\n// For testing the free space indicator \n//volumeFree = (long)(volumeTotal * Math.random());\n\n                    volumeSpaceLabel.setVolumeSpace(volumeTotal, volumeFree);\n\n                    LOGGER.debug(\"Adding to cache\");\n                    volumeInfoCache.add(volumePath, new Long[]{volumeTotal, volumeFree}, VOLUME_INFO_TIME_TO_LIVE);\n                }\n            }.start();\n        }\n    }\n\n\n    /**\n     * Displays the specified text and icon on the left-side of the status bar,\n     * replacing any previous information.\n     *\n     * @param text           the piece of text to display\n     * @param icon           the icon to display next to the text\n     * @param iconBeforeText if true, icon will be placed on the left side of the text, if not on the right side\n     */\n    public void setStatusInfo(String text, Icon icon, boolean iconBeforeText) {\n        selectedFilesLabel.setText(text);\n\n        if (icon == null) {\n            // What we don't want here is the label's height to change depending on whether it has an icon or not.\n            // This would result in having to revalidate the status bar and in turn the whole MainFrame.\n            // A label's height is roughly the max of the text's font height and the icon (if any). So if there is no\n            // icon for the label, we use a transparent image for padding in case the text's font height is smaller\n            // than a 'standard' (16x16) icon. This ensures that the label's height remains constant.\n            BufferedImage bi = new BufferedImage(1, 16, BufferedImage.TYPE_INT_ARGB);\n            icon = new ImageIcon(bi);\n        }\n        selectedFilesLabel.setIcon(icon);\n\n        selectedFilesLabel.setHorizontalTextPosition(iconBeforeText ? JLabel.TRAILING : JLabel.LEADING);\n    }\n\n\n    /**\n     * Displays the specified text on the left-side of the status bar,\n     * replacing any previous text and icon.\n     *\n     * @param infoMessage the piece of text to display\n     */\n    public void setStatusInfo(String infoMessage) {\n        setStatusInfo(infoMessage, null, false);\n    }\n\n\n    /**\n     * Starts a volume info auto-update thread, only if there isn't already one running.\n     */\n    private synchronized void startAutoUpdate() {\n        if (autoUpdateThread == null) {\n            // Start volume info auto-update thread\n            autoUpdateThread = new Thread(this, \"StatusBar autoUpdateThread\");\n            // Set the thread as a daemon thread\n            autoUpdateThread.setDaemon(true);\n            autoUpdateThread.start();\n        }\n    }\n\n\n    /**\n     * Overrides JComponent.setVisible(boolean) to start/stop volume info auto-update thread.\n     */\n    @Override\n    public void setVisible(boolean visible) {\n        if (visible) {\n            // Start auto-update thread\n            startAutoUpdate();\n            super.setVisible(true);\n            // Update status bar info\n            updateStatusInfo();\n        } else {\n            // Stop auto-update thread\n            this.autoUpdateThread = null;\n            super.setVisible(false);\n        }\n    }\n\n\n    /**\n     * Periodically updates volume info (free / total space).\n     */\n    @Override\n    public void run() {\n        do {\n            // Sleep for a while\n            try {\n                Thread.sleep(AUTO_UPDATE_PERIOD);\n            } catch (InterruptedException ignore) {\n            }\n\n            // Update volume info if:\n            // - status bar is visible\n            // - MainFrame isn't changing folders\n            // - MainFrame is active and in the foreground\n            // Volume info update will potentially hit the LRU cache and not actually update volume info\n            if (isVisible() && !mainFrame.getNoEventsMode() && mainFrame.isForegroundActive()) {\n                updateVolumeInfo();\n            }\n        } while (autoUpdateThread != null && mainFrame.getJFrame().isVisible());   // Stop when MainFrame is disposed\n    }\n\n\n    @Override\n    public void activePanelChanged(FolderPanel folderPanel) {\n        updateStatusInfo();\n    }\n\n\n    @Override\n    public void selectedFileChanged(FileTable source) {\n        // No need to update if the originating FileTable is not the currently active one\n        if (source == mainFrame.getActiveTable() && mainFrame.isForegroundActive()) {\n            updateSelectedFilesInfo();\n        }\n    }\n\n    @Override\n    public void markedFilesChanged(FileTable source) {\n        // No need to update if the originating FileTable is not the currently active one\n        if (source == mainFrame.getActiveTable() && mainFrame.isForegroundActive()) {\n            updateSelectedFilesInfo();\n        }\n    }\n\n    @Override\n    public void locationChanged(LocationEvent e) {\n        dial.setAnimated(false);\n        updateStatusInfo();\n    }\n\n    @Override\n    public void locationChanging(LocationEvent e) {\n        // Show a message in the status bar saying that folder is being changed\n        setStatusInfo(Translator.get(\"status_bar.connecting_to_folder\"), dial, true);\n        dial.setAnimated(true);\n    }\n\n    @Override\n    public void locationCancelled(LocationEvent e) {\n        dial.setAnimated(false);\n        updateStatusInfo();\n    }\n\n    @Override\n    public void locationFailed(LocationEvent e) {\n        dial.setAnimated(false);\n        updateStatusInfo();\n    }\n\n    @Override\n    public void mouseClicked(MouseEvent e) {\n        // Discard mouse events while in 'no events mode'\n        if (mainFrame.getNoEventsMode()) {\n            return;\n        }\n\n        // Right-clicking on the toolbar brings up a popup menu that allows the user to hide this status bar\n        if (DesktopManager.isRightMouseButton(e)) {\n            //\t\tif (e.isPopupTrigger()) {\t// Doesn't work under Mac OS X (CTRL+click doesn't return true)\n            JPopupMenu popupMenu = new JPopupMenu();\n            popupMenu.add(ActionManager.getActionInstance(com.mucommander.ui.action.impl.ToggleStatusBarAction.Descriptor.ACTION_ID, mainFrame));\n            popupMenu.show(this, e.getX(), e.getY());\n            popupMenu.setVisible(true);\n        }\n        if (e.getSource() == volumeSpaceLabel) {\n            volumeInfoCache.clearAll();\n            updateVolumeInfo();\n        }\n    }\n\n    @Override\n    public void mouseReleased(MouseEvent e) {\n    }\n\n    @Override\n    public void mousePressed(MouseEvent e) {\n    }\n\n    @Override\n    public void mouseEntered(MouseEvent e) {\n    }\n\n    @Override\n    public void mouseExited(MouseEvent e) {\n    }\n\n\n    @Override\n    public void componentShown(ComponentEvent e) {\n        // Invoked when the component has been made visible (apparently not called when just created)\n        // Status bar needs to be updated since it is not updated when not visible\n        updateStatusInfo();\n    }\n\n    @Override\n    public void componentHidden(ComponentEvent e) {\n    }\n\n    @Override\n    public void componentMoved(ComponentEvent e) {\n    }\n\n    @Override\n    public void componentResized(ComponentEvent e) {\n    }\n\n    @Override\n    public void fontChanged(FontChangedEvent event) {\n        if (event.getFontId() == Theme.STATUS_BAR_FONT) {\n            selectedFilesLabel.setFont(event.getFont());\n            volumeSpaceLabel.setFont(event.getFont());\n            repaint();\n        }\n    }\n\n    @Override\n    public void colorChanged(ColorChangedEvent event) {\n        if (event.getColorId() == Theme.STATUS_BAR_FOREGROUND_COLOR) {\n            selectedFilesLabel.setForeground(event.getColor());\n            volumeSpaceLabel.setForeground(event.getColor());\n            repaint();\n        }\n    }\n\n\n    public TaskPanel getTaskPanel() {\n        return taskPanel;\n    }\n\n    private void showProgress(int progress) {\n        if (progress >= 0) {\n            progressBar.setVisible(true);\n            progressBar.setValue(progress);\n            progressGlue.setMaximumSize(new Dimension(Short.MAX_VALUE, 0));\n            progressGlue.revalidate();\n        } else {\n            progressBar.setVisible(false);\n            progressGlue.setMaximumSize(new Dimension(0, 0));\n            progressGlue.revalidate();\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/statusbar/TaskPanel.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.main.statusbar;\n\nimport javax.swing.BoxLayout;\nimport javax.swing.JPanel;\n\n/**\n * @author Oleg Trifonov\n * Created on 08/12/14.\n */\npublic class TaskPanel extends JPanel {\n\n    public TaskPanel() {\n        super();\n        setLayout(new BoxLayout(this, BoxLayout.X_AXIS));\n    }\n\n\n    public void addTask(TaskWidget taskWidget) {\n        add(taskWidget);\n        validate();\n        repaint();\n        getParent().revalidate();\n        getParent().repaint();\n        taskWidget.taskPanel = this;\n    }\n\n    public void removeWidget(TaskWidget taskWidget) {\n        remove(taskWidget);\n        validate();\n        repaint();\n        getParent().revalidate();\n        getParent().repaint();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/statusbar/TaskWidget.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.main.statusbar;\n\nimport com.mucommander.ui.dialog.FocusDialog;\nimport org.apache.commons.lang.StringUtils;\nimport ru.trolsoft.ui.TProgressBar;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.*;\n\n/**\n * @author Oleg Trifonov\n * Created on 08/12/14.\n */\npublic class TaskWidget extends TProgressBar {//NonFocusableButton {\n\n    private static final Insets INSETS = new Insets(2, 2, 2, 2);\n    private static final int STRING_LENGTH = 25;\n\n    private int progress;\n\n    TaskPanel taskPanel;\n    private FocusDialog progressDialog;\n\n\n    public TaskWidget() {\n        super();\n        init();\n    }\n\n    public TaskWidget(String text) {\n        super();\n        setString(text);\n        init();\n    }\n\n    public TaskWidget(Icon icon) {\n        super();\n        init();\n    }\n\n    public TaskWidget(String text, Icon icon) {\n        super();\n        setText(text);\n        init();\n    }\n\n\n    private void init() {\n        setStringPainted(true);\n\n        addMouseListener(new MouseAdapter() {\n            @Override\n            public void mouseClicked(MouseEvent e) {\n                    if (progressDialog != null) {\n                        setVisible(false);\n                        progressDialog.showDialog();\n                        //progressDialog.setVisible(true);\n                    }\n            }\n        });\n    }\n\n\n    /**\n     * Replace the default insets to be exactly (2,2,2,2).\n     */\n    @Override\n    public Insets getInsets() {\n        return INSETS;\n    }\n\n    public void removeFromPanel() {\n        if (taskPanel != null) {\n            taskPanel.removeWidget(this);\n        }\n    }\n\n\n    public void setProgress(int progress) {\n        this.progress = progress;\n        setValue(progress);\n    }\n\n\n    public FocusDialog getProgressDialog() {\n        return progressDialog;\n    }\n\n\n    public void setProgressDialog(FocusDialog progressDialog) {\n        this.progressDialog = progressDialog;\n    }\n\n\n    public void setText(String s) {\n        if (s.length() > STRING_LENGTH) {\n            s = s.substring(0, STRING_LENGTH-4) + \"..\";\n        }\n        s = StringUtils.center(s, STRING_LENGTH);\n        setString(s);\n\n        Dimension dim = new JLabel(s).getPreferredSize();\n        setPreferredSize(new Dimension(dim.width + 20, getPreferredSize().height));\n        setMaximumSize(new Dimension(dim.width + 20, getMaximumSize().height));\n\n        if (getParent() != null) {\n            getParent().revalidate();\n        }\n    }\n\n\n    public String getText() {\n        return getString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/statusbar/TrashPopupButton.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.statusbar;\n\nimport com.mucommander.desktop.AbstractTrash;\nimport com.mucommander.desktop.DesktopManager;\nimport com.mucommander.ui.action.ActionManager;\nimport com.mucommander.ui.action.impl.EmptyTrashAction;\nimport com.mucommander.ui.action.impl.OpenTrashAction;\nimport com.mucommander.ui.button.PopupButton;\nimport com.mucommander.ui.button.RolloverButtonAdapter;\nimport com.mucommander.ui.icon.IconManager;\nimport com.mucommander.ui.main.MainFrame;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n/**\n * TrashPopupButton is a button that allows to interact with the current platform's trash, as returned by\n * {@link com.mucommander.desktop.DesktopManager#getTrash()}.\n * When the button is clicked, a popup menu is displayed, allowing to perform a choice of actions such as opening\n * the trash or emptying it.\n * Note that this button will only be functional if a trash is available on the current platform.\n *\n * @author Maxence Bernard\n */\npublic class TrashPopupButton extends PopupButton {\n\n    private final MainFrame mainFrame;\n\n    TrashPopupButton(MainFrame mainFrame) {\n        this.mainFrame = mainFrame;\n\n        setContentAreaFilled(false);\n        setIcon(IconManager.getIcon(IconManager.IconSet.STATUS_BAR, \"trash.png\"));\n\n        RolloverButtonAdapter.decorateButton(this);\n    }\n\n    @Override\n    public JPopupMenu getPopupMenu() {\n        JPopupMenu popupMenu = new JPopupMenu();\n\n        AbstractTrash trash = DesktopManager.getTrash();\n        if (trash != null) {\n            if (trash.canOpen()) {\n                popupMenu.add(ActionManager.getActionInstance(OpenTrashAction.Descriptor.ACTION_ID, mainFrame));\n            }\n\n            if (trash.canEmpty()) {\n                JMenuItem emptyTrashItem = new JMenuItem(ActionManager.getActionInstance(EmptyTrashAction.Descriptor.ACTION_ID, mainFrame));\n\n                // Retrieve the number of items that the trash contains, -1 if this information is not available.\n                int itemCount = trash.getItemCount();\n                if (itemCount == 0) {\n                    // Disable the 'empty trash' action if the trash contains no item\n                    emptyTrashItem.setEnabled(false);\n                } else if (itemCount > 0) {\n                    // Append the number of items to the menu item's label\n                    emptyTrashItem.setText(emptyTrashItem.getText()+\" (\"+itemCount+\")\");\n                }\n                // Note: 'empty trash' is enabled if itemCount==-1\n\n                popupMenu.add(emptyTrashItem);\n            }\n        }\n\n        return popupMenu;\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    /**\n     * Replace the default insets to be exactly (2,2,2,2).\n     */\n    @Override\n    public Insets getInsets() {\n        return new Insets(2, 2, 2, 2);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/statusbar/VolumeSpaceLabel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.main.statusbar;\n\nimport com.mucommander.utils.text.SizeFormat;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.border.MutableLineBorder;\nimport com.mucommander.ui.theme.*;\n\nimport javax.swing.JLabel;\nimport java.awt.Color;\nimport java.awt.Dimension;\nimport java.awt.Graphics;\n\nimport static com.mucommander.ui.theme.ThemeManager.*;\n/**\n * This label displays the amount of free and/or total space on a volume.\n */\nclass VolumeSpaceLabel extends JLabel implements ThemeListener {\n\n    /** SizeFormat's format used to display volume info in status bar */\n    private final static int VOLUME_INFO_SIZE_FORMAT = SizeFormat.DIGITS_MEDIUM | SizeFormat.UNIT_SHORT | SizeFormat.INCLUDE_SPACE | SizeFormat.ROUND_TO_KB;\n\n    private long freeSpace;\n    private long totalSpace;\n\n    private Color backgroundColor;\n    private Color okColor;\n    private Color warningColor;\n    private Color criticalColor;\n\n    private final static float SPACE_WARNING_THRESHOLD = 0.1f;\n    private final static float SPACE_CRITICAL_THRESHOLD = 0.05f;\n\n\n    VolumeSpaceLabel() {\n        super(\"\");\n        setHorizontalAlignment(CENTER);\n        backgroundColor = getCurrentColor(Theme.STATUS_BAR_BACKGROUND_COLOR);\n        //borderColor     = getCurrentColor(Theme.STATUS_BAR_BORDER_COLOR);\n        okColor = getCurrentColor(Theme.STATUS_BAR_OK_COLOR);\n        warningColor = getCurrentColor(Theme.STATUS_BAR_WARNING_COLOR);\n        criticalColor = getCurrentColor(Theme.STATUS_BAR_CRITICAL_COLOR);\n        setBorder(new MutableLineBorder(getCurrentColor(Theme.STATUS_BAR_BORDER_COLOR)));\n        ThemeManager.addCurrentThemeListener(this);\n    }\n\n    /**\n     * Sets the new volume total and free space, and updates the label's text to show the new values and,\n     * only if both total and free space are available (different from -1), paint a graphical representation\n     * of the amount of free space available and set a tooltip showing the percentage of free space on the volume.\n     *\n     * @param totalSpace total volume space, -1 if not available\n     * @param freeSpace free volume space, -1 if not available\n     */\n    void setVolumeSpace(long totalSpace, long freeSpace) {\n        this.freeSpace = freeSpace;\n        this.totalSpace = totalSpace;\n\n        // Set new label's text\n        setText(getVolumeInfo());\n\n        // Set tooltip\n        if (freeSpace < 0 || totalSpace < 0) {\n            setToolTipText(null);       // Removes any previous tooltip\n        } else {\n            setToolTipText((int) (100 * freeSpace / (float) totalSpace) + \"%\");\n        }\n        repaint();\n    }\n\n    private String getVolumeInfo() {\n        String volumeInfo;\n        if (freeSpace >= 0) {\n            volumeInfo = SizeFormat.format(freeSpace, VOLUME_INFO_SIZE_FORMAT);\n            if (totalSpace >= 0)\n                volumeInfo += \" / \"+ SizeFormat.format(totalSpace, VOLUME_INFO_SIZE_FORMAT);\n\n            volumeInfo = Translator.get(\"status_bar.volume_free\", volumeInfo);\n        } else if (totalSpace >= 0) {\n            volumeInfo = SizeFormat.format(totalSpace, VOLUME_INFO_SIZE_FORMAT);\n            volumeInfo = Translator.get(\"status_bar.volume_capacity\", volumeInfo);\n        } else {\n            volumeInfo = \"\";\n        }\n        return volumeInfo;\n    }\n\n\n    /**\n     * Adds some empty space around the label.\n     */\n    @Override\n    public Dimension getPreferredSize() {\n        Dimension d = super.getPreferredSize();\n        return new Dimension(d.width+4, d.height+2);\n    }\n\n    /**\n     * Returns an interpolated color value, located at percent between c1 and c2 in the RGB space.\n     *\n     * @param c1 first color\n     * @param c2 end color\n     * @param percent distance between c1 and c2, comprised between 0 and 1.\n     * @return an interpolated color value, located at percent between c1 and c2 in the RGB space.\n     */\n    private static Color interpolateColor(Color c1, Color c2, float percent) {\n        return new Color(\n                interpolate(c1.getRed(), c2.getRed(), percent),\n                interpolate(c1.getGreen(), c2.getGreen(), percent),\n                interpolate(c1.getBlue(), c2.getBlue(), percent)\n        );\n    }\n\n\n    private static int interpolate(int v1, int v2, float percent) {\n        return v1 + (int)((v2 - v1) * percent);\n    }\n\n    @Override\n    public void paint(Graphics g) {\n        // If free or total space is not available, this label will just be painted as a normal JLabel\n        if (freeSpace >= 0 && totalSpace >= 0) {\n            int width = getWidth();\n            int height = getHeight();\n\n            // Paint amount of free volume space if both free and total space are available\n            float freeSpacePercentage = freeSpace/(float)totalSpace;\n\n            Color c;\n            if (freeSpacePercentage <= SPACE_CRITICAL_THRESHOLD) {\n                c = criticalColor;\n            } else if (freeSpacePercentage <= SPACE_WARNING_THRESHOLD) {\n                c = interpolateColor(warningColor, criticalColor, (SPACE_WARNING_THRESHOLD-freeSpacePercentage)/SPACE_WARNING_THRESHOLD);\n            } else {\n                c = interpolateColor(okColor, warningColor, (1-freeSpacePercentage)/(1-SPACE_WARNING_THRESHOLD));\n            }\n\n            g.setColor(c);\n\n            int freeSpaceWidth = Math.max(Math.round(freeSpacePercentage*(float)(width-2)), 1);\n            g.fillRect(1, 1, freeSpaceWidth + 1, height - 2);\n\n            // Fill background\n            g.setColor(backgroundColor);\n            g.fillRect(freeSpaceWidth + 1, 1, width - freeSpaceWidth - 1, height - 2);\n        }\n\n        super.paint(g);\n    }\n\n\n// Total/Free space reversed, doesn't look quite right\n\n//        @Override\n//        public void paint(Graphics g) {\n//            // If free or total space is not available, this label will just be painted as a normal JLabel\n//            if(freeSpace!=-1 && totalSpace!=-1) {\n//                int width = getWidth();\n//                int height = getHeight();\n//\n//                // Paint amount of free volume space if both free and total space are available\n//                float freeSpacePercentage = freeSpace/(float)totalSpace;\n//                float usedSpacePercentage = (totalSpace-freeSpace)/(float)totalSpace;\n//\n//                Color c;\n//                if(freeSpacePercentage<=SPACE_CRITICAL_THRESHOLD) {\n//                    c = criticalColor;\n//                }\n//                else if(freeSpacePercentage<=SPACE_WARNING_THRESHOLD) {\n//                    c = interpolateColor(warningColor, criticalColor, (SPACE_WARNING_THRESHOLD-freeSpacePercentage)/SPACE_WARNING_THRESHOLD);\n//                }\n//                else {\n//                    c = interpolateColor(okColor, warningColor, (1-freeSpacePercentage)/(1-SPACE_WARNING_THRESHOLD));\n//                }\n//\n//                g.setColor(c);\n//\n//                int usedSpaceWidth = Math.max(Math.round(usedSpacePercentage*(float)(width-2)), 1);\n//                g.fillRect(1, 1, usedSpaceWidth + 1, height - 2);\n//\n//                // Fill background\n//                g.setColor(backgroundColor);\n//                g.fillRect(usedSpaceWidth + 1, 1, width - usedSpaceWidth - 1, height - 2);\n//            }\n//\n//            super.paint(g);\n//        }\n\n    public void fontChanged(FontChangedEvent event) {}\n\n    public void colorChanged(ColorChangedEvent event) {\n        switch (event.getColorId()) {\n            case Theme.STATUS_BAR_BACKGROUND_COLOR:\n                backgroundColor = event.getColor();\n                break;\n            case Theme.STATUS_BAR_BORDER_COLOR:\n                // Some (rather evil) look and feels will change borders outside of muCommander's control,\n                // this check is necessary to ensure no exception is thrown.\n                if (getBorder() instanceof MutableLineBorder)\n                    ((MutableLineBorder)getBorder()).setLineColor(event.getColor());\n                break;\n            case Theme.STATUS_BAR_OK_COLOR:\n                okColor = event.getColor();\n                break;\n            case Theme.STATUS_BAR_WARNING_COLOR:\n                warningColor = event.getColor();\n                break;\n            case Theme.STATUS_BAR_CRITICAL_COLOR:\n                criticalColor = event.getColor();\n                break;\n            default:\n                return;\n            }\n        repaint();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/table/CalculateDirectorySizeWorker.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.main.table;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.main.table.views.BaseFileTableModel;\n\nimport javax.swing.SwingWorker;\nimport java.io.IOException;\nimport java.util.List;\n\n/**\n * @author Oleg Trifonov\n * Created on 09/01/14.\n */\npublic class CalculateDirectorySizeWorker extends SwingWorker<Long, Long> {\n    /** Refresh rate in milliseconds  */\n    private static final long REFRESH_RATE_MS = 300;\n\n    private final BaseFileTableModel fileTableModel;\n    private final AbstractFile path;\n    private final FileTable table;\n    private long size;\n    private long lastRefreshTime;\n\n    public CalculateDirectorySizeWorker(BaseFileTableModel fileTableModel, FileTable table, AbstractFile path) {\n        this.fileTableModel = fileTableModel;\n        this.table = table;\n        this.path = path;\n    }\n\n    @Override\n    protected Long doInBackground() {\n        size = 0;\n        try {\n            calcDirectorySize(path);\n        } catch (Exception e) {\n            e.printStackTrace();\n            size = -1;\n        }\n        return size;\n    }\n\n    @Override\n    protected void done() {\n        fileTableModel.addProcessedDirectory(path, table, size, true);\n        fileTableModel.fillCellCache(table);\n        table.repaint();\n    }\n\n    @Override\n    protected void process(List<Long> chunks) {\n        fileTableModel.addProcessedDirectory(path, table, size, false);\n        fileTableModel.fillCellCache(table);\n        table.repaint();\n        table.updateSelectedFilesStatusBar();\n    }\n\n    private void calcDirectorySize(AbstractFile path) throws IOException {\n        if (isCancelled()) {\n            return;\n        }\n        long tm = System.currentTimeMillis();\n        if (tm - lastRefreshTime > REFRESH_RATE_MS) {\n            lastRefreshTime = tm;\n            publish(size);\n        }\n        if (path.isSymlink() && path != this.path) {\n            return;\n        }\n        AbstractFile[] childs;\n        try {\n            childs = path.ls();\n        } catch (IOException e) {\n            return;\n        }\n        for (AbstractFile f : childs) {\n            if (isCancelled()) {\n                return;\n            }\n            if (f.isDirectory()) {\n                calcDirectorySize(f);\n            } else if (!f.isSymlink()) {\n                size += f.getSize();\n            }\n        }\n\n    }\n\n\n    public AbstractFile getFile() {\n        return path;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/table/CellLabel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.main.table;\n\nimport com.mucommander.ui.main.table.views.full.FileTableCellRenderer;\n\nimport javax.swing.*;\nimport javax.swing.border.Border;\nimport javax.swing.border.EmptyBorder;\nimport java.awt.*;\n\n\n/**\n * A custom <code>JLabel</code> component used by {@link FileTableCellRenderer FileTableCellRenderer} to render table cells.\n *\n * <p>CellLabel is basically a faster dumbed-down JLabel which overrides some JLabel to be no-ops (see below)\n * and some other (setText, setIcon) to call JLabel's super methods only if value has changed since last call,\n * as very often values don't change from one cell to another. Some methods were borrowed from Sun's\n * <code>DefaultTableCellRender</code> implementation and are marked as much.\n * \n * <p>Quote from Sun's Javadoc : The table class defines a single cell renderer and uses it as a \n * rubber-stamp for rendering all cells in the table;  it renders the first cell,\n * changes the contents of that cell renderer, shifts the origin to the new location, re-draws it, and so on.\n * <p>The standard <code>JLabel</code> component was not\n * designed to be used this way, and we want to avoid\n * triggering a <code>revalidate</code> each time the\n * cell is drawn. This would greatly decrease performance because the\n * <code>revalidate</code> message would be\n * passed up the hierarchy of the container to determine whether any other\n * components would be affected.  So this class\n * overrides the <code>validate</code>, <code>revalidate</code>,\n * <code>repaint</code>, and <code>firePropertyChange</code> methods to be \n * no-ops.\n *\n * @author Maxence Bernard, Sun Microsystems\n */\npublic class CellLabel extends JLabel {\n    /** Amount of border space on the left and right of the cell */\n    public static final int CELL_BORDER_WIDTH = 4;\n    /** Amount of border space on the top and bottom of the cell */\n    public static final int CELL_BORDER_HEIGHT = 1;\n    /** Empty border to give more space around cells */\n    private static final Border CELL_BORDER = new EmptyBorder(CELL_BORDER_HEIGHT, CELL_BORDER_WIDTH, CELL_BORDER_HEIGHT, CELL_BORDER_WIDTH);\n\n    private static final Stroke DASHED_STROKE = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[]{1,1}, 0);\n    private static final String DOTS = \"...\";\n\n\n\n    /** Last text set by the setText method */\n    private String lastText;\n    /** Last icon set by the setIcon method */\n    private ImageIcon lastIcon;\n    /** Last tooltip text set by the setToolTipText method */\n    private String lastTooltip;\n    /** Last foreground color set by the setForeground method */\n    private Color lastForegroundColor;\n    /** Last background color set by the setBackground method */\n    private Color lastBackgroundColor;\n    /** Outline color (top and bottom). */\n    protected Color outlineColor;\n    /** Gradient color for the background. */\n    private Color gradientColor;\n\n    private boolean hasSeparatorLine;\n\n    private int progressValue;\n\n    private Color markerColor;\n\n\n    /**\n     * Creates a new blank CellLabel.\n     */\n    public CellLabel() {\n        setBorder(CELL_BORDER);\n    }\n\n\n    // - Color changing ------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    /**\n     * Overrides <code>JComponent.setForeground</code> to call \n     * the super method only if the value has changed since last call.\n     * \n     * @param c the new foreground's color for this label\n     */\n    @Override\n    public void setForeground(Color c) {\n        if ((c != null && !c.equals(lastForegroundColor)) || (lastForegroundColor != null && !lastForegroundColor.equals(c))) {\n            super.setForeground(c); \n            lastForegroundColor = c;\n        }\n    }\n    \n    /**\n     * Overrides <code>JComponent.setBackground</code> to call \n     * the super method only if the value has changed since last call.\n     * \n     * @param c the new background's color for this label\n     */\n    @Override\n    public void setBackground(Color c) {\n        if ((c != null && !c.equals(lastBackgroundColor)) || (lastBackgroundColor != null && !lastBackgroundColor.equals(c))) {\n            super.setBackground(c); \n            lastBackgroundColor = c;\n            gradientColor = null;\n        }\n    }\n\n    /**\n     * Sets the background to a gradient between the two specified colors.\n     * @param c1 first component of the gradient.\n     * @param c2 second component of the gradient.\n     */\n    public void setBackground(Color c1, Color c2) {\n        if (c1.equals(c2)) {\n            setBackground(c1);\n        } else {\n            lastBackgroundColor = c1;\n            gradientColor = c2;\n        }\n    }\n\n\n    /**\n     * Sets the label outline color.\n     * @param c the new background's color for this label\n     */\n    public void setOutline(Color c) {\n        outlineColor = c;\n    }\n\n\n\n    /**\n     * Overrides <code>JLabel.setText</code> to call \n     * the super method only if the value has changed since last call.\n     * \n     * @param text the new text this label will display\n     */\n    @Override\n    public void setText(String text) {\n        if ((text != null && !text.equals(lastText)) || (lastText != null && !lastText.equals(text))) {\n            super.setText(text);\n            lastText = text;\n        }\n    }\n\n\n    /**\n     * Overrides <code>JLabel.setIcon</code> to call \n     * the super method only if the value has changed since last call.\n     * \n     * @param icon the new icon this label will display\n     */\n    public void setIcon(ImageIcon icon) {\n        if (icon != lastIcon) {\n            super.setIcon(icon);\n            lastIcon = icon;\n        }\n    }\n\n\n    /**\n     * Overrides <code>JLabel.setToolTipText</code> to call \n     * the super method only if the value has changed since last call.\n     * \n     * @param tooltip the new tooltip this label will display\n     */\n    @Override\n    public void setToolTipText(String tooltip) {\n        if ((tooltip != null && !tooltip.equals(lastTooltip)) || (lastTooltip != null && !lastTooltip.equals(tooltip))) {\n            super.setToolTipText(tooltip);\n            lastTooltip = tooltip;\n        }\n    }\n\n\n    public void setupText(String text, int maxWidth) {\n        setText(text);\n\n        // If label's width is larger than the column width:\n        // - truncate the text from the center and equally to the left and right sides, adding an ellipsis ('...')\n        // where characters have been removed. This allows both the start and end of filename to be visible.\n        // - set a tooltip text that will display the whole text when mouse is over the label\n\n        //final TableColumn tableColumn = table.getColumnModel().getColumn(columnIndex);\n        if (maxWidth < getPreferredSize().getWidth()) {\n            final int tl = text.length();\n            final int tl2 = tl/2;\n            String leftText = text.substring(0, tl2);\n            String rightText = text.substring(tl2, tl);\n\n            while (maxWidth < getPreferredSize().getWidth() && !leftText.isEmpty() && !rightText.isEmpty()) {    // Prevents against going out of bounds\n                final int ltl = leftText.length();\n                final int rtl = rightText.length();\n                if (ltl > rtl) {\n                    leftText = leftText.substring(0, ltl - 1);\n                } else {\n                    rightText = rightText.substring(1, rtl);\n                }\n\n                setText(leftText + DOTS + rightText);\n            }\n\n            // Set the tool tip\n            setToolTipText(text);\n        } else {    // Have to set it to null otherwise the defaultRender sets the tooltip text to the last one specified\n            setToolTipText(null);\n        }\n\n    }\n\n\n\n    /**\n     * Paints the label.\n     * @param g where to paint the label.\n     */\n    @Override\n    public void paint(Graphics g) {\n        boolean doOutline = outlineColor != null && !outlineColor.equals(lastBackgroundColor);\n        final int w = getWidth();\n        final int h = getHeight();\n\n        // Checks whether we need to paint a gradient background.\n        if (gradientColor != null) {\n            // Initialisation.\n            Graphics2D g2 = (Graphics2D)g;  // Allows us to use the setPaint and getPaint methods.\n            Paint oldPaint = g2.getPaint(); // Previous Paint affected to g.\n\n            // Paints the gradient background.\n            // TODO avoid object creation in paint methods\n            g2.setPaint(new GradientPaint(0, 0, lastBackgroundColor, 0, getHeight(), gradientColor, false));\n            if (doOutline) {\n                g2.fillRect(0, 1, w, h - 2);\n            } else {\n                g2.fillRect(0, 0, w, h);\n            }\n\n            // Restores the graphics to its previous state.\n            g2.setPaint(oldPaint);\n        }\n\n        if (markerColor != null) {\n            drawMarker(g, w, h);\n        } else {\n            // Normal painting\n            super.paint(g);\n        }\n\n        // If necessary, paints the outline color.\n        if (doOutline) {\n            paintOutline(g);\n        }\n        if (hasSeparatorLine) {\n            Graphics2D g2d = (Graphics2D)g;\n            g2d.setColor(Color.GRAY);\n            g2d.setStroke(DASHED_STROKE);\n            g2d.drawLine(w-1, 0, w-1, getHeight());\n        }\n        // TODO improve it\n        if (progressValue > 0) {\n            drawProgress(g, w, h);\n        }\n    }\n\n    private void drawMarker(Graphics g, int w, int h) {\n        setSize(w - h, h);\n        super.paint(g);\n        setSize(w, h);\n        if (gradientColor == null) {\n            g.setColor(lastBackgroundColor);\n            g.fillRect(w - h, 1, h, h - 2);\n        }\n        g.setColor(markerColor);\n        g.fillArc(w - h, h/6, h*2/3, h*2/3, 0, 360);\n    }\n\n    private void drawProgress(Graphics g, int w, int h) {\n        int a = -progressValue*20 % 360;\n        int r = h - 4;\n        if (r % 2 != 0) {\n            r--;\n        }\n        g.setColor(lastForegroundColor);\n        g.fillArc(w - r, 2, r, r, a+100, 200);\n        g.setColor(lastBackgroundColor);\n        int r2 = r/2;\n        if (r2 % 2 != 0) {\n            r2++;\n        }\n        int d = (r - r2) / 2;\n        g.fillOval(w - r + d, 2 + d, r2, r2);\n        //g.fillArc(w - r + d, 2 + d, r2, r2, a+100, 200);\n    }\n\n    protected void paintOutline(Graphics g) {\n    \tg.setColor(outlineColor);\n        g.drawLine(0, 0, getWidth(), 0);\n        g.drawLine(0, getHeight() - 1, getWidth(), getHeight() - 1);\n    }\n\n    \n    // - DefaultTableCellRenderer implementation -----------------------------------------\n    // -----------------------------------------------------------------------------------\n    /*\n     * The following methods are overridden as a performance measure to \n     * prune code-paths are often called in the case of renders\n     * but which we know are unnecessary.  Great care should be taken\n     * when writing your own renderer to weigh the benefits and \n     * drawbacks of overriding methods like these.\n     */\n\n    /**\n     * Overridden for performance reasons.\n     */\n    @Override\n    public boolean isOpaque() {\n        // If we're not using a gradient background, the component's opaque\n        // status is context dependant.\n        if (gradientColor == null) {\n\n            Color back = lastBackgroundColor;\n            Component p = getParent();\n            if (p != null) {\n                p = p.getParent();\n            }\n\n            // The label does not need to be opaque if it has an opaque parent component\n            // of the same background color.\n            return !(back != null && p != null && back.equals(p.getBackground()) && p.isOpaque());\n        }\n\n        // We must consider the label not to be opaque, otherwise the gradient would be overpainted by\n        // the component's background color.\n        return false;\n    }\n\n    /**\n     * Overridden for performance reasons.\n     */\n    @Override\n    public void validate() {}\n\n    /**\n     * Overridden for performance reasons.\n     */\n    @Override\n    public void revalidate() {}\n\n    /**\n     * Overridden for performance reasons.\n     */\n    @Override\n    public void repaint(long tm, int x, int y, int width, int height) {}\n\n    /**\n     * Overridden for performance reasons.\n     */\n    @Override\n    public void repaint(Rectangle r) { }\n\n    /**\n     * Overridden for performance reasons.\n     */\n    @Override\n    protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {\n        // Strings get interned...\n        if (\"text\".equals(propertyName)) {\n            super.firePropertyChange(propertyName, oldValue, newValue);\n        }\n    }\n\n    /**\n     * Overridden for performance reasons.\n     */\n    @Override\n    public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { }\n\n    public void setHasSeparator(boolean hasSeparator) {\n        this.hasSeparatorLine = hasSeparator;\n    }\n\n    public void setProgressValue(int progressValue) {\n        this.progressValue = progressValue;\n    }\n\n    /**\n     * Set label color (Mac OS X only)\n     * @param markerColor color or null if file has no label\n     */\n    public void setMarkerColor(Color markerColor) {\n        this.markerColor = markerColor;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/table/Column.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.table;\n\nimport com.mucommander.commons.file.util.FileComparator;\nimport com.mucommander.utils.text.Translator;\nimport lombok.Getter;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Enumerates and describes the different columns used in the {@link FileTable}.\n *\n * @author Maxence Bernard\n */\npublic enum Column {\n\n    EXTENSION(\"extension\", true, true, FileComparator.EXTENSION_CRITERION, \"ToggleExtensionColumn\", \"SortByExtension\"),\n    NAME(\"name\", false, true, FileComparator.NAME_CRITERION, null, \"SortByName\"),\n    SIZE(\"size\", true, true, FileComparator.SIZE_CRITERION, \"ToggleSizeColumn\", \"SortBySize\"),\n    DATE(\"date\", true, true, FileComparator.DATE_CRITERION, \"ToggleDateColumn\", \"SortByDate\"),\n    PERMISSIONS(\"permissions\", true, true, FileComparator.PERMISSIONS_CRITERION, \"TogglePermissionsColumn\", \"SortByPermissions\"),\n    OWNER(\"owner\", true, false, FileComparator.OWNER_CRITERION, \"ToggleOwnerColumn\", \"SortByOwner\"),\n    GROUP(\"group\", true, false, FileComparator.GROUP_CRITERION, \"ToggleGroupColumn\", \"SortByGroup\");\n\n    private static final Map<Integer, Column> ORDINAL_TO_ENUM_MAPPING;\n    static {\n        Map<Integer, Column> map = new HashMap<>();\n        for (Column column : Column.values()) {\n            map.put(column.ordinal(), column);\n        }\n        ORDINAL_TO_ENUM_MAPPING = Collections.unmodifiableMap(map);\n    }\n\n    /** Standard minimum column width */\n    private final static int STANDARD_MINIMUM_WIDTH = 2 * CellLabel.CELL_BORDER_WIDTH;\n\n    /**\n     * -- GETTER --\n     *  Returns this column's localized label.\n     */\n    @Getter\n    private final String label;\n    private final int minimumWidth;\n    private final boolean showByDefault;\n    /**\n     * -- GETTER --\n     *  Returns the criterion used for sorting column values.\n     */\n    @Getter\n    private final int fileComparatorCriterion;\n    private final String toggleActionId;\n    private final String sortByActionId;\n\n    Column(String labelId, boolean hasMinimumWidth, boolean showByDefault, int fileComparatorCriterion, String toggleActionId, String sortByActionId) {\n        this.label = Translator.get(labelId);\n        this.minimumWidth = hasMinimumWidth ? STANDARD_MINIMUM_WIDTH : 0;\n        this.showByDefault = showByDefault;\n        this.fileComparatorCriterion = fileComparatorCriterion;\n        this.toggleActionId = toggleActionId;\n        this.sortByActionId = sortByActionId;\n    }\n\n    /**\n     * Returns this column's minimum width.\n     *\n     * @return this column's minimum width.\n     */\n    public int getMinimumColumnWidth() {\n        return minimumWidth;\n    }\n\n    /**\n     * Returns <code>true</code> if this column should be displayed, unless configured otherwise.\n     *\n     * @return <code>true</code> if this column should be displayed, unless configured otherwise.\n     */\n    public boolean showByDefault() {\n        return showByDefault;\n    }\n\n    /**\n     * Returns the column instance that has the specified {@link #ordinal()} value.\n     *\n     * @param ordinal the column's ordinal value\n     * @return the column instance that has the specified {@link #ordinal()} value.\n     */\n    public static Column valueOf(int ordinal) {\n      return ORDINAL_TO_ENUM_MAPPING.get(ordinal);\n    }\n\n    /**\n     * Returns the ID of the action that allows this column to be shown/hidden.\n     * Caution: the {@link #NAME} column cannot be toggled, therefore the returned action ID is <code>null</code>.\n     *\n     * @return the ID of the action that allows this column to be shown/hidden.\n     */\n    public String getToggleColumnActionId() {\n        return toggleActionId;\n    }\n\n    /**\n     * Returns the ID of the action that allows to sort the table by this column.\n     *\n     * @return the ID of the action that allows to sort the table by this column.\n     */\n    public String getSortByColumnActionId() {\n        return sortByActionId;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/table/FileGroupResolver.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.main.table;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.filter.WildcardFileFilter;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferencesAPI;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * @author Oleg Trifonov\n */\npublic class FileGroupResolver {\n\n    public static final int MAX_GROUPS = 10;\n\n    private static class ResolverRecord {\n        final int group;\n        final WildcardFileFilter filter;\n\n        ResolverRecord(int group, String mask) {\n            this.group = group;\n            this.filter = new WildcardFileFilter(mask);\n        }\n    }\n\n    private final Map<String, Integer> extensionsMap = new HashMap<>();\n    private final List<ResolverRecord> filtersList = new ArrayList<>();\n\n    private static FileGroupResolver instance;\n\n    private FileGroupResolver() {\n\n    }\n\n    public static FileGroupResolver getInstance() {\n        if (instance == null) {\n            instance = new FileGroupResolver();\n            instance.init();\n        }\n        return instance;\n    }\n\n    /**\n     * Reads and parse configuration\n     */\n    public void init() {\n        extensionsMap.clear();\n        filtersList.clear();\n\n        TcPreferencesAPI prefs = TcConfigurations.getPreferences();\n        for (int group = 0; group < MAX_GROUPS; group++) {\n            String masks = prefs.getVariable(TcPreference.values()[TcPreference.FILE_GROUP_1_MASK.ordinal() + group]);\n            if (masks == null) {\n                continue;\n            }\n            String[] split = masks.split(\",\");\n            for (String aSplit : split) {\n                String mask = aSplit.trim().toLowerCase();\n                addMask(mask, group);\n            }\n        }\n    }\n\n    private void addMask(String mask, int group) {\n        if (mask.startsWith(\"*.\")) {\n            String ext = mask.substring(2);\n            if (ext.contains(\"*\") || ext.contains(\"?\")) {\n                filtersList.add(new ResolverRecord(group, mask));\n            } else {\n                extensionsMap.put(ext, group);\n            }\n        } else {\n            filtersList.add(new ResolverRecord(group, mask));\n        }\n    }\n\n\n    /**\n     * Returns the number of group for specified filename or -1s\n     * @param file file\n     * @return group number (0..9) or -1\n     */\n    public int resolve(AbstractFile file) {\n        if (file.isDirectory() || file.isSymlink()) {\n            return -1;\n        }\n        String ext = file.getExtension();\n        if (ext == null) {\n            ext = \"\";\n        } else {\n            ext = ext.toLowerCase();\n        }\n        Integer group = extensionsMap.get(ext);\n        if (group != null) {\n            return group;\n        }\n        for (ResolverRecord rec : filtersList) {\n            if (rec.filter.accept(file)) {\n                return rec.group;\n            }\n        }\n        return -1;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/table/FileTable.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.table;\r\n\r\nimport java.awt.*;\r\nimport java.awt.event.*;\r\nimport java.io.Serial;\r\nimport java.util.Iterator;\r\nimport java.util.WeakHashMap;\r\n\r\nimport javax.swing.*;\r\nimport javax.swing.table.JTableHeader;\r\nimport javax.swing.table.TableCellRenderer;\r\nimport javax.swing.table.TableColumn;\r\nimport javax.swing.table.TableColumnModel;\r\n\r\nimport com.mucommander.utils.text.SizeFormat;\r\nimport com.mucommander.ui.action.impl.*;\r\nimport com.mucommander.ui.main.table.views.BaseCellRenderer;\r\nimport com.mucommander.ui.main.table.views.BaseFileTableModel;\r\nimport com.mucommander.ui.main.table.views.TableViewMode;\r\nimport com.mucommander.ui.main.table.views.compact.CompactFileTableColumnModel;\r\nimport com.mucommander.ui.main.table.views.compact.CompactFileTableModel;\r\nimport com.mucommander.ui.main.table.views.full.FileTableCellRenderer;\r\nimport com.mucommander.ui.main.table.views.full.FileTableColumnModel;\r\nimport com.mucommander.ui.main.table.views.full.FileTableConfiguration;\r\nimport com.mucommander.ui.main.table.views.full.FileTableModel;\r\nimport com.mucommander.ui.text.FilePathFieldKeyListener;\r\nimport com.mucommander.ui.theme.*;\r\nimport com.mucommander.utils.FileIconsCache;\r\nimport lombok.Getter;\r\nimport org.jetbrains.annotations.NotNull;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport com.jidesoft.swing.DefaultOverlayable;\r\nimport com.jidesoft.swing.StyledLabelBuilder;\r\n\r\nimport com.mucommander.commons.collections.Enumerator;\r\nimport com.mucommander.commons.conf.ConfigurationEvent;\r\nimport com.mucommander.commons.conf.ConfigurationListener;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.commons.runtime.OsVersion;\r\nimport com.mucommander.conf.TcConfigurations;\r\nimport com.mucommander.conf.TcPreference;\r\nimport com.mucommander.conf.TcPreferences;\r\nimport com.mucommander.desktop.DesktopManager;\r\nimport com.mucommander.job.MoveJob;\r\nimport com.mucommander.utils.text.CustomDateFormat;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.action.ActionKeymap;\r\nimport com.mucommander.ui.action.ActionManager;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.dialog.file.AbstractCopyDialog;\r\nimport com.mucommander.ui.dialog.file.FileCollisionDialog;\r\nimport com.mucommander.ui.dialog.file.ProgressDialog;\r\nimport com.mucommander.ui.event.ActivePanelListener;\r\nimport com.mucommander.ui.event.TableSelectionListener;\r\nimport com.mucommander.ui.icon.FileIcons;\r\nimport com.mucommander.ui.icon.IconManager;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.menu.TablePopupMenu;\r\nimport com.mucommander.ui.quicksearch.QuickSearch;\r\n\r\n\r\n/**\r\n * A heavily modified <code>JTable</code> which displays a folder's contents and allows file mouse and keyboard selection,\r\n * marking and navigation. <code>JTable</code> provides the basics for file selection but its behavior has to be\r\n * extended to allow file marking.\r\n *\r\n * @author Maxence Bernard, Nicolas Rinaudo\r\n */\r\npublic class FileTable extends JTable implements MouseListener, MouseMotionListener, KeyListener,\r\n                                                 ActivePanelListener, ConfigurationListener, ThemeListener {\r\n\tprivate static final Logger logger = LoggerFactory.getLogger(FileTable.class);\r\n\t\r\n    /** Minimum width for 'name' column when in automatic column sizing mode */\r\n    private final static int RESERVED_NAME_COLUMN_WIDTH = 40;\r\n    /** Minimum column width when in automatic column sizing mode */\r\n    private final static int MIN_COLUMN_AUTO_WIDTH = 20;\r\n\r\n    private static final int MAX_ROWS_FOR_AUTO_LAYOUT_CALCULATION = 50;\r\n    private static final Dimension INTERCELL_SPACING = new Dimension(0, 0);\r\n\r\n\r\n    /** Frame containing this file table. */\r\n    private final MainFrame mainFrame;\r\n    /** Folder panel containing this frame.\r\n     * -- GETTER --\r\n     *  Returns the FolderPanel that contains this FileTable\r\n     */\r\n    @Getter\r\n    private final FolderPanel folderPanel;\r\n\r\n\r\n    /** TableModel instance used by this JTable to get cells' values */\r\n    private BaseFileTableModel tableModel;\r\n    /** TableCellRender instance used by this JTable to render cells */\r\n    private BaseCellRenderer cellRenderer;\r\n    /** CellEditor used to edit filenames when clicked */\r\n    private final FilenameEditor filenameEditor;\r\n\r\n    /** Contains sort-related variables\r\n     * -- GETTER --\r\n     *  Returns a SortInfo instance that holds information about how this table is currently sorted\r\n     */\r\n    @Getter\r\n    private final SortInfo sortInfo = new SortInfo();\r\n\r\n    /** Row currently selected */\r\n    private int currentRow;\r\n\r\n    /** Column currently selected */\r\n    private int currentColumn;\r\n\r\n    // Used when right button is pressed and mouse is dragged\r\n    private boolean markOnRightClick;\r\n    private int lastDraggedRow = -1;\r\n\r\n    // Used by shift+Click\r\n    private int lastRow;\r\n\r\n    /** Allows to detect repeated key strokes of mark key (space/insert) */\r\n    private boolean markKeyRepeated;\r\n    /** In case of repeated mark keystrokes, true if last row has already been marked/unmarked */\r\n    private boolean lastRowMarked;\r\n\r\n    /** Timestamp of last row selection change */\r\n    private long selectionChangedTimestamp;\r\n\r\n    /** Timestamp of last double click */\r\n    private long lastDoubleClickTimestamp;\r\n\r\n    /** Is automatic columns sizing enabled ?\r\n     * -- GETTER --\r\n     *  Returns <code>true</code> if the auto-columns sizing is currently enabled.\r\n     */\r\n    @Getter\r\n    private boolean autoSizeColumnsEnabled;\r\n\r\n    /** Instance of the inner class that handles quick search\r\n     * -- GETTER --\r\n     *  Returns the the QuickSearch inner class instance used by this FileTable\r\n     */\r\n    @Getter\r\n    private final QuickSearch quickSearch = new FileTableQuickSearch();\r\n\r\n    /** TableSelectionListener instances registered to receive selection change events */\r\n    private final WeakHashMap<TableSelectionListener, Void> tableSelectionListeners = new WeakHashMap<>();\r\n\r\n    /** True when this table is the current or last active table in the MainFrame\r\n     * -- GETTER --\r\n     *  Returns <code>true/</code> if this table is the active one in the MainFrame.\r\n     *  Being the active table doesn't necessarily mean that it currently has focus, the focus can be in some other component\r\n     *  of the active, or nowhere in the MainFrame if the window is not in the foreground.\r\n     *  <p>Use\r\n     *  to test if the table currently has focus.\r\n     */\r\n    @Getter\r\n    private boolean isActiveTable;\r\n\r\n    /** Timestamp of the last focus gain (in milliseconds) */\r\n    private long focusGainedTime;\r\n\r\n\r\n    /** Delay in ms after which filename editor can be triggered when current row's filename cell is clicked */\r\n    private final static int EDIT_NAME_CLICK_DELAY = 500;\r\n\r\n    /** Timestamp of last double click - workaround for MouseEvent.getClickCount() */\r\n    private long doubleClickTime;\r\n\r\n    /** Counts the number of clicks within the double-click interval */\r\n    private int doubleClickCounter = 1;\r\n\r\n    /** Interval to wait for the double-click */\r\n    private static final int DOUBLE_CLICK_INTERVAL = DesktopManager.getMultiClickInterval();\r\n\r\n    /** Wrapper of presentation adjustments for the file-table */\r\n    private final FileTableWrapperForDisplay scrollpaneWrapper;\r\n\r\n    /** Table that shows the user to refresh if the location doesn't exist */\r\n    private final DefaultOverlayable overlayTable;\r\n\r\n    @Getter\r\n    private TableViewMode viewMode;\r\n\r\n    private final FileTableConfiguration conf;\r\n\r\n    /**\r\n     * Number of visible rows on table. Calculated on layout and used to compact/short model\r\n     */\r\n    private int pageSize;\r\n\r\n    /**\r\n     * Sometimes cursor gets \"sticky\". These variables used to detect this situation and fix it\r\n     */\r\n    private int lastSelectedRow, lastSelectedCol, lastSelectedEqCnt;\r\n\r\n    /** Whether to proceed with renaming the next file after renaming the selected file */\r\n    private boolean consecutiveRename;\r\n\r\n\r\n    public FileTable(MainFrame mainFrame, FolderPanel folderPanel, FileTableConfiguration conf) {\r\n        super(new FileTableModel(), new FileTableColumnModel(conf));\r\n\r\n        this.conf = conf;\r\n        tableModel = (BaseFileTableModel)getModel();\r\n        tableModel.setSortInfo(sortInfo);\r\n        tableModel.setQuickSearch(quickSearch);\r\n\r\n        ThemeManager.addCurrentThemeListener(this);\r\n\r\n        setAutoResizeMode(AUTO_RESIZE_NEXT_COLUMN);\r\n\r\n        // Stores the mainframe and folderpanel.\r\n        this.mainFrame   = mainFrame;\r\n        this.folderPanel = folderPanel;\r\n\r\n        // Remove all default action mappings as they conflict with corresponding mu actions\r\n        InputMap inputMap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);\r\n        inputMap.clear();\r\n        inputMap.setParent(null);\r\n\r\n        // Initializes the table.\r\n        setShowGrid(false);\r\n        setIntercellSpacing(INTERCELL_SPACING);\r\n        filenameEditor = new FilenameEditor(new JTextField());\r\n\r\n        setViewMode(TableViewMode.FULL);     // TODO !!!!!!\r\n        setTableHeader(new FileTableHeader(this));\r\n\r\n        // Initializes event listening.\r\n        addMouseListener(this);\r\n        folderPanel.getPanel().addMouseListener(this);\r\n        addMouseMotionListener(this);\r\n        addKeyListener(this);\r\n        mainFrame.addActivePanelListener(this);\r\n        TcConfigurations.addPreferencesListener(this);\r\n\r\n        // Mac OS X 10.5 (Leopard) and up uses JTableHeader properties to render sort indicators on table headers\r\n        // instead of a custom header renderer.\r\n        if (usesTableHeaderRenderingProperties()) {\r\n            setTableHeaderRenderingProperties();\r\n        }\r\n        \r\n        // Initialize a wrapper of presentation adjustments for the file-table\r\n        scrollpaneWrapper = new FileTableWrapperForDisplay(this, folderPanel, mainFrame);\r\n\r\n        overlayTable = createOverlayableTable();\r\n\r\n        addFocusListener(new FocusAdapter() {\r\n            @Override\r\n            public void focusGained(FocusEvent e) {\r\n                overlayTable.repaint();\r\n            }\r\n        });\r\n    }\r\n\r\n\r\n    /**\r\n     *\r\n     * @param mode - FULL, COMPACT or SHORT\r\n     */\r\n    public synchronized void setViewMode(TableViewMode mode) {\r\n        if (this.viewMode == mode) {\r\n            return;\r\n        }\r\n        final boolean fromConstructor = this.viewMode == null;\r\n        final int selectedFileIndex = fromConstructor ? 0 : getSelectedFileIndex();\r\n        final SortInfo sortInfo = getSortInfo();\r\n\r\n        this.viewMode = mode;\r\n        getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);\r\n        BaseFileTableModel oldModel = (BaseFileTableModel)getModel();\r\n        switch (mode) {\r\n            case FULL:\r\n                if (!fromConstructor) {\r\n                    setModel(new FileTableModel());\r\n                    setColumnModel(new FileTableColumnModel(conf));\r\n                }\r\n                getColumnModel().getColumn(convertColumnIndexToView(Column.NAME.ordinal())).setCellEditor(filenameEditor);\r\n                setAutoSizeColumnsEnabled(TcConfigurations.getPreferences().getVariable(TcPreference.AUTO_SIZE_COLUMNS, TcPreferences.DEFAULT_AUTO_SIZE_COLUMNS));\r\n                break;\r\n\r\n            case COMPACT:\r\n            case SHORT:\r\n                if (!fromConstructor) {\r\n                    CompactFileTableModel newModel = new CompactFileTableModel(mode.getColumnsCount(), pageSize > 0 ? pageSize : 10);\r\n                    newModel.setQuickSearch(quickSearch);\r\n                    setModel(newModel);\r\n                    setColumnModel(new CompactFileTableColumnModel(mode.getColumnsCount(), conf));\r\n                }\r\n                int columnWidth = getWidth()/mode.getColumnsCount();\r\n                for (int col = 0; col < mode.getColumnsCount(); col++) {\r\n                    TableColumn column = getColumnModel().getColumn(col);\r\n                    column.setCellEditor(filenameEditor);\r\n                    column.setWidth(columnWidth);\r\n                }\r\n                break;\r\n        }\r\n        setRowHeight();\r\n        BaseFileTableModel newModel = (BaseFileTableModel)getModel();\r\n        newModel.setupFromModel(oldModel);\r\n        tableModel = newModel;\r\n        cellRenderer = mode.createCellRenderer(this);\r\n        tableModel.setSortInfo(sortInfo);\r\n        if (!fromConstructor) {\r\n            doLayout();\r\n            try {\r\n                selectFile(selectedFileIndex);\r\n            } catch (Exception e) {\r\n                logger.error(\"Could not select file: {}\", selectedFileIndex, e);\r\n            }\r\n        }\r\n        invalidate();\r\n\r\n//        sortBy(Column.NAME);\r\n\r\n        //sortBy(sortInfo);\r\n        sortBy(sortInfo.getCriterion());\r\n        sortBy(sortInfo.getCriterion(), sortInfo.getAscendingOrder());\r\n        // TODO restore header selection\r\n    }\r\n\r\n\r\n    private DefaultOverlayable createOverlayableTable() {\r\n        return new DefaultOverlayable(scrollpaneWrapper) {\r\n            @Serial\r\n            private static final long serialVersionUID = 1L;\r\n\r\n            {\r\n                addOverlayComponent(createRefreshNonExistingLocationLabel());\r\n            }\r\n\r\n            private JLabel createRefreshNonExistingLocationLabel() {\r\n                JLabel label = StyledLabelBuilder.createStyledLabel(\"{Refresh to reconnect:f:darkGray}\");\r\n                label.setIcon(TcAction.getStandardIcon(RefreshAction.class));\r\n                return label;\r\n            }\r\n\r\n            @Override\r\n            public boolean requestFocusInWindow() {\r\n                return scrollpaneWrapper.requestFocusInWindow();\r\n            }\r\n\r\n            /**\r\n             * Overridden to ensure that the table is always visible.\r\n             */\r\n            @Override\r\n            public void setVisible(boolean visible) {\r\n                if (visible) {\r\n                    super.setVisible(true);\r\n                }\r\n            }\r\n        };\r\n    }\r\n    \r\n\r\n    /**\r\n     * Returns the FileTable as a UI component for display purpose.\r\n     * The UI component is actually a JScrollPane that allows the FileTable to scroll and\r\n     * responsible to set its viewing properties as needed.\r\n     *\r\n     * @return the FileTable as a UI component for display purpose\r\n     */\r\n    public JComponent getAsUIComponent() {\r\n        return overlayTable;\r\n    }\r\n\r\n    /**\r\n     * Under Mac OS X 10.5 (Leopard) and up, sets client properties on this table's JTableHeader to indicate the current\r\n     * sort criterion/column and sort order (ascending or descending). These properties allow Mac OS X/Java to render\r\n     * the headers accordingly, instead of having to use a {@link FileTableHeaderRenderer custom header renderer}.\r\n     * This method has no effect whatsoever on platforms other where {@link #usesTableHeaderRenderingProperties()}\r\n     * returns <code>false</code>.\r\n     */\r\n    private void setTableHeaderRenderingProperties() {\r\n        if (!usesTableHeaderRenderingProperties()) {\r\n            return;\r\n        }\r\n        JTableHeader tableHeader = getTableHeader();\r\n        if (tableHeader == null) {\r\n            return;\r\n        }\r\n\r\n        boolean isActiveTable = isActiveTable();\r\n\r\n        // Highlights the selected column\r\n        tableHeader.putClientProperty(\"JTableHeader.selectedColumn\", isActiveTable\r\n            ? convertColumnIndexToView(sortInfo.getCriterion().ordinal()) : null);\r\n\r\n        // Displays an ascending/descending arrow\r\n        tableHeader.putClientProperty(\"JTableHeader.sortDirection\", isActiveTable\r\n            ? sortInfo.getAscendingOrder() ? \"ascending\":\"descending\"      // descending is misspelled but this is OK\r\n            : null);\r\n\r\n            // Note: if this table is not currently active, properties are cleared to remove the highlighting effect.\r\n            // However, clearing the properties does not yield the desired behavior as it does not restore the table\r\n            // header back to normal. This looks like a bug in Apple's implementation.\r\n        }\r\n    \r\n    /**\r\n     * Restores selection when focus is gained.\r\n     * Note: this is not FocusListener implementation method\r\n     */\r\n    private void focusGained() {\r\n        focusGainedTime = System.currentTimeMillis();\r\n\r\n        if (isEditing()) {\r\n            filenameEditor.filenameField.requestFocus();\r\n        } else {\r\n            overlayTable.getOverlayComponents()[0].setEnabled(true);\r\n            // Repaints the table to reflect the new focused state\r\n            overlayTable.repaint();\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Hides selection when focus is lost.\r\n     * Note: this is not FocusListener implementation method\r\n     */\r\n    private void focusLost() {\r\n        // Repaints the table to reflect the new focused state\r\n        overlayTable.repaint();\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the current platform is capable of indicating the sort criterion and sort order\r\n     * on the table headers by setting client properties, instead of using a {@link FileTableHeaderRenderer custom header renderer}.\r\n     * At the moment this method returns <code>true</code> only under Mac OS X 10.5 (and up).\r\n     *  \r\n     * @return true if the current platform is capable of indicating the sort criterion and sort order on the table\r\n     * headers by setting client properties.\r\n     */\r\n    public static boolean usesTableHeaderRenderingProperties() {\r\n        return OsFamily.MAC_OS_X.isCurrent() && OsVersion.MAC_OS_X_10_5.isCurrentOrHigher();\r\n    }\r\n\r\n\r\n    /**\r\n     * Convenience method that returns this table's model (the one that {@link #getModel()} returns),\r\n     * as a {@link FileTableModel}, to avoid having to cast it.\r\n     *\r\n     * @return this table's model cast as a FileTableModel\r\n     */\r\n    public BaseFileTableModel getFileTableModel() {\r\n        return tableModel;\r\n    }\r\n\r\n    /**\r\n     * Returns the file that is currently selected (highlighted), <code>null</code> if the parent folder '..' is\r\n     * currently selected.\r\n     *\r\n     * @return the file that is currently selected (highlighted), null if the parent folder '..' is currently selected\r\n     */\r\n    public synchronized AbstractFile getSelectedFile() {\r\n        return getSelectedFile(false, false);\r\n    }\r\n\r\n    /**\r\n     * Returns the file that is currently selected (highlighted). If the currently selected file is the\r\n     * parent folder '..', the parent folder is returned only if the corresponding parameter is <code>true</code>.\r\n     *\r\n     * @param includeParentFolder if <code>true</code> and parent folder '..' is currently selected, the parent folder\r\n     * will be returned.\r\n     * @return the file that is currently selected (highlighted)\r\n     */\r\n    public synchronized AbstractFile getSelectedFile(boolean includeParentFolder) {\r\n        return getSelectedFile(includeParentFolder, false);\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the file that is currently selected (highlighted), wrapped in a {@link com.mucommander.commons.file.impl.CachedFile}\r\n     * instance if the corresponding parameter is <code>true</code>. If the currently selected file is the\r\n     * parent folder '..', the parent folder is returned only if the corresponding parameter is <code>true</code>.\r\n     *\r\n     * @param includeParentFolder if true and the parent folder '..' is currently selected, the parent folder file\r\n     * will be returned. If false, null will be returned if the parent folder file is currently selected.\r\n     * @param returnCachedFile if true, a CachedFile corresponding to the currently selected file will be returned\r\n     * @return the file that is currently selected (highlighted)\r\n     */\r\n    public synchronized AbstractFile getSelectedFile(boolean includeParentFolder, boolean returnCachedFile) {\r\n        if (tableModel.getRowCount() == 0 || (!includeParentFolder && isParentFolderSelected())) {\r\n            return null;\r\n        }\r\n        return returnCachedFile ? tableModel.getCachedFileAt(currentRow, currentColumn) : tableModel.getFileAt(currentRow, currentColumn);\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns selected files in a {@link FileSet}. Selected files are either the marked files or the currently selected\r\n     * file if no file is currently marked. The parent folder '..' is never included in the returned set.\r\n     *\r\n     * @return selected files in a FileSet\r\n     */\r\n    public FileSet getSelectedFiles() {\r\n        FileSet selectedFiles = tableModel.getMarkedFiles();\r\n        // if no row is marked, then add selected row if there is one, and if it is not parent folder\r\n        if (selectedFiles.isEmpty()) {\r\n            AbstractFile selectedFile = getSelectedFile();\r\n            if (selectedFile != null) {\r\n                selectedFiles.add(selectedFile);\r\n            }\r\n        }\r\n        return selectedFiles;\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the currently selected row/file is the parent folder '..' .\r\n     *\r\n     * @return true if the currently selected row/file is the parent folder '..'\r\n     */\r\n    public boolean isParentFolderSelected() {\r\n        int currentFileIndex = tableModel.getFileIndexAt(currentRow, currentColumn);\r\n        return currentFileIndex == 0 && tableModel.hasParentFolder();\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the given file index is the parent folder '..' .\r\n     *\r\n     * @param index index of the row to test\r\n     * @return true if the given row is the parent folder '..'\r\n     */\r\n    public boolean isParentFolder(int index) {\r\n        return index == 0 && tableModel.hasParentFolder();\r\n    }\r\n\r\n    /**\r\n     * Shorthand for {@link #setCurrentFolder(AbstractFile, AbstractFile[], AbstractFile)} called with no specific file\r\n     * to select (default selection).\r\n     *\r\n     * @param folder the new current folder\r\n     * @param children children of the specified folder\r\n     */\r\n    public void setCurrentFolder(AbstractFile folder, AbstractFile[] children) {\r\n        overlayTable.setOverlayVisible(!folder.exists());\r\n        setCurrentFolder(folder, children, null);\r\n    }\r\n\r\n    /**\r\n     * Changes the current folder to the specified one and refreshes the table to reflect the folder's contents.\r\n     * The current file selection is also updated, with the following behavior:\r\n     * <ul>\r\n     *   <li>If <code>filetoSelect</code> is not <code>null</code>, the specified file becomes the currently selected\r\n     * file, if it can be found in the new current folder. Previously marked files are cleared.</li>\r\n     *   <li>If it is <code>null</code>:\r\n     *     <ul>\r\n     *       <li>if the current folder is the same as the previous one, the currently selected file and marked files\r\n     * remain the same, provided they still exist.</li>\r\n     *       <li>if the new current folder is the parent of the previous one, the previous current folder is selected.</li>\r\n     *       <li>in any other case, the first row is selected, whether it be the parent directory ('..') or the first\r\n     * file of the current folder if it has no parent.</li>\r\n     *     </ul>\r\n     *   </li>\r\n     * </ul>\r\n     *\r\n     * <p>\r\n     * This method returns only when the folder has actually been changed and the table refreshed.<br>\r\n     * <b>Important:</b> This method should only be called by {@link FolderPanel} and in any case MUST be synchronized\r\n     * externally to ensure this method is never called concurrently by different threads.\r\n     *\r\n     * @param folder the new current folder\r\n     * @param children children of the specified folder\r\n     * @param fileToSelect the file to select, <code>null</code> for the default selection.\r\n     */\r\n    public void setCurrentFolder(AbstractFile folder, AbstractFile[] children, AbstractFile fileToSelect) {\r\n        // Stop quick search in case it was being used before folder change\r\n        if (!isQuickSearchMatchesFirst() || !folder.equals(tableModel.getCurrentFolder())) {\r\n            quickSearch.stop();\r\n        }\r\n\r\n        AbstractFile currentFolder = folderPanel.getCurrentFolder();\r\n        // If we're refreshing the current folder, save the current selection and marked files\r\n        // in order to restore them properly.\r\n        FileSet markedFiles  = null;\r\n        if (currentFolder != null && folder.equalsCanonical(currentFolder)) {\r\n            markedFiles = tableModel.getMarkedFiles();\r\n            if (fileToSelect == null) {\r\n                fileToSelect = getSelectedFile();\r\n            }\r\n        }\r\n\r\n        // If we're navigating to the current folder's parent, we select the current folder.\r\n        else if (fileToSelect == null) {\r\n            if (tableModel.hasParentFolder() && folder.equals(tableModel.getParentFolder())) {\r\n                fileToSelect = currentFolder;\r\n            }\r\n        }\r\n\r\n        // Changes the current folder in the swing thread to make sure that repaints cannot\r\n        // happen in the middle of the operation - this is used to prevent flickering, badly\r\n        // refreshed frames and such unpleasant graphical artifacts.\r\n        Runnable folderChangeThread = new FolderChangeThread(folder, children, markedFiles, fileToSelect);\r\n\r\n        // Wait for the getTask to complete, so that we return only when the folder has actually been changed and the\r\n        // table updated to reflect the new folder.\r\n        // Note: we use a wait/notify scheme rather than calling SwingUtilities#invokeAndWait to avoid deadlocks\r\n        // due to AWT thread synchronization issues.\r\n        synchronized(folderChangeThread) {\r\n            SwingUtilities.invokeLater(folderChangeThread);\r\n            while(true) {\r\n                try {\r\n                    // FolderChangeThread will call notify when done\r\n                    folderChangeThread.wait();\r\n                    break;\r\n                } catch (InterruptedException e) {\r\n                    // will keep looping\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Sets row height based on current cell's font and border, revalidates and repaints this JTable.\r\n     */\r\n    private void setRowHeight() {\r\n        // JTable.setRowHeight() revalidates and repaints the JTable.\r\n        // Note that it's important here to use the cell editor's font rather than the cell renderer's: if this method is called\r\n        // as a result to a font changed event, we do not know which class' fontChanged event will be called first.\r\n        setRowHeight(2*CellLabel.CELL_BORDER_HEIGHT + Math.max(getFontMetrics(filenameEditor.filenameField.getFont()).getHeight(), (int)FileIcons.getIconDimension().getHeight()));\r\n        // Filename editor's row resize disabled because of Java bug #4398268 which prevents new rows from being visible after setRowHeight(row, height) has been called :/\r\n        //\t\tsetRowHeight(Math.max(getFontMetrics(cellRenderer.getCellFont()).getHeight()+cellRenderer.CELL_BORDER_HEIGHT, editorRowHeight));\r\n    }\r\n\r\n\r\n    /**\r\n     * Enables/disables auto-columns sizing, which automatically resizes columns to fit the table's width.\r\n     *\r\n     * @param enabled true to enable auto-columns sizing, false to disable it\r\n     */\r\n    public void setAutoSizeColumnsEnabled(boolean enabled) {\r\n        this.autoSizeColumnsEnabled = enabled;\r\n        if (autoSizeColumnsEnabled) {\r\n            getTableHeader().setResizingAllowed(false);\r\n            // Will invoke doLayout()\r\n            resizeAndRepaint();\r\n        } else {\r\n            getTableHeader().setResizingAllowed(true);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Controls whether folders are displayed first in this FileTable or mixed with regular files.\r\n     * After calling this method, the table is refreshed to reflect the change.\r\n     *\r\n     * @param enabled if true, folders are displayed before regular files. If false, files are mixed with directories.\r\n     */\r\n    public void setFoldersFirst(boolean enabled) {\r\n        if (sortInfo.getFoldersFirst() != enabled) {\r\n            sortInfo.setFoldersFirst(enabled);\r\n            sortTable();\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Controls whether folders are sorted always alphabetical (if displayed first in this FileTable)\r\n     * After calling this method, the table is refreshed to reflect the change.\r\n     *\r\n     * @param enabled if true, folders are sorted alphabetical\r\n     */\r\n    public void setFoldersAlwaysAlphabetical(boolean enabled) {\r\n        if (sortInfo.getFoldersAlwaysAlphabetical() != enabled) {\r\n            sortInfo.setFoldersAlwaysAlphabetical(enabled);\r\n            sortTable();\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Controls whether quick search matches are displayed first in this FileTable or mixed with other files.\r\n     */\r\n    public void setShowMatchesFirst(boolean enabled) {\r\n        sortInfo.setQuickSearchMatchesFirst(enabled);\r\n    }\r\n\r\n\r\n    /**\r\n     * Selects the given file, does nothing if this table does not contain the file.\r\n     *\r\n     * @param file the file to select\r\n     * @return true if success\r\n     */\r\n    public boolean selectFile(AbstractFile file) {\r\n        int index = tableModel.getFileIndex(file);\r\n        if (index >= 0) {\r\n            selectFile(index);\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n\r\n\r\n    /**\r\n     * Makes the given row the currently selected one.\r\n     *\r\n     * @param index index of the row to select for full model or index of file for compact model\r\n     */\r\n    public void selectFile(int index) {\r\n        if (index < 0) {\r\n            index = 0;\r\n        }\r\n        if (viewMode == TableViewMode.FULL) {\r\n            changeSelection(index, 0, false, false);\r\n        } else {\r\n            CompactFileTableModel compactModel = (CompactFileTableModel)getModel();\r\n            final int cols = compactModel.getColumnCount();\r\n            final int rows = compactModel.getRowCount();\r\n            if (index < rows*cols) {\r\n                compactModel.setOffset(0);\r\n                changeSelection(index % rows, index / rows, false, false);\r\n            } else {\r\n                compactModel.setOffset((index / (rows * cols)) * rows * cols);\r\n                int index0 = index % (rows*cols);\r\n                changeSelection(index0 % rows, index0 / rows, false, false);\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Equivalent to calling {@link #setFileMarked(int, boolean, boolean)} with <code>repaint</code> enabled.\r\n     *\r\n     * @param index index of the file to mark/unmark\r\n     * @param marked true to mark the file, false to unmark it\r\n     */\r\n    private void setFileMarked(int index, boolean marked) {\r\n        setFileMarked(index, marked, true);\r\n    }\r\n\r\n    /**\r\n     * Sets the given row as marked/unmarked in the table model, repaints the row to reflect the change,\r\n     * and notifies registered {@link com.mucommander.ui.event.TableSelectionListener} that the files currently marked\r\n     * on this FileTable have changed.\r\n     *\r\n     * <p>This method has no effect if the row corresponds to the parent folder row '..' .\r\n     *\r\n     * @param index index of the file to mark/unmark\r\n     * @param marked true to mark the file, false to unmark it\r\n     * @param repaint true to repaint the row after it has been marked/unmarked\r\n     */\r\n    private void setFileMarked(int index, boolean marked, boolean repaint) {\r\n        if (isParentFolder(index)) {\r\n            return;\r\n        }\r\n\r\n        tableModel.setFileMarked(index, marked);\r\n        \r\n        if (repaint) {\r\n            int row = tableModel.getFileRow(index);\r\n            repaintRow(row);\r\n        }\r\n\r\n        // Notify registered listeners that currently marked files have changed on this FileTable\r\n        fireMarkedFilesChangedEvent();\r\n    }\r\n\r\n    /**\r\n     * Equivalent to calling {@link #setFileMarked(AbstractFile, boolean, boolean)} with <code>repaint</code> enabled.\r\n     *\r\n     * @param file file to mark/unmark\r\n     * @param marked true to mark the file, false to unmark it\r\n     */\r\n    public void setFileMarked(AbstractFile file, boolean marked) {\r\n        setFileMarked(file, marked, true);\r\n    }\r\n\r\n    /**\r\n     * Sets the given file as marked/unmarked in the table model, repaints the corresponding row to reflect the change,\r\n     * and notifies registered {@link com.mucommander.ui.event.TableSelectionListener} that currently marked files\r\n     * have changed on this FileTable.\r\n     *\r\n     * @param file file to mark/unmark\r\n     * @param marked true to mark the file, false to unmark it\r\n     * @param repaint true to repaint the file's row after it has been marked/unmarked\r\n     */\r\n    public void setFileMarked(AbstractFile file, boolean marked, boolean repaint) {\r\n        int index = tableModel.getFileIndex(file);\r\n\r\n        if (index >= 0) {\r\n            setFileMarked(index, marked, repaint);\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Marks or unmarks the current selected file (current row) and advance current row to the next one, \r\n     * with the following exceptions:\r\n     * <ul>\r\n     * <li>if quick search is active, this method does nothing\r\n     * <li>if '..' file is selected, file is not marked but current row is still advanced to the next one\r\n     * <li>if the {@link MarkSelectedFileAction} key event is repeated and the last file has already\r\n     * been marked/unmarked since the key was last released, the file is not marked in order to avoid\r\n     * marked/unmarked flaps when the mark key is kept pressed.\r\n     * </ul>\r\n     *\r\n     * @see MarkSelectedFileAction\r\n     */\r\n    public void markSelectedFile() {\r\n        // Avoids repeated mark/unmark on last row: return if last row has already been marked/unmarked by repeated mark key strokes\r\n        if (markKeyRepeated && lastRowMarked) {\r\n            return;\r\n        }\r\n\r\n        final int fileIndex = getFileTableModel().getFileIndexAt(currentRow, currentColumn);\r\n        // Don't mark '..' file but select next row\r\n        if (!isParentFolderSelected()) {\r\n            setFileMarked(fileIndex, !tableModel.isFileMarked(currentRow, currentColumn));\r\n        }\r\n\r\n        // Changes selected item to the next one\r\n        if (fileIndex < tableModel.getFilesCount()-1) {\r\n            selectFile(fileIndex + 1);\r\n        } else if (!lastRowMarked) {\r\n            // Need an explicit repaint to repaint the last row since select row is not called\r\n            repaintRow(currentRow);\r\n\r\n            // Last row has been marked/unmarked, value will be reset by keyReleased()\r\n            lastRowMarked = true;\r\n        }\r\n\r\n        // Any further mark key events will be considered as repeated until keyReleased() has been called\r\n        markKeyRepeated = true;\r\n    }\r\n\r\n\r\n    /**\r\n     * Marks or unmarks a range of rows, delimited by the provided start row index and end row index (inclusive).\r\n     * End row index can be lower, greater or equals to the start row.\r\n     *\r\n     * @param start index of the first file to repaint\r\n     * @param end index of the last file to mark, can be lower, greater or equals to startRow\r\n     * @param marked if true, the rows will be marked, unmarked otherwise\r\n     */\r\n    public void setRangeMarked(int start, int end, boolean marked) {\r\n        tableModel.setRangeMarked(start, end, marked);\r\n        int startRow = tableModel.getFileRow(start);\r\n        int endRow = tableModel.getFileRow(end);\r\n        if (viewMode == TableViewMode.FULL) {\r\n        repaintRange(startRow, endRow);\r\n        } else {\r\n            repaintRange(0, tableModel.getRowCount()-1);\r\n        }\r\n        fireMarkedFilesChangedEvent();\r\n    }\r\n\r\n\r\n    /**\r\n     * Repaints the given row.\r\n     *\r\n     * @param row the row to repaint\r\n     */\r\n    private void repaintRow(int row) {\r\n        repaint(0, row * getRowHeight(), getWidth(), rowHeight);\r\n    }\r\n\r\n    /**\r\n     * Repaints a range of rows, delimited by the provided start row index and end row index (inclusive).\r\n     * End row index can be lower, greater or equals to the start row.\r\n     *\r\n     * @param startRow index of the first row to repaint\r\n     * @param endRow index of the last row to repaint, can be lower, greater or equals to startRow\r\n     */\r\n    private void repaintRange(int startRow, int endRow) {\r\n        int rowHeight = getRowHeight();\r\n        repaint(0, Math.min(startRow, endRow)*rowHeight, getWidth(), (Math.abs(startRow-endRow)+1)*rowHeight);\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the number of rows that a page down/page up action should jump, based on this FileTable's viewport size.\r\n     * The returned number doesn't take into account the number of rows available in this FileTable.\r\n     *\r\n     * @return the number of rows that a page down/page up action should jump\r\n     */\r\n    public int getPageRowIncrement() {\r\n        return getScrollableBlockIncrement(getVisibleRect(), SwingConstants.VERTICAL, 1)/getRowHeight() - 1;\r\n    }\r\n\r\n    /**\r\n     * Sorts this FileTable by the given sort criterion, order and 'folders first' value. The criterion and ascending\r\n     * order will be ignored if the corresponding column is not currently visible, but the 'folders first' value will\r\n     * still be taken into account.\r\n     *\r\n     * @param criterion the sort criterion, see {@link Column} for possible values\r\n     * @param ascending true for ascending order, false for descending order\r\n     * @param foldersFirst if true, folders are displayed before regular files. If false, files are mixed with directories.\r\n     */\r\n    private void sortBy(Column criterion, boolean ascending, boolean foldersFirst, boolean foldersAlwaysAlphabetical) {\r\n        // If we're not changing the current sort values, abort.\r\n        if (criterion == sortInfo.getCriterion()\r\n                && ascending == sortInfo.getAscendingOrder()\r\n                && foldersFirst == sortInfo.getFoldersFirst()\r\n                && foldersAlwaysAlphabetical == sortInfo.getFoldersAlwaysAlphabetical()\r\n            ) {\r\n            return;\r\n        }\r\n\r\n        sortInfo.setFoldersFirst(foldersFirst);\r\n        sortInfo.setFoldersAlwaysAlphabetical(foldersAlwaysAlphabetical);\r\n\r\n        // Ignore the sort criterion and order if the corresponding column is not visible\r\n        if (isColumnVisible(criterion)) {\r\n            sortInfo.setCriterion(criterion);\r\n            sortInfo.setAscendingOrder(ascending);\r\n\r\n            // Mac OS X 10.5 (Leopard) and up uses JTableHeader properties to render sort indicators on table headers\r\n            if (usesTableHeaderRenderingProperties()) {\r\n                setTableHeaderRenderingProperties();\r\n            }\r\n\r\n            // Repaint header\r\n            getTableHeader().repaint();\r\n        }\r\n\r\n        // Sorts table while keeping the current file selection\r\n        sortTable();\r\n    }\r\n\r\n    /**\r\n     * Calls {@link #sortBy(Column, boolean, boolean, boolean)} with the sort information contained in the given {@link SortInfo}.\r\n     *\r\n     * @param sortInfo the information to use to sort this table.\r\n     */\r\n    public void sortBy(SortInfo sortInfo) {\r\n        sortBy(sortInfo.getCriterion(), sortInfo.getAscendingOrder(), sortInfo.getFoldersFirst(), sortInfo.getFoldersAlwaysAlphabetical());\r\n    }\r\n\r\n\r\n    /**\r\n     * Sorts this FileTable by the given sort criterion and order. The column corresponding to the specified criterion\r\n     * has to be visible when this method is called. If it isn't, this method won't have any effect.\r\n     *\r\n     * @param criterion the sort criterion, see {@link Column} for possible values\r\n     * @param ascending true for ascending order, false for descending order\r\n     */\r\n    public void sortBy(Column criterion, boolean ascending) {\r\n        sortBy(criterion, ascending, sortInfo.getFoldersFirst(), sortInfo.getFoldersAlwaysAlphabetical());\r\n    }\r\n\r\n    /**\r\n     * Sorts this FileTable by the given sort criterion. If the criterion is already the current one, the sort order\r\n     * (ascending or descending) will be reversed.\r\n     *\r\n     * @param criterion the sort criterion, see {@link Column} for possible values\r\n     */\r\n    public void sortBy(Column criterion) {\r\n        if (criterion == sortInfo.getCriterion()) {\r\n            reverseSortOrder();\r\n            return;\r\n        }\r\n\r\n        sortBy(criterion, sortInfo.getAscendingOrder());\r\n    }\r\n\r\n    /**\r\n     * Convenience method that returns this table's <code>javax.swing.table.TableColumnModel</code> cast as a\r\n     * {@link FileTableColumnModel}.\r\n     * This method return not null for full table view mode\r\n     *\r\n     * @return this table's TableColumnModel cast as a FileTableColumnModel\r\n     */\r\n    private FileTableColumnModel getFileTableColumnModel() {\r\n        return getColumnModel() instanceof FileTableColumnModel ? (FileTableColumnModel)getColumnModel() : null;\r\n    }\r\n\r\n    /**\r\n     * Convenience method that returns this table's <code>javax.swing.table.TableColumnModel</code> cast as a\r\n     * {@link CompactFileTableColumnModel}.\r\n     * This method return not null for compact table view mode\r\n     *\r\n     * @return this table's TableColumnModel cast as a CompactFileTableColumnModel\r\n     */\r\n    private CompactFileTableColumnModel getCompactFileTableColumnModel() {\r\n        return getColumnModel() instanceof CompactFileTableColumnModel ? (CompactFileTableColumnModel)getColumnModel() : null;\r\n    }\r\n\r\n    @Override\r\n    public void setColumnModel(@NotNull TableColumnModel columnModel) {\r\n        // super.setColumnModel() must be called BEFORE the methods below\r\n        super.setColumnModel(columnModel);\r\n        if (filenameEditor != null) {\r\n            if (viewMode == TableViewMode.FULL) {\r\n                columnModel.getColumn(convertColumnIndexToView(Column.NAME.ordinal())).setCellEditor(filenameEditor);\r\n            } else {\r\n                for (int i = 0; i < columnModel.getColumnCount(); i++) {\r\n                    columnModel.getColumn(i).setCellEditor(filenameEditor);\r\n                }\r\n            }\r\n        }\r\n\r\n        // Mac OS X 10.5 (Leopard) and up uses JTableHeader properties to render sort indicators on table headers\r\n        if (usesTableHeaderRenderingProperties()) {\r\n            setTableHeaderRenderingProperties();\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the specified column is currently visible.\r\n     *\r\n     * @param column column, see {@link Column} for possible values\r\n     * @return true if the specified column is currently visible\r\n     */\r\n    public boolean isColumnVisible(Column column) {\r\n        FileTableColumnModel fileTableColumnModel = getFileTableColumnModel();\r\n        return fileTableColumnModel == null || fileTableColumnModel.isColumnVisible(column);\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the given column can be displayed given the current folder. Certain columns such as\r\n     * {@link Column#OWNER} and {@link Column#GROUP} can be displayed only if current folder's files are capable\r\n     * of supplying this information.\r\n     * Note that the return value does not take into account the column's current enabled state.\r\n     *\r\n     * @param column column, see {@link Column} for possible values\r\n     * @return true if the given column can be displayed given the current folder\r\n     */\r\n    public boolean isColumnDisplayable(Column column) {\r\n        // Check this against the children's file implementation whenever possible: certain file implementations may\r\n        // return different values for the current folder than for its children. For instance, this is the case for file\r\n        // protocols that have a special file implementation for the root folder (s3 is one).\r\n        AbstractFile file = getFileTableModel().getFileAt(0);\r\n        if (file == null) {\r\n            file = folderPanel.getCurrentFolder();\r\n        }\r\n\r\n        // The Owner and Group columns are displayable only if current folder has this information\r\n        return switch (column) {\r\n            case OWNER -> file.canGetOwner();\r\n            case GROUP -> file.canGetGroup();\r\n            default -> true;\r\n        };\r\n    }\r\n\r\n    /**\r\n     * Updates the visibility of all columns based on their enabled state, and for conditional columns on the\r\n     * current folder.\r\n     */\r\n    public void updateColumnsVisibility() {\r\n        FileTableColumnModel columnModel = getFileTableColumnModel();\r\n        if (columnModel != null) {\r\n            // Full mode\r\n            for (Column c : Column.values()) {\r\n                columnModel.setColumnVisible(c, columnModel.isColumnEnabled(c) && isColumnDisplayable(c));\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the specified column is enabled.\r\n     *\r\n     * @param column column, see {@link Column} for possible values\r\n     * @return true if the specified column is enabled\r\n     */\r\n    public boolean isColumnEnabled(Column column) {\r\n        FileTableColumnModel fileTableColumnModel = getFileTableColumnModel();\r\n        return fileTableColumnModel == null || fileTableColumnModel.isColumnEnabled(column);\r\n    }\r\n\r\n    /**\r\n     * Enables/disables the specified column. Disabling a column will make it invisible. Enabling a column will make it\r\n     * visible only if the column can be displayed. See {@link #isColumnDisplayable(Column)} for more information about\r\n     * this.\r\n     *\r\n     * <p>If the current sort criterion corresponds to the specified column and this\r\n     * column is disabled, the sort criterion will be reset to {@link Column#NAME} to prevent the table from being\r\n     * sorted by an invisible column/criterion.\r\n     *\r\n     * @param column column, see {@link Column} for possible values\r\n     * @param enabled true to enable the column, false to disable it.\r\n     */\r\n    public void setColumnEnabled(Column column, boolean enabled) {\r\n        FileTableColumnModel columnModel = getFileTableColumnModel();\r\n        if (columnModel == null) {\r\n            return;\r\n        }\r\n        // Full view mode\r\n        columnModel.setColumnEnabled(column, enabled);\r\n\r\n        // Update the visibility of the column\r\n        updateColumnsVisibility();\r\n\r\n        // The column may be the current 'sort by' criterion and may have become invisible.\r\n        // If that is the case, change the criterion to NAME.\r\n        if (sortInfo.getCriterion() == column && !columnModel.isColumnVisible(column)) {\r\n            sortBy(Column.NAME);\r\n        }\r\n    }\r\n\r\n    public int getColumnPosition(Column column) {\r\n        FileTableColumnModel fileTableColumnModel = getFileTableColumnModel();\r\n        if (fileTableColumnModel == null) {\r\n            return 0;\r\n        }\r\n        return fileTableColumnModel.getColumnPosition(column.ordinal());\r\n    }\r\n\r\n    /**\r\n     * Reverses the current sort order, from ascending to descending or vice-versa.\r\n     */\r\n    public void reverseSortOrder() {\r\n        boolean newSortOrder = !sortInfo.getAscendingOrder();\r\n\r\n        sortInfo.setAscendingOrder(newSortOrder);\r\n\r\n        // Mac OS X 10.5 (Leopard) and up uses JTableHeader properties to render sort indicators on table headers\r\n        if (usesTableHeaderRenderingProperties()) {\r\n            setTableHeaderRenderingProperties();\r\n        }\r\n\r\n        // Repaint header\r\n        getTableHeader().repaint();\r\n        \r\n        // Sorts table while keeping current file selected\r\n        sortTable();\r\n    }\r\n\r\n\r\n    /**\r\n     * Turns on the filename editor on current row.\r\n     */\r\n    public void editCurrentFilename() {\r\n        // Forces CommandBar to return to its normal state as modify key release event is never fired to FileTable\r\n        mainFrame.getCommandBar().setAlternateActionsMode(false);\r\n        // Temporarily enable editing\r\n        tableModel.setNameColumnEditable(true);\r\n        // Filename editor's row resize disabled because of Java bug #4398268 which prevents new rows from being visible after setRowHeight(row, height) has been called :/\r\n        // Adjust row height to match filename editor's height\r\n        // setRowHeight(row, (int)filenameEditor.filenameField.getPreferredSize().getHeight());\r\n        // Starts editing clicked cell's name column\r\n\r\n        if (viewMode == TableViewMode.FULL) {\r\n            editCellAt(currentRow, convertColumnIndexToView(Column.NAME.ordinal()));\r\n        } else {\r\n            editCellAt(currentRow, currentColumn);\r\n        }\r\n        getFolderPanel().getFolderChangeMonitor().setPaused(true);\r\n        // Saves current/editing row in the filename editor and requests focus on the text field\r\n        filenameEditor.notifyEditing(currentRow, currentColumn);\r\n        // Disable editing\r\n        tableModel.setNameColumnEditable(false);\r\n    }\r\n\r\n\r\n    /**\r\n     * Sorts this FileTable and repaints it. Marked files and selected file will remain the same, only\r\n     * their position will have changed in the newly sorted table.\r\n     */\r\n    private synchronized void sortTable() {\r\n        // Save currently selected file\r\n        AbstractFile selectedFile = tableModel.getFileAt(currentRow, currentColumn);\r\n\r\n        // Sort table, doesn't affect marked files\r\n        tableModel.sortRows();\r\n\r\n        // Restore selected file\r\n        selectFile(selectedFile);\r\n\r\n        // Repaint table\r\n        repaint();\r\n    }\r\n\r\n\r\n    /**\r\n     * Adds the given TableSelectionListener to the list of listeners that are registered to receive\r\n     * notifications when the currently selected file changes.\r\n     *\r\n     * @param listener the TableSelectionListener instance to add to the list of registered listeners.\r\n     */\r\n    public void addTableSelectionListener(TableSelectionListener listener) {\r\n        tableSelectionListeners.put(listener, null);\r\n    }\r\n\r\n    /**\r\n     * Removes the given TableSelectionListener from the list of listeners that are registered to receive\r\n     * notifications when the currently selected file changes.\r\n     * The listener will not receive any further notification after this method has been called\r\n     * (or soon after if events are pending).\r\n     *\r\n     * @param listener the TableSelectionListener instance to add to the list of registered listeners.\r\n     */\r\n    public void removeTableSelectionListener(TableSelectionListener listener) {\r\n        tableSelectionListeners.remove(listener);\r\n    }\r\n\r\n\r\n    /**\r\n     * Notifies all registered listeners that the currently selected file has changed on this FileTable.\r\n     */\r\n    private void fireSelectedFileChangedEvent() {\r\n        for (TableSelectionListener listener : tableSelectionListeners.keySet()) {\r\n            listener.selectedFileChanged(this);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Notifies all registered listeners that the currently marked files have changed on this FileTable.\r\n     */\r\n    public void fireMarkedFilesChangedEvent() {\r\n        for (TableSelectionListener listener : tableSelectionListeners.keySet()) {\r\n            listener.markedFilesChanged(this);\r\n        }\r\n    }\r\n\r\n\r\n    private FontMetrics cellFontMetrics;\r\n    private Font lastCellFont;\r\n    private int dirStringWidth;\r\n    private int wwwStringWidth;\r\n\r\n    private void doAutoLayout(boolean respectSize) {\r\n        final AbstractFile currentFolder = getFolderPanel().getCurrentFolder();\r\n        if (cellFontMetrics == null || lastCellFont != FileTableCellRenderer.getCellFont()) {\r\n            lastCellFont = FileTableCellRenderer.getCellFont();\r\n            cellFontMetrics = getFontMetrics(lastCellFont);\r\n            final int dirStringWidth1 = cellFontMetrics.stringWidth(FileTableModel.DIRECTORY_SIZE_STRING);\r\n            final int dirStringWidth2 = cellFontMetrics.stringWidth(SizeFormat.format(1024 * 1024 * 555, FileTableModel.getSizeFormat())); // some big value with big string-length\r\n            final int dirStringWidth3 = cellFontMetrics.stringWidth(SizeFormat.format(1016 * 1024, FileTableModel.getSizeFormat())); // some other big value with big string-length\r\n            dirStringWidth = Math.max(Math.max(dirStringWidth1, dirStringWidth2), dirStringWidth3);\r\n            wwwStringWidth = cellFontMetrics.stringWidth(\"WWWW\");\r\n        }\r\n//        final FontMetrics fm = getFontMetrics(FileTableCellRenderer.getCellFont());\r\n\r\n        pageSize = getParent().getSize().height / getRowHeight();\r\n\r\n        int remainingWidth = getSize().width - RESERVED_NAME_COLUMN_WIDTH;\r\n        FileTableColumnModel fileTableColumnModel = getFileTableColumnModel();\r\n        if (fileTableColumnModel == null) {\r\n            respectSize = false;\r\n        }\r\n\r\n        // Calculate columns width in full view mode\r\n        if (viewMode != TableViewMode.FULL) {\r\n            return;\r\n        }\r\n        Iterator<TableColumn> columns = respectSize ? new Enumerator<>(getColumnModel().getColumns()) : fileTableColumnModel.getAllColumns();\r\n        TableColumn nameColumn = null;\r\n\r\n        while (columns.hasNext()) {\r\n            TableColumn column = columns.next();\r\n            Column c = Column.valueOf(column.getModelIndex());\r\n\r\n            if (c == Column.NAME) {\r\n                nameColumn = column;\r\n            } else {\r\n                int columnWidth;\r\n                if (c == Column.EXTENSION) {\r\n                    columnWidth = (int) FileIcons.getIconDimension().getWidth();\r\n                } else if (c == Column.DATE) {\r\n                    String val = currentFolder != null ? CustomDateFormat.format(currentFolder.getLastModifiedDate()) : \"\";\r\n                    columnWidth = Math.max(MIN_COLUMN_AUTO_WIDTH, cellFontMetrics.stringWidth(val));\r\n                    columnWidth *= 1.1;\r\n                } else if (c == Column.SIZE) {\r\n                    long size = 1000 * 1024 * 1024;\r\n                    String val = SizeFormat.format(size, BaseFileTableModel.getSizeFormat());\r\n                    columnWidth = Math.max(dirStringWidth, cellFontMetrics.stringWidth(val));\r\n                    columnWidth *= 1.1;\r\n                } else if (c == Column.PERMISSIONS) {\r\n                    try {\r\n                        String permissionStr = currentFolder != null ? currentFolder.getPermissionsString() : \"----\";\r\n                        columnWidth = Math.max(wwwStringWidth, cellFontMetrics.stringWidth(permissionStr));\r\n                    } catch (Exception e) {\r\n                        columnWidth = wwwStringWidth;\r\n                    }\r\n                } else {\r\n                    columnWidth = MIN_COLUMN_AUTO_WIDTH;\r\n\r\n                    int rowCount = getModel().getRowCount();\r\n                    for (int rowNum = 0; rowNum < rowCount; rowNum++) {\r\n                        if (rowNum >= MAX_ROWS_FOR_AUTO_LAYOUT_CALCULATION) {\r\n                            break;\r\n                        }\r\n                        String val = (String)getModel().getValueAt(rowNum, column.getModelIndex());\r\n                        int stringWidth = val == null ? 0 : c == Column.SIZE ? dirStringWidth : cellFontMetrics.stringWidth(val);\r\n                        columnWidth = Math.max(columnWidth, stringWidth);\r\n                    }\r\n                }\r\n                if (respectSize) {\r\n                    columnWidth = Math.min(columnWidth, remainingWidth);\r\n                }\r\n                columnWidth +=  2 * CellLabel.CELL_BORDER_WIDTH;\r\n                    \r\n                column.setWidth(columnWidth);\r\n\r\n                // Update subtotal\r\n                remainingWidth -= columnWidth;\r\n                if (remainingWidth < 0) {\r\n                    remainingWidth = 0;\r\n                }\r\n            }\r\n        }\r\n        if (nameColumn != null) {\r\n            nameColumn.setWidth(remainingWidth + RESERVED_NAME_COLUMN_WIDTH);\r\n        }\r\n    }\r\n\r\n    private void doStaticLayout() {\r\n        final int width = getSize().width;\r\n\r\n        pageSize = getParent().getSize().height / getRowHeight();\r\n\r\n        // If ve have compact layout type then just use average column width\r\n        if (getFileTableColumnModel() == null) {\r\n            int columns = getColumnModel().getColumnCount();\r\n            int columnWidth = width / getColumnModel().getColumnCount();\r\n            for (int i = 0; i < columns; i++) {\r\n                getColumnModel().getColumn(i).setWidth(columnWidth);\r\n            }\r\n\r\n            // update model if need\r\n            BaseFileTableModel model = getFileTableModel();\r\n            if (model instanceof CompactFileTableModel compactModel) {\r\n                if (compactModel.getVisibleRows() != pageSize) {\r\n                    compactModel.setVisibleRows(pageSize);\r\n                    SwingUtilities.invokeLater(this::invalidate);\r\n                }\r\n            }\r\n\r\n            final AbstractFile selectedFile = getSelectedFile();\r\n            ((CompactFileTableModel) getModel()).setVisibleRows(pageSize);\r\n\r\n            // Need to restore file selection\r\n            SwingUtilities.invokeLater(() -> {\r\n                if (selectedFile == null && getParent() != null) {\r\n                    try {\r\n                        selectFile(0);\r\n                    } catch (Exception e) {\r\n                        e.printStackTrace();\r\n                    }\r\n                } else {\r\n                    selectFile(selectedFile);\r\n                }\r\n            });\r\n\r\n            invalidate();\r\n            return;\r\n        }\r\n\r\n        // Calculate columns width for full panel view type\r\n        if (width - getColumnModel().getTotalColumnWidth() == 0) {\r\n            return;\r\n        }\r\n        TableColumn nameColumn = getColumnModel().getColumn(convertColumnIndexToView(Column.NAME.ordinal()));\r\n        nameColumn.setWidth(Math.max(nameColumn.getWidth() + width, RESERVED_NAME_COLUMN_WIDTH));\r\n    }\r\n\r\n    /**\r\n     * Overrides JTable's doLayout() method to use a custom column layout (if auto-column sizing is enabled).\r\n     */\r\n    @Override\r\n    public void doLayout() {\r\n        FileTableColumnModel fileTableColumnModel = getFileTableColumnModel();\r\n        if (!autoSizeColumnsEnabled) {\r\n            if (getTableHeader().getResizingColumn() != null) {\r\n                super.doLayout();\r\n            } else if (fileTableColumnModel != null && !fileTableColumnModel.wereColumnSizesSet()) {\r\n                doAutoLayout(false);\r\n            } else {\r\n                doStaticLayout();\r\n            }\r\n        } else {    // Custom layout\r\n            doAutoLayout(true);\r\n        }\r\n\r\n        // Ensures that current row is visible (within current viewport), and if not adjusts viewport to center it\r\n        Rectangle visibleRect = getVisibleRect();\r\n        final Rectangle cellRect = getCellRect(currentRow, 0, false);\r\n        if (cellRect.y < visibleRect.y || cellRect.y + getRowHeight( ) >visibleRect.y + visibleRect.height) {\r\n            if (scrollpaneWrapper != null) {\r\n                // At this point JViewport is not yet aware of the new FileTable dimensions, calling setViewPosition\r\n                // would not work. Instead, SwingUtilities.invokeLater is used to delay the call after all pending\r\n                // UI events (including JViewport revalidation) have been processed.\r\n                SwingUtilities.invokeLater(() ->\r\n                        scrollpaneWrapper.getViewport().setViewPosition(new Point(0, Math.max(0, cellRect.y-scrollpaneWrapper.getHeight()/2-getRowHeight()/2)))\r\n                );\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Method overridden to return a custom TableCellRenderer.\r\n     */\r\n    @Override\r\n    public TableCellRenderer getCellRenderer(int row, int column) {\r\n        return cellRenderer;\r\n    }\r\n\r\n\r\n    /**\r\n     * Method overridden to consume keyboard events when quick search is active or when a row is being editing\r\n     * in order to prevent registered actions from being fired.\r\n     */\r\n    @Override\r\n    protected boolean processKeyBinding(KeyStroke ks, KeyEvent ke, int condition, boolean pressed) {\r\n        if (quickSearch.isActive() || isEditing()) {\r\n            return true;\r\n        }\r\n\r\n//        if(ActionKeymap.isKeyStrokeRegistered(ks))\r\n//            return false;\r\n\r\n        return super.processKeyBinding(ks, ke, condition, pressed);\r\n    }\r\n\r\n    /**\r\n     * Overrides the changeSelection method from JTable to track the current selected row (the one that has focus)\r\n     * and fire a {@link com.mucommander.ui.event.TableSelectionListener#selectedFileChanged(FileTable)} event\r\n     * to registered listeners. \r\n     */\r\n    @Override\r\n    public void changeSelection(int row, int column, boolean toggle, boolean extend) {\r\n        // For shift+click\r\n        lastRow = currentRow;\r\n        int lastColumn = currentColumn;\r\n        currentRow = row;\r\n        currentColumn = column;\r\n\r\n        super.changeSelection(row, column, toggle, extend);\r\n\r\n        // Sometimes cursor gets \"sticky\".\r\n        // Here we detect this case and fix it by generating RuntimeException\r\n        if (getSelectedRow() == lastSelectedRow && getSelectedColumn() == lastSelectedCol) {\r\n            lastSelectedEqCnt++;\r\n\r\n            if (lastSelectedEqCnt == 10) {\r\n                logger.warn(\"Sticky cursor!\");\r\n                throw new RuntimeException(\"Sticky cursor!\");\r\n               /*\r\n                 at com.mucommander.ui.main.table.FileTable.changeSelection(FileTable.java:1432)\r\n                 at javax.swing.plaf.basic.BasicTableUI$Handler.mouseDragged(BasicTableUI.java:1253)\r\n                 at javax.swing.plaf.basic.BasicTableUI$MouseInputHandler.mouseDragged(BasicTableUI.java:818)\r\n                 at java.awt.AWTEventMulticaster.mouseDragged(AWTEventMulticaster.java:319)\r\n                 at java.awt.AWTEventMulticaster.mouseDragged(AWTEventMulticaster.java:319)\r\n                 at java.awt.AWTEventMulticaster.mouseDragged(AWTEventMulticaster.java:319)\r\n                 at java.awt.Component.processMouseMotionEvent(Component.java:6573)\r\n                 at javax.swing.JComponent.superProcessMouseMotionEvent(JComponent.java:3348)\r\n                 at javax.swing.Autoscroller.actionPerformed(Autoscroller.java:176)\r\n                 at javax.swing.Timer.fireActionPerformed(Timer.java:313)\r\n                 at javax.swing.Timer$DoPostEvent.run(Timer.java:245)\r\n                 at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)\r\n                 at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:749)\r\n                 at java.awt.EventQueue.access$500(EventQueue.java:97)\r\n                 at java.awt.EventQueue$3.run(EventQueue.java:702)\r\n                 at java.awt.EventQueue$3.run(EventQueue.java:696)\r\n                 at java.security.AccessController.doPrivileged(Native Method)\r\n                 at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:75)\r\n                 at java.awt.EventQueue.dispatchEvent(EventQueue.java:719)\r\n                 at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)\r\n                 at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)\r\n                 at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)\r\n                 at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)\r\n                 at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)\r\n                 at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)\r\n               */\r\n            }\r\n        } else {\r\n            lastSelectedEqCnt = 0;\r\n            lastSelectedRow = row;\r\n            lastSelectedCol = column;\r\n        }\r\n        // If file changed\r\n        if (currentRow != lastRow || (currentColumn != lastColumn && viewMode != TableViewMode.FULL)) {\r\n            // Update selection changed timestamp\r\n            selectionChangedTimestamp = System.currentTimeMillis();\r\n            // notify registered TableSelectionListener instances that the currently selected file has changed\r\n            fireSelectedFileChangedEvent();\r\n        }\r\n        //\t\t// Don't refresh status bar if up, down, space or insert key is pressed (repeated key strokes).\r\n        //\t\t// Status bar will be refreshed whenever the key is released.\r\n        //\t\t// We need this limit because refreshing status bar takes time.\r\n        //\t\tif(downKeyDown || upKeyDown || spaceKeyDown || insertKeyDown)\r\n        //\t\t\treturn;\r\n    }\r\n\r\n\r\n    @Override\r\n    public Dimension getPreferredSize() {\r\n        Container parentComp = getParent();\r\n\r\n        // Filename editor's row resize disabled because of Java bug #4398268 which prevents new rows from being visible after setRowHeight(row, height) has been called :/\r\n        /*\r\n          int height;\r\n          if(isEditing())\r\n          height = (tableModel.getRowCount()-1)*getRowHeight() + editorRowHeight;\r\n          else\r\n          height = tableModel.getRowCount()*getRowHeight();\r\n\r\n          return new Dimension(parentComp==null?0:parentComp.getWidth(), height);\r\n        */\r\n        return new Dimension(parentComp == null ? 0 : parentComp.getWidth(), tableModel.getRowCount()*getRowHeight());\r\n    }\r\n\r\n\r\n    @Override\r\n    public Dimension getPreferredScrollableViewportSize() {\r\n        return getPreferredSize();\r\n    }\r\n\r\n\r\n    @Override\r\n    public String toString() {\r\n        return getClass().getName()+\"@\"+hashCode() +\" currentFolder=\"+folderPanel.getCurrentFolder()+\" hasFocus=\"+hasFocus()+\" currentRow=\"+currentRow;\r\n    }\r\n\r\n    @Override\r\n    public void mouseClicked(MouseEvent e) {\r\n        // Discard mouse events while in 'no events mode'\r\n        if (mainFrame.getNoEventsMode()) {\r\n            return;\r\n        }\r\n\r\n        Object source = e.getSource();\r\n\r\n        // Under Linux with GNOME and KDE, Java does not honour the  multi/double-click speed preferences\r\n        // (see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5076635) and defaults to a 200ms double-click\r\n        // interval, which for most people is too low. Therefore, we cannot rely on MouseEvent#getClickCount() and\r\n        // MouseEvent#getMultiClickInterval() to always work properly and have to detect double-clicks using the\r\n        // proper system multi-click interval returned by DefaultManager#getMultiClickInterval().\r\n        if ((System.currentTimeMillis() - doubleClickTime) < DOUBLE_CLICK_INTERVAL && selectionChangedTimestamp < doubleClickTime) {\r\n            if (doubleClickCounter == 1) {\r\n                doubleClickCounter = 2; // increase only once\r\n                e.consume(); // and make sure this event is not sent anywhere else\r\n            }\r\n        } else {\r\n            // reset the counter for the double click count\r\n            doubleClickTime = System.currentTimeMillis();\r\n            doubleClickCounter = 1;\r\n        }\r\n\r\n        // If one of the table cells was left clicked...\r\n        if (source == this && DesktopManager.isLeftMouseButton(e)) {\r\n            // Clicking on the selected row's ... :\r\n            // - 'name' label triggers the filename editor\r\n            // - 'date' label triggers the change date dialog\r\n            // - 'permissions' label triggers the change permissions dialog, only if permissions can be changed\r\n            // Timestamp check is used to make sure that this mouse click did not trigger current row selection\r\n            if (doubleClickCounter == 1 && (System.currentTimeMillis() - selectionChangedTimestamp) > EDIT_NAME_CLICK_DELAY) {\r\n                int clickX = e.getX();\r\n                Point p = new Point(clickX, e.getY());\r\n                final int row = rowAtPoint(p);\r\n                final int viewColumn = columnAtPoint(p);\r\n\r\n                final Column column = viewMode == TableViewMode.FULL ? Column.valueOf(convertColumnIndexToModel(viewColumn)) : null;\r\n                final boolean isNameColumn = column == null || column == Column.NAME;\r\n                final boolean isDateColumn = column == Column.DATE;\r\n                final boolean isPermissionColumn = column == Column.PERMISSIONS;\r\n                // Test if the clicked row is current row, if column is name column, and if current row is not '..' file\r\n                if (row == currentRow && !isParentFolderSelected() && (isNameColumn || isDateColumn || isPermissionColumn)) {\r\n                    // Test if clicked point is inside the label and abort if not\r\n                    FontMetrics fm = getFontMetrics(FileTableCellRenderer.getCellFont());\r\n                    int labelWidth;\r\n                    if (column != null) {\r\n                        labelWidth = fm.stringWidth((String) tableModel.getValueAt(row, column.ordinal()));\r\n                    } else {\r\n                        labelWidth = fm.stringWidth((String) tableModel.getValueAt(row, viewColumn));\r\n                    }\r\n                    int columnX = (int) getTableHeader().getHeaderRect(viewColumn).getX();\r\n                    if (clickX < columnX+CellLabel.CELL_BORDER_WIDTH || clickX > columnX+labelWidth+CellLabel.CELL_BORDER_WIDTH) {\r\n                        return;\r\n                    }\r\n\r\n                    // The following test ensures that this mouse click is not the one that gave the focus to this table.\r\n                    // Not checking for this would cause a single click on the inactive table's current row to trigger\r\n                    // the filename/date/permission editor\r\n                    if (hasFocus() && System.currentTimeMillis() - focusGainedTime > 100) {\r\n                        // create a new thread and sleep long enough to ensure that this click was not the first of a double click\r\n                        new Thread(() -> {\r\n                            try {\r\n                                Thread.sleep(800);\r\n                            } catch (InterruptedException ignore) {}\r\n\r\n                            // Do not execute this block (cancel editing) if:\r\n                            // - a double click was made in the last second\r\n                            // - current row changed\r\n                            // - isEditing() is true which could happen if multiple clicks were made\r\n                            if ((System.currentTimeMillis() - lastDoubleClickTimestamp) > 1000 && row == currentRow) {\r\n                                if (isNameColumn) {\r\n                                    if (!isEditing()) {\r\n                                        editCurrentFilename();\r\n                                    }\r\n                                } else if (isDateColumn) {\r\n                                    ActionManager.performAction(ChangeDateAction.Descriptor.ACTION_ID, mainFrame);\r\n                                } else if (isPermissionColumn) {\r\n                                    if (getSelectedFile().getChangeablePermissions().getIntValue() != 0) {\r\n                                        ActionManager.performAction(ChangePermissionsAction.Descriptor.ACTION_ID, mainFrame);\r\n                                    }\r\n                                }\r\n                            }\r\n                        }).start();\r\n                    }\r\n                }\r\n            }\r\n            // Double-clicking on a row opens the file/folder\r\n            else if (doubleClickCounter == 2) { // Note: user can double-click multiple times\r\n                this.lastDoubleClickTimestamp = System.currentTimeMillis();\r\n                ActionManager.performAction(e.isShiftDown()\r\n                        ? OpenNativelyAction.Descriptor.ACTION_ID\r\n                        : OpenAction.Descriptor.ACTION_ID\r\n                    , mainFrame);\r\n            }\r\n\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void mouseEntered(MouseEvent e) {\r\n    }\r\n\r\n    @Override\r\n    public void mouseExited(MouseEvent e) {\r\n    }\r\n\r\n    @Override\r\n    public void mousePressed(MouseEvent e) {\r\n        // Discard mouse events while in 'no events mode'\r\n        if (mainFrame.getNoEventsMode()) {\r\n            return;\r\n        }\r\n\r\n        if (e.getSource() != this) {\r\n            return;\r\n        }\r\n\r\n        // Right-click brings a contextual popup menu\r\n        if (DesktopManager.isRightMouseButton(e)) {\r\n            // Find the row that was right-clicked\r\n            int x = e.getX();\r\n            int y = e.getY();\r\n            Point point = new Point(x, y);\r\n            int clickedRow = rowAtPoint(point);\r\n            int clickedCol = columnAtPoint(point);\r\n\r\n            // Does the row correspond to the parent '..' folder ? \r\n            boolean parentFolderClicked = clickedRow == 0 && tableModel.hasParentFolder();\r\n\r\n            // Select clicked row if it is not selected already\r\n            if (currentRow != clickedRow) {\r\n                int index = tableModel.getFileIndexAt(clickedRow, clickedCol);\r\n                selectFile(index);\r\n            }\r\n\r\n            // Request focus on this FileTable is focus is somewhere else\r\n            if (!hasFocus()) {\r\n                requestFocus();\r\n            }\r\n\r\n            // Popup menu where the user right-clicked\r\n            new TablePopupMenu(mainFrame, folderPanel.getCurrentFolder(), parentFolderClicked?null:tableModel.getFileAt(clickedRow, clickedCol), parentFolderClicked, tableModel.getMarkedFiles()).show(this, x, y);\r\n        }\r\n        // Middle-click on a row marks or unmarks it\r\n        // Control left-click also works\r\n        else if (DesktopManager.isMiddleMouseButton(e)) {\r\n            // Used by mouseDragged\r\n            lastDraggedRow = rowAtPoint(e.getPoint());\r\n            int lastDraggedCol = columnAtPoint(e.getPoint());\r\n            markOnRightClick = !tableModel.isFileMarked(lastDraggedRow, lastDraggedCol);\r\n\r\n            int lastDraggedFile = tableModel.getFileIndexAt(lastDraggedRow, lastDraggedCol);\r\n            setFileMarked(lastDraggedFile, markOnRightClick);\r\n        } else if (DesktopManager.isLeftMouseButton(e)) {\r\n            if (e.isShiftDown()) {\r\n                // Marks a group of rows, from last current row to clicked row (current row)\r\n                setRangeMarked(currentRow, lastRow, !tableModel.isFileMarked(currentRow, currentColumn));\r\n            } else if (e.isControlDown()) {\r\n                // Marks the clicked file\r\n                int rowNum = rowAtPoint(e.getPoint());\r\n                int colNum = columnAtPoint(e.getPoint());\r\n                int index = tableModel.getFileIndexAt(rowNum, colNum);\r\n                setFileMarked(index, !tableModel.isFileMarked(rowNum, colNum));\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void mouseReleased(MouseEvent e) {\r\n    }\r\n\r\n    public void mouseDragged(MouseEvent e) {\r\n        // Discard mouse motion events while in 'no events mode'\r\n        if (mainFrame.getNoEventsMode()) {\r\n            return;\r\n        }\r\n\r\n        // Marks or unmarks every row that was between the last mouseDragged point\r\n        // and the current one\r\n        if (DesktopManager.isMiddleMouseButton(e) && lastDraggedRow >= 0) {\r\n            int draggedRow = rowAtPoint(e.getPoint());\r\n            // Mouse was dragged outside of the FileTable\r\n            if (draggedRow < 0) {\r\n                return;\r\n            }\r\n\r\n            setRangeMarked(lastDraggedRow, draggedRow, markOnRightClick);\r\n            lastDraggedRow = draggedRow;\r\n        }\r\n    }\r\n\r\n\r\n    @Override\r\n    public void mouseMoved(MouseEvent e) {\r\n    }\r\n\r\n\r\n    @Override\r\n    public void keyPressed(KeyEvent e) {\r\n        // Handle Left/Right keys for compact modes\r\n        if (viewMode != TableViewMode.FULL) {\r\n            if (e.getKeyCode() == KeyEvent.VK_LEFT) {\r\n                if (getSelectedFileIndex() > 0) {\r\n                    int newFileIndex = Math.max(getSelectedFileIndex() - getRowCount(), 0);\r\n                    selectFile(newFileIndex);\r\n                    //repaintRow(getSelectedRow());\r\n                    repaint(0, 0, getWidth(), getHeight());\r\n                    e.consume();\r\n                }\r\n            } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {\r\n                int newFileIndex = Math.min(getSelectedFileIndex() + getRowCount(), getFilesCount() - 1);\r\n                selectFile(newFileIndex);\r\n                //repaintRow(getSelectedRow());\r\n                repaint(0, 0, getWidth(), getHeight());\r\n                e.consume();\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void keyTyped(KeyEvent e) {\r\n    }\r\n\r\n    @Override\r\n    public void keyReleased(KeyEvent e) {\r\n        // Discard keyReleased events while quick search is active\r\n        if (quickSearch.isActive()) {\r\n            return;\r\n        }\r\n\r\n        // Test if the event corresponds to the 'Mark/unmark selected file' action keystroke.\r\n        if (ActionManager.getActionInstance(MarkSelectedFileAction.Descriptor.ACTION_ID, mainFrame).isAccelerator(KeyStroke.getKeyStrokeForEvent(e))) {\r\n            // Reset variables used to detect repeated key strokes\r\n            markKeyRepeated = false;\r\n            lastRowMarked = false;\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void activePanelChanged(FolderPanel folderPanel) {\r\n        isActiveTable = folderPanel == getFolderPanel();\r\n\r\n        // Mac OS X 10.5 (Leopard) and up uses JTableHeader properties to render sort indicators on table headers\r\n        // instead of a custom header renderer. These indicators change when the active table has changed. \r\n        if (usesTableHeaderRenderingProperties()) {\r\n            setTableHeaderRenderingProperties();\r\n        }\r\n        \r\n        if (isActiveTable) {\r\n        \tfocusGained();\r\n        } else {\r\n        \tfocusLost();\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Listens to certain configuration variables.\r\n     */\r\n    @Override\r\n    public void configurationChanged(ConfigurationEvent event) {\r\n        switch (event.getVariable()) {\r\n            case TcPreferences.DISPLAY_COMPACT_FILE_SIZE:\r\n            \tFileTableModel.setSizeFormat(event.getBooleanValue());\r\n            \ttableModel.fillCellCache(mainFrame.getActiveTable());\r\n            \tresizeAndRepaint();\r\n                break;\r\n            case TcPreferences.DATE_FORMAT:\r\n            case TcPreferences.DATE_SEPARATOR:\r\n            case TcPreferences.TIME_FORMAT:\r\n                // Note: for the update to work properly, CustomDateFormat's configurationChanged() method has to be called\r\n                // before FileTable's, so that CustomDateFormat gets notified of date format first.\r\n                // Since listeners are stored by MuConfiguration in a hash map, order is pretty much random.\r\n                // So CustomDateFormat#updateDateFormat() has to be called before to ensure that is uses the new date format.\r\n                CustomDateFormat.updateDateFormat();\r\n                tableModel.fillCellCache(mainFrame.getActiveTable());\r\n                resizeAndRepaint();\r\n                break;\r\n            case TcPreferences.TABLE_ICON_SCALE:\r\n                // Repaint file icons if their size has changed\r\n                // Recalculate row height, revalidate and repaint the table\r\n                setRowHeight();\r\n                break;\r\n            case TcPreferences.USE_SYSTEM_FILE_ICONS:\r\n                // Repaint file icons if the system file icons policy has changed\r\n                repaint();\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * <p>A Custom CellEditor which provides the following functionalities:\r\n     * <ul>\r\n     * <li>Filename selection (without extension) when filename starts being edited.\r\n     * <li>Can be cancelled by pressing ESCAPE\r\n     * <li>Starts renaming the file when ENTER is pressed\r\n     * </ul>\r\n     *\r\n     * <p>Only once instance per FileTable is created.\r\n     *\r\n     * <p><b>Implementation note:</b> stopCellEditing() and cancelCellEditing() should not be overridden to detect\r\n     * accept/cancel user events as they are totally unrealiable and often not called, for example when clicking\r\n     * on one of the table's headers (many other cases).\r\n     */\r\n    private class FilenameEditor extends DefaultCellEditor {\r\n\r\n        private final JTextField filenameField;\r\n\r\n        /** Row that is currently being edited */\r\n        private int editingRow;\r\n        /** Column that is currently being edited */\r\n        private int editingCol;\r\n\r\n        /**\r\n         * Creates a new FilenameEditor instance.\r\n         *\r\n         * @param textField the text field to use for editing filenames\r\n         */\r\n        FilenameEditor(JTextField textField) {\r\n            super(textField);\r\n            this.filenameField = textField;\r\n            // Sets the font to the same one that's used for cell rendering (user-defined)\r\n            filenameField.setFont(FileTableCellRenderer.getCellFont());\r\n            filenameField.setFocusTraversalKeysEnabled(false);\r\n            textField.addKeyListener(\r\n                new FilePathFieldKeyListener(textField, false) {\r\n                    // Cancel editing when escape key pressed, this is unfortunately not DefaultCellEditor's default behavior\r\n                    @Override\r\n                    public void keyPressed(KeyEvent e) {\r\n                        if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {\r\n                            cancelCellEditing();\r\n                        } else if (e.getKeyCode() == KeyEvent.VK_TAB) {\r\n                            cancelCellEditing();\r\n                            consecutiveRename = editingRow < tableModel.getRowCount() - 1;\r\n                            rename();\r\n                        }\r\n                    }\r\n                }\r\n            );\r\n            textField.addActionListener(e -> rename());\r\n            textField.addFocusListener(new FocusAdapter() {\r\n\t\t\t\tpublic void focusLost(FocusEvent e) {\r\n\t\t\t\t\tcancelCellEditing();\r\n\t\t\t\t\tFileTable.this.repaint();\r\n\t\t\t\t}\r\n\t\t\t});\r\n        }\r\n        \r\n\r\n        /**\r\n         * Renames the currently edited name cell, only if the filename has changed.\r\n         */\r\n        private void rename() {\r\n            String newName = filenameField.getText();\r\n            AbstractFile fileToRename = tableModel.getFileAt(editingRow, editingCol);\r\n\r\n            if (!newName.equals(fileToRename.getName())) {\r\n                AbstractFile current = folderPanel.getCurrentFolder();\r\n                // Starts moving files\r\n                ProgressDialog progressDialog = new ProgressDialog(mainFrame, Translator.get(\"move_dialog.moving\"));\r\n                FileSet files = new FileSet(current);\r\n                files.add(fileToRename);\r\n                MoveJob renameJob;\r\n                if (!consecutiveRename) {\r\n                    renameJob = new MoveJob(null, mainFrame, files, current, newName, FileCollisionDialog.ASK_ACTION, true);\r\n                } else {\r\n                    AbstractFile fileToBeSelected = tableModel.getFileAt(editingRow+1, editingCol);\r\n                    renameJob = new MoveJob(null, mainFrame, files, current, newName, FileCollisionDialog.ASK_ACTION, true) {\r\n                        @Override\r\n                        protected void selectFileWhenFinished(AbstractFile file) {\r\n                            super.selectFileWhenFinished(fileToBeSelected);\r\n                        }\r\n                    };\r\n                }\r\n                progressDialog.start(renameJob);//renameJob.start();\r\n\r\n//                renameJob= new MoveJob(progressDialog, mainFrame, files, current, newName, FileCollisionDialog.ASK_ACTION, true);\r\n//                progressDialog.start(renameJob);\r\n            } else if (consecutiveRename) {\r\n                selectFile(editingRow + 1);\r\n                fireSelectedFileChangedEvent();\r\n\r\n                long tm = System.currentTimeMillis();\r\n                if (tm - lastInvokeEitCurrentFilename > 10) {\r\n                    SwingUtilities.invokeLater(FileTable.this::editCurrentFilename);\r\n                }\r\n                lastInvokeEitCurrentFilename = tm;\r\n\r\n//                SwingUtilities.invokeLater(FileTable.this::editCurrentFilename);\r\n//     [java] \tat com.mucommander.ui.main.table.FileTable$FilenameEditor.rename(FileTable.java:1960)\r\n//     [java] \tat com.mucommander.ui.main.table.FileTable$FilenameEditor$1.keyPressed(FileTable.java:1912)\r\n//     [java] \tat java.desktop/java.awt.AWTEventMulticaster.keyPressed(AWTEventMulticaster.java:257)\r\n\r\n//     [java] \tat com.mucommander.ui.main.table.FileTable$FilenameEditor.rename(FileTable.java:1960)\r\n//     [java] \tat com.mucommander.ui.main.table.FileTable$FilenameEditor$1.keyPressed(FileTable.java:1912)\r\n//     [java] \tat java.desktop/java.awt.AWTEventMulticaster.keyPressed(AWTEventMulticaster.java:258)\r\n            }\r\n        }\r\n\r\n        @Override\r\n        public void cancelCellEditing() {\r\n            getFolderPanel().getFolderChangeMonitor().setPaused(false);\r\n            super.cancelCellEditing();\r\n        }\r\n\r\n        static long lastInvokeEitCurrentFilename = System.currentTimeMillis();\r\n\r\n\r\n        /*\r\n        public void restore() {\r\n            // Filename editor's row resize disabled because of Java bug #4398268 which prevents new rows from being visible after setRowHeight(row, height) has been called.\r\n            // Add to that the fact that DefaultCellEditor's stopCellEditing() and cancelCellEditing() are not always called, for instance when table header is clicked.\r\n            //\t\t\t\tsetRowHeight(currentRow, cellRenderer.getFontMetrics(cellRenderer.getCellFont()).getHeight()+cellRenderer.CELL_BORDER_HEIGHT);\r\n        }\r\n*/\r\n\r\n        /**\r\n         * Notifies this editor that the given row's filename cell is being edited. This method has to be called once\r\n         * when a row just started being edited. It will save the row number and select the filename without\r\n         * its extension to make it easier to rename.\r\n         *\r\n         * @param row row which is being edited\r\n         * @param col column which is being edited\r\n         * @see AbstractCopyDialog#selectDestinationFilename(AbstractFile, String, int)\r\n         */\r\n        void notifyEditing(int row, int col) {\r\n            // The editing row has to be saved as it could change after row editing has been started\r\n            this.editingRow = row;\r\n            this.editingCol = col;\r\n\r\n            AbstractFile file = tableModel.getFileAt(editingRow, editingCol);\r\n            AbstractCopyDialog.selectDestinationFilename(file, file.getName(), 0).feedToPathField(filenameField);\r\n\r\n            // Request focus on text field\r\n            filenameField.requestFocus();\r\n        }\r\n\r\n//        private void notifyEditingRow(int row) {\r\n//            // The editing row has to be saved as it could change after row editing has been started\r\n//            this.editingRow = row;\r\n//\r\n//            AbstractFile file = tableModel.getFileAtRow(editingRow);\r\n//            AbstractCopyDialog.selectDestinationFilename(file, file.getName(), 0).feedToPathField(filenameField);\r\n//\r\n////            filenameField.setBorder(BorderFactory.createLineBorder(cellRenderer.getBakgroundOfSelectedFileInInactiveTable()));\r\n//\r\n//            // Request focus on text field\r\n//            filenameField.requestFocus();\r\n//        }\r\n\r\n\r\n        @Override\r\n        public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {\r\n            Component result = super.getTableCellEditorComponent(table, value, isSelected, row, column);\r\n            if (viewMode != TableViewMode.FULL) {\r\n                JPanel panel = new JPanel(new BorderLayout());\r\n                panel.add(result, BorderLayout.CENTER);\r\n                final AbstractFile file = tableModel.getFileAt(row, column);\r\n                CellLabel lblIcon = new CellLabel();\r\n                lblIcon.setIcon(FileIconsCache.getInstance().getIcon(file));\r\n                lblIcon.setBackground(ThemeCache.backgroundColors[ThemeCache.ACTIVE][(row % 2 == 0) ? ThemeCache.NORMAL : ThemeCache.ALTERNATE]);\r\n                panel.setBackground(lblIcon.getBackground());\r\n                //lblIcon.setHasSeparator(column < tableModel.getColumnCount() - 1);\r\n                panel.add(lblIcon, BorderLayout.WEST);\r\n                return panel;\r\n            }\r\n            return result;\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * This inner class adds 'quick search' functionality to the FileTable\r\n     */\r\n    private class FileTableQuickSearch extends QuickSearch {\r\n\r\n        /**\r\n         * Creates a new QuickSearch instance, only one instance per FileTable should be created.\r\n         */\r\n        private FileTableQuickSearch() {\r\n        \tsuper(FileTable.this);\r\n        }\r\n        \r\n        @Override\r\n\t\tprotected void searchStarted() {\r\n            sortInfo.setQuickSearchMatchesFirst(isQuickSearchMatchesFirst());\r\n        \t// Repaint the table to add the 'dim' effect on non-matching files\r\n            scrollpaneWrapper.dimBackground();\r\n\t\t}\r\n\r\n\t\t@Override\r\n\t\tprotected void searchStopped() {\r\n            if (sortInfo.getQuickSearchMatchesFirst()) {\r\n                sortTable();\r\n            }\r\n\t\t\tmainFrame.getStatusBar().updateSelectedFilesInfo();\r\n            // Removes the 'dim' effect on non-matching files.\r\n            scrollpaneWrapper.undimBackground();\r\n            // re-sort table if need\r\n        }\r\n\t\t\r\n\t\t@Override\r\n\t\tprotected int getNumOfItems() {\r\n\t\t\treturn tableModel.getFilesCount();\r\n\t\t}\r\n\r\n\t\t@Override\r\n\t\tprotected String getItemString(int index) {\r\n            return tableModel.getFileNameAt(index);\r\n\t\t}\r\n\r\n\t\t@Override\r\n\t\tprotected void searchStringBecameEmpty(String searchString) {\r\n\t\t\tmainFrame.getStatusBar().setStatusInfo(searchString); // TODO: is needed?\t\t\t\r\n\t\t}\r\n\r\n\t\t@Override\r\n\t\tprotected void matchFound(int index, String searchString, boolean itsBestSearch) {\r\n\t\t\t// Select best match's row\r\n            AbstractFile fileToSelect = tableModel.getFileAt(index);\r\n            if (sortInfo.getQuickSearchMatchesFirst()) {\r\n                //sortTable();\r\n                tableModel.sortRows();\r\n                // Restore selected file\r\n                selectFile(0);\r\n                if (fileToSelect != null) {\r\n                    selectFile(fileToSelect);\r\n                }\r\n\r\n                // Repaint table\r\n                repaint();\r\n            } else {\r\n                if (fileToSelect != null) {\r\n                    selectFile(fileToSelect);\r\n                    repaint();\r\n                }\r\n            }\r\n            // Display the new search string in the status bar\r\n            // that indicates that the search has yielded a match\r\n            String hint = \"<html><b>\" + Translator.get(\"quick_search\") + \": \" + searchString + \"</b>\";\r\n            if (sortInfo.getQuickSearchMatchesFirst()) {\r\n                hint += \"   (\" + Translator.get(\"status_bar.quick_search.press_esc_to_stop_search\") + \")\";\r\n            }\r\n            mainFrame.getStatusBar().setStatusInfo(hint, IconManager.getIcon(IconManager.IconSet.STATUS_BAR, QUICK_SEARCH_OK_ICON), false);\r\n            // re-sort table if need\r\n\t\t}\r\n\r\n\t\t@Override\r\n\t\tprotected void matchNotFound(String searchString) {\r\n\t\t\t// No file matching the search string, display the new search string with an icon\r\n            // that indicates that the search has failed\r\n            if (sortInfo.getQuickSearchMatchesFirst()) {\r\n                int currentFileIndex = tableModel.getFileIndexAt(currentRow, currentColumn);\r\n                if (currentFileIndex >= 0) {\r\n                    if (currentFileIndex > 0) {\r\n                        selectFile(tableModel.hasParentFolder() ? 1 : 0);\r\n                    }\r\n                    AbstractFile currentFile = tableModel.getFileAt(currentFileIndex);\r\n                    if (quickSearch.matches(currentFile.getName())) {\r\n                        return;\r\n                    }\r\n                }\r\n            }\r\n            String hint = \"<html><b>\" + Translator.get(\"quick_search\") + \": \" + searchString + \"</b>\";\r\n            if (sortInfo.getQuickSearchMatchesFirst()) {\r\n                hint += \"   (\" + Translator.get(\"status_bar.quick_search.press_esc_to_stop_search\") + \")\";\r\n            }\r\n            mainFrame.getStatusBar().setStatusInfo(hint, IconManager.getIcon(IconManager.IconSet.STATUS_BAR, QUICK_SEARCH_KO_ICON), false);\r\n\t\t}\r\n\r\n\t\t@Override\r\n\t    public synchronized void keyPressed(KeyEvent e) {\r\n\t    \t// Discard key events while in 'no events mode'\r\n\t        if (mainFrame.getNoEventsMode()) {\r\n\t            return;\r\n            }\r\n\t        \r\n\t        char keyChar = e.getKeyChar();\r\n\r\n\t        // If quick search is not active...\r\n\t        if (!isActive()) {\r\n\t            // Return (do not start quick search) if the key is not a valid quick search input\r\n\t            if (!isValidQuickSearchInput(e)) {\r\n\t                return;\r\n                }\r\n\r\n\t            // Return (do not start quick search) if the typed key corresponds to a registered action's accelerator\r\n\t            if (ActionKeymap.isKeyStrokeRegistered(KeyStroke.getKeyStrokeForEvent(e))) {\r\n\t                return;\r\n                }\r\n\r\n\t            // Start the quick search and continue to process the current key event\r\n\t            start();\r\n\t        }\r\n\r\n\t        // At this point, quick search is active\r\n\t        int keyCode = e.getKeyCode();\r\n\t        boolean keyHasModifiers = (e.getModifiersEx() & (KeyEvent.SHIFT_DOWN_MASK | KeyEvent.ALT_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK | KeyEvent.META_DOWN_MASK)) != 0;\r\n\r\n\t        // Backspace removes the last character of the search string\r\n\t        if (keyCode == KeyEvent.VK_BACK_SPACE && !keyHasModifiers) {\r\n\t            // Search string is empty already\r\n\t            if (isSearchStringEmpty()) {\r\n\t                return;\r\n                }\r\n\r\n\t            removeLastCharacterFromSearchString();\r\n\r\n\t            // Find the row that best matches the new search string and select it\r\n\t            findMatch(0, true, true);\r\n\t        }\r\n\t        // Escape immediately cancels the quick search\r\n\t        else if (keyCode == KeyEvent.VK_ESCAPE && !keyHasModifiers) {\r\n\t            stop();\r\n\t        }\r\n\t        // Up/Down jumps to previous/next match\r\n\t        // Shift+Up/Shift+Down marks currently selected file and jumps to previous/next match\r\n\t        else if ((keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN) && !keyHasModifiers) {\r\n\t            // Find the first row before/after the current row that matches the search string\r\n                jumpPrevNext(keyCode == KeyEvent.VK_DOWN);\r\n            }\r\n\t        // MarkSelectedFileAction and MarkNextRowAction mark the current row and moves to the next match\r\n\t        else if (ActionManager.getActionInstance(MarkSelectedFileAction.Descriptor.ACTION_ID, mainFrame).isAccelerator(KeyStroke.getKeyStrokeForEvent(e))\r\n\t             || ActionManager.getActionInstance(MarkNextRowAction.Descriptor.ACTION_ID, mainFrame).isAccelerator(KeyStroke.getKeyStrokeForEvent(e))) {\r\n                int currentIndex = tableModel.getFileIndexAt(currentRow, currentColumn);\r\n\t            if (!isParentFolderSelected()) { // Don't mark/unmark the '..' file\r\n                    setFileMarked(currentIndex, !tableModel.isFileMarked(currentRow, currentColumn));\r\n                }\r\n\r\n\t            // Find the first the next row that matches the search string\r\n\t            findMatch(currentIndex+1, true, false);\r\n\t        }\r\n\t        // MarkPreviousRowAction marks the current row and moves to the previous match\r\n\t        else if (ActionManager.getActionInstance(MarkPreviousRowAction.Descriptor.ACTION_ID, mainFrame).isAccelerator(KeyStroke.getKeyStrokeForEvent(e))) {\r\n                int currentIndex = tableModel.getFileIndexAt(currentRow, currentColumn);\r\n\t            if (!isParentFolderSelected()) { // Don't mark/unmark the '..' file\r\n                    setFileMarked(currentIndex, !tableModel.isFileMarked(currentRow, currentColumn));\r\n                }\r\n\r\n\t            // Find the first the previous row that matches the search string\r\n\t            findMatch(currentIndex-1, false, false);\r\n\t        }\r\n\t        // If no modifier other than Shift is pressed and the typed character is not a control character (space is ok)\r\n\t        // and a valid Unicode character, add it to the current search string\r\n\t        else if (isValidQuickSearchInput(e)) {\r\n\t            appendCharacterToSearchString(keyChar);\r\n\r\n\t            // Find the row that best matches the new search string and select it\r\n\t            findMatch(0, true, true);\r\n\t        } else {\r\n\t            // Test if the typed key combination corresponds to a registered action.\r\n\t            // If that's the case, the quick search is canceled and the action is performed.\r\n\t            String actionId = ActionKeymap.getRegisteredActionIdForKeystroke(KeyStroke.getKeyStrokeForEvent(e));\r\n\t            if (actionId != null) {\r\n\r\n\t                // Consume the key event otherwise it would be fired again on the FileTable\r\n\t                // (or any other KeyListener on this FileTable)\r\n\t                e.consume();\r\n\r\n                    if (getViewMode() != TableViewMode.FULL) {\r\n                        if (keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_RIGHT) {\r\n                            return;\r\n                        }\r\n                    }\r\n\t                // Cancel quick search\r\n                    if (!isQuickSearchMatchesFirst()) {\r\n                        stop();\r\n                    }\r\n\r\n\t                // Perform the action\r\n\t                ActionManager.getActionInstance(actionId, mainFrame).performAction();\r\n\t            }\r\n\r\n\t            // Do not update last search string's change timestamp\r\n\t            return;\r\n\t        }\r\n\r\n\t        // Update last search string's change timestamp\r\n\t        setLastSearchStringChange(System.currentTimeMillis());\r\n\t    }\r\n\r\n        private void jumpPrevNext(boolean next) {\r\n            int currentIndex = tableModel.getFileIndexAt(currentRow, currentColumn);\r\n            if (sortInfo.getQuickSearchMatchesFirst()) {\r\n                if (currentIndex <= 1 && !next && tableModel.hasParentFolder()) {\r\n                    findMatch(getFilesCount() - 1, false, false);\r\n                } else {\r\n                    findMatch(currentIndex + (next ? 1 : -1), next, false);\r\n                }\r\n            } else {\r\n                if (currentIndex != 1 || next || !tableModel.hasParentFolder()) {\r\n                    findMatch(currentIndex + (next ? 1 : -1), next, false);\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    // End of QuickSearch class\r\n\r\n\r\n    /**\r\n     * Not used.\r\n     */\r\n    @Override\r\n    public void colorChanged(ColorChangedEvent event) {}\r\n\r\n    /**\r\n     * Receives theme font changes notifications.\r\n     */\r\n    @Override\r\n    public void fontChanged(FontChangedEvent event) {\r\n        if (event.getFontId() == Theme.FILE_TABLE_FONT) {\r\n            // Changes filename editor's font\r\n            filenameEditor.filenameField.setFont(event.getFont());\r\n\r\n            // Recalculate row height, revalidate and repaint the table\r\n            setRowHeight();\r\n        }\r\n    }\r\n\r\n    public FileTableConfiguration getConfiguration() {\r\n        FileTableColumnModel fileTableColumnModel = getFileTableColumnModel();\r\n        return fileTableColumnModel == null ? null : fileTableColumnModel.getConfiguration();\r\n    }\r\n\r\n    public int getColumnWidth(Column column) {\r\n        FileTableColumnModel fileTableColumnModel = getFileTableColumnModel();\r\n        if (fileTableColumnModel != null) {\r\n            return fileTableColumnModel.getColumnFromId(column.ordinal()).getWidth();\r\n        }\r\n        var model = getCompactFileTableColumnModel();\r\n        if (model == null) {\r\n            return 0;\r\n        }\r\n        var c = getCompactFileTableColumnModel().getColumn(0);\r\n        return c == null ? 0 : c.getWidth();\r\n    }\r\n\r\n    /**\r\n     * This thread performs the change of current folder.\r\n     *\r\n     * @author Nicolas Rinaudo, Maxence Bernard\r\n     */\r\n    private class FolderChangeThread implements Runnable {\r\n        private final AbstractFile   folder;\r\n        private final AbstractFile[] children;\r\n        private final FileSet        markedFiles;\r\n        private final AbstractFile   selectedFile;\r\n\r\n        private FolderChangeThread(AbstractFile folder, AbstractFile[] children, FileSet markedFiles, AbstractFile selectedFile) {\r\n            this.folder       = folder;\r\n            this.children     = children;\r\n            this.markedFiles  = markedFiles;\r\n            this.selectedFile = selectedFile;\r\n            setName(getClass().getName());\r\n        }\r\n\r\n        public void run() {\r\n            try {\r\n                // Set the new current folder.\r\n                tableModel.setCurrentFolder(folder, children, FileTable.this);\r\n                // Update the visibility state of conditional columns\r\n                FileTableColumnModel columnModel = getFileTableColumnModel();\r\n                updateColumnsVisibility();\r\n\r\n                // The column corresponding to the current 'sort by' criterion may have become invisible.\r\n                // If that is the case, change the criterion to NAME. \r\n                if (columnModel != null && !columnModel.isColumnVisible(sortInfo.getCriterion())) {\r\n                    sortInfo.setCriterion(Column.NAME);\r\n\r\n                    // Mac OS X 10.5 (Leopard) and up uses JTableHeader properties to render sort indicators on table headers\r\n                    if (usesTableHeaderRenderingProperties()) {\r\n                        setTableHeaderRenderingProperties();\r\n                    }\r\n                }\r\n\r\n                // Sort the new folder using the current sort criteria, ascending/descending order and\r\n                // 'show folders first' values.\r\n                tableModel.sortRows();\r\n\r\n                // Computes the index of the new row selection.\r\n                int indexToSelect = getIndexToSelect();\r\n                selectFile(indexToSelect);\r\n                fireSelectedFileChangedEvent();\r\n\r\n                // Restore previously marked files (if any / current folder hasn't changed)\r\n                if (markedFiles != null) {\r\n                    // Restore previously marked files\r\n                    int nbMarkedFiles = markedFiles.size();\r\n                    for (int i = 0; i < nbMarkedFiles; i++) {\r\n                        int fileIndex = tableModel.getFileIndex(markedFiles.elementAt(i));\r\n                        if (fileIndex != -1) {\r\n                            tableModel.setFileMarked(fileIndex, true);\r\n                        }\r\n                    }\r\n                    // Notify registered listeners that currently marked files have changed on this FileTable\r\n                    fireMarkedFilesChangedEvent();\r\n                }\r\n                if (consecutiveRename) {\r\n                    editCurrentFilename();\r\n                    consecutiveRename = false;\r\n                }\r\n                resizeAndRepaint();\r\n            } catch (Throwable e) {\r\n                // While no such thing should happen, we want to make absolutely sure no exception\r\n                // is propagated to the AWT event dispatch thread.\r\n                logger.warn(\"Caught exception while changing folder, this should not happen!\", e);\r\n                logger.warn(e.getMessage());\r\n            } finally {\r\n                // Notify #setCurrentFolder that we're done changing the folder.\r\n                synchronized(this) {\r\n                    notify();\r\n                }\r\n            }\r\n        }\r\n\r\n        private int getIndexToSelect() {\r\n            int currentIndex = tableModel.getFileIndexAt(currentRow, currentColumn);\r\n            if (selectedFile == null) {\r\n                // If no file was marked as needing to be selected, selects the first line.\r\n                return 0;\r\n            }\r\n            // Tries to find the index of the file to select. If it cannot be found (the file might not\r\n            // exist anymore, for example), use the closest possible row.\r\n            int indexToSelect = tableModel.getFileIndex(selectedFile);\r\n            if (indexToSelect < 0) {\r\n                int filesCount = tableModel.getFilesCount();\r\n                return currentIndex < filesCount ? currentIndex : filesCount - 1;\r\n            }\r\n            return indexToSelect;\r\n        }\r\n    }\r\n\r\n    void updateSelectedFilesStatusBar() {\r\n        mainFrame.getStatusBar().updateSelectedFilesInfo();\r\n    }\r\n\r\n\r\n    /**\r\n     * For full view mode returns current row, for compact mode returns current file index\r\n     * @return full selected file index (that equals to selected row for full view mode)\r\n     */\r\n    public int getSelectedFileIndex() {\r\n        if (viewMode == TableViewMode.FULL) {\r\n            return getSelectedRow();\r\n        } else {\r\n            CompactFileTableModel model = (CompactFileTableModel)getModel();\r\n            return getSelectedRow() + getSelectedColumn() * getRowCount() + model.getOffset();\r\n        }\r\n    }\r\n\r\n\r\n    public int getFilesCount() {\r\n        return ((BaseFileTableModel)getModel()).getFilesCount();\r\n    }\r\n\r\n\r\n    private static boolean isQuickSearchMatchesFirst() {\r\n        return TcConfigurations.getPreferences().getVariable(TcPreference.SHOW_QUICK_SEARCH_MATCHES_FIRST,\r\n                TcPreferences.DEFAULT_SHOW_QUICK_SEARCH_MATCHES_FIRST);\r\n    }\r\n\r\n\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/table/FileTableHeader.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.table;\n\nimport com.mucommander.desktop.DesktopManager;\nimport com.mucommander.ui.action.ActionManager;\nimport com.mucommander.ui.action.impl.ToggleAutoSizeAction;\nimport com.mucommander.ui.main.MainFrame;\nimport ru.trolsoft.ui.TCheckBoxMenuItem;\nimport ru.trolsoft.ui.TMenuSeparator;\n\nimport javax.swing.*;\nimport javax.swing.table.JTableHeader;\nimport java.awt.event.MouseEvent;\nimport java.awt.event.MouseListener;\n\n/**\n * @author Maxence Bernard\n */\npublic class FileTableHeader extends JTableHeader implements MouseListener {\n\n    private final FileTable table;\n\n    FileTableHeader(FileTable table) {\n        super(table.getColumnModel());\n\n        this.table = table;\n        addMouseListener(this);\n    }\n\n\n    @Override\n    public boolean getReorderingAllowed() {\n        return true;\n    }\n\n\n    public void mouseClicked(MouseEvent e) {\n        Column col = Column.valueOf(table.convertColumnIndexToModel(getColumnModel().getColumnIndexAtX(e.getX())));\n\n        table.requestFocus();\n\n        if (DesktopManager.isLeftMouseButton(e)) {\n            // One of the table headers was left-clicked, sort the table by the clicked column's criterion\n            changeSortOrder(col);\n        } else if (DesktopManager.isRightMouseButton(e)) {\n            // One of the table headers was right-clicked, popup a menu that offers to hide the column\n            showSortPopupMenu(e);\n        }\n    }\n\n    private void showSortPopupMenu(MouseEvent e) {\n        JPopupMenu popupMenu = new JPopupMenu();\n        MainFrame mainFrame = table.getFolderPanel().getMainFrame();\n\n        for (Column c : Column.values()) {\n            if (c == Column.NAME) {\n                continue;\n            }\n\n            JCheckBoxMenuItem checkboxMenuItem = new TCheckBoxMenuItem(ActionManager.getActionInstance(c.getToggleColumnActionId(), mainFrame));\n\n            checkboxMenuItem.setSelected(table.isColumnEnabled(c));\n            checkboxMenuItem.setEnabled(table.isColumnDisplayable(c));\n            // Override the action's label to a shorter one\n            checkboxMenuItem.setText(c.getLabel());\n\n            popupMenu.add(checkboxMenuItem);\n        }\n\n        popupMenu.add(new TMenuSeparator());\n\n        JCheckBoxMenuItem checkboxMenuItem = new TCheckBoxMenuItem(ActionManager.getActionInstance(ToggleAutoSizeAction.Descriptor.ACTION_ID, mainFrame));\n        checkboxMenuItem.setSelected(mainFrame.isAutoSizeColumnsEnabled());\n        popupMenu.add(checkboxMenuItem);\n\n        popupMenu.show(this, e.getX(), e.getY());\n        popupMenu.setVisible(true);\n    }\n\n    private void changeSortOrder(Column col) {\n        // If the table was already sorted by this criteria, reverse order\n        if (table.getSortInfo().getCriterion() == col) {\n            table.reverseSortOrder();\n        } else {\n            table.sortBy(col);\n        }\n    }\n\n    public void mousePressed(MouseEvent e) {\n    }\n\n    public void mouseReleased(MouseEvent e) {\n    }\n\n    public void mouseEntered(MouseEvent e) {\n    }\n\n    public void mouseExited(MouseEvent e) {\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/table/FileTableHeaderRenderer.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.main.table;\n\nimport com.mucommander.ui.icon.IconManager;\n\nimport javax.swing.*;\nimport javax.swing.border.Border;\nimport javax.swing.table.DefaultTableCellRenderer;\nimport javax.swing.table.JTableHeader;\nimport java.awt.*;\n\n\n/**\n * Custom table header renderer that displays an icon to indicate the current sort criterion and the sort order\n * (ascending or descending).  \n *\n * @author Maxence Bernard\n */\npublic class FileTableHeaderRenderer extends DefaultTableCellRenderer {\n\n    private final static ImageIcon ASCENDING_ICON = IconManager.getIcon(IconManager.IconSet.COMMON, \"arrow_up.png\");\n    private final static ImageIcon DESCENDING_ICON = IconManager.getIcon(IconManager.IconSet.COMMON, \"arrow_down.png\");\n\n\n    public FileTableHeaderRenderer() {\n        // These properties can be set only once\n\n        // Icon should be on the right\n        setHorizontalTextPosition(LEFT);\n        // Increase gap size between text and icon (default is 4 pixels)\n        setIconTextGap(6);\n        // Note: the label is left-aligned by default\n        setHorizontalAlignment(JLabel.CENTER);\n    }\n\n\n    @Override\n    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {\n        // Note: the label is returned by DefaultTableHeaderRenderer#getTableCellRendererComponent() is in fact this\n        JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);\n\n        if (table != null) {\n            JTableHeader header = table.getTableHeader();\n            if (header != null) {\n                label.setForeground(header.getForeground());\n                label.setBackground(header.getBackground());\n                label.setFont(header.getFont());\n            }\n\n            FileTable fileTable = (FileTable)table;\n            if (fileTable.getSortInfo().getCriterion() == Column.valueOf(fileTable.convertColumnIndexToModel(column))) {\n                // This header is the currently selected one\n                label.setIcon(getSortingIcon(fileTable));\n            } else {\n                // The renderer component acts as a rubber-stamp, therefore the icon value needs to be set to null explicitly\n                // as it might still hold a previous value\n                label.setIcon(null);\n            }\n        }\n\n        // Use borders made specifically for table headers\n        Border border = UIManager.getBorder(\"TableHeader.cellBorder\");\n        label.setBorder(border);\n\n        // Add a tooltip as headers are sometimes too small for the text to fit entirely\n        label.setToolTipText((String)value);\n\n        return label;\n    }\n\n    private static ImageIcon getSortingIcon(FileTable fileTable) {\n        return fileTable.getSortInfo().getAscendingOrder() ? ASCENDING_ICON : DESCENDING_ICON;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/table/FileTableWrapperForDisplay.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.table;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.desktop.DesktopManager;\nimport com.mucommander.ui.border.MutableLineBorder;\nimport com.mucommander.ui.dnd.FileDropTargetListener;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.menu.TablePopupMenu;\nimport com.mucommander.ui.theme.*;\n\nimport javax.swing.*;\nimport javax.swing.border.Border;\nimport java.awt.*;\nimport java.awt.dnd.DropTarget;\nimport java.awt.event.*;\n\nimport static com.mucommander.ui.theme.ThemeManager.getCurrentColor;\n\n/**\n * This class is responsible for the viewing aspects of a FileTable component:\n * 1. Wraps the FileTable with a JScrollPane which allows it to scroll.\n * 2. Sets the colors of the FileTable.\n * 3. Sets other presentation aspects of the FileTable component.\n * 4. Initiates a popup window on right click on the FileTable component.\n * \n * @author Arik Hadas\n */\npublic class FileTableWrapperForDisplay extends JScrollPane implements FocusListener, ThemeListener {\n\n\t/** The FileTable being wrapped for display */\n\tprivate final FileTable fileTable;\n\t\n\t/** Colors relevant for the FileTable or its ScrollPane wrapper */\n\tprivate Color borderColor;\n    private Color unfocusedBorderColor;\n    private Color backgroundColor;\n    private Color unfocusedBackgroundColor;\n    private Color unmatchedBackgroundColor;\n    \n//    /** Frame containing this file table. */\n//    private final MainFrame mainFrame;\n//    /** Panel containing this file table */\n//    private final FolderPanel folderPanel;\n\n\tFileTableWrapperForDisplay(final FileTable fileTable, final FolderPanel folderPanel, final MainFrame mainFrame) {\n\t\tsuper(fileTable, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);\n\t\t\n//\t\tthis.mainFrame = mainFrame;\n//\t\tthis.folderPanel = folderPanel;\n\t\tthis.fileTable = fileTable;\n\t\t\n\t\tbackgroundColor          = getCurrentColor(Theme.FILE_TABLE_BACKGROUND_COLOR);\n        unmatchedBackgroundColor = getCurrentColor(Theme.FILE_TABLE_UNMATCHED_BACKGROUND_COLOR);\n        unfocusedBorderColor \t = getCurrentColor(Theme.FILE_TABLE_INACTIVE_BORDER_COLOR);\n        unfocusedBackgroundColor = getCurrentColor(Theme.FILE_TABLE_INACTIVE_BACKGROUND_COLOR);\n        \n\t\t// Sets the table border.\n        setBorder(new MutableLineBorder(unfocusedBorderColor, 1));\n        borderColor = getCurrentColor(Theme.FILE_TABLE_BORDER_COLOR);\n\n        // Set scroll pane's background color to match the one of this panel and FileTable\n        getViewport().setBackground(unfocusedBackgroundColor);\n        fileTable.setBackground(unfocusedBackgroundColor);\n\n        // Remove default action mappings that conflict with corresponding mu actions\n        InputMap inputMap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);\n        inputMap.clear();\n        inputMap.setParent(null);\n        \n        fileTable.addFocusListener(this);\n        \n     // Enable drop support to copy/move/change current folder when files are dropped on the FileTable\n        FileDropTargetListener dropTargetListener = new FileDropTargetListener(fileTable.getFolderPanel(), false);\n        fileTable.setDropTarget(new DropTarget(fileTable, dropTargetListener));\n        setDropTarget(new DropTarget(this, dropTargetListener));\n        \n     // Listens to theme events\n        ThemeManager.addCurrentThemeListener(this);\n        \n     // Catch mouse events on the ScrollPane\n        addMouseListener(new MouseAdapter() {\n            @Override\n            public void mousePressed(MouseEvent e) {\n                // Left-click requests focus on the FileTable\n                if (DesktopManager.isLeftMouseButton(e)) {\n                    fileTable.requestFocus();\n                }\n                // Right-click brings a contextual popup menu\n                else if (DesktopManager.isRightMouseButton(e)) {\n                    if (!fileTable.hasFocus()) {\n                        fileTable.requestFocus();\n                    }\n                    AbstractFile currentFolder = folderPanel.getCurrentFolder();\n                    new TablePopupMenu(mainFrame, currentFolder, null, false, fileTable.getFileTableModel().getMarkedFiles()).show(FileTableWrapperForDisplay.this, e.getX(), e.getY());\n                }\n            }\n        });\n\n        addMouseWheelListener(new MouseWheelListener() {\n            @Override\n            public void mouseWheelMoved(MouseWheelEvent e) {\n                int rotation = e.getWheelRotation();\n//                if (rotation > 0) {\n//                    move(1);\n//                } else if (rotation < 0) {\n//                    move(-1);\n//                }\n                if (rotation != 0) {\n                    move(rotation);\n                }\n            }\n\n            private void move(int move) {\n                Point pt = viewport.getViewPosition();\n\n                pt.y += move;\n                pt.y = Math.max(0, pt.y);\n                pt.y = Math.min(getMaxYExtent(), pt.y);\n                viewport.setViewPosition(pt);\n            }\n\n            private int getMaxYExtent() {\n                int result = viewport.getView().getHeight() - viewport.getHeight();\n                return Math.max(result, 0);\n            }\n        });\n\n    }\n\n\t@Override\n\tpublic void setVisible(boolean visible) {\n\t\tif (visible) {\n            super.setVisible(true);\n        }\n\t}\n\t\n\t@Override\n\tpublic boolean requestFocusInWindow() {\n\t\treturn fileTable.requestFocusInWindow();\n\t}\n\t\n\t/**\n     * Dims the scrollpane's background, called by {@link com.mucommander.ui.quicksearch.QuickSearch} when a quick search is started.\n     */\n    void dimBackground() {\n        fileTable.setBackground(unmatchedBackgroundColor);\n        getViewport().setBackground(unmatchedBackgroundColor);\n    }\n\n    /**\n     * Stops dimming the scrollpane's background (returns to a normal background color), called by\n     * {@link com.mucommander.ui.quicksearch.QuickSearch} when a quick search is over.\n     */\n    void undimBackground() {\n        // Identifies the new background color.\n    \tColor newColor = fileTable.hasFocus() ?  backgroundColor : unfocusedBackgroundColor;\n\n        // If the old and new background color differ, set the new background\n        // color.\n        // Otherwise, repaint the table - if we were to skip that step, quicksearch\n        // cancellation might result in a corrupt display.\n        if (newColor.equals(getViewport().getBackground())) {\n            fileTable.repaint();\n        } else {\n            fileTable.setBackground(newColor);\n            getViewport().setBackground(newColor);\n        }\n    }\n\n    @Override\n    public void focusGained(FocusEvent e) {\n    \tsetBorderColor(borderColor);\n    \tgetViewport().setBackground(backgroundColor);\n    \tfileTable.setBackground(backgroundColor);\n    \tgetViewport().repaint();\n    }\n\n    @Override\n    public void focusLost(FocusEvent e) {\n    \tsetBorderColor(unfocusedBorderColor);\n    \tgetViewport().setBackground(unfocusedBackgroundColor);\n    \tfileTable.setBackground(unfocusedBackgroundColor);\n    }\n\t\n\tprivate void setBorderColor(Color color) {\n        Border border = getBorder();\n        // Some (rather evil) look and feels will change borders outside of muCommander's control,\n        // this check is necessary to ensure no exception is thrown.\n        if (border instanceof MutableLineBorder) {\n            ((MutableLineBorder) border).setLineColor(color);\n        }\n    }\n\t\n    @Override\n    public void colorChanged(ColorChangedEvent event) {\n        switch (event.getColorId()) {\n            case Theme.FILE_TABLE_BORDER_COLOR:\n                borderColor = event.getColor();\n                if (fileTable.hasFocus()) {\n                    setBorderColor(borderColor);\n                    repaint();\n                }\n                break;\n            case Theme.FILE_TABLE_INACTIVE_BORDER_COLOR:\n                unfocusedBorderColor = event.getColor();\n                if (!fileTable.hasFocus()) {\n                    setBorderColor(unfocusedBorderColor);\n                    repaint();\n                }\n                break;\n            case Theme.FILE_TABLE_BACKGROUND_COLOR:\n                backgroundColor = event.getColor();\n                if (fileTable.hasFocus()) {\n                    getViewport().setBackground(backgroundColor);\n                    fileTable.setBackground(backgroundColor);\n                }\n                break;\n            case Theme.FILE_TABLE_INACTIVE_BACKGROUND_COLOR:\n                unfocusedBackgroundColor = event.getColor();\n                if (!fileTable.hasFocus()) {\n                    getViewport().setBackground(unfocusedBackgroundColor);\n                    fileTable.setBackground(unfocusedBackgroundColor);\n                }\n                break;\n\n            case Theme.FILE_TABLE_UNMATCHED_BACKGROUND_COLOR:\n                unmatchedBackgroundColor = event.getColor();\n                break;\n        }\n    }\n\n    /**\n     * Not used.\n     */\n    public void fontChanged(FontChangedEvent event) {\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/table/SortInfo.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.table;\n\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\n\n/**\n * This class holds information describes how a {@link FileTable} is currently sorted: sort criterion,\n * ascending/descending order, whether directories are displayed first or mixed with regular files.\n *\n * <p>The values are not meant to be changed outside this package: all setters are package-protected.\n * Use {@link FileTable} methods to change how the table is sorted.\n *\n * @author Maxence Bernard\n */\npublic class SortInfo implements Cloneable {\n\n    /** Current sort criterion */\n    private Column criterion = Column.NAME;\n\n    /** Ascending/descending order */\n    private boolean ascendingOrder = true;\n\n    /** Should folders be displayed first, or mixed with regular files */\n    private boolean showFoldersFirst = TcConfigurations.getPreferences().getVariable(TcPreference.SHOW_FOLDERS_FIRST, TcPreferences.DEFAULT_SHOW_FOLDERS_FIRST);\n\n    /** Should Folders also get sorted or always alphabetical ... only possible if Folders First enabled */\n    private boolean foldersAlwaysAlphabetical = TcConfigurations.getPreferences().getVariable(TcPreference.FOLDERS_ALWAYS_ALPHABETICAL, TcPreferences.DEFAULT_FOLDERS_ALWAYS_ALPHABETICAL);\n\n    private boolean showQuickSearchMatchesFirst = TcConfigurations.getPreferences().getVariable(TcPreference.SHOW_QUICK_SEARCH_MATCHES_FIRST, TcPreferences.DEFAULT_SHOW_QUICK_SEARCH_MATCHES_FIRST);\n\n    SortInfo() {\n    }\n\n\n    /**\n     * Returns the column used as a criterion to sort the table.\n     *\n     * @return the column used as a criterion to sort the table.\n     */\n    public Column getCriterion() {\n        return criterion;\n    }\n\n    /**\n     * Sets the column to be used as a criterion to sort the table.\n     *\n     * @param criterion the column to be used as a criterion to sort the table, see {@link Column} for possible values\n     */\n    public void setCriterion(Column criterion) {\n        this.criterion = criterion;\n    }\n\n    /**\n     * Returns <code>true</code> if the current sort order of is ascending, <code>false</code> if it is descending.\n     *\n     * @return true if the current sort order is ascending, false if it is descending\n     */\n    public boolean getAscendingOrder() {\n        return ascendingOrder;\n    }\n\n    /**\n     * Sets the sort order of the column corresponding to the current criterion.\n     *\n     * @param ascending true if the current sort order is ascending, false if it is descending\n     */\n    public void setAscendingOrder(boolean ascending) {\n        this.ascendingOrder = ascending;\n    }\n\n    /**\n     * Sets whether folders are currently sorted and displayed before regular files or mixed with them.\n     *\n     * @param showFoldersFirst true if folders are sorted and displayed before regular files, false if they are mixed with regular files and sorted altogether\n     */\n    public void setFoldersFirst(boolean showFoldersFirst) {\n        this.showFoldersFirst = showFoldersFirst;\n    }\n\n    /**\n     * Sets whether folders are currently sorted always alphabetical.\n     *\n     * @param foldersAlwaysAlphabetical true if folders are sorted always alphabetical\n     */\n    public void setFoldersAlwaysAlphabetical(boolean foldersAlwaysAlphabetical) {\n        this.foldersAlwaysAlphabetical = foldersAlwaysAlphabetical;\n    }\n\n    /**\n     * Returns <code>true</code> if folders are sorted and displayed before regular files, <code>false</code> if they\n     * are mixed with regular files and sorted altogether.\n     *\n     * @return true if folders are sorted and displayed before regular files, false if they are mixed with regular files and sorted altogether\n     */\n    public boolean getFoldersFirst() {\n        return showFoldersFirst;\n    }\n\n    /**\n     * Returns <code>true</code> if folders are sorted always alphabetical\n     *\n     * @return true if folders are sorted always alphabetical\n     */\n    public boolean getFoldersAlwaysAlphabetical() {\n        return foldersAlwaysAlphabetical;\n    }\n\n    /**\n     * Sets whether matched files are currently sorted and displayed before other files or mixed with them on quick search.\n     *\n     * @param quickSearchMatchesFirst true if matched are sorted and displayed before other files, false if they are mixed with regular files and sorted altogether\n     */\n    void setQuickSearchMatchesFirst(boolean quickSearchMatchesFirst) {\n        this.showQuickSearchMatchesFirst = quickSearchMatchesFirst;\n    }\n\n    /**\n     * Returns <code>true</code> if quick search matched are sorted and displayed before other files, <code>false</code> if they\n     * are mixed with other files and sorted altogether.\n     *\n     * @return true if matched are sorted and displayed before other files, false if they are mixed with other files and sorted altogether\n     */\n    public boolean getQuickSearchMatchesFirst() {\n        return showQuickSearchMatchesFirst;\n    }\n\n\n\n    @Override\n    public SortInfo clone() {\n        try {\n            return (SortInfo)super.clone();\n        } catch(CloneNotSupportedException e) {\n            // Should never happen\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/table/TransparentCellLabel.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.main.table;\n\nimport java.awt.*;\n\n/**\n * @author Oleg Trifonov\n * Created on 26/01/17.\n */\npublic class TransparentCellLabel extends CellLabel {\n\n    public TransparentCellLabel() {\n        super();\n    }\n\n    @Override\n    public void paint(Graphics g) {\n        Graphics2D g2 = (Graphics2D) g.create();\n        g2.setComposite(AlphaComposite.SrcAtop.derive(0.3f));\n        super.paint(g2);\n        g2.dispose();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/table/package.html",
    "content": "<body>\n  Contains all the classes used to display files in the main muCommander window.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/table/views/BaseCellRenderer.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.main.table.views;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.main.table.CellLabel;\nimport com.mucommander.ui.main.table.FileTable;\nimport com.mucommander.ui.main.table.Column;\nimport com.mucommander.ui.theme.*;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javax.swing.table.TableCellRenderer;\nimport java.awt.Font;\n\n/**\n * @author Oleg Trifonov\n * Created on 03/04/15.\n */\npublic abstract class BaseCellRenderer implements TableCellRenderer, ThemeListener {\n\n    private static Logger logger;\n\n    protected final FileTable table;\n    protected final BaseFileTableModel tableModel;\n\n\n    /** Custom JLabel that render specific column cells */\n    protected CellLabel[] cellLabels;\n\n    protected BaseCellRenderer(FileTable table) {\n        this.table = table;\n        this.tableModel = table.getFileTableModel();\n    }\n\n    protected static int getFileColorIndex(int fileIndex, AbstractFile file, BaseFileTableModel tableModel) {\n        // Parent directory.\n        if (fileIndex == 0 && tableModel.hasParentFolder()) {\n            return ThemeCache.FOLDER;\n        }\n        // Marked file\n        if (tableModel.isFileMarked(fileIndex)) {\n            return ThemeCache.MARKED;\n        }\n        // Symlink\n        if (file.isSymlink()) {\n            return ThemeCache.SYMLINK;\n        }\n        // Hidden file/folder\n        if (file.isHidden()) {\n            return file.isDirectory() ? ThemeCache.HIDDEN_FOLDER : ThemeCache.HIDDEN_FILE;\n        }\n        // Directory\n        if (file.isDirectory()) {\n            return ThemeCache.FOLDER;\n        }\n        // Archive\n        if (file.isBrowsable()) {\n            return ThemeCache.ARCHIVE;\n        }\n        // Executable\n        if (file.isExecutable()) {\n            return ThemeCache.EXECUTABLE;\n        }\n        // Plain file\n        return ThemeCache.PLAIN_FILE;\n    }\n\n\n    /**\n     * Returns the font used to render all table cells.\n     */\n    public static Font getCellFont() {\n        return ThemeCache.tableFont;\n    }\n\n\n    /**\n     * Sets CellLabels' font to the current one.\n     */\n    // TODO\n    protected void setCellLabelsFont(Font newFont) {\n        // Set custom font\n        for (Column c : Column.values()) {\n            // No need to set extension label's font as this label renders only icons and no text\n            if (c == Column.EXTENSION) {\n                continue;\n            }\n\n            cellLabels[c.ordinal()].setFont(newFont);\n        }\n    }\n\n\n    /**\n     * Receives theme color changes notifications.\n     */\n    @Override\n    public void colorChanged(ColorChangedEvent event) {\n        table.repaint();\n    }\n\n    /**\n     * Receives theme font changes notifications.\n     */\n    @Override\n    public void fontChanged(FontChangedEvent event) {\n        if (event.getFontId() == Theme.FILE_TABLE_FONT) {\n            setCellLabelsFont(ThemeCache.tableFont);\n        }\n    }\n\n    protected void debug(String s) {\n        getLogger().debug(s);\n    }\n\n\n    private static Logger getLogger() {\n        if (logger == null) {\n            logger = LoggerFactory.getLogger(BaseCellRenderer.class);\n        }\n        return logger;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/table/views/BaseFileTableModel.java",
    "content": "/*\r\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\r\n * Copyright (C) 2014-2016 Oleg Trifonov\r\n *\r\n * trolCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * trolCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\npackage com.mucommander.ui.main.table.views;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.filter.FileFilter;\r\nimport com.mucommander.commons.file.impl.CachedFile;\r\nimport com.mucommander.commons.file.impl.local.LocalFile;\r\nimport com.mucommander.commons.file.util.FileComparator;\r\nimport com.mucommander.commons.file.util.FileSet;\r\nimport com.mucommander.conf.TcConfigurations;\r\nimport com.mucommander.conf.TcPreference;\r\nimport com.mucommander.conf.TcPreferences;\r\nimport com.mucommander.utils.text.SizeFormat;\r\nimport com.mucommander.ui.main.table.CalculateDirectorySizeWorker;\r\nimport com.mucommander.ui.main.table.FileTable;\r\nimport com.mucommander.ui.main.table.SortInfo;\r\nimport com.mucommander.ui.quicksearch.QuickSearch;\r\n\r\nimport javax.swing.table.AbstractTableModel;\r\nimport java.awt.Cursor;\r\nimport java.util.*;\r\n\r\n/**\r\n * @author Oleg Trifonov\r\n * Created on 04/04/15.\r\n */\r\npublic abstract class BaseFileTableModel extends AbstractTableModel {\r\n\r\n    private static final Cursor WAIT_CURSOR = new Cursor(Cursor.WAIT_CURSOR);\r\n\r\n    /** String used as size information for directories */\r\n    public static final String DIRECTORY_SIZE_STRING = \"<DIR>\";\r\n\r\n    /** String used as size information for directories that queued to size calculation */\r\n    protected static final String QUEUED_DIRECTORY_SIZE_STRING = \"<...>\";\r\n\r\n\r\n    /** True if the name column is temporarily editable */\r\n    protected boolean nameColumnEditable;\r\n\r\n    /** SizeFormat format used to create the size column's string */\r\n    protected static int sizeFormat;\r\n\r\n    /** Contains sort-related variables */\r\n    private SortInfo sortInfo;\r\n\r\n    /**\r\n     * QuickSearch object to sorting and filtering matched files\r\n     */\r\n    protected QuickSearch quickSearch;\r\n\r\n    /** Index array */\r\n    protected int[] fileArrayIndex;\r\n\t\r\n    /** The current folder */\r\n    protected AbstractFile currentFolder;\r\n\r\n    /** Date of the current folder when it was changed */\r\n    protected long currentFolderDateSnapshot;\r\n\r\n    /** The current folder's parent folder, may be null */\r\n    protected AbstractFile parent;\r\n\r\n    /** Cached file instances */\r\n    private AbstractFile cachedFiles[];\r\n\r\n    /** Combined size of files currently marked */\r\n    private long markedTotalSize;\r\n\r\n    /** Number of files currently marked */\r\n    private int nbFilesMarked;\r\n\r\n    /** Marked files array */\r\n    private boolean fileMarked[];\r\n\r\n\r\n    /** Tasks queue for directory size calculate */\r\n    protected final List<AbstractFile> calculateSizeQueue = new LinkedList<>();\r\n\r\n    /** Worker to calculate directories sizes */\r\n    private CalculateDirectorySizeWorker calculateDirectorySizeWorker;\r\n\r\n    /** True if the table has directories with calculated size */\r\n    protected boolean hasCalculatedDirectories;\r\n\r\n    /** Stores marked directories to calculate these size if need */\r\n    private final Set<AbstractFile> markedDirectories = new HashSet<>();\r\n\r\n    /** Here will be stored sizes of directories calculated by F3 command */\r\n    protected final Map<AbstractFile, Long> directorySizes = new HashMap<>();\r\n\r\n    private FileComparator fileComparator;\r\n\r\n    /*\r\n     * First visible row\r\n     */\r\n    //protected int firstVisibleRow;\r\n\r\n\r\n\r\n\r\n    static {\r\n        // Initialize the size column format based on the configuration\r\n        setSizeFormat(getFileSizeFormat());\r\n    }\r\n\r\n    private static boolean getFileSizeFormat() {\r\n        return TcConfigurations.getPreferences().getVariable(TcPreference.DISPLAY_COMPACT_FILE_SIZE,\r\n                                                  TcPreferences.DEFAULT_DISPLAY_COMPACT_FILE_SIZE);\r\n    }\r\n\r\n\r\n    public abstract void fillCellCache(FileTable fileTable);\r\n    public abstract int getFileRow(int index);\r\n\r\n    /**\r\n     * Init and fill cell cache to speed up table even more\r\n     */\r\n    protected abstract void initCellValuesCache();\r\n\r\n    /**\r\n     * Returns index of file in directory (index of '..' == 0)\r\n     * @param row table row\r\n     * @param col table column\r\n     * @return index file in the table\r\n     */\r\n    public abstract int getFileIndexAt(int row, int col);\r\n\r\n\r\n    protected BaseFileTableModel() {\r\n        fileArrayIndex = new int[0];\r\n        fileMarked = new boolean[0];\r\n        // Init arrays to avoid NullPointerExceptions until setCurrentFolder() gets called for the first time\r\n        cachedFiles = new AbstractFile[0];\r\n    }\r\n\r\n\r\n    public synchronized void setupFromModel(BaseFileTableModel model) {\r\n        if (model == null) {\r\n            return;\r\n        }\r\n        this.sortInfo = model.sortInfo;\r\n        this.fileArrayIndex = model.fileArrayIndex;\r\n        this.currentFolder = model.currentFolder;\r\n        this.currentFolderDateSnapshot = model.currentFolderDateSnapshot;\r\n        this.parent = model.parent;\r\n        this.cachedFiles = model.cachedFiles;\r\n        this.markedTotalSize = model.markedTotalSize;\r\n        this.nbFilesMarked = model.nbFilesMarked;\r\n        this.fileMarked = model.fileMarked;\r\n        stopSizeCalculation();\r\n    }\r\n\r\n    /**\r\n     * Sets the SizeFormat format used to create the size column's string.\r\n     *\r\n     * @param compactSize true to use a compact size format, false for full size in bytes \r\n     */\r\n    public static void setSizeFormat(boolean compactSize) {\r\n        if (compactSize) {\r\n            sizeFormat = SizeFormat.DIGITS_MEDIUM | SizeFormat.UNIT_SHORT;// | SizeFormat.ROUND_TO_KB;\r\n        } else {\r\n            sizeFormat = SizeFormat.DIGITS_FULL;\r\n        }\r\n\r\n        sizeFormat |= SizeFormat.INCLUDE_SPACE;\r\n    }\r\n\r\n    /**\r\n     * Returns the SizeFormat format used to create the size column's string\r\n     * @return  SizeFormat bit mask\r\n     */\r\n    public static int getSizeFormat() {\r\n        return sizeFormat;\r\n    }\r\n\r\n    /**\r\n     * Pre-fetch the attributes that are used by the table renderer and some actions from the given CachedFile.\r\n     * By doing so, the attributes will be available when the associated getters are called and thus the methods won't\r\n     * be I/O bound and will not lock.\r\n     *\r\n     * @param cachedFile a CachedFile instance from which to pre-fetch attributes\r\n     */\r\n    private static void prefetchCachedFileAttributes(AbstractFile cachedFile) {\r\n        cachedFile.isDirectory();\r\n        cachedFile.isBrowsable();\r\n        cachedFile.isHidden();\r\n\r\n        // Pre-fetch isSymlink attribute and if the file is a symlink, pre-fetch the canonical file and its attributes\r\n        if (cachedFile.isSymlink()) {\r\n            AbstractFile canonicalFile = cachedFile.getCanonicalFile();\r\n            if (canonicalFile != cachedFile) {  // Cheap test to prevent infinite recursion on bogus file implementations\r\n                prefetchCachedFileAttributes(canonicalFile);\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the file located at the given index, not including the parent file.\r\n     * Returns <code>null</code> if fileIndex is lower than 0 or is greater than or equals {@link #getFileCount() getFileCount()}.\r\n     *\r\n     * @param fileIndex index of a file, comprised between 0 and #getFileCount()\r\n     * @return the file located at the given index, not including the parent file\r\n     */\r\n    public synchronized AbstractFile getFileAt(int fileIndex) {\r\n        if (parent != null) {\r\n            if (fileIndex == 0) {\r\n                return parent;\r\n            }\r\n            fileIndex--;\r\n        }\r\n\r\n        // Need to check that row index is not larger than actual number of rows\r\n        // because if table has just been changed (rows have been removed),\r\n        // JTable may have an old row count value and may try to repaint rows that are out of bounds.\r\n        if (fileIndex >= 0 && fileIndex < fileArrayIndex.length) {\r\n            return ((CachedFile)cachedFiles[fileArrayIndex[fileIndex]]).getProxiedFile();\r\n        }\r\n        return null;\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the actual number of files the current folder contains, including the parent '..' file (if any).\r\n     *\r\n     * @return the actual number of files the current folder contains, including the parent '..' file (if any)\r\n     */\r\n    public synchronized int getFileCount() {\r\n        return cachedFiles.length + (parent != null ? 1 : 0);\r\n    }\r\n\r\n    /**\r\n     * Returns the actual number of files the current folder contains, not including the parent '..' file (if any).\r\n     *\r\n     * @return the actual number of files the current folder contains, not including the parent '..' file (if any)\r\n     */\r\n    public synchronized int getFileCountWithoutParent() {\r\n        return cachedFiles.length;\r\n    }\r\n\r\n    /**\r\n     * Returns the current folder's children. The returned array contains {@link AbstractFile} instances, and not\r\n     * CachedFile instances contrary to {@link #getCachedFiles()}.\r\n     *\r\n     * @return the current folder's children\r\n     * @see #getCachedFiles()\r\n     */\r\n    public synchronized AbstractFile[] getFiles() {\r\n        int nbFiles = cachedFiles.length;\r\n        AbstractFile[] files = new AbstractFile[nbFiles];\r\n        for (int i = 0; i < nbFiles; i++) {\r\n            files[i] = cachedFiles[i] == null ? null : ((CachedFile) cachedFiles[i]).getProxiedFile();\r\n        }\r\n\r\n        return files;\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the current folder's children. The returned array contains {@link CachedFile} instances, where\r\n     * most attributes have already been fetched and cached.\r\n     *\r\n     * @return the current folder's children, as an array of CachedFile instances\r\n     * @see #getFiles()\r\n     */\r\n    private synchronized AbstractFile[] getCachedFiles() {\r\n        // Clone the array to make sure it can't be modified outside of this class\r\n        AbstractFile[] cachedFilesCopy = new AbstractFile[cachedFiles.length];\r\n        System.arraycopy(cachedFiles, 0, cachedFilesCopy, 0, cachedFiles.length);\r\n\r\n        return cachedFilesCopy;\r\n    }\r\n\r\n    /**\r\n     * Sorts rows by the current criterion, ascending/descending order and 'folders first' value.\r\n     */\r\n    public synchronized void sortRows() {\r\n        this.fileComparator = createFileComparator(sortInfo);\r\n        sort(0, fileArrayIndex.length - 1);\r\n        this.fileComparator = null;\r\n    }\r\n\r\n\r\n    //////////////////\r\n    // Sort methods //\r\n    //////////////////\r\n\r\n    private FileComparator createFileComparator(SortInfo sortInfo) {\r\n        QuickSearch qs = quickSearch != null && quickSearch.isActive() && sortInfo.getQuickSearchMatchesFirst() ? quickSearch : null;\r\n        return new FileComparator(sortInfo.getCriterion().getFileComparatorCriterion(), sortInfo.getAscendingOrder(),\r\n                sortInfo.getFoldersFirst(), sortInfo.getFoldersAlwaysAlphabetical(), qs);\r\n    }\r\n\r\n\r\n    /**\r\n     * Quick sort implementation, based on James Gosling's implementation.\r\n     */\r\n    protected void sort(int lo0, int hi0) {\r\n        int lo = lo0;\r\n        int hi = hi0;\r\n\r\n        if (lo >= hi) {\r\n            return;\r\n        } else if (lo == hi - 1) {\r\n            // sort a two element list by swapping if necessary\r\n            int loIndex = fileArrayIndex[lo];\r\n            int hiIndex = fileArrayIndex[hi];\r\n            if (compare(loIndex, hiIndex) > 0) {\r\n                fileArrayIndex[lo] = hiIndex;\r\n                fileArrayIndex[hi] = loIndex;\r\n            }\r\n            return;\r\n        }\r\n\r\n        // Pick a pivot and move it out of the way\r\n        int pivotIndex = fileArrayIndex[(lo + hi) / 2];\r\n        fileArrayIndex[(lo + hi) / 2] = fileArrayIndex[hi];\r\n        fileArrayIndex[hi] = pivotIndex;\r\n\r\n        while (lo < hi) {\r\n            // Search forward from files[lo] until an element is found that\r\n            // is greater than the pivot or lo >= hi\r\n            //while (compare(cachedFiles[fileArrayIndex[lo]], pivot)<=0 && lo < hi) {\r\n            while (compare(fileArrayIndex[lo], pivotIndex) <= 0 && lo < hi) {\r\n                lo++;\r\n            }\r\n\r\n            // Search backward from files[hi] until element is found that\r\n            // is less than the pivot, or lo >= hi\r\n            //while (compare(pivot, cachedFiles[fileArrayIndex[hi]])<=0 && lo < hi ) {\r\n            while (compare(pivotIndex, fileArrayIndex[hi]) <= 0 && lo < hi ) {\r\n                hi--;\r\n            }\r\n\r\n            // Swap elements files[lo] and files[hi]\r\n            if (lo < hi) {\r\n                int temp = fileArrayIndex[lo];\r\n                fileArrayIndex[lo] = fileArrayIndex[hi];\r\n                fileArrayIndex[hi] = temp;\r\n            }\r\n        }\r\n\r\n        // Put the median in the \"center\" of the list\r\n        fileArrayIndex[hi0] = fileArrayIndex[hi];\r\n        fileArrayIndex[hi] = pivotIndex;\r\n\r\n        // Recursive calls, elements files[lo0] to files[lo-1] are less than or\r\n        // equal to pivot, elements files[hi+1] to files[hi0] are greater than pivot.\r\n        sort(lo0, lo-1);\r\n        sort(hi+1, hi0);\r\n    }\r\n\r\n\r\n    private int compare(int index1, int index2) {\r\n        if (index1 == index2) {\r\n            return 0;\r\n        }\r\n        return fileComparator.compare(cachedFiles[index1], cachedFiles[index2]);\r\n    }\r\n\r\n\r\n\r\n    /**\r\n     * Returns the current folder, i.e. the last folder set using {@link #setCurrentFolder(com.mucommander.commons.file.AbstractFile, com.mucommander.commons.file.AbstractFile[], FileTable table)}.\r\n     *\r\n     * @return the current folder\r\n     */\r\n    public synchronized AbstractFile getCurrentFolder() {\r\n        return currentFolder;\r\n    }\r\n\r\n    /**\r\n     * Sets the {@link SortInfo} instance that describes how the associated table is\r\n     * sorted.\r\n     *\r\n     * @param sortInfo SortInfo instance that describes how the associated table is sorted\r\n     */\r\n    public void setSortInfo(SortInfo sortInfo) {\r\n        this.sortInfo = sortInfo;\r\n    }\r\n\r\n    public void setQuickSearch(QuickSearch quickSearch) {\r\n        this.quickSearch = quickSearch;\r\n    }\r\n\r\n\r\n\r\n    /**\r\n     * Sets the current folder and its children.\r\n     *\r\n     * @param folder the current folder\r\n     * @param children the current folder's children\r\n     */\r\n    public synchronized void setCurrentFolder(AbstractFile folder, AbstractFile children[], FileTable table) {\r\n        int nbFiles = children.length;\r\n        this.currentFolder = (folder instanceof CachedFile) ? folder : new CachedFile(folder, true);\r\n\r\n        this.parent = currentFolder.getParent();    // Note: the returned parent is a CachedFile instance\r\n        if (parent != null) {\r\n            // Pre-fetch the attributes that are used by the table renderer and some actions.\r\n            prefetchCachedFileAttributes(parent);\r\n        }\r\n        stopSizeCalculation();\r\n\r\n        // Initialize file indexes and create CachedFile instances to speed up table display and navigation\r\n        this.cachedFiles = children;\r\n        this.fileArrayIndex = new int[nbFiles];\r\n\r\n        // we needn't prefetch local files for performance optimization purposes\r\n        // in the case of local files the lazy initialization will be enough\r\n        boolean needPrefetch = nbFiles > 0 && !(children[0] instanceof LocalFile);\r\n\r\n        for (int i = 0; i < nbFiles; i++) {\r\n            AbstractFile child = children[i];\r\n            AbstractFile file = child instanceof CachedFile ? child : new CachedFile(child, true);\r\n\r\n            // Pre-fetch the attributes that are used by the table renderer and some actions.\r\n            if (needPrefetch) {\r\n                prefetchCachedFileAttributes(file);\r\n            }\r\n\r\n            cachedFiles[i] = file;\r\n            fileArrayIndex[i] = i;\r\n        }\r\n\r\n        // Reset marked files\r\n        //this.rowMarked = new boolean[getRowCount()];\r\n        this.fileMarked = new boolean[getFilesCount()];\r\n        this.markedTotalSize = 0;\r\n        this.nbFilesMarked = 0;\r\n\r\n        // Init and fill cell cache to speed up table even more\r\n        initCellValuesCache();\r\n\r\n        fillCellCache(table);\r\n    }\r\n\r\n    /**\r\n     * Returns the date of the current folder, when it was set using\r\n     * {@link #setCurrentFolder(com.mucommander.commons.file.AbstractFile, com.mucommander.commons.file.AbstractFile[], FileTable table)}.\r\n     * In other words, the returned date is a snapshot of the current folder's date which is never updated.\r\n     *\r\n     * @return Returns the date of the current folder, when it was set using #setCurrentFolder(Abstract, Abstract[])\r\n     */\r\n    public synchronized long getCurrentFolderDateSnapshot() {\r\n        return currentFolderDateSnapshot;\r\n        }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the current folder has a parent.\r\n     *\r\n     * @return <code>true</code> if the current folder has a parent\r\n     */\r\n    public synchronized boolean hasParentFolder() {\r\n        return parent != null;\r\n    }\r\n\r\n    /**\r\n     * Returns the current folder's parent if there is one, <code>null</code> otherwise.\r\n     *\r\n     * @return the current folder's parent if there is one, <code>null</code> otherwise\r\n     */\r\n    public synchronized AbstractFile getParentFolder() {\r\n        return parent;\r\n    }\r\n\r\n    /**\r\n     * Returns the index of the first row that can be marked/unmarked : <code>1</code> if the current folder has a\r\n     * parent folder, <code>0</code> otherwise (parent folder row '..' cannot be marked).\r\n     *\r\n     * @return the index of the first row that can be marked/unmarked\r\n     */\r\n    public int getFirstMarkableIndex() {\r\n        return parent == null ? 0 : 1;\r\n    }\r\n\t\r\n    /**\r\n     * Marks/unmarks the given row range, delimited by the provided start row index and end row index (inclusive).\r\n     * End row may be less, greater or equal to the start row.\r\n     *\r\n     * @param start index of the first file to mark/unmark\r\n     * @param end index of the last file to mark/ummark, startRow may be less or greater than startRow\r\n     * @param marked if true, all the files within the range will be marked, unmarked otherwise\r\n     */\r\n    public void setRangeMarked(int start, int end, boolean marked) {\r\n        if (end >= start) {\r\n            for (int i = start; i <= end; i++) {\r\n                setFileMarked(i, marked);\r\n            }\r\n        } else {\r\n            for (int i = start; i >= end; i--) {\r\n                setFileMarked(i, marked);\r\n            }\r\n        }\r\n    }\r\n\t\t\r\n\r\n    /**\r\n     * Marks/Unmarks the given file.\r\n     *\r\n     * @param file the file to mark/unmark\r\n     * @param marked <code>true</code> to mark the row, <code>false</code> to unmark it.\r\n     */\r\n    public synchronized void setFileMarked(AbstractFile file, boolean marked) {\r\n        int index = getFileIndex(file);\r\n\r\n        if (index >= 0) {\r\n            setFileMarked(index, marked);\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Marks/unmarks the files that match the given {@link FileFilter}.\r\n     *\r\n     * @param filter the FileFilter to match the files against\r\n     * @param marked if true, matching files will be marked, if false, they will be unmarked\r\n     */\r\n    public synchronized void setFilesMarked(FileFilter filter, boolean marked) {\r\n        int nbFiles = getFilesCount();\r\n        for (int i = parent == null ? 0 : 1; i < nbFiles; i++) {\r\n            if (filter.match(getCachedFileAt(i))) {\r\n                setFileMarked(i, marked);\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns a {@link com.mucommander.commons.file.util.FileSet FileSet} with all currently marked files.\r\n     * <p>\r\n     * The returned <code>FileSet</code> is a freshly created instance, so it can be safely modified.\r\n     * However, it won't be kept current : the returned FileSet is just a snapshot\r\n     * which might not reflect the current marked files state after this method has returned and additional\r\n     * files have been marked/unmarked.\r\n     *\r\n     * @return a FileSet containing all the files that are currently marked\r\n     */\r\n    public synchronized FileSet getMarkedFiles() {\r\n        FileSet markedFiles = new FileSet(currentFolder, nbFilesMarked);\r\n        int nbFiles = getFilesCount();\r\n\r\n        if (parent == null) {\r\n            for (int i = 0; i < nbFiles; i++) {\r\n                if (fileMarked[fileArrayIndex[i]]) {\r\n                    markedFiles.add(getFileAt(i));\r\n                }\r\n            }\r\n        } else {\r\n            for (int i = 1, iMinusOne = 0; i < nbFiles; i++) {\r\n                if (fileMarked[fileArrayIndex[iMinusOne]]) {\r\n                    markedFiles.add(getFileAt(i));\r\n                }\r\n                iMinusOne = i;\r\n            }\r\n        }\r\n\r\n        return markedFiles;\r\n    }\r\n\t\r\n\t\r\n    /**\r\n     * Returns a CachedFile instance of the file located at the given row index.\r\n     * This method can return the parent folder file ('..') if a parent exists and rowIndex is 0.\r\n     * \r\n     * <p>Returns <code>null</code> if rowIndex is lower than 0 or is greater than or equals\r\n     * {@link #getFilesCount() getFilesCount()}.\r\n     *\r\n     * @param row a row index, comprised between 0 and #getRowCount()-1\r\n     * @param col a column index, comprised between 0 and #getColumnCount()-1\r\n     * @return a CachedFile instance of the file located at the given row index\r\n     */\r\n    public synchronized AbstractFile getCachedFileAt(int row, int col) {\r\n        int index = getFileIndexAt(row, col);\r\n\r\n        if (parent != null) {\r\n            if (index == 0) {\r\n                return parent;\r\n            }\r\n            index--;\r\n        }\r\n\t\t\r\n        // Need to check that row index is not larger than actual number of rows\r\n        // because if table has just been changed (rows have been removed),\r\n        // JTable may have an old row count value and may try to repaint rows that are out of bounds.\r\n        if (index >= 0 && index < fileArrayIndex.length) {\r\n            return cachedFiles[fileArrayIndex[index]];\r\n        }\r\n        return null;\r\n    }\r\n\r\n    public synchronized AbstractFile getCachedFileAt(int index) {\r\n        if (parent != null) {\r\n            if (index == 0) {\r\n                return parent;\r\n            }\r\n            index--;\r\n        }\r\n\r\n        // Need to check that row index is not larger than actual number of rows\r\n        // because if table has just been changed (rows have been removed),\r\n        // JTable may have an old row count value and may try to repaint rows that are out of bounds.\r\n        if (index >= 0 && index < fileArrayIndex.length) {\r\n            return cachedFiles[fileArrayIndex[index]];\r\n        }\r\n        return null;\r\n    }\r\n\r\n\r\n\r\n    /**\r\n     * Returns the file located at the given row index. \r\n     * This method can return the parent folder file ('..') if a parent exists and rowIndex is 0.\r\n     *\r\n     * <p>Returns <code>null</code> if rowIndex is lower than 0 or is greater than or equals\r\n     * {@link #getFilesCount() getFilesCount()}.\r\n     *\r\n     * @param row a row index, comprised between 0 and #getRowCount()-1\r\n     * @param col a column index, comprised between 0 and #getColumnCount()-1\r\n     * @return the file located at the given row index\r\n     */\r\n    public synchronized AbstractFile getFileAt(int row, int col) {\r\n        AbstractFile file = getCachedFileAt(row, col);\r\n\t\r\n        if (file instanceof CachedFile) {\r\n            return ((CachedFile) file).getProxiedFile();\r\n        }\r\n        return file;\r\n    }\r\n\t\r\n\r\n    /**\r\n     * Returns the index of the row where the given file is located, <code>-1</code> if the file is not in the\r\n     * current folder.\r\n     *\r\n     * @param file the file for which to find the row index\r\n     * @return the index of the file where the given file is located, <code>-1</code> if the file is not in the\r\n     * current folder\r\n     */\r\n    public synchronized int getFileIndex(AbstractFile file) {\r\n        // Handle parent folder file\r\n        if (parent != null && file.equals(parent)) {\r\n            return 0;\r\n        }\r\n\r\n        // Use dichotomic binary search rather than a dumb linear search since file array is sorted, complexity is\r\n        // reduced to O(log n) instead of O(n^2)\r\n        int left = parent == null ? 0 : 1;\r\n        int right = getFilesCount() - 1;\r\n        FileComparator fc = createFileComparator(sortInfo);\r\n\r\n        while (left <= right) {\r\n            int mid = (right-left)/2 + left;\r\n            AbstractFile midFile = getCachedFileAt(mid);\r\n            if (midFile.equals(file)) {\r\n                return mid;\r\n            } if (fc.compare(file, midFile) < 0) {\r\n                right = mid - 1;\r\n            } else {\r\n                left = mid+1;\r\n            }\r\n        }\r\n\t\t\r\n        return -1;\r\n    }\r\n\r\n\t\r\n\r\n\r\n    /**\r\n     * Returns <code>true</code> if the given file is marked (/!\\ not selected). If the specified row corresponds to the\r\n     * special '..' parent file, <code>false</code> is always returned.\r\n     *\r\n     * @param row index of a row to test\r\n     * @param col index of a column to test\r\n     * @return <code>true</code> if the given row is marked\r\n     */\r\n    public synchronized boolean isFileMarked(int row, int col) {\r\n        if (row == 0 && col <= 0 && parent != null) {\r\n            return false;\r\n        }\r\n        final int firstOffset = parent == null ? 0 : 1;\r\n        int total = fileArrayIndex.length + firstOffset;\r\n        //return row < total && rowMarked[fileArrayIndex[row - firstOffset]];\r\n        //row -= firstOffset;\r\n        int index = getFileIndexAt(row, col);\r\n        try {\r\n            return index < total && fileMarked[fileArrayIndex[index - firstOffset]];\r\n        } catch (Exception e) {\r\n            e.printStackTrace();\r\n            return false;\r\n        }\r\n    }\r\n\r\n    public synchronized boolean isFileMarked(int index) {\r\n        if (index == 0 && parent != null) {\r\n            return false;\r\n        }\r\n        final int firstOffset = parent == null ? 0 : 1;\r\n        int total = fileArrayIndex.length + firstOffset;\r\n        try {\r\n            return index < total && fileMarked[fileArrayIndex[index - firstOffset]];\r\n        } catch (Exception e) {\r\n            e.printStackTrace();\r\n            return false;\r\n        }\r\n    }\r\n\r\n\t\r\n    /**\r\n     * Makes the name column temporarily editable. This method should only be called by FileTable.\r\n     *\r\n     * @param editable <code>true</code> to make the name column editable, false to prevent it from being edited\r\n     */\r\n    public void setNameColumnEditable(boolean editable) {\r\n        this.nameColumnEditable = editable;\r\n    }\r\n\r\n    /**\r\n     * Marks/Unmarks the given row. If the specified row corresponds to the special '..' parent file, the row won't\r\n     * be marked.\r\n     *\r\n     * @param index the file index to mark/unmark\r\n     * @param marked <code>true</code> to mark the row, <code>false</code> to unmark it\r\n     */\r\n    public synchronized void setFileMarked(int index, boolean marked) {\r\n        if (index == 0 && parent != null) {\r\n            return;\r\n        }\r\n        // Return if the row is already marked/unmarked\r\n        final int fileIndex = fileArrayIndex[parent != null ? index - 1 : index];\r\n//        if((marked && rowMarked[fileIndex]) || (!marked && !rowMarked[fileIndex]))\r\n//            return;\r\n        if (marked == fileMarked[fileIndex]) {\r\n            return;\r\n        }\r\n\r\n        AbstractFile file = getCachedFileAt(index);\r\n\r\n        // Do not call getSize() on directories, it's unnecessary and the value is most likely not cached by CachedFile yet\r\n        long fileSize;\r\n\r\n        if (file.isDirectory()) {\r\n            markedDirectories.add(file);\r\n            fileSize = 0;\r\n        } else {\r\n            fileSize = file.getSize();\r\n        }\r\n\r\n        // Update :\r\n        // - Combined size of marked files\r\n        // - marked files FileSet\r\n        if (marked) {\r\n            // File size can equal -1 if not available, do not count that in total\r\n            if (fileSize > 0) {\r\n                markedTotalSize += fileSize;\r\n            }\r\n            nbFilesMarked++;\r\n        } else {\r\n            // File size can equal -1 if not available, do not count that in total\r\n            if (fileSize > 0) {\r\n                markedTotalSize -= fileSize;\r\n            }\r\n\r\n            nbFilesMarked--;\r\n        }\r\n\r\n        fileMarked[fileIndex] = marked;\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the number of marked files. This number is pre-calculated so calling this method is much faster than\r\n     * retrieving the list of marked files and counting them.\r\n     *\r\n     * @return the number of marked files\r\n     */\r\n    public int getNbMarkedFiles() {\r\n        return nbFilesMarked;\r\n    }\r\n\r\n\t\r\n    /**\r\n     * Returns the combined size of marked files. This number consists of two parts:\r\n     * 1) pre-calculated size of files so calling this method is much faster\r\n     * than retrieving the list of marked files and calculating their combined size.\r\n     * 2) calculated size of directories (if that was calculated)\r\n     *\r\n     * @return the combined size of marked files and directories\r\n     */\r\n    public long getTotalMarkedSize() {\r\n        return markedTotalSize + calcMarkedDirectoriesSize();\r\n    }\r\n\r\n\r\n    /**\r\n     * Add directory to size calculation and start calculation worker if it doesn't busy\r\n     * @param table file table\r\n     * @param file directory to add\r\n     */\r\n    public void startDirectorySizeCalculation(FileTable table, AbstractFile file) {\r\n        if (!file.isDirectory()) {\r\n            return;\r\n        }\r\n        hasCalculatedDirectories = true;\r\n        synchronized (directorySizes) {\r\n            if (directorySizes.containsKey(file)) {\r\n                return;\r\n            }\r\n        }\r\n        synchronized (calculateSizeQueue) {\r\n            if (calculateSizeQueue.contains(file)) {\r\n                return;\r\n            }\r\n            calculateSizeQueue.add(file);\r\n        }\r\n        if (calculateDirectorySizeWorker == null) {\r\n            processNextQueuedFile(table);\r\n        }\r\n    }\r\n\r\n\r\n\r\n    /**\r\n     * Takes a first ask for queue and starts calculation worker\r\n     * @param table file table\r\n     */\r\n    private void processNextQueuedFile(FileTable table) {\r\n        AbstractFile nextFile;\r\n        synchronized (calculateSizeQueue) {\r\n            nextFile = calculateSizeQueue.isEmpty() ? null : calculateSizeQueue.removeFirst();\r\n            }\r\n        if (nextFile == null) {\r\n            calculateDirectorySizeWorker = null;\r\n            table.getParent().setCursor(Cursor.getDefaultCursor());\r\n        } else {\r\n            calculateDirectorySizeWorker = new CalculateDirectorySizeWorker(this, table, nextFile);\r\n            table.getParent().setCursor(WAIT_CURSOR);\r\n            calculateDirectorySizeWorker.execute();\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Called from size-calculation worker after it finish or requests to repaint table.\r\n     * Updates map of directory sizes and starts next task if worker finished\r\n     *\r\n     * @param path directory to process\r\n     * @param table file table\r\n     * @param size calculated directory size\r\n     * @param finish true if worker completely finish task, false if it will just repaint table\r\n     */\r\n    public void addProcessedDirectory(AbstractFile path, FileTable table, long size, boolean finish) {\r\n        synchronized (directorySizes) {\r\n            directorySizes.put(path, size);\r\n        }\r\n        synchronized (calculateSizeQueue) {\r\n            calculateSizeQueue.remove(path);\r\n        }\r\n        if (finish) {\r\n            processNextQueuedFile(table);\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Stops directory calculation, clears calculated size ant tasks queue, interrupts currently executed worker if exists\r\n     */\r\n    private void stopSizeCalculation() {\r\n        synchronized (directorySizes) {\r\n            directorySizes.clear();\r\n        }\r\n        synchronized (calculateSizeQueue) {\r\n            calculateSizeQueue.clear();\r\n        }\r\n        if (calculateDirectorySizeWorker != null) {\r\n            try {\r\n                calculateDirectorySizeWorker.cancel(true);\r\n            } catch (Exception ignore) { }\r\n            calculateDirectorySizeWorker = null;\r\n        }\r\n        synchronized (this) {\r\n            markedDirectories.clear();\r\n        }\r\n        hasCalculatedDirectories = false;\r\n    }\r\n\r\n\r\n    private long calcMarkedDirectoriesSize() {\r\n        if (!hasCalculatedDirectories) {\r\n            return 0;\r\n        }\r\n        long result = 0;\r\n        synchronized (this) {\r\n            for (AbstractFile file : markedDirectories) {\r\n                Long dirSize;\r\n                synchronized (directorySizes) {\r\n                    dirSize = directorySizes.get(file);\r\n                }\r\n                if (dirSize != null) {\r\n                    result += dirSize;\r\n                }\r\n            }\r\n        }\r\n        return result;\r\n    }\r\n\r\n    public String getFileNameAt(int index) {\r\n        return (index == 0 && hasParentFolder()) ? \"..\" : getFileAt(index).getName();\r\n    }\r\n\r\n    public synchronized int getFilesCount() {\r\n        return fileArrayIndex.length + (parent == null ? 0 : 1);\r\n    }\r\n\r\n//    public void setFirstVisibleRow(int row) {\r\n//        this.firstVisibleRow = row;\r\n//    }\r\n\r\n\r\n    public AbstractFile getCurrentCalculatedSizeDirectory() {\r\n        if (calculateDirectorySizeWorker != null) {\r\n            return calculateDirectorySizeWorker.getFile();\r\n        }\r\n        return null;\r\n    }\r\n\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/table/views/TableViewMode.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.main.table.views;\n\nimport com.mucommander.ui.main.table.Column;\nimport com.mucommander.ui.main.table.FileTable;\nimport com.mucommander.ui.main.table.views.compact.CompactFileTableCellRenderer;\nimport com.mucommander.ui.main.table.views.full.FileTableCellRenderer;\n\n/**\n * @author Oleg Trifonov\n * Created on 03/04/15.\n */\npublic enum TableViewMode {\n\n    FULL(Column.values().length) {\n        @Override\n        public BaseCellRenderer createCellRenderer(FileTable table) {\n            return new FileTableCellRenderer(table);\n        }\n    },\n    COMPACT(2) {\n        @Override\n        public BaseCellRenderer createCellRenderer(FileTable table) {\n            return new CompactFileTableCellRenderer(table);\n        }\n    },\n    SHORT(3) {\n        @Override\n        public BaseCellRenderer createCellRenderer(FileTable table) {\n            return new CompactFileTableCellRenderer(table);\n        }\n    };\n\n\n    private final int columns;\n\n    TableViewMode(int columns) {\n        this.columns = columns;\n    }\n\n    public int getColumnsCount() {\n        return columns;\n    }\n\n    public abstract BaseCellRenderer createCellRenderer(FileTable table);\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/table/views/compact/CompactFileTableCellRenderer.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.main.table.views.compact;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.icon.CustomFileIconProvider;\nimport com.mucommander.ui.icon.FileIcons;\nimport com.mucommander.ui.icon.IconManager;\nimport com.mucommander.ui.main.table.CellLabel;\nimport com.mucommander.ui.main.table.FileGroupResolver;\nimport com.mucommander.ui.main.table.FileTable;\nimport com.mucommander.ui.main.table.views.BaseCellRenderer;\nimport com.mucommander.ui.quicksearch.QuickSearch;\nimport com.mucommander.ui.theme.ThemeCache;\nimport com.mucommander.utils.FileIconsCache;\n\nimport javax.swing.JTable;\nimport javax.swing.table.TableColumn;\nimport java.awt.Color;\nimport java.awt.Component;\n\n/**\n * @author Oleg Trifonov\n * Created on 04/04/15.\n */\npublic class CompactFileTableCellRenderer extends BaseCellRenderer {\n\n    private final CellLabel emptyLabel = new CellLabel();\n\n    public CompactFileTableCellRenderer(FileTable table) {\n        super(table);\n\n        this.cellLabels = new CellLabel[table.getColumnCount()];\n        for (int i = 0; i < cellLabels.length; i++) {\n            this.cellLabels[i] = new CellLabel();\n        }\n    }\n\n\n    @Override\n    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {\n        // Need to check that row index is not out of bounds because when the folder\n        // has just been changed, the JTable may try to repaint the old folder and\n        // ask for a row index greater than the length if the old folder contained more files\n        if (row < 0 || row >= tableModel.getRowCount()) {\n            return null;\n        }\n        CompactFileTableModel model = (CompactFileTableModel)tableModel;\n        // Sanity check.\n        final AbstractFile file = model.getFileAt(row, column);\n        if (file == null) {\n            debug(\"tableModel.getCachedFileAtRow( \" + row + \") RETURNED NULL !\");\n//            emptyLabel.setupText(\"\", 0);\n//            emptyLabel.setIcon(null);\n//            final QuickSearch search = this.table.getQuickSearch();\n//            int matchesColorIndex;\n//            if (table.hasFocus() && search.isActive()) {\n//                matchesColorIndex = ThemeCache.NORMAL;\n//            } else {\n//                matchesColorIndex = (row % 2 == 0) ? ThemeCache.NORMAL : ThemeCache.ALTERNATE;\n//            }\n            final int focusedIndex = table.hasFocus() ? ThemeCache.ACTIVE : ThemeCache.INACTIVE;\n//            emptyLabel.setBackground(ThemeCache.backgroundColors[focusedIndex][matchesColorIndex]);\n            emptyLabel.setBackground(ThemeCache.backgroundColors[focusedIndex][ThemeCache.NORMAL]);\n            emptyLabel.setHasSeparator(column < tableModel.getColumnCount()-1);\n            return emptyLabel;\n        }\n\n        final QuickSearch search = this.table.getQuickSearch();\n        final boolean matches = !table.hasFocus() || !search.isActive() || (file != tableModel.getParentFolder() && search.matches(file.getName()));\n\n        // Retrieves the various indexes of the colors to apply.\n        // Selection only applies when the table is the active one\n        final int selectedIndex = (isSelected && ((FileTable)table).isActiveTable()) ? ThemeCache.SELECTED : ThemeCache.NORMAL;\n        final int focusedIndex = table.hasFocus() ? ThemeCache.ACTIVE : ThemeCache.INACTIVE;\n        final int fileIndex = model.getFileIndexAt(row, column);\n        final int colorIndex = getFileColorIndex(fileIndex, file, tableModel);\n\n        final CellLabel label = cellLabels[column];\n\n        label.setIcon(fileIndex == 0 && tableModel.hasParentFolder()\n                ? IconManager.getIcon(IconManager.IconSet.FILE, CustomFileIconProvider.PARENT_FOLDER_ICON_NAME, FileIcons.getScaleFactor())\n                : FileIconsCache.getInstance().getIcon(file));\n\n\n        String text = (String)value;\n\n        final TableColumn tableColumn = table.getColumnModel().getColumn(column);\n        label.setupText(text, tableColumn.getWidth());\n\n        // Set foreground color\n        Color foregroundColor;\n        if (matches || isSelected) {\n            int group = (selectedIndex == ThemeCache.SELECTED) ? -1 : FileGroupResolver.getInstance().resolve(file);\n            if (group >= 0 && colorIndex != ThemeCache.MARKED) {\n                foregroundColor = ThemeCache.groupColors[group];\n            } else {\n                foregroundColor = ThemeCache.foregroundColors[focusedIndex][selectedIndex][colorIndex];\n            }\n        } else {\n            foregroundColor = ThemeCache.unmatchedForeground;\n        }\n        label.setForeground(foregroundColor);\n\n        // Set background color depending on whether the row is selected or not, and whether the table has focus or not\n        if (selectedIndex == ThemeCache.SELECTED) {\n            label.setBackground(ThemeCache.backgroundColors[focusedIndex][ThemeCache.SELECTED], ThemeCache.backgroundColors[focusedIndex][ThemeCache.SECONDARY]);\n        } else if (matches) {\n            int matchesColorIndex;\n            if (table.hasFocus() && search.isActive()) {\n                matchesColorIndex = ThemeCache.NORMAL;\n            } else {\n                matchesColorIndex = (row % 2 == 0) ? ThemeCache.NORMAL : ThemeCache.ALTERNATE;\n            }\n            label.setBackground(ThemeCache.backgroundColors[focusedIndex][matchesColorIndex]);\n        } else {\n            label.setBackground(ThemeCache.unmatchedBackground);\n        }\n\n        if (selectedIndex == ThemeCache.SELECTED) {\n            label.setOutline(table.hasFocus() ? ThemeCache.activeOutlineColor : ThemeCache.inactiveOutlineColor);\n        } else {\n            label.setOutline(null);\n        }\n        label.setHasSeparator(column < tableModel.getColumnCount()-1);\n\n        return label;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/table/views/compact/CompactFileTableColumnModel.java",
    "content": "/*\r\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\r\n * Copyright (C) 2014-2016 Oleg Trifonov\r\n *\r\n * trolCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * trolCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\npackage com.mucommander.ui.main.table.views.compact;\r\n\r\nimport com.mucommander.ui.main.table.FileTable;\r\nimport com.mucommander.ui.main.table.FileTableHeaderRenderer;\r\nimport com.mucommander.ui.main.table.views.full.FileTableConfiguration;\r\n\r\nimport javax.swing.DefaultListSelectionModel;\r\nimport javax.swing.ListSelectionModel;\r\nimport javax.swing.event.TableColumnModelListener;\r\nimport javax.swing.table.TableColumn;\r\nimport javax.swing.table.TableColumnModel;\r\nimport java.beans.PropertyChangeEvent;\r\nimport java.beans.PropertyChangeListener;\r\nimport java.util.Enumeration;\r\nimport java.util.NoSuchElementException;\r\nimport java.util.WeakHashMap;\r\n\r\n/**\r\n * @author Oleg Trifonov\r\n * Created on 04/04/15.\r\n */\r\npublic class CompactFileTableColumnModel implements TableColumnModel, PropertyChangeListener {\r\n    /** Even though we're not using column selection, the table API forces us to return this instance or will crash. */\r\n    private static final ListSelectionModel SELECTION_MODEL = new DefaultListSelectionModel();\r\n\r\n    /** If {@link #widthCache} is set to this, it needs to be recalculated. */\r\n    private static final int CACHE_OUT_OF_DATE = -1;\r\n\r\n\r\n    private final TableColumn[] columns;\r\n\r\n    /** Cache for the table's total width. */\r\n    private int widthCache = CACHE_OUT_OF_DATE;\r\n\r\n    /** All registered listeners. */\r\n    private final WeakHashMap<TableColumnModelListener, ?> listeners  = new WeakHashMap<>();\r\n\r\n    public CompactFileTableColumnModel(int columns, FileTableConfiguration conf) {\r\n        super();\r\n        this.columns = new TableColumn[columns];\r\n        for (int i = 0; i < columns; i++) {\r\n            TableColumn column = new TableColumn();\r\n\r\n            column.setCellEditor(null);\r\n            column.setHeaderValue(\"Name\");\r\n            column.addPropertyChangeListener(this);\r\n            column.setMinWidth(200);\r\n            column.setModelIndex(i);\r\n\r\n            this.columns[i] = column;\r\n            // Mac OS X 10.5 (Leopard) and up uses JTableHeader properties to render sort indicators on table headers.\r\n            // On other platforms, we use a custom table header renderer.\r\n            if (!FileTable.usesTableHeaderRenderingProperties()) {\r\n                column.setHeaderRenderer(new FileTableHeaderRenderer());\r\n            }\r\n            column.addPropertyChangeListener(this);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void addColumn(TableColumn aColumn) {\r\n\r\n    }\r\n\r\n    @Override\r\n    public void removeColumn(TableColumn column) {\r\n\r\n    }\r\n\r\n    @Override\r\n    public void moveColumn(int columnIndex, int newIndex) {\r\n\r\n    }\r\n\r\n    /**\r\n     * Ignored.\r\n     */\r\n    @Override\r\n    public void setColumnMargin(int newMargin) {\r\n\r\n    }\r\n\r\n    @Override\r\n    public int getColumnCount() {\r\n        return columns.length;\r\n    }\r\n\r\n    /**\r\n     * Returns an enumeration on all visible columns.\r\n     * @return an enumeration on all visible columns.\r\n     */\r\n    @Override\r\n    public Enumeration<TableColumn> getColumns() {\r\n        return new ColumnEnumeration();\r\n    }\r\n\r\n    @Override\r\n    public int getColumnIndex(Object columnIdentifier) {\r\n        return 0;\r\n    }\r\n\r\n    @Override\r\n    public TableColumn getColumn(int columnIndex) {\r\n        return columns[columnIndex];\r\n    }\r\n\r\n    /**\r\n     * Returns 0.\r\n     * @return 0.\r\n     */\r\n    @Override\r\n    public int getColumnMargin() {\r\n        return 0;\r\n    }\r\n\r\n    /**\r\n     * Returns the index of the column at the specified position.\r\n     * @param  x position of the column to look for.\r\n     * @return  the index of the column at the specified position, <code>-1</code> if not found.\r\n     */\r\n    @Override\r\n    public int getColumnIndexAtX(int x) {\r\n        int count = getColumnCount();\r\n        for (int i = 0; i < count; i++) {\r\n            x = x - getColumn(i).getWidth();\r\n            if (x < 0) {\r\n                return i;\r\n            }\r\n        }\r\n        return -1;\r\n    }\r\n\r\n    @Override\r\n    public int getTotalColumnWidth() {\r\n        if (widthCache == CACHE_OUT_OF_DATE) {\r\n            computeWidthCache();\r\n        }\r\n        return widthCache;\r\n    }\r\n\r\n    /**\r\n     * Computes the model's width.\r\n     */\r\n    private void computeWidthCache() {\r\n        Enumeration<TableColumn> elements = getColumns();\r\n        widthCache = 0;\r\n        while (elements.hasMoreElements()) {\r\n            widthCache += elements.nextElement().getWidth();\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Ignored.\r\n     */\r\n    @Override\r\n    public void setColumnSelectionAllowed(boolean flag) {\r\n\r\n    }\r\n\r\n    @Override\r\n    public boolean getColumnSelectionAllowed() {\r\n        return true;\r\n    }\r\n\r\n    /**\r\n     * Returns an integer array of size 0.\r\n     * @return an integer array of size 0.\r\n     */\r\n    @Override\r\n    public int[] getSelectedColumns() {\r\n        return new int[]{0};\r\n    }\r\n\r\n    /**\r\n     * Returns <code>0</code>.\r\n     * @return <code>0</code>.\r\n     */\r\n    @Override\r\n    public int getSelectedColumnCount() {\r\n        return 1;\r\n    }\r\n\r\n    /**\r\n     * Ignored.\r\n     */\r\n    @Override\r\n    public void setSelectionModel(ListSelectionModel newModel) {\r\n    }\r\n\r\n    /**\r\n     * Returns a default list selection model.\r\n     * <p>\r\n     * Ideally, we'd like to return <code>null</code> here, but the table API takes a dim view\r\n     * of this and we're forced to keep a useless reference.\r\n     *\r\n     * @return a default list selection model.\r\n     */\r\n    @Override\r\n    public ListSelectionModel getSelectionModel() {\r\n        return SELECTION_MODEL;\r\n    }\r\n\r\n    @Override\r\n    public void addColumnModelListener(TableColumnModelListener listener) {\r\n        listeners.put(listener, null);\r\n    }\r\n\r\n    @Override\r\n    public void removeColumnModelListener(TableColumnModelListener listener) {\r\n        listeners.remove(listener);\r\n    }\r\n\r\n    @Override\r\n    public void propertyChange(PropertyChangeEvent evt) {\r\n\r\n    }\r\n\r\n\r\n    /**\r\n     * Browses through the model's visible columns\r\n\r\n     * @author Oleg Trifonov\r\n     */\r\n    private class ColumnEnumeration implements Enumeration<TableColumn> {\r\n        /** Index of the next available element in the enumeration. */\r\n        private int nextIndex;\r\n\r\n        /**\r\n         * Creates a new column enumeration.\r\n         */\r\n        ColumnEnumeration() {\r\n            nextIndex = 0;\r\n        }\r\n\r\n        /**\r\n         * Returns <code>true</code> if there's a next element in the enumeration.\r\n         * @return <code>true</code> if there's a next element in the enumeration, <code>false</code> otherwise.\r\n         */\r\n        public boolean hasMoreElements() {\r\n            return nextIndex < columns.length;\r\n        }\r\n\r\n        /**\r\n         * Returns the next element in the enumeration.\r\n         * @return the next element in the enumeration.\r\n         * @throws NoSuchElementException if there is no next element in the enumeration.\r\n         */\r\n        public TableColumn nextElement() {\r\n            // Makes sure we have at least one more element to return.\r\n            if (!hasMoreElements()) {\r\n                throw new NoSuchElementException();\r\n            }\r\n\r\n            return columns[nextIndex++];\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/table/views/compact/CompactFileTableModel.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.main.table.views.compact;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.main.table.FileTable;\nimport com.mucommander.ui.main.table.views.BaseFileTableModel;\n\n\n/**\n * @author Oleg Trifonov\n * Created on 04/04/15.\n */\npublic class CompactFileTableModel extends BaseFileTableModel {\n\n    final int columns;\n\n    private int visibleRows;\n\n//    private int rowCount;\n\n    private int offset;\n\n    /** Cell values cache */\n    private String[] cellValuesCache;\n\n    public CompactFileTableModel(int columns, int visibleRows) {\n        super();\n        this.columns = columns;\n        this.visibleRows = visibleRows;\n        //rowCount = calcRowCount();\n    }\n\n\n    @Override\n    public synchronized void setupFromModel(BaseFileTableModel model) {\n        super.setupFromModel(model);\n        initCellValuesCache();\n    }\n\n    private int calcRowCount() {\n        int files = getFileCount();\n        if (parent != null ) {\n            files++;\n        }\n        if (files <= visibleRows) {\n            return files;\n        }\n        int rows = files / columns;\n        if (files % columns != 0) {\n            rows++;\n        }\n        return Math.max(visibleRows, rows);\n    }\n\n\n    public synchronized void setCurrentFolder(AbstractFile folder, AbstractFile children[], FileTable table) {\n        super.setCurrentFolder(folder, children, table);\n        //rowCount = calcRowCount();\n    }\n\n    @Override\n    public void fillCellCache(FileTable fileTable) {\n        int len = cellValuesCache.length;\n        if (len == 0) {\n            return;\n        }\n        // Special '..' file\n        if (parent != null) {\n//            cellValuesCache[0] = \"..\";\n            currentFolderDateSnapshot = currentFolder.getLastModifiedDate();\n        }\n\n        for (int i = 0; i < len; i++) {\n            cellValuesCache[i] = null;\n        }\n//        int fileIndex = 0;\n//        final int indexOffset = parent == null ? 0 : 1;\n//        for (int i = indexOffset; i < len; i++) {\n//            int cellIndex = fileIndex + indexOffset;\n//            cellValuesCache[cellIndex] = null;\n//            fileIndex++;\n//        }\n    }\n\n    /**\n     * Init and fill cell cache to speed up table even more\n     */\n    @Override\n    protected void initCellValuesCache() {\n        //int files = parent == null ? getFileCount() : getFileCount() + 1;\n        this.cellValuesCache = new String[getFileCount()];\n    }\n\n    @Override\n    public int getRowCount() {\n        return visibleRows;\n    }\n\n    @Override\n    public int getColumnCount() {\n        return columns;\n    }\n\n    @Override\n    public Object getValueAt(int row, int column) {\n        int fileIndex = getFileIndexAt(row, column);\n        if (parent != null) {\n            // Handle special '..' file\n            if (fileIndex == 0) {\n                return \"..\";\n            }\n            fileIndex--;\n        }\n        // Need to check that file index is not larger than actual number of files\n        if (fileIndex < 0 || fileIndex >= fileArrayIndex.length) {\n            return null;\n        }\n        int index = fileArrayIndex[fileIndex];\n        String result = cellValuesCache[index];\n        if (result == null) {\n            result = fillOneCellCache(parent != null ? fileIndex + 1 : fileIndex);\n            cellValuesCache[index] = result;\n        }\n        // TODO preload icons for all visible files\n        return result;\n        //return fileIndex + \":\" + offset + \":\" + result;\n    }\n\n\n    /**\n     * Returns <code>true</code> if name column has temporarily be made editable by FileTable\n     * and given row doesn't correspond to parent file '..', <code>false</code> otherwise.\n     */\n    @Override\n    public boolean isCellEditable(int row, int column) {\n        // Name column can temporarily be made editable by FileTable\n        // but parent file '..' name should never be editable\n        return nameColumnEditable && (parent == null || row != 0 || column != 0 || offset != 0);\n    }\n\n    private String fillOneCellCache(int fileIndex) {\n        AbstractFile file = getCachedFileAt(fileIndex);\n        return file.getName();\n    }\n\n    public AbstractFile getFileAt(int row, int column) {\n        int index = offset + row + column * visibleRows;\n        return (index == 0 && hasParentFolder()) ? parent : getFileAt(index);\n    }\n\n    public int getFileIndexAt(int row, int column) {\n        if (row < 0 || column < 0) {\n            return -1;\n        }\n        return offset + row + column * visibleRows;\n    }\n\n    @Override\n    public int getFileRow(int index) {\n        return (index - offset) % visibleRows;\n    }\n\n\n    public String getFileNameAt(int row, int column) {\n        int index = offset + row + column * visibleRows;\n        return (index == 0 && hasParentFolder()) ? \"..\" : getFileAt(index).getName();\n    }\n\n\n    public int getVisibleRows() {\n        return visibleRows;\n    }\n\n    public void setVisibleRows(int visibleRows) {\n        this.visibleRows = visibleRows;\n        //rowCount = calcRowCount();\n        fireTableDataChanged();\n    }\n\n    public int getOffset() {\n        return offset;\n    }\n\n    public void setOffset(int offset) {\n        this.offset = offset;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/table/views/full/FileTableCellRenderer.java",
    "content": "/*\r\n\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.table.views.full;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.ui.icon.CustomFileIconProvider;\r\nimport com.mucommander.ui.icon.FileIcons;\r\nimport com.mucommander.ui.icon.IconManager;\r\nimport com.mucommander.ui.main.table.*;\r\nimport com.mucommander.ui.main.table.views.BaseCellRenderer;\r\nimport com.mucommander.ui.quicksearch.QuickSearch;\r\nimport com.mucommander.ui.theme.*;\r\nimport com.mucommander.utils.FileIconsCache;\r\nimport ru.trolsoft.macosx.FileLabelCache;\r\n\r\nimport javax.swing.*;\r\nimport javax.swing.table.TableColumn;\r\nimport java.awt.Color;\r\nimport java.awt.Component;\r\n\r\n\r\n/**\r\n * The custom <code>TableCellRenderer</code> class used by {@link FileTable} to render all table cells.\r\n *\r\n * <p>Quote from Sun's Javadoc : The table class defines a single cell renderer and uses it as a \r\n * as a rubber-stamp for rendering all cells in the table;  it renders the first cell,\r\n * changes the contents of that cell renderer, shifts the origin to the new location, re-draws it, and so on.\r\n *\r\n * <p>This <code>TableCellRender</code> is written from scratch instead of overriding <code>DefaultTableCellRender</code>\r\n * to provide a more efficient (and more specialized) implementation: each column is rendered using a dedicated \r\n * {@link com.mucommander.ui.main.table.CellLabel CellLabel} which takes into account the column's specificities.\r\n * Having a dedicated for each column avoids calling the label's <code>set</code> methods (alignment, border, font...) \r\n * each time {@link #getTableCellRendererComponent(javax.swing.JTable, Object, boolean, boolean, int, int)}}\r\n * is invoked, making cell rendering faster.\r\n *\r\n * <p>Contrarily to <code>DefaultTableCellRender</code>, <code>FileTableCellRenderer</code> does not extend JLabel,\r\n * instead the dedicated {@link CellLabel} class is used to render cells, making the implementation\r\n * less confusing IMO.\r\n *\r\n * @author Maxence Bernard, Nicolas Rinaudo\r\n */\r\npublic class FileTableCellRenderer extends BaseCellRenderer {\r\n\r\n    private static int progressIndicatorCounter;\r\n    private final CellLabel transparentLabel = new TransparentCellLabel();\r\n\r\n    public FileTableCellRenderer(FileTable table) {\r\n        super(table);\r\n\r\n        this.cellLabels = new CellLabel[Column.values().length];\r\n\r\n        // create a label for each column\r\n        for (Column c : Column.values()) {\r\n            this.cellLabels[c.ordinal()] = new CellLabel();\r\n        }\r\n\r\n        // Set labels' font.\r\n        setCellLabelsFont(ThemeCache.tableFont);\r\n\r\n        // Set labels' text alignment\r\n        cellLabels[Column.EXTENSION.ordinal()].setHorizontalAlignment(CellLabel.CENTER);\r\n        cellLabels[Column.NAME.ordinal()].setHorizontalAlignment(CellLabel.LEFT);\r\n        cellLabels[Column.SIZE.ordinal()].setHorizontalAlignment(CellLabel.RIGHT);\r\n        cellLabels[Column.DATE.ordinal()].setHorizontalAlignment(CellLabel.RIGHT);\r\n        cellLabels[Column.PERMISSIONS.ordinal()].setHorizontalAlignment(CellLabel.LEFT);\r\n        cellLabels[Column.OWNER.ordinal()].setHorizontalAlignment(CellLabel.LEFT);\r\n        cellLabels[Column.GROUP.ordinal()].setHorizontalAlignment(CellLabel.LEFT);\r\n\r\n        // Listens to certain configuration variables\r\n        ThemeCache.addThemeListener(this);\r\n    }\r\n\r\n\r\n\r\n    ///////////////////////////////\r\n    // TableCellRenderer methods //\r\n    ///////////////////////////////\r\n\r\n    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {\r\n        // Need to check that row index is not out of bounds because when the folder\r\n        // has just been changed, the JTable may try to repaint the old folder and\r\n        // ask for a row index greater than the length if the old folder contained more files\r\n        if (row < 0 || row >= tableModel.getRowCount()) {\r\n            return null;\r\n        }\r\n\r\n        // Sanity check.\r\n        final AbstractFile file = tableModel.getCachedFileAt(row, col);\r\n        if (file == null) {\r\n            debug(\"tableModel.getCachedFileAtRow( \" + row + \") RETURNED NULL !\");\r\n            return null;\r\n        }\r\n        boolean isCalculatedSizeDir = file.isDirectory() && tableModel.getCurrentCalculatedSizeDirectory() == file;\r\n\r\n        final QuickSearch search = this.table.getQuickSearch();\r\n\r\n        //final boolean matches = !table.hasFocus() || !search.isActive() || search.matches(this.tableModel.getFileNameAt(row));\r\n        final boolean searchMatches = search.isActive() && search.matches(file);\r\n        //final boolean matches = !table.hasFocus() || !search.isActive() || searchMatches;\r\n        final boolean matches = !search.isActive() || searchMatches;\r\n\r\n        // Retrieves the various indexes of the colors to apply.\r\n        // Selection only applies when the table is the active one\r\n        final int selectedIndex =  (isSelected && ((FileTable)table).isActiveTable()) ? ThemeCache.SELECTED : ThemeCache.NORMAL;\r\n        final int focusedIndex = table.hasFocus() ? ThemeCache.ACTIVE : ThemeCache.INACTIVE;\r\n        final int colorIndex = getFileColorIndex(row, file, tableModel);\r\n\r\n        final Column column = Column.valueOf(table.convertColumnIndexToModel(col));\r\n        CellLabel label = cellLabels[column.ordinal()];\r\n\r\n        if (isCalculatedSizeDir && column == Column.NAME) {\r\n            label.setProgressValue(progressIndicatorCounter++);\r\n        } else {\r\n            label.setProgressValue(0);\r\n        }\r\n\r\n        // Extension/icon column: return ImageIcon instance\r\n        if (column == Column.EXTENSION) {\r\n\r\n            if (search.isActive() && !searchMatches && row != 0) {\r\n                label = transparentLabel;\r\n            }\r\n            label.setIcon(row == 0 && tableModel.hasParentFolder()\r\n                    ? IconManager.getIcon(IconManager.IconSet.FILE, CustomFileIconProvider.PARENT_FOLDER_ICON_NAME, FileIcons.getScaleFactor())\r\n                    // : FileIcons.getFileIcon(file));\r\n                    : FileIconsCache.getInstance().getIcon(file));\r\n        } else {    // Any other column (name, date or size)\r\n            String text = (String)value;\r\n            Color foregroundColor;\r\n            if (matches || isSelected) {\r\n                int group = (selectedIndex == ThemeCache.SELECTED) ? -1 : FileGroupResolver.getInstance().resolve(file);\r\n                if (group >= 0 && colorIndex != ThemeCache.MARKED) {\r\n                    foregroundColor = ThemeCache.groupColors[group];\r\n                } else {\r\n                    foregroundColor = ThemeCache.foregroundColors[focusedIndex][selectedIndex][colorIndex];\r\n                }\r\n            } else {\r\n                foregroundColor = ThemeCache.unmatchedForeground;\r\n            }\r\n            label.setForeground(foregroundColor);\r\n\r\n            // Set the label's text, before calculating it width\r\n\r\n//            label.setText(text);\r\n            final TableColumn tableColumn = table.getColumnModel().getColumn(col);\r\n            label.setupText(text, tableColumn.getWidth());\r\n\r\n            // If label's width is larger than the column width:\r\n            // - truncate the text from the center and equally to the left and right sides, adding an ellipsis ('...')\r\n            // where characters have been removed. This allows both the start and end of filename to be visible.\r\n            // - set a tooltip text that will display the whole text when mouse is over the label\r\n/*\r\n            final TableColumn tableColumn = table.getColumnModel().getColumn(columnIndex);\r\n            if (tableColumn.getWidth() < label.getPreferredSize().getWidth()) {\r\n                final int tl = text.length();\r\n                final int tl2 = tl/2;\r\n                String leftText = text.substring(0, tl2);\r\n                String rightText = text.substring(tl2, tl);\r\n\r\n                while (tableColumn.getWidth() < label.getPreferredSize().getWidth() && !leftText.isEmpty() && !rightText.isEmpty()) {    // Prevents against going out of bounds\r\n                    final int ltl = leftText.length();\r\n                    final int rtl = rightText.length();\r\n                    if (ltl > rtl) {\r\n                        leftText = leftText.substring(0, ltl - 1);\r\n                    } else {\r\n                        rightText = rightText.substring(1, rtl);\r\n                    }\r\n\r\n                    label.setText(leftText + DOTS + rightText);\r\n                }\r\n\r\n                // Set the tool tip\r\n                label.setToolTipText(text);\r\n            } else {    // Have to set it to null otherwise the defaultRender sets the tooltip text to the last one specified\r\n                label.setToolTipText(null);\r\n            }\r\n*/\r\n        }\r\n\r\n        // Set background color depending on whether the row is selected or not, and whether the table has focus or not\r\n        if (selectedIndex == ThemeCache.SELECTED) {\r\n            label.setBackground(ThemeCache.backgroundColors[focusedIndex][ThemeCache.SELECTED], ThemeCache.backgroundColors[focusedIndex][ThemeCache.SECONDARY]);\r\n        } else if (matches) {\r\n            int matchesColorIndex;\r\n            if (table.hasFocus() && search.isActive()) {\r\n                matchesColorIndex = ThemeCache.NORMAL;\r\n            } else {\r\n                matchesColorIndex = (row % 2 == 0) ? ThemeCache.NORMAL : ThemeCache.ALTERNATE;\r\n            }\r\n            label.setBackground(ThemeCache.backgroundColors[focusedIndex][matchesColorIndex]);\r\n        } else {\r\n            label.setBackground(ThemeCache.unmatchedBackground);\r\n        }\r\n\r\n        if (selectedIndex == ThemeCache.SELECTED) {\r\n            label.setOutline(table.hasFocus() ? ThemeCache.activeOutlineColor : ThemeCache.inactiveOutlineColor);\r\n        } else {\r\n            label.setOutline(null);\r\n        }\r\n\r\n        if (column == Column.NAME) {\r\n            label.setMarkerColor(FileLabelCache.getInstance().getLabelColor(file));\r\n        } else {\r\n            label.setMarkerColor(null);\r\n        }\r\n\r\n        return label;\r\n    }\r\n\r\n\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/table/views/full/FileTableColumnModel.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.table.views.full;\r\n\r\nimport com.mucommander.ui.main.table.Column;\r\nimport com.mucommander.ui.main.table.FileTable;\r\nimport com.mucommander.ui.main.table.FileTableHeaderRenderer;\r\n\r\nimport javax.swing.*;\r\nimport javax.swing.event.ChangeEvent;\r\nimport javax.swing.event.TableColumnModelEvent;\r\nimport javax.swing.event.TableColumnModelListener;\r\nimport javax.swing.table.TableColumn;\r\nimport javax.swing.table.TableColumnModel;\r\nimport java.beans.PropertyChangeEvent;\r\nimport java.beans.PropertyChangeListener;\r\nimport java.util.*;\r\n\r\n/**\r\n * Used to keep track of a file table's columns position and visibility settings.\r\n * @author Nicolas Rinaudo, Maxence Bernard\r\n */\r\npublic class FileTableColumnModel implements TableColumnModel, PropertyChangeListener {\r\n    /** If {@link #widthCache} is set to this, it needs to be recalculated. */\r\n    private static final int                CACHE_OUT_OF_DATE = -1;\r\n    /** Even though we're not using column selection, the table API forces us to return this instance or will crash. */\r\n    private static final ListSelectionModel SELECTION_MODEL   = new DefaultListSelectionModel();\r\n\r\n\r\n    /** All registered listeners. */\r\n    private final WeakHashMap<TableColumnModelListener, ?> listeners  = new WeakHashMap<>();\r\n    /** Cache for the table's total width. */\r\n    private int                                      widthCache = CACHE_OUT_OF_DATE;\r\n    /** All available columns. */\r\n    private final List<TableColumn> columns = new ArrayList<>(Column.values().length);\r\n    /** Enabled state of each column. */\r\n    private final boolean[]     enabled = new boolean[Column.values().length];\r\n    /** Visibility state of each column. */\r\n    private final boolean[]     visibility = new boolean[Column.values().length];\r\n    /** Cache for the number of available columns. */\r\n    private int           countCache;\r\n    /** Whether the column sizes were set already. */\r\n    private boolean       columnSizesSet;\r\n    private int[] internalIndexCache;\r\n\r\n\r\n\r\n    /**\r\n     * Creates a new file table column model.\r\n     */\r\n    public FileTableColumnModel(FileTableConfiguration conf) {\r\n        // The name column is always visible, so we know that the column count is always\r\n        // at least 1.\r\n        countCache = 1;\r\n\r\n        // Initializes the columns.\r\n        for (Column c : Column.values()) {\r\n            int columnIndex = c.ordinal();\r\n\r\n            TableColumn column = new TableColumn(columnIndex);         // Buffer for the current column.\r\n            columns.add(column);\r\n            column.setCellEditor(null);\r\n            column.setHeaderValue(c.getLabel());\r\n\r\n            // Mac OS X 10.5 (Leopard) and up uses JTableHeader properties to render sort indicators on table headers.\r\n            // On other platforms, we use a custom table header renderer.\r\n            if (!FileTable.usesTableHeaderRenderingProperties()) {\r\n                column.setHeaderRenderer(new FileTableHeaderRenderer());\r\n            }\r\n\r\n            column.addPropertyChangeListener(this);\r\n\r\n            // Sets the column's initial width.\r\n            if (conf.getWidth(c) != 0) {\r\n                column.setWidth(conf.getWidth(c));\r\n            }\r\n\r\n            // Initializes the column's visibility and minimum width.\r\n            if (c == Column.NAME) {\r\n                enabled[columnIndex] = true;\r\n            } else {\r\n                if ((enabled[columnIndex] = conf.isEnabled(c))) {\r\n                    countCache++;\r\n                }\r\n            }\r\n            column.setMinWidth(c.getMinimumColumnWidth());\r\n\r\n            // Init visibility state to enabled state, FileTable will adjust the values for conditional columns later\r\n            visibility[columnIndex] = enabled[columnIndex];\r\n        }\r\n\r\n        // Sorts the columns.\r\n        columns.sort(new ColumnSorter(conf));\r\n    }\r\n\r\n\r\n\r\n    public synchronized FileTableConfiguration getConfiguration() {\r\n        FileTableConfiguration conf = new FileTableConfiguration();\r\n        for (Column c : Column.values()) {\r\n            TableColumn column = columns.get(c.ordinal());\r\n            Column modelC = Column.valueOf(column.getModelIndex());\r\n            int modelCIndex = modelC.ordinal();\r\n\r\n            conf.setEnabled(modelC, enabled[modelCIndex]);\r\n            conf.setPosition(modelC, c.ordinal());\r\n            conf.setWidth(modelC, column.getWidth());\r\n        }\r\n        return conf;\r\n    }\r\n\r\n\r\n\r\n    /**\r\n     * Returns <code>true</code> if the specified column is enabled.\r\n     * @param column column, see {@link Column} for possible values\r\n     * @return true if the column is enabled, false if disabled.\r\n     */\r\n    public synchronized boolean isColumnEnabled(Column column) {\r\n        return enabled[column.ordinal()];\r\n    }\r\n\r\n    /**\r\n     * Sets the specified column's enabled state.\r\n     * @param column column, see {@link Column} for possible values\r\n     * @param enabled true to enable the column, false to disable it.\r\n     */\r\n    public synchronized void setColumnEnabled(Column column, boolean enabled) {\r\n        this.enabled[column.ordinal()] = enabled;\r\n    }\r\n\r\n\r\n    /**\r\n     * Sets the specified column's visibility state.\r\n     * @param column column, see {@link Column} for possible values\r\n     * @param visible whether the column should be visible or not.\r\n     */\r\n    public synchronized void setColumnVisible(Column column, boolean visible) {\r\n        // Ignores calls that won't actually change anything.\r\n        int columnVal = column.ordinal();\r\n        if (visibility[columnVal] != visible) {\r\n            visibility[columnVal] = visible;\r\n            widthCache = CACHE_OUT_OF_DATE;\r\n\r\n            if (visible) {\r\n            // Adds the column.\r\n                countCache++;\r\n                triggerColumnAdded(new TableColumnModelEvent(this, columnVal, columnVal));\r\n            } else {\r\n            // Removes the column.\r\n                countCache--;\r\n                triggerColumnRemoved(new TableColumnModelEvent(this, columnVal, columnVal));\r\n            }\r\n            internalIndexCache = null;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the specified column is visible.\r\n     * @param column column, see {@link Column} for possible values\r\n     * @return <code>true</code> if the specified column is visible, <code>false</code> otherwise.\r\n     */\r\n    public boolean isColumnVisible(Column column) {\r\n        return visibility[column.ordinal()];\r\n    }\r\n\r\n    /**\r\n     * Adds the specified column to the model.\r\n     * @param column column to add to the model.\r\n     */\r\n    public void addColumn(TableColumn column) {\r\n        setColumnVisible(Column.valueOf(column.getModelIndex()), true);\r\n    }\r\n\r\n    /**\r\n     * Removes the specified column from the model.\r\n     * @param column column to remove from the model.\r\n     */\r\n    public void removeColumn(TableColumn column) {\r\n        setColumnVisible(Column.valueOf(column.getModelIndex()), false);\r\n    }\r\n\r\n\r\n\r\n    // - Column retrieval ----------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    private int getInternalIndex(int index) {\r\n        if (internalIndexCache != null) {\r\n            return internalIndexCache[index];\r\n        }\r\n//            // Looks for the visible column of index 'index'.\r\n//            int visibleIndex = -1;\r\n//            for (int i = 0; i < visibility.length; i++) {\r\n//                TableColumn column = columns.get(i);\r\n//                if (visibility[column.getModelIndex()]) {\r\n//                    if (++visibleIndex == index) {\r\n//                        return i;\r\n//                    }\r\n//                }\r\n//            }\r\n//            // Index doesn't exist.\r\n//            throw new ArrayIndexOutOfBoundsException(Integer.toString(index));\r\n            // Looks for the visible column of index 'index'.\r\n        buildColumnIndexCache();\r\n        return internalIndexCache[index];\r\n    }\r\n\r\n    private synchronized void buildColumnIndexCache() {\r\n        internalIndexCache = new int[visibility.length];\r\n        int visibleIndex = 0;\r\n        for (int i = 0; i < visibility.length; i++) {\r\n            TableColumn column = columns.get(i);\r\n            if (visibility[column.getModelIndex()]) {\r\n                internalIndexCache[visibleIndex++] = i;\r\n            }\r\n        }\r\n        while (visibleIndex < visibility.length) {\r\n            internalIndexCache[visibleIndex++] = -1;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Returns the specified column.\r\n     * @param  index index of the column in the model.\r\n     * @return       the requested column.\r\n     */\r\n    public synchronized TableColumn getColumn(int index) {\r\n        return columns.get(getInternalIndex(index));\r\n    }\r\n\r\n    public synchronized TableColumn getColumnFromId(int id) {\r\n        return columns.get(id);\r\n    }\r\n\r\n    public synchronized int getColumnPosition(int id) {\r\n        for (int i = 0; i < visibility.length; i++) {\r\n            if (columns.get(i).getModelIndex() == id) {\r\n                return i;\r\n            }\r\n        }\r\n        return -1;\r\n    }\r\n\r\n    /**\r\n     * Returns the number of columns currently displayed.\r\n     * @return the number of columns currently displayed.\r\n     */\r\n    public synchronized int getColumnCount() {return countCache;}\r\n\r\n    /**\r\n     * Moves a column.\r\n     * @param from index of the column to move.\r\n     * @param to   where to move the column.\r\n     */\r\n    public synchronized void moveColumn(int from, int to) {\r\n        // We need to trigger these for the file table to display the 'column dragging' animation.\r\n        if (from == to) {\r\n            triggerColumnMoved(new TableColumnModelEvent(this, from, to));\r\n            return;\r\n        }\r\n\r\n        // Locates the internal index of the requested column\r\n        // and removes that column\r\n        int index = getInternalIndex(from);    // Used to store the internal index of 'from' and 'to'.\r\n        TableColumn column = columns.get(index);    // Buffer for the table to remove.\r\n        columns.remove(index);\r\n\r\n        // If the column needs to be moved at the end of the set, no need to locate its correct index.\r\n        if (to == countCache - 1) {\r\n            columns.add(column);\r\n        } else {\r\n            // Otherwise, finds the column's internal index and inserts it there.\r\n            index  = getInternalIndex(to);\r\n            columns.add(index, column);\r\n        }\r\n\r\n        // Notifies listeners and stores the new configuration.\r\n        triggerColumnMoved(new TableColumnModelEvent(this, from, to));\r\n        internalIndexCache = null;\r\n    }\r\n\r\n    public int getColumnIndex(Object identifier) {\r\n        return 0;\r\n    }\r\n\r\n\r\n\r\n    // - Columns width -------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /**\r\n     * Returns the index of the column at the specified position.\r\n     * @param  x position of the column to look for.\r\n     * @return   the index of the column at the specified position, <code>-1</code> if not found.\r\n     */\r\n    @Override\r\n    public int getColumnIndexAtX(int x) {\r\n        int count = getColumnCount();\r\n        for (int i = 0; i < count; i++) {\r\n            x = x - getColumn(i).getWidth();\r\n            if (x < 0) {\r\n            return i;\r\n            }\r\n        }\r\n        return -1;\r\n    }\r\n\r\n    /**\r\n     * Returns the total width of the table column model.\r\n     * @return the total width of the table column model.\r\n     */\r\n    public int getTotalColumnWidth() {\r\n        if (widthCache == CACHE_OUT_OF_DATE) {\r\n            computeWidthCache();\r\n        }\r\n        return widthCache;\r\n    }\r\n\r\n    /**\r\n     * Computes the model's width.\r\n     */\r\n    private void computeWidthCache() {\r\n        Enumeration<TableColumn> elements = getColumns();\r\n        widthCache = 0;\r\n        while (elements.hasMoreElements()) {\r\n            widthCache += elements.nextElement().getWidth();\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Invalidates the width cache if a column's width has changed.\r\n     */\r\n    public void propertyChange(PropertyChangeEvent event) {\r\n        String name = event.getPropertyName();\r\n        if (\"width\".equals(name)) {\r\n                columnSizesSet = true;\r\n            widthCache = CACHE_OUT_OF_DATE;\r\n                // Notifies the table that columns width have changed and that it should repaint itself.\r\n                triggerColumnMarginChanged(new ChangeEvent(this));\r\n        }\r\n    }\r\n\r\n    public boolean wereColumnSizesSet() {\r\n        return columnSizesSet;\r\n    }\r\n\r\n\r\n    // - Columns margin ------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    // Column margin is fixed to 0.\r\n\r\n    /**\r\n     * Returns 0.\r\n     * @return 0.\r\n     */\r\n    @Override\r\n    public int getColumnMargin() {\r\n        return 0;\r\n    }\r\n\r\n    /**\r\n     * Ignored.\r\n     */\r\n    @Override\r\n    public void setColumnMargin(int margin) {}\r\n\r\n\r\n\r\n    // - Listeners -----------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /**\r\n     * Registers the specified column model listener.\r\n     * @param listener listener to register.\r\n     */\r\n    public void addColumnModelListener(TableColumnModelListener listener) {\r\n        listeners.put(listener, null);\r\n    }\r\n\r\n    /**\r\n     * Removes the specified column model listener.\r\n     * @param listener listener to remove.\r\n     */\r\n    public void removeColumnModelListener(TableColumnModelListener listener) {\r\n        listeners.remove(listener);\r\n    }\r\n\r\n    /**\r\n     * Calls all registered listeners' <code>columnAdded(event)</code>.\r\n     * @param event event to propagate.\r\n     */\r\n    private void triggerColumnAdded(TableColumnModelEvent event) {\r\n        for (TableColumnModelListener listener: listeners.keySet()) {\r\n            listener.columnAdded(event);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Calls all registered listeners' <code>columnMarginChanged(event)</code>.\r\n     * @param event event to propagate.\r\n     */\r\n    private void triggerColumnMarginChanged(ChangeEvent event) {\r\n        for (TableColumnModelListener listener: listeners.keySet()) {\r\n            listener.columnMarginChanged(event);\r\n    }\r\n    }\r\n\r\n    /**\r\n     * Calls all registered listeners' <code>columnMoved(event)</code>.\r\n     * @param event event to propagate.\r\n     */\r\n    private void triggerColumnMoved(TableColumnModelEvent event) {\r\n        for (TableColumnModelListener listener: listeners.keySet()) {\r\n            listener.columnMoved(event);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Calls all registered listeners' <code>columnRemoved(event)</code>.\r\n     * @param event event to propagate.\r\n     */\r\n    private void triggerColumnRemoved(TableColumnModelEvent event) {\r\n        for (TableColumnModelListener listener: listeners.keySet()) {\r\n            listener.columnRemoved(event);\r\n        }\r\n    }\r\n\r\n\r\n\r\n    // - Column selection ----------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    // Column selection is not allowed, this methods are ignored or return default values.\r\n\r\n    /**\r\n     * Returns <code>false</code>.\r\n     * @return <code>false</code>.\r\n     */\r\n    public boolean getColumnSelectionAllowed() {\r\n        return false;\r\n    }\r\n\r\n    /**\r\n     * Returns <code>0</code>.\r\n     * @return <code>0</code>.\r\n     */\r\n    @Override\r\n    public int getSelectedColumnCount() {\r\n        return 0;\r\n    }\r\n\r\n    /**\r\n     * Returns an integer array of size 0.\r\n     * @return an integer array of size 0.\r\n     */\r\n    @Override\r\n    public int[] getSelectedColumns() {\r\n        return new int[0];\r\n    }\r\n\r\n    /**\r\n     * Ignored.\r\n     */\r\n    public void setColumnSelectionAllowed(boolean flag) {}\r\n\r\n    /**\r\n     * Returns a default list selection model.\r\n     * <p>\r\n     * Ideally, we'd like to return <code>null</code> here, but the table API takes a dim view\r\n     * of this and we're forced to keep a useless reference.\r\n     *\r\n     * @return a default list selection model.\r\n     */\r\n    public ListSelectionModel getSelectionModel() {return SELECTION_MODEL;}\r\n\r\n    /**\r\n     * Ignored.\r\n     */\r\n    public void setSelectionModel(ListSelectionModel model) {}\r\n\r\n\r\n\r\n    // - Column enumeration --------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /**\r\n     * Returns an enumeration on all visible columns.\r\n     * @return an enumeration on all visible columns.\r\n     */\r\n    public Enumeration<TableColumn> getColumns() {\r\n        return new ColumnEnumeration();\r\n    }\r\n\r\n    public Iterator<TableColumn> getAllColumns() {\r\n        return columns.iterator();\r\n    }\r\n\r\n    /**\r\n     * Browses through the model's visible columns\r\n     * <p>\r\n     * This will enumerate all the elements of {@link FileTableColumnModel#columns}, skipping\r\n     * over any that's marked as invisible.\r\n     *\r\n     * @author Nicolas Rinaudo\r\n     */\r\n    private class ColumnEnumeration implements Enumeration<TableColumn> {\r\n        /** Index of the next available element in the enumeration. */\r\n        private int nextIndex;\r\n\r\n\r\n\r\n        /**\r\n         * Creates a new column enumeration.\r\n         */\r\n        ColumnEnumeration() {\r\n            nextIndex = -1;\r\n            findNextElement();\r\n        }\r\n\r\n        /**\r\n         * Finds the next visible element in the model.\r\n         */\r\n        private void findNextElement() {\r\n            for (nextIndex++; nextIndex < visibility.length; nextIndex++) {\r\n                TableColumn column = columns.get(nextIndex);\r\n                if (visibility[column.getModelIndex()]) {\r\n                    break;\r\n                }\r\n            }\r\n        }\r\n\r\n\r\n        /**\r\n         * Returns <code>true</code> if there's a next element in the enumeration.\r\n         * @return <code>true</code> if there's a next element in the enumeration, <code>false</code> otherwise.\r\n         */\r\n        public boolean hasMoreElements() {\r\n            return nextIndex < visibility.length;\r\n        }\r\n\r\n        /**\r\n         * Returns the next element in the enumeration.\r\n         * @return                        the next element in the enumeration.\r\n         * @throws NoSuchElementException if there is no next element in the enumeration.\r\n         */\r\n        public TableColumn nextElement() {\r\n            // Makes sure we have at least one more element to return.\r\n            if (!hasMoreElements()) {\r\n                throw new NoSuchElementException();\r\n            }\r\n\r\n            // Retrieves the next element.\r\n            TableColumn column = columns.get(nextIndex);\r\n\r\n            // Looks for the next one.\r\n            findNextElement();\r\n\r\n            return column;\r\n        }\r\n    }\r\n\r\n\r\n\r\n    // - Column sorting ------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /**\r\n     * Used to sort the model's columns at boot time.\r\n     * <p>\r\n     * The sort is done by first comparing each column's index as defined in the configuration and,\r\n     * if there's a conflict, by comparing each column's identifier.\r\n     *\r\n     * @author Nicolas Rinaudo\r\n     */\r\n    private static class ColumnSorter implements Comparator<TableColumn> {\r\n        /** Defines the columns order. */\r\n        private final FileTableConfiguration conf;\r\n\r\n        /**\r\n         * Loads the columns order as defined in the configuration.\r\n         */\r\n        ColumnSorter(FileTableConfiguration conf) {\r\n            this.conf = conf;\r\n        }\r\n\r\n        /**\r\n         * Compares <code>o1</code> and <code>o2</code>.\r\n         */\r\n        public int compare(TableColumn tc1, TableColumn tc2) {\r\n            int id1 = tc1.getModelIndex();    // Identifier of the first column.\r\n            int id2 = tc2.getModelIndex();    // Identifier of the second column.\r\n\r\n            // Retrieves the two columns' indexes and identifiers.\r\n            int index1 = conf.getPosition(Column.valueOf(id1)); // Index of the first column as defined in the configuration.\r\n            int index2 = conf.getPosition(Column.valueOf(id2)); // Index of the second column as defined in the configuration.\r\n\r\n            // Sort by index, then by identifier.\r\n            if (index1 < index2) {\r\n                return -1;\r\n            }\r\n            if (index1 == index2) {\r\n                if (id1 < id2) {\r\n                    return -1;\r\n                }\r\n                if (id1 == id2) {\r\n                    return 0;\r\n                }\r\n            }\r\n            return 1;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/table/views/full/FileTableConfiguration.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.table.views.full;\n\nimport com.mucommander.ui.main.table.Column;\n\n/**\n * Describes a file table's initial configuration.\n * @author Nicolas Rinaudo\n */\npublic class FileTableConfiguration {\n    /** Each column's enabled state. */\n    private final boolean[] enabled;\n    /** Initial width of each column. */\n    private final int[] width;\n    /** Columns initial order. */\n    private final int[] order;\n\n\n\n    /**\n     * Creates a new file table configuration.\n     */\n    public FileTableConfiguration() {\n        int columnCount = Column.values().length;\n        enabled = new boolean[columnCount];\n        width = new int[columnCount];\n        order = new int[columnCount];\n    }\n\n\n    /**\n     * Returns <code>true</code> if the specified column is enabled.\n     * @param  column column whose enabled state should be returned.\n     * @return        <code>true</code> if the specified column is enabled, <code>false</code> otherwise.\n     */\n    public boolean isEnabled(Column column) {\n        return enabled[column.ordinal()];\n    }\n\n    /**\n     * Sets the enabled state of the specified column.\n     * <p>\n     * Note that the {@link Column#NAME} column's enabled state is ignored as it will always be enabled.\n     *\n     * @param column column whose enabled state should be set.\n     * @param flag   whether the column should be enabled.\n     */\n    public void setEnabled(Column column, boolean flag) {\n        enabled[column.ordinal()] = flag;\n    }\n\n\n\n    /**\n     * Returns the initial width of the specified column.\n     * @param  column column whose width should be retrieved.\n     * @return        the requested column's width.\n     */\n    public int getWidth(Column column) {\n        return width[column.ordinal()];\n    }\n\n    /**\n     * Sets the specified column's width.\n     * <p>\n     * Note that the {@link Column#NAME} column's width will be ignored, as it depends on the frame's\n     * initial dimensions.\n     *\n     * @param column column whose width should be set.\n     * @param value  column's initial width.\n     */\n    public void setWidth(Column column, int value) {\n        width[column.ordinal()] = value;\n    }\n\n\n\n    /**\n     * Returns the desired initial position of the specified column.\n     * <p>\n     * Note that the returned value isn't necessarily a legal column position. It's used\n     * as a comparison value rather than an index.\n     *\n     * @param  column column whose initial position will be returned.\n     * @return        the desired initial position of the specified column.\n     */\n    public int getPosition(Column column) {\n        return order[column.ordinal()];\n    }\n\n    /**\n     * Sets the specified column's initial position.\n     * @param column   column whose position will be set.\n     * @param position desired position for the specified column.\n     */\n    public void setPosition(Column column, int position) {\n        order[column.ordinal()] = position;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/table/views/full/FileTableModel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.table.views.full;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.main.table.FileTable;\nimport com.mucommander.utils.text.CustomDateFormat;\nimport com.mucommander.utils.text.SizeFormat;\nimport com.mucommander.ui.main.table.Column;\nimport com.mucommander.ui.main.table.views.BaseFileTableModel;\nimport org.jetbrains.annotations.NotNull;\n\n\n/**\n * This class maps table cells onto file attributes.\n *\n * @author Maxence Bernard\n */\npublic class FileTableModel extends BaseFileTableModel {\n\n    /** Cell values cache */\n    private Object[][] cellValuesCache;\n\n    private int columnsVisibilityMask;\n\n\n    /**\n     * Creates a new FileTableModel, without any initial current folder.\n     */\n    public FileTableModel() {\n        super();\n        cellValuesCache = new Object[0][Column.values().length-1];\n    }\n\n\n    @Override\n    public synchronized void setupFromModel(BaseFileTableModel model) {\n        super.setupFromModel(model);\n        initCellValuesCache();\n        fillCellCache(null);\n    }\n\n    /**\n     * Init and fill cell cache to speed up table even more\n     */\n    @Override\n    protected void initCellValuesCache() {\n        this.cellValuesCache = new Object[getRowCount()][Column.values().length-1];\n    }\n\n\n    /**\n     * Retrieves all cell values and stores them in an array for fast access.\n     */\n    @Override\n    public synchronized void fillCellCache(FileTable fileTable) {\n        int len = cellValuesCache.length;\n        if (len == 0) {\n            return;\n        }\n\n        columnsVisibilityMask = calcColumnVisibilityMask(fileTable);\n        // Special '..' file\n        if (parent != null) {\n            Object[] cell = cellValuesCache[0];\n            cell[Column.NAME.ordinal()-1] = \"..\";\n            cell[Column.SIZE.ordinal()-1] = DIRECTORY_SIZE_STRING;\n            currentFolderDateSnapshot = currentFolder.getLastModifiedDate();\n            cell[Column.DATE.ordinal()-1] =\tCustomDateFormat.format(currentFolderDateSnapshot);\n            // Don't display parent's permissions as they can have a different format from the folder contents\n            // (e.g. for archives) and this looks weird\n            cell[Column.PERMISSIONS.ordinal()-1] = \"\";\n            cell[Column.OWNER.ordinal()-1] = \"\";\n            cell[Column.GROUP.ordinal()-1] = \"\";\n        }\n\n        int fileIndex = 0;\n        final int indexOffset = parent == null ? 0 : 1;\n        for (int i = indexOffset; i < len; i++) {\n            int cellIndex = fileIndex + indexOffset;\n            //int cellIndex = fileArrayIndex[fileIndex] + indexOffset;\n            //fillOneCellCache(cellIndex, cellIndex);\n            Object[] cell = cellValuesCache[cellIndex];\n            for (int ci = Column.NAME.ordinal()-1; ci <= Column.GROUP.ordinal()-1; ci++) {\n                cell[ci] = null;\n            }\n            fileIndex++;\n        }\n    }\n\n    private static int calcColumnVisibilityMask(FileTable fileTable) {\n        if (fileTable == null) {\n            return 0xffff;\n        }\n        int mask = 0;\n        for (Column column : Column.values()) {\n            if (fileTable.isColumnVisible(column)) {\n                mask |= 1 << column.ordinal();\n            }\n        }\n        return mask;\n    }\n\n    private Object[] fillOneCellCache(int cellIndex, int fileIndex) {\n        AbstractFile file = getCachedFileAt(fileIndex);\n        Object[] cell = cellValuesCache[cellIndex];\n        cell[Column.NAME.ordinal()-1] = file.getName();\n        if (isColumnVisible(Column.SIZE)) {\n            cell[Column.SIZE.ordinal() - 1] = getSizeValue(file);\n        }\n        if (isColumnVisible(Column.DATE)) {\n            cell[Column.DATE.ordinal() - 1] = CustomDateFormat.format(file.getLastModifiedDate());\n        }\n        if (isColumnVisible(Column.PERMISSIONS)) {\n            cell[Column.PERMISSIONS.ordinal() - 1] = file.getPermissionsString();\n        }\n        if (isColumnVisible(Column.OWNER) && file.canGetOwner()) {\n            cell[Column.OWNER.ordinal() - 1] = file.getOwner();\n        }\n        if (isColumnVisible(Column.GROUP) && file.canGetGroup()) {\n            cell[Column.GROUP.ordinal() - 1] = file.getGroup();\n        }\n        return cell;\n    }\n\n    private boolean isColumnVisible(Column column) {\n        return (columnsVisibilityMask & (1 << column.ordinal())) != 0;\n    }\n\n    @NotNull\n    private String getSizeValue(AbstractFile file) {\n        if (file.isDirectory()) {\n            if (hasCalculatedDirectories) {\n                Long dirSize;\n                synchronized (directorySizes) {\n                    dirSize = directorySizes.get(file);\n                }\n                if (dirSize != null) {\n                    return SizeFormat.format(dirSize, sizeFormat);\n                } else {\n                    synchronized (calculateSizeQueue) {\n                        return calculateSizeQueue.contains(file) ? QUEUED_DIRECTORY_SIZE_STRING : DIRECTORY_SIZE_STRING;\n                    }\n                }\n            } else {\n                return DIRECTORY_SIZE_STRING;\n            }\n        } else {\n            return SizeFormat.format(file.getSize(), sizeFormat);\n        }\n    }\n\n\n    //////////////////////////////////////////\n    // Overridden AbstractTableModel methods //\n    //////////////////////////////////////////\n\n    @Override\n    public int getColumnCount() {\n        return Column.values().length; // icon, name, size, date, permissions, owner, group\n    }\n\n    @Override\n    public String getColumnName(int columnIndex) {\n        return Column.valueOf(columnIndex).getLabel();\n    }\n\n    /**\n     * Returns the total number of rows, including the special parent folder file '..', if there is one.\n     */\n    @Override\n    public synchronized int getRowCount() {\n        return fileArrayIndex.length + (parent == null ? 0 : 1);\n    }\n\n\n    @Override\n    public synchronized Object getValueAt(int rowIndex, int columnIndex) {\n        if (rowIndex >= cellValuesCache.length || columnIndex >= cellValuesCache[rowIndex].length) {\n            return null;\n        }\n        // Need to check that row index is not larger than actual number of rows\n        // because if table has just been changed (rows have been removed),\n        // JTable may have an old row count value and may try to repaint rows that are out of bounds.\n        if (rowIndex >= getRowCount()) {\n            // Returning null will have JTable ignore this row\n            return null;\n        }\n\n        // Icon/extension column, return a null value\n        Column column = Column.valueOf(columnIndex);\n        if (column == Column.EXTENSION) {\n            return null;\n        }\n\t\t\n        // Decrement column index for cellValuesCache array\n        columnIndex--;\n        // Handle special '..' file\n        if (rowIndex == 0 && parent != null) {\n            return cellValuesCache[0][columnIndex];\n//            Object result = cellValuesCache[0][columnIndex];\n//            if (result == null) {\n//                result = fillOneCellCache(0, 0)[columnIndex];\n//            }\n//            return result;\n        }\n        int fileIndex = parent == null ? rowIndex : rowIndex-1;\n        int index = fileArrayIndex[fileIndex];\n        if (parent != null) {\n            index++;\n        }\n        Object result = cellValuesCache[index][columnIndex];\n        if (result == null) {\n            result = fillOneCellCache(index, parent != null ? fileIndex + 1 : fileIndex)[columnIndex];\n        }\n        return result;\n    }\n\n\t\n    /**\n     * Returns <code>true</code> if name column has temporarily be made editable by FileTable\n     * and given row doesn't correspond to parent file '..', <code>false</code> otherwise.\n     */\n    @Override\n    public boolean isCellEditable(int rowIndex, int columnIndex) {\n        // Name column can temporarily be made editable by FileTable\n        // but parent file '..' name should never be editable\n        return Column.valueOf(columnIndex) == Column.NAME && (parent == null || rowIndex != 0) && nameColumnEditable;\n    }\n\n    public String getFileNameAt(int index) {\n        return (index == 0 && hasParentFolder()) ? \"..\" : getFileAt(index).getName();\n    }\n\n\n    public int getFileIndexAt(int row, int column) {\n        return row;\n    }\n\n    @Override\n    public int getFileRow(int index) {\n        return index;\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tabs/ClonedFileTableTabFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.tabs;\n\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.core.LocalLocationHistory;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.tabs.TabFactory;\n\n/**\n * Factory for creating {@link com.mucommander.ui.main.tabs.FileTableTab} which is a clone of a given {@link com.mucommander.ui.main.tabs.FileTableTab}\n * \n * @author Arik Hadas\n */\npublic class ClonedFileTableTabFactory implements TabFactory<FileTableTab, FileTableTab> {\n\n\tprivate final FolderPanel folderPanel;\n\t\n\tClonedFileTableTabFactory(FolderPanel folderPanel) {\n\t\tthis.folderPanel = folderPanel;\n\t}\n\t\n\tpublic FileTableTab createTab(FileTableTab tab) {\n\t\tif (tab.getLocation() == null) {\n\t\t\tthrow new RuntimeException(\"Invalid location\");\n\t\t}\n\n\t\treturn new ClonedFileTableTab(tab, folderPanel);\n\t}\n\n\tstatic class ClonedFileTableTab extends FileTableTab {\n\t\t\n\t\t/** The location presented in this tab */\n\t\tprivate FileURL location;\n\n\t\t/** Flag that indicates whether the tab is locked or not */\n\t\tprivate boolean locked;\n\n\t\t/** Title that is assigned for the tab */\n\t\tprivate String title;\n\n\t\t/** History of accessed location within the tab */\n\t\tprivate final LocalLocationHistory locationHistory;\n\n\t\t/**\n\t\t * Private constructor\n\t\t * \n\t\t * @param tab - the location that would be presented in the tab\n\t\t */\n\t\tprivate ClonedFileTableTab(FileTableTab tab, FolderPanel folderPanel) {\n\t\t\tthis.location = tab.getLocation();\n\t\t\tthis.locked = tab.isLocked();\n\t\t\tthis.title = tab.getTitle();\n\t\t\tlocationHistory = new LocalLocationHistory(folderPanel);\n\t\t}\n\t\t\n\t\t@Override\n\t\tpublic void setLocation(FileURL location) {\n\t\t\tthis.location = location;\n\t\t\t\n\t\t\t// add location to the history (See LocalLocationHistory to see how it handles the first location it gets)\n\t\t\tlocationHistory.tryToAddToHistory(location);\n\t\t}\n\n\t\t@Override\n\t\tpublic FileURL getLocation() {\n\t\t\treturn location;\n\t\t}\n\n\t\t@Override\n\t\tpublic void setLocked(boolean locked) {\n\t\t\tthis.locked = locked;\n\t\t}\n\n\t\t@Override\n\t\tpublic void setTitle(String title) {\n\t\t\tthis.title = title;\n\t\t}\n\n\t\t@Override\n\t\tpublic String getTitle() {\n\t\t\treturn title;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean isLocked() {\n\t\t\t return locked;\n\t\t}\n\n\t\t@Override\n\t\tpublic LocalLocationHistory getLocationHistory() {\n\t\t\treturn locationHistory;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean equals(Object obj) {\n\t\t\tif (obj instanceof FileTableTab) {\n\t\t\t\tFileTableTab other = ((FileTableTab) obj);\n\t\t\t\treturn location.equals(other.getLocation()) && locked == ((FileTableTab) obj).isLocked();\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\n\t\t@Override\n\t\tpublic int hashCode() {\n\t\t\treturn location.hashCode();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tabs/ConfFileTableTab.java",
    "content": "package com.mucommander.ui.main.tabs;\n\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.core.LocalLocationHistory;\n\npublic class ConfFileTableTab extends FileTableTab {\n\n\tprivate final boolean lock;\n\tprivate final FileURL location;\n\tprivate final String title;\n\t\n\tpublic ConfFileTableTab(FileURL location) {\n\t\tthis(false, location, null);\n\t}\n\n\tpublic ConfFileTableTab(boolean lock, FileURL location, String title) {\n\t\tthis.lock = lock;\n\t\tthis.location = location;\n\t\tthis.title = title;\n\t}\n\n\t@Override\n\tpublic boolean isLocked() {\n\t\treturn lock;\n\t}\n\n\t@Override\n\tpublic FileURL getLocation() {\n\t\treturn location;\n\t}\n\n\t@Override\n\tpublic String getTitle() {\n\t\treturn title;\n\t}\n\n\t@Override\n\tpublic void setLocation(FileURL location) {\n\t\tthrow new UnsupportedOperationException(\"cannot change location of configuration tab\");\n\t}\n\n\t@Override\n\tpublic void setLocked(boolean locked) {\n\t\tthrow new UnsupportedOperationException(\"cannot lock configuration tab\");\n\t}\n\n\t@Override\n\tpublic void setTitle(String title) {\n\t\tthrow new UnsupportedOperationException(\"cannot change title of configuration tab\");\n\t}\n\n\t@Override\n\tpublic LocalLocationHistory getLocationHistory() {\n\t\treturn null;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tabs/DefaultFileTableTabFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.tabs;\n\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.core.LocalLocationHistory;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.tabs.TabFactory;\n\n/**\n * Factory for creating regular {@link com.mucommander.ui.main.tabs.FileTableTab} presenting the given location\n * \n * @author Arik Hadas\n */\npublic class DefaultFileTableTabFactory implements TabFactory<FileTableTab, FileURL> {\n\n\tprivate final FolderPanel folderPanel;\n\t\n\tpublic DefaultFileTableTabFactory(FolderPanel folderPanel) {\n\t\tthis.folderPanel = folderPanel;\n\t}\n\n\tpublic FileTableTab createTab(FileURL location) {\n\t\tif (location == null)\n\t\t\tthrow new RuntimeException(\"Invalid location\");\n\n\t\treturn new DefaultFileTableTab(location, folderPanel);\n\t}\n\n\tstatic class DefaultFileTableTab extends FileTableTab {\n\t\t\n\t\t/** The location presented in this tab */\n\t\tprivate FileURL location;\n\n\t\t/** Flag that indicates whether the tab is locked or not */\n\t\tprivate boolean locked;\n\n\t\t/** Title that is assigned for the tab */\n\t\tprivate String title;\n\t\t\n\t\t/** History of accessed location within the tab */\n\t\tprivate final LocalLocationHistory locationHistory;\n\n\t\t/**\n\t\t * Private constructor\n\t\t * \n\t\t * @param location - the location that would be presented in the tab\n\t\t */\n\t\tprivate DefaultFileTableTab(FileURL location, FolderPanel folderPanel) {\n\t\t\tthis.location = location;\n\t\t\tthis.locked = false;\n\t\t\tlocationHistory = new LocalLocationHistory(folderPanel);\n\t\t}\n\n\t\t@Override\n\t\tpublic void setLocation(FileURL location) {\n\t\t\tthis.location = location;\n\t\t\t\n\t\t\t// add location to the history (See LocalLocationHistory to see how it handles the first location it gets)\n\t\t\tlocationHistory.tryToAddToHistory(location);\n\t\t}\n\n\t\t@Override\n\t\tpublic FileURL getLocation() {\n\t\t\treturn location;\n\t\t}\n\n\t\t@Override\n\t\tpublic void setLocked(boolean locked) {\n\t\t\tthis.locked = locked;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean isLocked() {\n\t\t\t return locked;\n\t\t}\n\n\t\t@Override\n\t\tpublic void setTitle(String title) {\n\t\t\tthis.title = title;\n\t\t}\n\n\t\t@Override\n\t\tpublic String getTitle() {\n\t\t\treturn title;\n\t\t}\n\n\t\t@Override\n\t\tpublic LocalLocationHistory getLocationHistory() {\n\t\t\treturn locationHistory;\n\t\t}\n\t\t\n\t\t@Override\n\t\tpublic boolean equals(Object obj) {\n\t\t\tif (obj instanceof FileTableTab) {\n\t\t\t\tFileTableTab other = (FileTableTab) obj;\n\t\t\t\treturn location.equals(other.getLocation()) &&\n\t\t\t\t\t   locked == other.isLocked();\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\n\t\t@Override\n\t\tpublic int hashCode() {\n\t\t\treturn location.hashCode();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tabs/DefaultFileTableTabHeaderFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.tabs;\n\nimport com.mucommander.ui.main.FolderPanel;\n\n/**\n * \n * @author Arik Hadas\n */\npublic class DefaultFileTableTabHeaderFactory extends FileTableTabHeaderFactory {\n\n\tpublic DefaultFileTableTabHeaderFactory(FolderPanel folderPanel) {\n\t\tsuper(folderPanel);\n\t}\n\t\n\t@Override\n\tpublic FileTableTabHeader create(FileTableTab tab) {\n\t\treturn new FileTableTabHeader(folderPanel, true, tab);\n\t}\n\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tabs/FileTableTab.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.tabs;\n\nimport com.mucommander.bookmark.BookmarkManager;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport com.mucommander.commons.file.util.PathUtils;\nimport com.mucommander.core.LocalLocationHistory;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.tabs.Tab;\n\n/**\n * Interface of tab in the {@link com.mucommander.ui.main.FolderPanel} that contains a {@link com.mucommander.ui.main.table.FileTable}\n *\n * @author Arik Hadas\n */\npublic abstract class FileTableTab implements Tab {\n\n\t/**\n\t * Setter for the location presented in the tab\n\t * \n\t * @param location the file that is going to be presented in the tab\n\t */\n\tpublic abstract void setLocation(FileURL location);\n\n\t/**\n\t * Getter for the location presented in the tab\n\t * \n\t * @return the file that is being presented in the tab\n\t */\n\tpublic abstract FileURL getLocation();\n\t\n\t/**\n\t * Set the tab to be locked or unlocked according to the given flag\n\t * \n\t * @param locked flag that indicates whether the tab should be locked or not\n\t */\n\tpublic abstract void setLocked(boolean locked);\n\t\n\t/**\n\t * Returns whether the tab is locked\n\t * \n\t * @return indication whether the tab is locked\n\t */\n\tpublic abstract boolean isLocked();\n\n\t/**\n\t * Set the title of the tab to the given string\n\t * \n\t * @param title - predefined title to be assigned to the tab, null for no predefined title\n\t */\n\tpublic abstract void setTitle(String title);\n\n\t/**\n\t * Returns the title that was assigned for the tab\n\t * \n\t * @return the title that was assigned for the tab, null is returned if no title was assigned\n\t */\n\tpublic abstract String getTitle();\n\n\t/**\n\t * Returns a string representation for the tab:\n\t *  the tab's fixed title will be returned if such title was assigned,\n\t *  otherwise, a string representation will be created based on the tab's location:\n\t *    for local file, the filename will be returned (\"/\" in case the root folder is presented)\n\t *    for remote file, the returned pattern will be \"\\&lt;host\\&gt;:\\&lt;filename\\&gt;\"\n\t * \n\t * @return String representation of the tab\n\t */\n\tString getDisplayableTitle() {\n\t\tString title = getTitle();\n\n\t\treturn title != null ? title : createDisplayableTitleFromLocation(getLocation());\n\t}\n\n\tprivate String createDisplayableTitleFromLocation(FileURL location) {\n\t\tif (BookmarkManager.isBookmark(location) && location.getHost() == null) {\n\t\t\treturn Translator.get(\"bookmarks_menu\");\n\t\t}\n\t\tboolean local = FileURL.LOCALHOST.equals(location.getHost());\n\t\treturn getHostRepresentation(location.getHost(), local) + getFilenameRepresentation(location.getFilename(), local);\n\t}\n\n\tprivate String getHostRepresentation(String host, boolean local) {\n\t\treturn local ? \"\" : host + \":\";\n\t}\n\n\tprivate String getFilenameRepresentation(String filename, boolean local) {\n\t\t// Under for OSes with 'root drives' (Windows, OS/2), remove the leading '/' character\n\t\tif (local && LocalFile.hasRootDrives() && filename != null) {\n\t\t\treturn PathUtils.removeLeadingSeparator(filename, \"/\");\n\t\t}\n\t\t// Under other OSes, if the filename is empty return \"/\"\n\t\treturn filename == null ? \"/\" : filename;\n\t}\n\n\t/**\n\t * Returns the tracker of the last accessed locations within the tab\n\t * \n\t * @return tracker of the last accessed locations within the tab\n\t */\n\tpublic abstract LocalLocationHistory getLocationHistory();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tabs/FileTableTabHeader.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.tabs;\n\nimport java.awt.Dimension;\nimport java.awt.Font;\nimport java.awt.GridBagConstraints;\nimport java.awt.GridBagLayout;\nimport java.awt.Insets;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\n\nimport javax.swing.BorderFactory;\nimport javax.swing.JButton;\nimport javax.swing.JLabel;\nimport javax.swing.JPanel;\nimport javax.swing.plaf.basic.BasicButtonUI;\n\nimport com.mucommander.ui.icon.IconManager;\nimport com.mucommander.ui.main.FolderPanel;\n\n/**\n* This panel is the header of the presented tabs under Java 1.6 and above.\n* The panel contains a button for closing the tab.\n* \n* @author Arik Hadas, Maxence Bernard\n*/\npublic class FileTableTabHeader extends JPanel implements ActionListener {\n\t\n\tprivate final FolderPanel folderPanel;\n\t\n\tprivate static final String CLOSE_ICON_NAME = \"close.png\";\n    private static final String CLOSE_ROLLOVER_ICON_NAME = \"close_rollover.png\";\n    private static final int CLOSE_ICON_SIZE = 12;\n    \n    public static final String LOCKED_ICON_NAME = \"lock.png\";\n    private static final int LOCKED_ICON_SIZE = 12;\n\n    FileTableTabHeader(FolderPanel folderPanel, boolean closable, FileTableTab tab) {\n        super(new GridBagLayout());\n\n        this.folderPanel = folderPanel;\n        setOpaque(false);\n\n        GridBagConstraints gbc = new GridBagConstraints();\n        gbc.fill = GridBagConstraints.NONE;\n        gbc.anchor = GridBagConstraints.LINE_START;\n        gbc.gridy = 0;\n\n        // Locked tab icon\n        JLabel lockedIcon = new LockedIcon();\n        gbc.weightx = 0;    // required otherwise extra width may be redistributed around the button\n        gbc.gridx = 0;\n        lockedIcon.setVisible(false);\n        add(lockedIcon, gbc);\n        \n        // Label\n        JLabel label = new JLabel();\n        Font font = new JLabel().getFont();\n        label.setFont(font.deriveFont(font.getStyle(), font.getSize()-2));\n        // Add extra space between the label and the button\n        label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));\n        gbc.weightx = 1;\n        gbc.gridx = 1;\n        add(label, gbc);\n\n        if (closable && !tab.isLocked()) {\n        \t// Close tab button\n        \tJButton closeButton = new CloseButton();\n        \tcloseButton.addActionListener(this);\n        \tgbc.weightx = 1;    // required otherwise extra width may be redistributed around the button\n        \tgbc.gridx = 2;\n        \tadd(closeButton, gbc);\n        }\n\n        setText(tab.getDisplayableTitle());\n        \n        lockedIcon.setVisible(tab.isLocked());\n    }\n\n    private void setText(String text) {\n    \tJLabel label = (JLabel)getComponent(1); \n\n        // Truncate the title if it is too long.\n        // Note: 31 is the maximum title length displayed in tabs by Firefox and Safari at the time of this writing\n        if(text.length()>31)\n            text = text.substring(0, 32) + \"…\";\n\n    \tlabel.setText(text);\n\n    \tvalidate();\n    }\n    \n    /********************************\n\t * ActionListener Implementation\n\t ********************************/\n    \n\tpublic void actionPerformed(ActionEvent e) {\n    \tfolderPanel.getTabs().close(this);\n\t}\n    \n    /**************************************************\n\t * Buttons which are presented in the tab's header\n\t **************************************************/\n    private static class CloseButton extends JButton {\n    \t \n        CloseButton() {\n            setPreferredSize(new Dimension(CLOSE_ICON_SIZE, CLOSE_ICON_SIZE));\n            //Make the button looks the same for all Laf's\n            setUI(new BasicButtonUI());\n            //Make it transparent\n            setContentAreaFilled(false);\n            //No need to be focusable\n            setFocusable(false);\n            setBorderPainted(false);\n            setIcon(IconManager.getIcon(IconManager.IconSet.COMMON, CLOSE_ICON_NAME));\n            //Making nice rollover effect\n            setRolloverEnabled(true);\n            setRolloverIcon(IconManager.getIcon(IconManager.IconSet.COMMON, CLOSE_ROLLOVER_ICON_NAME));\n        }\n\n\n        // Remove default insets\n        @Override\n        public Insets getInsets() {\n            return new Insets(0,0,0,0);\n        }\n\n        // We don't want to update UI for this button\n        @Override\n        public void updateUI() {\n        }\n    }\n    \n    private static class LockedIcon extends JLabel {\n   \t \n        LockedIcon() {\n        \tsuper(IconManager.getIcon(IconManager.IconSet.COMMON, LOCKED_ICON_NAME));\n            setPreferredSize(new Dimension(LOCKED_ICON_SIZE, LOCKED_ICON_SIZE));\n            //No need to be focusable\n            setFocusable(false);\n        }\n\n        // Remove default insets\n        @Override\n        public Insets getInsets() {\n            return new Insets(0,0,0,0);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tabs/FileTableTabHeaderFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.tabs;\n\nimport com.mucommander.ui.main.FolderPanel;\n\n/**\n * \n * @author Arik Hadas\n */\npublic abstract class FileTableTabHeaderFactory {\n\n\tprotected final FolderPanel folderPanel;\n\t\n\tFileTableTabHeaderFactory(FolderPanel folderPanel) {\n\t\tthis.folderPanel = folderPanel;\n\t}\n\t\n\tpublic abstract FileTableTabHeader create(FileTableTab tab);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tabs/FileTableTabPopupMenu.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.tabs;\n\nimport com.mucommander.ui.action.impl.CloneTabToOtherPanelAction;\nimport com.mucommander.ui.action.impl.CloseDuplicateTabsAction;\nimport com.mucommander.ui.action.impl.CloseOtherTabsAction;\nimport com.mucommander.ui.action.impl.CloseTabAction;\nimport com.mucommander.ui.action.impl.DuplicateTabAction;\nimport com.mucommander.ui.action.impl.MoveTabToOtherPanelAction;\nimport com.mucommander.ui.action.impl.SetTabTitleAction;\nimport com.mucommander.ui.action.impl.ToggleLockTabAction;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.popup.TcActionsPopupMenu;\n\n/**\n* Contextual popup menu invoked by {@link FileTableTabbedPane} when right-clicking on a tab's title.\n* \n* @author Arik Hadas\n*/\nclass FileTableTabPopupMenu extends TcActionsPopupMenu {\n\n\tFileTableTabPopupMenu(MainFrame mainFrame) {\n\t\tsuper(mainFrame);\n\n\t\t//FileTableTab tab = mainFrame.getActivePanel().getTabs().getCurrentTab();\n\t\t\n\t\taddAction(DuplicateTabAction.Descriptor.ACTION_ID);\n\t\taddAction(CloseTabAction.Descriptor.ACTION_ID);\n\t\taddAction(CloseOtherTabsAction.Descriptor.ACTION_ID);\n\t\taddAction(CloseDuplicateTabsAction.Descriptor.ACTION_ID);\n\t\taddSeparator();\n\t\taddAction(ToggleLockTabAction.Descriptor.ACTION_ID);\n\t\taddAction(SetTabTitleAction.Descriptor.ACTION_ID);\n\t\taddSeparator();\n\t\taddAction(MoveTabToOtherPanelAction.Descriptor.ACTION_ID);\n\t\taddAction(CloneTabToOtherPanelAction.Descriptor.ACTION_ID);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tabs/FileTableTabbedPane.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.tabs;\n\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport com.mucommander.commons.file.util.PathUtils;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.desktop.DesktopManager;\nimport com.mucommander.ui.action.ActionManager;\nimport com.mucommander.ui.macosx.TabbedPaneUICustomizer;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.tabs.TabbedPane;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.FocusEvent;\nimport java.awt.event.FocusListener;\nimport java.awt.event.MouseAdapter;\nimport java.awt.event.MouseEvent;\n\n/**\n * TabbedPane that present the FileTable tabs.\n * This TabbedPane doesn't contain different FileTable for each tab, instead\n * it use one FileTable instance as a shared object for all tabs. when switching between\n * tabs, the FileTable instance is updated as needed according to the selected tab state.\n * \n * @author Arik Hadas\n */\npublic class FileTableTabbedPane extends TabbedPane<FileTableTab> implements FocusListener {\n\n\t/** The FileTable instance presented in each tab */\n\tprivate final JComponent fileTableComponent;\n\t\n\tprivate final MainFrame mainFrame;\n\tprivate final FolderPanel folderPanel;\n\tprivate final FileTableTabHeaderFactory headersFactory;\n\t\n\n\tFileTableTabbedPane(MainFrame mainFrame, FolderPanel folderPanel, JComponent fileTableComponent, FileTableTabHeaderFactory headersFactory) {\n\t\tthis.fileTableComponent = fileTableComponent;\n\t\tthis.mainFrame = mainFrame;\n\t\tthis.folderPanel = folderPanel;\n\t\tthis.headersFactory = headersFactory;\n\n\t\tsetTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);\n\n\t\taddMouseListener(new MouseAdapter() {\n\n\t\t\tpublic void mouseClicked(MouseEvent e) {\n\t\t\t\tfinal Point clickedPoint = e.getPoint();\n\t\t\t\tint selectedTabIndex = indexAtLocation(clickedPoint.x, clickedPoint.y);\n\t\t\t\tif (selectedTabIndex != -1) {\n\t\t\t\t\tsetSelectedIndex(selectedTabIndex);\n\n\t\t\t\t\tif (DesktopManager.isRightMouseButton(e)) {\n\t\t\t\t\t\t// Open the popup menu only after all swing events are finished, to ensure that when the popup menu is shown\n\t\t\t\t\t\t// and asks for the currently selected tab in the active panel, it'll get the right one\n\t\t\t\t\t\tSwingUtilities.invokeLater(() -> new FileTableTabPopupMenu(mainFrame).show(FileTableTabbedPane.this, clickedPoint.x, clickedPoint.y));\n\t\t\t\t\t}\n\n\t\t\t\t\tif (DesktopManager.isMiddleMouseButton(e)) {\n\t\t\t\t\t\tActionManager.performAction(com.mucommander.ui.action.impl.CloseTabAction.Descriptor.ACTION_ID, mainFrame);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tDesktopManager.customizeTabbedPaneUI(this);\n\n\t\taddFocusListener(this);\n\t}\n\n\t@Override\n\tpublic boolean requestFocusInWindow() {\n\t\treturn fileTableComponent.requestFocusInWindow();\n\t}\n\n\t@Override\n\tpublic void removeTabAt(int index) {\n\t\tsuper.removeTabAt(index);\n\n\t\tif (index == 0 && getTabCount() > 0) {\n\t\t\tsetComponentAt(0, fileTableComponent);\n\t\t}\n\t}\n\n\tprivate void setTabHeader(int index, FileTableTabHeader component) {\n\t\tsuper.setTabComponentAt(index, component);\n\t}\n\n\t@Override\n\tpublic void add(FileTableTab tab) {\n\t\tadd(tab, getTabCount());\n\t}\n\n\t@Override\n\tpublic void add(FileTableTab tab, int index) {\n\t\tadd(getTabCount() == 0 ? fileTableComponent : new JLabel(), index);\n\t\tupdate(tab, index);\n\t}\n\n\t@Override\n\tpublic void setSelectedIndex(int index) {\n\t\t// Allow tabs switching only when no-events-mode is disabled\n\t\tif (!mainFrame.getNoEventsMode()) {\n\t\t    super.setSelectedIndex(index);\n\t\t\trequestFocusInWindow();\n\t\t}\n\t}\n\n\t@Override\n\tpublic void update(FileTableTab tab, int index) {\n\t\tsetTabHeader(index, headersFactory.create(tab));\n\n\t\tString locationText = tab.getLocation().getPath();\n\t\t// For OSes with 'root drives' (Windows, OS/2), remove the leading '/' character\n\t\tif (LocalFile.hasRootDrives()) {\n\t\t\tlocationText = PathUtils.removeLeadingSeparator(locationText, \"/\");\n\t\t}\n\t\tsetToolTipTextAt(index, locationText);\n\n\t\tSwingUtilities.invokeLater(this::validate);\n\t}\n\n\t//////////////////////////////////\n\t// FocusListener implementation //\n\t//////////////////////////////////\n\n\tpublic void focusGained(FocusEvent e) {\n\t\tfolderPanel.getTabs().requestFocus();\n\t}\n\n\tpublic void focusLost(FocusEvent e) { }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tabs/FileTableTabs.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.tabs;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileURL;\r\nimport com.mucommander.ui.event.LocationEvent;\r\nimport com.mucommander.ui.event.LocationListener;\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.tabs.HideableTabbedPane;\r\nimport com.mucommander.ui.tabs.TabFactory;\r\n\r\n/**\r\n* HideableTabbedPane of {@link com.mucommander.ui.main.tabs.FileTableTab} instances.\r\n* \r\n* @author Arik Hadas\r\n*/\r\npublic class FileTableTabs extends HideableTabbedPane<FileTableTab> implements LocationListener {\r\n\r\n\t/** FolderPanel containing those tabs */\r\n\tprivate final FolderPanel folderPanel;\r\n\r\n\t/** Factory of instances of FileTableTab */\r\n\tprivate final TabFactory<FileTableTab, FileURL> defaultTabsFactory;\r\n\r\n\t/** Factory of instances of FileTableTab */\r\n\tprivate final TabFactory<FileTableTab, FileTableTab> clonedTabsFactory;\r\n\r\n\tpublic FileTableTabs(MainFrame mainFrame, FolderPanel folderPanel, ConfFileTableTab[] initialTabs) {\r\n\t\tsuper(new FileTableTabsWithoutHeadersViewerFactory(folderPanel), new FileTableTabsWithHeadersViewerFactory(mainFrame, folderPanel));\r\n\r\n\t\tthis.folderPanel = folderPanel;\r\n\r\n\t\tdefaultTabsFactory = new DefaultFileTableTabFactory(folderPanel);\r\n\t\tclonedTabsFactory = new ClonedFileTableTabFactory(folderPanel);\r\n\r\n\t\t// Register to location change events\r\n\t\tfolderPanel.getLocationManager().addLocationListener(this);\r\n\r\n\t\t// Add the initial folders\r\n\t\tfor (FileTableTab tab : initialTabs) {\r\n\t\t\taddTab(clonedTabsFactory.createTab(tab));\r\n\t\t}\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void selectTab(int index) {\r\n\t\tsuper.selectTab(index);\r\n\r\n\t\tshow(index);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected void show(final int tabIndex) {\r\n\t\ttry {\r\n\t\t\tfolderPanel.tryChangeCurrentFolderInternal(getTab(tabIndex).getLocation(), this::fireActiveTabChanged);\r\n\t\t} catch (Throwable ignore) {}\r\n\t}\r\n\r\n\t/**\r\n\t * Return the currently selected tab\r\n\t * \r\n\t * @return currently selected tab\r\n\t */\r\n\tpublic FileTableTab getCurrentTab() {\r\n\t\tFileTableTab result = getTab(getSelectedIndex());\r\n\t\treturn result != null ? result : getTab(0);\r\n\t}\r\n\r\n\tprivate void updateTabLocation(final FileURL location) {\r\n\t\tupdateCurrentTab(tab -> tab.setLocation(location));\r\n\t}\r\n\t\r\n\tprivate void updateTabLocking(final boolean lock) {\r\n\t\tupdateCurrentTab(tab -> tab.setLocked(lock));\r\n\t}\r\n\r\n\tprivate void updateTabTitle(final String title) {\r\n\t\tupdateCurrentTab(tab -> tab.setTitle(title));\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean showSingleTabHeader() {\r\n\t\tint nbTabs = getTabs().count();\r\n\t\t\r\n\t\tif (nbTabs == 1) {\r\n\t\t\tFileTableTab tab = getTab(0);\r\n\t\t\t\r\n\t\t\t// If there's just single tab that is locked don't remove his header\r\n\t\t\tif (tab.isLocked())\r\n\t\t\t\treturn true;\r\n\t\t}\r\n\t\t\r\n\t\treturn super.showSingleTabHeader();\r\n\t}\r\n\t\r\n\t@Override\r\n\tprotected FileTableTab removeTab() {\r\n\t\treturn !getCurrentTab().isLocked() ? super.removeTab() : null;\r\n\t}\r\n\t\r\n\t/********************\r\n\t * MuActions support\r\n\t ********************/\r\n\r\n\tpublic void add(AbstractFile file) {\r\n\t\taddTab(defaultTabsFactory.createTab(file.getURL()));\r\n\t}\r\n\t\r\n\tpublic void add(FileTableTab tab) {\r\n\t\taddAndSelectTab(tab);\r\n\t}\r\n\r\n\tpublic void add(FileURL fileURL) {\r\n\t\taddTab(defaultTabsFactory.createTab(fileURL));\r\n\t}\r\n\t\r\n\tpublic FileTableTab closeCurrentTab() {\r\n\t\treturn removeTab();\r\n\t}\r\n\t\r\n\tpublic void closeDuplicateTabs() {\r\n\t\tremoveDuplicateTabs();\r\n\t}\r\n\t\r\n\tpublic void closeOtherTabs() {\r\n\t\tremoveOtherTabs();\r\n\t}\r\n\t\r\n\tpublic void duplicate() {\r\n\t\tadd(clonedTabsFactory.createTab(getCurrentTab()));\r\n\t}\r\n\t\r\n\tpublic void lock() {\r\n\t\tupdateTabLocking(true);\r\n\t}\r\n\t\r\n\tpublic void unlock() {\r\n\t\tupdateTabLocking(false);\r\n\t}\r\n\r\n\tpublic void setTitle(String title) {\r\n\t\tupdateTabTitle(title);\r\n\t}\r\n\r\n\t/****************\r\n\t * Other Actions\r\n\t ****************/\r\n\r\n\tpublic void close(FileTableTabHeader fileTableTabHeader) {\r\n\t\tremoveTab(fileTableTabHeader);\r\n\t}\r\n\t\r\n\r\n\t@Override\r\n\tpublic void locationChanged(LocationEvent locationEvent) {\r\n\t\tAbstractFile folder = folderPanel.getCurrentFolder();\r\n\t\tif (folder != null) {\r\n\t\t\tupdateTabLocation(folder.getURL());\r\n\t\t}\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void locationCancelled(LocationEvent locationEvent) {\r\n\t\tAbstractFile folder = folderPanel.getCurrentFolder();\r\n\t\tif (folder != null) {\r\n\t\t\tupdateTabLocation(folder.getURL());\r\n\t\t}\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void locationFailed(LocationEvent locationEvent) {\r\n\t\tAbstractFile folder = folderPanel.getCurrentFolder();\r\n\t\tif (folder != null) {\r\n\t\t\tupdateTabLocation(folder.getURL());\r\n\t\t}\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void locationChanging(LocationEvent locationEvent) { }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tabs/FileTableTabsWithHeadersViewerFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.tabs;\n\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.tabs.TabsCollection;\nimport com.mucommander.ui.tabs.TabsViewer;\nimport com.mucommander.ui.tabs.TabsViewerFactory;\nimport com.mucommander.ui.tabs.TabsWithHeaderViewer;\n\n/**\n* Factory that creates viewers presenting tabs with headers\n* \n* @author Arik Hadas\n*/\npublic class FileTableTabsWithHeadersViewerFactory implements TabsViewerFactory<FileTableTab> {\n\tprivate final FolderPanel folderPanel;\n\tprivate final MainFrame mainFrame;\n\t\n\tpublic FileTableTabsWithHeadersViewerFactory(MainFrame mainFrame, FolderPanel folderPanel) {\n\t\tthis.folderPanel = folderPanel;\n\t\tthis.mainFrame = mainFrame;\n\t}\n\n\t@Override\n\tpublic TabsViewer<FileTableTab> create(TabsCollection<FileTableTab> tabs) {\n\t\tFileTableTabHeaderFactory headersFactory = tabs.count() == 1 ? new NotClosableFileTableTabHeaderFactory(folderPanel) : new DefaultFileTableTabHeaderFactory(folderPanel);\n\t\treturn new TabsWithHeaderViewer<>(tabs, new FileTableTabbedPane(mainFrame, folderPanel, folderPanel.getFileTable().getAsUIComponent(), headersFactory));\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tabs/FileTableTabsWithoutHeadersViewerFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.tabs;\n\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.tabs.TabWithoutHeaderViewer;\nimport com.mucommander.ui.tabs.TabsCollection;\nimport com.mucommander.ui.tabs.TabsViewer;\nimport com.mucommander.ui.tabs.TabsViewerFactory;\n\n/**\n* Factory that creates viewers presenting tabs with no header\n* \n* @author Arik Hadas\n*/\npublic class FileTableTabsWithoutHeadersViewerFactory implements TabsViewerFactory<FileTableTab> {\n\n\tprivate final FolderPanel folderPanel;\n\t\n\tFileTableTabsWithoutHeadersViewerFactory(FolderPanel folderPanel) {\n\t\tthis.folderPanel = folderPanel;\n\t}\n\n\t/***********************************\n\t * TabsViewerFactory Implementation\n\t ***********************************/\n\n\tpublic TabsViewer<FileTableTab> create(TabsCollection<FileTableTab> tabs) {\n\t\treturn new TabWithoutHeaderViewer<>(tabs, folderPanel.getFileTable().getAsUIComponent());\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tabs/NotClosableFileTableTabHeaderFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.tabs;\n\nimport com.mucommander.ui.main.FolderPanel;\n\n/**\n * \n * @author Arik Hadas\n */\npublic class NotClosableFileTableTabHeaderFactory extends FileTableTabHeaderFactory {\n\n\tNotClosableFileTableTabHeaderFactory(FolderPanel folderPanel) {\n\t\tsuper(folderPanel);\n\t}\n\t\n\t@Override\n\tpublic FileTableTabHeader create(FileTableTab tab) {\n\t\treturn new FileTableTabHeader(folderPanel, false, tab);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tabs/PrintableFileTableTabFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.tabs;\n\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.core.LocalLocationHistory;\nimport com.mucommander.ui.tabs.TabFactory;\n\n/**\n * Factory for creating {@link com.mucommander.ui.main.tabs.FileTableTab} for presentation in {@link com.mucommander.ui.main.quicklist.TabsQL}\n * \n * @author Arik Hadas\n */\npublic class PrintableFileTableTabFactory implements TabFactory<FileTableTab, FileTableTab> {\n\n\tpublic FileTableTab createTab(FileTableTab tab) {\n\t\treturn new PrintableFileTableTab(tab);\n\t}\n\n\t/**\n\t * Implementation of the Decorator design pattern which is used to modify the way FileTableTab\n\t * is presented (by overriding its toString method) and the way it's compared to \n\t * FileTableTabFactory.DefaultFileTableTab instances (by overriding its equals method)\n\t */\n\tprivate static class PrintableFileTableTab extends FileTableTab {\n\n\t\tprivate final FileTableTab tab;\n\t\t\n\t\tprivate PrintableFileTableTab(FileTableTab tab) {\n\t\t\tthis.tab = tab;\n\t\t}\n\n\t\t@Override\n\t\tpublic void setLocation(FileURL location) {\n\t\t\ttab.setLocation(location);\n\t\t}\n\n\t\t@Override\n\t\tpublic FileURL getLocation() {\n\t\t\treturn tab.getLocation();\n\t\t}\n\n\t\t@Override\n\t\tpublic void setLocked(boolean locked) {\n\t\t\ttab.setLocked(locked);\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean isLocked() {\n\t\t\treturn tab.isLocked();\n\t\t}\n\n\t\t@Override\n\t\tpublic void setTitle(String title) {\n\t\t\ttab.setTitle(title);\n\t\t}\n\n\t\t@Override\n\t\tpublic String getTitle() {\n\t\t\treturn tab.getTitle();\n\t\t}\n\n\t\t@Override\n\t\tpublic LocalLocationHistory getLocationHistory() {\n\t\t\treturn tab.getLocationHistory();\n\t\t}\n\t\t\n\t\t@Override\n\t\tpublic boolean equals(Object obj) {\n\t\t\treturn tab == obj;\n\t\t}\n\t\t\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn getDisplayableTitle();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/toolbar/ToolBar.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.toolbar;\r\n\r\nimport java.awt.Component;\r\nimport java.awt.Dimension;\r\nimport java.awt.Insets;\r\nimport java.awt.event.MouseEvent;\r\nimport java.awt.event.MouseListener;\r\nimport java.util.HashMap;\r\n\r\nimport javax.swing.Action;\r\nimport javax.swing.ImageIcon;\r\nimport javax.swing.JButton;\r\nimport javax.swing.JPopupMenu;\r\nimport javax.swing.JToolBar;\r\n\r\nimport com.mucommander.commons.conf.ConfigurationEvent;\r\nimport com.mucommander.commons.conf.ConfigurationListener;\r\nimport com.mucommander.commons.file.FileURL;\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.commons.runtime.OsVersion;\r\nimport com.mucommander.conf.TcConfigurations;\r\nimport com.mucommander.conf.TcPreference;\r\nimport com.mucommander.conf.TcPreferences;\r\nimport com.mucommander.core.LocalLocationHistory;\r\nimport com.mucommander.desktop.DesktopManager;\r\nimport com.mucommander.ui.action.ActionManager;\r\nimport com.mucommander.ui.action.TcAction;\r\nimport com.mucommander.ui.action.impl.GoBackAction;\r\nimport com.mucommander.ui.action.impl.GoForwardAction;\r\nimport com.mucommander.ui.action.impl.OpenLocationAction;\r\nimport com.mucommander.ui.action.impl.ToggleToolBarAction;\r\nimport com.mucommander.ui.button.NonFocusableButton;\r\nimport com.mucommander.ui.button.PopupButton;\r\nimport com.mucommander.ui.button.RolloverButtonAdapter;\r\nimport com.mucommander.ui.icon.IconManager;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport ru.trolsoft.macosx.RetinaImageIcon;\r\n\r\n/**\r\n * This class is the icon toolbar attached to a MainFrame, triggering events when buttons are clicked.\r\n *\r\n * @author Maxence Bernard, Arik Hadas\r\n */\r\npublic class ToolBar extends JToolBar implements ConfigurationListener, MouseListener, ToolBarAttributesListener {\r\n\r\n    private final MainFrame mainFrame;\r\n\r\n    /** Dimension of button separators */\r\n    private final static Dimension SEPARATOR_DIMENSION = new Dimension(10, 16);\r\n\r\n    /** Whether to use the new JButton decorations introduced in Mac OS X 10.5 (Leopard) */\r\n    private final static boolean USE_MAC_OS_X_CLIENT_PROPERTIES =\r\n    \t\tOsFamily.MAC_OS_X.isCurrent() &&\r\n            OsVersion.MAC_OS_X_10_5.isCurrentOrHigher();\r\n\r\n    /** Current icon scale value */\r\n    // The math.max(1.0f, ...) part is to workaround a bug which cause(d) this value to be set to 0.0 in the configuration file.\r\n    private static float scaleFactor = Math.max(1.0f, TcConfigurations.getPreferences().getVariable(TcPreference.TOOLBAR_ICON_SCALE,\r\n                                                                        TcPreferences.DEFAULT_TOOLBAR_ICON_SCALE));\r\n\r\n\r\n    /**\r\n     * Creates a new toolbar and attaches it to the given frame.\r\n     */\r\n    public ToolBar(MainFrame mainFrame) {\r\n        this.mainFrame = mainFrame;\r\n\r\n        // Decoration properties\r\n        setBorderPainted(false);\r\n        setFloatable(false);\r\n        putClientProperty(\"JToolBar.isRollover\", Boolean.TRUE);\r\n\r\n        // Listen to mouse events in order to popup a menu when toolbar is right-clicked\r\n        addMouseListener(this);\r\n\r\n        // Listen to configuration changes to reload toolbar buttons when icon size has changed\r\n        TcConfigurations.addPreferencesListener(this);\r\n\r\n        // create buttons for each action and add them to the toolbar\r\n        addButtons(ToolBarAttributes.getActions());\r\n        \r\n        ToolBarAttributes.addToolBarAttributesListener(this);\r\n    }\r\n    \r\n    private void addButtons(String[] actionIds) {\r\n        for (String actionId : actionIds) {\r\n            if (actionId == null) {\r\n                addSeparator(SEPARATOR_DIMENSION);\r\n            } else {\r\n                // Get a MuAction instance\r\n                TcAction action = ActionManager.getActionInstance(actionId, mainFrame);\r\n                // Do not add buttons for actions that do not have an icon\r\n                if (action != null && action.getIcon() != null) {\r\n                    addButton(action);\r\n                }\r\n            }\r\n        }\r\n\r\n        if (USE_MAC_OS_X_CLIENT_PROPERTIES) {\r\n            int nbComponents = getComponentCount();\r\n\r\n            // Set the 'segment position' required for the 'segmented capsule' style  \r\n            for( int i = 0; i < nbComponents; i++) {\r\n                Component comp = getComponent(i);\r\n                if (!(comp instanceof JButton)) {\r\n                    continue;\r\n                }\r\n\r\n                boolean hasPrevious = i != 0 && (getComponent(i-1) instanceof JButton);\r\n                boolean hasNext = i != nbComponents-1 && (getComponent(i+1) instanceof JButton);\r\n\r\n                String segmentPosition;\r\n                if (hasPrevious && hasNext) {\r\n                    segmentPosition = \"middle\";\r\n                } else if (hasPrevious) {\r\n                    segmentPosition = \"last\";\r\n                } else if (hasNext) {\r\n                    segmentPosition = \"first\";\r\n                } else {\r\n                    segmentPosition = \"only\";\r\n                }\r\n\r\n                ((JButton)comp).putClientProperty(\"JButton.segmentPosition\", segmentPosition);\r\n             }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Adds a button to this toolbar using the given action.\r\n     */\r\n    private void addButton(TcAction action) {\r\n        JButton button;\r\n\r\n        if (action instanceof GoBackAction || action instanceof GoForwardAction) {\r\n            button = new HistoryPopupButton(action);\r\n        } else {\r\n            button = new NonFocusableButton(action);\r\n        }\r\n\r\n        // Remove label\r\n        button.setText(null);\r\n\r\n        // Add tooltip using the action's label and accelerator\r\n        String toolTipText = action.getLabel();\r\n        String acceleratorText = action.getAcceleratorText();\r\n        if (acceleratorText != null) {\r\n            toolTipText += \" (\" + acceleratorText + \")\";\r\n        }\r\n        button.setToolTipText(toolTipText);\r\n\r\n        // Sets the button icon, taking into account the icon scale factor\r\n        setButtonIcon(button);\r\n\r\n        if (USE_MAC_OS_X_CLIENT_PROPERTIES) {\r\n            if (button.getIcon() == null || button.getIcon().getIconHeight() <= 16) {\r\n                button.putClientProperty(\"JButton.buttonType\", \"segmentedTextured\");\r\n            }\r\n            button.setRolloverEnabled(true);\r\n        } else {\r\n            RolloverButtonAdapter.decorateButton(button);\r\n        }\r\n        add(button);\r\n    }\r\n\r\n    /**\r\n     * Sets the specified button's icon to the proper scale.\r\n     *\r\n     * @param button the button to update\r\n     */\r\n    private void setButtonIcon(JButton button) {\r\n        // Note: the action's icon must not be changed and remain in its original, non-scaled size\r\n        ImageIcon icon = IconManager.getScaledIcon((ImageIcon)button.getAction().getValue(Action.SMALL_ICON), scaleFactor);\r\n\r\n        if (!USE_MAC_OS_X_CLIENT_PROPERTIES) {    // Add padding around the icon so the button feels less crowded\r\n            icon = IconManager.getPaddedIcon(icon, new Insets(3, 4, 3, 4));\r\n        }\r\n\r\n        button.setIcon(icon);\r\n        if (icon instanceof RetinaImageIcon) {\r\n            button.setDisabledIcon(((RetinaImageIcon)icon).buildDisabledIcon());\r\n            button.setPressedIcon(icon);\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Listens to certain configuration variables.\r\n     */\r\n    @Override\r\n    public void configurationChanged(ConfigurationEvent event) {\r\n        String var = event.getVariable();\r\n\r\n        // Rescale buttons icon\r\n        if (var.equals(TcPreferences.TOOLBAR_ICON_SCALE)) {\r\n            scaleFactor = event.getFloatValue();\r\n            Component[] components = getComponents();\r\n\r\n            for (Component component : components) {\r\n                if (component instanceof JButton) {\r\n                    setButtonIcon((JButton) component);\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n    @Override\r\n    public void mouseClicked(MouseEvent e) {\r\n        Object source = e.getSource();\r\n\r\n        // Right-clicking on the toolbar brings up a popup menu\r\n        if (source == this) {\r\n            if (DesktopManager.isRightMouseButton(e)) {\r\n                //\t\t\tif (e.isPopupTrigger()) {\t// Doesn't work under Mac OS X (CTRL+click doesn't return true)\r\n                JPopupMenu popupMenu = new JPopupMenu();\r\n                popupMenu.add(ActionManager.getActionInstance(ToggleToolBarAction.Descriptor.ACTION_ID, mainFrame));\r\n                popupMenu.show(this, e.getX(), e.getY());\r\n                popupMenu.setVisible(true);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void mouseEntered(MouseEvent e) {\r\n        Object source = e.getSource();\r\n        if (source instanceof JButton) {\r\n            ((JButton) source).setBorderPainted(true);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void mouseExited(MouseEvent e) {\r\n        Object source = e.getSource();\r\n        if (source instanceof JButton) {\r\n            ((JButton) source).setBorderPainted(false);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void mouseReleased(MouseEvent e) {\r\n    }\r\n\r\n    @Override\r\n    public void mousePressed(MouseEvent e) {\r\n    }\r\n\r\n\r\n    @Override\r\n    public void toolBarActionsChanged() {\r\n\t\tremoveAll();\r\n\t\taddButtons(ToolBarAttributes.getActions());\r\n\t}\r\n\r\n    /**\r\n     * PopupButton used for 'Go back' and 'Go forward' actions which displays the list of back/forward folders in the\r\n     * popup menu and allows to recall them by clicking on them.\r\n     */\r\n    private class HistoryPopupButton extends PopupButton {\r\n\r\n        private final TcAction action;\r\n\r\n        private HistoryPopupButton(TcAction action) {\r\n            super(action);\r\n            this.action = action;\r\n        }\r\n\r\n        @Override\r\n        public JPopupMenu getPopupMenu() {\r\n            LocalLocationHistory locationHistory = mainFrame.getActivePanel().getFolderHistory();\r\n            FileURL[] history = action instanceof GoBackAction ? locationHistory.getBackFolders() : locationHistory.getForwardFolders();\r\n\r\n            // If no back/forward folder, do not display popup menu\r\n            if (history.length == 0) {\r\n                return null;\r\n            }\r\n\r\n            JPopupMenu popupMenu = new JPopupMenu();\r\n            for (FileURL url : history) {\r\n                popupMenu.add(new OpenLocationAction(mainFrame, new HashMap<>(), url));\r\n            }\r\n\r\n            return popupMenu;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/toolbar/ToolBarAttributes.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.toolbar;\r\n\r\nimport java.util.Map;\r\nimport java.util.WeakHashMap;\r\n\r\nimport com.mucommander.ui.action.impl.*;\r\n\r\n/**\r\n * This class is responsible to handle the attributes of ToolBars - their actions and separators.\r\n * Every ToolBar should get its attributes from this class, and register in it for receiving attributes modifications.\r\n * \r\n * @author Arik Hadas\r\n */\r\npublic class ToolBarAttributes {\r\n\t\r\n\t/** Command bar actions: Class instances or null to signify a separator */\r\n    private static String[] actionIds;\r\n    \r\n    private static boolean useDefaultActions = true;\r\n    \r\n    /** Contains all registered toolbar-attributes listeners, stored as weak references */\r\n    private static final Map<ToolBarAttributesListener, ?> listeners = new WeakHashMap<>();\r\n    \r\n    /** Default command bar actions: Class instances or null to signify a separator */\r\n    private final static String[] DEFAULT_TOOLBAR_ACTIONS = new String[] {\r\n            NewWindowAction.Descriptor.ACTION_ID,\r\n            AddTabAction.Descriptor.ACTION_ID,\r\n            null,\r\n            GoBackAction.Descriptor.ACTION_ID,\r\n            GoForwardAction.Descriptor.ACTION_ID,\r\n            null,\r\n            GoToParentAction.Descriptor.ACTION_ID,\r\n            GoToHomeAction.Descriptor.ACTION_ID,\r\n            null,\r\n            StopAction.Descriptor.ACTION_ID,\r\n            null,\r\n            MarkGroupAction.Descriptor.ACTION_ID,\r\n            UnmarkGroupAction.Descriptor.ACTION_ID,\r\n            null,\r\n            SwapFoldersAction.Descriptor.ACTION_ID,\r\n            SetSameFolderAction.Descriptor.ACTION_ID,\r\n            null,\r\n            PackAction.Descriptor.ACTION_ID,\r\n            UnpackAction.Descriptor.ACTION_ID,\r\n            null,\r\n            AddBookmarkAction.Descriptor.ACTION_ID,\r\n            EditBookmarksAction.Descriptor.ACTION_ID,\r\n            EditCredentialsAction.Descriptor.ACTION_ID,\r\n            null,\r\n            FindFileAction.Descriptor.ACTION_ID,\r\n            null,\r\n            ConnectToServerAction.Descriptor.ACTION_ID,\r\n            ShowServerConnectionsAction.Descriptor.ACTION_ID,\r\n            RunCommandAction.Descriptor.ACTION_ID,\r\n            EmailAction.Descriptor.ACTION_ID,\r\n            null,\r\n            RevealInDesktopAction.Descriptor.ACTION_ID,\r\n            ShowFilePropertiesAction.Descriptor.ACTION_ID,\r\n            null,\r\n            ShowPreferencesAction.Descriptor.ACTION_ID\r\n    };\r\n\r\n    /**\r\n     * Removes leading and trailing separators (<code>null</code> elements) from the given action Class array, and\r\n     * returns the trimmed action array.\r\n     *\r\n     * @param actions the action Class array to trim.\r\n     * @return the trimmed action Class array, free of leading and trailing separators.\r\n     */\r\n    private static String[] trimActionsArray(String[] actions) {\r\n        int start = 0;\r\n        int end = actions.length;\r\n\r\n        while (start < end && actions[start] == null) {\r\n            start++;\r\n        }\r\n\r\n        if (start == end) {\r\n            return new String[]{};\r\n        }\r\n\r\n        while (end > start && actions[end-1] == null) {\r\n            end--;\r\n        }\r\n\r\n        int newLen = end-start;\r\n        String[] newActions = new String[newLen];\r\n        System.arraycopy(actions, start, newActions, 0, newLen);\r\n\r\n        return newActions;\r\n    }\r\n\r\n    /**\r\n     * Sets the toolbar actions to the given action classes. <code>null</code> elements are used to insert a separator\r\n     * between buttons.\r\n     *\r\n     * @param actions the new toolbar actions classes\r\n     */\r\n    public static void setActions(String[] actions) {\r\n        ToolBarAttributes.actionIds = trimActionsArray(actions);\r\n    \tuseDefaultActions = false;\r\n    \tfireActionsChanged();\r\n    }\r\n    \r\n    /**\r\n     * Check whether the default attributes are used.\r\n     * \r\n     * @return true if the default attributes are used, false otherwise.\r\n     */\r\n    static boolean areDefaultAttributes() {\r\n    \tif (useDefaultActions) {\r\n    \t\treturn true;\r\n        }\r\n    \tint nbActions = actionIds.length;\r\n    \tif (nbActions != DEFAULT_TOOLBAR_ACTIONS.length) {\r\n    \t\treturn false;\r\n        }\r\n    \t\r\n    \tfor (int i = 0; i < nbActions; ++i) {\r\n            if (!equals(actionIds[i], DEFAULT_TOOLBAR_ACTIONS[i])) {\r\n    \t\t\treturn false;\r\n            }\r\n        }\r\n    \t\r\n    \treturn true;\r\n    }\r\n    \r\n    private static boolean equals(Object action1, Object action2) {\r\n    \tif (action1 == null) {\r\n    \t\treturn action2 == null;\r\n        }\r\n    \treturn action1.equals(action2);\r\n    }\r\n    \r\n    /**\r\n     * Returns the actions classes that constitute the toolbar. <code>null</code> elements are used to insert a separator\r\n     * between buttons.\r\n     *\r\n     * @return the actions classes that constitute the toolbar.\r\n     */\r\n    public static String[] getActions() {\r\n    \treturn useDefaultActions ? DEFAULT_TOOLBAR_ACTIONS : actionIds;\r\n    }\r\n    \r\n    // - Listeners -------------------------------------------------------------\r\n    // -------------------------------------------------------------------------\r\n    static void addToolBarAttributesListener(ToolBarAttributesListener listener) {\r\n    \tsynchronized(listeners) {listeners.put(listener, null);}\r\n    }\r\n    \r\n    public static void removeToolBarAttributesListener(ToolBarAttributesListener listener) {\r\n    \tsynchronized(listeners) {\r\n    \t    listeners.remove(listener);\r\n    \t}\r\n    }\r\n    \r\n    private static void fireActionsChanged() {\r\n    \tsynchronized(listeners) {\r\n            for (ToolBarAttributesListener listener : listeners.keySet())\r\n                listener.toolBarActionsChanged();\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/toolbar/ToolBarAttributesListener.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.toolbar;\r\n\r\n/**\r\n * This is an interface that each class that should listen to ToolBar's attributes modifications need to implement.\r\n * \r\n * @author Arik Hadas\r\n */\r\npublic interface ToolBarAttributesListener {\r\n\t/**\r\n     * This method is invoked when toolbar's actions have been modified.\r\n     */\r\n    void toolBarActionsChanged();\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/toolbar/ToolBarIO.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.toolbar;\r\n\r\nimport com.mucommander.PlatformManager;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileFactory;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.xml.sax.helpers.DefaultHandler;\r\n\r\nimport java.io.File;\r\nimport java.io.FileNotFoundException;\r\nimport java.io.IOException;\r\n\r\n/**\r\n * \r\n * @author Arik Hadas\r\n */\r\npublic abstract class ToolBarIO extends DefaultHandler {\r\n\tprivate static Logger logger;\r\n\t\r\n\t/* Variables used for XML parsing */\r\n\t/** Root element */\r\n\tprotected static final String ROOT_ELEMENT = \"toolbar\";\r\n\t/** Attribute containing the last muCommander version that was used to create the file */\r\n\tprotected static final String VERSION_ATTRIBUTE  = \"version\";\r\n    /** Element describing one of the button in the list */\r\n\tstatic final String BUTTON_ELEMENT = \"button\";\r\n    /** Attribute containing the action class associated with the button */\r\n\tstatic final String ACTION_ATTRIBUTE  = \"action\";\r\n\t/** Attribute containing the action id associated with the button */\r\n\tstatic final String ACTION_ID_ATTRIBUTE  = \"action_id\";\r\n    /** Element describing one of the separator in the list */\r\n\tstatic final String SEPARATOR_ELEMENT = \"separator\";\r\n\r\n\t/** Default toolbar descriptor filename */\r\n    final static String DEFAULT_TOOLBAR_FILE_NAME = \"toolbar.xml\";\r\n\r\n    /** Toolbar descriptor file used when calling {@link #loadDescriptionFile()} */\r\n    private static AbstractFile descriptionFile;\r\n    \r\n    /** ToolBarWriter instance */\r\n\tprivate static ToolBarWriter toolBarWriter;\r\n    \r\n    /** Whether the command-bar has been modified and should be saved */\r\n    static boolean wasToolBarModified;\r\n    \r\n    /**\r\n     * Parses the XML file describing the toolbar's buttons and associated actions.\r\n     * If the file doesn't exist, default toolbar elements will be used.\r\n     */\r\n    public static void loadDescriptionFile() throws Exception {\r\n    \tAbstractFile descriptionFile = getDescriptionFile();\r\n        if (descriptionFile != null && descriptionFile.exists()) {\r\n        \tToolBarReader reader = new ToolBarReader(descriptionFile);\r\n        \tToolBarAttributes.setActions(reader.getActionsRead());\r\n        } else {\r\n            getLogger().debug(\"User toolbar.xml was not found, using default toolbar\");\r\n        }\r\n        \r\n        toolBarWriter = ToolBarWriter.create();\r\n    }\r\n    \r\n    /**\r\n     * Writes the current tool bar to the user's toolbar file.\r\n     */\r\n    public static void saveToolBar() throws IOException {\r\n    \tif (ToolBarAttributes.areDefaultAttributes()) {\r\n    \t\tAbstractFile toolBarFile = getDescriptionFile();\r\n    \t\tif (toolBarFile != null && toolBarFile.exists()) {\r\n                getLogger().info(\"Toolbar use default settings, removing descriptor file\");\r\n    \t\t\ttoolBarFile.delete();\r\n    \t\t} else {\r\n                getLogger().debug(\"Toolbar not modified, not saving\");\r\n            }\r\n    \t} else if (toolBarWriter != null) {\r\n    \t\tif (wasToolBarModified) {\r\n                toolBarWriter.write();\r\n            } else {\r\n                getLogger().debug(\"Toolbar not modified, not saving\");\r\n            }\r\n    \t} else {\r\n            getLogger().warn(\"Could not save toolbar. writer is null\");\r\n        }\r\n    }\r\n    \r\n    /**\r\n     * Mark that actions were modified and therefore should be saved.\r\n     */\r\n    static void setModified() {\r\n        wasToolBarModified = true;\r\n    }\r\n    \r\n    /**\r\n     * Sets the path to the toolbar description file to be loaded when calling {@link #loadDescriptionFile()}.\r\n     * By default, this file is {@link #DEFAULT_TOOLBAR_FILE_NAME} within the preferences folder.\r\n     * @param file path to the toolbar descriptor file\r\n     */\r\n    private static void setDescriptionFile(AbstractFile file) throws FileNotFoundException {\r\n        if (file.isBrowsable()) {\r\n            throw new FileNotFoundException(\"Not a valid file: \" + file);\r\n        }\r\n        descriptionFile = file;\r\n    }\r\n\r\n    static AbstractFile getDescriptionFile() throws IOException {\r\n        if (descriptionFile == null) {\r\n            return PlatformManager.getPreferencesFolder().getChild(DEFAULT_TOOLBAR_FILE_NAME);\r\n        }\r\n        return descriptionFile;\r\n    }\r\n    \r\n    /**\r\n     * Sets the path to the toolbar description file to be loaded when calling {@link #loadDescriptionFile()}.\r\n     * By default, this file is {@link #DEFAULT_TOOLBAR_FILE_NAME} within the preferences folder.\r\n     * @param path path to the toolbar descriptor file\r\n     */\r\n    public static void setDescriptionFile(String path) throws FileNotFoundException {\r\n        AbstractFile file = FileFactory.getFile(path);\r\n\r\n        if (file == null) {\r\n            setDescriptionFile(new File(path));\r\n        } else {\r\n            setDescriptionFile(file);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Sets the path to the toolbar description file to be loaded when calling {@link #loadDescriptionFile()}.\r\n     * By default, this file is {@link #DEFAULT_TOOLBAR_FILE_NAME} within the preferences folder.\r\n     * @param file path to the toolbar descriptor file\r\n     */\r\n    private static void setDescriptionFile(File file) throws FileNotFoundException {\r\n        AbstractFile descriptionFile = FileFactory.getFile(file.getAbsolutePath());\r\n        if (descriptionFile != null) {\r\n            setDescriptionFile(descriptionFile);\r\n        }\r\n    }\r\n\r\n\r\n    private static Logger getLogger() {\r\n        if (logger == null) {\r\n            logger = LoggerFactory.getLogger(ToolBarIO.class);\r\n        }\r\n        return logger;\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/toolbar/ToolBarReader.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.toolbar;\r\n\r\nimport com.mucommander.RuntimeConstants;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.io.backup.BackupInputStream;\r\nimport com.mucommander.ui.action.ActionManager;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.xml.sax.Attributes;\r\n\r\nimport javax.xml.parsers.SAXParserFactory;\r\nimport java.io.InputStream;\r\nimport java.util.List;\r\nimport java.util.Vector;\r\n\r\n/**\r\n * This class parses the XML file describing the toolbar's buttons and associated actions.\r\n *\r\n * @author Maxence Bernard, Arik Hadas\r\n */\r\npublic class ToolBarReader extends ToolBarIO {\r\n\tprivate static Logger logger;\r\n\r\n    /** Temporarily used for XML parsing */\r\n    private List<String> actionIdsV;\r\n\r\n    /**\r\n     * Starts parsing the XML description file.\r\n     */\r\n    ToolBarReader(AbstractFile descriptionFile) throws Exception {\r\n        try (InputStream in = new BackupInputStream(descriptionFile)) {\r\n            SAXParserFactory.newInstance().newSAXParser().parse(in, this);\r\n        }\r\n    }\r\n    \r\n    String[] getActionsRead() {\r\n    \tint nbActions = actionIdsV.size();\r\n    \tString[] actionIds = new String[nbActions];\r\n        actionIdsV.toArray(actionIds);\r\n        return actionIds;\r\n    }\r\n\r\n    @Override\r\n    public void startDocument() {\r\n        actionIdsV = new Vector<>();\r\n    }\r\n\r\n    @Override\r\n    public void endDocument() {}\r\n\r\n    @Override\r\n    public void startElement(String uri, String localName, String qName, Attributes attributes) {\r\n        switch (qName) {\r\n            case BUTTON_ELEMENT:\r\n                // Resolve action id\r\n                String actionIdAttribute = attributes.getValue(ACTION_ID_ATTRIBUTE);\r\n                if (actionIdAttribute != null) {\r\n                    if (ActionManager.isActionExist(actionIdAttribute))\r\n                        actionIdsV.add(actionIdAttribute);\r\n                    else\r\n                        getLogger().warn(\"Error in \" + DEFAULT_TOOLBAR_FILE_NAME + \": action id \\\"\" + actionIdAttribute + \"\\\" not found\");\r\n                } else {\r\n                    // Resolve action class\r\n                    String actionClassAttribute = attributes.getValue(ACTION_ATTRIBUTE);\r\n                    String actionId = ActionManager.extrapolateId(actionClassAttribute);\r\n                    if (ActionManager.isActionExist(actionId)) {\r\n                        actionIdsV.add(actionId);\r\n                    } else {\r\n                        getLogger().warn(\"Error in \" + DEFAULT_TOOLBAR_FILE_NAME + \": action id for class \" + actionClassAttribute + \" was not found\");\r\n                    }\r\n                }\r\n                break;\r\n            case SEPARATOR_ELEMENT:\r\n                actionIdsV.add(null);\r\n                break;\r\n            case ROOT_ELEMENT:\r\n                // Note: early 0.8 beta3 nightly builds did not have version attribute, so the attribute may be null\r\n                String fileVersion = attributes.getValue(VERSION_ATTRIBUTE);\r\n\r\n                // if the file's version is not up-to-date, update the file to the current version at quitting.\r\n                if (!RuntimeConstants.VERSION.equals(fileVersion)) {\r\n                    setModified();\r\n                }\r\n                break;\r\n        }\r\n    }\r\n\r\n    private static Logger getLogger() {\r\n        if (logger == null) {\r\n            logger = LoggerFactory.getLogger(ToolBarReader.class);\r\n        }\r\n        return logger;\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/toolbar/ToolBarWriter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.toolbar;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.RuntimeConstants;\nimport com.mucommander.io.backup.BackupOutputStream;\nimport com.mucommander.utils.xml.XmlAttributes;\nimport com.mucommander.utils.xml.XmlWriter;\n\n/**\n * This class is responsible for writing the tool-bar attributes (buttons and separators).\n * \n * @author Arik Hadas\n */\npublic class ToolBarWriter extends ToolBarIO {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(ToolBarWriter.class);\n\t\n\tprivate static ToolBarWriter instance;\n\t\n\tpublic static ToolBarWriter create() {\n\t\tif (instance == null) {\n\t\t\tinstance = new ToolBarWriter();\n\t\t}\n\t\treturn instance;\n\t}\n\t\n\tprivate ToolBarWriter() {}\n\t\n\tvoid write() {\n\t\tString[] actionIds = ToolBarAttributes.getActions();\n\n\t\ttry (BackupOutputStream bos = new BackupOutputStream(getDescriptionFile())) {\n\t\t\tnew Writer(bos).write(actionIds);\n\t\t\twasToolBarModified = false;\n\t\t} catch (Exception e) {\n\t\t\tLOGGER.debug(\"Caught exception\", e);\n\t\t}\n\t}\n\t\n\tprivate static class Writer {\n\t\tprivate final XmlWriter writer;\n\t\t\n\t\tprivate Writer(OutputStream stream) throws IOException {\n    \t\tthis.writer = new XmlWriter(stream);\n    \t}\n\t\t\n\t\tprivate void write(String[] actionIds) throws IOException {\n\t\t\ttry {\n\t\t\t\twriter.writeCommentLine(\"See http://trac.mucommander.com/wiki/ToolBar for information on how to customize this file\");\n\t\t\t\t\n\t\t\t\tXmlAttributes rootElementAttributes = new XmlAttributes();\n\t\t\t\trootElementAttributes.add(VERSION_ATTRIBUTE, RuntimeConstants.VERSION);\n\n    \t\t\twriter.startElement(ROOT_ELEMENT, rootElementAttributes, true);    \t\t\t\n\n\t\t\t\tfor (String actionId : actionIds) {\n\t\t\t\t\twrite(actionId);\n\t\t\t\t}\n\n    \t\t} finally {\n    \t\t\twriter.endElement(ROOT_ELEMENT);\n    \t\t}\n\t\t}\n\t\t\n\t\tprivate void write(String actionId) throws IOException {\n\t\t\tif (actionId == null) {\n\t\t\t\twriter.writeStandAloneElement(SEPARATOR_ELEMENT);\n\t\t\t} else {\n\t\t\t\tXmlAttributes attributes = new XmlAttributes();\n\t\t\t\tattributes.add(ACTION_ID_ATTRIBUTE, actionId);\n\n\t\t\t\t// AppLogger.finest(\"Writing button: action_id = \"  + attributes.getValue(ACTION_ATTRIBUTE_ID) + \", alt_action_id = \" + attributes.getValue(ALT_ACTION_ATTRIBUTE_ID));\n\n\t\t\t\twriter.writeStandAloneElement(BUTTON_ELEMENT, attributes);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tree/AbstractIOThreadManager.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.tree;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.Collections;\r\nimport java.util.List;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\n\r\n/**\r\n * A class that monitors IOThread if it is running or has been blocked.\r\n * This class maintains a list of tasks to execute and a thread that \r\n * executes these tasks. It checks periodically if the IOThread is running.\r\n * If IOThread has been blocked then it's killed and a new IOThread is \r\n * instantiated. Then the next getTask from the list will be executed.\r\n * @author Mariusz Jakubowski\r\n *\r\n */\r\npublic class AbstractIOThreadManager extends Thread {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(AbstractIOThreadManager.class);\r\n\t\r\n    /** a queue with tasks to execute */\r\n    protected final List<Runnable> queue = Collections.synchronizedList(new ArrayList<>());\r\n\r\n    /** a thread that executes tasks */\r\n    protected IOThread ioThread;\r\n    \r\n    /** a time after i/o thread is marked as blocked */\r\n    protected long blockThreshold;\r\n    \r\n\r\n    /**\r\n     * Creates a new monitoring thread.\r\n     * @param name a name of this thread\r\n     * @param blockThreshold a time after an i/o getTask is marked as blocked [ms]\r\n     */\r\n    public AbstractIOThreadManager(String name, long blockThreshold) {\r\n        super(name);\r\n        this.blockThreshold = blockThreshold;\r\n        ioThread = new IOThread(queue, blockThreshold);\r\n        ioThread.start();\r\n    }\r\n\r\n    /**\r\n     * Adds new getTask to execute. A getTask is an instance of Runnable interface.\r\n     * A proper exception handling within the Runnable instance have to be implemented.\r\n     * If this getTask rises an exception, this exception is printed to stderr.\r\n     * @param task a getTask to be executed\r\n     */\r\n    public void addTask(Runnable task) {\r\n        queue.add(task);\r\n        synchronized(ioThread) {\r\n            ioThread.notify();\r\n        }\r\n    }\r\n    \r\n\r\n    \r\n    @Override\r\n    public void run() {\r\n        while (!interrupted()) {\r\n            synchronized (queue) {\r\n                if (ioThread.isBlocked()) {\r\n                \tLOGGER.debug(\"Killing IOThread \" + ioThread);\r\n                    ioThread.interrupt();\r\n                    ioThread = new IOThread(queue, blockThreshold);\r\n                    ioThread.start();\r\n                }\r\n            }\r\n            try {\r\n                sleep(blockThreshold);\r\n            } catch (InterruptedException e) {\r\n                break;\r\n            }\r\n        }\r\n        ioThread.interrupt();\r\n    }\r\n    \r\n    \r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tree/CachedDirectory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.main.tree;\n\nimport java.util.Arrays;\n\nimport javax.swing.Icon;\nimport javax.swing.ImageIcon;\nimport javax.swing.SwingUtilities;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.impl.ProxyFile;\nimport com.mucommander.ui.icon.CustomFileIconProvider;\nimport com.mucommander.ui.icon.FileIcons;\nimport com.mucommander.ui.icon.IconManager;\n\n/**\n * A class that holds cached children of a directory.\n * \n * @author Mariusz Jakubowski\n * \n */\npublic class CachedDirectory extends ProxyFile {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(CachedDirectory.class);\n\t\n    private static final ImageIcon NOT_ACCESSIBLE_ICON = IconManager.getIcon(IconManager.IconSet.FILE, CustomFileIconProvider.NOT_ACCESSIBLE_FILE);\n\n    /** an array of cached children */\n    private AbstractFile[] cachedChildren = null;\n    \n    /** a flag indicating that a thread is running, caching children */\n    private boolean readingChildren = false;\n    \n    /** a timestamp of last modification time of this directory */\n    private long lsTimeStamp = -1;\n    \n    /** a cache in which this object is stored */\n    private final DirectoryCache cache;\n\n    /** a cached icon */\n    private Icon cachedIcon;\n    \n\n    /**\n     * Creates a new instance.\n     * \n     * @param directory a directory to cache\n     */\n    public CachedDirectory(AbstractFile directory, DirectoryCache cache) {\n        super(directory);\n        this.cache = cache;\n    }\n\n    /**\n     * Checks if this directory is already cached. If it isn't cached then a new\n     * cache thread is started.\n     * @return true if directory is cached, false otherwise\n     */\n    public synchronized boolean isCached() {\n        // check if caching thread is running\n        if (isReadingChildren()) {\n            return false;\n        }\n        // check if directory contents changed\n        if (lsTimeStamp != file.getLastModifiedDate()) {\n            setReadingChildren(true);\n            // read children in caching thread\n            TreeIOThreadManager.getInstance().addTask(this::lsAsync);\n            return false;\n        }\n        return true;\n    }\n\n    /**\n     * Gets children of current directory. Files are filtered and then sorted. This\n     * method is executed in caching thread.\n     */\n    private void lsAsync() {\n        if (getCachedIcon() == null || getCachedIcon() == NOT_ACCESSIBLE_ICON) {\n            setCachedIcon(FileIcons.getFileIcon(getProxiedFile()));\n        }\n\n        AbstractFile[] children;\n        try {\n            children = file.ls(cache.getFilter());\n        } catch (Exception e) {\n            LOGGER.debug(\"Caught exception\", e);\n            children = new AbstractFile[0];\n            setCachedIcon(NOT_ACCESSIBLE_ICON);\n        }\n\n        Arrays.sort(children, cache.getSort());\n        Icon[] icons = new Icon[children.length];\n        for (int i = 0; i < children.length; i++) {\n            icons[i] = FileIcons.getFileIcon(children[i]);\n        }\n        synchronized (cache) {\n            for (int i = 0; i < children.length; i++) {\n                CachedDirectory cachedChild = cache.getOrAdd(children[i]);\n                cachedChild.setCachedIcon(icons[i]);\n            }\n        }\n        \n        final AbstractFile[] children2 = children;\n        try {\n            /*\n             * Set cache to new value. This is invoked in swing thread\n             * so event listeners are called from right thread. \n             */\n            SwingUtilities.invokeAndWait(() -> setLsCache(children2, file.getLastModifiedDate()));\n        } catch (Exception e) {\n            LOGGER.debug(\"Caught exception\", e);\n        }\n    }\n\n    /**\n     * Sets cache information.\n     * @param children array of children of this directory\n     * @param lsTimeStamp timestamp of cache\n     */\n    private synchronized void setLsCache(AbstractFile[] children, long lsTimeStamp) {\n        this.lsTimeStamp = lsTimeStamp;\n        this.cachedChildren = children;\n        setReadingChildren(false);\n    }\n\n    /**\n     * Returns true if caching thread is running.\n     */\n    public synchronized boolean isReadingChildren() {\n        return readingChildren;\n    }\n\n    /**\n     * Sets a flag that indicates if caching thread is running. This method also\n     * initializes spinning icon.\n     * @param readingChildren\n     */\n    private synchronized void setReadingChildren(boolean readingChildren) {\n        this.readingChildren = readingChildren;\n        cache.fireChildrenCached(this, readingChildren);\n    }\n\n    /**\n     * Gets cached children.\n     * @return cached children.\n     */\n    public synchronized AbstractFile[] get() {\n        return cachedChildren;\n    }\n    \n    /**\n     * Gets a cached icon for this folder. \n     * @return a cached icon\n     */\n    public Icon getCachedIcon() {\n        return cachedIcon;\n    }\n    \n    /**\n     * Sets a cached icon for this folder.\n     * @param cachedIcon a cached icon\n     */\n    public void setCachedIcon(Icon cachedIcon) {\n        this.cachedIcon = cachedIcon;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tree/CachedDirectoryListener.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.tree;\n\nimport com.mucommander.commons.file.AbstractFile;\n\nimport java.util.EventListener;\n\n/**\n * An interface that listeners to a directory cache must implement.\n * @author Mariusz Jakubowski\n *\n */\npublic interface CachedDirectoryListener extends EventListener {\n   \n    void cachingStarted(AbstractFile parent);\n    \n    void cachingEnded(AbstractFile parent);\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tree/DirectoryCache.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.tree;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.filter.FileFilter;\nimport com.mucommander.commons.file.util.FileComparator;\n\nimport javax.swing.event.EventListenerList;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * This class holds cached directories. \n * It maps AbstractFiles to DirectoryCache instances.\n * @author Mariusz Jakubowski\n *\n */\npublic class DirectoryCache {\n    \n    /** a map that holds cached folders */\n    private final Map<AbstractFile, CachedDirectory> cache = new HashMap<>();\n    \n    /** Comparator used to sort folders */\n    private final FileComparator sort;\n\n    /** A file IMAGE_FILTER */\n    private final FileFilter filter;\n\n    /** Listeners. */\n    private final EventListenerList listenerList = new EventListenerList();\n\n\n    /**\n     * Creates a new directory cache.\n     * @param filter IMAGE_FILTER used to IMAGE_FILTER children directories.\n     * @param sort a comparator used to sort children\n     */\n    DirectoryCache(FileFilter filter, FileComparator sort) {\n        //this.cache = Collections.synchronizedMap(new HashMap());\n        this.filter = filter;\n        this.sort = sort;\n    }\n\n    /**\n     * Returns current sort order.\n     */\n    public FileComparator getSort() {\n        return sort;\n    }\n\n    /**\n     * Returns current IMAGE_FILTER.\n     */\n    public FileFilter getFilter() {\n        return filter;\n    }\n\n    /**\n     * Fires a cachingStarted or cachingEnded event on all listeners.\n     * @param cachedDirectory a directory those children has been cached\n     * @param readingChildren \n     */\n    void fireChildrenCached(CachedDirectory cachedDirectory, boolean readingChildren) {\n        Object[] listeners = listenerList.getListenerList();\n        for (int i = listeners.length - 2; i >= 0; i -= 2) {\n            if (listeners[i] == CachedDirectoryListener.class) {\n                if (readingChildren) {\n                    ((CachedDirectoryListener) listeners[i + 1]).cachingStarted(cachedDirectory);\n                } else {\n                    ((CachedDirectoryListener) listeners[i + 1]).cachingEnded(cachedDirectory);\n                }\n            }\n        }\n    }\n    \n    void addCachedDirectoryListener(CachedDirectoryListener l) {\n        listenerList.add(CachedDirectoryListener.class, l);\n    }\n\n    public void removeCachedDirectoryListener(CachedDirectoryListener l) {\n        listenerList.remove(CachedDirectoryListener.class, l);\n    }\n\n    public synchronized void clear() {\n        cache.clear();\n    }\n\n    public synchronized CachedDirectory get(AbstractFile key) {\n        return cache.get(key);\n    }\n\n    public synchronized void put(AbstractFile key, CachedDirectory value) {\n        cache.put(key, value);\n    }\n    \n    /**\n     * Deletes entry and all children from the cache.\n     */\n    synchronized void removeWithChildren(AbstractFile key) {\n        CachedDirectory cachedDir = cache.get(key);\n        if (cachedDir != null) {\n            cache.remove(key);\n            AbstractFile[] children = cachedDir.get();\n            if (children != null) {\n                for (AbstractFile child : children) {\n                    removeWithChildren(child);\n                }\n            }\n        }\n    }\n    \n    /**\n     * Gets a cached instance of a file. If the cached instance\n     * of the file doesn't exists it's added to the cache.\n     * @param key an AbstractFile instance\n     * @return a cached file instance\n     */\n    synchronized CachedDirectory getOrAdd(AbstractFile key) {\n        CachedDirectory cachedDir = cache.get(key);\n        if (cachedDir == null) {\n            cachedDir = new CachedDirectory(key, this);\n            cache.put(key, cachedDir);\n        }\n        return cachedDir;\n    }\n   \n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tree/FilesTreeModel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.tree;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.filter.FileFilter;\nimport com.mucommander.commons.file.util.FileComparator;\nimport com.mucommander.ui.icon.FileIcons;\nimport com.mucommander.ui.icon.SpinningDial;\n\nimport javax.swing.*;\nimport javax.swing.event.EventListenerList;\nimport javax.swing.event.TreeModelEvent;\nimport javax.swing.event.TreeModelListener;\nimport javax.swing.tree.TreeModel;\nimport javax.swing.tree.TreePath;\nimport java.util.Arrays;\n\n/**\n * A tree model for files.\n * This class contains a tree structure defined by AbstractFile objects.\n\n * @author Mariusz Jakubowski\n * \n */\npublic class FilesTreeModel implements TreeModel, CachedDirectoryListener {\n\n    private final DirectoryCache cache;\n    \n    /** Comparator used to sort folders */\n    private final FileComparator sort;\n    \n    /** Listeners. */\n    private final EventListenerList listenerList = new EventListenerList();\n\n    /** Root of the directory tree. */\n    private AbstractFile root;\n\n    /** number of caching children at the time, used to control spinning icon */\n    private int cachingNum = 0;\n\n    /** icon used to show that a children of a directory are being cached */\n    private final SpinningDial spinningIcon = new SpinningDial(16, 16, false);\n\n\n    FilesTreeModel(FileFilter filter, FileComparator sort) {\n        super();\n        this.sort = sort;\n        cache = new DirectoryCache(filter, sort);\n        cache.addCachedDirectoryListener(this);\n    }\n\n    /**\n     * Changes the current root of a tree\n     * Fires 'tree structure changed' event.\n     * @param newRoot the new root of a tree\n     */\n    public void setRoot(AbstractFile newRoot) {\n        final CachedDirectory cachedRoot = new CachedDirectory(newRoot, cache);\n        cachedRoot.setCachedIcon(FileIcons.getFileIcon(newRoot));\n        SwingUtilities.invokeLater(() -> {\n            root = cachedRoot.getProxiedFile();\n            cache.clear();\n            cache.put(root, cachedRoot);\n            TreePath path = new TreePath(root);\n            fireTreeStructureChanged(this, path);\n        });\n    }\n    \n    public Object getRoot() {\n        return root;\n    }\n    \n\n    /**\n     * Returns children folders of a parent folder sorted by name.\n     * @param parent parent folder\n     * @return children folders\n     */\n    private AbstractFile[] getChildren(AbstractFile parent) {\n        CachedDirectory cachedDir = cache.getOrAdd(parent);\n        return cachedDir.isCached() ? cachedDir.get() : null;\n    }\n\n    public Object getChild(Object parent, int index) {\n        AbstractFile[] children = getChildren((AbstractFile) parent);\n        return children != null ? children[index] : null;\n    }\n\n    public int getChildCount(Object parent) {\n        AbstractFile[] children = getChildren((AbstractFile) parent);\n        return children != null ? children.length : 0;\n    }\n\n    public int getIndexOfChild(Object parent, Object child) {\n        AbstractFile[] children = getChildren((AbstractFile) parent);\n        return children != null ? Arrays.binarySearch(children, (AbstractFile)child, sort) : 0;\n    }\n\n    public boolean isLeaf(Object node) {\n        return false;\n    }\n\n    public void valueForPathChanged(TreePath path, Object newValue) {\n    }\n\n    /**\n     * Notifies all listeners that have registered interest for notification on this event type.\n     * @param source the node where the tree model has changed\n     * @param path the path to the root node\n     * @see EventListenerList\n     */\n    void fireTreeStructureChanged(Object source, TreePath path) {\n        // Guaranteed to return a non-null array\n        Object[] listeners = listenerList.getListenerList();\n        TreeModelEvent e = null;\n        // Process the listeners last to first, notifying\n        // those that are interested in this event\n        for (int i = listeners.length - 2; i >= 0; i -= 2) {\n            if (listeners[i] == TreeModelListener.class) {\n                // Lazily create the event:\n                if (e == null) {\n                    e = new TreeModelEvent(source, path);\n                }\n                ((TreeModelListener) listeners[i + 1]).treeStructureChanged(e);\n            }\n        }\n    }\n\n    /**\n     * Builds the parents of node up to and including the root node, where the original node is the last element\n     * in the returned array. The length of the returned array gives the node's depth in the tree.\n     * @param aNode the TreeNode to get the path for\n     */\n    AbstractFile[] getPathToRoot(AbstractFile aNode) {\n        return getPathToRoot(aNode, 0);\n    }\n\n    /**\n     * Builds the parents of node up to and including the root node,\n     * where the original node is the last element in the returned array.\n     * The length of the returned array gives the node's depth in the\n     * tree.\n     * \n     * @param aNode  the TreeNode to get the path for\n     * @param depth  an int giving the number of steps already taken towards\n     *        the root (on recursive calls), used to size the returned array\n     * @return an array of TreeNodes giving the path from the root to the\n     *         specified node \n     */\n    private AbstractFile[] getPathToRoot(AbstractFile aNode, int depth) {\n        AbstractFile[]              retNodes;\n    // This method recurses, traversing towards the root in order\n    // size the array. On the way back, it fills in the nodes,\n    // starting from the root and working back to the original node.\n\n        /* Check for null, in case someone passed in a null node, or\n           they passed in an element that isn't rooted at root. */\n        if (aNode == null) {\n            if (depth == 0) {\n                return null;\n            } else {\n                retNodes = new AbstractFile[depth];\n            }\n        } else {\n            depth++;\n            retNodes = aNode == root ? new AbstractFile[depth] : getPathToRoot(aNode.getParent(), depth);\n            retNodes[retNodes.length - depth] = aNode;\n            cache.getOrAdd(aNode).isCached();       // ensures that a path is in cache\n        }\n        return retNodes;\n    }\n    \n    public void addTreeModelListener(TreeModelListener l) {\n        listenerList.add(TreeModelListener.class, l);\n    }\n\n    public void removeTreeModelListener(TreeModelListener l) {\n        listenerList.remove(TreeModelListener.class, l);\n    }\n\n    /**\n     * Refreshes tree model from given path.\n     * @param path a path to refresh\n     */\n    public void refresh(TreePath path) {\n        AbstractFile folder = (AbstractFile) path.getLastPathComponent();\n        CachedDirectory cached = cache.get(folder);\n        Icon cachedIcon = cached.getCachedIcon();        \n        cache.removeWithChildren(folder);\n        cached = cache.getOrAdd(folder);\n        cached.setCachedIcon(cachedIcon);\n        fireTreeStructureChanged(this, path);\n    }\n\n    public void cachingStarted(AbstractFile parent) {\n        cachingNum++;\n        if (cachingNum == 1) {\n            spinningIcon.setAnimated(true);\n        }\n    }\n\n    public void cachingEnded(AbstractFile parent) {\n        cachingNum--;\n        if (cachingNum == 0) {\n            spinningIcon.setAnimated(false);\n        }\n        TreePath path = new TreePath(getPathToRoot(parent));\n        fireTreeStructureChanged(this, path);\n    }\n    \n    /**\n     * Returns an icon of this directory or spinning icon if this directory is\n     * being cached.\n     * @return an icon of this directory or spinning icon if this directory is\n     *         being cached.\n     */\n    Icon getCurrentIcon(AbstractFile file) {\n        CachedDirectory cached = cache.get(file);\n        if (cached != null) {\n            return cached.isReadingChildren() ? spinningIcon : cached.getCachedIcon();\n        }\n        return spinningIcon;\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tree/FoldersTreePanel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.tree;\n\nimport java.awt.BorderLayout;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.FocusEvent;\nimport java.awt.event.FocusListener;\nimport java.awt.event.KeyEvent;\nimport java.awt.event.MouseAdapter;\nimport java.awt.event.MouseEvent;\n\nimport javax.swing.JMenuItem;\nimport javax.swing.JPanel;\nimport javax.swing.JPopupMenu;\nimport javax.swing.JScrollPane;\nimport javax.swing.JTree;\nimport javax.swing.SwingUtilities;\nimport javax.swing.Timer;\nimport javax.swing.event.TreeModelEvent;\nimport javax.swing.event.TreeModelListener;\nimport javax.swing.event.TreeSelectionEvent;\nimport javax.swing.event.TreeSelectionListener;\nimport javax.swing.tree.TreePath;\nimport javax.swing.tree.TreeSelectionModel;\n\nimport com.mucommander.ui.PreloadedJFrame;\nimport lombok.Getter;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jetbrains.annotations.NotNull;\n\nimport com.mucommander.commons.conf.ConfigurationEvent;\nimport com.mucommander.commons.conf.ConfigurationListener;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.filter.AndFileFilter;\nimport com.mucommander.commons.file.filter.AttributeFileFilter;\nimport com.mucommander.commons.file.filter.AttributeFileFilter.FileAttribute;\nimport com.mucommander.commons.file.util.FileComparator;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.ui.action.ActionProperties;\nimport com.mucommander.ui.action.impl.RefreshAction;\nimport com.mucommander.ui.event.LocationEvent;\nimport com.mucommander.ui.event.LocationListener;\nimport com.mucommander.ui.main.ConfigurableFolderFilter;\nimport com.mucommander.ui.main.FolderPanel;\nimport com.mucommander.ui.theme.ColorChangedEvent;\nimport com.mucommander.ui.theme.FontChangedEvent;\nimport com.mucommander.ui.theme.ThemeCache;\nimport com.mucommander.ui.theme.ThemeListener;\n\n/**\n * A panel which contains a directory tree. This panel is attached to the left\n * side of the files table. It allows for a quick navigation in a directory\n * tree. Selecting folder on the tree changes folder in files folder.\n * \n * @author Mariusz Jakubowski\n * \n */\n@Slf4j\npublic class FoldersTreePanel implements TreeSelectionListener,\n\t\t\t\t\t\t\tLocationListener, FocusListener, ThemeListener, \n\t\t\t\t\t\t\tTreeModelListener, ConfigurationListener {\n\t\n    /** Directory tree\n     * -- GETTER --\n     *  Returns tree component.\n     */\n    @Getter private final JTree tree;\n    @Getter private final JPanel panel;\n\n    /** Folder panel to which this tree is attached */\n    private final FolderPanel folderPanel;\n\n    /** A model with a directory tree */\n    private final FilesTreeModel model;\n\n    /** A timer that fires a directory change */\n    private final ChangeTimer changeTimer = new ChangeTimer();\n\n    static {\n        TreeIOThreadManager.getInstance().start();\n    }\n\n   \n    /**\n     * Creates a panel with directory tree attached to a specified folder panel.\n     * @param folderPanel a folder panel to attach tree\n     */\n    public FoldersTreePanel(FolderPanel folderPanel) {\n        panel = PreloadedJFrame.getJPanel(new BorderLayout());\n        this.folderPanel = folderPanel;\n        \n        panel.setLayout(new BorderLayout());\n\n        // Filters out the files that should not be displayed in the tree view\n        AndFileFilter treeFileFilter = new AndFileFilter(\n            new AttributeFileFilter(FileAttribute.DIRECTORY),\n            new ConfigurableFolderFilter()\n        );\n\n        FileComparator sort = new FileComparator(FileComparator.NAME_CRITERION, true, true, false);\n        model = new FilesTreeModel(treeFileFilter, sort);\n        tree = new JTree(model);\n\t\ttree.setFont(ThemeCache.tableFont);\n        tree.setBackground(ThemeCache.backgroundColors[ThemeCache.INACTIVE][ThemeCache.NORMAL]);\n\n        tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);\n        tree.setExpandsSelectedPaths(true);\n        tree.getModel().addTreeModelListener(this);\n\n        JScrollPane sp = new JScrollPane(tree);\n        // JScrollPane usually comes with a tiny border, remove it\n        sp.setBorder(null);\n\n        panel.add(sp, BorderLayout.CENTER);\n\n        // create tree renderer. We're not using default tree renderer, because\n        // AbstractFile.toString method returns full path, and we want to\n        // display only a file name.\n        FoldersTreeRenderer renderer = new FoldersTreeRenderer(tree);\n        tree.setCellRenderer(renderer);\n\n        tree.addTreeSelectionListener(this);\n        tree.addFocusListener(this);\n\n        // add a popup menu\n        final JPopupMenu popup = new JPopupMenu();\n        // refresh action\n        JMenuItem item = new JMenuItem(\n        \t\tActionProperties.getActionLabel(RefreshAction.Descriptor.ACTION_ID),\n                KeyEvent.VK_R);\n        item.addActionListener(e -> {\n            model.refresh(tree.getSelectionPath());\n//                model.fireTreeStructureChanged(tree, tree.getSelectionPath());\n        });\n        popup.add(item);\n        tree.addMouseListener(new MouseAdapter() {\n            @Override\n            public void mousePressed(MouseEvent e) {\n                maybeShowPopup(e);\n            }\n\n            @Override\n            public void mouseReleased(MouseEvent e) {\n                maybeShowPopup(e);\n            }\n\n            private void maybeShowPopup(MouseEvent e) {\n                if (e.isPopupTrigger()) {\n                    popup.show(e.getComponent(), e.getX(), e.getY());\n                }\n            }\n        });\n        \n        ThemeCache.addThemeListener(this);\n        \n        TcConfigurations.addPreferencesListener(this);\n    }\n\n    \n    \n    /** \n     * Listens to certain configuration variables.\n     */\n    public void configurationChanged(ConfigurationEvent event) {\n        String var = event.getVariable();\n        if (var.equals(TcPreferences.SHOW_HIDDEN_FILES) ||\n                var.equals(TcPreferences.SHOW_DS_STORE_FILES) ||\n                var.equals(TcPreferences.SHOW_SYSTEM_FOLDERS)) {\n            Object root = model.getRoot();\n            if (root != null) {\n                TreePath path = new TreePath(root);\n                model.refresh(path);\n            }\n        }\n    }\n\n    /**\n     * Adds or removes location change listeners depending on the tree\n     * visibility.\n     */\n    public void setVisible(boolean flag) {\n        panel.setVisible(flag);\n        if (flag) {\n            updateSelectedFolder();\n            folderPanel.getLocationManager().addLocationListener(this);\n            // tree.requestFocus();\n        } else {\n            folderPanel.getLocationManager().removeLocationListener(this);\n        }\n    }\n\n\n\t/**\n     * Updates selection in a tree to the current folder. When necessary updates\n     * the current root of a tree. Invoked when location on folder pane has changed or \n     * when a tree has been updated (when directories have been loaded).\n     */\n    private void updateSelectedFolder() {\n        final AbstractFile currentFolder = folderPanel.getCurrentFolder();\n\n        // get selected directory (ignore archives - TODO make archives browsable (option))\n        final AbstractFile parentFolder = getParentFolder(currentFolder);\n\n        // compare selection on tree and panel\n        TreePath selectionPath = tree.getSelectionPath();\n        if (selectionPath != null) {\n            if (selectionPath.getLastPathComponent() == currentFolder) {\n                return;\n            }\n        }\n\n        // check if root has changed\n        final AbstractFile currentRoot = parentFolder.getRoot();\n        if (!currentRoot.equals(model.getRoot())) {\n            model.setRoot(currentRoot);\n        }\n        // refresh selection on tree\n        SwingUtilities.invokeLater(() -> {\n            try {\n                TreePath path = new TreePath(model.getPathToRoot(parentFolder));\n                tree.expandPath(path);\n                tree.setSelectionPath(path);\n                tree.scrollPathToVisible(path);\n            } catch (Exception e) {\n                log.debug(\"Caught exception\", e);\n            }\n         });\n    }\n\n    @NotNull\n    private AbstractFile getParentFolder(AbstractFile currentFolder) {\n        AbstractFile tempFolder = currentFolder;\n        while (!tempFolder.isDirectory()) {\n            AbstractFile tempParent = tempFolder.getParent();\n            if (tempParent == null) {\n                break;\n            }\n            tempFolder = tempParent;\n        }\n        return tempFolder;\n    }\n\n    /**\n     * Refreshes folder after a change (e.g. mkdir).\n     * @param folder a folder to refresh on the tree\n     */\n    public void refreshFolder(AbstractFile folder) {\n        if (!panel.isVisible()) {\n            return;\n        }\n        model.fireTreeStructureChanged(tree, new TreePath(model.getPathToRoot(folder)));\n    }\n    \n    /**\n     * Changes focus to tree.\n     */\n    public void requestFocus() {\n        tree.requestFocus();\n    }\n\n\n    // - TreeSelectionListener code --------------------------------------------\n    // -------------------------------------------------------------------------\n    \n    /**\n     * This class is used to change folder after a user selects a folder in\n     * tree. This change occurs after small delay (1 sec) to allow a user to\n     * navigate a tree using keyboard.\n     * \n     * @author Mariusz Jakubowski\n     * \n     */\n    private class ChangeTimer extends Timer {\n        private transient AbstractFile folder;\n\n        ChangeTimer() {\n            super(1000, null);\n            setRepeats(false);\n        }\n\n        @Override\n        public void fireActionPerformed(ActionEvent ae) {\n            if (!folderPanel.getCurrentFolder().equals(folder)) {\n                folderPanel.tryChangeCurrentFolder(folder);\n            }\n        }\n    }\n    \n\n    /**\n     * Changes the current folder in an associated folder panel, depending on\n     * the current selection in tree.\n     */\n    public void valueChanged(TreeSelectionEvent e) {\n        TreePath path = e.getNewLeadSelectionPath();\n        if (path != null) {\n            AbstractFile f = (AbstractFile) path.getLastPathComponent();\n            if (f != null && f.isBrowsable() && f != folderPanel.getCurrentFolder()) {\n                changeTimer.folder = f;\n                changeTimer.restart();\n            }\n        }\n    }\n\n\n    @Override\n    public void locationCancelled(LocationEvent locationEvent) {\n    }\n\n    @Override\n    public void locationChanged(LocationEvent locationEvent) {\n        updateSelectedFolder();\n    }\n\n    @Override\n    public void locationChanging(LocationEvent locationEvent) {\n    }\n\n    @Override\n    public void locationFailed(LocationEvent locationEvent) {\n    }\n\n\n    @Override\n    public void focusGained(FocusEvent e) {\n\t\ttree.setBackground(ThemeCache.backgroundColors[ThemeCache.ACTIVE][ThemeCache.NORMAL]);\t\n\t}\n\n    @Override\n\tpublic void focusLost(FocusEvent e) {\n\t\ttree.setBackground(ThemeCache.backgroundColors[ThemeCache.INACTIVE][ThemeCache.NORMAL]);\t\n\t}\n\n    @Override\n    public void colorChanged(ColorChangedEvent event) {\n        int type = tree.hasFocus()  ? ThemeCache.ACTIVE : ThemeCache.INACTIVE;\n        tree.setBackground(ThemeCache.backgroundColors[type][ThemeCache.NORMAL]);\n\t\ttree.repaint();\n\t}\n\n    @Override\n    public void fontChanged(FontChangedEvent event) {\n\t\ttree.setFont(ThemeCache.tableFont);\n\t\ttree.repaint();\n\t}\n\n    @Override\n    public void treeNodesChanged(TreeModelEvent e) {\n    }\n\n    @Override\n    public void treeNodesInserted(TreeModelEvent e) {\n    }\n\n    @Override\n    public void treeNodesRemoved(TreeModelEvent e) {\n    }\n\n    @Override\n    public void treeStructureChanged(TreeModelEvent e) {\n        // ensures that a selection is repainted correctly\n        // after nodes have been inserted                \n        if (!changeTimer.isRunning()) {        \n            updateSelectedFolder();\n            tree.repaint();\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tree/FoldersTreeRenderer.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.main.tree;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.theme.ThemeCache;\n\nimport javax.swing.*;\nimport javax.swing.tree.DefaultTreeCellRenderer;\nimport java.awt.*;\n\n/**\n * A renderer for the directory tree. It renders model's items (which are\n * AbstractFiles), using file names. It also renders a correct icon for a folder.\n * \n * @author Mariusz Jakubowski\n * \n */\npublic class FoldersTreeRenderer extends DefaultTreeCellRenderer {\n\n    private final JTree tree;\n    private final FilesTreeModel model;\n    \n\tFoldersTreeRenderer(JTree tree) {\n        super();\n        this.tree = tree;\n        this.model = (FilesTreeModel) tree.getModel();\n    }\n    \n    @Override\n    public Color getBackgroundSelectionColor() {\n    \tif (tree!=null && tree.hasFocus()) {\n            return ThemeCache.backgroundColors[ThemeCache.ACTIVE][ThemeCache.SELECTED];    \t\t\n    \t} else {\n            return ThemeCache.backgroundColors[ThemeCache.INACTIVE][ThemeCache.SELECTED];    \t\t\n    \t}\n    }\n    \n    @Override\n    public Color getBackgroundNonSelectionColor() {\n    \tif (tree!=null && tree.hasFocus()) {\n    \t\treturn ThemeCache.backgroundColors[ThemeCache.ACTIVE][ThemeCache.NORMAL];\n    \t} else {\n    \t\treturn ThemeCache.backgroundColors[ThemeCache.INACTIVE][ThemeCache.NORMAL];\n    \t}\n    }\n    \n    @Override\n    public Color getForeground() {\n    \tif (tree!=null && tree.hasFocus()) {\n    \t\treturn selected ? ThemeCache.foregroundColors[ThemeCache.ACTIVE][ThemeCache.SELECTED][ThemeCache.FOLDER] : \n    \t\t\tThemeCache.foregroundColors[ThemeCache.ACTIVE][ThemeCache.NORMAL][ThemeCache.FOLDER];\n    \t} else {\n    \t\treturn selected ? ThemeCache.foregroundColors[ThemeCache.INACTIVE][ThemeCache.SELECTED][ThemeCache.FOLDER] : \n    \t\t\tThemeCache.foregroundColors[ThemeCache.INACTIVE][ThemeCache.NORMAL][ThemeCache.FOLDER];\n    \t}\n    }\n\n    @Override\n    public Component getTreeCellRendererComponent(JTree tree, Object value,\n            boolean sel, boolean expanded, boolean leaf, int row,\n            boolean hasFocus) {\n        // get file name and create default component (JLabel) to display it\n        AbstractFile file = (AbstractFile) value;\n        String name = file.isRoot()?file.getAbsolutePath():file.getName();\n        super.getTreeCellRendererComponent(tree, name, sel, expanded, leaf,\n                row, hasFocus);\n        setIcon(model.getCurrentIcon(file));\n        return this;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tree/IOThread.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.tree;\r\n\r\nimport java.util.List;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\n/**\r\n * A thread that executes i/o operations. \r\n * @author Mariusz Jakubowski\r\n *\r\n */\r\npublic class IOThread extends Thread {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(IOThread.class);\r\n\t\r\n    /** a queue with tasks to execute */\r\n    private final List<Runnable> queue;\r\n    \r\n    /** a time after this thread is marked as blocked */\r\n    private final long blockThreshold;\r\n\r\n    /** a time when this thread signalled that is alive */\r\n    private volatile long lastActionTime = 0;\r\n    \r\n    \r\n    /**\r\n     * Creates a new instance of an IOThread.\r\n     * @param queue a queue with tasks\r\n     * @param blockThreshold a time after this thread is marked as blocked [ms]\r\n     */\r\n    IOThread(List<Runnable> queue, long blockThreshold) {\r\n        super(\"IOThread\");\r\n        this.queue = queue;\r\n        this.blockThreshold = blockThreshold;\r\n    }\r\n    \r\n    \r\n    \r\n    @Override\r\n    public void run() {\r\n        \r\n        while (!interrupted()) {\r\n            lastActionTime = System.currentTimeMillis(); \r\n            while (!queue.isEmpty()) {\r\n                Runnable task = queue.remove(0);\r\n                try {\r\n                    task.run();\r\n                } catch (Exception e) {\r\n                    LOGGER.debug(\"Caught exception\", e);\r\n                }\r\n                lastActionTime = System.currentTimeMillis(); \r\n            }\r\n            try {\r\n                synchronized (this) {\r\n                    wait(blockThreshold / 2);\r\n                }\r\n            } catch (InterruptedException e) {\r\n                break;\r\n            }\r\n        }\r\n        \r\n    }\r\n    \r\n    /**\r\n     * Checks if current thread is blocked. This is done by checking if \r\n     * last action time is smaller than block threshold.\r\n     * @return true if thread is running\r\n     */\r\n    public boolean isBlocked() {\r\n        return (lastActionTime != 0) && (System.currentTimeMillis() - lastActionTime > blockThreshold); \r\n    }\r\n    \r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tree/TreeIOThreadManager.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.main.tree;\r\n\r\n/**\r\n * Monitors thread that reads children and icons for the tree.\r\n * @author Mariusz Jakubowski\r\n *\r\n */\r\npublic class TreeIOThreadManager extends AbstractIOThreadManager {\r\n\r\n    public final static TreeIOThreadManager instance = new TreeIOThreadManager();\r\n    \r\n    private TreeIOThreadManager() {\r\n        super(\"TreeIOThreadManager\", 5000);\r\n    }\r\n    \r\n    public static TreeIOThreadManager getInstance() {\r\n        return instance;\r\n    }\r\n    \r\n    \r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/main/tree/package.html",
    "content": "<body>\n  Contains classes used to display a directory tree.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/menu/JScrollMenu.java",
    "content": "package com.mucommander.ui.menu;\n\nimport ru.trolsoft.ui.TMenuSeparator;\n\nimport javax.swing.Action;\nimport javax.swing.JButton;\nimport javax.swing.JMenu;\nimport javax.swing.JMenuItem;\nimport javax.swing.JPopupMenu;\nimport javax.swing.MenuElement;\nimport javax.swing.UIManager;\nimport javax.swing.plaf.MenuItemUI;\nimport javax.swing.plaf.PopupMenuUI;\nimport java.awt.Component;\nimport java.awt.ComponentOrientation;\n\npublic class JScrollMenu extends JMenu {\n    // Covers the one in the JMenu because the method that creates it in JMenu is private\n    /**\n     * The popup menu portion of the menu.\n     */\n    private JScrollPopupMenu popupMenu;\n\n    /**\n     * Constructs a new <code>JMenu</code> with no text.\n     */\n    public JScrollMenu() {\n        this(\"\");\n    }\n\n    /**\n     * Constructs a new <code>JMenu</code> with the supplied string as its text.\n     *\n     * @param s the text for the menu label\n     */\n    public JScrollMenu(String s) {\n        super(s);\n    }\n\n    /**\n     * Constructs a menu whose properties are taken from the <code>Action</code> supplied.\n     *\n     * @param a an <code>Action</code>\n     */\n    public JScrollMenu(Action a) {\n        this();\n        setAction(a);\n    }\n\n    /**\n     * Lazily creates the popup menu. This method will create the popup using the <code>JScrollPopupMenu</code> class.\n     */\n    protected void ensurePopupMenuCreated() {\n        if (popupMenu == null) {\n            popupMenu = new JScrollPopupMenu();\n            popupMenu.setInvoker(this);\n            popupListener = createWinListener(popupMenu);\n        }\n    }\n\n    //////////////////////////////\n    //// All of these methods are necessary because ensurePopupMenuCreated() is private in JMenu\n    //////////////////////////////\n    @Override\n    public void updateUI() {\n        setUI((MenuItemUI) UIManager.getUI(this));\n        if (popupMenu != null) {\n            popupMenu.setUI((PopupMenuUI) UIManager.getUI(popupMenu));\n        }\n    }\n\n    @Override\n    public boolean isPopupMenuVisible() {\n        ensurePopupMenuCreated();\n        return popupMenu.isVisible();\n    }\n\n    @Override\n    public void setMenuLocation(int x, int y) {\n        super.setMenuLocation(x, y);\n        if (popupMenu != null) {\n            popupMenu.setLocation(x, y);\n        }\n    }\n\n    @Override\n    public JMenuItem add(JMenuItem menuItem) {\n        ensurePopupMenuCreated();\n        return popupMenu.add(menuItem);\n    }\n\n    @Override\n    public Component add(Component c) {\n        ensurePopupMenuCreated();\n        popupMenu.add(c);\n        return c;\n    }\n\n    @Override\n    public Component add(Component c, int index) {\n        ensurePopupMenuCreated();\n        popupMenu.add(c, index);\n        return c;\n    }\n\n    @Override\n    public void addSeparator() {\n        ensurePopupMenuCreated();\n        popupMenu.add(new TMenuSeparator());\n    }\n\n    @Override\n    public void insert(String s, int pos) {\n        ensurePopupMenuCreated();\n        popupMenu.insert(new JMenuItem(s), pos);\n    }\n\n    @Override\n    public JMenuItem insert(JMenuItem mi, int pos) {\n        ensurePopupMenuCreated();\n        popupMenu.insert(mi, pos);\n        return mi;\n    }\n\n    @Override\n    public JMenuItem insert(Action a, int pos) {\n        ensurePopupMenuCreated();\n        JMenuItem mi = new JMenuItem(a);\n        mi.setHorizontalTextPosition(JButton.TRAILING);\n        mi.setVerticalTextPosition(JButton.CENTER);\n        popupMenu.insert(mi, pos);\n        return mi;\n    }\n\n    @Override\n    public void insertSeparator(int index) {\n        ensurePopupMenuCreated();\n        popupMenu.insert(new JPopupMenu.Separator(), index);\n    }\n\n    @Override\n    public void remove(JMenuItem item) {\n        if (popupMenu != null) {\n            popupMenu.remove(item);\n        }\n    }\n\n    @Override\n    public void remove(int pos) {\n        if (pos < 0) {\n            throw new IllegalArgumentException(\"index less than zero.\");\n        }\n        if (pos > getItemCount()) {\n            throw new IllegalArgumentException(\"index greater than the number of items.\");\n        }\n        if (popupMenu != null) {\n            popupMenu.remove(pos);\n        }\n    }\n\n    @Override\n    public void remove(Component c) {\n        if (popupMenu != null) {\n            popupMenu.remove(c);\n        }\n    }\n\n    @Override\n    public void removeAll() {\n        if (popupMenu != null) {\n            for (int i = getMenuComponentCount() - 2; i >= 0; i--) {\n                remove(i);\n            }\n        }\n    }\n\n    @Override\n    public int getMenuComponentCount() {\n        return (popupMenu == null) ? 0 : popupMenu.getComponentCount();\n    }\n\n    @Override\n    public Component getMenuComponent(int n) {\n        return (popupMenu == null) ? null : popupMenu.getComponent(n);\n    }\n\n    @Override\n    public Component[] getMenuComponents() {\n        return (popupMenu == null) ? new Component[0] : popupMenu.getComponents();\n    }\n\n    @Override\n    public JScrollPopupMenu getPopupMenu() {\n        ensurePopupMenuCreated();\n        return popupMenu;\n    }\n\n    @Override\n    public MenuElement[] getSubElements() {\n        return popupMenu == null ? new MenuElement[0] : new MenuElement[]{popupMenu};\n    }\n\n    @Override\n    public void applyComponentOrientation(ComponentOrientation o) {\n        super.applyComponentOrientation(o);\n\n        if (popupMenu != null) {\n            int ncomponents = getMenuComponentCount();\n            for (int i = 0; i < ncomponents; ++i) {\n                getMenuComponent(i).applyComponentOrientation(o);\n            }\n            popupMenu.setComponentOrientation(o);\n        }\n    }\n\n    @Override\n    public void setComponentOrientation(ComponentOrientation o) {\n        super.setComponentOrientation(o);\n        if (popupMenu != null) {\n            popupMenu.setComponentOrientation(o);\n        }\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/menu/JScrollPopupMenu.java",
    "content": "package com.mucommander.ui.menu;\n\nimport javax.swing.JPopupMenu;\nimport javax.swing.JScrollBar;\nimport java.awt.Component;\nimport java.awt.Container;\nimport java.awt.Dimension;\nimport java.awt.Graphics;\nimport java.awt.Insets;\nimport java.awt.LayoutManager;\nimport java.awt.event.MouseWheelEvent;\n\npublic class JScrollPopupMenu extends JPopupMenu {\n\n    private int maximumVisibleRows = 10;\n\n    public JScrollPopupMenu() {\n        this(null);\n    }\n\n    public JScrollPopupMenu(String label) {\n        super(label);\n        setLayout(new ScrollPopupMenuLayout());\n\n        super.add(getScrollBar());\n        addMouseWheelListener(event -> {\n            JScrollBar scrollBar = getScrollBar();\n            int amount = (event.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL)\n                    ? event.getUnitsToScroll() * scrollBar.getUnitIncrement()\n                    : (event.getWheelRotation() < 0 ? -1 : 1) * scrollBar.getBlockIncrement();\n\n            scrollBar.setValue(scrollBar.getValue() + amount);\n            event.consume();\n        });\n    }\n\n    private JScrollBar popupScrollBar;\n\n    protected JScrollBar getScrollBar() {\n        if (popupScrollBar == null) {\n            popupScrollBar = new JScrollBar(JScrollBar.VERTICAL);\n            popupScrollBar.addAdjustmentListener(e -> {\n                doLayout();\n                repaint();\n            });\n\n            popupScrollBar.setVisible(false);\n        }\n\n        return popupScrollBar;\n    }\n\n    public int getMaximumVisibleRows() {\n        return maximumVisibleRows;\n    }\n\n    public void setMaximumVisibleRows(int maximumVisibleRows) {\n        this.maximumVisibleRows = maximumVisibleRows;\n    }\n\n    @Override\n    public void paintChildren(Graphics g) {\n        Insets insets = getInsets();\n        g.clipRect(insets.left, insets.top, getWidth(), getHeight() - insets.top - insets.bottom);\n        super.paintChildren(g);\n    }\n\n    @Override\n    protected void addImpl(Component comp, Object constraints, int index) {\n        super.addImpl(comp, constraints, index);\n\n        if (maximumVisibleRows < getComponentCount() - 1) {\n            getScrollBar().setVisible(true);\n        }\n    }\n\n    @Override\n    public void remove(int index) {\n        // can't remove the scrollbar\n        ++index;\n\n        super.remove(index);\n\n        if (maximumVisibleRows >= getComponentCount() - 1) {\n            getScrollBar().setVisible(false);\n        }\n    }\n\n    @Override\n    public void show(Component invoker, int x, int y) {\n        JScrollBar scrollBar = getScrollBar();\n        if (scrollBar.isVisible()) {\n            int extent = 0;\n            int max = 0;\n            int i = 0;\n            int unit = -1;\n            int width = 0;\n            for (Component comp : getComponents()) {\n                if (!(comp instanceof JScrollBar)) {\n                    Dimension preferredSize = comp.getPreferredSize();\n                    width = Math.max(width, preferredSize.width);\n                    if (unit < 0) {\n                        unit = preferredSize.height;\n                    }\n                    if (i++ < maximumVisibleRows) {\n                        extent += preferredSize.height;\n                    }\n                    max += preferredSize.height;\n                }\n            }\n\n            Insets insets = getInsets();\n            int widthMargin = insets.left + insets.right;\n            int heightMargin = insets.top + insets.bottom;\n            scrollBar.setUnitIncrement(unit);\n            scrollBar.setBlockIncrement(extent);\n            scrollBar.setValues(0, heightMargin + extent, 0, heightMargin + max);\n\n            width += scrollBar.getPreferredSize().width + widthMargin;\n            int height = heightMargin + extent;\n\n            setPopupSize(new Dimension(width, height));\n        }\n\n        super.show(invoker, x, y);\n    }\n\n    protected static class ScrollPopupMenuLayout implements LayoutManager {\n        @Override\n        public void addLayoutComponent(String name, Component comp) {\n        }\n\n        @Override\n        public void removeLayoutComponent(Component comp) {\n        }\n\n        @Override\n        public Dimension preferredLayoutSize(Container parent) {\n            int visibleAmount = Integer.MAX_VALUE;\n            Dimension dim = new Dimension();\n            for (Component comp : parent.getComponents()) {\n                if (comp.isVisible()) {\n                    if (comp instanceof JScrollBar) {\n                        JScrollBar scrollBar = (JScrollBar) comp;\n                        visibleAmount = scrollBar.getVisibleAmount();\n                    } else {\n                        Dimension pref = comp.getPreferredSize();\n                        dim.width = Math.max(dim.width, pref.width);\n                        dim.height += pref.height;\n                    }\n                }\n            }\n\n            Insets insets = parent.getInsets();\n            dim.height = Math.min(dim.height + insets.top + insets.bottom, visibleAmount);\n\n            return dim;\n        }\n\n        @Override\n        public Dimension minimumLayoutSize(Container parent) {\n            int visibleAmount = Integer.MAX_VALUE;\n            Dimension dim = new Dimension();\n            for (Component comp : parent.getComponents()) {\n                if (comp.isVisible()) {\n                    if (comp instanceof JScrollBar) {\n                        JScrollBar scrollBar = (JScrollBar) comp;\n                        visibleAmount = scrollBar.getVisibleAmount();\n                    } else {\n                        Dimension min = comp.getMinimumSize();\n                        dim.width = Math.max(dim.width, min.width);\n                        dim.height += min.height;\n                    }\n                }\n            }\n\n            Insets insets = parent.getInsets();\n            dim.height = Math.min(dim.height + insets.top + insets.bottom, visibleAmount);\n\n            return dim;\n        }\n\n        @Override\n        public void layoutContainer(Container parent) {\n            Insets insets = parent.getInsets();\n\n            int width = parent.getWidth() - insets.left - insets.right;\n            int height = parent.getHeight() - insets.top - insets.bottom;\n\n            int x = insets.left;\n            int y = insets.top;\n            int position = 0;\n\n            for (Component comp : parent.getComponents()) {\n                if ((comp instanceof JScrollBar) && comp.isVisible()) {\n                    JScrollBar scrollBar = (JScrollBar) comp;\n                    Dimension dim = scrollBar.getPreferredSize();\n                    scrollBar.setBounds(x + width - dim.width, y, dim.width, height);\n                    width -= dim.width;\n                    position = scrollBar.getValue();\n                }\n            }\n\n            y -= position;\n            for (Component comp : parent.getComponents()) {\n                if (!(comp instanceof JScrollBar) && comp.isVisible()) {\n                    Dimension pref = comp.getPreferredSize();\n                    comp.setBounds(x, y, width, pref.height);\n                    y += pref.height;\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/notifier/AbstractNotifier.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.notifier;\n\nimport java.awt.SystemTray;\n\nimport javax.swing.SwingUtilities;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.ui.main.WindowManager;\n\n/**\n * AbstractNotifier is a generic representation of a system notifier. It also provides factory methods to\n * retrieve the current platform's notifier instance, if there is one.\n * <p>\n * A notifier serves the purpose of displaying notifications to the screen, to inform the user of an event when\n * the application is not visible (in the background).\n * <p>\n * The notifier instance returned by {@link #getNotifier()} is platform-dependent. At this time, two notifier\n * implementations are available:\n * <ul>\n *  <li>{@link GrowlNotifier}: for Mac OS X, requires Growl to be installed\n *  <li>{@link SystemTrayNotifier}: for Java 1.6 and up, using the java.awt.SystemTray API\n * </ul>\n *\n * @author Maxence Bernard\n */\npublic abstract class AbstractNotifier {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(AbstractNotifier.class);\n\n    /** AbstractNotifier instance, null if none is available on the current platform */\n    private static AbstractNotifier notifier;\n\n    static {\n        // Finds and creates a suitable AbstractNotifier instance for the platform, if there is one\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            notifier = new GrowlNotifier();\n        } else if (SystemTray.isSupported()) {\n            notifier = new SystemTrayNotifier();\n        }\n    }\n\n    /**\n     * Returns <code>true</code> if an AbstractNotifier instance is available. In other words, if <code>true</code> is\n     * returned, {@link #getNotifier()} will return a non-null value.\n     *\n     * @return true if an AbstractNotifier instance is available\n     */\n    public static boolean isAvailable() {\n        return notifier != null;\n    }\n\n    /**\n     * Returns an AbstractNotifier instance that can be used on the current platform, <code>null</code> if none\n     * is available.\n     * Note that the returned <code>AbstractNotifier</code> must be enabled before it can be used, which is not\n     * guaranteed to succeed.\n     *\n     * @return an AbstractNotifier instance that can be used on the current platform, null if none is available\n     */\n    public static AbstractNotifier getNotifier() {\n        return notifier;\n    }\n\n\n    /**\n     * Displays a notification with the specified type, title and description and returns <code>true</code> if the\n     * notification could be displayed. The notification will not be displayed if the current muCommander window\n     * (or one of its child windows) is presently in the foreground, so that the user doesn't get notified for things\n     * that he/she can already see on the screen.\n     *\n     * <p>\n     * The notification will not be displayed if:\n     * <ul>\n     *  <li>muCommander is in the foreground\n     *  <li>this notifier is not enabled\n     *  <li>the notification could not be delivered because of an error\n     * </ul>\n     *\n     * <p>\n     * Note that this method is executed in a separate thread after all pending Swing events have been processed,\n     * to ensure in the event of a window being made inactive that the notification will not be triggered. This method\n     * immediately return s(i.e. does not wait for pending events) and thus is not be able to return if the notification\n     * was displayed or not, unlike {@link #displayNotification(NotificationType, String, String)}.\n     *\n     * @param notificationType one of the available notification types, see {@link NotificationType} for possible values\n     * @param title the title of the notification to display\n     * @param description the description of the notification to display\n     */\n    public void displayBackgroundNotification(final NotificationType notificationType, final String title, final String description) {\n        SwingUtilities.invokeLater(() -> {\n            if (WindowManager.getCurrentMainFrame().isAncestorOfActiveWindow()) {\n                LOGGER.debug(\"Ignoring notification, application is in foreground\");\n                return;\n            }\n\n            if (!displayNotification(notificationType, title, description)) {\n                LOGGER.debug(\"Notification failed to be displayed\");\n            }\n        });\n    }\n\n\n    //////////////////////\n    // Abstract methods //\n    //////////////////////\n\n    /**\n     * Enables/disables this notifier and returns <code>true</code> if the operation succeeded. A typical case\n     * for returning false, is when the underlying notification system (e.g. Growl under Mac OS X) could not be reached.\n     * \n     * @param enabled true to enable this notifier, false to disable it\n     * @return true if the operation succeeded\n     */\n    public abstract boolean setEnabled(boolean enabled);\n\n    /**\n     * Returns <code>true</code> if this notifier is enabled and ready to display notifications.\n     *\n     * @return true if this notifier is enabled and ready to display notifications\n     */\n    public abstract boolean isEnabled();\n\n    /**\n     * Displays a notification with the specified type, title and description and returns <code>true</code> if the\n     * notification could be displayed. Unlike {@link #displayBackgroundNotification(NotificationType, String, String)}, the\n     * notification will be attempted for display even if muCommander is currently in the foreground.\n     *\n     * <p>\n     * Returns <code>true</code> if the notification could be displayed, <code>false</code> if:\n     * <ul>\n     *  <li>this notifier is not enabled\n     *  <li>the notification could not be delivered because of an error\n     * </ul>\n     *\n     * @param notificationType one of the available notification types, see {@link NotificationType} for possible values\n     * @param title the title of the notification to display\n     * @param description the description of the notification to display\n     * @return true if the notification was properly sent, false otherwise\n     */\n    public abstract boolean displayNotification(NotificationType notificationType, String title, String description);\n\n    /**\n     * Returns a pretty name for the underlying notification system that can be displayed to the end user.\n     *\n     * @return a pretty name for the underlying notification system\n     */\n    public abstract String getPrettyName();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/notifier/GrowlNotifier.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.notifier;\n\nimport java.util.Hashtable;\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.macosx.AppleScript;\n\n/**\n * GrowlNotifier implements a notifier that uses the Growl notification system.\n *\n * <p>Growl is a third party notification system for Mac OS X, which allows Growl-enabled applications to display small,\n * unintrusive popup notifications to inform the user of noteworthy events. Growl can be found at:\n * <a href=\"http://growl.info\">http://growl.info</a>.\n *\n * <p>This class communicates with Growl using {@link AppleScript}. More information about the AppleScript syntax can\n * be found <a href=\"http://growl.info/documentation/applescript-support.php\">here</a>.\n * The Growl Java library part of the Growl SDK was previously used but it relied on the Cocoa-Java library which has\n * been deprecated by Apple since then.\n *\n * @author Maxence Bernard\n */\npublic class GrowlNotifier extends AbstractNotifier {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(GrowlNotifier.class);\n\t\n    /** Is this notifier enabled ? */\n    private static boolean isEnabled;\n\n    /** Has muCommander been registered ? */\n    private static boolean isRegistered;\n\n    /** Dictionary keys for the different notification types */\n    private final static Map<NotificationType, String> NOTIFICATION_KEYS = new Hashtable<>();\n\n    /** Name of the application to be registered with Growl, as spelled in the .app */\n    private final static String APP_NAME = \"muCommander\";\n\n    /** This AppleScript returns \"true\" if Growl is currently running, \"false\" if it isn't */\n    private final static String IS_GROWL_RUNNING_APPLESCRIPT =\n            \"tell application \\\"System Events\\\"\\n\" +\n                \"\\tset isRunning to (count of (every process whose name is \\\"GrowlHelperApp\\\")) > 0\\n\" +\n            \"end tell\";\n\n    static {\n        NOTIFICATION_KEYS.put(NotificationType.JOB_COMPLETED, \"progress_dialog.job_finished\");\n        NOTIFICATION_KEYS.put(NotificationType.JOB_ERROR, \"progress_dialog.job_error\");\n    }\n\n\n    public GrowlNotifier() {\n    }\n\n\n    /**\n     * Puts the given AppleScript bit inside a <code>tell application / end tell</code> block, executes the script\n     * and returns <code>true</code> if it was successfully executed.\n     *\n     * @param appleScript the AppleScript bit to execute\n     * @return true if the script was successfully executed\n     */\n    private static boolean tellGrowl(String appleScript) {\n        return AppleScript.execute(\n                \"tell application \\\"GrowlHelperApp\\\"\\n\" +\n                    \"\\t\"+appleScript+\"\\n\" +\n                \"end tell\",\n            null);\n    }\n\n\n    @Override\n    public String getPrettyName() {\n        return \"Growl\";\n    }\n\n\n    @Override\n    public boolean setEnabled(boolean enabled) {\n        if (!enabled) {\n            return (isEnabled = false);\n        }\n        // No need to bother if the OS is not Mac OS X\n        if (!OsFamily.MAC_OS_X.isCurrent()) {\n            return false;\n        }\n\n        // Nothing else to do if the application has already been registered\n        if (isRegistered) {\n            return (isEnabled = true);\n        }\n\n        // Test if Growl is currently running and abort if it is not\n        StringBuilder outputBuffer = new StringBuilder();\n        if (!(AppleScript.execute(IS_GROWL_RUNNING_APPLESCRIPT, outputBuffer) && outputBuffer.toString().equals(\"true\"))) {\n            LOGGER.debug(\"Growl is not running, aborting\");\n            return false;\n        }\n\n        // Register the application (muCommander) with Growl\n\n        // The list of notification types muCommander uses\n        String notificationTypes =\n            \"{\"+\n                \"\\\"\"+Translator.get(NOTIFICATION_KEYS.get(NotificationType.JOB_COMPLETED))+\"\\\",\"+\n                \"\\\"\"+Translator.get(NOTIFICATION_KEYS.get(NotificationType.JOB_ERROR))+\"\\\"\"+\n            \"}\";\n\n        // Register muCommander with Growl, declare the notifications types and enable all of them by default\n        isRegistered = tellGrowl(\n            \"register as application \\\"\"+APP_NAME+\"\\\"\"+\n            \" all notifications \"+notificationTypes+\n            \" default notifications \"+notificationTypes+\n            \" icon of application \\\"\"+APP_NAME+\"\\\"\");\n\n        LOGGER.info(isRegistered ?\n            \"Successfully registered \"+APP_NAME+\" with Growl\":\n            \"Error while registering \"+APP_NAME+\" with Growl\");\n\n        return isEnabled = isRegistered;\n    }\n\n    public static boolean isGrowlRunning() {\n        StringBuilder outputBuffer = new StringBuilder();\n        return AppleScript.execute(IS_GROWL_RUNNING_APPLESCRIPT, outputBuffer) && outputBuffer.toString().equals(\"true\");\n    }\n\n\n    @Override\n    public boolean isEnabled() {\n        return isEnabled;\n    }\n\n    @Override\n    public boolean displayNotification(NotificationType notificationType, String title, String description) {\n    \tLOGGER.debug(\"notificationType=\"+notificationType+\" title=\"+title+\" description=\"+description);\n\n        if (!isEnabled()) {\n        \tLOGGER.debug(\"Ignoring notification, this notifier is not enabled\");\n            return false;\n        }\n\n        boolean success = tellGrowl(\n            \"notify with\"+\n            \" name \\\"\"+Translator.get(NOTIFICATION_KEYS.get(notificationType))+\"\\\"\"+\n            \" title \\\"\"+title+\"\\\"\"+\n            \" description \\\"\"+description+\"\\\"\"+\n            \" application name \\\"\"+APP_NAME+\"\\\"\");\n\n        LOGGER.debug(success?\n            \"Notification sent successfully\":\n            \"Error while sending notification\");\n\n        return success;\n    }\n\n// The following commented methods are implemented using the Growl Java library that comes with the Growl SDK. This\n// library relies on the Cocoa-Java library which has been deprecated by Apple, which is why we're not using it anymore.\n// The code has been kept for the record, in case the Growl Java library is ever used again.    \n\n//    public boolean setEnabled(boolean enabled) {\n//        if(enabled) {\n//            // No need to bother if the OS is not Mac OS X\n//            if(PlatformManager.getOsFamily()!=PlatformManager.MAC_OS_X)\n//                return false;\n//\n//            // If Growl notifier has already been initialized\n//            if(growl!=null) {\n//                return (isEnabled = true);\n//            }\n//\n//            try {\n//                // Register the application (muCommander) and its icon. Growl doesn't seem to be able to retrieve the\n//                // application's icon by itself, so we have to use some Cocoa magic to get it and feed it to Growl.\n//                growl = new Growl(\"muCommander\", com.apple.cocoa.application.NSApplication.sharedApplication().applicationIconImage().TIFFRepresentation());\n//\n//                String notificationTypes[] = new String[]{\n//                        Translator.get(NOTIFICATION_KEYS[NOTIFICATION_TYPE_JOB_COMPLETED]),\n//                        Translator.get(NOTIFICATION_KEYS[NOTIFICATION_TYPE_JOB_ERROR])\n//                };\n//\n//                // Declare a list of available notification types\n//                growl.setAllowedNotifications(notificationTypes);\n//                // Declare a list of notification types enabled by default\n//                growl.setDefaultNotifications(notificationTypes);\n//\n//                // Commit everything\n//                growl.register();\n//\n//                AppLogger.fine(\"Application registered OK\");\n//\n//                return (isEnabled = true);\n//            }\n//            catch(Exception e) {\n//                AppLogger.fine(\"Exception thrown while initializing Growl support (Growl not running?)\", e);\n//            }\n//            catch(Error e) {\n//                AppLogger.fine(\"Error while initializing Growl support (cocoa-java not available?)\", e);\n//            }\n//\n//            growl = null;\n//            return (isEnabled = false);\n//        }\n//        else {\n//            return (isEnabled = false);\n//        }\n//    }\n//\n//    public boolean isEnabled() {\n//        return growl!=null && isEnabled;\n//    }\n//\n//    public boolean displayNotification(int notificationType, String title, String description) {\n//        AppLogger.finer(\"notificationType=\"+notificationType+\" title=\"+title+\" description=\"+description);\n//\n//        if(!isEnabled()) {\n//            AppLogger.fine(\"Ignoring notification, this notifier is not enabled\");\n//\n//            return false;\n//        }\n//\n//        try {\n//            growl.notifyGrowlOf(Translator.get(NOTIFICATION_KEYS[notificationType]), title, description);\n//            AppLogger.finer(\"Notification sent OK\");\n//\n//            return true;\n//        }\n//        catch(Exception e) {\n//            AppLogger.fine(\"Exception thrown while sending notification\", e);\n//\n//            return false;\n//        }\n//    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/notifier/NotificationType.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.notifier;\n\n/**\n * Defines the different kinds of notification.\n *\n * @author Maxence Bernard\n */\npublic enum NotificationType {\n\n    /** Used to indicate that a job has finished */\n    JOB_COMPLETED,\n\n    /** Used to indicate that a job has failed */\n    JOB_ERROR\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/notifier/SystemTrayNotifier.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.notifier;\n\nimport java.awt.Dimension;\nimport java.awt.Image;\nimport java.awt.Menu;\nimport java.awt.MenuItem;\nimport java.awt.PopupMenu;\nimport java.awt.SystemTray;\nimport java.awt.TrayIcon;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.awt.image.BufferedImage;\nimport java.util.Hashtable;\nimport java.util.Map;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.ui.action.AwtActionProxy;\nimport com.mucommander.ui.action.ActionManager;\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.action.impl.BringAllToFrontAction;\nimport com.mucommander.ui.action.impl.NewWindowAction;\nimport com.mucommander.ui.action.impl.QuitAction;\nimport com.mucommander.ui.icon.IconManager;\nimport com.mucommander.ui.main.WindowManager;\n\n/**\n * SystemTrayNotifier implements a notifier that uses the System Tray to display notifications. When enabled, this\n * notifier displays an icon in the system tray that recalls the current {@link com.mucommander.ui.main.MainFrame}\n * when double-clicked, or shows a popup menu with additional actions ('Bring all to front', 'Quit') when right-clicked.\n *\n * <p>This notifier is available only with Java 1.6 and up.\n *\n * @author Maxence Bernard\n */\npublic class SystemTrayNotifier extends AbstractNotifier implements ActionListener {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(SystemTrayNotifier.class);\n\t\n    /** TrayIcon being displayed in the system tray, null when this notifier is not enabled */\n    private TrayIcon trayIcon;\n\n    /** Is this notifier enabled ? */\n    private boolean isEnabled;\n\n    /** Name of the tray icon image */\n    private final static String TRAY_ICON_NAME = \"icon16_8.png\";\n\n    /** Width of the muCommander tray icon */\n    private final static int TRAY_ICON_WIDTH = 16;\n\n    /** Height of the muCommander tray icon */\n    private final static int TRAY_ICON_HEIGHT = 16;\n\n    /** System tray message types for the different notification types */\n    private final static Map<NotificationType, TrayIcon.MessageType> MESSAGE_TYPES;\n\n    static {\n        MESSAGE_TYPES = new Hashtable<>();\n\n        MESSAGE_TYPES.put(NotificationType.JOB_COMPLETED, TrayIcon.MessageType.INFO);\n        MESSAGE_TYPES.put(NotificationType.JOB_ERROR, TrayIcon.MessageType.ERROR);\n    }\n\n    SystemTrayNotifier() {\n    }\n\n    /**\n     * Creates and adds a menu item that triggers the MuAction denoted by the given Class. The menu item's label\n     * is set to the value returned by {@link TcAction#getLabel()}.\n     */\n    private void addMenuItem(Menu menu, String muActionId) {\n        TcAction action = ActionManager.getActionInstance(muActionId, WindowManager.getCurrentMainFrame());\n        MenuItem menuItem = new MenuItem(action.getLabel());\n        menuItem.addActionListener(new AwtActionProxy(action));\n        menu.add(menuItem);\n    }\n\n\n    @Override\n    public boolean setEnabled(boolean enabled) {\n        if (enabled) {\n            // No need to bother if the current Java runtime version is not 1.6 or up, or if SystemTray is not available\n            if (!SystemTray.isSupported()) {\n                return false;\n            }\n\n            // If System Tray has already been initialized\n            if (trayIcon != null) {\n                return (isEnabled = true);\n            }\n\n            SystemTray systemTray = SystemTray.getSystemTray();\n\n            // create the tray icon and disable image auto-size which shouldn't be used anyway but just in case\n            trayIcon = new TrayIcon(createIconImage(systemTray.getTrayIconSize()));\n            trayIcon.setImageAutoSize(false);\n            trayIcon.setPopupMenu(createPopupMenu());\n\n            // Add the tray icon to the system tray. If an exception is caught, clean things up and leave this notifier\n            // disabled.\n            try {\n                systemTray.add(trayIcon);\n                // Tray icon was added OK, listen to action events\n                trayIcon.addActionListener(this);\n\n                return (isEnabled = true);\n            } catch(java.awt.AWTException e) {\n                trayIcon = null;\n                return (isEnabled = false);\n            }\n        } else {\n            if (trayIcon != null) {\n                // Remove tray icon from the system tray\n                SystemTray.getSystemTray().remove(trayIcon);\n                trayIcon.removeActionListener(this);\n\n                trayIcon = null;\n            }\n\n            return (isEnabled = false);\n        }\n    }\n\n    private Image createIconImage(Dimension trayIconSize) {\n        Image iconImage = IconManager.getIcon(IconManager.IconSet.TROLCOMMANDER, TRAY_ICON_NAME).getImage();\n        // If the system tray icon size is larger than the icon size, center the icon as the default is to display\n        // the icon in the top left corner which is plain ugly\n        if (trayIconSize.width > TRAY_ICON_WIDTH || trayIconSize.height > TRAY_ICON_HEIGHT) {\n            // The buffered image uses ARGB for transparency\n            BufferedImage bi = new BufferedImage(trayIconSize.width, trayIconSize.height, BufferedImage.TYPE_INT_ARGB);\n            bi.getGraphics().drawImage(iconImage, (trayIconSize.width-TRAY_ICON_WIDTH)/2, (trayIconSize.height-TRAY_ICON_HEIGHT)/2, null);\n            iconImage = bi;\n        }\n        return iconImage;\n    }\n\n    @NotNull\n    private PopupMenu createPopupMenu() {\n        // create the popup (AWT!) menu. Note there is no way with java.awt.Menu to know when the menu is selected\n        // and thus it makes it hard to have contextual menu items such as the list of open windows.\n        PopupMenu menu = new PopupMenu();\n        addMenuItem(menu, NewWindowAction.Descriptor.ACTION_ID);\n        addMenuItem(menu, BringAllToFrontAction.Descriptor.ACTION_ID);\n        menu.addSeparator();\n        addMenuItem(menu, QuitAction.Descriptor.ACTION_ID);\n        return menu;\n    }\n\n    @Override\n    public boolean isEnabled() {\n        return trayIcon != null && isEnabled;\n    }\n\n    @Override\n    public boolean displayNotification(NotificationType notificationType, String title, String description) {\n        LOGGER.debug(\"notificationType={} title={} description={}\", notificationType, title, description);\n\n        if (!isEnabled()) {\n            LOGGER.debug(\"Ignoring notification, this notifier is not enabled\");\n            return false;\n        }\n\n        trayIcon.displayMessage(title, description, MESSAGE_TYPES.get(notificationType));\n        return true;\n    }\n\n    @Override\n    public String getPrettyName() {\n        return \"System Tray\";\n    }\n\n\n    @Override\n    public void actionPerformed(ActionEvent actionEvent) {\n        LOGGER.trace(\"caught SystemTray ActionEvent\");\n\n        WindowManager.getCurrentMainFrame().toFront();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/notifier/package.html",
    "content": "<body>\n  API for system-independent notification.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/popup/TcActionsPopupMenu.kt",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.popup\n\nimport com.mucommander.ui.action.ActionManager\nimport com.mucommander.ui.helper.MenuToolkit\nimport com.mucommander.ui.main.MainFrame\nimport ru.trolsoft.ui.TMenuSeparator\nimport javax.swing.Action\nimport javax.swing.JMenuItem\nimport javax.swing.JPopupMenu\n\n/**\n * Abstract class for popup menus that display MuActions.\n * \n * @author Maxence Bernard, Nicolas Rinaudo, Arik Hadas\n */\nabstract class TcActionsPopupMenu(\n    private val mainFrame: MainFrame\n) : JPopupMenu() {\n    /**\n     * Adds the MuAction denoted by the given ID to this popup menu, as a `JMenuItem`.\n     * @param actionId action ID\n     */\n    protected fun addAction(actionId: String?): JMenuItem {\n        return add(ActionManager.getActionInstance(actionId, mainFrame))\n    }\n\n    override fun add(a: Action?): JMenuItem {\n        val item = super.add(a)\n        MenuToolkit.configureActionMenuItem(item)\n        return item\n    }\n\n    override fun addSeparator() {\n        add(TMenuSeparator())\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/progress/ProgressTextField.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.progress;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n\n/**\n * A text fields which can display progress information, a la Mac OS X Safari's location bar, filling a portion of the\n * text field's background with a specified color.\n *\n * @author Maxence Bernard\n */\npublic class ProgressTextField extends JTextField {\n\n    /** Progress value, between 0 and 100 */\n    private int progressValue;\n\n    /** Background color used to symbolize progress */\n    private Color progressColor;\n\n    /**\n     * Creates a new ProgressTextField, using the given initial progress value\n     * and progress color which will be used as background color to show progress.\n     *\n     * @param initialProgressValue initial progress value, between 0 and 100\n     * @param progressColor background color used to symbolize progress\n     */\n    public ProgressTextField(int initialProgressValue, Color progressColor) {\n        this.progressValue = initialProgressValue;\n        this.progressColor = progressColor;\n    }\n\t\n\t\n    /**\n     * Sets current progress value and repaints this component.\n     *\n     * @param value current progress value, between 0 and 100.\n     */\n    public void setProgressValue(int value) {\n        this.progressValue = value;\n        repaint();\n    }\n\n    /**\n     * Returns current progress value, as displayed on the component.\n     *\n     * @return current progress value\n     */\n    public int getProgressValue() {\n        return progressValue;\n    }\n\n    /**\n     * Sets the color used to represent progress.\n     * @param color new progress color.\n     */\n    public void setProgressColor(Color color) {\n        if (color != null && progressColor != null && !color.equals(progressColor)) {\n            progressColor = color;\n            repaint();\n        }\n    }\n\t\n    /**\n     * Override JTextField's paint method to show progress information.\n     */\n    @Override\n    public void paint(Graphics g) {\n        super.paint(g);\n\n        if (progressValue > 0) {\n            g.setColor(progressColor);\n            g.fillRect(0, 0, (int)(getWidth()*progressValue/(float)100), getHeight());\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/progress/package.html",
    "content": "<body>\n  Various task progress related components.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/quicklist/QuickList.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.quicklist;\r\n\r\nimport java.awt.Color;\r\nimport java.awt.Component;\r\nimport java.awt.Dimension;\r\nimport java.awt.Insets;\r\nimport java.awt.Point;\r\nimport java.awt.event.FocusEvent;\r\nimport java.awt.event.FocusListener;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\nimport javax.swing.JMenuItem;\r\nimport javax.swing.JPopupMenu;\r\nimport javax.swing.border.LineBorder;\r\n\r\nimport com.mucommander.ui.quicklist.item.QuickListHeaderItem;\r\nimport com.mucommander.utils.text.Translator;\r\n\r\n/**\r\n * This abstract class contains some common features to all file table's popups:\r\n * 1. add HeaderMenuItem as the first item.\r\n * 2. set custom line border.\r\n * 3. does the calculations needed in order to show the popup in the center of the\r\n * \t  invoker FolderPanel. \r\n * \r\n * @author Arik Hadas\r\n */\r\n\r\npublic abstract class QuickList extends JPopupMenu implements FocusListener {\r\n\r\n\tprivate static final int PADDING = 2;\r\n\r\n\tprivate QuickListHeaderItem headerMenuItem;\r\n\tprivate final List<Component> items = new ArrayList<>();\r\n\tprivate final QuickListContainer container;\r\n\t\r\n\tQuickList(QuickListContainer container, String header) {\r\n\t\tsuper();\r\n\t\t\r\n\t\tthis.container = container;\r\n\t\t\r\n\t\tsetBorder(new PopupsBorder());\r\n\t\tadd(headerMenuItem = new QuickListHeaderItem(header));\r\n\t\tsetFocusTraversalKeysEnabled(false);\r\n\t}\r\n\t\r\n\tComponent nextFocusableComponent() {\r\n\t\treturn container.nextFocusableComponent();\r\n\t}\r\n\t\r\n\t/**\r\n\t * This function is called before showing quick-list.\r\n\t * If the return value is true, the quick list will be shown. Otherwise, it won't be shown.\r\n\t */\r\n\tprotected abstract boolean prepareForShowing(QuickListContainer container);\r\n\r\n\t@Override\r\n\t@SuppressWarnings(\"deprecated\")\r\n\tpublic void show() {\r\n//\tpublic void setVisible(boolean visible) {\r\n//\t\tif (!visible) {\r\n//\t\t\tsuper.setVisible(false);\r\n//\t\t\treturn;\r\n//\t\t}\r\n\t\tif (prepareForShowing(container)) {\r\n\t\t\t// Note: the actual popup menu's size is not known at this stage so we use the component's preferred size\r\n\t        Dimension dim = getPreferredSize();\r\n\r\n\t        Point location = container.calcQuickListPosition(dim);\r\n\t        \r\n\t        show(container.containerComponent(), location.x, location.y);\r\n\t        getFocus();\r\n\t\t}\r\n\t}\r\n\t\r\n\t@Override\r\n    public Component add(Component comp) {\r\n\t\titems.add(comp);\r\n\t\treturn super.add(comp);\r\n\t}\r\n\t\r\n\t@Override\r\n    public JMenuItem add(JMenuItem comp) {\r\n\t\titems.add(comp);\r\n\t\treturn super.add(comp);\r\n\t}\r\n\t\r\n\t@Override\r\n    public Dimension getPreferredSize() {\r\n\t\tdouble width = PADDING, height = PADDING;\r\n\r\n\t\tfor (Component item : items) {\r\n\t\t\twidth = Math.max(width, item.getPreferredSize().getWidth());\r\n\t\t\theight += item.getPreferredSize().getHeight();\r\n\t\t}\r\n\r\n\t\twidth = Math.ceil(Math.max(container == null ? 0 : container.getWidth() / 2.0, width * 1.05));\r\n\t\theight = Math.ceil(height);\r\n\t\treturn new Dimension((int)width, (int)height);\r\n\t}\r\n\r\n\tstatic protected String i18n(String key, String ...paramValues) {\r\n\t    return Translator.get(key, paramValues);\r\n    }\r\n\r\n\t@Override\r\n\tpublic void focusGained(FocusEvent arg0) {}\r\n\r\n\t@Override\r\n\tpublic void focusLost(FocusEvent arg0) {\r\n\t\tsetVisible(false);\t\t\r\n\t}\r\n\t\r\n\t/**\r\n\t * Get focus for the desired subcomponent. Only subclasses know which\r\n\t * component to focus, so they must implement it.\r\n\t */\r\n\tprotected abstract void getFocus();\r\n\t\r\n\tpublic static class PopupsBorder extends LineBorder {\r\n\t\tpublic PopupsBorder() {\r\n\t\t\tsuper(Color.gray);\t\t\r\n\t\t}\r\n\t\t\r\n\t\t@Override\r\n        public Insets getBorderInsets(Component c) {\r\n\t\t\treturn new Insets(1,1,1,1);\r\n\t\t}\r\n\t\t\r\n\t\t@Override\r\n        public Insets getBorderInsets(Component c, Insets i) {\r\n\t\t\treturn new Insets(1,1,1,1);\r\n\t\t}\r\n\t}\t\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/quicklist/QuickListContainer.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.quicklist;\n\nimport java.awt.Component;\nimport java.awt.Dimension;\nimport java.awt.Point;\n\n/**\n * \n * @author Arik Hadas\n */\npublic interface QuickListContainer {\n\t\n\tPoint calcQuickListPosition(Dimension dim);\n\t\n\tComponent containerComponent();\n\t\n\tComponent nextFocusableComponent();\n\t\n\tint getWidth();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/quicklist/QuickListWithDataList.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.quicklist;\r\n\r\nimport com.mucommander.ui.main.FolderPanel;\r\nimport com.mucommander.ui.quicklist.item.QuickListDataList;\r\nimport com.mucommander.ui.quicklist.item.QuickListDataModel;\r\n\r\nimport javax.swing.JScrollPane;\r\nimport javax.swing.SwingUtilities;\r\nimport java.awt.Dimension;\r\nimport java.awt.event.KeyEvent;\r\nimport java.awt.event.KeyListener;\r\n\r\n/**\r\n * FileTablePopupWithDataList is a FileTablePopup which contains FileTablePopupDataList.\r\n * \r\n * @author Arik Hadas\r\n */\r\n\r\npublic abstract class QuickListWithDataList<T> extends QuickList implements KeyListener {\r\n\tprotected QuickListDataList<T> dataList;\r\n\tprivate QuickListWithEmptyMsg emptyPopup;\r\n\r\n\r\n    private boolean supportDeleteItem;\r\n\t\r\n\tpublic QuickListWithDataList(QuickListContainer container, String header, String emptyPopupHeader) {\r\n\t\tsuper(container, header);\r\n\r\n\t\t// get the TablePopupDataList.\r\n\t\tdataList = getList();\r\n\r\n\t\t// add JScrollPane that contains the TablePopupDataList to the popup.\r\n\t\tJScrollPane scroll = new JScrollPane(dataList,\r\n\t\t\t\tJScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,\r\n\t\t\t\tJScrollPane.HORIZONTAL_SCROLLBAR_NEVER) {\r\n            @Override\r\n            public Dimension getPreferredSize() {\r\n                // default we have quicklist height for 10 lines maximum\r\n                // calculate new height to better filling\r\n                if (container instanceof FolderPanel folderPanel) {\r\n                    Dimension parentSize = folderPanel.getPanel().getSize();\r\n                    Dimension preferredSize = dataList.getPreferredSize();\r\n                    return new Dimension(super.getPreferredSize().width,\r\n                            Math.min(preferredSize.height, parentSize.height * 8 / 10));\r\n                }\r\n                return super.getPreferredSize();\r\n            }\r\n        };\r\n\t\tscroll.setBorder(null);\r\n\t\tscroll.getVerticalScrollBar().setFocusable(false);\r\n        scroll.getHorizontalScrollBar().setFocusable(false);\r\n        add(scroll);\r\n        \r\n        dataList.addFocusListener(this);\r\n        \r\n        // create TablePopupWithEmptyMsg that will be shown instead of this popup, if this\r\n        // popup's data list won't have any elements.\r\n        emptyPopup = new QuickListWithEmptyMsg(container, header, emptyPopupHeader);\r\n        dataList.addKeyListener(this);\r\n\t}\r\n\t\r\n\tprotected abstract T[] getData();\r\n\t\r\n\t/**\r\n\t * This function will be called when an element from the data list will be selected.\r\n\t * \r\n\t * @param item - The selected item from the data list.\r\n\t */\r\n\tpublic void itemSelected(T item) {\r\n\t\tsetVisible(false);\r\n\t\tacceptListItem(item);\r\n\t}\t\t\r\n\t\r\n\t@Override\r\n    protected boolean prepareForShowing(QuickListContainer container) {\r\n\t\tboolean toShow = false;\r\n\t\t// if data list contains at least 1 element, show this popup.\r\n\t\tT[] data = getData();\r\n\t\tif (data.length > 0) {\r\n\t\t\tdataList.setListData(data);\r\n\t\t\ttoShow = true;\r\n\t\t} else {        // else, show popup with a \"no elements\" message.\r\n            emptyPopup.show();\r\n        }\r\n\t\t\r\n\t\treturn toShow;\r\n\t}\r\n\r\n\t@Override\r\n\tprotected void getFocus() {\r\n\t\t// to overcome #552 (right recentQL not focused) both must be used:\r\n\t\t// invokeLater and requestFocus (requestFocusInWindow is not sufficient)\r\n\t\tSwingUtilities.invokeLater(dataList::requestFocus);\r\n\t}\r\n\t\r\n\t/**\r\n\t * This function defines what should be done with a selected item from the data list.\r\n\t * \r\n\t * @param item - The selected item from the data list.\r\n\t */\r\n\tprotected abstract void acceptListItem(T item);\r\n\t\r\n\tprotected abstract QuickListDataList<T> getList();\r\n\r\n    protected void deleteListItem(int index, T item) {\r\n        if (!supportDeleteItem) {\r\n            return;\r\n        }\r\n        onDeleteItem(index, item);\r\n        QuickListDataModel model = (QuickListDataModel)dataList.getModel();\r\n        model.remove(index);\r\n\r\n        int selectedIndex = index;\r\n        if (selectedIndex >= model.getSize()) {\r\n            selectedIndex--;\r\n        }\r\n        dataList.setSelectedIndex(selectedIndex);\r\n        dataList.repaint();\r\n    }\r\n\r\n    protected void onDeleteItem(int index, T item) {\r\n    }\r\n\r\n    @Override\r\n    public void keyTyped(KeyEvent e) {\r\n    }\r\n\r\n    @Override\r\n    public void keyPressed(KeyEvent e) {\r\n        if (dataList == null) {\r\n            return;\r\n        }\r\n        int selectedIndex = dataList.getSelectedIndex();\r\n        int lastIndex = dataList.getModel().getSize() - 1;\r\n        if (e.getExtendedKeyCode() == KeyEvent.VK_UP && selectedIndex == 0) {\r\n            dataList.setSelectedIndex(lastIndex);\r\n            dataList.ensureIndexIsVisible(lastIndex);\r\n            e.consume();\r\n        } else if (e.getExtendedKeyCode() == KeyEvent.VK_DOWN && dataList.getSelectedIndex() == lastIndex) {\r\n            dataList.setSelectedIndex(0);\r\n            dataList.ensureIndexIsVisible(0);\r\n            e.consume();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void keyReleased(KeyEvent e) {\r\n        if (supportDeleteItem && e.getKeyCode() == KeyEvent.VK_DELETE) {\r\n            int index = dataList.getSelectedIndex();\r\n            if (index >= 0 && dataList.getModel().getSize() > 0) {\r\n                deleteListItem(index, dataList.getSelectedValue());\r\n            }\r\n        }\r\n    }\r\n\r\n    protected boolean isSupportDeleteItem() {\r\n        return supportDeleteItem;\r\n    }\r\n\r\n    protected void setSupportDeleteItem(boolean supportDeleteItem) {\r\n        this.supportDeleteItem = supportDeleteItem;\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/quicklist/QuickListWithEmptyMsg.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.quicklist;\r\n\r\nimport java.awt.event.KeyAdapter;\r\nimport java.awt.event.KeyEvent;\r\n\r\nimport javax.swing.SwingUtilities;\r\n\r\nimport com.mucommander.ui.quicklist.item.QuickListEmptyMessageItem;\r\n\r\n/**\r\n * FileTablePopupWithEmptyMsg is a FileTablePopup which contains EmptyMessageItem.\r\n * \r\n * @author Arik Hadas\r\n */\r\n\r\nclass QuickListWithEmptyMsg extends QuickList {\r\n\tprotected QuickListEmptyMessageItem emptyMenuItem;\r\n\t\r\n\tpublic QuickListWithEmptyMsg(QuickListContainer container, String header, String emptyPopupHeader) {\r\n\t\tsuper(container, header);\r\n\t\t\r\n\t\tadd(emptyMenuItem = new QuickListEmptyMessageItem(emptyPopupHeader));\r\n\t\t\r\n\t\taddKeyListenerToList();\r\n\t\taddFocusListener(this);\r\n\t}\r\n\t\r\n\t@Override\r\n    protected boolean prepareForShowing(QuickListContainer container) {\r\n\t\tgetFocus();\r\n\t\treturn true;\r\n\t}\r\n\t\r\n\t@Override\r\n\tpublic void getFocus(){\r\n\t\tSwingUtilities.invokeLater(this::requestFocus);\r\n\t}\r\n\t\r\n\tprivate void addKeyListenerToList() {\t\t\r\n\t\taddKeyListener(new KeyAdapter() {\r\n\r\n\t\t\tpublic void keyPressed(KeyEvent e) {\r\n\t\t\t\tswitch(e.getKeyCode()) {\t\t\t\t\r\n\t\t\t\tdefault:\r\n\t\t\t\t\tnextFocusableComponent().requestFocus();\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\t\t\t\t\r\n\t\t\t}\r\n\t\t});\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/quicklist/QuickListWithIcons.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.quicklist;\r\n\r\nimport java.awt.Dimension;\r\nimport java.awt.Image;\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\n\r\nimport javax.swing.Icon;\r\nimport javax.swing.ImageIcon;\r\nimport javax.swing.event.PopupMenuEvent;\r\nimport javax.swing.event.PopupMenuListener;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.ui.icon.CustomFileIconProvider;\r\nimport com.mucommander.ui.icon.FileIcons;\r\nimport com.mucommander.ui.icon.IconManager;\r\nimport com.mucommander.ui.icon.SpinningDial;\r\nimport com.mucommander.ui.quicklist.item.QuickListDataList;\r\nimport com.mucommander.ui.quicklist.item.QuickListDataListWithIcons;\r\n\r\n/**\r\n * FileTablePopupWithIcons is a FileTablePopupWithDataList in which the data list \r\n * contains icons.\r\n * \r\n * @author Arik Hadas\r\n */\r\n\r\npublic abstract class QuickListWithIcons<T> extends QuickListWithDataList<T> {\r\n    /**\r\n     * This Map's keys are items and its objects are the corresponding icon.\r\n     */\r\n\tprivate final Map<T, Icon> itemToIconCacheMap = new HashMap<>();\r\n    /**\r\n     * This SpinningDial will appear until the icon fetching of an item is over.\r\n     */\r\n\tprivate static final SpinningDial WAITING_ICON = new SpinningDial();\r\n    /**\r\n     * If the icon fetching fails for some item, the following icon will appear for it.\r\n     */\r\n\tprivate static final Icon NOT_AVAILABLE_ICON = IconManager.getIcon(IconManager.IconSet.FILE, CustomFileIconProvider.NOT_ACCESSIBLE_FILE);\r\n\t/**\r\n\t * Saves the number of waiting-icons (SpinningDials) appearing in the list.\r\n\t */\r\n\tprivate int numOfWaitingIconInList;\r\n\t\r\n\tpublic QuickListWithIcons(QuickListContainer container, String header, String emptyPopupHeader) {\r\n\t\tsuper(container, header, emptyPopupHeader);\r\n\t\tnumOfWaitingIconInList = 0;\r\n\t\taddPopupMenuListener(new PopupMenuListener() {\r\n\r\n\t\t\tpublic void popupMenuCanceled(PopupMenuEvent e) {}\r\n\r\n\t\t\tpublic void popupMenuWillBecomeInvisible(PopupMenuEvent e) {\r\n\t\t\t    onHide();\r\n            }\r\n\r\n\t\t\tpublic void popupMenuWillBecomeVisible(PopupMenuEvent e) {\r\n\t\t\t\tonShow();\r\n\t\t\t}\r\n\t\t});\r\n\t}\r\n\r\n    protected void onShow() {\r\n        // Clear icon-caching before opening popup-list in order to let the icons be fetched again.\r\n        itemToIconCacheMap.clear();\r\n    }\r\n\r\n    protected void onHide() {}\r\n\r\n\r\n    /**\r\n\t * Called when waitingIcon is added to the list.\r\n\t */\r\n\tprivate synchronized void waitingIconAddedToList() {\r\n\t\t// If there was no other waitingIcon in the list before current addition - start the spinning dial.\r\n\t\tif (numOfWaitingIconInList++ == 0) {\r\n\t\t\tWAITING_ICON.setAnimated(true);\r\n\t\t}\r\n\t}\r\n\t\r\n\t/**\r\n\t * Called when waitingIcon is removed from the list.\r\n\t */\r\n\tprivate synchronized void waitingIconRemovedFromList() {\r\n\t\t// If after current remove operation, there will be no waitingIcon in the list - stop the spinning dial.\r\n\t\tif (--numOfWaitingIconInList == 0) {\r\n\t\t\tWAITING_ICON.setAnimated(false);\r\n\t\t}\r\n\t}\r\n\t\r\n\t@Override\r\n    protected QuickListDataList<T> getList() {\r\n\t\treturn new QuickListDataListWithIcons<T>(nextFocusableComponent()) {\r\n\t\t\t@Override\r\n            public Icon getImageIconOfItem(T item,  final Dimension preferredSize) {\r\n\t\t\t\treturn getImageIconOfItemImp(item, preferredSize);\r\n\t\t\t}\r\n\t\t};\r\n\t}\r\n\t\r\n\t/**\r\n\t * This function gets an item from the data list and return its icon.\r\n\t *  \r\n\t * @param item a list item\r\n     * @return an icon for the specified item\r\n\t */\r\n\tprotected abstract Icon itemToIcon(T item);\r\n\t\r\n\t/**\r\n\t * This function return an icon for the specified file.\r\n\t * \r\n\t * @param file the file for which to return an icon\r\n\t * @return the specified file's icon. null is returned if the file does not exist\r\n\t */\r\n\tprotected Icon getIconOfFile(AbstractFile file) {\r\n\t\treturn (file != null && file.exists()) ?\r\n\t\t\tIconManager.getImageIcon(FileIcons.getFileIcon(file)) : null; \r\n\t}\r\n\t\r\n\tprotected Icon getImageIconOfItemImp(final T item,  final Dimension preferredSize) {\r\n\t\tboolean found;\r\n\t\tsynchronized(itemToIconCacheMap) {\r\n\t\t\tif (!(found = itemToIconCacheMap.containsKey(item))) {\r\n\t\t\t\titemToIconCacheMap.put(item, WAITING_ICON);\r\n\t\t\t\twaitingIconAddedToList();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tIcon result = itemToIconCacheMap.get(item);\r\n\r\n\t\tif (!found)\r\n\t\t\tnew Thread(() -> {\r\n                Icon icon = itemToIcon(item);\r\n                // If the item does not exist or is not accessible, show notAvailableIcon for it.\r\n                itemToIconCacheMap.put(item, icon != null ? icon : NOT_AVAILABLE_ICON);\r\n                waitingIconRemovedFromList();\r\n                repaint();\r\n            }).start();\r\n\t\t\r\n\t\treturn resizeIcon(result, preferredSize);\r\n\t}\r\n\r\n\tprivate Icon resizeIcon(Icon icon, final Dimension preferredSize) {\r\n\t\tif (icon instanceof ImageIcon) {\r\n\t\t\tImage image = ((ImageIcon) icon).getImage();\r\n\t\t\tfinal double height = preferredSize.getHeight();\r\n\t\t\tfinal double width = (height / icon.getIconHeight()) * icon.getIconWidth();\r\n\t\t\tImage scaledImage = image.getScaledInstance((int)width, (int)height, Image.SCALE_SMOOTH);\r\n\t\t\treturn new ImageIcon(scaledImage);\r\n\t\t}\r\n\r\n\t\treturn icon;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/quicklist/QuickListWithoutIcons.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.quicklist;\r\n\r\nimport com.mucommander.ui.quicklist.item.QuickListDataList;\r\n\r\n/**\r\n * FileTablePopupWithoutIcons is a FileTablePopupWithDataList in which the data list \r\n * \tdoesn't contain icons.\r\n * \r\n * @author Arik Hadas\r\n */\r\n\r\npublic abstract class QuickListWithoutIcons<T> extends QuickListWithDataList<T> {\r\n\r\n\tpublic QuickListWithoutIcons(QuickListContainer container, String header, String emptyPopupHeader) {\r\n\t\tsuper(container, header, emptyPopupHeader);\r\n\t}\r\n\t\r\n\t@Override\r\n    protected QuickListDataList<T> getList() { return new QuickListDataList<>(nextFocusableComponent()); }\r\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/quicklist/item/QuickListDataList.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.quicklist.item;\n\nimport java.awt.*;\nimport java.awt.event.KeyEvent;\nimport java.awt.event.MouseAdapter;\nimport java.awt.event.MouseEvent;\n\nimport javax.swing.DefaultListCellRenderer;\nimport javax.swing.JList;\nimport javax.swing.text.Position;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.ui.icon.IconManager;\nimport com.mucommander.ui.main.WindowManager;\nimport com.mucommander.ui.main.table.CellLabel;\nimport com.mucommander.ui.quicklist.QuickListWithDataList;\nimport com.mucommander.ui.quicksearch.QuickSearch;\nimport com.mucommander.ui.theme.ColorChangedEvent;\nimport com.mucommander.ui.theme.FontChangedEvent;\nimport com.mucommander.ui.theme.ThemeCache;\nimport com.mucommander.ui.theme.ThemeData;\nimport com.mucommander.ui.theme.ThemeListener;\nimport com.mucommander.ui.theme.ThemeManager;\n\n/**\n * This class represent a data list for FileTablePopupWithDataList.\n * \n * @author Arik Hadas\n */\n\npublic class QuickListDataList<T> extends JList<T> {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(QuickListDataList.class);\n\t\n\tprivate final static int VISIBLE_ROWS_COUNT = 10;\n\n\t@Getter\n    private final QuickSearch quickSearch = new QuickListQuickSearch();\n\t\n\tprivate final Component nextFocusableComponent;\n\n\tpublic QuickListDataList(Component nextFocusableComponent){\n\t\tthis.nextFocusableComponent = nextFocusableComponent;\n\t\t\n\t\tsetFocusTraversalKeysEnabled(false);\n\n\t\taddMouseListenerToList();\n\n\t\tDataListItemRenderer itemRenderer = getItemRenderer();\n\t\tThemeManager.addCurrentThemeListener(itemRenderer);\n\t\tsetCellRenderer(itemRenderer);\n\t}\n\n\tpublic QuickListDataList(T[] data) {\n\t\tthis(new Component() {});\n\t\tsetListData(data);\n\t}\n\n\tprotected DataListItemRenderer getItemRenderer() {\n\t\treturn new DataListItemRenderer();\n\t}\n\n    /**\n\t * This function is called before showing TablePopupWithDataList.\n\t * It does the required steps before the popup is shown.\t\n\t */\n\t@Override\n\tpublic void setListData(final T[] data) {\n\t\t//super.setListData(data);\n        setModel(new QuickListDataModel<>(data));\n\n        int numOfRowsInList = getModel().getSize();\n\t\tif (numOfRowsInList > 0) {\n\t\t\tsetVisibleRowCount(Math.min(numOfRowsInList, VISIBLE_ROWS_COUNT));\n\t\t\tsetSelectedIndex(0);\n\t\t\tensureIndexIsVisible(0);\n\t\t}\n\t}\n\n\tprotected T getListItem(int index) {\n\t\tif (index > getModel().getSize() || index < 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn getModel().getElementAt(index);\n\t}\n\n\tprotected void addMouseListenerToList() {\n\t\taddMouseListener(new MouseAdapter() {\n\n\t\t\tpublic void mouseClicked(MouseEvent e) {\n\t\t\t\t// If there was double click on item of the popup's list, \n\t\t\t\t// select it, and update the text component.\n\t\t\t\tif (e.getClickCount() == 2) {\n\t\t\t\t\tint index = locationToIndex(e.getPoint());\n\t\t\t\t\tsetSelectedIndex(index);\n\t\t\t\t\t((QuickListWithDataList<T>)(getParent().getParent().getParent())).itemSelected(getSelectedValue());\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\tpublic void setForegroundColors(Color foreground, Color selectedForeground) {\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tDataListItemRenderer cellRenderer = (DataListItemRenderer) getCellRenderer();\n\t\tcellRenderer.setItemForeground(foreground);\n\t\tcellRenderer.setSelectedItemForeground(selectedForeground);\n\t}\n\n\tpublic void setBackgroundColors(Color background, Color selectedBackground) {\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tDataListItemRenderer cellRenderer = (DataListItemRenderer) getCellRenderer();\n\t\tcellRenderer.setItemBackground(background);\n\t\tcellRenderer.setSelectedItemBackground(selectedBackground);\n\t}\n\n\tprotected class DataListItemRenderer extends DefaultListCellRenderer implements ThemeListener {\n\n\t\t@Setter\n        private Color selectedItemBackground = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_SELECTED_ITEM_BACKGROUND_COLOR);\n\t\t@Setter\n        private Color selectedItemForeground = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_SELECTED_ITEM_FOREGROUND_COLOR);\n\t\t@Setter\n        private Color itemBackground = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_ITEM_BACKGROUND_COLOR);\n\t\t@Setter\n        private Color itemForeground = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_ITEM_FOREGROUND_COLOR);\n\n\t\tprivate Font itemFont = ThemeManager.getCurrentFont(ThemeData.QUICK_LIST_ITEM_FONT);\n\n\t\tDataListItemRenderer() { }\n\n\t\t@Override\n\t\tpublic Component getListCellRendererComponent(JList list, Object value, int rowIndex, boolean isSelected, boolean cellHasFocus) {\n\t\t\t// Let superclass deal with most of it...\n\t\t\tsuper.getListCellRendererComponent(list, value, rowIndex, isSelected, cellHasFocus);\n\n\t\t\tT item = getListItem(rowIndex);\n\n\t\t\tCellLabel label = new CellLabel();\n\t\t\tif (item == null) {\n\t\t\t\tLOGGER.debug(\"tableModel.getCachedFileAtRow({}) RETURNED NULL !\", rowIndex);\n\t\t\t\treturn label;\n\t\t\t}\n\n\t\t\tQuickSearch search = QuickListDataList.this.getQuickSearch();\n\t\t\tboolean matches = !search.isActive() || search.matches(item.toString());\n\n\t\t\tlabel.setFont(itemFont);\n\n\t\t\tlabel.setText(item.toString());\n\t\t\t//label.setToolTipText(\"\"+item);\n\n\t\t\t// Set background color depending on whether the row is selected or not, and whether the table has focus or not\n\t\t\tif (isSelected) {\n\t\t\t\tlabel.setBackground(selectedItemBackground);\n\t\t\t\tlabel.setForeground(selectedItemForeground);\n\t\t\t} else {\n\t\t\t\tlabel.setBackground(matches ? itemBackground : ThemeCache.unmatchedBackground);\n\t\t\t\tlabel.setForeground(matches ? itemForeground : ThemeCache.unmatchedForeground);\n\t\t\t}\n\n\t\t\treturn label;\n\t\t}\n\n\n        @Override\n\t\tpublic void colorChanged(ColorChangedEvent event) {\n            if (event.getColorId() == ThemeData.QUICK_LIST_ITEM_BACKGROUND_COLOR) {\n                itemBackground = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_ITEM_BACKGROUND_COLOR);\n            } else if (event.getColorId() == ThemeData.QUICK_LIST_ITEM_FOREGROUND_COLOR) {\n                itemForeground = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_ITEM_FOREGROUND_COLOR);\n            } else if (event.getColorId() == ThemeData.QUICK_LIST_SELECTED_ITEM_BACKGROUND_COLOR) {\n                selectedItemBackground = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_SELECTED_ITEM_BACKGROUND_COLOR);\n            } else if (event.getColorId() == ThemeData.QUICK_LIST_SELECTED_ITEM_FOREGROUND_COLOR) {\n                selectedItemForeground = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_SELECTED_ITEM_FOREGROUND_COLOR);\n            }\n\t\t}\n\n\t\tpublic void fontChanged(FontChangedEvent event) {\n\t\t\titemFont = ThemeManager.getCurrentFont(ThemeData.QUICK_LIST_ITEM_FONT);\t\t\n\t\t}\n\t}\n\n\tpublic class QuickListQuickSearch extends QuickSearch {\n\n\t\tpublic QuickListQuickSearch() {\n\t\t\tsuper(QuickListDataList.this);\n\t\t}\n\n\t\t@Override\n\t\tprotected void searchStarted() {\n\t\t}\n\n\t\t@Override\n\t\tprotected void searchStopped() {\n\t\t\tWindowManager.getCurrentMainFrame().getStatusBar().updateSelectedFilesInfo();\n\t\t\tQuickListDataList.this.repaint();\n\t\t}\n\n\t\t@Override\n\t\tprotected int getNumOfItems() {\n\t\t\treturn QuickListDataList.this.getModel().getSize();\n\t\t}\n\n\t\t@Override\n\t\tprotected String getItemString(int index) {\n\t\t\treturn getListItem(index).toString();\n\t\t}\n\n\t\t@Override\n\t\tprotected void searchStringBecameEmpty(String searchString) {\n\t\t\tWindowManager.getCurrentMainFrame().getStatusBar().setStatusInfo(searchString); // TODO: is needed?\n\t\t}\n\n\t\t@Override\n\t\tprotected void matchFound(int row, String searchString, boolean itsBestMatch) {\n\t\t\tif (row != getSelectedIndex()) {\n\t\t\t\tsetSelectedIndex(row);\n\t\t\t\tensureIndexIsVisible(row);\n\t\t\t}\n\t\t\t\n\t\t\t// Display the new search string in the status bar\n            // that indicates that the search has yielded a match\n\t\t\tWindowManager.getCurrentMainFrame().getStatusBar().setStatusInfo(searchString, IconManager.getIcon(IconManager.IconSet.STATUS_BAR, QUICK_SEARCH_OK_ICON), false);\n\t\t}\n\n\t\t@Override\n\t\tprotected void matchNotFound(String searchString) {\n\t\t\t// No file matching the search string, display the new search string with an icon\n            // that indicates that the search has failed\n\t\t\tWindowManager.getCurrentMainFrame().getStatusBar().setStatusInfo(searchString, IconManager.getIcon(IconManager.IconSet.STATUS_BAR, QUICK_SEARCH_KO_ICON), false);\n\t\t}\n\n\t\t@Override\n\t\tpublic synchronized void keyPressed(KeyEvent e) {\n\t\t\tint keyCode = e.getKeyCode();\n\n\t\t\t// If quick search is not active...\n\t\t\tif (!isActive()) {\n\t\t\t\t// Return (do not start quick search) if the key is not a valid quick search input\n\t\t\t\tif (!isValidQuickSearchInput(e)) {\n\t\t\t\t\tif (keyCode == KeyEvent.VK_ESCAPE || keyCode == KeyEvent.VK_ENTER) {\n\t\t\t\t\t\ttryToTransferFocusToTheNextComponent();\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tif (keyCode == KeyEvent.VK_ENTER) {\n\t\t\t\t\t\tContainer c = getParent().getParent().getParent();\n\t\t\t\t\t\t((QuickListWithDataList<T>)(c)).itemSelected(getSelectedValue());\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Start the quick search and continue to process the current key event\n\t\t\t\tstart();\n\t\t\t}\n\n\t\t\t// At this point, quick search is active\n\t\t\tboolean keyHasModifiers = (e.getModifiersEx()&(KeyEvent.SHIFT_DOWN_MASK|KeyEvent.ALT_DOWN_MASK|KeyEvent.CTRL_DOWN_MASK|KeyEvent.META_DOWN_MASK))!=0;\n\n\t\t\t// Backspace removes the last character of the search string\n\t\t\tif (keyCode == KeyEvent.VK_BACK_SPACE && !keyHasModifiers) {\n\t\t\t\t// Search string is empty already\n\t\t\t\tif (isSearchStringEmpty()) {\n                    return;\n                }\n\n\t\t\t\tremoveLastCharacterFromSearchString();\n\n\t\t\t\t// Find the row that best matches the new search string and select it\n\t\t\t\tfindMatch(0, true, true);\n\t\t\t}\n\t\t\t// Escape immediately cancels the quick search\n\t\t\telse if (keyCode == KeyEvent.VK_ESCAPE && !keyHasModifiers) {\n\t\t\t\tstop();\n\t\t\t}\n\t\t\t// Up/Down jumps to previous/next match\n\t\t\t// Shift+Up/Shift+Down marks currently selected file and jumps to previous/next match\n\t\t\telse if ((keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN) && !keyHasModifiers) {\n\t\t\t\t// Find the first row before/after the current row that matches the search string\n\t\t\t\tboolean down = keyCode == KeyEvent.VK_DOWN;\n\t\t\t\tfindMatch(getSelectedIndex() + (down ? 1 : -1), down, false);\n\t\t\t}\n\t\t\t// If no modifier other than Shift is pressed and the typed character is not a control character (space is ok)\n\t\t\t// and a valid Unicode character, add it to the current search string\n\t\t\telse if (isValidQuickSearchInput(e)) {\n\t\t\t\tappendCharacterToSearchString(e.getKeyChar());\n\n\t\t\t\t// Find the row that best matches the new search string and select it\n\t\t\t\tfindMatch(0, true, true);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tswitch(e.getKeyCode()) {\n\t\t\t\tcase KeyEvent.VK_UP:\n\t\t\t\t{\n\t\t\t\t\tint numOfItems = getModel().getSize();\t\t\t\t\n\t\t\t\t\tif (numOfItems > 0 && getSelectedIndex() == 0) {\n\t\t\t\t\t\tsetSelectedIndex(numOfItems - 1);\n\t\t\t\t\t\tensureIndexIsVisible(numOfItems - 1);\n\t\t\t\t\t\te.consume();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t\tcase KeyEvent.VK_DOWN:\n\t\t\t\t{\n\t\t\t\t\tint numOfItems = getModel().getSize();\n\t\t\t\t\tif (numOfItems > 0 && getSelectedIndex() == numOfItems - 1) {\t\t\t\t\n\t\t\t\t\t\tsetSelectedIndex(0);\n\t\t\t\t\t\tensureIndexIsVisible(0);\n\t\t\t\t\t\te.consume();\n\t\t\t\t\t}\t\t\t\t\t\t\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t\tcase KeyEvent.VK_ENTER:\n\t\t\t\t\ttryToTransferFocusToTheNextComponent();\n\t\t\t\t\t((QuickListWithDataList<T>)(getParent().getParent().getParent())).itemSelected(getSelectedValue());\n\t\t\t\t\tstop();\n\t\t\t\t\tbreak;\n\t\t\t\tcase KeyEvent.VK_TAB:\n\t\t\t\t\ttryToTransferFocusToTheNextComponent();\n\t\t\t\t\tstop();\n\t\t\t\t\tbreak;\n\n\t\t\t\t}\n\n\t\t\t\t// Do not update last search string's change timestamp\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Update last search string's change timestamp\n\t\t\tsetLastSearchStringChange(System.currentTimeMillis());\n\n\t\t\te.consume();\n\t\t}\n\t\t\n\t\tprivate void tryToTransferFocusToTheNextComponent() {\n\t\t\tif (nextFocusableComponent != null)\n\t\t\t\tnextFocusableComponent.requestFocus();\n\t\t}\n\t}\n\n\n    @Override\n    public int getNextMatch(String prefix, int startIndex, Position.Bias bias) {\n        // This method is overridden to prevent the swing native search\n        return -1;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/quicklist/item/QuickListDataListWithIcons.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.quicklist.item;\n\n\nimport java.awt.Component;\nimport java.awt.Dimension;\n\nimport javax.swing.Icon;\nimport javax.swing.JList;\n\nimport com.mucommander.ui.main.table.CellLabel;\n\n/**\n * \n * @author Arik Hadas\n */\npublic abstract class QuickListDataListWithIcons<T> extends QuickListDataList<T> {\n\t\n\tpublic QuickListDataListWithIcons(Component nextFocusableComponent) {\n\t\tsuper(nextFocusableComponent);\n\t}\n\t\n\tpublic QuickListDataListWithIcons(T[] data) {\n\t\tsuper(data);\n\t}\n\t\n\t@Override\n\tprotected DataListItemRenderer getItemRenderer() {\n\t\treturn new DataListItemWithIconRenderer();\n\t}\n\t\n\t//////////////////////\n\t// Abstract methods //\n\t//////////////////////\n\n\tpublic abstract Icon getImageIconOfItem(final T item, final Dimension preferredSize);\n\n\tprotected class DataListItemWithIconRenderer extends DataListItemRenderer {\n\t\t\n\t\t@Override\n        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {\n\t\t\t// Let superclass deal with most of it...\n\t\t\tCellLabel label = (CellLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);\n\n\t\t\t// Add its icon\n\t\t\tT item = getListItem(index);\n\t\t\tIcon icon = getImageIconOfItem(item, this.getPreferredSize());\n\t\t\tlabel.setIcon(icon);\n\n\t\t\treturn label;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/quicklist/item/QuickListDataModel.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.quicklist.item;\n\nimport javax.swing.AbstractListModel;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * @author Oleg Trifonov\n * Created on 17/10/14.\n */\npublic class QuickListDataModel<T> extends AbstractListModel<T> {\n\n    private List<T> data = new ArrayList<>();\n\n    public QuickListDataModel(T[] data) {\n        super();\n        Collections.addAll(this.data, data);\n    }\n    @Override\n    public int getSize() {\n        return data.size();\n    }\n\n    @Override\n    public T getElementAt(int index) {\n        return data.get(index);\n    }\n\n    public void remove(int index) {\n        data.remove(index);\n    }\n\n    public void remove(T item) {\n        data.remove(item);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/quicklist/item/QuickListEmptyMessageItem.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.quicklist.item;\n\nimport com.mucommander.ui.theme.ColorChangedEvent;\nimport com.mucommander.ui.theme.FontChangedEvent;\nimport com.mucommander.ui.theme.ThemeData;\nimport com.mucommander.ui.theme.ThemeManager;\n\nimport java.awt.*;\n\n/**\n * This class represent an item that will be shown in a QuickList which doesn't\n * contain any elements, with a relevant message.\n * \n * @author Arik Hadas\n */\n\npublic class QuickListEmptyMessageItem extends QuickListItem {\n\t\n\tprotected Color foreground;\n\tprotected Color background;\n\t\n\tpublic QuickListEmptyMessageItem(String text) {\n\t\tsuper(text);\n\t\tforeground = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_ITEM_FOREGROUND_COLOR);\n\t\tbackground = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_ITEM_BACKGROUND_COLOR);\n\t\tsetFont(ThemeManager.getCurrentFont(ThemeData.QUICK_LIST_ITEM_FONT));\n\t}\n\t\n\t@Override\n    protected final void paintComponent(Graphics g) {\n\t\tGraphics2D graphics = (Graphics2D) g;\n\t\t\n\t\t// paint the background of this item with lightGray color\n\t\tgraphics.setColor(background);\n\t\tgraphics.fillRect(0, 0, getWidth(), getHeight());\n\n\t\t// draw message:\t\t\n\t\tgraphics.setFont(mFont);\n\t\tgraphics.setColor(foreground);\n\t\tgraphics.drawString(getText(), X_AXIS_OFFSET, (int) graphics.getFontMetrics().getLineMetrics(this.getText(), graphics).getHeight());\n\t}\n\n\t@Override\n    public void colorChanged(ColorChangedEvent event) {\n\t\tif (event.getColorId() == ThemeData.QUICK_LIST_ITEM_BACKGROUND_COLOR)\n\t\t\tbackground = event.getColor();\n\t\t\t\n\t\telse if (event.getColorId() == ThemeData.QUICK_LIST_ITEM_FOREGROUND_COLOR)\n\t\t\tforeground = event.getColor();\n\t}\n\t\n\t@Override\n    public void fontChanged(FontChangedEvent event) {\n\t\tsetFont(ThemeManager.getCurrentFont(ThemeData.QUICK_LIST_ITEM_FONT));\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/quicklist/item/QuickListHeaderItem.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.quicklist.item;\n\nimport com.mucommander.ui.theme.ColorChangedEvent;\nimport com.mucommander.ui.theme.FontChangedEvent;\nimport com.mucommander.ui.theme.ThemeData;\nimport com.mucommander.ui.theme.ThemeManager;\n\nimport java.awt.*;\nimport java.awt.image.BufferedImage;\n\n/**\n * HeaderMenuItem is a custom MenuItem that shown as the first item in every QuickList.\n * \n * @author Arik Hadas\n */\npublic class QuickListHeaderItem extends QuickListItem {\n\t\n\tprotected Color foreground;\n\tprotected Color background;\n\tprotected Color secondaryBackground;\n\n\tpublic QuickListHeaderItem(String text) {\n\t   super(text);\n\t   foreground = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_HEADER_FOREGROUND_COLOR);\n\t   background = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_HEADER_BACKGROUND_COLOR);\n\t   secondaryBackground = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_HEADER_SECONDARY_BACKGROUND_COLOR);\n\t   setFont(ThemeManager.getCurrentFont(ThemeData.QUICK_LIST_HEADER_FONT));\n\t}\n\t\t\n\t@Override\n    protected final void paintComponent(Graphics g) {\n\t\tGraphics2D graphics = (Graphics2D) g;\n\t\n\t\tgraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);\n\t\t\n\t\t// paint background image\t\n\t\tgraphics.drawImage(getBackgroundImage(getWidth(), getHeight(),\n\t\t\t\tgraphics, background, secondaryBackground), \t\t\t\t\n\t\t\t\t0, 0, null);\n\n\t\t// draw text:\n\t\tgraphics.setFont(mFont);\n\t\tgraphics.setColor(foreground);\n\t\tgraphics.drawString(getText(), X_AXIS_OFFSET, (int) graphics.getFontMetrics().getLineMetrics(this.getText(), graphics).getHeight());\n\t}\n\t\n\tprivate BufferedImage getBackgroundImage(int width, int height, Graphics2D graphics, Color leftColor, Color rightColor) {\n\t\t//clear previous painting:\n\t\tgraphics.setColor(Color.white);\n\t\tgraphics.fillRect(0, 0, width, height);\n\n\t\tBufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);\n\t\tgraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);\n\n\t\t// paint transition color\n\t\tGradientPaint gradient = new GradientPaint(0, 0, leftColor, width, 0, rightColor);\n\t\tgraphics.setPaint(gradient);\n\t\tgraphics.fillRect(0, 0, width, height);\n\n\t\treturn image;\n\t}\n\t\n\tpublic void setForegroundColor(Color foreground) {\n\t\tthis.foreground = foreground;\n\t\trepaint();\n\t}\n\n\tpublic void setBackgroundColors(Color background, Color secondaryBackground) {\t\t\n\t\tthis.background = background;\n\t\tthis.secondaryBackground = secondaryBackground;\n\t\trepaint();\n\t}\n\t\n\t@Override\n    public void colorChanged(ColorChangedEvent event) {\n        switch (event.getColorId()) {\n            case ThemeData.QUICK_LIST_HEADER_BACKGROUND_COLOR:\n                background = event.getColor();\n                break;\n            case ThemeData.QUICK_LIST_HEADER_FOREGROUND_COLOR:\n                foreground = event.getColor();\n                break;\n            case ThemeData.QUICK_LIST_HEADER_SECONDARY_BACKGROUND_COLOR:\n                secondaryBackground = event.getColor();\n                break;\n\n        }\n\t}\n\t\n\t@Override\n    public void fontChanged(FontChangedEvent event) {\n\t\tsetFont(ThemeManager.getCurrentFont(ThemeData.QUICK_LIST_HEADER_FONT));\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/quicklist/item/QuickListItem.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.quicklist.item;\n\nimport com.mucommander.ui.theme.ColorChangedEvent;\nimport com.mucommander.ui.theme.FontChangedEvent;\nimport com.mucommander.ui.theme.ThemeListener;\nimport com.mucommander.ui.theme.ThemeManager;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n/**\n * This abstract class represent menu item of QuickList.\n *\n * @author Arik Hadas\n */\n\nabstract class QuickListItem extends JMenuItem implements ThemeListener {\t\n\t\n\tprotected static final int X_AXIS_OFFSET = 5;\n\t\n\tprotected Font mFont;\n\tprotected Dimension dimension;\n\t\n\tpublic QuickListItem(String text) {\n\t\tsuper(text);\n\t\tsetEnabled(false);\n\t\t\n\t\tThemeManager.addCurrentThemeListener(this);\n\t}\n\t\n\t@Override\n    public void setFont(Font font) {\n\t\tmFont = font;\n\t\tdimension = new Dimension((int) Math.ceil(getFontMetrics(font).stringWidth(getText()) * 1.1), (int) (font.getSize() * 1.5));\n\t\tsetPreferredSize(dimension);\n\t\tsetSize(dimension);\n\t}\n\t\n\t/**\n\t * This function returns the item's dimension which is based on the item's font.\n\t */\n\t@Override\n    public Dimension getPreferredSize() { return dimension; }\n\t\n\t/////////////////////////////\n\t/// ThemeListener methods ///\n\t/////////////////////////////\n\tabstract public void colorChanged(ColorChangedEvent event);\n\t\n\tabstract public void fontChanged(FontChangedEvent event);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/quicksearch/QuickSearch.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.quicksearch;\n\nimport java.awt.event.KeyAdapter;\nimport java.awt.event.KeyEvent;\n\nimport javax.swing.JComponent;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * This class contains 'quick search' common functionality - selection of rows that match\n * the user's keyboard input.\n * This class is abstract, and should be inherited by subclasses that define 'quick search' \n * functionality for specific components. \n * \n * @author Arik Hadas\n */\npublic abstract class QuickSearch extends KeyAdapter implements Runnable {\n\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(QuickSearch.class);\n\n    /** Icon that is used to indicate in the status bar that quick search has failed */\n    protected final static String QUICK_SEARCH_KO_ICON = \"quick_search_ko.png\";\n\n    /** Icon that is used to indicate in the status bar that quick search has found a match */\n    protected final static String QUICK_SEARCH_OK_ICON = \"quick_search_ok.png\";\n\n\n    /** Quick search string */\n    private String searchString;\n\n\t/** Timestamp of the last search string change, used when quick search is active */\n    private long lastSearchStringChange;\n\n    /** Thread that's responsible for canceling the quick search on timeout,\n     * has a null value when quick search is not active */\n    private Thread timeoutThread;\n\n\t/** Quick search timeout in ms. No timeout if <= 0 */\n    private int quickSearchTimeout;\n\n\n    private final JComponent component;\n    private volatile boolean active;\n    \n    protected QuickSearch(JComponent component) {\n    \tthis.component = component;\n    \t\n    \t// Listener to key events to start quick search or update search string when it is active\n    \tcomponent.addKeyListener(this);\n    }\n    \n    /**\n     * Turns on quick search mode. This method has no effect if the quick search is already active.\n     * {@link #isActive() isActive()} will return <code>true</code> after this call, and until the quick search has\n     * timed out or has been cancelled by user.\n     */\n    protected synchronized void start() {\n        if (!isActive()) {\n            // Reset search string\n            searchString = \"\";\n            // Start the thread that's responsible for canceling the quick search on timeout\n            quickSearchTimeout = TcConfigurations.getPreferences().getVariable(TcPreference.QUICK_SEARCH_TIMEOUT, TcPreferences.DEFAULT_QUICK_SEARCH_TIMEOUT);\n            if (quickSearchTimeout > 0) {\n                timeoutThread = new Thread(this, \"QuickSearch timeout thread\");\n                timeoutThread.start();\n            }\n            active = true;\n            lastSearchStringChange = System.currentTimeMillis();\n\n            searchStarted();\n        }\n    }\n\n    /**\n     * Stops the current quick search. This method has no effect if the quick search is not currently active.\n     */\n    public synchronized void stop() {\n        if (isActive()) {\n            timeoutThread = null;\n            active = false;\n            searchStopped();\n        }\n    }\n\n    /**\n     * Returns <code>true</code> if a quick search is being performed.\n     *\n     * @return true if a quick search is being performed\n     */\n    public boolean isActive() {\n        return active;\n    }\n\n\n    /**\n     * Returns <code>true</code> if the current quick search string matches the given string.\n     * Always returns <code>false</code> when the quick search is inactive.\n     *\n     * @param string the string to test against the quick search string\n     * @return true if the current quick search string matches the given string\n     */\n    public boolean matches(String string) {\n        return isActive() && string.toLowerCase().contains(searchString.toLowerCase());\n    }\n\n    public boolean matches(AbstractFile file) {\n        return matches(file.getName());\n    }\n\n\n    /**\n     * Returns <code>true</code> if the given <code>KeyEvent</code> corresponds to a valid quick search input,\n     * <code>false</code> in any of the following cases:\n     *\n     * <ul>\n     *   <li>has any of the Alt, Ctrl or Meta modifier keys down (Shift is OK)</li>\n     *   <li>is an ASCII control character (&lt;32 or ==127)</li>\n     *   <li>is not a valid Unicode character</li>\n     * </ul>\n     *\n     * @param e the KeyEvent to test\n     * @return true if the given <code>KeyEvent</code> corresponds to a valid quick search input\n     */\n    protected boolean isValidQuickSearchInput(KeyEvent e) {\n        if ((e.getModifiersEx()&(KeyEvent.ALT_DOWN_MASK|KeyEvent.CTRL_DOWN_MASK|KeyEvent.META_DOWN_MASK))!=0)\n            return false;\n\n        char keyChar = e.getKeyChar();\n        return keyChar >= 32 && keyChar != 127 && Character.isDefined(keyChar);\n    }\n    \n    /**\n     * Setter for the last search string change time\n     * \n     * @param lastSearchStringChange - the time of the last change made to the search string\n     */\n\tprotected void setLastSearchStringChange(long lastSearchStringChange) {\n\t\tthis.lastSearchStringChange = lastSearchStringChange;\n\t}\n\n\tprotected boolean isSearchStringEmpty() {\n\t\treturn searchString.isEmpty();\n\t}\n\t\n\tprotected void removeLastCharacterFromSearchString() {\n\t\t// Remove last character from the search string\n        // Since the search string has been updated, match information has changed as well\n        // and we need to repaint the table.\n        // Note that we only repaint if the search string is not empty: if it's empty,\n        // the cancel() method will be called, and repainting twice would result in an\n        // unpleasant graphical artifact.\n        searchString = searchString.substring(0, searchString.length()-1);\n        if (!searchString.isEmpty()) {\n            component.repaint();\n        }\n\t}\n\t\n\tprotected void appendCharacterToSearchString(char keyChar) {\n\t\t// Update search string with the key that has just been typed\n        // Since the search string has been updated, match information has changed as well\n        // and we need to repaint the table.\n        searchString += keyChar;\n        component.repaint();\n\t}\n\t\n\t/**\n     * Finds a match (if any) for the current quick search string and selects the corresponding row.\n     *\n     * @param startIndex first row to be tested\n     * @param descending specifies whether rows should be tested in ascending or descending order\n     * @param findBestMatch if <code>true</code>, all rows will be tested in the specified order, looking for the best match. If not, it will stop to the first match (not necessarily the best).\n     */\n    protected void findMatch(int startIndex, boolean descending, boolean findBestMatch) {\n        LOGGER.trace(\"startRow=\"+startIndex+\" descending=\"+descending+\" findMatch=\"+findBestMatch);\n        // If search string is empty, update status bar without any icon and return\n        if (searchString.isEmpty()) {\n            searchStringBecameEmpty(searchString);\n        } else {\n        \tint bestMatch = getBestMatch(startIndex, descending, findBestMatch);\n            if (bestMatch >= 0) {\n                matchFound(bestMatch, searchString, findBestMatch);\n            } else {\n                matchNotFound(searchString);\n            }\n        }\n    }\n\t\n\tprivate int getBestMatch(int startIndex, boolean descending, boolean findBestMatch) {\n    \tString searchStringLC = searchString.toLowerCase();\n    \tint searchStringLen = searchString.length();\n        int startsWithCaseMatch = -1;\n        int startsWithNoCaseMatch = -1;\n        int containsCaseMatch = -1;\n        int containsNoCaseMatch = -1;\n        int nbFiles = getNumOfItems();\n\n        // Iterate on rows and look the first strings to match one of the following tests,\n        // in the following order of importance :\n        // - search string matches the beginning of the string with the same case\n        // - search string matches the beginning of the string with a different case\n        // - string contains search string with the same case\n        // - string contains search string with a different case\n        for (int i = startIndex; descending ? i < nbFiles : i >= 0; i = descending ? i+1 : i-1) {\n            // if findBestMatch was not specified, stop to the first match\n            if (!findBestMatch && (startsWithCaseMatch != -1 || startsWithNoCaseMatch != -1 || containsCaseMatch != -1 || containsNoCaseMatch != -1)) {\n                break;\n            }\n\n            String item = getItemString(i);\n            int itemLen = item.length();\n\n            // No need to compare strings if quick search string is longer than compared string,\n            // they won't match\n            if (itemLen < searchStringLen) {\n                continue;\n            }\n\n            // Compare quick search string against\n            if (item.startsWith(searchString)) {\n                // We've got the best match we could ever have, let's get out of this loop!\n                startsWithCaseMatch = i;\n                break;\n            }\n\n            // If we already have a match on this test case, let's skip to the next string\n            if (startsWithNoCaseMatch >= 0) {\n                continue;\n            }\n\n            String itemLC = item.toLowerCase();\n            if (itemLC.startsWith(searchStringLC)) {\n                // We've got a match, let's see if we can find a better match on the next string\n                startsWithNoCaseMatch = i;\n            }\n\n            // No need to check if the compared string contains search string if both size are equal,\n            // in the case startsWith test yields the same result\n            if (itemLen == searchStringLen) {\n                continue;\n            }\n\n            // If we already have a match on this test case, let's skip to the next string\n            if (containsCaseMatch != -1) {\n                continue;\n            }\n\n            if (item.contains(searchString)) {\n                // We've got a match, let's see if we can find a better match on the next string\n                containsCaseMatch = i;\n                continue;\n            }\n\n            // If we already have a match on this test case, let's skip to the next string\n            if (containsNoCaseMatch != -1) {\n                continue;\n            }\n\n            if (itemLC.contains(searchStringLC)) {\n                // We've got a match, let's see if we can find a better match on the next string\n                containsNoCaseMatch = i;\n                //continue;\n            }\n        }\n    \t\n        // Determines what the best match is, based on all the matches we found\n        int bestMatch = startsWithCaseMatch != -1 ? startsWithCaseMatch\n            : startsWithNoCaseMatch != -1 ? startsWithNoCaseMatch\n            : containsCaseMatch !=-1 ? containsCaseMatch\n            : containsNoCaseMatch;\n        LOGGER.trace(\"startsWithCaseMatch=\"+startsWithCaseMatch+\" containsCaseMatch=\"+containsCaseMatch+\" startsWithNoCaseMatch=\"+startsWithNoCaseMatch+\" containsNoCaseMatch=\"+containsNoCaseMatch);\n        LOGGER.trace(\"bestMatch=\"+bestMatch);\n\n        return bestMatch;\n    }\n\n\t/**\n\t * Hook that is called after the search is started\n\t */\n\tprotected abstract void searchStarted();\n\t\n\t/**\n\t * Hook that is called after the search is stopped\n\t */\n\tprotected abstract void searchStopped();\n\t\n\t/**\n\t * Return number of items to be searched in\n\t * \n\t * @return number of items\n\t */\n\tprotected abstract int getNumOfItems();\n\t\n\t/**\n\t * Return item at a given index as String\n\t * \n\t * @param index - index of item\n\t * @return item at index as String\n\t */\n\tprotected abstract String getItemString(int index);\n\t\n\t/**\n\t * Hook that is called after a search was done for an empty string\n\t * \n\t * @param searchString - the string that was being searched\n\t */\n\tprotected abstract void searchStringBecameEmpty(String searchString);\n\t\n\t/**\n\t * Hook that is called after a search was done and an item was found\n\t * \n\t * @param row - the row of the item that was found\n\t * @param searchString - the string that was being searched\n     * @param itsBestMatch - if found best match (true it it's a new search, elsewhere if we navigate between matced files)\n\t */\n\tprotected abstract void matchFound(int row, String searchString, boolean itsBestMatch);\n\t\n\t/**\n\t * Hood that is called after a search was done and no item was found\n\t * \n\t * @param searchString - the string that was being searched\n\t */\n\tprotected abstract void matchNotFound(String searchString);\n\n\n    public void run() {\n        do {\n            try {\n                Thread.sleep(100);\n            } catch(InterruptedException ignore) {\n            }\n\n            synchronized(this) {\n                if (timeoutThread != null && System.currentTimeMillis()-lastSearchStringChange >= quickSearchTimeout) {\n                    stop();\n                }\n            }\n        } while(timeoutThread != null);\n    }\n\n\n    @Override\n    public synchronized void keyReleased(KeyEvent e) {\n        // Cancel quick search if backspace key has been pressed and search string is empty.\n        // This check is done on key release, so that if backspace key is maintained pressed to remove all the search\n        // string, it does not trigger the JComponent's back action which is mapped on backspace too\n        if (isActive() && e.getKeyCode() == KeyEvent.VK_BACK_SPACE && searchString.isEmpty()) {\n            e.consume();\n            stop();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/table/CenteredTableHeaderRenderer.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.table;\r\n\r\nimport javax.swing.*;\r\nimport javax.swing.table.TableCellRenderer;\r\nimport java.awt.*;\r\n\r\n/**\r\n * A TableCellRenderer which can be used to center the text in table's headers.\r\n * \r\n * @author Arik Hadas\r\n */\r\npublic class CenteredTableHeaderRenderer extends JLabel implements TableCellRenderer {\r\n\r\n\tpublic CenteredTableHeaderRenderer() {\r\n\t\tsetHorizontalAlignment(JLabel.CENTER);\r\n        setBorder(UIManager.getBorder(\"TableHeader.cellBorder\"));\r\n\t}\r\n\t\t\t\r\n    public Component getTableCellRendererComponent(JTable table, Object value,\r\n            boolean isSelected, boolean hasFocus, int rowIndex, int vColIndex) {\r\n    \t// Configure the component with the specified value\r\n        setText(value.toString());\r\n        return this;\r\n    }\r\n\r\n    // The following methods override the defaults for performance reasons\r\n    @Override\r\n    public void validate() {}\r\n\r\n    @Override\r\n    public void revalidate() {}\r\n\r\n    @Override\r\n    protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {}\r\n\r\n    @Override\r\n    public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/table/EditableHeader.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.table;\r\n\r\nimport javax.swing.event.CellEditorListener;\r\nimport javax.swing.event.ChangeEvent;\r\nimport javax.swing.table.JTableHeader;\r\nimport javax.swing.table.TableCellEditor;\r\nimport javax.swing.table.TableColumn;\r\nimport javax.swing.table.TableColumnModel;\r\nimport java.awt.*;\r\nimport java.util.EventObject;\r\n\r\npublic class EditableHeader extends JTableHeader implements CellEditorListener {\r\n\r\n\tprivate final int HEADER_ROW = -10;\r\n\r\n\ttransient protected int editingColumn;\r\n\r\n\ttransient protected TableCellEditor cellEditor;\r\n\r\n\ttransient protected Component editorComp;\r\n\r\n\tpublic EditableHeader(TableColumnModel columnModel) {\r\n\t\tsuper(columnModel);\r\n\t\tsetReorderingAllowed(false);\r\n\t\tcellEditor = null;\r\n\t\trecreateTableColumn(columnModel);\r\n\t}\r\n\r\n\t@Override\r\n    public void updateUI() {\r\n\t\tsetUI(new EditableHeaderUI());\r\n\t\tresizeAndRepaint();\r\n\t\tinvalidate();\r\n\t}\r\n\r\n\tprotected void recreateTableColumn(TableColumnModel columnModel) {\r\n\t\tint n = columnModel.getColumnCount();\r\n\t\tEditableHeaderTableColumn[] newCols = new EditableHeaderTableColumn[n];\r\n\t\tTableColumn[] oldCols = new TableColumn[n];\r\n\t\tfor (int i = 0; i < n; i++) {\r\n\t\t\toldCols[i] = columnModel.getColumn(i);\r\n\t\t\tnewCols[i] = new EditableHeaderTableColumn();\r\n\t\t\tnewCols[i].copyValues(oldCols[i]);\r\n\t\t}\r\n\t\tfor (int i = 0; i < n; i++) {\r\n\t\t\tcolumnModel.removeColumn(oldCols[i]);\r\n\t\t}\r\n\t\tfor (int i = 0; i < n; i++) {\r\n\t\t\tcolumnModel.addColumn(newCols[i]);\r\n\t\t}\r\n\t}\r\n\r\n\tpublic boolean editCellAt(int index, EventObject e) {\r\n\t\tif (cellEditor != null && !cellEditor.stopCellEditing()) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\t/*if (!isCellEditable(index)) {\r\n\t\t\treturn false;\r\n\t\t} */\r\n\t\tTableCellEditor editor = getCellEditor(index);\r\n\r\n\t\tif (editor != null && editor.isCellEditable(e)) {\r\n\t\t\teditorComp = prepareEditor(editor, index);\r\n\t\t\teditorComp.setBounds(getHeaderRect(index));\r\n\t\t\tadd(editorComp);\r\n\t\t\teditorComp.validate();\r\n\t\t\tsetCellEditor(editor);\r\n\t\t\tsetEditingColumn(index);\r\n\t\t\teditor.addCellEditorListener(this);\r\n\r\n\t\t\treturn true;\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n\r\n\t/*public boolean isCellEditable(int index) {\r\n\t\treturn false;\r\n\t}*/\r\n\r\n\tpublic TableCellEditor getCellEditor(int index) {\r\n\t\tint columnIndex = columnModel.getColumn(index).getModelIndex();\r\n\t\tEditableHeaderTableColumn col = (EditableHeaderTableColumn) columnModel.getColumn(columnIndex);\r\n\t\treturn col.getHeaderEditor();\r\n\t}\r\n\r\n\tpublic void setCellEditor(TableCellEditor newEditor) {\r\n\t\tTableCellEditor oldEditor = cellEditor;\r\n\t\tcellEditor = newEditor;\r\n\r\n\t\t// firePropertyChange\r\n\r\n\t\tif (oldEditor != null) {\r\n\t\t\toldEditor.removeCellEditorListener(this);\r\n\t\t}\r\n\t\tif (newEditor != null) {\r\n\t\t\tnewEditor.addCellEditorListener(this);\r\n\t\t}\r\n\t}\r\n\r\n\tpublic Component prepareEditor(TableCellEditor editor, int index) {\r\n\t\tObject value = columnModel.getColumn(index).getHeaderValue();\r\n\t\treturn editor.getTableCellEditorComponent(getTable(), value, true, HEADER_ROW, index);\r\n\t}\r\n\r\n\tpublic TableCellEditor getCellEditor() { return cellEditor; }\r\n\r\n\tpublic Component getEditorComponent() { return editorComp; }\r\n\r\n\tpublic void setEditingColumn(int aColumn) { editingColumn = aColumn; }\r\n\r\n\tpublic int getEditingColumn() { return editingColumn; }\r\n\r\n\tpublic void removeEditor() {\r\n\t\tTableCellEditor editor = getCellEditor();\r\n\t\tif (editor != null) {\r\n\t\t\teditor.removeCellEditorListener(this);\r\n\r\n\t\t\trequestFocus();\r\n\t\t\tremove(editorComp);\r\n\r\n\t\t\tint index = getEditingColumn();\r\n\t\t\tRectangle cellRect = getHeaderRect(index);\r\n\r\n\t\t\tsetCellEditor(null);\r\n\t\t\tsetEditingColumn(-1);\r\n\t\t\teditorComp = null;\r\n\r\n\t\t\trepaint(cellRect);\r\n\t\t}\r\n\t}\r\n\r\n\tpublic boolean isEditing() { return cellEditor != null; }\r\n\r\n\t//\r\n\t// CellEditorListener\r\n\t//\r\n\tpublic void editingStopped(ChangeEvent e) {\r\n\t\tTableCellEditor editor = getCellEditor();\r\n\t\tif (editor != null) {\r\n\t\t\tObject value = editor.getCellEditorValue();\r\n\t\t\tint index = getEditingColumn();\r\n\t\t\tcolumnModel.getColumn(index).setHeaderValue(value);\r\n\t\t\tremoveEditor();\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void editingCanceled(ChangeEvent e) {\r\n\t\tremoveEditor();\r\n\t}\r\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/table/EditableHeaderTableColumn.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.table;\r\n\r\nimport javax.swing.table.TableCellEditor;\r\nimport javax.swing.table.TableColumn;\r\n\r\npublic class EditableHeaderTableColumn extends TableColumn {\r\n\r\n\tprotected TableCellEditor headerEditor;\r\n\r\n\tprotected boolean isHeaderEditable;\r\n\r\n\tpublic EditableHeaderTableColumn() {\r\n\t\tisHeaderEditable = false;\r\n\t}\r\n\r\n\tpublic void setHeaderEditor(TableCellEditor headerEditor) {\r\n\t\tthis.headerEditor = headerEditor;\r\n\t}\r\n\r\n\tpublic TableCellEditor getHeaderEditor() {\r\n\t\treturn headerEditor;\r\n\t}\r\n\r\n\tpublic void setHeaderEditable(boolean isEditable) {\r\n\t\tisHeaderEditable = isEditable;\r\n\t}\r\n\r\n\tpublic boolean isHeaderEditable() {\r\n\t\treturn headerEditor != null && isHeaderEditable;\r\n\t}\r\n\r\n\tpublic void copyValues(TableColumn base) {\r\n\t\tmodelIndex = base.getModelIndex();\r\n\t\tidentifier = base.getIdentifier();\r\n\t\twidth = base.getWidth();\r\n\t\tminWidth = base.getMinWidth();\r\n\t\tsetPreferredWidth(base.getPreferredWidth());\r\n\t\tmaxWidth = base.getMaxWidth();\r\n\t\theaderRenderer = base.getHeaderRenderer();\r\n\t\theaderValue = base.getHeaderValue();\r\n\t\tcellRenderer = base.getCellRenderer();\r\n\t\tcellEditor = base.getCellEditor();\r\n\t\tisResizable = base.getResizable();\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/table/EditableHeaderUI.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.table;\r\n\r\nimport javax.swing.*;\r\nimport javax.swing.event.MouseInputListener;\r\nimport javax.swing.plaf.basic.BasicTableHeaderUI;\r\nimport javax.swing.table.TableColumnModel;\r\nimport java.awt.*;\r\nimport java.awt.event.MouseEvent;\r\n\r\nclass EditableHeaderUI extends BasicTableHeaderUI {\r\n\r\n\t@Override\r\n    protected MouseInputListener createMouseInputListener() {\r\n\t\treturn new MouseInputHandler((EditableHeader) header);\r\n\t}\r\n\r\n\tpublic class MouseInputHandler extends BasicTableHeaderUI.MouseInputHandler {\r\n\t\tprivate Component dispatchComponent;\r\n\r\n\t\tprotected final EditableHeader header;\r\n\r\n\t\tMouseInputHandler(EditableHeader header) {\r\n\t\t\tthis.header = header;\r\n\t\t}\r\n\r\n\t\tprivate void setDispatchComponent(MouseEvent e) {\r\n\t\t\tComponent editorComponent = header.getEditorComponent();\r\n\t\t\tPoint p = e.getPoint();\r\n\t\t\tPoint p2 = SwingUtilities.convertPoint(header, p, editorComponent);\r\n\t\t\tdispatchComponent = SwingUtilities.getDeepestComponentAt(\r\n\t\t\t\t\teditorComponent, p2.x, p2.y);\r\n\t\t}\r\n\r\n\t\tprivate boolean repostEvent(MouseEvent e) {\r\n\t\t\tif (dispatchComponent == null) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\tMouseEvent e2 = SwingUtilities.convertMouseEvent(header, e, dispatchComponent);\r\n\t\t\tdispatchComponent.dispatchEvent(e2);\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\t@Override\r\n        public void mousePressed(MouseEvent e) {\r\n\t\t\tif (!SwingUtilities.isLeftMouseButton(e)) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\tsuper.mousePressed(e);\r\n\r\n\t\t\tif (header.getResizingColumn() == null) {\r\n\t\t\t\tPoint p = e.getPoint();\r\n\t\t\t\tTableColumnModel columnModel = header.getColumnModel();\r\n\t\t\t\tint index = columnModel.getColumnIndexAtX(p.x);\r\n\t\t\t\tif (index != -1) {\r\n\t\t\t\t\tif (header.editCellAt(index, e)) {\r\n\t\t\t\t\t\tsetDispatchComponent(e);\r\n\t\t\t\t\t\trepostEvent(e);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t@Override\r\n        public void mouseReleased(MouseEvent e) {\r\n\t\t\tsuper.mouseReleased(e);\r\n\t\t\tif (!SwingUtilities.isLeftMouseButton(e)) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\trepostEvent(e);\r\n\t\t\tdispatchComponent = null;\r\n\t\t}\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/tabs/ActiveTabListener.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.tabs;\n\n/**\n * Interface to be implemented by classes that wish to be notified of tabs switching\n * or when properties of the active tab changed\n * \n * @author Arik Hadas\n */\npublic interface ActiveTabListener {\n\n\tvoid activeTabChanged();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/tabs/HideableTabbedPane.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.tabs;\n\nimport java.awt.*;\nimport java.util.Iterator;\nimport java.util.WeakHashMap;\n\nimport javax.swing.JComponent;\nimport javax.swing.SwingUtilities;\nimport javax.swing.event.ChangeEvent;\nimport javax.swing.event.ChangeListener;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.commons.conf.ConfigurationEvent;\nimport com.mucommander.commons.conf.ConfigurationListener;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\n\n/**\n * This component acts like a tabbedpane in which multiple tabs are presented in a JTabbedPane layout \n * and single tab is presented without the JTabbedPane layout, only the tab's data.\n * \n * When a single tab is presented and new tab is added this component makes a switch to JTabbedPane layout,\n * and when two tabs are presented and there is a removal of one of the tabs this component makes a switch\n * to JPanel layout that contains the data of the tab that is left.\n * \n * This component also provides an interface for the other parts of the application to make operations\n * that influence the tabs layout.\n *  \n * @author Arik Hadas\n */\npublic class HideableTabbedPane<T extends Tab> extends JComponent implements TabsEventListener, ConfigurationListener, ChangeListener {\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(HideableTabbedPane.class);\n\n\t/* The tabs which are being displayed */\n\tprivate final TabsCollection<T> tabsCollection = new TabsCollection<>();\n\t/* The tabs display type (with/without tabs headers)\n\t * It is initialize as nullable so that it can be destroyed when it's replaced for the first time (see @{link tabAdded()})*/\n\tprivate TabsViewer<T> tabsViewer = new NullableTabsViewer<>();\n\t/* The factory that will be used to create the viewers for tabs with no headers */\t\n\tprivate final TabsViewerFactory<T> tabsWithoutHeadersViewerFactory;\n\t/* The factory that will be used to create the viewers for tabs with headers */\t\n\tprivate final TabsViewerFactory<T> tabsWithHeadersViewerFactory;\n\t/* Contains all registered active tab change listeners, stored as weak references */\n    private final WeakHashMap<ActiveTabListener, ?> activeTabChangedListener = new WeakHashMap<>();\n\n\t/**\n\t * Constructor\n\t *  \n\t * @param tabsWithoutHeadersViewerFactory - factory of tabs-display\n     * @param tabsWithHeadersViewerFactory - factory of tabs-display\n\t */\n\tpublic HideableTabbedPane(TabsViewerFactory<T> tabsWithoutHeadersViewerFactory, TabsViewerFactory<T> tabsWithHeadersViewerFactory) {\n\t\tsetLayout(new BorderLayout());\n\n\t\tthis.tabsWithoutHeadersViewerFactory = tabsWithoutHeadersViewerFactory;\n\t\tthis.tabsWithHeadersViewerFactory = tabsWithHeadersViewerFactory;\n\n\t\t// Register for tabs changes\n\t\ttabsCollection.addTabsListener(this);\n\n\t\tTcConfigurations.addPreferencesListener(this);\n\t}\n\n\t/**\n     */\n    public synchronized void addActiveTabListener(ActiveTabListener listener) {\n        activeTabChangedListener.put(listener, null);\n    }\n\n    /**\n     */\n    public synchronized void removeActiveTabChangedListener(ActiveTabListener listener) {\n    \tactiveTabChangedListener.remove(listener);\n    }\n\n    /**\n     */\n    protected synchronized void fireActiveTabChanged() {\n        for (ActiveTabListener listener : activeTabChangedListener.keySet()) {\n\t\t\tlistener.activeTabChanged();\n\t\t}\n    }\n\n\t/**\n\t * This function returns an iterator that points to the current Tabs contained in the TabbedPane\n\t * \n\t * @return Iterator that points to current Tabs\n\t */\n\tpublic Iterator<T> iterator() {\n\t\treturn tabsCollection.iterator();\n\t}\n\n\t/**\n\t * Select the given tab\n\t * \n\t * @param tab the tab to be selected\n\t */\n\tpublic void selectTab(T tab) {\n\t\tint index = tabsCollection.indexOf(tab);\n\n\t\tif (index != -1) {\n\t\t\tselectTab(index);\n\t\t} else {\n\t\t\tLOGGER.error(\"Was requested to change to non-existing tab, ignoring\");\n\t\t}\n\t}\n\n\t/**\n\t * Select the tab at the given index\n\t * An exception will be thrown if no tab exists in the given index\n\t * \n\t * @param index of the tab to be selected\n\t */\n\tpublic void selectTab(int index) {\n\t\ttabsViewer.setSelectedTabIndex(index);\n\t}\n\n\t/**\n\t * Return the index of the selected tab\n\t * \n\t * @return index of the selected tab\n\t */\n\tpublic int getSelectedIndex() {\n\t\treturn tabsViewer.getSelectedTabIndex();\n\t}\n\n\t/**\n\t * Return how many tabs the panel contains\n\t * \n\t * @return number of tabs contained in the panel\n\t */\n\tpublic int getTabsCount() {\n\t\treturn tabsCollection.count();\n\t}\n\n\tprotected TabsCollection<T> getTabs() {\n\t\treturn tabsCollection;\n\t}\n\n\t/* Actions which are not depended on the display type (single/multiple tabs) */\n\n\t/**\n\t * Add new tab\n\t * \n\t * @param tab - new tab's data\n\t */\n\tprotected void addTab(T tab) {\n\t\ttabsCollection.add(tab);\n\t}\n\n\t/**\n\t * Add new tab and select it\n\t * \n\t * @param tab - new tab's data\n\t */\n\tprotected void addAndSelectTab(T tab) {\n\t\taddTab(tab);\n\t\ttabsViewer.setSelectedTabIndex(tabsCollection.count()-1);\n\t}\n\n\t/**\n\t * Update the current displayed tab's data with the given {@link TabUpdater}\n\t * \n\t * @param updater - object that will be used to update the tab\n\t */\n\tprotected void updateCurrentTab(TabUpdater<T> updater) {\n\t\ttabsCollection.updateTab(getSelectedIndex(), updater);\n\t}\n\n\t/* Actions that depended on the display type (single/multiple tabs) */\n\n\t/**\n\t * Remove tab with the given header\n\t */\n\tprotected void removeTab(Component header) {\n\t\ttabsViewer.removeTab(header);\n\t}\n\n\t/**\n\t * Remove current displayed tab\n\t */\n\tprotected T removeTab() {\n\t\treturn tabsCollection.remove(getSelectedIndex());\n\t}\n\n\t/**\n\t * Remove duplicate tabs\n\t */\n\tprotected void removeDuplicateTabs() {\n\t\ttabsViewer.removeDuplicateTabs();\n\t}\n\n\t/**\n\t * Remove all tabs except the current displayed tab\n\t */\n\tprotected void removeOtherTabs() {\n\t\ttabsViewer.removeOtherTabs();\n\t}\n\n\t/**\n\t * Change the current displayed tab to the tab which is located to the right of the\n\t * current displayed tab.\n\t * If the current displayed tab is the rightmost tab, the leftmost tab will be displayed.\n\t */\n\tpublic void nextTab() {\n\t\ttabsViewer.nextTab();\n\t}\n\n\t/**\n\t * Change the current displayed tab to the tab which is located to the left of the\n\t * current displayed tab.\n\t * If the current displayed tab is the leftmost tab, the rightmost tab will be displayed.\n\t */\n\tpublic void previousTab() {\n\t\ttabsViewer.previousTab();\n\t}\n\n\t/******************\n\t * Private Methods\n\t ******************/\n\n\tprivate void switchToTabsWithHeaders() {\n\t\tsetTabsViewer(tabsWithHeadersViewerFactory);\n\t}\n\n\tprivate void switchToTabWithoutHeader() {\n\t\tsetTabsViewer(tabsWithoutHeadersViewerFactory);\n\t}\n\n\tprivate void setTabsViewer(TabsViewerFactory<T> tabsViewerFactory) {\n\t\ttabsViewer.removeChangeListener(this);\n\t\ttabsViewer = tabsViewerFactory.create(tabsCollection);\n\t\ttabsViewer.addChangeListener(this);\n\n\t\tremoveAll();\n\t\tadd(tabsViewer);\n\t\tvalidate();\n\n\t\ttabsViewer.requestFocus();\n\t}\n\n    /**\n\t * Returns the tab at the given index\n\t * An exception will be thrown if no tab exists in the given index\n\t * \n\t * @param index of the requested tab\n\t * @return tab in the given index\n\t */\n\tprotected T getTab(int index) {\n\t\treturn tabsCollection.get(index);\n\t}\n\n\tprivate boolean refreshViewer() {\n\t\tint nbTabs = tabsCollection.count();\n\n        return switch (nbTabs) {\n            case 2 -> {\n                switchToTabsWithHeaders();\n                yield true;\n            }\n            case 1 -> {\n                if (showSingleTabHeader()) {\n\t\t\t\t\tswitchToTabsWithHeaders();\n\t\t\t\t} else {\n\t\t\t\t\tswitchToTabWithoutHeader();\n\t\t\t\t}\n                yield true;\n            }\n            default -> false;\n        };\n\t}\n\n\tprotected boolean showSingleTabHeader() {\n\t\treturn TcConfigurations.getPreferences().getVariable(TcPreference.SHOW_TAB_HEADER, TcPreferences.DEFAULT_SHOW_TAB_HEADER);\n\t}\n\n\tprotected void show(int tabIndex) {\n\t}\n\n\n\t@Override\n\tpublic void tabAdded(int index) {\n\t\tif (!refreshViewer())\n\t\t\ttabsViewer.add(tabsCollection.get(index), index);\n\t\t\n\t\tif (isDisplayable()) {\n\t\t\ttabsViewer.setSelectedTabIndex(index);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void tabRemoved(int index) {\n\t\tint previouslySelectedIndex = tabsViewer.getSelectedTabIndex();\n\n\t\tif (!refreshViewer()) {\n\t\t\ttabsViewer.removeTab(index);\n\t\t} else {\n\t\t\tselectTab(Math.max(previouslySelectedIndex - 1, 0));\n\t\t}\n\t}\n\n\t@Override\n\tpublic void tabUpdated(int index) {\n\t\ttabsViewer.update(tabsCollection.get(index), index);\n\t\t\n\t\tfireActiveTabChanged();\n\t}\n\n\n\t@Override\n\tpublic void configurationChanged(ConfigurationEvent event) {\n\t\tString var = event.getVariable();\n\n        // Update the button's icon if the system file icons policy has changed\n        if (var.equals(TcPreferences.SHOW_SINGLE_TAB_HEADER))\n            refreshViewer();\n\t}\n\n\t@Override\n\tpublic void stateChanged(ChangeEvent e) {\n\t\tfinal int selectedIndex = tabsViewer.getSelectedTabIndex();\n\n\t\tif (selectedIndex != -1)\n\t\t\tshow(selectedIndex);\n\n\t\tSwingUtilities.invokeLater(tabsViewer::requestFocus);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/tabs/NullableTabsViewer.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.tabs;\n\nimport java.awt.Component;\n\nimport javax.swing.JLabel;\n\n/**\n * Initial value for TabsDisplay object.\n * \n * @author Arik Hadas\n */\nclass NullableTabsViewer<T extends Tab> extends TabsViewer<T> {\n\n\tpublic NullableTabsViewer() {\n\t\tsuper(new JLabel(), null);\n\t}\n\n\t@Override\n\tpublic void add(T tab) { }\n\n\t@Override\n\tpublic void add(T tab, int index) { }\n\n\t@Override\n\tpublic void update(T tab, int index) { }\n\n\t@Override\n\tpublic void remove(int index) { }\n\n\t@Override\n\tpublic int getSelectedTabIndex() {\n\t\treturn 0;\n\t}\n\n\t@Override\n\tpublic void setSelectedTabIndex(int index) { }\n\n\t@Override\n\tpublic T removeCurrentTab() { return null; }\n\n\t@Override\n\tpublic void removeOtherTabs() { }\n\n\t@Override\n\tpublic void removeTab(Component header) { }\n\n\t@Override\n\tpublic void removeDuplicateTabs() { }\n\n\t@Override\n\tpublic void removeTab(int index) { }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/tabs/Tab.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.tabs;\n\n/**\n* Interface for tabs.\n* \n* @author Arik Hadas\n*/\npublic interface Tab {\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/tabs/TabFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.tabs;\n\n/**\n * interface for factory of {@link com.mucommander.ui.tabs.Tab} \n * \n * @author Arik Hadas\n *\n * @param <T> kind-of Tab\n * @param <K> parameter for initiating the new tab\n */\npublic interface TabFactory<T extends Tab, K> {\n\n\t/**\n\t * This method returns new {@link com.mucommander.ui.tabs.Tab} based on the given parameter\n\t * \n\t * @param k a parameter that the new tab is based on\n\t * @return instance of subclass of {@link com.mucommander.ui.tabs.Tab}\n\t */\n\tT createTab(K k);\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/tabs/TabUpdater.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.tabs;\n\n/**\n * This interface declare objects that are used to update properties of Tabs in a generic way\n * \n * @author Arik Hadas\n */\npublic interface TabUpdater<T> {\n\n\tvoid update(T tab);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/tabs/TabWithoutHeaderViewer.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.tabs;\n\nimport java.awt.Component;\n\nimport javax.swing.JComponent;\n\n/**\n* Component that presents tab with no header\n* \n* @author Arik Hadas\n*/\npublic class TabWithoutHeaderViewer<T extends Tab> extends TabsViewer<T> {\n\n\t/** The component to be displayed in the tab */\n\tprivate final JComponent component;\n\n\tpublic TabWithoutHeaderViewer(TabsCollection<T> tabs, JComponent component) {\n\t\tsuper(component, tabs);\n\n\t\tthis.component = component;\n\t}\n\n\t@Override\n\tpublic void requestFocus() {\n\t\tcomponent.requestFocusInWindow();\n\t}\n\n\t@Override\n\tpublic int getSelectedTabIndex() {\n\t\treturn 0;\n\t}\n\n\t@Override\n\tpublic void add(T tab, int index) {\n\t\tif (index > 0) {\n\t\t\tthrow new IllegalArgumentException(\"Unable to add tab at index > 0 to single tab display\");\n\t\t}\n\t\tadd(tab);\n\t}\n\n\t@Override\n\tpublic void update(T tab, int index) { }\n\n\t@Override\n\tpublic void setSelectedTabIndex(int index) { }\n\n\t@Override\n\tpublic void add(T tab) { }\n\n\t@Override\n\tpublic T removeCurrentTab() { return null; }\n\n\t@Override\n\tpublic void removeOtherTabs() { }\n\n\t@Override\n\tpublic void removeTab(Component header) { }\n\n\t@Override\n\tpublic void removeDuplicateTabs() { }\n\n\t@Override\n\tpublic void removeTab(int index) { }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/tabs/TabbedPane.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.tabs;\n\nimport javax.swing.JTabbedPane;\n\n/**\n* TabbedPane that contains implementations of Tab interface\n* \n* @author Arik Hadas\n*/\npublic abstract class TabbedPane<T extends Tab> extends JTabbedPane {\n\n\t/**\n\t * Add tab to the end of the tabbed pane\n\t * \n\t * @param tab - implementation of Tab interface\n\t */\n\tpublic abstract void add(T tab);\n\t\n\t/**\n\t * Add tab in a given index\n\t * \n\t * @param tab - implementation of Tab interface\n\t * @param index - the index in which the tab would be added\n\t */\n\tpublic abstract void add(T tab, int index);\n\t\n\t/**\n\t * Update tab in a given index\n\t * The updated tab would be selected in the end of the operation\n\t * \n\t * @param tab - implementation of Tab interface\n\t * @param index - the index of the tab to be updated\n\t */\n\tpublic abstract void update(T tab, int index);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/tabs/TabsCollection.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.tabs;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.WeakHashMap;\n\n/**\n* Collection of tabs\n* The collection is iterable, and can notify listeners about added/removed/updated tabs\n* \n* @author Arik Hadas\n*/\npublic class TabsCollection<T extends Tab> implements java.lang.Iterable<T> {\n\t\n\t/** List of tabs */\n\tprivate final List<T> collection = new ArrayList<>();\n\t\n\t/** Listeners that were registered to be notified when tabs are added/removed/updated */\n\tprivate final WeakHashMap<TabsEventListener, ?> tabsListeners = new WeakHashMap<>();\n\t\n\t/**\n\t * Empty constructor\n\t */\n\tTabsCollection() {\n\t}\n\t\n\t/**\n\t * Constructor that creates the collection with a single given tab\n\t * \n\t * @param tab - a tab\n\t */\n\tpublic TabsCollection(T tab) {\n\t\tcollection.add(tab);\n\t}\n\t\n\t/**\n\t * Constructor that creates the collection with multiple given tabs\n\t * \n\t * @param tabs - list of tabs\n\t */\n\tpublic TabsCollection(List<T> tabs) {\n\t\tcollection.addAll(tabs);\n\t}\n\t\n\t/**\n\t * Add the given tab to the collection\n\t * The tab would be inserted in the last index in the collection\n\t * \n\t * @param tab - tab\n\t */\n\tpublic void add(T tab) {\n\t\tadd(tab, count());\n\t}\n\t\n\t/**\n\t * Add the given tab to the collection in a given index\n\t * \n\t * @param tab - tab\n\t * @param index - the index in which the tab would be inserted in the collection\n\t */\n\tpublic void add(T tab, int index) {\n\t\tcollection.add(index, tab);\n\t\tfireTabAdded(count() - 1);\n\t}\n\t\n\t/**\n\t * Update the tab in the given index with the given {@link TabUpdater}\n\t * \n\t * @param index - the index of the tab to be updated\n\t * @param updater - the object that will be used to update the tab\n\t */\n\tvoid updateTab(int index, TabUpdater<T> updater) {\n\t\tupdater.update(get(index));\n\t\tfireTabUpdated(index);\n\t}\n\t\n\t/**\n\t * Remove the tab in the given index\n\t * If there are less than two tabs in the collection, the tab\n\t * won't be removed in order to prevent a situation in which\n\t * we remain with no tabs at all\n\t * \n\t * @param index - the index of the tab to be removed\n\t */\n\tpublic T remove(int index) {\n\t\tif (collection.size() > 1) {\n\t\t\tT tab = collection.remove(index);\n\t\t\tfireTabRemoved(index);\n\t\t\treturn tab;\n\t\t}\n\t\treturn null;\n\t}\n\t\n\t/**\n\t * Return the tab in the given index\n\t * \n\t * @param index - the index of the tab to be returned\n\t * @return the tab in the given index\n\t */\n\tpublic T get(int index) {\n\t\tif (index < 0 || index >= collection.size()) {\n\t\t\treturn null;\n\t\t}\n\t\treturn collection.get(index);\n\t}\n\t\n\t/**\n\t * Return the number of tabs contained in the collection\n\t * \n\t * @return the number of tabs contained in the collection\n\t */\n\tpublic int count() {\n\t\treturn collection.size();\n\t}\n\t\n\t/**\n\t * Return the index of the given tab in the collection\n\t * \n\t * @return the index of the given tab or -1 if the tab is not exist in the collection\n\t */\n\tpublic int indexOf(T tab) {\n\t\treturn collection.indexOf(tab);\n\t}\n\n    /**\n\t * Add a given listener to the listeners to be notified about tabs-changes\n\t * \n\t * @param listener - object that implements TabsChangeListener interface\n\t */\n\tsynchronized void addTabsListener(TabsEventListener listener) {\n        tabsListeners.put(listener, null);\n    }\n\n\t/**\n\t * Remove a given listener from the listeners to be notifies about tabs-changes\n\t * \n\t * @param listener - object that implements TabsChangeListener interface\n\t */\n    public synchronized void removeTabsListener(TabsEventListener listener) {\n    \ttabsListeners.remove(listener);\n    }\n    \n    /**\n     * Notify the registered listeners about addition of tab in the given index\n     * \n     * @param index - the index of the added tab\n     */\n    private synchronized void fireTabAdded(int index) {\n    \tSet<TabsEventListener> listeners = new HashSet<>(tabsListeners.keySet());\n    \tfor (TabsEventListener listener : listeners) {\n\t\t\tlistener.tabAdded(index);\n\t\t}\n    }\n    \n    /**\n     * Notify the registered listeners about removal of tab in the given index\n     * \n     * @param index - the index in which the removed tab was located\n     */\n    private synchronized void fireTabRemoved(int index) {\n    \tSet<TabsEventListener> listeners = tabsListeners.keySet();\n        for (TabsEventListener listener : listeners) {\n\t\t\tlistener.tabRemoved(index);\n\t\t}\n    }\n    \n    /**\n     * Notify the registered listeners about tab that was updated in the given index\n     * \n     * @param index - the index of the updated tab\n     */\n    private synchronized void fireTabUpdated(int index) {\n    \tSet<TabsEventListener> listeners = tabsListeners.keySet();\n        for (TabsEventListener listener : listeners) {\n\t\t\tlistener.tabUpdated(index);\n\t\t}\n    }\n\n\t@NotNull\n    @Override\n\tpublic Iterator<T> iterator() {\n\t\treturn collection.iterator();\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/tabs/TabsEventListener.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.tabs;\n\n/**\n* Interface to be implemented by classes that wish to be notified of tabs changes on a particular\n* HideableTabbedPane. Those classes need to be registered to receive those events, this can be done by calling\n* {@link TabsCollection#addTabsListener(TabsEventListener)}.\n*\n* @see com.mucommander.ui.tabs.TabsCollection\n* @author Arik Hadas\n*/\npublic interface TabsEventListener {\n\t\n\t/**\n\t * This method is invoked when a tab was added.\n\t * \n\t * @param index - the index in which the tab was added\n\t */\n\tvoid tabAdded(int index);\n\t\n\t/**\n\t * This method is invoked when a tab was removed.\n\t * \n\t * @param index - the index in which the tab was added\n\t */\n\tvoid tabRemoved(int index);\n\t\n\t/**\n\t * This method is invoked when a tab data was updated.\n\t * \n\t * @param index - the index of the updated tab\n\t */\n\tvoid tabUpdated(int index);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/tabs/TabsViewer.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.tabs;\n\nimport java.awt.Component;\nimport java.awt.GridLayout;\n\nimport javax.swing.JComponent;\nimport javax.swing.event.ChangeListener;\n\n/**\n* Abstract class for components that display tabs.\n* \n* @author Arik Hadas\n*/\npublic abstract class TabsViewer<T extends Tab> extends JComponent {\n\t\n\t/** Collection of the displayed tabs */\n\tprivate final TabsCollection<T> tabs;\n\t\n\tTabsViewer(JComponent component, TabsCollection<T> tabs) {\n\t\tthis.tabs = tabs;\n\t\t\n\t\tsetLayout(new GridLayout(1, 1));\n\t\tadd(component);\n\t}\n\t\n\tpublic void addChangeListener(ChangeListener listener) { }\n\t\n\tpublic void removeChangeListener(ChangeListener listener) { }\n\n\t/*************** \n\t * Tabs Actions\n\t ***************/\n\t\n\tpublic abstract void add(T tab);\n\t\n\tpublic abstract void add(T tab, int index);\n\t\n\tpublic abstract void update(T tab, int index);\n\t\n\tpublic abstract int getSelectedTabIndex();\n\t\n\tpublic abstract void setSelectedTabIndex(int index);\n\n\tpublic abstract T removeCurrentTab();\n\t\n\tpublic abstract void removeDuplicateTabs();\n\t\n\tpublic abstract void removeOtherTabs();\n\n\tpublic abstract void removeTab(Component header);\n\t\n\tpublic abstract void removeTab(int index);\n\t\n\tpublic void nextTab() {\n\t\tsetSelectedTabIndex((getSelectedTabIndex()+1) % tabs.count());\n\t}\n\t\n\tpublic void previousTab() {\n\t\tint numOfTabs = tabs.count();\n\t\tsetSelectedTabIndex((getSelectedTabIndex()-1+numOfTabs) % numOfTabs);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/tabs/TabsViewerFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.tabs;\n\n/**\n* Factory that creates tabs viewer.\n* \n* @author Arik Hadas\n*/\npublic interface TabsViewerFactory<T extends Tab> {\n\n\tTabsViewer<T> create(TabsCollection<T> tabs);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/tabs/TabsWithHeaderViewer.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.tabs;\n\nimport java.awt.Component;\nimport java.util.HashSet;\nimport java.util.Set;\n\nimport javax.swing.event.ChangeListener;\n\n/**\n* Component that presents tabs with headers\n* \n* @author Arik Hadas\n*/\npublic class TabsWithHeaderViewer<T extends Tab> extends TabsViewer<T> {\n\n\tprivate final TabsCollection<T> tabsCollection;\n\tprivate final TabbedPane<T> tabbedPane;\n\n\tpublic TabsWithHeaderViewer(TabsCollection<T> tabs, TabbedPane<T> tabbedpane) {\n\t\tsuper(tabbedpane, tabs);\n\n\t\tthis.tabsCollection = tabs;\n\t\tthis.tabbedPane = tabbedpane;\n\n\t\tint index = 0;\n\t\tfor (T tab : tabs) {\n\t\t\ttabbedpane.add(tab, index++);\n\t\t}\n\t}\n\n\t/**************\n\t * TabsViewer\n\t **************/\n\n\t@Override\n\tpublic int getSelectedTabIndex() {\n\t\treturn tabbedPane.getSelectedIndex();\n\t}\n\n\t@Override\n\tpublic void removeTab(int index) {\n\t\ttabbedPane.remove(index);\n\t}\n\n\t@Override\n\tpublic void addChangeListener(ChangeListener listener) { \n\t\ttabbedPane.addChangeListener(listener);\n\t}\n\n\t@Override\n\tpublic void removeChangeListener(ChangeListener listener) { \n\t\ttabbedPane.removeChangeListener(listener);\n\t}\n\n\t@Override\n\tpublic void add(T tab) {\n\t\tadd(tab, tabsCollection.count());\n\t}\n\n\t@Override\n\tpublic void add(T tab, int index) {\n\t\ttabbedPane.add(tab, index);\n\t}\n\n\t@Override\n\tpublic void update(T tab, int index) {\n\t\ttabbedPane.update(tab, index);\n\t}\n\n\t@Override\n\tpublic void setSelectedTabIndex(int index) {\n\t\ttabbedPane.setSelectedIndex(index);\n\t}\n\n\t@Override\n\tpublic void requestFocus() {\n\t\ttabbedPane.requestFocusInWindow();\n\t}\n\n\t@Override\n\tpublic T removeCurrentTab() {\n\t\tT tab = tabsCollection.get(getSelectedTabIndex());\n\t\ttabsCollection.remove(getSelectedTabIndex());\n\t\treturn tab;\n\t}\n\n\t@Override\n\tpublic void removeDuplicateTabs() {\n\t\t// a Set that will contain the tabs we've seen\n\t\tSet<T> visitedTabs = new HashSet<>();\n\t\t// a Set that will contain the tabs which are duplicated\n\t\tSet<T> duplicatedTabs = new HashSet<>();\n\t\t// The index of the selected tab\n\t\tint selectedTabIndex = getSelectedTabIndex();\n\n\t\t// add all duplicated tabs to the duplicatedTab Set\n        for (T tab : tabsCollection) {\n            if (!visitedTabs.add(tab))\n                duplicatedTabs.add(tab);\n        }\n\n\t\t// remove all duplicated tabs which are identical to the selected tab without\n\t\t// changing the tab selection\n\t\tT selectedTab = tabsCollection.get(selectedTabIndex);\n\t\tif (duplicatedTabs.remove(selectedTab)) {\n\t\t\tint removedTabsCount = 0;\n\t\t\tint tabsCount = tabsCollection.count();\n\t\t\tfor (int i=0; i<tabsCount; ++i) {\n\t\t\t\tif (i == selectedTabIndex) // do not remove the selected tab\n\t\t\t\t\tcontinue;\n\t\t\t\tif (selectedTab.equals(tabsCollection.get(i-removedTabsCount)))\n\t\t\t\t\ttabsCollection.remove(i-removedTabsCount++);\n\t\t\t}\n\t\t}\n\n\t\t// remove all other duplicated tabs\n\t\tfor (int i = 0; i < tabsCollection.count(); ++i) {\n\t\t\tT currentTab = tabsCollection.get(i);\n\t\t\tif (duplicatedTabs.remove(currentTab)) {\n\t\t\t\tfor (int j = i + 1; j < tabsCollection.count(); ++j)\n\t\t\t\t\tif (currentTab.equals(tabsCollection.get(j)))\n\t\t\t\t\t\ttabsCollection.remove(j--);\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic void removeOtherTabs() {\n\t\tint selectedTabIndex = getSelectedTabIndex();\n\n\t\tfor (int i = 0; i < selectedTabIndex; ++i) {\n\t\t\ttabsCollection.remove(0);\n\t\t}\n\n\t\tfor(int i = tabsCollection.count()-1; i>0; --i) {\n\t\t\ttabsCollection.remove(1);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void removeTab(Component header) {\n\t\ttabsCollection.remove(tabbedPane.indexOfTabComponent(header));\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/terminal/JediTerminalPanelEx.kt",
    "content": "/*\r\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\r\n * Copyright (C) 2013-2026 Oleg Trifonov\r\n *\r\n * trolCommander is free software; you can redistribute it and/or modify it under the terms of the GNU General Public\r\n * License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.\r\n *\r\n * trolCommander is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied\r\n * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\npackage com.mucommander.ui.terminal\r\n\r\nimport com.jediterm.terminal.CursorShape\r\nimport com.jediterm.terminal.model.StyleState\r\nimport com.jediterm.terminal.model.TerminalTextBuffer\r\nimport com.jediterm.terminal.ui.TerminalPanel\r\nimport com.jediterm.terminal.ui.settings.SettingsProvider\r\nimport com.mucommander.commons.runtime.OsFamily\r\nimport com.mucommander.ui.action.ActionKeymap\r\nimport com.mucommander.ui.action.impl.TerminalPanelAction\r\nimport com.mucommander.ui.main.MainFrame\r\nimport ru.trolsoft.calculator.CalculatorDialog\r\nimport java.awt.event.InputEvent\r\nimport java.awt.event.KeyEvent\r\nimport javax.swing.KeyStroke\r\n\r\n/**\r\n * @author Oleg Trifonov\r\n * Created on 27/10/14.\r\n */\r\nclass JediTerminalPanelEx internal constructor(\r\n    settingsProvider: SettingsProvider,\r\n    terminalTextBuffer: TerminalTextBuffer,\r\n    styleState: StyleState,\r\n    private val mainFrame: MainFrame\r\n) : TerminalPanel(settingsProvider, terminalTextBuffer, styleState) {\r\n    private val keyModifier: Int = if (OsFamily.MAC_OS_X.isCurrent) KeyEvent.META_DOWN_MASK else KeyEvent.CTRL_DOWN_MASK\r\n    private var lineHeight = 0\r\n\r\n    init {\r\n        setDefaultCursorShape(CursorShape.BLINK_VERTICAL_BAR)\r\n    }\r\n\r\n\r\n    override fun processKeyEvent(e: KeyEvent) {\r\n        val id = e.getID()\r\n\r\n        if (id == KeyEvent.KEY_PRESSED) {\r\n            val actionId = ActionKeymap.getRegisteredActionIdForKeystroke(\r\n                KeyStroke.getKeyStroke(\r\n                    e.getKeyCode(),\r\n                    e.modifiersEx,\r\n                    false\r\n                )\r\n            )\r\n            if (TerminalPanelAction.Descriptor.ACTION_ID == actionId) {\r\n                mainFrame.showTerminalPanel(false)\r\n                return\r\n            }\r\n            if (e.modifiersEx == keyModifier) {\r\n                when (e.getKeyCode()) {\r\n                    KeyEvent.VK_UP -> {\r\n                        resizePanel(1)\r\n                        return\r\n                    }\r\n\r\n                    KeyEvent.VK_DOWN -> {\r\n                        resizePanel(-1)\r\n                        return\r\n                    }\r\n\r\n                    KeyEvent.VK_LEFT -> {\r\n                        resizePanel(-10)\r\n                        return\r\n                    }\r\n\r\n                    KeyEvent.VK_RIGHT -> {\r\n                        resizePanel(10)\r\n                        return\r\n                    }\r\n\r\n                    KeyEvent.VK_1 -> {\r\n                        mainFrame.leftPanel.fileTable.requestFocus()\r\n                        return\r\n                    }\r\n\r\n                    KeyEvent.VK_2 -> {\r\n                        mainFrame.rightPanel.fileTable.requestFocus()\r\n                        return\r\n                    }\r\n                }\r\n            }\r\n            super.processKeyEvent(e)\r\n        } else if (id == KeyEvent.KEY_TYPED) {\r\n            super.processKeyEvent(e)\r\n        }\r\n\r\n        if (e.modifiersEx == InputEvent.ALT_DOWN_MASK && e.getKeyCode() == KeyEvent.VK_C) {\r\n            if (id == KeyEvent.KEY_RELEASED) {\r\n                CalculatorDialog(mainFrame.jFrame).showDialog()\r\n            }\r\n            e.consume()\r\n            return\r\n        }\r\n\r\n        e.consume()\r\n    }\r\n\r\n    private fun resizePanel(delta: Int) {\r\n        var lineHeight = getHeight() / terminalTextBuffer.height\r\n        if (lineHeight > 0 && (lineHeight < this.lineHeight || this.lineHeight == 0)) {\r\n            this.lineHeight = lineHeight\r\n        }\r\n        lineHeight = this.lineHeight\r\n        var height = mainFrame.terminalPanelHeight + delta * lineHeight\r\n        val minHeight = 2 * lineHeight\r\n        val maxHeight = mainFrame.jFrame.getHeight() - minHeight\r\n\r\n        if (delta < -2) {\r\n            height = minHeight\r\n        } else if (delta > 2) {\r\n            height = maxHeight\r\n            mainFrame.setTerminalPanelHeight(height)\r\n        } else if (height !in minHeight..maxHeight) {\r\n            return\r\n        }\r\n\r\n        mainFrame.setTerminalPanelHeight(height)\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/terminal/TcTerminal.kt",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2026 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify it under the terms of the GNU General Public\n * License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied\n * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.terminal\n\nimport com.jediterm.terminal.TtyConnector\nimport com.jediterm.terminal.model.StyleState\nimport com.jediterm.terminal.model.TerminalTextBuffer\nimport com.jediterm.terminal.ui.JediTermWidget\nimport com.jediterm.terminal.ui.TerminalPanel\nimport com.jediterm.terminal.ui.TerminalSession\nimport com.jediterm.terminal.ui.TerminalWidget\nimport com.jediterm.terminal.ui.settings.SettingsProvider\nimport com.mucommander.cache.WindowsStorage\nimport com.mucommander.ui.main.MainFrame\nimport java.awt.event.FocusEvent\nimport java.awt.event.FocusListener\nimport java.io.IOException\nimport javax.swing.JComponent\n\n/**\n * @author Oleg Trifonov\n * Created on 24/10/14.\n */\nclass TcTerminal(private val mainFrame: MainFrame) {\n    private val termWidget: TerminalWidget\n\n    init {\n        val settingsProvider: SettingsProvider = TerminalSettingsProvider()\n        val ttyConnector = createTtyConnector(getCurrentFolder())\n\n        termWidget = object : JediTermWidget(settingsProvider) {\n            override fun createTerminalPanel(\n                settingsProvider: SettingsProvider,\n                styleState: StyleState,\n                textBuffer: TerminalTextBuffer\n            ): TerminalPanel =\n                JediTerminalPanelEx(settingsProvider, textBuffer, styleState, mainFrame)\n        }\n\n        termWidget.component.addFocusListener(object : FocusListener {\n            override fun focusGained(e: FocusEvent?) {\n                updateTitle()\n            }\n\n            override fun focusLost(e: FocusEvent?) {\n                mainFrame.updateWindowTitle()\n            }\n        })\n\n        if (termWidget.canOpenSession()) {\n            openSession(termWidget, ttyConnector)\n        }\n    }\n\n\n    private fun createTtyConnector(directory: String?): TcTerminalTtyConnector? {\n        try {\n            return object : TcTerminalTtyConnector(directory) {\n                override fun close() {\n                    super.close()\n                    mainFrame.closeTerminalSession()\n                }\n            }\n        } catch (e: IOException) {\n            e.printStackTrace()\n            return null\n        }\n    }\n\n    fun openSession(terminal: TerminalWidget, ttyConnector: TtyConnector?) {\n        val session: TerminalSession = terminal.createTerminalSession(ttyConnector)\n        session.start()\n    }\n\n\n    fun storeHeight(height: Int) {\n        WindowsStorage.getInstance().put(STORAGE_KEY, WindowsStorage.Record(0, 0, 0, height))\n    }\n\n    fun loadHeight(): Int =\n        WindowsStorage.getInstance().get(STORAGE_KEY)?.height ?: -1\n\n    fun getComponent(): JComponent = termWidget.component\n\n    fun show(show: Boolean) {\n        termWidget.component.isVisible = show\n    }\n\n    private fun getCurrentFolder(): String? {\n        val currentFolder = mainFrame.activePanel.currentFolder.absolutePath\n        return if (currentFolder.contains(\"://\")) null else currentFolder\n    }\n\n    fun updateTitle() {\n        mainFrame.jFrame.setTitle(termWidget.terminalDisplay.windowTitle)\n    }\n\n    companion object {\n        private const val STORAGE_KEY = \"TerminalPanel\"\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/terminal/TcTerminalTtyConnector.kt",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2026 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify it under the terms of the GNU General Public\n * License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied\n * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.terminal\n\nimport com.jediterm.terminal.ProcessTtyConnector\nimport com.mucommander.conf.TcConfigurations\nimport com.mucommander.conf.TcPreference\nimport com.mucommander.conf.TcPreferences\nimport com.mucommander.desktop.DesktopManager\nimport com.pty4j.PtyProcess\nimport com.pty4j.PtyProcessBuilder\nimport java.io.IOException\nimport java.nio.charset.StandardCharsets\nimport java.util.*\n\n/**\n * @author Oleg Trifonov\n * Created on 28/10/14.\n */\nopen class TcTerminalTtyConnector private constructor(process: PtyProcess) :\n    ProcessTtyConnector(process, StandardCharsets.UTF_8) {\n    private val myDataChunks: MutableList<CharArray?> = ArrayList<CharArray?>()\n\n    internal constructor(directory: String?) : this(createPtyProcess(directory))\n\n    override fun getName(): String = \"\"\n\n    @Throws(IOException::class)\n    override fun read(buf: CharArray, offset: Int, length: Int): Int {\n        val len = super.read(buf, offset, length)\n        if (len > 0) {\n            val arr = buf.copyOfRange(offset, len)\n            myDataChunks.add(arr)\n        }\n        return len\n    }\n\n    fun getChunks(): MutableList<CharArray> =\n        ArrayList<CharArray>(myDataChunks)\n\n\n    companion object {\n        @Throws(IOException::class)\n        private fun createPtyProcess(directory: String?): PtyProcess {\n            val envs: MutableMap<String?, String?> = HashMap(System.getenv()).apply {\n                put(\"TERM\", \"xterm-256color\")\n            }\n\n            val pref = TcConfigurations.getPreferences()\n            var cmd = if (pref.getVariable(TcPreference.TERMINAL_USE_CUSTOM_SHELL,TcPreferences.DEFAULT_TERMINAL_USE_CUSTOM_SHELL)) {\n                pref.getVariable(TcPreference.TERMINAL_SHELL)\n            } else {\n                DesktopManager.getDefaultTerminalShellCommand()\n            }\n\n            cmd = cmd.replace(\"\\t\", \" \").replace(\" +\".toRegex(), \" \")\n            val command: Array<String> = cmd.split(\" \".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()\n\n            return PtyProcessBuilder()\n                .setCommand(command)\n                .setEnvironment(envs)\n                .setDirectory(directory)\n                .setRedirectErrorStream(true)\n                .setWindowsAnsiColorEnabled(true)\n                .setUnixOpenTtyToPreserveOutputAfterTermination(true)\n                .setSpawnProcessUsingJdkOnMacIntel(true)\n                .setConsole(false) // Windows only ?\n                .start()\n        }\n    }\n}\n\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/terminal/TerminalSettingsProvider.kt",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2026 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify it under the terms of the GNU General Public\n * License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied\n * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.terminal\n\nimport com.jediterm.terminal.HyperlinkStyle.HighlightMode\nimport com.jediterm.terminal.TerminalColor\nimport com.jediterm.terminal.TerminalColor.rgb\nimport com.jediterm.terminal.TextStyle\nimport com.jediterm.terminal.emulator.ColorPalette\nimport com.jediterm.terminal.emulator.ColorPaletteImpl\nimport com.jediterm.terminal.model.TerminalTypeAheadSettings\nimport com.jediterm.terminal.ui.settings.DefaultSettingsProvider\nimport com.mucommander.commons.runtime.OsFamily\nimport com.mucommander.ui.theme.Theme\nimport com.mucommander.ui.theme.ThemeManager\nimport org.slf4j.LoggerFactory\nimport java.awt.Color\nimport java.awt.Font\n\n/**\n * Provides terminal settings synchronized with application theme.\n * Extends DefaultSettingsProvider to integrate with JediTerm terminal emulator.\n * \n * @author Oleg Trifonov\n * Created on 27/10/14.\n */\nclass TerminalSettingsProvider : DefaultSettingsProvider() {\n    // Cached color palette to avoid recreation on each call\n    private var colorPalette: ColorPalette? = null\n    private val log = LoggerFactory.getLogger(TerminalSettingsProvider::class.java)\n\n    override fun getDefaultForeground(): TerminalColor =\n        getThemeColor(Theme.TERMINAL_FOREGROUND_COLOR, Color.GREEN).toTerm()\n\n    override fun getDefaultBackground(): TerminalColor =\n        getThemeColor(Theme.TERMINAL_BACKGROUND_COLOR, Color.BLACK).toTerm()\n\n    override fun getSelectionColor(): TextStyle {\n        val fg = getThemeColor(Theme.TERMINAL_SELECTED_FOREGROUND_COLOR, Color.WHITE)\n        val bg = getThemeColor(Theme.TERMINAL_SELECTED_BACKGROUND_COLOR, Color(0x6666ff))\n        return TextStyle(fg.toTerm(), bg.toTerm())\n    }\n\n    override fun getFoundPatternColor(): TextStyle =\n        // Use selection colors for found pattern highlighting\n        getSelectionColor()\n\n    override fun getHyperlinkColor(): TextStyle {\n        val hyperlinkColor = getThemeColor(Theme.TERMINAL_FOREGROUND_COLOR, Color.BLUE)\n        return TextStyle(hyperlinkColor.toTerm(), null)\n    }\n\n    override fun getHyperlinkHighlightingMode(): HighlightMode =\n        HighlightMode.HOVER\n\n    override fun getTerminalFont(): Font =\n        ThemeManager.getCurrentFont(Theme.TERMINAL_FONT) ?: super.getTerminalFont()\n\n\n    override fun getTerminalFontSize(): Float =\n        getTerminalFont().getSize2D()\n\n\n    override fun getTerminalColorPalette(): ColorPalette? {\n        if (colorPalette == null) {\n            colorPalette = createColorPalette()\n        }\n        return colorPalette\n    }\n\n    override fun useInverseSelectionColor(): Boolean = false\n    override fun copyOnSelect(): Boolean = true        // Enable copy on select for better UX\n    override fun pasteOnMiddleMouseClick(): Boolean = true\n    override fun emulateX11CopyPaste(): Boolean = false\n    override fun useAntialiasing(): Boolean = true\n    override fun audibleBell(): Boolean = false\n    override fun enableMouseReporting(): Boolean = true\n    override fun caretBlinkingMs(): Int = 500\n    override fun scrollToBottomOnTyping(): Boolean = true\n    override fun maxRefreshRate(): Int = 50\n    override fun DECCompatibilityMode(): Boolean = true\n    override fun forceActionOnMouseReporting(): Boolean = false\n    override fun getBufferMaxLinesCount(): Int = 5000\n    override fun altSendsEscape(): Boolean = true\n    override fun ambiguousCharsAreDoubleWidth(): Boolean = false\n    override fun getTypeAheadSettings() = TerminalTypeAheadSettings.DEFAULT\n\n    /**\n     * Safely retrieves color from ThemeManager with fallback value.\n     * \n     * @param themeId theme color identifier\n     * @param defaultColor fallback color if theme manager returns null\n     * @return color from theme or default color\n     */\n    private fun getThemeColor(themeId: Int, defaultColor: Color): Color =\n        try {\n            ThemeManager.getCurrentColor(themeId) ?: defaultColor\n        } catch (e: Exception) {\n            log.warn(\"Failed to get theme color for id={}, using default\", themeId, e)\n            defaultColor\n        }\n\n    /**\n     * Creates color palette from current theme.\n     * \n     * @return color palette with ANSI colors from theme\n     */\n    private fun createColorPalette(): ColorPalette =\n        if (OsFamily.getCurrent() == OsFamily.WINDOWS) {\n            ColorPaletteImpl.WINDOWS_PALETTE\n        } else {\n            ColorPaletteImpl.XTERM_PALETTE\n        }\n\n    private fun Color.toTerm(): TerminalColor =\n        rgb(red, green, blue)\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/text/FileLabel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.text;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.icon.FileIcons;\n\nimport javax.swing.*;\n\n/**\n * A simple JLabel that displays information about a file:\n * <ul>\n *  <li>the label's text is set to the file's name or canonical path (specified in the constructor)</li>\n *  <li>the label's icon is set to the file's icon, as returned by {@link FileIcons#getFileIcon(com.mucommander.commons.file.AbstractFile)}</li>\n *  <li>the label's tooltip is set to the file's canonical path, only if the label's text is the file's name</li>\n * </ul>\n *\n * @author Maxence Bernard\n */\npublic class FileLabel extends JLabel {\n\n    /**\n     * Creates a new FileLabel, showing the file's name or full canonical path depending on the value of\n     * <code>showFullPath</code>.\n     *\n     * @param file the file to show\n     * @param showFullPath if true, the file's canonical path will be displayed, if false its filename.\n     */\n    public FileLabel(AbstractFile file, boolean showFullPath) {\n        String path = file.getCanonicalPath();\n\n        if (showFullPath) {\n            setText(path);\n        } else {\n            setText(file.getName());\n            setToolTipText(path);\n        }\n\n        setIcon(FileIcons.getFileIcon(file));\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/text/FilePathField.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.text;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.autocomplete.BasicAutocompleterTextComponent;\nimport com.mucommander.ui.autocomplete.CompleterFactory;\nimport com.mucommander.ui.autocomplete.TextFieldCompletion;\nimport com.mucommander.ui.autocomplete.completers.PathCompleter;\n\nimport javax.swing.*;\nimport javax.swing.text.Document;\n\n/**\n * <code>FilePathField</code> is a text field that is made to receive a file path. It provides auto-completion\n * capabilities, suggesting files/folders to the user as the path is being entered.\n *\n * @author Maxence Bernard\n */\npublic class FilePathField extends JTextField {\n\n    private PathCompleter pathCompleter;\n\n    public FilePathField() {\n        super();\n        init();\n    }\n\n    public FilePathField(String text) {\n        super(text);\n        init();\n    }\n\n    public FilePathField(int columns) {\n        super(columns);\n        init();\n    }\n\n    public FilePathField(String text, int columns) {\n        super(text, columns);\n        init();\n    }\n\n    public FilePathField(Document doc, String text, int columns) {\n        super(doc, text, columns);\n        init();\n    }\n\n\n    /**\n     * Adds auto-completion capabilities to this text field.\n     */\n    private void init() {\n        pathCompleter = (PathCompleter)CompleterFactory.getPathCompleter();\n        new TextFieldCompletion(new BasicAutocompleterTextComponent(this), pathCompleter);\n        new FilePathFieldKeyListener(this, true);\n    }\n\n\n    public void setDefaultLocation(AbstractFile dir) {\n        pathCompleter.setCurrentLocation(dir);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/text/FilePathFieldKeyListener.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2017 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.text;\n\nimport javax.swing.*;\nimport java.awt.event.KeyEvent;\nimport java.awt.event.KeyListener;\n\n/**\n * @author Oleg Trifonov\n * Created on 25/01/17.\n */\npublic class FilePathFieldKeyListener implements KeyListener {\n\n    private final JTextField textField;\n    private final boolean deleteOnFirstAction;\n\n    protected FilePathFieldKeyListener(JTextField textField, boolean deleteOnFirstAction) {\n        super();\n        this.textField = textField;\n        this.deleteOnFirstAction = deleteOnFirstAction;\n        textField.addKeyListener(this);\n    }\n\n    @Override\n    public void keyPressed(KeyEvent e) {\n        // Key listener to detect first left/right arrow pressed\n        // if user press the left button then move cursor to the start of file name\n        // if user press the right button then move cursor to the end of file name\n\n        if (e.getKeyCode() == KeyEvent.VK_LEFT) {\n            if (deleteOnFirstAction) {\n                textField.removeKeyListener(this);\n            }\n            if (e.getModifiersEx() != 0) {\n                return;\n            }\n            int len = textField.getText().length();\n            int pos = textField.getCaretPosition();\n            String selected = textField.getSelectedText();\n            String text = textField.getText();\n\n            if (selected != null && ((len > pos && text.charAt(pos) == '.') || len == pos)) {\n                int newPos = pos - selected.length();\n                if (newPos >= 0) {\n                    textField.setCaretPosition(newPos);\n                }\n                e.consume();\n            }\n        } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {\n            if (deleteOnFirstAction) {\n                textField.removeKeyListener(this);\n            }\n            if (e.getModifiersEx() != 0) {\n                return;\n            }\n            int pos = textField.getCaretPosition();\n            String selected = textField.getSelectedText();\n            String text = textField.getText();\n\n            if (selected != null && text.length() > pos && text.charAt(pos)  == '.') {\n                textField.setCaretPosition(pos);\n                e.consume();\n            }\n        }\n\n    }\n\n    @Override\n    public void keyTyped(KeyEvent e) {\n\n    }\n\n\n    @Override\n    public void keyReleased(KeyEvent e) {\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/text/FontUtils.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.text;\n\nimport com.mucommander.RuntimeConstants;\nimport com.mucommander.commons.runtime.OsFamily;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n/**\n * This class contains a set of helper methods that allow to easily change the font of a <code>JComponent</code>\n *\n * @author Maxence Bernard\n */\npublic class FontUtils {\n    public static void setup() {\n        if (OsFamily.getCurrent() == OsFamily.LINUX && RuntimeConstants.DISPLAY_4K) {\n            scaleFont(\"Menu.font\", 18, 24);\n            scaleFont(\"MenuItem.font\", 18, 24);\n            scaleFont(\"CheckBoxMenuItem.font\", 18, 24);\n            scaleFont(\"TabbedPane.font\", 18, 22);\n            scaleFont(\"CheckBox.font\", 18, 24);\n            scaleFont(\"ComboBox.font\", 18, 24);\n            scaleFont(\"RadioButton.font\", 18, 24);\n            scaleFont(\"Button.font\", 18, 22);\n            scaleFont(\"Label.font\", 18, 24);\n            scaleFont(\"List.font\", 18, 22);\n            scaleFont(\"TextField.font\", 18, 22);\n            scaleFont(\"ToolTip.font\", 18, 24);\n            scaleFont(\"Table.font\", 18, 22);\n            scaleFont(\"TableHeader.font\", 18, 24);\n            scaleFont(\"TitledBorder.font\", 18, 24);\n            scaleFont(\"ProgressBar.font\", 18, 22);\n            scaleFont(\"JideSplitButton.font\", 18, 22);\n        }\n    }\n\n    private static void scaleFont(String uiManagerName, int minSize, int size) {\n        Font font = UIManager.getFont(uiManagerName);\n        if (font != null && font.getSize() <= minSize) {\n            Font newFont = new Font(font.getFontName(), font.getStyle(), size);\n            UIManager.put(uiManagerName, newFont);\n        }\n    }\n\n    public static Font scaleFont(Font font, int minSize, int size) {\n        if (font.getSize() <= minSize) {\n            return  new Font(font.getFontName(), font.getStyle(), size);\n        } else {\n            return font;\n        }\n    }\n\n    public static void scaleFont(JComponent component, int minSize, int size) {\n        component.setFont(scaleFont(component.getFont(), minSize, size));\n    }\n\n\n    /**\n     * Changes the style of the given component's font. Other attributes of the font are left unchanged.\n     *\n     * @param comp the component for which to change the font\n     * @param newStyle the new Font style to use, see <code>java.awt.Font</code> for allowed values\n     * @return the component that was passed, for convenience only\n     */\n    public static JComponent changeStyle(JComponent comp, int newStyle) {\n        comp.setFont(comp.getFont().deriveFont(newStyle));\n        return comp;\n    }\n\n    /**\n     * Changes the size of the given component's font. Other attributes of the font are left unchanged.\n     *\n     * @param comp the component for which to change the font\n     * @param newSize the new Font size to use, see <code>java.awt.Font</code> for allowed values\n     * @return the component that was passed, for convenience only\n     */\n    public static JComponent changeSize(JComponent comp, float newSize) {\n        comp.setFont(comp.getFont().deriveFont(newSize));\n        return comp;\n    }\n\n    /**\n     * Changes the style and size of the given component's font. Other attributes of the font are left unchanged.\n     *\n     * @param comp the component for which to change the font\n     * @param newStyle the new Font style to use, see <code>java.awt.Font</code> for allowed values\n     * @param newSize the new Font size to use, see <code>java.awt.Font</code> for allowed values\n     * @return the component that was passed, for convenience only\n     */\n    public static JComponent changeStyleAndSize(JComponent comp, int newStyle, float newSize) {\n        comp.setFont(comp.getFont().deriveFont(newStyle, newSize));\n        return comp;\n    }\n\n    /**\n     * Changes the style of the given component's font to {@link java.awt.Font#BOLD}.\n     * Other attributes of the font are left unchanged.\n     *\n     * @param comp the component for which to change the font\n     * @return the component that was passed, for convenience only\n     */\n    public static JComponent makeBold(JComponent comp) {\n        changeStyle(comp, Font.BOLD);\n        return comp;\n    }\n\n    /**\n     * Changes the style of the given component's font to {@link java.awt.Font#ITALIC}.\n     * Other attributes of the font are left unchanged.\n     *\n     * @param comp the component for which to change the font\n     * @return the component that was passed, for convenience only\n     */\n    public static JComponent makeItalic(JComponent comp) {\n        changeStyle(comp, Font.BOLD);\n        return comp;\n    }\n\n    /**\n     * Changes the style of the given component's font to {@link java.awt.Font#BOLD}|{@link java.awt.Font#ITALIC}.\n     * Other attributes of the font are left unchanged.\n     *\n     * @param comp the component for which to change the font\n     * @return the component that was passed, for convenience only\n     */\n    public static JComponent makeBoldItalic(JComponent comp) {\n        changeStyle(comp, Font.BOLD|Font.ITALIC);\n        return comp;\n    }\n\n    /**\n     * Changes the style of the given component's font to {@link java.awt.Font#PLAIN}.\n     * Other attributes of the font are left unchanged.\n     *\n     * @param comp the component for which to change the font\n     * @return the component that was passed, for convenience only\n     */\n    public static JComponent makePlain(JComponent comp) {\n        changeStyle(comp, Font.PLAIN);\n        return comp;\n    }\n    /**\n     * Decreases the size of the given component's font by 2 units. Other attributes of the font are left unchanged.\n     *\n     * @param comp the component for which to change the font\n     * @return the component that was passed, for convenience only\n     */\n    public static JComponent makeMini(JComponent comp) {\n        changeSize(comp, comp.getFont().getSize()-2);\n        return comp;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/text/KeyStrokeUtils.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.text;\r\n\r\nimport java.awt.event.InputEvent;\r\nimport java.awt.event.KeyEvent;\r\n\r\nimport javax.swing.KeyStroke;\r\n\r\nimport com.mucommander.commons.runtime.OsFamily;\r\n\r\n/**\r\n * This class offers utility methods for converting KeyStrokes to texts.\r\n * \r\n * @author Arik Hadas, Maxence Bernard\r\n */\r\npublic class KeyStrokeUtils {\r\n    private final static String SHIFT_MODIFIER_STRING = InputEvent.getModifiersExText(KeyEvent.SHIFT_DOWN_MASK);\r\n    private final static String CTRL_MODIFIER_STRING  = InputEvent.getModifiersExText(KeyEvent.CTRL_DOWN_MASK);\r\n    private final static String ALT_MODIFIER_STRING   = InputEvent.getModifiersExText(KeyEvent.ALT_DOWN_MASK);\r\n    private final static String META_MODIFIER_STRING  = InputEvent.getModifiersExText(KeyEvent.META_DOWN_MASK);\r\n\r\n\t\r\n    /**\r\n     * Returns a String representation for the given KeyStroke for display, in the following format:<br>\r\n     * <code>modifier+modifier+...+key</code>\r\n     *\r\n     * <p>For example, <code>KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK|InputEvent.ALT_MASK)</code>\r\n     * will return <code>Ctrl+Alt+C</code>.\r\n     *\r\n     * @param ks the KeyStroke for which to return a String representation\r\n     * @return a String representation of the given KeyStroke for display, in the <code>[modifier]+[modifier]+...+key</code> format\r\n     */\r\n\tpublic static String getKeyStrokeRepresentation(KeyStroke ks) {\r\n\t\treturn ks.toString().replaceFirst(\"(released )|(pressed )|(typed )\", \"\");\r\n\t}\r\n\t\r\n\t/**\r\n     * Returns a String representation for the given KeyStroke <b>for display</b>, in the following format:<br>\r\n     * <code>modifier+modifier+...+key</code>\r\n     *\r\n     * <p>For example, <code>KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK|InputEvent.ALT_MASK)</code>\r\n     * will return <code>Ctrl+Alt+C</code>.\r\n     *\r\n     * @param ks the KeyStroke for which to return a String representation\r\n     * @return a String representation of the given KeyStroke <b>for display</b>, in the <code>[modifier]+[modifier]+...+key</code> format\r\n     */\r\n    public static String getKeyStrokeDisplayableRepresentation(KeyStroke ks) {\r\n    \tif (ks == null) {\r\n            return null;\r\n        }\r\n    \t\r\n        int modifiers = ks.getModifiers();\r\n        String keyText = KeyEvent.getKeyText(ks.getKeyCode());\r\n\r\n        if (modifiers != 0) {\r\n            return getModifiersDisplayableRepresentation(modifiers)+\"+\"+keyText;\r\n        }\r\n        return keyText;\r\n    }\r\n\r\n    /**\r\n     * Returns a String representations of the given modifiers bitwise mask, in the following format:<br>\r\n     * <code>modifier+...+modifier</code>\r\n     *\r\n     * <p>The modifiers' order in the returned String tries to mimick the keyboard layout of the current platform as\r\n     * much as possible:\r\n     *\r\n     * <p><ul>\r\n     *  <li>Under Mac OS X, the order is: <code>Shift, Ctrl, Alt, Meta</code>\r\n     *  <li>Under other platforms, the order is <code>Shift, Ctrl, Meta, Alt</code>\r\n     * </ul><p>\r\n     *\r\n     * @param modifiers a modifiers bitwise mask\r\n     * @return a String representations of the given modifiers bitwise mask\r\n     */\r\n    public static String getModifiersDisplayableRepresentation(int modifiers) {\r\n        StringBuilder result = new StringBuilder();\r\n\r\n        if ((modifiers&KeyEvent.SHIFT_DOWN_MASK) != 0) {\r\n            result.append(SHIFT_MODIFIER_STRING);\r\n        }\r\n\r\n        if ((modifiers&KeyEvent.CTRL_DOWN_MASK) != 0) {\r\n            appendModifier(result, CTRL_MODIFIER_STRING);\r\n        }\r\n\r\n        if (OsFamily.MAC_OS_X.isCurrent()) {\r\n            if ((modifiers&KeyEvent.ALT_DOWN_MASK) != 0) {\r\n                appendModifier(result, ALT_MODIFIER_STRING);\r\n            }\r\n            if ((modifiers&KeyEvent.META_DOWN_MASK) != 0) {\r\n                appendModifier(result, META_MODIFIER_STRING);\r\n            }\r\n        } else {\r\n            if ((modifiers&KeyEvent.META_DOWN_MASK) != 0) {\r\n                appendModifier(result, META_MODIFIER_STRING);\r\n            }\r\n            if ((modifiers&KeyEvent.ALT_DOWN_MASK) != 0) {\r\n                appendModifier(result, ALT_MODIFIER_STRING);\r\n            }\r\n        }\r\n\r\n        return result.toString();\r\n    }\r\n\r\n    private static void appendModifier(StringBuilder sb, String s) {\r\n        if (!sb.isEmpty()) {\r\n            sb.append('+');\r\n        }\r\n        sb.append(s);\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/text/MultiLineLabel.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.text;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.ComponentAdapter;\nimport java.awt.event.ComponentEvent;\n\n/**\n * MultiLineLabel is a line-wrapping label that spawns the text over multiple lines as necessary. Unlike what its name\n * suggests, it is derived from <code>JTextArea</code> and not <code>JLabel</code>, but it looks just like a label.\n * When added to a container, this component takes just the amount of space it needs, without the need to set a fixed\n * number of rows or columns.\n *\n * @author Maxence Bernard\n */\npublic class MultiLineLabel extends JTextArea {\n\n    /**\n     * Equivalent to calling {@link #MultiLineLabel(String, boolean)} with auto-repack enabled. \n     *\n     * @param text the initial label's text\n     */\n    public MultiLineLabel(String text) {\n        this(text, true);\n    }\n\n    /**\n     * Creates a new <code>MultiLineLabel</code>, spawning over multiple lines as necessary. By default, lines are\n     * wrapped at word boundaries, i.e. words are not split over multiple lines. This behavior can be changed by\n     * calling {@link #setWrapStyleWord(boolean)}.\n     * <p>\n     * The <code>autoRepack</code> parameter allows to automatically issue an extra call to the <code>pack()</code>\n     * method of the Window that contains this component, for the window to be layed out properly. This works around a\n     * well-known bug that affects line-wrapping text components which report an incorrect preferred size, causing\n     * layout issues. This parameter should be always enabled unless a fixed number of rows or columns is set using\n     * {@link #setRows(int)} or {@link #setColumns(int)}.<br>\n     * For reference, here are links to the afore-mentionned issue:\n     * <ul>\n     *  <li>http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4924163</li>\n     *  <li>http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4446522</li>\n     * </ul>\n     *\n     * @param text the initial label's text\n     * @param autoRepack if <code>true</code>, an extra call to the <code>pack()</code> method of the Window that\n     * contains this component will be automatically issued after this component has first been layed out.\n     */\n    public MultiLineLabel(String text, boolean autoRepack) {\n        super(text);\n        setEditable(false);\n        setLineWrap(true);\n        setWrapStyleWord(true);\n\n        // Make this text area look like a label\n        setOpaque(false);\n        setBackground((Color) UIManager.get(\"Label.background\"));\n        setForeground((Color) UIManager.get(\"Label.foreground\"));\n        setFont((Font) UIManager.get(\"Label.font\"));\n\n        if (autoRepack) {\n            addComponentListener(new ComponentAdapter() {\n                @Override\n                public void componentResized(ComponentEvent e) {\n                    Container tla = getTopLevelAncestor();\n                    if(tla instanceof Window) {\n                        ((Window)tla).pack();\n                    }\n\n                    removeComponentListener(this);\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/text/RecordingKeyStrokeTextField.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.text;\r\n\r\nimport javax.swing.*;\r\nimport javax.swing.border.Border;\r\nimport java.awt.*;\r\nimport java.awt.event.FocusEvent;\r\nimport java.awt.event.FocusListener;\r\nimport java.awt.event.KeyEvent;\r\nimport java.awt.event.KeyListener;\r\n\r\n/**\r\n * <code>RecordingKeyStrokeTextField</code> is a text field that record a KeyStroke entered by the user.\r\n * \r\n * @author Arik Hadas\r\n */\r\npublic class RecordingKeyStrokeTextField extends JTextField implements FocusListener, KeyListener {\r\n\r\n\t/** The last KeyStroke that was entered to the field. */\r\n\tprivate KeyStroke lastKeyStroke;\r\n\t\r\n\t/** The default border of JTextField */\r\n\tprivate final Border defaultTextFieldBorder = getBorder();\r\n\t\r\n\tprotected RecordingKeyStrokeTextField(int columns, KeyStroke keyStroke) {\r\n\t\t// set text field's length\r\n\t\tsetColumns(columns);\r\n\t\t// The text will be shown at the center of the text field\r\n\t\tsetHorizontalAlignment(JTextField.CENTER);\r\n\t\t// The text field should not be editable\r\n\t\tsetEditable(false);\r\n\t\t// Change colors to prevent the user from marking the field's text\r\n\t\tsetSelectionColor(UIManager.getColor(\"jtextfield.background\"));\r\n\t\tsetSelectedTextColor(getForeground());\r\n\t\t// Use JTextField's \"setText\" method to set the initial KeyStroke in the text field\r\n\t\tsuper.setText(KeyStrokeUtils.getKeyStrokeDisplayableRepresentation(lastKeyStroke = keyStroke));\r\n\t\t\r\n\t\t// Add listeners:\r\n\t\taddFocusListener(this);\r\n\t\taddKeyListener(this);\r\n\t}\r\n\t\r\n\t/**\r\n\t * This method is used to fetch the KeyStroke in the text-field.\r\n\t * The returned KeyStrole is the last KeyStroke entered to the field by the user, or\r\n\t * the initial KeyStroke that was loaded to the field if the user didn't entered anything.\r\n\t * \r\n\t * @return the KeyStroke in the text-field\r\n\t */\r\n\tpublic KeyStroke getKeyStroke() { return lastKeyStroke; }\r\n\t\r\n\t//////////////////////////////////\r\n\t/////  FocusListener methods  ////\r\n\t/////////////////////////////////\r\n\r\n\tpublic void focusGained(FocusEvent e) {\r\n\t\t// change border to indicate this field gained the focus\r\n\t\t// and the user can type\r\n\t\tsetBorder(BorderFactory.createLineBorder(Color.orange, 2));\r\n\t}\r\n\r\n\tpublic void focusLost(FocusEvent e) {\r\n\t\t// change border to indicate this field lost the focus\r\n\t\tsetBorder(defaultTextFieldBorder);\r\n\t}\r\n\t\r\n\t//////////////////////////////// \r\n\t/////  KeyListener methods  ////\r\n\t////////////////////////////////\r\n\t\r\n\tpublic void keyPressed(KeyEvent e) {\r\n\t\tif (e.getKeyCode() != KeyEvent.VK_ESCAPE) {\r\n\t\t\tlastKeyStroke = KeyStroke.getKeyStroke(e.getKeyCode(), 0);\r\n\t\t\tsetText(KeyStrokeUtils.getKeyStrokeDisplayableRepresentation(lastKeyStroke));\r\n\t\t}\r\n\t\te.consume();\r\n\t}\r\n\r\n\tpublic void keyReleased(KeyEvent e) {}\r\n\r\n\tpublic void keyTyped(KeyEvent e) {}\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/text/SizeConstrainedDocument.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.text;\n\n\n/**\n * Document that can be used with <code>java.swing.JTextField</code> and <code>javax.swing.JTextArea</code> to limit\n * the number of characters that can be entered by the user.\n *\n * @author Maxence Bernard\n */\npublic class SizeConstrainedDocument extends javax.swing.text.PlainDocument {\n\n    /** Maximum number of characters allowed */\n    private final int maxLen;\n\n    /**\n     * Creates a new instance of SizeConstrainedDocument, using the specified length\n     * to limit the number of characters allowed.\n     *\n     * @param maxLen maximum number of characters allowed\n     */\n    public SizeConstrainedDocument(int maxLen) {\n        this.maxLen = maxLen;\n    }\n\n\n    @Override\n    public void insertString(int offset, String str, javax.swing.text.AttributeSet attributeSet) throws javax.swing.text.BadLocationException {\n        if (str != null && maxLen > 0 && this.getLength() + str.length() > maxLen) {\n            return;\n        }\n\n        super.insertString(offset, str, attributeSet);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/theme/ColorChangedEvent.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.theme;\n\nimport java.awt.*;\n\npublic class ColorChangedEvent {\n    private final Theme source;\n    private final int colorId;\n    private final Color color;\n\n    ColorChangedEvent(Theme source, int colorId, Color color) {\n        this.source = source;\n        this.colorId = colorId;\n        this.color = color;\n    }\n\n    public boolean isDefaultColor() {\n        return source == null;\n    }\n\n    public Theme getSource() {\n        return source;\n    }\n\n    public int getColorId() {\n        return colorId;\n    }\n\n    public Color getColor() {\n        return color;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/theme/ComponentMapper.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.theme;\n\nimport javax.swing.*;\n\n/**\n * Used to provide instances of {@link JComponent} to {@link SystemDefaultColor} and {@link SystemDefaultFont}.\n * @author Nicolas Rinaudo\n */\npublic abstract class ComponentMapper {\n    /**\n     * Returns a new instance of the {@link JComponent} this instance maps.\n     * @return a new instance of the {@link JComponent} this instance maps.\n     */\n    public abstract JComponent getComponent();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/theme/DefaultColor.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.theme;\n\n\nimport java.awt.Color;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Represents a default value for a theme color.\n * <p>\n * Instances of this class are used to provide default values for theme colors and notify the current theme when they\n * are modified.\n * <p>\n * If, for example, a color should default to the look and feel defined TextArea foreground color and the look and feel\n * is changed, the corresponding {@link DefaultColor} instance will catch that event and notify the current theme of the\n * change.\n *\n * @author Nicolas Rinaudo\n */\npublic abstract class DefaultColor {\n    /** List of colors linked to this default value. */\n    private final List<Integer> linkedColors = new ArrayList<>();\n\n\n    /**\n     * Creates a new instance of {@link DefaultColor}.\n     */\n    DefaultColor() {\n    }\n\n\n    /**\n     * Notifies the current theme of a default value change to all linked colors.\n     * @param color new default color value.\n     */\n    void notifyChange(Color color) {\n        for(int i : linkedColors) {\n            ThemeData.triggerColorEvent(i, color);\n        }\n    }\n\n    /**\n     * Registers a theme color as defaulting to the current instance.\n     * <p>\n     * If the default color's value were to change, the current theme will automatically be notified of the change and\n     * ultimately propagate to all registered theme listeners if necessary.\n     *\n     * @param colorId identifier of the color that uses this instance as a default value.\n     */\n    public void link(Integer colorId) {\n        linkedColors.add(colorId);\n    }\n\n\n    /**\n     * Returns the color this default value represents.\n     * @param  data contains all the current theme values.\n     * @return      the color this default value represents.\n     */\n    public abstract Color getColor(ThemeData data);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/theme/DefaultFont.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.theme;\n\nimport java.awt.Font;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Represents a default value for a theme font.\n * <p>\n * Instances of this class are used to provide default values for theme fonts and notify the current theme when they\n * are modified.\n *\n * <p>\n * If, for example, a font should default to the look and feel defined TextArea font and the look and feel is changed,\n * the corresponding {@link DefaultFont} instance will catch that event and notify the current theme of the change.\n * @author Nicolas Rinaudo\n */\npublic abstract class DefaultFont {\n    // - Instance fields -----------------------------------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------------------------------------\n    /** List of fonts linked to this default value. */\n    private List<Integer> linkedFonts = new ArrayList<>();\n\n\n\n    // - Event propagation ---------------------------------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------------------------------------\n    /**\n     * Registers a theme font as defaulting to the current instance.\n     * <p>\n     * If the default font's value were to change, the current theme will automatically be notified of the change and\n     * ultimately propagate to all registered theme listeners if necessary.\n     *\n     * @param fontId identifier of the font that uses this instance as a default value.\n     */\n    public void link(Integer fontId) {\n        linkedFonts.add(fontId);\n    }\n\n    /**\n     * Notifies the current theme of a default value change to all linked fonts.\n     * @param font new default font value.\n     */\n    protected void notifyChange(Font font) {\n        for (int i : linkedFonts) {\n            ThemeData.triggerFontEvent(i, font);\n        }\n    }\n\n\n\n    // - Abstract methods ----------------------------------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------------------------------------\n    /**\n     * Returns the font this default value represents.\n     * @param  data contains all the current theme values.\n     * @return      the font this default value represents.\n     */\n    public abstract Font getFont(ThemeData data);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/theme/EditorTheme.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.theme;\n\nimport org.fife.ui.rsyntaxtextarea.*;\nimport org.fife.ui.rsyntaxtextarea.Theme;\n\nimport java.awt.Font;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n/**\n * Created on 04/01/14.\n */\npublic class EditorTheme {\n    private org.fife.ui.rsyntaxtextarea.Theme theme;\n\n    private EditorTheme() {\n    }\n\n    private EditorTheme(org.fife.ui.rsyntaxtextarea.Theme theme) {\n        this.theme = theme;\n    }\n\n    public EditorTheme(RSyntaxTextArea textArea) {\n        theme = new Theme(textArea);\n    }\n\n    public static EditorTheme load(InputStream in) throws IOException {\n        return new EditorTheme(org.fife.ui.rsyntaxtextarea.Theme.load(in));\n    }\n\n    public static EditorTheme load(InputStream in, Font baseFont) throws IOException {\n        return new EditorTheme(org.fife.ui.rsyntaxtextarea.Theme.load(in, baseFont));\n    }\n\n    public void apply(RSyntaxTextArea textArea) {\n        theme.apply(textArea);\n    }\n\n    public void save(OutputStream out) throws IOException {\n        theme.save(out);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/theme/FixedDefaultColor.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.theme;\n\nimport java.awt.*;\n\n/**\n * {@link DefaultColor} implementation that maps to a fixed value.\n * @author Nicolas Rinaudo\n */\npublic class FixedDefaultColor extends DefaultColor {\n    /** Color to default to. */\n    private final Color color;\n\n\n    /**\n     * Creates a new instance of {@link FixedDefaultColor}.\n     * @param color color to default to.\n     */\n    public FixedDefaultColor(Color color) {\n        this.color = color;\n    }\n\n\n    @Override\n    public Color getColor(ThemeData data) {\n        return color;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/theme/FixedDefaultFont.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.theme;\n\nimport java.awt.*;\n\n/**\n * {@link DefaultFont} implementation that maps to a fixed value.\n * @author Nicolas Rinaudo\n */\npublic class FixedDefaultFont extends DefaultFont {\n    /** Font to default to. */\n    private final Font font;\n\n\n    /**\n     * Creates a new instance of {@link FixedDefaultFont}.\n     * @param font font to default to.\n     */\n    FixedDefaultFont(Font font) {\n        this.font = font;\n    }\n\n\n    @Override\n    public Font getFont(ThemeData data) {\n        return font;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/theme/FontChangedEvent.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.theme;\n\nimport java.awt.*;\n\npublic class FontChangedEvent {\n    private final Theme source;\n    private final int fontId;\n    private final Font font;\n\n    FontChangedEvent(Theme source, int fontId, Font font) {\n        this.source = source;\n        this.fontId = fontId;\n        this.font   = font;\n    }\n\n    public boolean isDefaultFont() {\n        return source == null;\n    }\n\n    public Theme getSource() {\n        return source;\n    }\n\n    public int getFontId() {\n        return fontId;\n    }\n\n    public Font getFont() {\n        return font;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/theme/LinkedDefaultColor.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.theme;\n\nimport java.awt.*;\n\n/**\n * {@link DefaultColor} implementation that maps to a value in the current theme.\n * <p>\n * This is typically useful to make sure that a color always defaults to the same value as another theme property.\n * Care should be exercised when using this class, however: it doesn't check for infinite recursion, meaning it's\n * entirely possible to freeze muCommander by linking a color to itself as a default.\n *\n * @author Nicolas Rinaudo\n */\npublic class LinkedDefaultColor extends DefaultColor implements ThemeListener {\n    /** Identifier of the current theme color to default to. */\n    private final int colorId;\n\n    /**\n     * Creates a new instance of {@link LinkedDefaultColor}.\n     * @param colorId identifier of the current theme color to default to.\n     */\n    LinkedDefaultColor(int colorId) {\n        this.colorId = colorId;\n        ThemeData.addDefaultValuesListener(this);\n    }\n\n    @Override\n    public Color getColor(ThemeData data) {\n        return data.getColor(colorId); \n    }\n\n\n    public void colorChanged(ColorChangedEvent event) {\n        if (event.getColorId() == colorId) {\n            notifyChange(event.getColor());\n        }\n    }\n\n    public void fontChanged(FontChangedEvent event) {\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/theme/LinkedDefaultFont.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.theme;\n\nimport java.awt.*;\n\n/**\n * {@link DefaultFont} implementation that maps to a value in the current theme.\n * <p>\n * This is typically useful to make sure that a font always defaults to the same value as another theme property.\n * Care should be exercised when using this class, however: it doesn't check for infinite recursion, meaning it's\n * entirely possible to freeze muCommander by linking a font to itself as a default.\n *\n * @author Nicolas Rinaudo\n */\npublic class LinkedDefaultFont extends DefaultFont implements ThemeListener {\n    /** Identifier of the current theme font to default to. */\n    private int id;\n\n\n\n    /**\n     * Creates a new instance of {@link LinkedDefaultFont}.\n     * @param id identifier of the current theme font to default to.\n     */\n    public LinkedDefaultFont(int id) {\n        this.id = id;\n    }\n\n\n\n    @Override\n    public Font getFont(ThemeData data) {\n        return data.getFont(id);\n    }\n\n\n\n    public void colorChanged(ColorChangedEvent event) {\n    }\n\n    public void fontChanged(FontChangedEvent event) {\n        if (event.getFontId() == id) {\n            notifyChange(event.getFont());\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/theme/SystemDefaultColor.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.theme;\n\nimport javax.swing.*;\nimport javax.swing.text.JTextComponent;\nimport java.awt.*;\nimport java.beans.PropertyChangeEvent;\nimport java.beans.PropertyChangeListener;\n\n/**\n * {@link DefaultColor} implementation that maps to a system value.\n * <p>\n * The purpose of this class is to create default colors that map, for example, to the default text area foreground\n * color for the current look and feel.\n *\n * <p>\n * The mechanism used to identify the default color goes through three different stages:\n * <ul>\n * <li>Look for a specific property in {@link UIManager}.</li>\n * <li>\n * If this isn't found, rely on a {@link ComponentMapper} to get an instance of the target and retrieve the relevant\n * color.\n * </li>\n * <li>\n * If this is <code>null</code>, return a hard-coded default value.\n * </li>\n * </ul>\n *\n * @author Nicolas Rinaudo\n */\npublic class SystemDefaultColor extends DefaultColor implements PropertyChangeListener {\n    // - Fallbacks -----------------------------------------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------------------------------------\n    /** Foreground color used in case no system default could be identified. */\n    private static final Color DEFAULT_FOREGROUND           = Color.BLACK;\n    /** Background color used in case no system default could be identified. */\n    private static final Color DEFAULT_BACKGROUND           = Color.WHITE;\n    /** Selection foreground color used in case no system default could be identified. */\n    private static final Color DEFAULT_SELECTION_FOREGROUND = Color.WHITE;\n    /** Selection background color used in case no system default could be identified. */\n    private static final Color DEFAULT_SELECTION_BACKGROUND = Color.BLUE;\n\n\n\n    /** Identifies a foreground color (linked to {@link JComponent#getForeground()}). */\n    static final int FOREGROUND = 1;\n    /** Identifies a background color (linked to {@link JComponent#getBackground()}). */\n    static final int BACKGROUND = 2;\n    /** Identifies a selection foreground color (linked to {@link JTextComponent#getSelectedTextColor()}). */\n    static final int SELECTION_FOREGROUND = 3;\n    /** Identifies a selection background color (linked to {@link JTextComponent#getSelectionColor()}). */\n    static final int SELECTION_BACKGROUND = 4;\n    /** Identifies a current line background color. */\n    static final int CURRENT_LINE_BACKGROUND = 5;\n\n\n    /** {@link UIManager} property to look for. */\n    private final String property;\n    /**\n     * Type of the default color (can be one of {@link #FOREGROUND}, {@link #BACKGROUND}, {@link #SELECTION_FOREGROUND}\n     * or {@link #SELECTION_BACKGROUND}). \n     */\n    private final int type;\n    /** Current default color value. */\n    private Color color;\n     /** Used to create instance of the component whose color will be retrieved (in case {@link #property} isn't set). */\n    private final ComponentMapper mapper;\n\n\n\n    /**\n     * Creates a new instance of {@link SystemDefaultColor}.\n     * @param type     type of the color being described (can be one of {@link #FOREGROUND}, {@link #BACKGROUND},\n     *                 {@link #SELECTION_FOREGROUND} or {@link #SELECTION_BACKGROUND}).\n     * @param property name of the {@link UIManager} property to look for.\n     * @param mapper   component mapper to use when the {@link UIManager} property isn't set.\n     */\n    SystemDefaultColor(int type, String property, ComponentMapper mapper) {\n        UIManager.addPropertyChangeListener(this);\n        this.property = property;\n        this.mapper   = mapper;\n        this.type     = type;\n    }\n\n\n\n    /**\n     * Returns the color of the right {@link #type type} used by the specified component.\n     * @param  component component to analyse.\n     * @return           the color of the right {@link #type type} used by the specified component.\n     */\n    private Color getColor(JComponent component) {\n        if (type == FOREGROUND) {\n            return component.getForeground();\n        } else if(type == BACKGROUND) {\n            return component.getBackground();\n        }\n\n        // Text component specific colors.\n        else if (component instanceof JTextComponent comp) {\n            if (type == SELECTION_FOREGROUND) {\n                return comp.getSelectedTextColor();\n            } else if(type == SELECTION_BACKGROUND) {\n                return comp.getSelectionColor();\n            }\n        }\n        return null;\n    }\n\n    /**\n     * Returns the fallback color of the right {@link #type type}.\n     * @return the fallback color of the right {@link #type type}.\n     */\n    private Color getColor() {\n        return switch (type) {\n            case FOREGROUND -> DEFAULT_FOREGROUND;\n            case SELECTION_FOREGROUND -> DEFAULT_SELECTION_FOREGROUND;\n            case SELECTION_BACKGROUND -> DEFAULT_SELECTION_BACKGROUND;\n            default -> DEFAULT_BACKGROUND;\n        };\n    }\n\n    @Override\n    public Color getColor(ThemeData data) {\n        if (color == null) {\n            if ((color = UIManager.getColor(property)) == null) {\n                if ((color = getColor(mapper.getComponent())) == null) {\n                    color = getColor();\n                }\n            }\n                \n            color = new Color(color.getRGB(), (color.getRGB() & 0xFF000000) != 0xFF000000);\n        }\n\n        return color;\n    }\n\n\n\n\n    // - PropertyChangeListener implementation -------------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------------------------------------\n    public void propertyChange(PropertyChangeEvent evt) {\n        String name = evt.getPropertyName().toLowerCase();\n\n        if (\"lookandfeel\".equals(name) || name.equalsIgnoreCase(property)) {\n            //color = null;\n            Color oldColor = color;\n            color = getColor((ThemeData)null);\n            if (!color.equals(oldColor)) {\n                notifyChange(color);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/theme/SystemDefaultFont.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.theme;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.beans.PropertyChangeEvent;\nimport java.beans.PropertyChangeListener;\n\n/**\n * {@link DefaultFont} implementation that maps to a system value.\n * <p>\n * The purpose of this class is to create default fonts that map, for example, to the default text area font for the\n * current look and feel.\n *\n * <p>\n * The mechanism used to identify the default font goes through three different stages:\n * <ul>\n * <li>Look for a specific property in {@link UIManager}.</li>\n * <li>\n * If this isn't found, rely on a {@link ComponentMapper} to get an instance of the target and retrieve its font.\n * </li>\n * <li>\n * If this is <code>null</code>, return a default SansSerif font.\n * </li>\n * </ul>\n *\n * @author Nicolas Rinaudo\n */\npublic class SystemDefaultFont extends DefaultFont implements PropertyChangeListener {\n    /** Name of the {@link UIManager#getFont(Object)} font property} to query. */\n    private final String property;\n    /** Current value of the default font. */\n    private Font font;\n    /** Used to create instance of the component whose font will be retrieved (in case {@link #property} isn't set). */\n    private final ComponentMapper mapper;\n\n\n\n    /**\n     * Creates a new instance of {@link SystemDefaultFont}.\n     * @param property {@link UIManager} property to query for the default font.\n     * @param mapper   component mapper to use when the {@link UIManager} property isn't set.\n     */\n    SystemDefaultFont(String property, ComponentMapper mapper) {\n        UIManager.addPropertyChangeListener(this);\n        this.property = property;\n        this.mapper = mapper;\n    }\n\n\n    @Override\n    public Font getFont(ThemeData data) {\n        // If the font hasn't been identified yet...\n        if (font == null) {\n            // ... try to retrieve it from the UIManager.\n            font = UIManager.getFont(property);\n            if (font == null)\n                // If the current l&f didn't set the right property, attempt to retrieve it from a component of the desired type.\n                font = mapper.getComponent().getFont();\n                if (font == null) {\n                    // If that failed, defaults to SansSerif (guaranteed to be supported by the VM).\n                    font = Font.decode(\"SansSerif\");\n                }\n        }\n        return font;\n    }\n\n\n    @Override\n    public void propertyChange(PropertyChangeEvent evt) {\n        // Monitors changes to both the global look & feel and the target property and react to them if necessary.\n        String name = evt.getPropertyName().toLowerCase();\n        if (name.equals(\"lookandfeel\") || name.equalsIgnoreCase(property)) {\n            Font oldFont = font;\n\n            // We first set font to null to ensure that the value is refreshed.\n            font = null;\n            font = getFont(null);\n\n            if (!font.equals(oldFont)) {\n                notifyChange(font);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/theme/Theme.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2020 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.theme;\n\nimport com.mucommander.utils.text.Translator;\n\nimport java.awt.*;\nimport java.util.WeakHashMap;\n\n/**\n * @author Nicolas Rinaudo\n */\npublic class Theme extends ThemeData {\n    // - Theme types ---------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    public enum Type {\n        /** Describes the user defined theme. */\n        USER,\n        /** Describes predefined muCommander themes. */\n        PREDEFINED,\n        /** Describes custom muCommander themes. */\n        CUSTOM\n    }\n\n\n\n    // - Theme listeners -----------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    private static final WeakHashMap<ThemeListener, ?> listeners = new WeakHashMap<>();\n\n\n    \n    // - Instance variables --------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    /** Theme name. */\n    private String name;\n    /** Theme type. */\n    private Type type;\n\n    // While this field might look useless, it's actually critical for proper event notification:\n    // ThemeData uses a weak hashmap to store its listeners, meaning that each listener must be 'linked'\n    // somewhere or be garbage collected. Simply put, if we do not store the instance here, we might\n    // as well not bother registering it.\n    private DefaultValuesListener defaultValuesListener;\n\n\n    /**\n     * Creates a new empty user theme.\n     */\n    Theme(ThemeListener listener) {\n        super();\n        init(listener, Type.USER, null);\n    }\n\n    Theme(ThemeListener listener, Type type, String name) {\n        super();\n        init(listener, type, name);\n    }\n\n    Theme(ThemeListener listener, ThemeData template) {\n        super(template);\n        init(listener, Type.USER, null);\n    }\n\n    Theme(ThemeListener listener, ThemeData template, Type type, String name) {\n        super(template);\n        init(listener, type, name);\n    }\n\n    private void init(ThemeListener listener, Type type, String name) {\n        // This might seem like a roundabout way of doing things, but it's actually necessary.\n        // If we didn't explicitly call a defaultValuesListener method, proGuard would 'optimise'\n        // the instance out with catastrophic results (the listener would become a weak reference,\n        // be removed by the garbage collector, and all our carefully crafted event system would\n        // crumble).\n        // While Theme.addDefaultValuesListener(defaultValuesListener = new DefaultValuesListener(this));\n        // might seem like a more compact way of doing things, it wouldn't actually work.\n        defaultValuesListener = new DefaultValuesListener();\n        defaultValuesListener.setTheme(this);\n        ThemeData.addDefaultValuesListener(defaultValuesListener);\n\n        addThemeListener(listener);\n        setType(type);\n        if (name != null) {\n            setName(name);\n        }\n    }\n\n\t@Override\n\tpublic int hashCode() {\n\t\tfinal int prime = 31;\n\t\tint result = 1;\n\t\tresult = prime * result + ((name == null) ? 0 : name.hashCode());\n\t\tresult = prime * result + ((type == null) ? 0 : type.hashCode());\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object obj) {\n\t\tif (this == obj) {\n            return true;\n        }\n\t\tif (obj == null) {\n            return false;\n        }\n\t\tif (getClass() != obj.getClass()) {\n            return false;\n        }\n\t\tTheme other = (Theme) obj;\n\t\tif (name == null) {\n\t\t\tif (other.name != null) {\n                return false;\n            }\n\t\t} else if (!name.equals(other.name)) {\n            return false;\n        }\n        return type == other.type;\n    }\n\n\t// - Data retrieval ------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    /**\n\t * Checks whether this theme is modifiable.\n\t * <p>\n\t * A theme is modifiable if and only if it's not a predefined theme.\n\t *\n\t * @return <code>true</code> if the theme is modifiable, <code>false</code> otherwise.\n\t */\n\tpublic boolean canModify() {\n\t\treturn type != Type.PREDEFINED;\n\t}\n\n    /**\n     * Returns the theme's type.\n     * @return the theme's type.\n     */\n    public Type getType() {return type;}\n\n    /**\n     * Returns the theme's name.\n     * @return the theme's name.\n     */\n    public String getName() {\n        // Lazy loading for Launcher speedup\n        if (name == null && type == Type.USER) {\n            name = Translator.get(\"theme.custom_theme\");\n        }\n        return name;\n    }\n\n\n\n    // - Data modification ---------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    /**\n\t * Sets one of the theme's fonts.\n\t * <p>\n\t * Note that this method will only work if the theme is not a predifined one. Any other theme type will throw an\n\t * exception.\n\t * \n\t * @see ThemeManager#setCurrentFont(int,Font)\n\t * @param id\n\t *            identifier of the font to set.\n\t * @param font\n\t *            value for the specified font.\n\t * @throws IllegalStateException\n\t *             thrown if the theme is a predifined one.\n\t */\n    @Override\n    public boolean setFont(int id, Font font) {\n        // Makes sure we're not trying to modify a non-user theme.\n\t\tif (type == Type.PREDEFINED) {\n\t\t\tthrow new IllegalStateException(\"Trying to modify a predefined theme.\");\n        }\n\n        if (super.setFont(id, font)) {\n            // We're using getFont here to make sure that no event is propagated with a null value.\n            triggerFontEvent(new FontChangedEvent(this, id, getFont(id)));\n            return true;\n        }\n        return false;\n    }\n\n    /**\n\t * Sets one of the theme's colors.\n\t * <p>\n\t * Note that this method will not work if the theme is a predefined one. Any other theme type will throw an\n\t * exception.\n\t *\n\t * @see ThemeManager#setCurrentColor(int,Color)\n\t * @param id\n\t *            identifier of the color to set.\n\t * @param color\n\t *            value for the specified color.\n\t * @throws IllegalStateException\n\t *             thrown if the theme is a predefined one.\n\t */\n    @Override\n    public boolean setColor(int id, Color color) {\n        // Makes sure we're not trying to modify a non-user theme.\n\t\tif (type == Type.PREDEFINED) {\n\t\t\tthrow new IllegalStateException(\"Trying to modify a predefined theme.\");\n        }\n\n        if (super.setColor(id, color)) {\n            // We're using getColor here to make sure that no event is propagated with a null value.\n            triggerColorEvent(new ColorChangedEvent(this, id, getColor(id)));\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * Sets this theme's type.\n     * <p>\n     * If <code>type</code> is set to {@link Type#USER}, this method will also set the\n     * theme's name to the proper value taken from the dictionary.\n     *\n     * @param type theme's type.\n     */\n    void setType(Type type) {\n        checkType(type);\n\n        this.type = type;\n        if (type == Type.USER) {\n            setName(null);      // the name will be lazy loaded later after dictionaly loading\n        }\n    }\n\n    /**\n     * Sets this theme's name.\n     * @param name theme's name.\n     */\n    void setName(String name) {\n        this.name = name;\n    }\n\n\n\n    static void checkType(Type type) {\n        if (type != Type.USER && type != Type.PREDEFINED && type != Type.CUSTOM) {\n            throw new IllegalArgumentException(\"Illegal theme type: \" + type);\n        }\n    }\n\n    /**\n     * Returns the theme's name.\n     * @return the theme's name.\n     */\n    public String toString() {\n        return getName();\n    }\n\n    private static void addThemeListener(ThemeListener listener) {\n        listeners.put(listener, null);\n    }\n\n    private static void removeThemeListener(ThemeListener listener) {\n        listeners.remove(listener);\n    }\n\n    private static void triggerFontEvent(FontChangedEvent event) {\n        for (ThemeListener listener : listeners.keySet()) {\n            listener.fontChanged(event);\n        }\n    }\n\n    private static void triggerColorEvent(ColorChangedEvent event) {\n        for (ThemeListener listener : listeners.keySet()) {\n            listener.colorChanged(event);\n        }\n    }\n\n    private class DefaultValuesListener implements ThemeListener {\n        private Theme theme;\n\n        DefaultValuesListener() {}\n\n        public void setTheme(Theme theme) {this.theme = theme;}\n\n        public void colorChanged(ColorChangedEvent event) {\n            if (!theme.isColorSet(event.getColorId())) {\n                int colorId = event.getColorId();\n                Theme.triggerColorEvent(new ColorChangedEvent(theme, colorId, getColor(colorId)));\n            }\n        }\n\n        public void fontChanged(FontChangedEvent event) {\n            if (!theme.isFontSet(event.getFontId())) {\n                int fontId = event.getFontId();\n                Theme.triggerFontEvent(new FontChangedEvent(theme, fontId, getFont(fontId)));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/theme/ThemeCache.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.theme;\n\nimport com.mucommander.ui.main.table.FileGroupResolver;\n\nimport java.awt.*;\nimport java.util.WeakHashMap;\n\n\n/**\n * Contains cached colors and fonts for current theme.\n *\n * @author Mariusz Jakubowski\n */\npublic class ThemeCache implements ThemeListener {\n\n    public static final int NORMAL               = 0;\n    public static final int SELECTED             = 1;\n    public static final int ALTERNATE            = 2;\n    public static final int SECONDARY            = 3;\n\n    public static final int INACTIVE             = 0;\n    public static final int ACTIVE               = 1;\n\n    public static final int HIDDEN_FOLDER        = 0;\n    public static final int HIDDEN_FILE          = 1;\n    public static final int FOLDER               = 2;\n    public static final int ARCHIVE              = 3;\n    public static final int SYMLINK              = 4;\n    public static final int MARKED               = 5;\n    public static final int EXECUTABLE           = 6;\n    public static final int PLAIN_FILE           = 7;\n\n\n    public static Color[][][] foregroundColors;\n    public static Color[][]   backgroundColors;\n    public static Color[]     groupColors;\n    public static Color       unmatchedForeground;\n    public static Color       unmatchedBackground;\n    public static Color       activeOutlineColor;\n    public static Color       inactiveOutlineColor;\n\n\n    public static Font tableFont;\n    \n    /** Theme cache instance */\n    public static final ThemeCache instance = new ThemeCache();\n  \n    static {\n        foregroundColors = new Color[2][2][8];\n        backgroundColors = new Color[2][4];\n        groupColors = new Color[FileGroupResolver.MAX_GROUPS];\n\n        // Active background colors.\n        backgroundColors[ACTIVE][NORMAL]    = ThemeManager.getCurrentColor(Theme.FILE_TABLE_BACKGROUND_COLOR);\n        backgroundColors[ACTIVE][SELECTED]  = ThemeManager.getCurrentColor(Theme.FILE_TABLE_SELECTED_BACKGROUND_COLOR);\n        backgroundColors[ACTIVE][ALTERNATE] = ThemeManager.getCurrentColor(Theme.FILE_TABLE_ALTERNATE_BACKGROUND_COLOR);\n        backgroundColors[ACTIVE][SECONDARY] = ThemeManager.getCurrentColor(Theme.FILE_TABLE_SELECTED_SECONDARY_BACKGROUND_COLOR);\n\n        // Inactive background colors.\n        backgroundColors[INACTIVE][NORMAL]    = ThemeManager.getCurrentColor(Theme.FILE_TABLE_INACTIVE_BACKGROUND_COLOR);\n        backgroundColors[INACTIVE][SELECTED]  = ThemeManager.getCurrentColor(Theme.FILE_TABLE_INACTIVE_SELECTED_BACKGROUND_COLOR);\n        backgroundColors[INACTIVE][ALTERNATE] = ThemeManager.getCurrentColor(Theme.FILE_TABLE_INACTIVE_ALTERNATE_BACKGROUND_COLOR);\n        backgroundColors[INACTIVE][SECONDARY] = ThemeManager.getCurrentColor(Theme.FILE_TABLE_INACTIVE_SELECTED_SECONDARY_BACKGROUND_COLOR);\n\n        // Normal foreground foregroundColors.\n        foregroundColors[ACTIVE][NORMAL][HIDDEN_FOLDER]   = ThemeManager.getCurrentColor(Theme.HIDDEN_FOLDER_FOREGROUND_COLOR);\n        foregroundColors[ACTIVE][NORMAL][HIDDEN_FILE]     = ThemeManager.getCurrentColor(Theme.HIDDEN_FILE_FOREGROUND_COLOR);\n        foregroundColors[ACTIVE][NORMAL][FOLDER]          = ThemeManager.getCurrentColor(Theme.FOLDER_FOREGROUND_COLOR);\n        foregroundColors[ACTIVE][NORMAL][ARCHIVE]         = ThemeManager.getCurrentColor(Theme.ARCHIVE_FOREGROUND_COLOR);\n        foregroundColors[ACTIVE][NORMAL][SYMLINK]         = ThemeManager.getCurrentColor(Theme.SYMLINK_FOREGROUND_COLOR);\n        foregroundColors[ACTIVE][NORMAL][MARKED]          = ThemeManager.getCurrentColor(Theme.MARKED_FOREGROUND_COLOR);\n        foregroundColors[ACTIVE][NORMAL][EXECUTABLE]      = ThemeManager.getCurrentColor(Theme.EXECUTABLE_FOREGROUND_COLOR);\n        foregroundColors[ACTIVE][NORMAL][PLAIN_FILE]      = ThemeManager.getCurrentColor(Theme.FILE_FOREGROUND_COLOR);\n\n        // Normal unfocused foreground foregroundColors.\n        foregroundColors[INACTIVE][NORMAL][HIDDEN_FOLDER]  = ThemeManager.getCurrentColor(Theme.HIDDEN_FOLDER_INACTIVE_FOREGROUND_COLOR);\n        foregroundColors[INACTIVE][NORMAL][HIDDEN_FILE]    = ThemeManager.getCurrentColor(Theme.HIDDEN_FILE_INACTIVE_FOREGROUND_COLOR);\n        foregroundColors[INACTIVE][NORMAL][FOLDER]         = ThemeManager.getCurrentColor(Theme.FOLDER_INACTIVE_FOREGROUND_COLOR);\n        foregroundColors[INACTIVE][NORMAL][ARCHIVE]        = ThemeManager.getCurrentColor(Theme.ARCHIVE_INACTIVE_FOREGROUND_COLOR);\n        foregroundColors[INACTIVE][NORMAL][SYMLINK]        = ThemeManager.getCurrentColor(Theme.SYMLINK_INACTIVE_FOREGROUND_COLOR);\n        foregroundColors[INACTIVE][NORMAL][MARKED]         = ThemeManager.getCurrentColor(Theme.MARKED_INACTIVE_FOREGROUND_COLOR);\n        foregroundColors[INACTIVE][NORMAL][EXECUTABLE]     = ThemeManager.getCurrentColor(Theme.EXECUTABLE_INACTIVE_FOREGROUND_COLOR);\n        foregroundColors[INACTIVE][NORMAL][PLAIN_FILE]     = ThemeManager.getCurrentColor(Theme.FILE_INACTIVE_FOREGROUND_COLOR);\n\n        // Selected foreground foregroundColors.\n        foregroundColors[ACTIVE][SELECTED][HIDDEN_FOLDER] = ThemeManager.getCurrentColor(Theme.HIDDEN_FOLDER_SELECTED_FOREGROUND_COLOR);\n        foregroundColors[ACTIVE][SELECTED][HIDDEN_FILE]   = ThemeManager.getCurrentColor(Theme.HIDDEN_FILE_SELECTED_FOREGROUND_COLOR);\n        foregroundColors[ACTIVE][SELECTED][FOLDER]        = ThemeManager.getCurrentColor(Theme.FOLDER_SELECTED_FOREGROUND_COLOR);\n        foregroundColors[ACTIVE][SELECTED][ARCHIVE]       = ThemeManager.getCurrentColor(Theme.ARCHIVE_SELECTED_FOREGROUND_COLOR);\n        foregroundColors[ACTIVE][SELECTED][SYMLINK]       = ThemeManager.getCurrentColor(Theme.SYMLINK_SELECTED_FOREGROUND_COLOR);\n        foregroundColors[ACTIVE][SELECTED][MARKED]        = ThemeManager.getCurrentColor(Theme.MARKED_SELECTED_FOREGROUND_COLOR);\n        foregroundColors[ACTIVE][SELECTED][EXECUTABLE]    = ThemeManager.getCurrentColor(Theme.EXECUTABLE_SELECTED_FOREGROUND_COLOR);\n        foregroundColors[ACTIVE][SELECTED][PLAIN_FILE]    = ThemeManager.getCurrentColor(Theme.FILE_SELECTED_FOREGROUND_COLOR);\n\n        // Selected unfocused foreground foregroundColors.\n        foregroundColors[INACTIVE][SELECTED][HIDDEN_FOLDER]= ThemeManager.getCurrentColor(Theme.HIDDEN_FOLDER_INACTIVE_SELECTED_FOREGROUND_COLOR);\n        foregroundColors[INACTIVE][SELECTED][HIDDEN_FILE]  = ThemeManager.getCurrentColor(Theme.HIDDEN_FILE_INACTIVE_SELECTED_FOREGROUND_COLOR);\n        foregroundColors[INACTIVE][SELECTED][FOLDER]       = ThemeManager.getCurrentColor(Theme.FOLDER_INACTIVE_SELECTED_FOREGROUND_COLOR);\n        foregroundColors[INACTIVE][SELECTED][ARCHIVE]      = ThemeManager.getCurrentColor(Theme.ARCHIVE_INACTIVE_SELECTED_FOREGROUND_COLOR);\n        foregroundColors[INACTIVE][SELECTED][SYMLINK]      = ThemeManager.getCurrentColor(Theme.SYMLINK_INACTIVE_SELECTED_FOREGROUND_COLOR);\n        foregroundColors[INACTIVE][SELECTED][MARKED]       = ThemeManager.getCurrentColor(Theme.MARKED_INACTIVE_SELECTED_FOREGROUND_COLOR);\n        foregroundColors[INACTIVE][SELECTED][EXECUTABLE]   = ThemeManager.getCurrentColor(Theme.EXECUTABLE_INACTIVE_SELECTED_FOREGROUND_COLOR);\n        foregroundColors[INACTIVE][SELECTED][PLAIN_FILE]   = ThemeManager.getCurrentColor(Theme.FILE_INACTIVE_SELECTED_FOREGROUND_COLOR);\n\n        unmatchedForeground                                = ThemeManager.getCurrentColor(Theme.FILE_TABLE_UNMATCHED_FOREGROUND_COLOR);\n        unmatchedBackground                                = ThemeManager.getCurrentColor(Theme.FILE_TABLE_UNMATCHED_BACKGROUND_COLOR);\n        tableFont                                          = ThemeManager.getCurrentFont(Theme.FILE_TABLE_FONT);\n\n        activeOutlineColor                                 = ThemeManager.getCurrentColor(Theme.FILE_TABLE_SELECTED_OUTLINE_COLOR);\n        inactiveOutlineColor                               = ThemeManager.getCurrentColor(Theme.FILE_TABLE_INACTIVE_SELECTED_OUTLINE_COLOR);\n\n        for (int i = 0; i < 10; i++) {\n            groupColors[i] = ThemeManager.getCurrentColor(Theme.FILE_GROUP_1_FOREGROUND_COLOR + i);\n        }\n\n        ThemeManager.addCurrentThemeListener(instance);\n    }\n\n   \n    /** Listeners. */\n    private static final WeakHashMap<ThemeListener, ?> listeners = new WeakHashMap<>();\n    \n\n    private ThemeCache() {\n\t}\n    \n    public static void addThemeListener(ThemeListener listener) {\n        listeners.put(listener, null);\n    }\n\n    public static void removeThemeListener(ThemeListener listener) {\n        listeners.remove(listener);\n    }\n    \n    private static void fireColorChanged(ColorChangedEvent event) {\n        for (ThemeListener listener : listeners.keySet()) {\n            listener.colorChanged(event);\n        }\n    }\n    \n    private static void fireFontChanged(FontChangedEvent event) {\n        for (ThemeListener listener : listeners.keySet()) {\n            listener.fontChanged(event);\n        }\n    }\n    \n\n    /**\n     * Receives theme color changes notifications.\n     */\n    @Override\n    public void colorChanged(ColorChangedEvent event) {\n        int colorId = event.getColorId();\n        if (colorId >= Theme.FILE_GROUP_1_FOREGROUND_COLOR && colorId <= Theme.FILE_GROUP_10_FOREGROUND_COLOR) {\n            groupColors[colorId - Theme.FILE_GROUP_1_FOREGROUND_COLOR] = event.getColor();\n        } else {\n            switch(colorId) {\n                // Plain file color.\n            case Theme.FILE_FOREGROUND_COLOR:\n                foregroundColors[ACTIVE][NORMAL][PLAIN_FILE] = event.getColor();\n                break;\n\n                // Selected file color.\n            case Theme.FILE_SELECTED_FOREGROUND_COLOR:\n                foregroundColors[ACTIVE][SELECTED][PLAIN_FILE] = event.getColor();\n                break;\n\n            // Hidden folders.\n            case Theme.HIDDEN_FOLDER_FOREGROUND_COLOR:\n                foregroundColors[ACTIVE][NORMAL][HIDDEN_FOLDER] = event.getColor();\n                break;\n\n            // Selected hidden folders.\n            case Theme.HIDDEN_FOLDER_SELECTED_FOREGROUND_COLOR:\n                foregroundColors[ACTIVE][SELECTED][HIDDEN_FOLDER] = event.getColor();\n                break;\n\n                // Hidden files.\n            case Theme.HIDDEN_FILE_FOREGROUND_COLOR:\n                foregroundColors[ACTIVE][NORMAL][HIDDEN_FILE] = event.getColor();\n                break;\n\n                // Selected hidden files.\n            case Theme.HIDDEN_FILE_SELECTED_FOREGROUND_COLOR:\n                foregroundColors[ACTIVE][SELECTED][HIDDEN_FILE] = event.getColor();\n                break;\n\n                // Folders.\n            case Theme.FOLDER_FOREGROUND_COLOR:\n                foregroundColors[ACTIVE][NORMAL][FOLDER] = event.getColor();\n                break;\n\n                // Selected folders.\n            case Theme.FOLDER_SELECTED_FOREGROUND_COLOR:\n                foregroundColors[ACTIVE][SELECTED][FOLDER] = event.getColor();\n                break;\n\n                // Archives.\n            case Theme.ARCHIVE_FOREGROUND_COLOR:\n                foregroundColors[ACTIVE][NORMAL][ARCHIVE] = event.getColor();\n                break;\n\n                // Selected archives.\n            case Theme.ARCHIVE_SELECTED_FOREGROUND_COLOR:\n                foregroundColors[ACTIVE][SELECTED][ARCHIVE] = event.getColor();\n                break;\n\n                // Symlinks.\n            case Theme.SYMLINK_FOREGROUND_COLOR:\n                foregroundColors[ACTIVE][NORMAL][SYMLINK] = event.getColor();\n                break;\n\n                // Selected symlinks.\n            case Theme.SYMLINK_SELECTED_FOREGROUND_COLOR:\n                foregroundColors[ACTIVE][SELECTED][SYMLINK] = event.getColor();\n                break;\n\n                // Marked files.\n            case Theme.MARKED_FOREGROUND_COLOR:\n                foregroundColors[ACTIVE][NORMAL][MARKED] = event.getColor();\n                break;\n\n                // Selected marked files.\n            case Theme.MARKED_SELECTED_FOREGROUND_COLOR:\n                foregroundColors[ACTIVE][SELECTED][MARKED] = event.getColor();\n                break;\n\n                // Plain file color.\n            case Theme.FILE_INACTIVE_FOREGROUND_COLOR:\n                foregroundColors[INACTIVE][NORMAL][PLAIN_FILE] = event.getColor();\n                break;\n\n                // Selected file color.\n            case Theme.FILE_INACTIVE_SELECTED_FOREGROUND_COLOR:\n                foregroundColors[INACTIVE][SELECTED][PLAIN_FILE] = event.getColor();\n                break;\n\n                // Hidden files.\n            case Theme.HIDDEN_FILE_INACTIVE_FOREGROUND_COLOR:\n                foregroundColors[INACTIVE][NORMAL][HIDDEN_FILE] = event.getColor();\n                break;\n\n                // Selected hidden files.\n            case Theme.HIDDEN_FILE_INACTIVE_SELECTED_FOREGROUND_COLOR:\n                foregroundColors[INACTIVE][SELECTED][HIDDEN_FILE] = event.getColor();\n                break;\n\n                // Folders.\n            case Theme.FOLDER_INACTIVE_FOREGROUND_COLOR:\n                foregroundColors[INACTIVE][NORMAL][FOLDER] = event.getColor();\n                break;\n\n                // Selected folders.\n            case Theme.FOLDER_INACTIVE_SELECTED_FOREGROUND_COLOR:\n                foregroundColors[INACTIVE][SELECTED][FOLDER] = event.getColor();\n                break;\n\n                // Archives.\n            case Theme.ARCHIVE_INACTIVE_FOREGROUND_COLOR:\n                foregroundColors[INACTIVE][NORMAL][ARCHIVE] = event.getColor();\n                break;\n\n                // Selected archives.\n            case Theme.ARCHIVE_INACTIVE_SELECTED_FOREGROUND_COLOR:\n                foregroundColors[INACTIVE][SELECTED][ARCHIVE] = event.getColor();\n                break;\n\n                // Symlinks.\n            case Theme.SYMLINK_INACTIVE_FOREGROUND_COLOR:\n                foregroundColors[INACTIVE][NORMAL][SYMLINK] = event.getColor();\n                break;\n\n                // Selected symlinks.\n            case Theme.SYMLINK_INACTIVE_SELECTED_FOREGROUND_COLOR:\n                foregroundColors[INACTIVE][SELECTED][SYMLINK] = event.getColor();\n                break;\n\n                // Marked files.\n            case Theme.MARKED_INACTIVE_FOREGROUND_COLOR:\n                foregroundColors[INACTIVE][NORMAL][MARKED] = event.getColor();\n                break;\n\n                // Selected marked files.\n            case Theme.MARKED_INACTIVE_SELECTED_FOREGROUND_COLOR:\n                foregroundColors[INACTIVE][SELECTED][MARKED] = event.getColor();\n                break;\n\n                // Unmatched foreground\n            case Theme.FILE_TABLE_UNMATCHED_FOREGROUND_COLOR:\n                unmatchedForeground = event.getColor();\n                break;\n\n                // Unmached background\n            case Theme.FILE_TABLE_UNMATCHED_BACKGROUND_COLOR:\n                unmatchedBackground = event.getColor();\n                break;\n\n                // Active normal background.\n            case Theme.FILE_TABLE_BACKGROUND_COLOR:\n                backgroundColors[ACTIVE][NORMAL] = event.getColor();\n                break;\n\n                // Active selected background.\n            case Theme.FILE_TABLE_SELECTED_BACKGROUND_COLOR:\n                backgroundColors[ACTIVE][SELECTED] = event.getColor();\n                break;\n\n                // Active alternate background.\n            case Theme.FILE_TABLE_ALTERNATE_BACKGROUND_COLOR:\n                backgroundColors[ACTIVE][ALTERNATE] = event.getColor();\n                break;\n\n                // Inactive normal background.\n            case Theme.FILE_TABLE_INACTIVE_BACKGROUND_COLOR:\n                backgroundColors[INACTIVE][NORMAL] = event.getColor();\n                break;\n\n                // Inactive selected background.\n            case Theme.FILE_TABLE_INACTIVE_SELECTED_BACKGROUND_COLOR:\n                backgroundColors[INACTIVE][SELECTED] = event.getColor();\n                break;\n\n                // Inactive alternate background.\n            case Theme.FILE_TABLE_INACTIVE_ALTERNATE_BACKGROUND_COLOR:\n                backgroundColors[INACTIVE][ALTERNATE] = event.getColor();\n                break;\n\n                // Active selection outline.\n            case Theme.FILE_TABLE_SELECTED_OUTLINE_COLOR:\n                activeOutlineColor = event.getColor();\n                break;\n\n                // Inactive selection outline.\n            case Theme.FILE_TABLE_INACTIVE_SELECTED_OUTLINE_COLOR:\n                inactiveOutlineColor = event.getColor();\n                break;\n\n                // Secondary background color.\n            case Theme.FILE_TABLE_SELECTED_SECONDARY_BACKGROUND_COLOR:\n                backgroundColors[ACTIVE][SECONDARY] = event.getColor();\n                break;\n\n                // Inactive secondary background color.\n            case Theme.FILE_TABLE_INACTIVE_SELECTED_SECONDARY_BACKGROUND_COLOR:\n                backgroundColors[INACTIVE][SECONDARY] = event.getColor();\n                break;\n\n            default:\n                return;\n            }\n        }\n        fireColorChanged(event);\n    }\n\n    /**\n     * Receives theme font changes notifications.\n     */\n    public void fontChanged(FontChangedEvent event) {\n        if (event.getFontId() == Theme.FILE_TABLE_FONT) {\n            tableFont = event.getFont();\n        } else {\n            return;\n        }\n    \tfireFontChanged(event);\n    }\n\t\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/theme/ThemeData.java",
    "content": "/*\r\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\r\n * Copyright (C) 2013-2020 Oleg Trifonov\r\n *\r\n * trolCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * trolCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.theme;\r\n\r\nimport com.mucommander.RuntimeConstants;\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.ui.text.FontUtils;\r\nimport org.fife.ui.rtextarea.RTextArea;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.*;\r\nimport java.util.Hashtable;\r\nimport java.util.Map;\r\nimport java.util.WeakHashMap;\r\n\r\n/**\r\n * Base class for all things Theme.\r\n * <p>\r\n * The role of <code>ThemeData</code> is twofold:<br>\r\n * - theme data storage.<br>\r\n * - default values retrievals and notification.<br>\r\n *\r\n * <p>\r\n * In the current version, theme data is solely composed of assorted colors and fonts. <code>ThemeData</code>\r\n * offers methods to {@link #setColor(int,Color) set}, {@link #getColor(int) retrieve}, {@link #isIdentical(ThemeData,boolean) compare}\r\n * and {@link #cloneData() clone} these values.\r\n *\r\n * <p>\r\n * One of its major constraints is that it can <b>never</b> return <code>null</code> values for the items it contains. Whenever a specific\r\n * value hasn't been set, <code>ThemeData</code> will seemlessly provide the rest of the world with default values retrieved from the current\r\n * look&amp;feel.\r\n *\r\n * <p>\r\n * This default values system means that theme items can change outside of anybody's control: Swing UI properties can be updated, the current\r\n * look&amp;feel can be modified... <code>ThemeData</code> will track this changes and make sure that the proper event are dispatched\r\n * to listeners.\r\n *\r\n * <p>\r\n * In theory, classes that use the theme API should not need to worry about default value modifications. This is already managed internally, and\r\n * if the change affects any of the themes being listened on, the event will be propagated to them. There might special cases where it's necessary,\r\n * however, for which <code>ThemeData</code> provides a {@link #addDefaultValuesListener(ThemeListener) listening} mechanism.\r\n *\r\n * @see Theme\r\n * @see ThemeManager\r\n * @see javax.swing.UIManager\r\n * @author Nicolas Rinaudo\r\n */\r\npublic class ThemeData implements ThemeId {\r\n    // - Default fonts -------------------------------------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------------------------------------\r\n    // The following fields are look&feel dependant values for the fonts that are used by\r\n    // themes. We need to monitor them, as they are prone to change through UIManager.\r\n\r\n\r\n\r\n    // - Default identifiers -------------------------------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------------------------------------\r\n    private static final String DEFAULT_TEXT_AREA_FOREGROUND            = \"TextArea.foreground\";\r\n    private static final String DEFAULT_TEXT_AREA_BACKGROUND            = \"TextArea.background\";\r\n    private static final String DEFAULT_TEXT_AREA_SELECTION_FOREGROUND  = \"TextArea.selectionForeground\";\r\n    private static final String DEFAULT_TEXT_AREA_SELECTION_BACKGROUND  = \"TextArea.selectionBackground\";\r\n    private static final String DEFAULT_TEXT_AREA_CURRENT_BACKGROUND    = \"TextArea.currentBackground\";\r\n    private static final String DEFAULT_TEXT_FIELD_FOREGROUND           = \"TextField.foreground\";\r\n    private static final String DEFAULT_TEXT_FIELD_BACKGROUND           = \"TextField.background\";\r\n    private static final String DEFAULT_TEXT_FIELD_SELECTION_FOREGROUND = \"TextField.selectionForeground\";\r\n    private static final String DEFAULT_TEXT_FIELD_SELECTION_BACKGROUND = \"TextField.selectionBackground\";\r\n    private static final String DEFAULT_TEXT_FIELD_PROGRESS_BACKGROUND  = \"TextField.progress\";\r\n    private static final String DEFAULT_TABLE_FOREGROUND                = \"Table.foreground\";\r\n    private static final String DEFAULT_TABLE_BACKGROUND                = \"Table.background\";\r\n    private static final String DEFAULT_TABLE_SELECTION_FOREGROUND      = \"Table.selectionForeground\";\r\n    private static final String DEFAULT_TABLE_SELECTION_BACKGROUND      = \"Table.selectionBackground\";\r\n    private static final String DEFAULT_TABLE_UNMATCHED_FOREGROUND      = \"Table.unmatchedForeground\";\r\n    private static final String DEFAULT_TABLE_UNMATCHED_BACKGROUND      = \"Table.unmatchedBackground\";\r\n    private static final String DEFAULT_MENU_HEADER_FOREGROUND          = \"MenuHeader.foreground\";\r\n    private static final String DEFAULT_MENU_HEADER_BACKGROUND          = \"MenuHeader.background\";\r\n    private static final String DEFAULT_TEXT_AREA_FONT                  = \"TextArea.font\";\r\n    private static final String DEFAULT_TEXT_FIELD_FONT                 = \"TextField.font\";\r\n    private static final String DEFAULT_LABEL_FONT                      = \"Label.font\";\r\n    private static final String DEFAULT_TABLE_FONT                      = \"Table.font\";\r\n    private static final String DEFAULT_MENU_HEADER_FONT                = \"MenuHeader.font\";\r\n    private static final String DEFAULT_HEX_VIEWER_FONT                 = \"HexViewer.font\";\r\n\r\n\r\n    // - Listeners -----------------------------------------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------------------------------------\r\n    /** Listeners on the default font and colors. */\r\n    private static final WeakHashMap<ThemeListener, ?> listeners = new WeakHashMap<>();\r\n\r\n\r\n\r\n    // - Registered colors & fonts -------------------------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------------------------------------\r\n    /** All registered colors. */\r\n    private static final Map<Integer, DefaultColor> COLORS = new Hashtable<>();\r\n    /** All registered default colors. */\r\n    private static final Map<String, DefaultColor>  DEFAULT_COLORS = new Hashtable<>();\r\n    /** All registered fonts. */\r\n    private static final Map<Integer, DefaultFont>  FONTS = new Hashtable<>();\r\n    /** All registered default fonts. */\r\n    private static final Map<String, DefaultFont>   DEFAULT_FONTS = new Hashtable<>();\r\n\r\n\r\n\r\n    // - Instance variables --------------------------------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------------------------------------\r\n    /** All the colors contained by the theme. */\r\n    private Color[] colors;\r\n    /** All the fonts contained by the theme. */\r\n    private Font[]  fonts;\r\n\r\n\r\n    private static void registerDefaultColor(String name, DefaultColor color) {\r\n        DEFAULT_COLORS.put(name, color);\r\n    }\r\n\r\n    private static void registerDefaultFont(String name, DefaultFont font) {\r\n        DEFAULT_FONTS.put(name, font);\r\n    }\r\n\r\n    private static void registerColor(int id, String defaultColor) {\r\n        DefaultColor color = DEFAULT_COLORS.get(defaultColor);\r\n        if (color == null) {\r\n            throw new IllegalArgumentException(\"Not a registered default color: \" + defaultColor);\r\n        }\r\n        registerColor(id, color);\r\n    }\r\n\r\n    private static void registerFont(int id, String defaultFont) {\r\n        DefaultFont font = DEFAULT_FONTS.get(defaultFont);\r\n\r\n        if (font == null) {\r\n            throw new IllegalArgumentException(\"Not a registered default font: \" + defaultFont);\r\n        }\r\n        registerFont(id, font);\r\n    }\r\n\r\n    private static void registerColor(int id, Color color) {\r\n        registerColor(id, new FixedDefaultColor(color));\r\n    }\r\n\r\n    private static void registerFont(int id, Font font) {\r\n        registerFont(id, new FixedDefaultFont(font));\r\n    }\r\n\r\n    private static void registerColor(int id, int defaultId) {\r\n        registerColor(id, new LinkedDefaultColor(defaultId));\r\n    }\r\n\r\n    public static void registerFont(int id, int defaultId) {\r\n        registerFont(id, new LinkedDefaultFont(defaultId));\r\n    }\r\n\r\n    private static void registerColor(int id, DefaultColor color) {\r\n        Integer colorId = id;\r\n        COLORS.put(colorId, color);\r\n        color.link(colorId);\r\n    }\r\n\r\n    private static void registerFont(int id, DefaultFont font) {\r\n        Integer fontId = id;\r\n        FONTS.put(fontId, font);\r\n        font.link(fontId);\r\n    }\r\n\r\n\r\n    static {\r\n        FontUtils.setup();\r\n        // - Default values registering --------------------------------------------------------------------------------\r\n        // -------------------------------------------------------------------------------------------------------------\r\n        ComponentMapper mapper = new ComponentMapper() {\r\n            @Override\r\n            public JComponent getComponent() {\r\n                return new RTextArea();\r\n            }\r\n        };\r\n\r\n        registerDefaultFont(DEFAULT_TEXT_AREA_FONT, new SystemDefaultFont(\"TextArea.font\", mapper) {\r\n            @Override\r\n            public Font getFont(ThemeData data) {\r\n                Font font = super.getFont(data);\r\n                switch (OsFamily.getCurrent()) {\r\n                    case MAC_OS_X:\r\n                        return new Font(\"Menlo\", font.getStyle(), font.getSize());\r\n                    case LINUX:\r\n                        if (RuntimeConstants.DISPLAY_4K) {\r\n                            return new Font(font.getName(), font.getStyle(), 32);\r\n                        }\r\n                }\r\n                return font;\r\n            }\r\n        });\r\n        registerDefaultFont(DEFAULT_HEX_VIEWER_FONT, new SystemDefaultFont(\"Table.font\", mapper) {\r\n            @Override\r\n            public Font getFont(ThemeData data) {\r\n                return createDefaultHexViewerFont();\r\n            }\r\n        });\r\n        registerDefaultColor(DEFAULT_TEXT_AREA_FOREGROUND, new SystemDefaultColor(SystemDefaultColor.FOREGROUND, \"TextArea.foreground\", mapper));\r\n        registerDefaultColor(DEFAULT_TEXT_AREA_BACKGROUND, new SystemDefaultColor(SystemDefaultColor.BACKGROUND, \"TextArea.background\", mapper));\r\n        registerDefaultColor(DEFAULT_TEXT_AREA_SELECTION_FOREGROUND, new SystemDefaultColor(SystemDefaultColor.SELECTION_FOREGROUND, \"TextArea.selectionForeground\", mapper));\r\n        registerDefaultColor(DEFAULT_TEXT_AREA_SELECTION_BACKGROUND, new SystemDefaultColor(SystemDefaultColor.SELECTION_BACKGROUND, \"TextArea.selectionBackground\", mapper));\r\n        registerDefaultColor(DEFAULT_TEXT_AREA_CURRENT_BACKGROUND, new SystemDefaultColor(SystemDefaultColor.CURRENT_LINE_BACKGROUND, \"TextArea.currentBackground\", mapper));\r\n\r\n\r\n        // Register TextField related default values.\r\n        mapper = new ComponentMapper() {\r\n            @Override\r\n            public JComponent getComponent() {\r\n                return new JTextField();\r\n            }\r\n        };\r\n\r\n        registerDefaultFont(DEFAULT_TEXT_FIELD_FONT, new SystemDefaultFont(\"TextField.font\", mapper));\r\n        registerDefaultColor(DEFAULT_TEXT_FIELD_FOREGROUND, new SystemDefaultColor(SystemDefaultColor.FOREGROUND, \"TextField.foreground\", mapper));\r\n        registerDefaultColor(DEFAULT_TEXT_FIELD_BACKGROUND, new SystemDefaultColor(SystemDefaultColor.BACKGROUND, \"TextField.background\", mapper));\r\n        registerDefaultColor(DEFAULT_TEXT_FIELD_SELECTION_FOREGROUND, new SystemDefaultColor(SystemDefaultColor.SELECTION_FOREGROUND, \"TextField.selectionForeground\", mapper));\r\n        registerDefaultColor(DEFAULT_TEXT_FIELD_SELECTION_BACKGROUND, new SystemDefaultColor(SystemDefaultColor.SELECTION_BACKGROUND, \"TextField.selectionBackground\", mapper));\r\n        registerDefaultColor(DEFAULT_TEXT_FIELD_PROGRESS_BACKGROUND, new SystemDefaultColor(SystemDefaultColor.SELECTION_BACKGROUND, \"TextField.selectionBackground\", mapper) {\r\n            @Override\r\n            public Color getColor(ThemeData data) {\r\n                Color color = super.getColor(data);\r\n                return new Color(color.getRed(), color.getGreen(), color.getBlue(), 64);\r\n            }\r\n        });\r\n\r\n        // Register Table related default values.\r\n        mapper = new ComponentMapper() {\r\n            @Override\r\n            public JComponent getComponent() {\r\n                return new JTable();\r\n            }\r\n        };\r\n\r\n        registerDefaultFont(DEFAULT_TABLE_FONT, new SystemDefaultFont(\"Table.font\", mapper));\r\n        registerDefaultColor(DEFAULT_TABLE_FOREGROUND, new SystemDefaultColor(SystemDefaultColor.FOREGROUND, \"Table.foreground\", mapper));\r\n        registerDefaultColor(DEFAULT_TABLE_BACKGROUND, new SystemDefaultColor(SystemDefaultColor.BACKGROUND, \"Table.background\", mapper));\r\n        registerDefaultColor(DEFAULT_TABLE_SELECTION_FOREGROUND, new SystemDefaultColor(SystemDefaultColor.SELECTION_FOREGROUND, \"Table.selectionForeground\", mapper));\r\n        registerDefaultColor(DEFAULT_TABLE_SELECTION_BACKGROUND, new SystemDefaultColor(SystemDefaultColor.SELECTION_BACKGROUND, \"Table.selectionBackground\", mapper));\r\n        registerDefaultColor(DEFAULT_TABLE_UNMATCHED_FOREGROUND, new SystemDefaultColor(SystemDefaultColor.FOREGROUND, \"Table.foreground\", mapper) {\r\n            @Override\r\n            public Color getColor(ThemeData data) {\r\n                                     return super.getColor(data).darker();\r\n                                 }\r\n        });\r\n        registerDefaultColor(DEFAULT_TABLE_UNMATCHED_BACKGROUND, new SystemDefaultColor(SystemDefaultColor.BACKGROUND, \"Table.background\", mapper) {\r\n            @Override\r\n            public Color getColor(ThemeData data) {\r\n                                     return super.getColor(data).darker();\r\n                                 }\r\n        });\r\n\r\n        // Menu header related default values.\r\n        mapper = new ComponentMapper() {\r\n            @Override\r\n            public JComponent getComponent() {\r\n                return new JInternalFrame();\r\n            }\r\n        };\r\n        registerDefaultFont(DEFAULT_MENU_HEADER_FONT, new SystemDefaultFont(\"InternalFrame.font\", mapper));\r\n        registerDefaultColor(DEFAULT_MENU_HEADER_BACKGROUND, new SystemDefaultColor(SystemDefaultColor.BACKGROUND, \"InternalFrame.activeTitleBackground\", mapper));\r\n        registerDefaultColor(DEFAULT_MENU_HEADER_FOREGROUND, new SystemDefaultColor(SystemDefaultColor.FOREGROUND, \"InternalFrame.activeTitleForeground\", mapper));\r\n\r\n        // Label related default values.\r\n        mapper = new ComponentMapper() {\r\n            @Override\r\n            public JComponent getComponent() {\r\n                return new JLabel();\r\n            }\r\n        };\r\n        registerDefaultFont(DEFAULT_LABEL_FONT, new SystemDefaultFont(\"Label.font\", mapper));\r\n\r\n        \r\n\r\n        // - Default values linking ------------------------------------------------------------------------------------\r\n        // -------------------------------------------------------------------------------------------------------------\r\n        // QuickList default values.\r\n        registerFont(QUICK_LIST_ITEM_FONT,                          DEFAULT_TABLE_FONT);\r\n        registerFont(QUICK_LIST_HEADER_FONT,                        DEFAULT_MENU_HEADER_FONT);\r\n        registerColor(QUICK_LIST_HEADER_SECONDARY_BACKGROUND_COLOR, DEFAULT_MENU_HEADER_BACKGROUND);\r\n        registerColor(QUICK_LIST_HEADER_BACKGROUND_COLOR,           DEFAULT_MENU_HEADER_BACKGROUND);\r\n        registerColor(QUICK_LIST_HEADER_FOREGROUND_COLOR,           DEFAULT_MENU_HEADER_FOREGROUND);\r\n        registerColor(QUICK_LIST_ITEM_BACKGROUND_COLOR,             FILE_TABLE_BACKGROUND_COLOR);\r\n        registerColor(QUICK_LIST_ITEM_FOREGROUND_COLOR,             FILE_FOREGROUND_COLOR);\r\n        registerColor(QUICK_LIST_SELECTED_ITEM_BACKGROUND_COLOR,    FILE_TABLE_SELECTED_BACKGROUND_COLOR);\r\n        registerColor(QUICK_LIST_SELECTED_ITEM_FOREGROUND_COLOR,    FILE_SELECTED_FOREGROUND_COLOR);\r\n\r\n        // File default values.\r\n        registerColor(HIDDEN_FOLDER_FOREGROUND_COLOR,                 Color.GRAY);\r\n        registerColor(HIDDEN_FILE_FOREGROUND_COLOR,                   Color.GRAY);\r\n        registerColor(FOLDER_FOREGROUND_COLOR,                        DEFAULT_TABLE_FOREGROUND);\r\n        registerColor(ARCHIVE_FOREGROUND_COLOR,                       DEFAULT_TABLE_FOREGROUND);\r\n        registerColor(SYMLINK_FOREGROUND_COLOR,                       DEFAULT_TABLE_FOREGROUND);\r\n        registerColor(FILE_INACTIVE_FOREGROUND_COLOR,                 DEFAULT_TABLE_FOREGROUND);\r\n        registerColor(HIDDEN_FOLDER_INACTIVE_FOREGROUND_COLOR,        Color.GRAY);\r\n        registerColor(HIDDEN_FILE_INACTIVE_FOREGROUND_COLOR,          Color.GRAY);\r\n        registerColor(FOLDER_INACTIVE_FOREGROUND_COLOR,               DEFAULT_TABLE_FOREGROUND);\r\n        registerColor(ARCHIVE_INACTIVE_FOREGROUND_COLOR,              DEFAULT_TABLE_FOREGROUND);\r\n        registerColor(SYMLINK_INACTIVE_FOREGROUND_COLOR,              DEFAULT_TABLE_FOREGROUND);\r\n        registerColor(FILE_FOREGROUND_COLOR,                          DEFAULT_TABLE_FOREGROUND);\r\n        registerColor(HIDDEN_FOLDER_SELECTED_FOREGROUND_COLOR,        DEFAULT_TABLE_SELECTION_FOREGROUND);\r\n        registerColor(HIDDEN_FILE_SELECTED_FOREGROUND_COLOR,          DEFAULT_TABLE_SELECTION_FOREGROUND);\r\n        registerColor(FOLDER_SELECTED_FOREGROUND_COLOR,               DEFAULT_TABLE_SELECTION_FOREGROUND);\r\n        registerColor(ARCHIVE_SELECTED_FOREGROUND_COLOR,              DEFAULT_TABLE_SELECTION_FOREGROUND);\r\n        registerColor(SYMLINK_SELECTED_FOREGROUND_COLOR,              DEFAULT_TABLE_SELECTION_FOREGROUND);\r\n        registerColor(HIDDEN_FOLDER_INACTIVE_SELECTED_FOREGROUND_COLOR, DEFAULT_TABLE_SELECTION_FOREGROUND);\r\n        registerColor(HIDDEN_FILE_INACTIVE_SELECTED_FOREGROUND_COLOR, DEFAULT_TABLE_SELECTION_FOREGROUND);\r\n        registerColor(FOLDER_INACTIVE_SELECTED_FOREGROUND_COLOR,      DEFAULT_TABLE_SELECTION_FOREGROUND);\r\n        registerColor(ARCHIVE_INACTIVE_SELECTED_FOREGROUND_COLOR,     DEFAULT_TABLE_SELECTION_FOREGROUND);\r\n        registerColor(SYMLINK_INACTIVE_SELECTED_FOREGROUND_COLOR,     DEFAULT_TABLE_SELECTION_FOREGROUND);\r\n        registerColor(FILE_INACTIVE_SELECTED_FOREGROUND_COLOR,        DEFAULT_TABLE_SELECTION_FOREGROUND);\r\n        registerColor(FILE_SELECTED_FOREGROUND_COLOR,                 DEFAULT_TABLE_SELECTION_FOREGROUND);\r\n\r\n        // FileTable default values.\r\n        registerFont(FILE_TABLE_FONT,                                          DEFAULT_TABLE_FONT);\r\n        registerColor(FILE_TABLE_BACKGROUND_COLOR,                             DEFAULT_TABLE_BACKGROUND);\r\n        registerColor(FILE_TABLE_INACTIVE_BACKGROUND_COLOR,                    DEFAULT_TABLE_BACKGROUND);\r\n        registerColor(FILE_TABLE_ALTERNATE_BACKGROUND_COLOR,                   DEFAULT_TABLE_BACKGROUND);\r\n        registerColor(FILE_TABLE_INACTIVE_ALTERNATE_BACKGROUND_COLOR,          DEFAULT_TABLE_BACKGROUND);\r\n        registerColor(FILE_TABLE_SELECTED_BACKGROUND_COLOR,                    DEFAULT_TABLE_SELECTION_BACKGROUND);\r\n        registerColor(FILE_TABLE_INACTIVE_SELECTED_BACKGROUND_COLOR,           DEFAULT_TABLE_SELECTION_BACKGROUND);\r\n        registerColor(FILE_TABLE_UNMATCHED_FOREGROUND_COLOR,                   DEFAULT_TABLE_UNMATCHED_FOREGROUND);\r\n        registerColor(FILE_TABLE_UNMATCHED_BACKGROUND_COLOR,                   DEFAULT_TABLE_UNMATCHED_BACKGROUND);\r\n        registerColor(STATUS_BAR_BACKGROUND_COLOR,                             new Color(0xD5D5D5));\r\n        registerColor(MARKED_FOREGROUND_COLOR,                                 Color.RED);\r\n        registerColor(MARKED_INACTIVE_FOREGROUND_COLOR,                        Color.RED);\r\n        registerColor(MARKED_SELECTED_FOREGROUND_COLOR,                        Color.RED);\r\n        registerColor(MARKED_INACTIVE_SELECTED_FOREGROUND_COLOR,               Color.RED);\r\n\r\n        registerColor(EXECUTABLE_FOREGROUND_COLOR,                             Color.GREEN);\r\n        registerColor(EXECUTABLE_INACTIVE_FOREGROUND_COLOR,                    Color.GREEN);\r\n        registerColor(EXECUTABLE_SELECTED_FOREGROUND_COLOR,                    Color.GREEN);\r\n        registerColor(EXECUTABLE_INACTIVE_SELECTED_FOREGROUND_COLOR,           Color.GREEN);\r\n\r\n        registerColor(FILE_TABLE_BORDER_COLOR,                                 Color.GRAY);\r\n        registerColor(FILE_TABLE_INACTIVE_BORDER_COLOR,                        Color.GRAY);\r\n        registerColor(FILE_TABLE_SELECTED_SECONDARY_BACKGROUND_COLOR,          FILE_TABLE_SELECTED_BACKGROUND_COLOR);\r\n        registerColor(FILE_TABLE_SELECTED_OUTLINE_COLOR,                       FILE_TABLE_SELECTED_BACKGROUND_COLOR);\r\n        registerColor(FILE_TABLE_INACTIVE_SELECTED_SECONDARY_BACKGROUND_COLOR, FILE_TABLE_INACTIVE_SELECTED_BACKGROUND_COLOR);\r\n        registerColor(FILE_TABLE_INACTIVE_SELECTED_OUTLINE_COLOR,              FILE_TABLE_INACTIVE_SELECTED_BACKGROUND_COLOR);\r\n\r\n        // File groups colors\r\n        for (int i = FILE_GROUP_1_FOREGROUND_COLOR; i<= FILE_GROUP_10_FOREGROUND_COLOR; i++) {\r\n            registerColor(i, DEFAULT_TABLE_FOREGROUND);\r\n        }\r\n        // Shell default values.\r\n        registerFont(SHELL_FONT,                               DEFAULT_TEXT_AREA_FONT);\r\n        registerFont(SHELL_HISTORY_FONT,                       DEFAULT_TEXT_FIELD_FONT);\r\n        registerColor(SHELL_FOREGROUND_COLOR,                  DEFAULT_TEXT_AREA_FOREGROUND);\r\n        registerColor(SHELL_BACKGROUND_COLOR,                  DEFAULT_TEXT_AREA_BACKGROUND);\r\n        registerColor(SHELL_SELECTED_FOREGROUND_COLOR,         DEFAULT_TEXT_AREA_SELECTION_FOREGROUND);\r\n        registerColor(SHELL_SELECTED_BACKGROUND_COLOR,         DEFAULT_TEXT_AREA_SELECTION_BACKGROUND);\r\n        registerColor(SHELL_HISTORY_FOREGROUND_COLOR,          DEFAULT_TEXT_FIELD_FOREGROUND);\r\n        registerColor(SHELL_HISTORY_BACKGROUND_COLOR,          DEFAULT_TEXT_FIELD_BACKGROUND);\r\n        registerColor(SHELL_HISTORY_SELECTED_FOREGROUND_COLOR, DEFAULT_TEXT_FIELD_SELECTION_FOREGROUND);\r\n        registerColor(SHELL_HISTORY_SELECTED_BACKGROUND_COLOR, DEFAULT_TEXT_FIELD_SELECTION_BACKGROUND);\r\n\r\n        // Terminal\r\n        registerFont(TERMINAL_FONT,                           createDefaultTerminalFont());\r\n        registerColor(TERMINAL_BACKGROUND_COLOR,              Color.BLACK);\r\n        registerColor(TERMINAL_FOREGROUND_COLOR,              Color.GREEN);\r\n        registerColor(TERMINAL_SELECTED_BACKGROUND_COLOR,     new Color(0x6666ff));\r\n        registerColor(TERMINAL_SELECTED_FOREGROUND_COLOR,     Color.WHITE);\r\n\r\n        // Editor default values.\r\n        registerFont(EDITOR_FONT,                       DEFAULT_TEXT_AREA_FONT);\r\n        registerColor(EDITOR_FOREGROUND_COLOR,          DEFAULT_TEXT_AREA_FOREGROUND);\r\n        registerColor(EDITOR_BACKGROUND_COLOR,          DEFAULT_TEXT_AREA_BACKGROUND);\r\n        registerColor(EDITOR_SELECTED_FOREGROUND_COLOR, DEFAULT_TEXT_AREA_SELECTION_FOREGROUND);\r\n        registerColor(EDITOR_SELECTED_BACKGROUND_COLOR, DEFAULT_TEXT_AREA_SELECTION_BACKGROUND);\r\n        registerColor(EDITOR_CURRENT_BACKGROUND_COLOR,  DEFAULT_TEXT_AREA_CURRENT_BACKGROUND);\r\n\r\n        // Location default values.\r\n        registerFont(LOCATION_BAR_FONT,                       DEFAULT_TEXT_FIELD_FONT);\r\n        registerColor(LOCATION_BAR_FOREGROUND_COLOR,          DEFAULT_TEXT_FIELD_FOREGROUND);\r\n        registerColor(LOCATION_BAR_BACKGROUND_COLOR,          DEFAULT_TEXT_FIELD_BACKGROUND);\r\n        registerColor(LOCATION_BAR_SELECTED_FOREGROUND_COLOR, DEFAULT_TEXT_FIELD_SELECTION_FOREGROUND);\r\n        registerColor(LOCATION_BAR_SELECTED_BACKGROUND_COLOR, DEFAULT_TEXT_FIELD_SELECTION_BACKGROUND);\r\n        registerColor(LOCATION_BAR_PROGRESS_COLOR,            DEFAULT_TEXT_FIELD_PROGRESS_BACKGROUND);\r\n\r\n        // Status bar default values.\r\n        registerFont(STATUS_BAR_FONT,              DEFAULT_LABEL_FONT);\r\n        registerColor(STATUS_BAR_FOREGROUND_COLOR, DEFAULT_TEXT_FIELD_FOREGROUND);\r\n        registerColor(STATUS_BAR_CRITICAL_COLOR,   Color.RED);\r\n        registerColor(STATUS_BAR_BORDER_COLOR,     Color.GRAY);\r\n        registerColor(STATUS_BAR_BACKGROUND_COLOR, new Color(0xD5D5D5));\r\n        registerColor(STATUS_BAR_OK_COLOR,         new Color(0x70EC2B));\r\n        registerColor(STATUS_BAR_WARNING_COLOR,    new Color(0xFF7F00));\r\n\r\n\r\n        // Hex viewer default values\r\n        registerFont(HEX_VIEWER_FONT, DEFAULT_HEX_VIEWER_FONT);\r\n        registerColor(HEX_VIEWER_HEX_FOREGROUND_COLOR, DEFAULT_TEXT_FIELD_FOREGROUND);\r\n        registerColor(HEX_VIEWER_BACKGROUND_COLOR, DEFAULT_TEXT_FIELD_BACKGROUND);\r\n        registerColor(HEX_VIEWER_ALTERNATE_BACKGROUND_COLOR, DEFAULT_TEXT_FIELD_BACKGROUND);\r\n        registerColor(HEX_VIEWER_ASCII_FOREGROUND_COLOR, DEFAULT_TEXT_FIELD_FOREGROUND);\r\n        registerColor(HEX_VIEWER_OFFSET_FOREGROUND_COLOR, DEFAULT_TEXT_FIELD_FOREGROUND);\r\n        registerColor(HEX_VIEWER_SELECTED_DUMP_FOREGROUND_COLOR, DEFAULT_TEXT_FIELD_FOREGROUND);\r\n        registerColor(HEX_VIEWER_SELECTED_BACKGROUND_COLOR, new Color(0x0000ff));\r\n        registerColor(HEX_VIEWER_SELECTED_ASCII_BACKGROUND_COLOR, DEFAULT_TEXT_FIELD_FOREGROUND);\r\n    }\r\n\r\n\r\n    /**\r\n     * Creates an empty set of theme data.\r\n     * <p>\r\n     * <code>ThemeData</code> instances created that way will return default values for every\r\n     * single one of their items.\r\n     *\r\n     * @see #cloneData()\r\n     */\r\n    public ThemeData() {\r\n        colors = new Color[COLOR_COUNT];\r\n        fonts  = new Font[FONT_COUNT];\r\n    }\r\n\r\n    /**\r\n     * Creates a new set of theme data.\r\n     * <p>\r\n     * The content of <code>from</code> will be copied in the new theme data. Note that\r\n     * since we're copying the arrays themselves, rather than creating new ones and copying\r\n     * each color and font individually, <code>from</code> will be unreliable at the end of this\r\n     * call.\r\n     *\r\n     * <p>\r\n     * This constructor is only meant for optimisation purposes. When transforming\r\n     * theme data in a proper theme, using this constructor allows us to not duplicate\r\n     * all the fonts and color. It's a risky constructor to use, however, and should not be exposed\r\n     * outside of the scope of the package.\r\n     *\r\n     * @param from theme data from which to import values.\r\n     */\r\n    ThemeData(ThemeData from) {\r\n        this();\r\n        fonts  = from.fonts;\r\n        colors = from.colors;\r\n    }\r\n\r\n\r\n\r\n    // - Data import / export ------------------------------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------------------------------------\r\n    /**\r\n     * Clones the current theme data.\r\n     * <p>\r\n     * This method allows callers to decide whether they want to <i>freeze</i> default values or\r\n     * not. Freezing a value means that it will be considered to have been set to the default value,\r\n     * and will not be updated when this default value changes.\r\n     *\r\n     * @param  freezeDefaults whether to freeze the data's default values.\r\n     * @return                a clone of the current theme data.\r\n     * @see                   #cloneData()\r\n     */\r\n    private ThemeData cloneData(boolean freezeDefaults) {\r\n        ThemeData data = new ThemeData();\r\n\r\n        // Clones the theme's colors.\r\n        for (int i = 0; i < COLOR_COUNT; i++) {\r\n            data.colors[i] = freezeDefaults ? getColor(i) : colors[i];\r\n        }\r\n\r\n        // Clones the theme's fonts.\r\n        for (int i = 0; i < FONT_COUNT; i++) {\r\n            data.fonts[i] = freezeDefaults ? getFont(i) : fonts[i];\r\n        }\r\n\r\n        return data;\r\n    }\r\n\r\n    /**\r\n     * Clones the theme data without freezing default values.\r\n     * <p>\r\n     * This is a convenience method, and is exactly equivalent to calling <code>{@link #cloneData(boolean) cloneData(false)}</code>.\r\n     *\r\n     * @return a clone of the current theme data.\r\n     */\r\n    public ThemeData cloneData() {return cloneData(false);}\r\n\r\n    /**\r\n     * Imports the specified data in the current one.\r\n     * <p>\r\n     * This method can be dangerous in that it overwrites every single value\r\n     * of the current data without hope of retrieval. Moreoever, if something were to\r\n     * go wrong during the operation and an exception was raised, the current data would\r\n     * find itself in an invalid state, where some of its values would have been updated but\r\n     * not all of them. It is up to callers to deal with these issues.\r\n     *\r\n     * <p>\r\n     * Values overwriting is done through the use of the current instance's {@link #setColor(int,Color)}\r\n     * and {@link #setFont(int,Font)} methods. This allows subclasses to plug their own code here. A good\r\n     * example of that is {@link Theme}, which will automatically trigger font and color events when\r\n     * importing data.\r\n     *\r\n     * @param data data to import.\r\n     */\r\n    public void importData(ThemeData data) {\r\n        // Imports the theme's colors.\r\n        for (int i = 0; i < COLOR_COUNT; i++) {\r\n            setColor(i, data.colors[i]);\r\n        }\r\n\r\n        // Imports the theme's fonts.\r\n        for (int i = 0; i < FONT_COUNT; i++) {\r\n            setFont(i, data.fonts[i]);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Sets the specified color to the specified value.\r\n     * <p>\r\n     * Use a value of <code>null</code> to restore the color to it's default value.\r\n     *\r\n     * <p>\r\n     * This method will return <code>false</code> if it didn't actually change the theme data.\r\n     * This is checked through the use of <code>{@link #isColorDifferent(int,Color) isColorDifferent(}id,color)</code>.\r\n     *\r\n     * <p>\r\n     * Note that even if the color is found to be identical, the previous value will be overwritten -\r\n     * this is a design choice, meant for these cases where developers need to work with home-made\r\n     * subclasses of <code>Color</code>.\r\n     *\r\n     * @param  id    identifier of the color to set.\r\n     * @param  color value to which the color should be set.\r\n     * @return       <code>true</code> if the call actually changed the data, <code>false</code> otherwise.\r\n     */\r\n    public synchronized boolean setColor(int id, Color color) {\r\n        boolean buffer = isColorDifferent(id, color);   // Used to store the result of isColorDifferent.\r\n        colors[id] = color;\r\n        if (id >= FILE_GROUP_1_FOREGROUND_COLOR && id <= FILE_GROUP_10_FOREGROUND_COLOR) {\r\n            triggerColorEvent(id, color);\r\n        } else {\r\n            switch (id) {\r\n                case FILE_TABLE_SELECTED_SECONDARY_BACKGROUND_COLOR:\r\n                case FILE_TABLE_SELECTED_OUTLINE_COLOR:\r\n                case FILE_TABLE_INACTIVE_SELECTED_SECONDARY_BACKGROUND_COLOR:\r\n                case FILE_TABLE_INACTIVE_SELECTED_OUTLINE_COLOR:\r\n                    triggerColorEvent(id, color);\r\n                }\r\n        }\r\n        return buffer;\r\n    }\r\n\r\n    void setColorFast(int id, Color color) {\r\n        colors[id] = color;\r\n    }\r\n\r\n    /**\r\n     * Sets the specified font to the specified value.\r\n     * <p>\r\n     * Use a value of <code>null</code> to restore the font to it's default value.\r\n     *\r\n     * <p>\r\n     * This method will return <code>false</code> if it didn't actually change the theme data.\r\n     * This is checked through the use of <code>{@link #isFontDifferent(int,Font) isFontDifferent(}id, font)</code>.\r\n     *\r\n     * <p>\r\n     * Note that even if the font is found to be identical, the previous value will be overwritten -\r\n     * this is a design choice, meant for these cases where developers need to work with home-made\r\n     * subclasses of <code>Font</code>.\r\n     *\r\n     * @param  id   identifier of the font to set.\r\n     * @param  font value to which the font should be set.\r\n     * @return      <code>true</code> if the call actually changed the data, <code>false</code> otherwise.\r\n     */\r\n    public synchronized boolean setFont(int id, Font font) {\r\n        boolean buffer = isFontDifferent(id, font); // Used to store the result of isFontDifferent.\r\n        fonts[id] = font;\r\n        return buffer;\r\n    }\r\n\r\n    void setFontFast(int id, Font font) {\r\n        fonts[id] = font;\r\n    }\r\n\r\n\r\n    // - Items retrieval -----------------------------------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------------------------------------\r\n    /**\r\n     * Returns the requested color.\r\n     * <p>\r\n     * If the requested color wasn't set, its default value will be returned.\r\n     *\r\n     * @param  id identifier of the color to retrieve.\r\n     * @return    the requested color, or its default value if not set.\r\n     * @see       #getDefaultColor(int,ThemeData)\r\n     * @see       #isColorSet(int)\r\n     */\r\n    public synchronized Color getColor(int id) {\r\n        checkColorIdentifier(id);\r\n        return (colors[id] == null) ? getDefaultColor(id, this) : colors[id];\r\n    }\r\n\r\n    /**\r\n     * Returns the requested font.\r\n     * <p>\r\n     * If the requested font wasn't set, its default value will be returned.\r\n     *\r\n     * @param  id identifier of the font to retrieve.\r\n     * @return    the requested font, or its default value if not set.\r\n     * @see       #getDefaultFont(int, ThemeData)\r\n     * @see       #isFontSet(int)\r\n     */\r\n    public synchronized Font getFont(int id) {\r\n        checkFontIdentifier(id);\r\n        return fonts[id] == null ? getDefaultFont(id, this) : fonts[id];\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns <code>true</code> if the specified color is set.\r\n     * @param  id identifier of the color to check for.\r\n     * @return    <code>true</code> if the specified color is set, <code>false</code> otherwise.\r\n     * @see       #getDefaultColor(int,ThemeData)\r\n     */\r\n    boolean isColorSet(int id) {\r\n        return colors[id] != null;\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the specified font is set.\r\n     * @param  id identifier of the font to check for.\r\n     * @return    <code>true</code> if the specified font is set, <code>false</code> otherwise.\r\n     * @see       #getDefaultFont(int, ThemeData)\r\n     */\r\n    boolean isFontSet(int id) {\r\n        return fonts[id] != null;\r\n    }\r\n\r\n    /**\r\n     * Returns the default value for the specified color.\r\n     * <p>\r\n     * Default values are look&amp;feel dependant, and are subject to change during the application's\r\n     * life time.<br/>\r\n     * Classes that need to monitor such changes can register themselves using {@link #addDefaultValuesListener(ThemeListener)}.\r\n     *\r\n     * @param  id   identifier of the color whose default value should be retrieved.\r\n     * @param  data theme data from which to retrieve default values.\r\n     * @return      the default value for the specified color.\r\n     * @see         #addDefaultValuesListener(ThemeListener)\r\n     */\r\n    private static Color getDefaultColor(int id, ThemeData data) {\r\n        // Makes sure id is a legal color identifier.\r\n        checkColorIdentifier(id);\r\n        return COLORS.get(id).getColor(data);\r\n    }\r\n\r\n    /**\r\n     * Returns the default value for the specified font.\r\n     * <p>\r\n     * Default values are look&amp;feel dependant, and are subject to change during the application's\r\n     * life time.<br/>\r\n     * Classes that need to monitor such changes can register themselves using {@link #addDefaultValuesListener(ThemeListener)}.\r\n     *\r\n     * @param  id identifier of the font whose default value should be retrieved.\r\n     * @return    the default value for the specified font.\r\n     * @see       #addDefaultValuesListener(ThemeListener)\r\n     */\r\n    private static Font getDefaultFont(int id, ThemeData data) {\r\n        checkFontIdentifier(id);\r\n\r\n        return FONTS.get(id).getFont(data);\r\n    }\r\n\r\n\r\n\r\n    // - Comparison ----------------------------------------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------------------------------------\r\n    /**\r\n     * Returns <code>true</code> if the specified data and the current one are identical.\r\n     * <p>\r\n     * Comparisons is done by calling {@link #isFontDifferent(int,Font,boolean)} and {@link #isColorDifferent(int,Color,boolean)}\r\n     * on every font and color. Refer to the documentation of these methods for more information on using the <code>ignoreDefaults</code>\r\n     * parameter.\r\n     *\r\n     * @param  data           data against which to compare.\r\n     * @param  ignoreDefaults whether to compare default values.\r\n     * @return                <code>true</code> if the specified data and the current one are identical, <code>false</code> otherwise.\r\n     * @see                   #isFontDifferent(int,Font,boolean)\r\n     * @see                   #isColorDifferent(int,Color,boolean)\r\n     */\r\n    private boolean isIdentical(ThemeData data, boolean ignoreDefaults) {\r\n        // Compares the colors.\r\n        for (int i = 0; i < COLOR_COUNT; i++) {\r\n            if (isColorDifferent(i, data.colors[i], ignoreDefaults)) {\r\n                return false;\r\n            }\r\n        }\r\n        // Compares the fonts.\r\n        for (int i = 0; i < FONT_COUNT; i++) {\r\n            if (isFontDifferent(i, data.fonts[i], ignoreDefaults)) {\r\n                return false;\r\n            }\r\n        }\r\n        return true;\r\n    }\r\n\r\n    /**\r\n     * Returns <code>true</code> if the current data is identical to the specified one, using default values when items haven't been set.\r\n     * <p>\r\n     * This is a convenience method, and is strictly equivalent to calling {@link #isIdentical(ThemeData,boolean) isIdentical(data, false)}.\r\n     *\r\n     * @param  data data against which to compare.\r\n     * @return      <code>true</code> if the specified data and the current one are identical, <code>false</code> otherwise.\r\n     */\r\n    public boolean isIdentical(ThemeData data) {return isIdentical(data, false);}\r\n\r\n    /**\r\n     * Checks whether the current font and the specified one are different from one another.\r\n     * <p>\r\n     * This is a convenience method, and is stricly equivalent to calling\r\n     * <code>{@link #isFontDifferent(int,Font,boolean) isFontDifferent(}id, font, false)</code>.\r\n     *\r\n     * @param  id   identifier of the font to check.\r\n     * @param  font font to check.\r\n     * @return      <code>true</code> if <code>font</code> is different from the one defined in the data.\r\n     * @see         #isFontDifferent(int,Font,boolean)\r\n     * @see         #isColorDifferent(int,Color)\r\n     */\r\n    boolean isFontDifferent(int id, Font font) {return isFontDifferent(id, font, false);}\r\n\r\n    /**\r\n     * Checks whether the current font and the specified one are different from one another.\r\n     * <p>\r\n     * Setting <code>ignoreDefaults</code> to <code>false</code> will compare both fonts from a 'user' point of view: comparison\r\n     * will be done on the values that are used by the rest of the application. It might however be necessary to consider\r\n     * fonts to be different if one is set but not the other. This can be achieved by setting <code>ignoreDefaults</code> to <code>true</code>.\r\n     *\r\n     * @param  id             identifier of the font to check.\r\n     * @param  font           font to check.\r\n     * @param  ignoreDefaults whether to ignore defaults if the requested item doesn't have a value.\r\n     * @return                <code>true</code> if <code>font</code> is different from the one defined in the data.\r\n     * @see                   #isFontDifferent(int,Font)\r\n     * @see                   #isColorDifferent(int,Color)\r\n     */\r\n    private synchronized boolean isFontDifferent(int id, Font font, boolean ignoreDefaults) {\r\n        checkFontIdentifier(id);\r\n\r\n        // If the specified font is null, the only way for both fonts to be equal is for fonts[id]\r\n        // to be null as well.\r\n        if (font == null) {\r\n            return fonts[id] != null;\r\n        }\r\n\r\n        // If fonts[id] is null and we're set to ignore defaults, both fonts are different.\r\n        // If we're set to use defaults, we must compare font and the default value for id.\r\n        if (fonts[id] == null) {\r\n            return ignoreDefaults || !getDefaultFont(id, this).equals(font);\r\n        }\r\n\r\n        // 'Standard' case: both fonts are set, compare them normally.\r\n        return !font.equals(fonts[id]);\r\n    }\r\n\r\n    /**\r\n     * Checks whether the current color and the specified one are different from one another.\r\n     * <p>\r\n     * This is a convenience method, and is strictly equivalent to calling\r\n     * <code>{@link #isColorDifferent(int,Color,boolean) isColorDifferent(}id, color, false)</code>.\r\n     *\r\n     * @param  id   identifier of the color to check.\r\n     * @param  color color to check.\r\n     * @return      <code>true</code> if <code>color</code> is different from the one defined in the data.\r\n     * @see         #isColorDifferent(int,Color,boolean)\r\n     * @see         #isFontDifferent(int,Font)\r\n     */\r\n    public boolean isColorDifferent(int id, Color color) {return isColorDifferent(id, color, false);}\r\n\r\n    /**\r\n     * Checks whether the current color and the specified one are different from one another.\r\n     * <p>\r\n     * Setting <code>ignoreDefaults</code> to <code>false</code> will compare both colors from a 'user' point of view: comparison\r\n     * will be done on the values that are used by the rest of the application. It might however be necessary to consider\r\n     * colors to be different if one is set but not the other. This can be achieved by setting <code>ignoreDefaults</code> to <code>true</code>.\r\n     *\r\n     * @param  id             identifier of the color to check.\r\n     * @param  color           color to check.\r\n     * @param  ignoreDefaults whether to ignore defaults if the requested item doesn't have a value.\r\n     * @return                <code>true</code> if <code>color</code> is different from the one defined in the data.\r\n     * @see                   #isColorDifferent(int,Color)\r\n     * @see                   #isFontDifferent(int,Font)\r\n     */\r\n    private synchronized boolean isColorDifferent(int id, Color color, boolean ignoreDefaults) {\r\n        checkColorIdentifier(id);\r\n\r\n        // If the specified color is null, the only way for both colors to be equal is for colors[id]\r\n        // to be null as well.\r\n        if (color == null)\r\n            return colors[id] != null;\r\n\r\n        // If colors[id] is null and we're set to ignore defaults, both colors are different.\r\n        // If we're set to use defaults, we must compare color and the default value for id.\r\n        if (colors[id] == null)\r\n            return ignoreDefaults || !getDefaultColor(id, this).equals(color);\r\n\r\n        // 'Standard' case: both colors are set, compare them normally.\r\n        return !color.equals(colors[id]);\r\n    }\r\n\r\n\r\n\r\n    // - Theme events --------------------------------------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------------------------------------\r\n    /**\r\n     * Registers the specified theme listener.\r\n     * <p>\r\n     * The listener will receive {@link FontChangedEvent font} and {@link ColorChangedEvent color} events whenever\r\n     * one of the default values has been changed, by a modification to the current look&amp;feel for example.\r\n     *\r\n     * <p>\r\n     * It is not necessary for 'themable' components to listen to default values, as they are automatically propagated\r\n     * through {@link Theme} and {@link ThemeManager}.\r\n     *\r\n     * <p>\r\n     * Note that listeners are stored as weak references, to make sure that the API doesn't keep ghost copies of objects\r\n     * whose usefulness is long since past. This forces callers to make sure they keep a copy of the listener's instance: if\r\n     * they do not, the instance will be weakly linked and garbage collected out of existence.\r\n     *\r\n     * @param listener theme listener to register.\r\n     * @see            #removeDefaultValuesListener(ThemeListener)\r\n     */\r\n    static void addDefaultValuesListener(ThemeListener listener) {\r\n        listeners.put(listener, null);\r\n    }\r\n\r\n    /**\r\n     * Removes the specified instance from the list of registered theme listeners.\r\n     * <p>\r\n     * Note that since listeners are stored as weak references, calling this method is not strictly necessary. As soon\r\n     * as a listener instance is not referenced anymore, it will automatically be caught and destroyed by the garbage\r\n     * collector.\r\n     *\r\n     * @param listener instance to remove from the list of registered theme listeners.\r\n     * @see            #addDefaultValuesListener(ThemeListener)\r\n     */\r\n    private static void removeDefaultValuesListener(ThemeListener listener) {\r\n        listeners.remove(listener);\r\n    }\r\n\r\n    /**\r\n     * Dispatches a {@link FontChangedEvent} to all registered listeners.\r\n     * @param id   identifier of the font that changed.\r\n     * @param font new value for the font that changed.\r\n     */\r\n    static void triggerFontEvent(int id, Font font) {\r\n        // Creates the event.\r\n        FontChangedEvent event = new FontChangedEvent(null, id, font);  // Event that will be dispatched.\r\n\r\n        // Dispatches it.\r\n        for (ThemeListener listener : listeners.keySet()) {\r\n            listener.fontChanged(event);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Dispatches a {@link ColorChangedEvent} to all registered listeners.\r\n     * @param id    identifier of the color that changed.\r\n     * @param color new value for the color that changed.\r\n     */\r\n    static void triggerColorEvent(int id, Color color) {\r\n        ColorChangedEvent event = new ColorChangedEvent(null, id, color);\r\n\r\n        // Dispatches it.\r\n        for (ThemeListener listener : listeners.keySet()) {\r\n            listener.colorChanged(event);\r\n        }\r\n    }\r\n\r\n\r\n\r\n    // - Helper methods ------------------------------------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------------------------------------\r\n    /**\r\n     * Checks whether the specified color identifier is legal.\r\n     * @param  id                       identifier to check against.\r\n     * @throws IllegalArgumentException if <code>id</code> is not a legal color identifier.\r\n     */\r\n    private static void checkColorIdentifier(int id) {\r\n        if (id < 0 || id >= COLOR_COUNT) {\r\n            throw new IllegalArgumentException(\"Illegal color identifier: \" + id);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Checks whether the specified font identifier is legal.\r\n     * @param  id                       identifier to check against.\r\n     * @throws IllegalArgumentException if <code>id</code> is not a legal font identifier.\r\n     */\r\n    private static void checkFontIdentifier(int id) {\r\n        if (id < 0 || id >= FONT_COUNT) {\r\n            throw new IllegalArgumentException(\"Illegal font identifier: \" + id);\r\n        }\r\n    }\r\n\r\n    private static Font createDefaultTerminalFont() {\r\n        String name;\r\n        switch (OsFamily.getCurrent()) {\r\n            case WINDOWS:\r\n                name = \"Consolas\";\r\n                break;\r\n            case MAC_OS_X:\r\n                return new Font(\"Menlo\", Font.PLAIN, 14);\r\n            case LINUX:\r\n                int size = RuntimeConstants.DISPLAY_4K ? 32 : 14;\r\n                return new Font(\"Monospaced\", Font.PLAIN, size);\r\n            default:\r\n                name = \"Monospaced\";\r\n        }\r\n        return new Font(name, Font.PLAIN, 14);\r\n    }\r\n\r\n\r\n    private static Font createDefaultHexViewerFont() {\r\n        int size = OsFamily.getCurrent() == OsFamily.LINUX && RuntimeConstants.DISPLAY_4K ? 28 : 14;\r\n        return new Font(\"Monospaced\", Font.PLAIN, size);\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/theme/ThemeId.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2017 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.theme;\n\n/**\n * Created on 09/02/17.\n * @author Oleg Trifonov\n */\npublic interface ThemeId {\n    // - Dirty hack ----------------------------------------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------------------------------------\n    // This is an effort to make the ThemeData class a bit easier to maintain, but I'm the first\n    // to admit it's rather dirty.\n    //\n    // For optimization reasons, we're storing the fonts and colors in arrays, using their\n    // identifiers as indexes in the array. This, however, means that lots of bits of code\n    // must be updated whenever a font or color is added or removed. The probability of\n    // someone forgetting this is, well, 100%.\n    //\n    // For this reason, we've declared the number of font and colors as constants.\n    // People are still going to forget to update these constants, but at least it'll be\n    // a lot easier to fix.\n\n\n\n    // - Font definitions ----------------------------------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------------------------------------\n    /**\n     * Font used in the folder panels.\n     * <p>\n     * This defaults to the current <code>JTable</code> font.\n     */\n    int FILE_TABLE_FONT = 0;\n\n    /**\n     * Font used to display shell output.\n     * <p>\n     * This defaults to the current <code>JTextArea</code> font.\n     */\n    int SHELL_FONT = 1;\n\n    /**\n     * Font used in the file editor and viewer.\n     * <p>\n     * This defaults to the current <code>JTable</code> font.\n     */\n    int EDITOR_FONT = 2;\n\n    /**\n     * Font used in the location bar.\n     * <p>\n     * This defaults to the current <code>JTextField</code> font.\n     */\n    int LOCATION_BAR_FONT = 3;\n\n    /**\n     * Font used in the shell history widget.\n     * <p>\n     * This defaults to the current <code>JTextField</code> font.\n     */\n    int SHELL_HISTORY_FONT = 4;\n\n    /**\n     * Font used in the status bar.\n     * <p>\n     * This defaults to the current <code>JLabel</code> font.\n     */\n    int STATUS_BAR_FONT = 5;\n\n    /**\n     * Font used in the quick list header.\n     * <p>\n     * This defaults to a similar font of the current <code>JTable</code> font, but a little bigger.\n     */\n    int QUICK_LIST_HEADER_FONT = 6;\n\n    /**\n     * Font used in the quick list item.\n     * <p>\n     * This defaults to the current <code>JTable</code> font.\n     */\n    int QUICK_LIST_ITEM_FONT = 7;\n\n    int TERMINAL_FONT = 8;\n\n    /**\n     * Font used in the file editor and viewer.\n     * <p>\n     * This defaults to the current <code>JTable</code> font.\n     */\n    int HEX_VIEWER_FONT = 9;\n\n    /**\n     * Number of known fonts.\n     * <p>\n     * Since font identifiers are contiguous, it is possible to explore all fonts contained\n     * by an instance of theme data by looping from 0 to this value.\n     */\n    int FONT_COUNT  = 10;\n\n\n\n    // - Color definitions ---------------------------------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------------------------------------\n    /**\n     * Color used to paint the folder panels' borders.\n     * <p>\n     * This defaults to <code>Color.GRAY</code>.\n     */\n    int FILE_TABLE_BORDER_COLOR = 0;\n\n    /**\n     * Color used to paint the folder panels' borders when it doesn't have the focus.\n     * <p>\n     * This defaults to <code>Color.GRAY</code>.\n     */\n    int FILE_TABLE_INACTIVE_BORDER_COLOR = 1;\n\n    /**\n     * Color used to paint the folder panel's background color.\n     * <p>\n     * This defaults to the current <code>JTable</code> background color.\n     */\n    int FILE_TABLE_BACKGROUND_COLOR = 2;\n\n    /**\n     * Color used to paint the folder panel's alternate background color.\n     * <p>\n     * This defaults to the current <code>JTable</code> background color.\n     */\n    int FILE_TABLE_ALTERNATE_BACKGROUND_COLOR = 3;\n\n    /**\n     * Color used to paint the folder panel's background color when it doesn't have the focus.\n     * <p>\n     * This behaves in exactly the same fashion as {@link #FILE_TABLE_BACKGROUND_COLOR}, and defaults\n     * to the same value.\n     */\n    int FILE_TABLE_INACTIVE_BACKGROUND_COLOR = 4;\n\n    /**\n     * Color used to paint the folder panel's alternate background color when inactive.\n     * <p>\n     * This defaults to the current <code>JTable</code> background color.\n     */\n    int FILE_TABLE_INACTIVE_ALTERNATE_BACKGROUND_COLOR = 5;\n\n    /**\n     * Color used to paint the file table's background color when it's part of an unmatched file.\n     */\n    int FILE_TABLE_UNMATCHED_BACKGROUND_COLOR = 6;\n\n    /**\n     * Color used to paint the file table's foreground color when it's part of an unmatched file.\n     */\n    int FILE_TABLE_UNMATCHED_FOREGROUND_COLOR = 7;\n\n    /**\n     * Color used to paint the file table's background color when in a selected row.\n     */\n    int FILE_TABLE_SELECTED_BACKGROUND_COLOR = 8;\n\n    /**\n     * Color used to paint the gradient of the file table's selection.\n     */\n    int FILE_TABLE_SELECTED_SECONDARY_BACKGROUND_COLOR = 9;\n\n    /**\n     * Color used to paint the gradient of the file table's selection when inactive.\n     */\n    int FILE_TABLE_INACTIVE_SELECTED_SECONDARY_BACKGROUND_COLOR = 10;\n\n    /**\n     * Colors used to pain the file table's background color when in an inactive selected row.\n     */\n    int FILE_TABLE_INACTIVE_SELECTED_BACKGROUND_COLOR = 11;\n\n    /**\n     * Color used to paint hidden files text in the folder panels.\n     * <p>\n     * This defaults to the current <code>JTable</code> foreground color.\n     */\n    int HIDDEN_FOLDER_FOREGROUND_COLOR = 12;\n\n    /**\n     * Color used to paint hidden files text in the folder panels when they don't have the focus.\n     * <p>\n     * This behaves in exactly the same fashion as {@link #HIDDEN_FILE_FOREGROUND_COLOR}, and defaults\n     * to the same value.\n     */\n    int HIDDEN_FOLDER_INACTIVE_FOREGROUND_COLOR = 13;\n\n    /**\n     * Color used to paint selected hidden files text in the folder panels.\n     * <p>\n     * This defaults to the current <code>JTable</code> selection foreground color.\n     */\n    int HIDDEN_FOLDER_SELECTED_FOREGROUND_COLOR = 14;\n\n    /**\n     * Color used to paint selected hidden files text in the folder panels when they don't have the focus.\n     * <p>\n     * This behaves in exactly the same fashion as {@link #HIDDEN_FILE_SELECTED_FOREGROUND_COLOR}, and defaults\n     * to the same value.\n     */\n    int HIDDEN_FOLDER_INACTIVE_SELECTED_FOREGROUND_COLOR = 15;\n\n\n    /**\n     * Color used to paint hidden files text in the folder panels.\n     * <p>\n     * This defaults to the current <code>JTable</code> foreground color.\n     */\n    int HIDDEN_FILE_FOREGROUND_COLOR = 16;\n\n    /**\n     * Color used to paint hidden files text in the folder panels when they don't have the focus.\n     * <p>\n     * This behaves in exactly the same fashion as {@link #HIDDEN_FILE_FOREGROUND_COLOR}, and defaults\n     * to the same value.\n     */\n    int HIDDEN_FILE_INACTIVE_FOREGROUND_COLOR = 17;\n\n    /**\n     * Color used to paint selected hidden files text in the folder panels.\n     * <p>\n     * This defaults to the current <code>JTable</code> selection foreground color.\n     */\n    int HIDDEN_FILE_SELECTED_FOREGROUND_COLOR = 18;\n\n    /**\n     * Color used to paint selected hidden files text in the folder panels when they don't have the focus.\n     * <p>\n     * This behaves in exactly the same fashion as {@link #HIDDEN_FILE_SELECTED_FOREGROUND_COLOR}, and defaults\n     * to the same value.\n     */\n    int HIDDEN_FILE_INACTIVE_SELECTED_FOREGROUND_COLOR = 19;\n\n    /**\n     * Color used to paint folders text in the folder panels.\n     * <p>\n     * This defaults to the current <code>JTable</code> foreground color.\n     */\n    int FOLDER_FOREGROUND_COLOR = 20;\n\n    /**\n     * Color used to paint folders text in the folder panels when they don't have the focus.\n     * <p>\n     * This behaves in exactly the same fashion as {@link #FOLDER_FOREGROUND_COLOR}, and defaults\n     * to the same value.\n     */\n    int FOLDER_INACTIVE_FOREGROUND_COLOR = 21;\n\n    /**\n     * Color used to paint selected folders text in the folder panels.\n     * <p>\n     * This defaults to the current <code>JTable</code> selection foreground color.\n     */\n    int FOLDER_SELECTED_FOREGROUND_COLOR = 22;\n\n    /**\n     * Color used to paint selected folders text in the folder panels when they don't have the focus.\n     * <p>\n     * This behaves in exactly the same fashion as {@link #FOLDER_SELECTED_FOREGROUND_COLOR}, and defaults\n     * to the same value.\n     */\n    int FOLDER_INACTIVE_SELECTED_FOREGROUND_COLOR = 23;\n\n    /**\n     * Color used to paint archives text in the folder panels.\n     * <p>\n     * This defaults to the current <code>JTable</code> foreground color.\n     */\n    int ARCHIVE_FOREGROUND_COLOR = 24;\n\n    /**\n     * Color used to paint archives text in the folder panels when they don't have the focus.\n     * <p>\n     * This behaves in exactly the same fashion as {@link #ARCHIVE_FOREGROUND_COLOR}, and defaults\n     * to the same value.\n     */\n    int ARCHIVE_INACTIVE_FOREGROUND_COLOR = 25;\n\n    /**\n     * Color used to paint selected archives text in the folder panels.\n     * <p>\n     * This defaults to the current <code>JTable</code> selection foreground color.\n     */\n    int ARCHIVE_SELECTED_FOREGROUND_COLOR = 26;\n\n    /**\n     * Color used to paint selected archives text in the folder panels when they don't have the focus.\n     * <p>\n     * This behaves in exactly the same fashion as {@link #ARCHIVE_SELECTED_FOREGROUND_COLOR}, and defaults\n     * to the same value.\n     */\n    int ARCHIVE_INACTIVE_SELECTED_FOREGROUND_COLOR = 27;\n\n    /**\n     * Color used to paint symlinks text in the folder panels.\n     * <p>\n     * This defaults to the current <code>JTable</code> foreground color.\n     */\n    int SYMLINK_FOREGROUND_COLOR = 28;\n\n    /**\n     * Color used to paint symlinks text in the folder panels when they don't have the focus.\n     * <p>\n     * This behaves in exactly the same fashion as {@link #SYMLINK_FOREGROUND_COLOR}, and defaults\n     * to the same value.\n     */\n    int SYMLINK_INACTIVE_FOREGROUND_COLOR = 29;\n\n    /**\n     * Color used to paint selected symlinks text in the folder panels.\n     * <p>\n     * This defaults to the current <code>JTable</code> selection foreground color.\n     */\n    int SYMLINK_SELECTED_FOREGROUND_COLOR = 30;\n\n    /**\n     * Color used to paint selected symlinks text in the folder panels when they don't have the focus.\n     * <p>\n     * This behaves in exactly the same fashion as {@link #SYMLINK_SELECTED_FOREGROUND_COLOR}, and defaults\n     * to the same value.\n     */\n    int SYMLINK_INACTIVE_SELECTED_FOREGROUND_COLOR = 31;\n\n    /**\n     * Color used to paint marked files text in the folder panels.\n     * <p>\n     * This defaults to the current <code>JTable</code> foreground color.\n     */\n    int MARKED_FOREGROUND_COLOR = 32;\n\n    /**\n     * Color used to paint marked files text in the folder panels when they don't have the focus.\n     * <p>\n     * This behaves in exactly the same fashion as {@link #MARKED_FOREGROUND_COLOR}, and defaults\n     * to the same value.\n     */\n    int MARKED_INACTIVE_FOREGROUND_COLOR = 33;\n\n    /**\n     * Color used to paint selected marked files text in the folder panels.\n     * <p>\n     * This defaults to the current <code>JTable</code> selection foreground color.\n     */\n    int MARKED_SELECTED_FOREGROUND_COLOR = 34;\n\n    /**\n     * Color used to paint selected marked files text in the folder panels when they don't have the focus.\n     * <p>\n     * This behaves in exactly the same fashion as {@link #MARKED_SELECTED_FOREGROUND_COLOR}, and defaults\n     * to the same value.\n     */\n    int MARKED_INACTIVE_SELECTED_FOREGROUND_COLOR = 35;\n\n    /**\n     * Color used to paint plain files text in the folder panels.\n     * <p>\n     * This defaults to the current <code>JTable</code> foreground color.\n     */\n    int FILE_FOREGROUND_COLOR = 36;\n\n    /**\n     * Color used to paint plain files text in the folder panels when they don't have the focus.\n     * <p>\n     * This behaves in exactly the same fashion as {@link #FILE_FOREGROUND_COLOR}, and defaults\n     * to the same value.\n     */\n    int FILE_INACTIVE_FOREGROUND_COLOR = 37;\n\n    /**\n     * Color used to paint selected plain files text in the folder panels.\n     * <p>\n     * This defaults to the current <code>JTable</code> selection foreground color.\n     */\n    int FILE_SELECTED_FOREGROUND_COLOR = 38;\n\n    /**\n     * Color used to paint selected plain files text in the folder panels when they don't have the focus.\n     * <p>\n     * This behaves in exactly the same fashion as {@link #FILE_SELECTED_FOREGROUND_COLOR}, and defaults\n     * to the same value.\n     */\n    int FILE_INACTIVE_SELECTED_FOREGROUND_COLOR = 39;\n\n    /**\n     * Color used to paint shell commands output.\n     * <p>\n     * This defaults to the current <code>JTextArea</code> foreground color.\n     */\n    int SHELL_FOREGROUND_COLOR = 40;\n\n    /**\n     * Color used to paint the background of shell commands output.\n     * <p>\n     * This defaults to the current <code>JTextArea</code> background color.\n     */\n    int SHELL_BACKGROUND_COLOR = 41;\n\n    /**\n     * Color used to paint shell commands output when selected.\n     * <p>\n     * This defaults to the current <code>JTextArea</code> selection foreground color.\n     */\n    int SHELL_SELECTED_FOREGROUND_COLOR = 42;\n\n    /**\n     * Color used to paint the background of shell commands output when selected.\n     * <p>\n     * This defaults to the current <code>JTextArea</code> selection background color.\n     */\n    int SHELL_SELECTED_BACKGROUND_COLOR = 43;\n\n    /**\n     * Color used to paint the shell history's text.\n     * <p>\n     * This defaults to the current <code>JTextField</code> foreground color.\n     */\n    int SHELL_HISTORY_FOREGROUND_COLOR = 44;\n\n    /**\n     * Color used to paint the shell history's background.\n     * <p>\n     * This defaults to the current <code>JTextField</code> background color.\n     */\n    int SHELL_HISTORY_BACKGROUND_COLOR = 45;\n\n    /**\n     * Color used to paint the shell history's text when selected.\n     * <p>\n     * This defaults to the current <code>JTextField</code> selection foreground color.\n     */\n    int SHELL_HISTORY_SELECTED_FOREGROUND_COLOR = 46;\n\n    /**\n     * Color used to paint the shell history's background when selected.\n     * <p>\n     * This defaults to the current <code>JTextField</code> selection background color.\n     */\n    int SHELL_HISTORY_SELECTED_BACKGROUND_COLOR = 47;\n\n    /**\n     * Color used to paint the file editor / viewer's text.\n     * <p>\n     * This defaults to the current <code>JTextArea</code> foreground color.\n     */\n    int EDITOR_FOREGROUND_COLOR = 48;\n\n    /**\n     * Color used to paint the file editor / viewer's background.\n     * <p>\n     * This defaults to the current <code>JTextArea</code> background color.\n     */\n    int EDITOR_BACKGROUND_COLOR = 49;\n\n    /**\n     * Color used to paint the file editor / viewer's foreground when selected.\n     * <p>\n     * This defaults to the current <code>JTextArea</code> selection foreground color.\n     */\n    int EDITOR_SELECTED_FOREGROUND_COLOR = 50;\n\n    /**\n     * Color used to paint the file editor / viewer's background when selected.\n     * <p>\n     * This defaults to the current <code>JTextArea</code> selection background color.\n     */\n    int EDITOR_SELECTED_BACKGROUND_COLOR = 51;\n\n    /**\n     * Color used to paint the location's bar text.\n     * <p>\n     * This defaults to the current <code>JTextField</code> foreground color.\n     */\n    int LOCATION_BAR_FOREGROUND_COLOR = 52;\n\n    /**\n     * Color used to paint the location's bar background.\n     * <p>\n     * This defaults to the current <code>JTextField</code> background color.\n     */\n    int LOCATION_BAR_BACKGROUND_COLOR = 53;\n\n    /**\n     * Color used to paint the location's bar text when selected.\n     * <p>\n     * This defaults to the current <code>JTextField</code> selection foreground color.\n     */\n    int LOCATION_BAR_SELECTED_FOREGROUND_COLOR = 54;\n\n    /**\n     * Color used to paint the location's bar background when selected.\n     * <p>\n     * This defaults to the current <code>JTextField</code> selection background color.\n     */\n    int LOCATION_BAR_SELECTED_BACKGROUND_COLOR = 55;\n\n    /**\n     * Color used to paint the location's bar background when used as a progress bar.\n     * <p>\n     * Note that this color is painted over the location's bar background and foreground. In order\n     * for anything to be visible under it, it needs to have an alpha transparency component.\n     * <p>\n     * This defaults to the current <code>JTextField</code> selection background color, with an\n     * alpha transparency value of 64.\n     */\n    int LOCATION_BAR_PROGRESS_COLOR = 56;\n\n    /**\n     * Color used to paint the status bar's text.\n     * <p>\n     * This defaults to the current <code>JLabel</code> foreground color.\n     */\n    int STATUS_BAR_FOREGROUND_COLOR = 57;\n\n    /**\n     * Color used to paint the status bar's background\n     * <p>\n     * This defaults to the current <code>JLabel</code> background color.\n     */\n    int STATUS_BAR_BACKGROUND_COLOR = 58;\n\n    /**\n     * Color used to paint the status bar's border.\n     * <p>\n     * This defaults to <code>Color.GRAY</code>.\n     */\n    int STATUS_BAR_BORDER_COLOR = 59;\n\n    /**\n     * Color used to paint the status bar's drive usage color when there's plenty of space left.\n     * <p>\n     * This defaults to <code>0x70EC2B</code>.\n     */\n    int STATUS_BAR_OK_COLOR = 60;\n\n    /**\n     * Color used to paint the status bar's drive usage color when there's an average amount of space left.\n     * <p>\n     * This defaults to <code>0xFF7F00</code>.\n     */\n    int STATUS_BAR_WARNING_COLOR = 61;\n\n    /**\n     * Color used to paint the status bar's drive usage color when there's dangerously little space left.\n     * <p>\n     * This defaults to <code>Color.RED</code>.\n     */\n    int STATUS_BAR_CRITICAL_COLOR = 62;\n\n    /**\n     * Color used to paint the outline of selected files.\n     */\n    int FILE_TABLE_SELECTED_OUTLINE_COLOR = 63;\n\n    /**\n     * Color used to paint the outline of selected files in an inactive table.\n     */\n    int FILE_TABLE_INACTIVE_SELECTED_OUTLINE_COLOR = 64;\n\n    /**\n     * Color used to paint the main background of a quick list header.\n     */\n    int QUICK_LIST_HEADER_BACKGROUND_COLOR = 65;\n\n    /**\n     * Color used to paint the secondary background of a quick list header.\n     */\n    int QUICK_LIST_HEADER_SECONDARY_BACKGROUND_COLOR = 66;\n\n    /**\n     * Color used to paint the text of a quick list header.\n     */\n    int QUICK_LIST_HEADER_FOREGROUND_COLOR = 67;\n\n    /**\n     * Color used to paint the background of a quick list item.\n     */\n    int QUICK_LIST_ITEM_BACKGROUND_COLOR = 68;\n\n    /**\n     * Color used to paint the text of a quick list item.\n     */\n    int QUICK_LIST_ITEM_FOREGROUND_COLOR = 69;\n\n    /**\n     * Color used to paint the background of a selected quick list item.\n     */\n    int QUICK_LIST_SELECTED_ITEM_BACKGROUND_COLOR = 70;\n\n    /**\n     * Color used to paint the text of a selected quick list item.\n     */\n    int QUICK_LIST_SELECTED_ITEM_FOREGROUND_COLOR = 71;\n\n    /**\n     * Color used to paint current line in the file editor / viewer's background when selected.\n     * <p>\n     * This defaults to the current <code>RTextArea</code> selection background color\n     */\n    int EDITOR_CURRENT_BACKGROUND_COLOR = 72;\n\n    int FILE_GROUP_1_FOREGROUND_COLOR = 73;\n//    static final int FILE_GROUP_2_FOREGROUND_COLOR = 74;\n//    static final int FILE_GROUP_3_FOREGROUND_COLOR = 75;\n//    static final int FILE_GROUP_4_FOREGROUND_COLOR = 76;\n//    static final int FILE_GROUP_5_FOREGROUND_COLOR = 77;\n//    static final int FILE_GROUP_6_FOREGROUND_COLOR = 78;\n//    static final int FILE_GROUP_7_FOREGROUND_COLOR = 79;\n//    static final int FILE_GROUP_8_FOREGROUND_COLOR = 80;\n//    static final int FILE_GROUP_9_FOREGROUND_COLOR = 81;\n    int FILE_GROUP_10_FOREGROUND_COLOR = 82;\n\n    int TERMINAL_BACKGROUND_COLOR = 83;\n    int TERMINAL_FOREGROUND_COLOR = 84;\n    int TERMINAL_SELECTED_FOREGROUND_COLOR = 85;\n    int TERMINAL_SELECTED_BACKGROUND_COLOR = 86;\n\n    /**\n     * Color used to paint plain files text in the folder panels.\n     * <p>\n     * This defaults to the current <code>JTable</code> foreground color.\n     */\n    int EXECUTABLE_FOREGROUND_COLOR = 87;\n\n    /**\n     * Color used to paint plain files text in the folder panels when they don't have the focus.\n     * <p>\n     * This behaves in exactly the same fashion as {@link #FILE_FOREGROUND_COLOR}, and defaults\n     * to the same value.\n     */\n    int EXECUTABLE_INACTIVE_FOREGROUND_COLOR = 88;\n\n    /**\n     * Color used to paint selected plain files text in the folder panels.\n     * <p>\n     * This defaults to the current <code>JTable</code> selection foreground color.\n     */\n    int EXECUTABLE_SELECTED_FOREGROUND_COLOR = 89;\n\n    /**\n     * Color used to paint selected plain files text in the folder panels when they don't have the focus.\n     * <p>\n     * This behaves in exactly the same fashion as {@link #FILE_SELECTED_FOREGROUND_COLOR}, and defaults\n     * to the same value.\n     */\n    int EXECUTABLE_INACTIVE_SELECTED_FOREGROUND_COLOR = 90;\n\n    /**\n     * Color used to paint the hex viewer dump's.\n     */\n    int HEX_VIEWER_HEX_FOREGROUND_COLOR = 91;\n    /**\n     * Color used to paint the hex viewer background.\n     */\n    int HEX_VIEWER_BACKGROUND_COLOR = 92;\n    /**\n     * Color used to paint the hex viewer alternate background.\n     */\n    int HEX_VIEWER_ALTERNATE_BACKGROUND_COLOR = 93;\n    /**\n     * Color used to paint the hex viewer ascii's text.\n     */\n    int HEX_VIEWER_ASCII_FOREGROUND_COLOR = 94;\n    /**\n     * Color used to paint the hex viewer offset's text.\n     */\n    int HEX_VIEWER_OFFSET_FOREGROUND_COLOR = 95;\n    /**\n     * Color used to paint the hex viewer's dump foreground when selected.\n     */\n    int HEX_VIEWER_SELECTED_DUMP_FOREGROUND_COLOR = 96;\n    /**\n     * Color used to paint the hex viewer's background when selected.\n     */\n    int HEX_VIEWER_SELECTED_BACKGROUND_COLOR = 97;\n    /**\n     * Color used to paint the hex viewer's ascii foreground when selected.\n     */\n    int HEX_VIEWER_SELECTED_ASCII_BACKGROUND_COLOR = 98;\n\n    /**\n     * Number of known colors.\n     * <p>\n     * Since color identifiers are contiguous, it is possible to explore all colors contained\n     * by an instance of theme data by looping from 0 to this color.\n     */\n    int COLOR_COUNT = 99;\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/theme/ThemeListener.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.theme;\n\n/**\n * Implementations of this interface can listen to changes in the current theme.\n * @author Nicolas Rinaudo\n */\npublic interface ThemeListener {\n    /**\n     * Notifies the listener that a color has been changed.\n     */\n    void colorChanged(ColorChangedEvent event);\n\n    /**\n     * Notifies the listener that a font has been changed.\n     */\n    void fontChanged(FontChangedEvent event);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/theme/ThemeManager.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.theme;\n\nimport java.awt.Color;\nimport java.awt.Font;\nimport java.io.*;\nimport java.util.*;\n\nimport com.mucommander.commons.file.util.PathUtils;\nimport lombok.Getter;\nimport lombok.extern.slf4j.Slf4j;\n\nimport com.mucommander.PlatformManager;\nimport com.mucommander.RuntimeConstants;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.commons.file.util.ResourceLoader;\nimport com.mucommander.commons.io.StreamUtils;\nimport com.mucommander.commons.util.StringUtils;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.io.backup.BackupInputStream;\nimport com.mucommander.io.backup.BackupOutputStream;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.theme.Theme.Type;\n\n/**\n * Offers methods for accessing and modifying themes.\n * @author Nicolas Rinaudo\n */\n@Slf4j\npublic class ThemeManager {\n    // - Class variables -----------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    /** Path to the user defined theme file. */\n    private static       AbstractFile userThemeFile;\n    /** Default user defined theme file name. */\n    private static final String       USER_THEME_FILE_NAME             = \"user_theme.xml\";\n    /** Path to the custom themes repository. */\n    private static final String       CUSTOM_THEME_FOLDER              = \"themes\";\n    /** List of all registered theme change listeners. */\n    private static final WeakHashMap<ThemeListener, Object>  listeners = new WeakHashMap<>();\n    /** List of all predefined theme names. */\n    private static List<String> predefinedThemeNames;\n    /** List of all predefined syntax highlight theme names. */\n    private static List<String> predefinedSyntaxThemeNames;\n\n\n    // - Instance variables --------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    /** Whether the user theme was modified. */\n    private static boolean       wasUserThemeModified;\n    /** Theme that is currently applied to muCommander. */\n    @Getter\n    private static Theme         currentTheme;\n    /** Used to listen on the current theme's modifications. */\n    private static final ThemeListener listener = new CurrentThemeListener();\n    /** Theme that is currently applied to viewer and editor. */\n    @Getter\n    private static String currentSyntaxThemeName;\n\n\n    private ThemeManager() {}\n\n    /**\n     * Loads the current theme.\n     * <p>\n     * This method goes through the following steps:\n     * <ul>\n     *  <li>Try to load the theme defined in the configuration.</li>\n     *  <li>If that failed, try to load the default theme.</li>\n     *  <li>If that failed, try to load the user theme if that hasn't been tried yet.</li>\n     *  <li>If that failed, use an empty theme.</li>\n     * </ul>\n     */\n    public static void loadCurrentTheme() {\n        Theme.Type type;               // Current theme's type.\n        String name;               // Current theme's name.\n        boolean wasUserThemeLoaded; // Whether we have tried loading the user theme or not.\n\n        // Loads the current theme type as defined in configuration.\n        try {\n            type = getThemeTypeFromLabel(TcConfigurations.getPreferences().getVariable(TcPreference.THEME_TYPE, TcPreferences.DEFAULT_THEME_TYPE));\n        } catch(Exception e) {\n            log.error(\"Theme type error\", e);\n            type = getThemeTypeFromLabel(TcPreferences.DEFAULT_THEME_TYPE);\n        }\n\n        // Loads the current theme name as defined in configuration.\n        if (type != Theme.Type.USER) {\n            wasUserThemeLoaded = false;\n            name = TcConfigurations.getPreferences().getVariable(TcPreference.THEME_NAME, TcPreferences.DEFAULT_THEME_NAME);\n        } else {\n            name = null;\n            wasUserThemeLoaded = true;\n        }\n        // If the current theme couldn't be loaded, uses the default theme as defined in the configuration.\n        currentTheme = null;\n        try {\n            currentTheme = readTheme(type, name);\n        } catch(Exception e1) {\n            log.error(\"Can't read theme\", e1);\n            type = getThemeTypeFromLabel(TcPreferences.DEFAULT_THEME_TYPE);\n            name = TcPreferences.DEFAULT_THEME_NAME;\n\n            if (type == Theme.Type.USER) {\n                wasUserThemeLoaded = true;\n            }\n\n            // If the default theme can be loaded, tries to load the user theme if we haven't done so yet.\n            // If we have, or if it fails, defaults to an empty user theme.\n            try {\n                currentTheme = readTheme(type, name);\n            } catch(Exception e2) {\n                if (!wasUserThemeLoaded) {\n                    try {\n                        currentTheme = readTheme(Theme.Type.USER, null);\n                    } catch(Exception e3) {\n                        log.error(\"Can't read theme\", e3);\n                    }\n                }\n                if (currentTheme == null) {\n                    currentTheme = new Theme(listener);\n                    wasUserThemeModified = true;\n                }\n            }\n            setConfigurationTheme(currentTheme);\n        }\n        currentSyntaxThemeName = TcConfigurations.getPreferences().getVariable(TcPreference.SYNTAX_THEME_NAME, TcPreferences.DEFAULT_SYNTAX_THEME_NAME);\n    }\n\n\n\n    // - Themes access -------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    private static Iterator<String> predefinedThemeNames() {\n        // The list of predefined themes is no longer dynamically created as this causes Webstart to retrieve and\n        // explore the application's JAR via HTTP, which is inefficient and prevents the application from being\n        // launched offline.\n        if (predefinedThemeNames == null) {\n            try {\n                predefinedThemeNames = getThemeNames(ResourceLoader.getRootPackageAsFile(ThemeManager.class).getChild(PathUtils.removeLeadingSeparator(RuntimeConstants.THEMES_PATH, \"/\")));\n            } catch (IOException e) {\n                predefinedThemeNames = new ArrayList<>();\n            }\n        }\n        return predefinedThemeNames.iterator();\n    }\n\n    public static List<String> predefinedSyntaxThemeNames() {\n        // The list of predefined themes is no longer dynamically created as this causes Webstart to retrieve and\n        // explore the application's JAR via HTTP, which is inefficient and prevents the application from being\n        // launched offline.\n        if (predefinedSyntaxThemeNames == null) {\n            try {\n                predefinedSyntaxThemeNames = getThemeNames(ResourceLoader.getRootPackageAsFile(ThemeManager.class).getChild(PathUtils.removeLeadingSeparator(RuntimeConstants.TEXT_SYNTAX_THEMES_PATH, \"/\")));\n            } catch (IOException e) {\n                predefinedSyntaxThemeNames = new ArrayList<>();\n            }\n        }\n        return predefinedSyntaxThemeNames;\n    }\n\n\n    private static Iterator<String> customThemeNames() throws IOException {\n        return getThemeNames(FileFactory.getFile(getCustomThemesFolder().getAbsolutePath())).iterator();\n    }\n\n    private static List<String> getThemeNames(AbstractFile themeFolder) {\n        try {\n            AbstractFile[] files = themeFolder.ls(new ExtensionFilenameFilter(\".xml\"));\n            List<String> names = new ArrayList<>();\n            for (AbstractFile file : files)\n                names.add(getThemeName(file));\n            return names;\n        } catch(Exception e) {\n            return new ArrayList<>();\n        }\n    }\n\n    private static List<Theme> getAvailableThemes() {\n        Iterator<String> iterator;\n\n        List<Theme> themes = new ArrayList<>();\n\n        // Tries to load the user theme. If it's corrupt, uses an empty user theme.\n        try {\n            themes.add(readTheme(Theme.Type.USER, null));\n        } catch(Exception e) {\n            themes.add(new Theme(listener));\n        }\n\n        // Loads predefined themes.\n        iterator = predefinedThemeNames();\n        while (iterator.hasNext()) {\n            String name = iterator.next();\n            try {\n                themes.add(readTheme(Theme.Type.PREDEFINED, name));\n            } catch(Exception e) {\n                log.warn(\"Failed to load predefined theme \" + name, e);\n            }\n        }\n\n        // Loads custom themes.\n        try {\n            iterator = customThemeNames();\n            while (iterator.hasNext()) {\n                String name = iterator.next();\n                try {\n                    themes.add(readTheme(Theme.Type.CUSTOM, name));\n                } catch(Exception e) {\n                    log.warn(\"Failed to load custom theme \" + name, e);\n                }\n            }\n        } catch(Exception e) {\n            log.warn(\"Failed to load custom themes\", e);\n        }\n\n        // Sorts the themes by name.\n        themes.sort(Comparator.comparing(Theme::getName));\n\n        return themes;\n    }\n\n    private static List<String> getAvailableThemeNames() {\n        List<String> themes = new ArrayList<>();\n\n        // Adds the user theme name.\n        themes.add(Translator.get(\"theme.custom_theme\"));\n\n        // Adds predefined theme names.\n        Iterator<String> iterator = predefinedThemeNames();\n        while(iterator.hasNext())\n            themes.add(iterator.next());\n\n        // Adds custom theme names.\n        try {\n            iterator = customThemeNames();\n            while (iterator.hasNext()) {\n                themes.add(iterator.next());\n            }\n        } catch(Exception e) {\n            log.debug(\"Failed to load custom theme names\", e);\n        }\n\n        // Sorts the theme names.\n        Collections.sort(themes);\n\n        return themes;\n    }\n\n    public static Iterator<String> availableThemeNames() {\n        return getAvailableThemeNames().iterator();\n    }\n\n    public static synchronized Iterator<Theme> availableThemes() {\n        return getAvailableThemes().iterator();\n    }\n\n\n\n    // - Theme paths access --------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    /**\n     * Returns the path to the user's theme file.\n     * <p>\n     * This method cannot guarantee the file's existence, and it's up to the caller\n     * to deal with the fact that the user might not actually have created a user theme.\n     * <p>\n     * This method's return value can be modified through {@link #setUserThemeFile(String)}.\n     * If this wasn't called, the default path will be used. This is generated by calling\n     * <code>new java.io.File({@link com.mucommander.PlatformManager#getPreferencesFolder()}, {@link #USER_THEME_FILE_NAME})</code>.\n     *\n     * @return             the path to the user's theme file.\n     * @see                #setUserThemeFile(String)\n     * @throws IOException if an error occurred while locating the default user theme file.\n     */\n    private static AbstractFile getUserThemeFile() throws IOException {\n        if (userThemeFile == null) {\n            return PlatformManager.getPreferencesFolder().getChild(USER_THEME_FILE_NAME);\n        }\n        return userThemeFile;\n    }\n\n    /**\n     * Sets the path to the user theme file.\n     * <p>\n     * The specified file does not have to exist. If it does, however, it must be accessible.\n     *\n     * @param  file                  path to the user theme file.\n     * @throws FileNotFoundException if <code>file</code> is not accessible.\n     * @see                          #getUserThemeFile()\n     */\n    private static void setUserThemeFile(File file) throws FileNotFoundException {\n        setUserThemeFile(FileFactory.getFile(file.getAbsolutePath()));\n    }\n\n    /**\n     * Sets the path to the user theme file.\n     * <p>\n     * The specified file does not have to exist. If it does, however, it must be accessible.\n     *\n     * @param  file                     path to the user theme file.\n     * @throws IllegalArgumentException if <code>file</code> exists but is not accessible.\n     * @see                             #getUserThemeFile()\n     */\n    private static void setUserThemeFile(AbstractFile file) throws FileNotFoundException {\n        if (file.isBrowsable()) {\n            throw new FileNotFoundException(\"Not a valid file: \" + file.getAbsolutePath());\n        }\n        userThemeFile = file;\n    }\n\n    /**\n     * Sets the path to the user theme file.\n     * <p>\n     * The specified file does not have to exist. If it does, however, it must be accessible.\n     *\n     * @param  path                  path to the user theme file.\n     * @throws FileNotFoundException if <code>path</code> is not accessible.\n     * @see                          #getUserThemeFile()\n     */\n    private static void setUserThemeFile(String path) throws FileNotFoundException {\n        AbstractFile file = FileFactory.getFile(path);\n\n        if (file == null) {\n            setUserThemeFile(new File(path));\n        } else {\n            setUserThemeFile(file);\n        }\n    }\n\n    /**\n     * Returns the path to the custom themes' folder.\n     * <p>\n     * This method guarantees that the returned file actually exists.\n     *\n     * @return the path to the custom themes' folder.\n     * @throws IOException if an error occured while locating the default user themes folder.\n     */\n    public static AbstractFile getCustomThemesFolder() throws IOException {\n        // Retrieves the path to the custom themes folder and creates it if necessary.\n        AbstractFile customFolder = PlatformManager.getPreferencesFolder().getChild(CUSTOM_THEME_FOLDER);\n        if (!customFolder.exists()) {\n            customFolder.mkdir();\n        }\n\n        return customFolder;\n    }\n\n\n    // - Theme renaming / deleting -------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    public static void deleteCustomTheme(String name) throws IOException {\n        // Makes sure the specified theme is not the current one.\n        if (isCurrentTheme(Theme.Type.CUSTOM, name)) {\n            throw new IllegalArgumentException(\"Cannot delete current theme.\");\n        }\n\n        // Deletes the theme.\n        AbstractFile file = getFile(Type.CUSTOM, name);\n        if (file.exists()) {\n            file.delete();\n        }\n    }\n\n    public static void renameCustomTheme(Theme theme, String name) throws IOException {\n        if (theme.getType() != Theme.Type.CUSTOM) {\n            throw new IllegalArgumentException(\"Cannot rename non-custom themes.\");\n        }\n\n        // Makes sure the operation is necessary.\n        if (theme.getName().equals(name)) {\n            return;\n        }\n\n        // Computes a legal new name and renames theme.\n        name = getAvailableCustomThemeName(name);\n\t\tgetFile(Type.CUSTOM, theme.getName()).renameTo(getFile(Type.CUSTOM, name));\n        theme.setName(name);\n        if (isCurrentTheme(theme)) {\n            setConfigurationTheme(theme);\n        }\n    }\n\n\n\n    // - Theme writing -------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    /**\n     * Returns an output stream on the specified custom theme.\n     * @param  name        name of the custom theme on which to open an output stream.\n     * @return             an output stream on the specified custom theme.\n     * @throws IOException if an I/O related error occurs.\n     */\n    private static BackupOutputStream getCustomThemeOutputStream(String name) throws IOException {\n        return new BackupOutputStream(getCustomThemesFolder().getChild(name + \".xml\"));\n    }\n\n    /**\n     * Returns an output stream on the user theme.\n     * @return             an output stream on the user theme.\n     * @throws IOException if an I/O related error occurs.\n     */\n    private static BackupOutputStream getUserThemeOutputStream() throws IOException {\n        return new BackupOutputStream(getUserThemeFile());\n    }\n\n    /**\n\t * Returns the file to read/write the requested theme.\n\t * <p>\n\t * If <code>type</code> is equal to {@link Theme.Type#USER}, the <code>name</code> argument will be ignored: there\n\t * is only one user theme.\n\t *\n\t * If <code>type</code> is equal to {@link Theme.Type#PREDEFINED}, an <code>IllegalArgumentException</code> will be\n\t * thrown: predefined themes are not editable.\n\t *\n\t * @param type\n\t *            type of the theme for which to get the file.\n\t * @param name\n\t *            name of the theme for which to get the file.\n\t * @return a file for the requested theme.\n\t * @throws IllegalArgumentException\n\t *             if <code>type</code> is equal to {@link Theme.Type#PREDEFINED}.\n\t */\n\tpublic static AbstractFile getFile(Theme.Type type, String name) throws IOException {\n\t\tswitch (type) {\n            case PREDEFINED:\n                throw new IllegalArgumentException(\"Can not open output streams on predefined themes.\");\n\n            case CUSTOM:\n                return getCustomThemesFolder().getChild(name + \".xml\");\n\n            case USER:\n                return getUserThemeFile();\n            }\n\n\t\t// Unknown theme.\n\t\tthrow new IllegalArgumentException(\"Illegal theme type: \" + type);\n\t}\n\n    /**\n     * Returns an output stream on the requested theme.\n     * <p>\n     * This method is just a convenience, and wraps calls to {@link #getUserThemeInputStream()},\n     * and {@link #getCustomThemeInputStream(String)}.\n     *\n     * <p>\n     * If <code>type</code> is equal to {@link Theme.Type#USER}, the <code>name</code> argument\n     * will be ignored: there is only one user theme.\n     *\n     * <p>\n     * If <code>type</code> is equal to {@link Theme.Type#PREDEFINED}, an <code>IllegalArgumentException</code>\n     * will be thrown: predefined themes are not editable.\n     *\n     * @param  type        type of the theme on which to open an output stream.\n     * @param  name        name of the theme on which to open an output stream.\n     * @return             an output stream on the requested theme.\n     * @throws IOException if an I/O related error occurs.\n     */\n    private static BackupOutputStream getOutputStream(Theme.Type type, String name) throws IOException {\n        switch(type) {\n            case PREDEFINED:\n                throw new IllegalArgumentException(\"Can not open output streams on predefined themes.\");\n\n            case CUSTOM:\n                return getCustomThemeOutputStream(name);\n\n            case USER:\n                return getUserThemeOutputStream();\n        }\n\n        // Unknown theme.\n        throw new IllegalArgumentException(\"Illegal theme type: \" + type);\n    }\n\n    /**\n     * Writes the content of the specified theme data to the specified output stream.\n     * <p>\n     * This method differs from {@link #exportTheme(Theme,OutputStream)} in that it will\n     * write the theme data only, skipping comments and other metadata.\n     *\n     * @param  data        theme data to write.\n     * @param  out         where to write the theme data.\n     * @throws IOException if an I/O related error occurs.\n     * @see                #exportTheme(Theme,OutputStream)\n     * @see                #exportTheme(Theme,File)\n     * @see                #writeThemeData(ThemeData,File).\n     */\n    private static void writeThemeData(ThemeData data, OutputStream out) throws IOException {ThemeWriter.write(data, out);}\n\n    /**\n     * Writes the content of the specified theme data to the specified file.\n     * <p>\n     * This method differs from {@link #exportTheme(Theme,File)} in that it will\n     * write the theme data only, skipping comments and other metadata.\n     *\n     * @param  data        theme data to write.\n     * @param  file        file in which to write the theme data.\n     * @throws IOException if an I/O related error occurs.\n     * @see                #exportTheme(Theme,OutputStream)\n     * @see                #exportTheme(Theme,File)\n     * @see                #writeThemeData(ThemeData,OutputStream).\n     */\n    private static void writeThemeData(ThemeData data, File file) throws IOException {\n        try (OutputStream out = new FileOutputStream(file)) {\n            writeThemeData(data, out);\n        }\n\n    }\n\n    /**\n     * Writes the content of the specified theme to its description file.\n     * @param  theme                    theme to write.\n     * @throws IOException              if any I/O related error occurs.\n     * @throws IllegalArgumentException if <code>theme</code> is a predefined theme.\n     * @see                             #writeTheme(ThemeData,Theme.Type,String)\n     */\n    public static void writeTheme(Theme theme) throws IOException {writeTheme(theme, theme.getType(), theme.getName());}\n\n    /**\n     * Writes the specified theme data over the theme described by <code>type</code> and <code>name</code>.\n     * <p>\n     * Note that this method doesn't check whether this will overwrite an existing theme.\n     *\n     * <p>\n     * If <code>type</code> equals {@link Theme.Type#USER}, <code>name</code> will be ignored.\n     *\n     * @param  data                     data to write.\n     * @param  type                     type of the theme that is being written.\n     * @param  name                     name of the theme that is being written.\n     * @throws IOException              if any I/O related error occurs.\n     * @throws IllegalArgumentException if <code>theme</code> is a predefined theme.\n     * @see                             #writeTheme(Theme)\n     */\n    public static void writeTheme(ThemeData data, Theme.Type type, String name) throws IOException {\n        try (OutputStream out = getOutputStream(type, name)) {\n            writeThemeData(data, out);\n        }\n    }\n\n    /**\n     * Exports the specified theme to the specified output stream.\n     * <p>\n     * If <code>type</code> is equal to {@link Theme.Type#USER}, the <code>name</code> argument will be ignored\n     * as there is only one user theme.\n     *\n     * <p>\n     * This method differs from {@link #writeThemeData(ThemeData,OutputStream)} in that it doesn't only copy\n     * the theme's data, but the whole content of the theme file, including comments. It also requires the theme\n     * file to exist.\n     *\n     * @param  type        type of the theme to export.\n     * @param  name        name of the theme to export.\n     * @param  out         where to write the theme.\n     * @throws IOException if any I/O related error occurs.\n     * @see                #exportTheme(Theme.Type,String,File)\n     * @see                #writeThemeData(ThemeData,OutputStream)\n     */\n    private static void exportTheme(Theme.Type type, String name, OutputStream out) throws IOException {\n        try (InputStream in = getInputStream(type, name)) {\n            StreamUtils.copyStream(in, out);\n        }\n    }\n\n    /**\n     * Exports the specified theme to the specified output stream.\n     * <p>\n     * If <code>type</code> is equal to {@link Theme.Type#USER}, the <code>name</code> argument will be ignored\n     * as there is only one user theme.\n     *\n     * <p>\n     * This method differs from {@link #writeThemeData(ThemeData,File)} in that it doesn't only copy\n     * the theme's data, but the whole content of the theme file, including comments.\n     *\n     * @param  type        type of the theme to export.\n     * @param  name        name of the theme to export.\n     * @param  file        where to write the theme.\n     * @throws IOException if any I/O related error occurs\n     * @see                #exportTheme(Theme.Type,String,OutputStream)\n     * @see                #writeThemeData(ThemeData,File).\n     */\n    private static void exportTheme(Theme.Type type, String name, File file) throws IOException {\n        try (OutputStream out = new FileOutputStream(file)) {\n            exportTheme(type, name, out);\n        }\n    }\n\n    /**\n     * Exports the specified theme to the specified output stream.\n     * <p>\n     * This is a convenience method only and is strictly equivalent to calling\n     * <code>{@link #exportTheme(Theme.Type,String,OutputStream) exportTheme(}theme.getType(), theme.getName(), out);</code>\n     *\n     * @param  theme       theme to export.\n     * @param  out         where to write the theme.\n     * @throws IOException if any I/O related error occurs.\n     */\n    public static void exportTheme(Theme theme, OutputStream out) throws IOException {exportTheme(theme.getType(), theme.getName(), out);}\n\n    /**\n     * Exports the specified theme to the specified output stream.\n     * <p>\n     * This is a convenience method only and is strictly equivalent to calling\n     * <code>{@link #exportTheme(Theme.Type,String,File) exportTheme(}theme.getType(), theme.getName(), file);</code>\n     *\n     * @param  theme       theme to export.\n     * @param  file        where to write the theme.\n     * @throws IOException if any I/O related error occurs.\n     */\n    public static void exportTheme(Theme theme, File file) throws IOException {exportTheme(theme.getType(), theme.getName(), file);}\n\n    private static String getAvailableCustomThemeName(File file) {\n        String   name;\n\n        // Retrieves the file's name, cutting the .xml extension off if\n        // necessary.\n        if(StringUtils.endsWithIgnoreCase(name = file.getName(), \".xml\"))\n            name = name.substring(0, name.length() - 4);\n\n        return getAvailableCustomThemeName(name);\n    }\n\n    private static boolean isNameAvailable(String name, Iterator<String> names) {\n        while(names.hasNext())\n            if(names.next().equals(name))\n                return false;\n        return true;\n    }\n\n    private static String getAvailableCustomThemeName(String name) {\n        List<String> names = getAvailableThemeNames();\n\n        // If the name is available, no need to suffix it with (xx).\n        if(isNameAvailable(name, names.iterator()))\n            return name;\n\n        // Removes any trailing (x) construct, and adds a trailing space if necessary.\n        name = name.replaceFirst(\"\\\\([0-9]+\\\\)$\", \"\");\n        if (name.charAt(name.length() - 1) != ' ') {\n            name = name + ' ';\n        }\n\n        int i = 1;\n        String buffer;\n        do {\n            buffer = name + '(' + (++i) + ')';\n        } while(!isNameAvailable(buffer, names.iterator()));\n\n        return buffer;\n    }\n\n    public static Theme duplicateTheme(Theme theme) throws IOException {\n        return importTheme(theme.cloneData(), theme.getName());\n    }\n\n    public static Theme importTheme(ThemeData data, String name) throws IOException {\n        writeTheme(data, Theme.Type.CUSTOM, name = getAvailableCustomThemeName(name));\n        return new Theme(listener, data, Theme.Type.CUSTOM, name);\n    }\n\n    public static Theme importTheme(File file) throws Exception {\n        String       name; // Name of the new theme.\n        OutputStream out;  // Where to write the theme data to.\n        InputStream  in;   // Where to read the theme data from.\n        ThemeData    data;\n\n        // Makes sure the file contains a valid theme.\n        data = readThemeData(file);\n\n        // Initialisation.\n        name = getAvailableCustomThemeName(file);\n        out  = null;\n        in   = null;\n\n        // Imports the theme.\n        try {StreamUtils.copyStream(in = new FileInputStream(file), out = getCustomThemeOutputStream(name));}\n\n        // Cleanup.\n        finally {\n            if (in != null) {\n                try {\n                    in.close();\n                } catch(Exception e) {\n                    log.error(\"Can't close in file\", e);\n                }\n            }\n            if (out != null) {\n                try {\n                    out.close();\n                } catch(Exception e) {\n                    log.error(\"Can't close out file\", e);\n                }\n            }\n        }\n\n        return new Theme(listener, data, Theme.Type.CUSTOM, name);\n    }\n\n    /**\n     * Returns an input stream on the user theme.\n     * @return             an input stream on the user theme.\n     * @throws IOException if an I/O related error occurs.\n     */\n    private static InputStream getUserThemeInputStream() throws IOException {\n        return new BackupInputStream(getUserThemeFile());\n    }\n\n    /**\n     * Returns an input stream on the requested predefined theme.\n     * @param  name        name of the predefined theme on which to open an input stream.\n     * @return             an input stream on the requested predefined theme.\n     */\n    private static InputStream getPredefinedThemeInputStream(String name) {\n        return ResourceLoader.getResourceAsStream(RuntimeConstants.THEMES_PATH + \"/\" + name + \".xml\");\n    }\n\n    /**\n     * Returns an input stream on the requested predefined theme for editor.\n     * @param  name        name of the predefined editor theme on which to open an input stream.\n     * @return             an input stream on the requested predefined theme.\n     */\n    private static InputStream getPredefinedEditorThemeInputStream(String name) {\n        return ResourceLoader.getResourceAsStream(RuntimeConstants.TEXT_SYNTAX_THEMES_PATH + \"/\" + name + \".xml\");\n    }\n\n    /**\n     * Returns an input stream on the requested custom theme.\n     * @param  name        name of the custom theme on which to open an input stream.\n     * @return             an input stream on the requested custom theme.\n     * @throws IOException if an I/O related error occurs.\n     */\n    private static InputStream getCustomThemeInputStream(String name) throws IOException {\n\t\treturn new BackupInputStream(getFile(Type.CUSTOM, name));\n    }\n\n    /**\n     * Opens an input stream on the requested theme.\n     * <p>\n     * This method is just a convenience, and wraps calls to {@link #getUserThemeInputStream()},\n     * {@link #getPredefinedThemeInputStream(String)} and {@link #getCustomThemeInputStream(String)}.\n     *\n     * @param  type                     type of the theme to open an input stream on.\n     * @param  name                     name of the theme to open an input stream on.\n     * @return                          an input stream opened on the requested theme.\n     * @throws IOException              thrown if an IO related error occurs.\n     * @throws IllegalArgumentException thrown if <code>type</code> is not a legal theme type.\n     */\n    private static InputStream getInputStream(Theme.Type type, String name) throws IOException {\n        switch(type) {\n            case USER:\n                return getUserThemeInputStream();\n\n            case PREDEFINED:\n                return getPredefinedThemeInputStream(name);\n\n            case CUSTOM:\n                return getCustomThemeInputStream(name);\n        }\n\n        // Error handling.\n        throw new IllegalArgumentException(\"Illegal theme type: \" + type);\n    }\n\n    /**\n     * Returns the requested theme.\n     * @param  type type of theme to retrieve.\n     * @param  name name of the theme to retrieve.\n     * @return the requested theme.\n     */\n    private static Theme readTheme(Theme.Type type, String name) throws Exception {\n\n        // Do not reload the current theme, both for optimisation purposes and because\n        // it might cause user theme modifications to be lost.\n        if (currentTheme != null && isCurrentTheme(type, name))\n            return currentTheme;\n\n        // Reads the theme data.\n        try (InputStream in = getInputStream(type, name)) {\n            ThemeData data = readThemeData(in);\n            return new Theme(listener, data, type, name);\n        }\n    }\n\n    /**\n     * Return the requested theme for file viewer/editor\n     * @param name name ot the theme\n     * @return the resulting theme data.\n     */\n    public static EditorTheme readEditorTheme(String name) throws IOException {\n        InputStream is = getPredefinedEditorThemeInputStream(name);\n        return EditorTheme.load(is, ThemeManager.getCurrentFont(Theme.EDITOR_FONT));\n    }\n\n    /**\n     * Reads theme data from the specified input stream.\n     * @param  in        where to read the theme data from.\n     * @return           the resulting theme data.\n     * @throws IOException if an I/O or syntax error occurs.\n     */\n    private static ThemeData readThemeData(InputStream in) throws Exception {\n        ThemeData data = new ThemeData(); // Buffer for the data.\n\n        // Reads the theme data.\n        ThemeReader.read(in, data);\n\n        return data;\n    }\n\n    /**\n     * Reads theme data from the specified file.\n     * @param  file      where to read the theme data from.\n     * @return           the resulting theme data.\n     * @throws Exception if an I/O or syntax error occurs.\n     */\n    private static ThemeData readThemeData(File file) throws Exception {\n        try (InputStream in = new BufferedInputStream(new FileInputStream(file))) {\n            return readThemeData(in);\n        }\n    }\n\n\n\n    // - Current theme access ------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    private static void setConfigurationTheme(Theme.Type type, String name) {\n        // Sets configuration depending on the new theme's type.\n        switch(type) {\n            case USER:\n                TcConfigurations.getPreferences().setVariable(TcPreference.THEME_TYPE, TcPreferences.THEME_USER);\n                TcConfigurations.getPreferences().setVariable(TcPreference.THEME_NAME, null);\n                break;\n\n            case PREDEFINED:\n                TcConfigurations.getPreferences().setVariable(TcPreference.THEME_TYPE, TcPreferences.THEME_PREDEFINED);\n                TcConfigurations.getPreferences().setVariable(TcPreference.THEME_NAME, name);\n                break;\n\n            case CUSTOM:\n                TcConfigurations.getPreferences().setVariable(TcPreference.THEME_TYPE, TcPreferences.THEME_CUSTOM);\n                TcConfigurations.getPreferences().setVariable(TcPreference.THEME_NAME, name);\n                break;\n\n                // Error.\n            default:\n                throw new IllegalStateException(\"Illegal theme type: \" + type);\n            }\n    }\n\n    /**\n     * Sets the specified theme as the current theme in configuration.\n     * @param theme theme to set as current.\n     */\n    private static void setConfigurationTheme(Theme theme) {setConfigurationTheme(theme.getType(), theme.getName());}\n\n\n    /**\n     * Saves the current theme if necessary.\n     */\n    private static void saveCurrentTheme() throws IOException {\n        // Makes sure no NullPointerException is raised if this method is called\n        // before themes have been initialized.\n        if (currentTheme == null) {\n            return;\n        }\n\n        // Saves the user theme if it's the current one.\n        if (currentTheme.getType() == Theme.Type.USER && wasUserThemeModified) {\n            writeTheme(currentTheme);\n            wasUserThemeModified = false;\n        }\n    }\n\n    /**\n     * Changes the current theme.\n     * <p>\n     * This method will change the current theme and trigger all the proper events.\n     *\n     * @param  theme                    theme to use as the current theme.\n     * @throws IllegalArgumentException thrown if the specified theme could not be loaded.\n     */\n    public synchronized static void setCurrentTheme(Theme theme) {\n        // Makes sure we're not doing something useless.\n        if (isCurrentTheme(theme)) {\n            return;\n        }\n\n        // Saves the current theme if necessary.\n        try {\n            saveCurrentTheme();\n        } catch(IOException e) {\n            log.warn(\"Couldn't save current theme\", e);\n        }\n\n        // Updates muCommander's configuration.\n        Theme oldTheme = currentTheme;\n        setConfigurationTheme(currentTheme = theme);\n\n        // Triggers the events generated by the theme change.\n        triggerThemeChange(oldTheme, currentTheme);\n    }\n\n    /**\n     *\n     * @param name name of the theme\n     */\n    public synchronized static void setCurrentSyntaxTheme(String name) {\n        currentSyntaxThemeName = name;\n        TcConfigurations.getPreferences().setVariable(TcPreference.SYNTAX_THEME_NAME, name);\n    }\n\n    public synchronized static Font getCurrentFont(int id) {return currentTheme.getFont(id);}\n\n    public synchronized static Color getCurrentColor(int id) {return currentTheme.getColor(id);}\n\n    public synchronized static Theme overwriteUserTheme(ThemeData themeData) throws IOException {\n        // If the current theme is the user one, we just need to import the new data.\n        if (currentTheme.getType() == Theme.Type.USER) {\n            currentTheme.importData(themeData);\n            writeTheme(currentTheme);\n            return currentTheme;\n        } else {\n            writeTheme(themeData, Theme.Type.CUSTOM, currentTheme.getName());\n            return new Theme(listener, themeData);\n        }\n    }\n\n    /**\n     * Checks whether setting the specified font would require overwriting of the user theme.\n     * @param  fontId identifier of the font to set.\n     * @param  font   value for the specified font.\n     * @return        <code>true</code> if applying the specified font will overwrite the user theme,\n     *                <code>false</code> otherwise.\n     */\n    private synchronized static boolean willOverwriteUserTheme(int fontId, Font font) {\n        return currentTheme.isFontDifferent(fontId, font) && currentTheme.getType() != Theme.Type.USER;\n    }\n\n    /**\n     * Checks whether setting the specified color would require overwriting of the user theme.\n     * @param  colorId identifier of the color to set.\n     * @param  color   value for the specified color.\n     * @return         <code>true</code> if applying the specified color will overwrite the user theme,\n     *                 <code>false</code> otherwise.\n     */\n    private synchronized static boolean willOverwriteUserTheme(int colorId, Color color) {\n        return currentTheme.isColorDifferent(colorId, color) && currentTheme.getType() != Theme.Type.USER;\n    }\n\n    /**\n     * Updates the current theme with the specified font.\n     * <p>\n     * This method might require to overwrite the user theme: custom and predefined themes are\n     * read only. In order to modify them, the ThemeManager must overwrite the user theme with\n     * the current theme and then set the font.<br/>\n     * If necessary, this can be checked beforehand by a call to {@link #willOverwriteUserTheme(int,Font)}.\n     *\n     * @param  id   identifier of the font to set.\n     * @param  font font to set.\n     */\n    synchronized static boolean setCurrentFont(int id, Font font) {\n        // Only updates if necessary.\n        if (currentTheme.isFontDifferent(id, font)) {\n            // Checks whether we need to overwrite the user theme to perform this action.\n            if (currentTheme.getType() != Theme.Type.USER) {\n                currentTheme.setType(Theme.Type.USER);\n                setConfigurationTheme(currentTheme);\n            }\n\n            currentTheme.setFont(id, font);\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * Updates the current theme with the specified color.\n     * <p>\n     * This method might require to overwrite the user theme: custom and predefined themes are\n     * read only. In order to modify them, the ThemeManager must overwrite the user theme with\n     * the current theme and then set the color.<br/>\n     * If necessary, this can be checked beforehand by a call to {@link #willOverwriteUserTheme(int,Color)}.\n     *\n     * @param  id   identifier of the color to set.\n     * @param  color color to set.\n     */\n    synchronized static boolean setCurrentColor(int id, Color color) {\n        // Only updates if necessary.\n        if(currentTheme.isColorDifferent(id, color)) {\n            // Checks whether we need to overwrite the user theme to perform this action.\n            if(currentTheme.getType() != Theme.Type.USER) {\n                currentTheme.setType(Theme.Type.USER);\n                setConfigurationTheme(currentTheme);\n            }\n\n            // Updates the color and notifies listeners.\n            currentTheme.setColor(id, color);\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * Returns <code>true</code> if the specified theme is the current one.\n     * @param theme theme to check.\n     * @return <code>true</code> if the specified theme is the current one, <code>false</code> otherwise.\n     */\n    public static boolean isCurrentTheme(Theme theme) {return theme == currentTheme;}\n\n    private static boolean isCurrentTheme(Theme.Type type, String name) {\n        return type == currentTheme.getType() && (type == Type.USER || name.equals(currentTheme.getName()));\n    }\n\n\n\n\n    // - Events management ---------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    /**\n     * Notifies all listeners that the current theme has changed.\n     * <p>\n     * This method is meant to be called when the current theme has been changed.\n     * It will compare all fonts and colors in <code>oldTheme</code> and <code>newTheme</code> and,\n     * if any is found to be different, trigger the corresponding event.\n     *\n     * <p>\n     * At the end of this method, all registered listeners will have been made aware of the new values\n     * they should be using.\n     *\n     * @param oldTheme previous current theme.\n     * @param newTheme new current theme.\n     * @see            #triggerFontEvent(FontChangedEvent)\n     * @see            #triggerColorEvent(ColorChangedEvent)\n     */\n    private static void triggerThemeChange(Theme oldTheme, Theme newTheme) {\n        // Triggers font events.\n        for(int i = 0; i < Theme.FONT_COUNT; i++)\n            if(oldTheme.isFontDifferent(i, newTheme.getFont(i)))\n                triggerFontEvent(new FontChangedEvent(currentTheme, i, newTheme.getFont(i)));\n\n        // Triggers color events.\n        for(int i = 0; i < Theme.COLOR_COUNT; i++)\n            if(oldTheme.isColorDifferent(i, newTheme.getColor(i)))\n                triggerColorEvent(new ColorChangedEvent(currentTheme, i, newTheme.getColor(i)));\n    }\n\n    /**\n     * Adds the specified object to the list of registered current theme listeners.\n     * <p>\n     * Any object registered through this method will received {@link ThemeListener#colorChanged(ColorChangedEvent) color}\n     * and {@link ThemeListener#fontChanged(FontChangedEvent) font} events whenever the current theme changes.\n     *\n     * <p>\n     * Note that these events will not necessarily be fired as a result of a direct theme change: if, for example,\n     * the current theme is using look&amp;feel dependant values and the current look&amp;feel changes, the corresponding\n     * events will be passed to registered listeners.\n     *\n     * <p>\n     * Listeners are stored as weak references, to make sure that the API doesn't keep ghost copies of objects\n     * whose usefulness is long since past. This forces callers to make sure they keep a copy of the listener's instance: if\n     * they do not, the instance will be weakly linked and garbage collected out of existence.\n     *\n     * @param listener new current theme listener.\n     */\n    public static void addCurrentThemeListener(ThemeListener listener) {\n        synchronized (listeners) {\n            listeners.put(listener, null);\n        }\n    }\n\n    /**\n     * Removes the specified object from the list of registered theme listeners.\n     * <p>\n     * Note that since listeners are stored as weak references, calling this method is not strictly necessary. As soon\n     * as a listener instance is not referenced anymore, it will automatically be caught and destroyed by the garbage\n     * collector.\n     *\n     * @param listener current theme listener to remove.\n     */\n    public static void removeCurrentThemeListener(ThemeListener listener) {\n        synchronized (listeners) {\n            listeners.remove(listener);\n        }\n    }\n\n    /**\n     * Notifies all theme listeners of the specified font event.\n     * @param event event to pass down to registered listeners.\n     * @see         #triggerThemeChange(Theme,Theme)\n     */\n    private static void triggerFontEvent(FontChangedEvent event) {\n        synchronized (listeners) {\n            for (ThemeListener listener : listeners.keySet()) {\n                listener.fontChanged(event);\n            }\n        }\n    }\n\n    /**\n     * Notifies all theme listeners of the specified color event.\n     * @param event event to pass down to registered listeners.\n     * @see         #triggerThemeChange(Theme,Theme)\n     */\n    private static void triggerColorEvent(ColorChangedEvent event) {\n        synchronized (listeners) {\n            for (ThemeListener listener : listeners.keySet()) {\n                listener.colorChanged(event);\n            }\n        }\n    }\n\n\n\n    // - Helper methods ------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    /**\n     * Returns a valid type identifier from the specified configuration type definition.\n     * @param  label label of the theme type as defined in {@link TcPreferences}.\n     * @return       a valid theme type identifier.\n     */\n    private static Theme.Type getThemeTypeFromLabel(String label) {\n        switch (label) {\n            case TcPreferences.THEME_USER:\n                return Theme.Type.USER;\n            case TcPreferences.THEME_PREDEFINED:\n                return Theme.Type.PREDEFINED;\n            case TcPreferences.THEME_CUSTOM:\n                return Theme.Type.CUSTOM;\n        }\n        throw new IllegalStateException(\"Unknown theme type: \" + label);\n    }\n\n    private static String getThemeName(AbstractFile themeFile) {\n        return themeFile.getNameWithoutExtension();\n    }\n\n\n\n    // - Listener methods ----------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    /**\n     * @author Nicolas Rinaudo\n     */\n    private static class CurrentThemeListener implements ThemeListener {\n        public void fontChanged(FontChangedEvent event) {\n            if (event.getSource().getType() == Theme.Type.USER) {\n                wasUserThemeModified = true;\n            }\n\n            if (event.getSource() == currentTheme) {\n                triggerFontEvent(event);\n            }\n        }\n\n        public void colorChanged(ColorChangedEvent event) {\n            if (event.getSource().getType() == Theme.Type.USER) {\n                wasUserThemeModified = true;\n            }\n\n            if (event.getSource() == currentTheme) {\n                triggerColorEvent(event);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/theme/ThemeReader.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.theme;\r\n\r\nimport java.awt.Color;\r\nimport java.awt.Font;\r\nimport java.awt.GraphicsEnvironment;\r\nimport java.io.InputStream;\r\nimport java.util.StringTokenizer;\r\n\r\nimport javax.xml.parsers.SAXParserFactory;\r\n\r\nimport org.intellij.lang.annotations.MagicConstant;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.xml.sax.Attributes;\r\nimport org.xml.sax.helpers.DefaultHandler;\r\n\r\n\r\n/**\r\n * Loads theme instances from properly formatted XML files.\r\n * @author Nicolas Rinaudo\r\n */\r\nclass ThemeReader extends DefaultHandler implements ThemeXmlConstants, ThemeId {\r\n\tprivate static Logger logger;\r\n\r\n\t// TODO !!! add previous state to enum insteadof terrible if-else list\r\n    private enum State {\r\n    /** Parsing hasn't started yet. */\r\n        UNKNOWN,\r\n    /** Parsing the root element. */\r\n        ROOT,\r\n    /** Parsing the table element.*/\r\n        TABLE,\r\n    /** Parsing the shell element. */\r\n        SHELL,\r\n    /** Parsing the editor element. */\r\n        EDITOR,\r\n    /** Parsing the location bar element. */\r\n        LOCATION_BAR,\r\n    /** Parsing the shell.normal element. */\r\n        SHELL_NORMAL,\r\n    /** Parsing the shell.selected element. */\r\n        SHELL_SELECTED,\r\n    /** Parsing the editor.normal element. */\r\n        EDITOR_NORMAL,\r\n    /** Parsing the location bar.normal element. */\r\n        LOCATION_BAR_NORMAL,\r\n    /** Parsing the editor.selected element. */\r\n        EDITOR_SELECTED,\r\n    /** Parsing the location bar.selected element. */\r\n        LOCATION_BAR_SELECTED,\r\n    /** Parsing the shell_history element. */\r\n        SHELL_HISTORY,\r\n    /** Parsing the shell_history.normal element. */\r\n        SHELL_HISTORY_NORMAL,\r\n    /** Parsing the shell_history.selected element. */\r\n        SHELL_HISTORY_SELECTED,\r\n    /** Parsing the volume_label element. */\r\n        STATUS_BAR,\r\n        HIDDEN_FILE,\r\n        HIDDEN_FILE_NORMAL,\r\n        HIDDEN_FILE_SELECTED,\r\n        HIDDEN_FOLDER,\r\n        HIDDEN_FOLDER_NORMAL,\r\n        HIDDEN_FOLDER_SELECTED,\r\n        FOLDER,\r\n        FOLDER_NORMAL,\r\n        FOLDER_SELECTED,\r\n        ARCHIVE,\r\n        ARCHIVE_NORMAL,\r\n        ARCHIVE_SELECTED,\r\n        SYMLINK,\r\n        SYMLINK_NORMAL,\r\n        SYMLINK_SELECTED,\r\n        MARKED,\r\n        MARKED_NORMAL,\r\n        MARKED_SELECTED,\r\n        EXECUTABLE,\r\n        EXECUTABLE_NORMAL,\r\n        EXECUTABLE_SELECTED,\r\n        FILE,\r\n        FILE_NORMAL,\r\n        FILE_SELECTED,\r\n        TABLE_NORMAL,\r\n        TABLE_SELECTED,\r\n        TABLE_ALTERNATE,\r\n        TABLE_UNMATCHED,\r\n    /** Parsing the quick list element. */\r\n        QUICK_LIST,\r\n    /** Parsing the quick list header element. */\r\n        QUICK_LIST_HEADER,\r\n    /** Parsing the quick list item element. */\r\n        QUICK_LIST_ITEM,\r\n        QUICK_LIST_ITEM_NORMAL,\r\n        QUICK_LIST_ITEM_SELECTED,\r\n    /** Parsing the editor.selected element. */\r\n        EDITOR_CURRENT,\r\n        FILE_GROUP,\r\n        GROUP_1,\r\n        GROUP_2,\r\n        GROUP_3,\r\n        GROUP_4,\r\n        GROUP_5,\r\n        GROUP_6,\r\n        GROUP_7,\r\n        GROUP_8,\r\n        GROUP_9,\r\n        GROUP_10,\r\n\r\n        TERMINAL,\r\n        /** Parsing the terminal.normal element. */\r\n        TERMINAL_NORMAL,\r\n        /** Parsing the terminal.selected element. */\r\n        TERMINAL_SELECTED,\r\n\r\n        HEX_VIEWER,\r\n        HEX_VIEWER_NORMAL,\r\n        HEX_VIEWER_SELECTED,\r\n\r\n    }\r\n\r\n    /** Theme template that is currently being built. */\r\n    private final ThemeData template;\r\n    /** Current state of the XML parser. */\r\n    private State state;\r\n    /** Used to ignore the content of an unknown tag. */\r\n    private String unknownElement;\r\n\r\n\r\n\r\n    /**\r\n     * Creates a new theme reader.\r\n     */\r\n    private ThemeReader(ThemeData t) {\r\n        template = t;\r\n        state = State.UNKNOWN;\r\n    }\r\n\r\n    /**\r\n     * Attempts to read a theme from the specified input stream.\r\n     * @param     in        where to read the theme from.\r\n     * @param     template  template in which to store the data.\r\n     * @exception Exception thrown if an error occured while reading the template.\r\n     */\r\n    public static void read(InputStream in, ThemeData template) throws Exception {\r\n        SAXParserFactory.newInstance().newSAXParser().parse(in, new ThemeReader(template));\r\n    }\r\n\r\n\r\n    // - XML interaction -----------------------------------------------------\r\n    // -----------------------------------------------------------------------\r\n    /**\r\n     * Notifies the reader that a new XML element is starting.\r\n     */\r\n    @Override\r\n    public void startElement(String uri, String localName, String qName, Attributes attributes) {\r\n        // Ignores the content of unknown elements.\r\n        if (unknownElement != null) {\r\n            getLogger().debug(\"Ignoring element \" + qName);\r\n            return;\r\n        }\r\n\r\n        // Normal element declaration.\r\n        if (ELEMENT_NORMAL.equals(qName)) {\r\n            if (state == State.SHELL) {\r\n                state = State.SHELL_NORMAL;\r\n            } else if (state == State.EDITOR) {\r\n                state = State.EDITOR_NORMAL;\r\n            } else if (state == State.LOCATION_BAR) {\r\n                state = State.LOCATION_BAR_NORMAL;\r\n            } else if (state == State.SHELL_HISTORY) {\r\n                state = State.SHELL_HISTORY_NORMAL;\r\n            } else if (state == State.TERMINAL) {\r\n                state = State.TERMINAL_NORMAL;\r\n            } else if (state == State.HIDDEN_FILE) {\r\n                state = State.HIDDEN_FILE_NORMAL;\r\n            } else if (state == State.HIDDEN_FOLDER) {\r\n                state = State.HIDDEN_FOLDER_NORMAL;\r\n            } else if (state == State.FOLDER) {\r\n                state = State.FOLDER_NORMAL;\r\n            } else if (state == State.ARCHIVE) {\r\n                state = State.ARCHIVE_NORMAL;\r\n            } else if (state == State.SYMLINK) {\r\n                state = State.SYMLINK_NORMAL;\r\n            } else if (state == State.MARKED) {\r\n                state = State.MARKED_NORMAL;\r\n            } else if (state == State.EXECUTABLE) {\r\n                state = State.EXECUTABLE_NORMAL;\r\n            } else if (state == State.FILE) {\r\n                state = State.FILE_NORMAL;\r\n            } else if (state == State.TABLE) {\r\n                state = State.TABLE_NORMAL;\r\n            } else if (state == State.QUICK_LIST_ITEM) {\r\n                state = State.QUICK_LIST_ITEM_NORMAL;\r\n            } else if (state.ordinal() >= State.GROUP_1.ordinal() && state.ordinal() <= State.GROUP_10.ordinal()) {\r\n                int group = state.ordinal() - State.GROUP_1.ordinal();\r\n                setColor(FILE_GROUP_1_FOREGROUND_COLOR + group, attributes);\r\n            } else if (state == State.HEX_VIEWER) {\r\n                state = State.HEX_VIEWER_NORMAL;\r\n            } else {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n        }\r\n\r\n        // Background color.\r\n        else if (ELEMENT_BACKGROUND.equals(qName)) {\r\n            if (state == State.TABLE_NORMAL) {\r\n                setColor(FILE_TABLE_BACKGROUND_COLOR, attributes);\r\n            } else if (state == State.TABLE_SELECTED) {\r\n                setColor(FILE_TABLE_SELECTED_BACKGROUND_COLOR, attributes);\r\n            } else if (state == State.TABLE_ALTERNATE) {\r\n                setColor(FILE_TABLE_ALTERNATE_BACKGROUND_COLOR, attributes);\r\n            } else if (state == State.TABLE_UNMATCHED) {\r\n                setColor(FILE_TABLE_UNMATCHED_BACKGROUND_COLOR, attributes);\r\n            } else if (state == State.SHELL_NORMAL) {\r\n                setColor(SHELL_BACKGROUND_COLOR, attributes);\r\n            } else if (state == State.SHELL_SELECTED) {\r\n                setColor(SHELL_SELECTED_BACKGROUND_COLOR, attributes);\r\n            } else if (state == State.EDITOR_NORMAL) {\r\n                setColor(EDITOR_BACKGROUND_COLOR, attributes);\r\n            } else if (state == State.EDITOR_SELECTED) {\r\n                setColor(EDITOR_SELECTED_BACKGROUND_COLOR, attributes);\r\n            } else if (state == State.EDITOR_CURRENT) {\r\n                setColor(EDITOR_CURRENT_BACKGROUND_COLOR, attributes);\r\n            } else if (state == State.LOCATION_BAR_NORMAL) {\r\n                setColor(LOCATION_BAR_BACKGROUND_COLOR, attributes);\r\n            } else if (state == State.LOCATION_BAR_SELECTED) {\r\n                setColor(LOCATION_BAR_SELECTED_BACKGROUND_COLOR, attributes);\r\n            } else if (state == State.SHELL_HISTORY_NORMAL) {\r\n                setColor(SHELL_HISTORY_BACKGROUND_COLOR, attributes);\r\n            } else if (state == State.SHELL_HISTORY_SELECTED) {\r\n                setColor(SHELL_HISTORY_SELECTED_BACKGROUND_COLOR, attributes);\r\n            } else if (state == State.TERMINAL_NORMAL) {\r\n                setColor(TERMINAL_BACKGROUND_COLOR, attributes);\r\n            } else if (state == State.TERMINAL_SELECTED) {\r\n                setColor(TERMINAL_SELECTED_BACKGROUND_COLOR, attributes);\r\n            } else if (state == State.STATUS_BAR) {\r\n                setColor(STATUS_BAR_BACKGROUND_COLOR, attributes);\r\n            } else if (state == State.QUICK_LIST_HEADER) {\r\n                setColor(QUICK_LIST_HEADER_BACKGROUND_COLOR, attributes);\r\n            } else if (state == State.QUICK_LIST_ITEM_NORMAL) {\r\n                setColor(QUICK_LIST_ITEM_BACKGROUND_COLOR, attributes);\r\n            } else if (state == State.QUICK_LIST_ITEM_SELECTED) {\r\n                setColor(QUICK_LIST_SELECTED_ITEM_BACKGROUND_COLOR, attributes);\r\n            } else if (state == State.HEX_VIEWER_NORMAL) {\r\n                setColor(HEX_VIEWER_BACKGROUND_COLOR, attributes);\r\n            } else if (state == State.HEX_VIEWER_SELECTED) {\r\n                setColor(HEX_VIEWER_SELECTED_BACKGROUND_COLOR, attributes);\r\n            } else {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n        }\r\n\r\n        // Selected element declaration.\r\n        else if (ELEMENT_SELECTED.equals(qName)) {\r\n            if (state == State.SHELL) {\r\n                state = State.SHELL_SELECTED;\r\n            } else if (state == State.EDITOR) {\r\n                state = State.EDITOR_SELECTED;\r\n            } else if (state == State.LOCATION_BAR) {\r\n                state = State.LOCATION_BAR_SELECTED;\r\n            } else if (state == State.SHELL_HISTORY) {\r\n                state = State.SHELL_HISTORY_SELECTED;\r\n            } else if (state == State.TERMINAL) {\r\n                state = State.TERMINAL_SELECTED;\r\n            } else if (state == State.HIDDEN_FILE) {\r\n                state = State.HIDDEN_FILE_SELECTED;\r\n            } else if (state == State.HIDDEN_FOLDER) {\r\n                state = State.HIDDEN_FOLDER_SELECTED;\r\n            } else if (state == State.FOLDER) {\r\n                state = State.FOLDER_SELECTED;\r\n            } else if (state == State.ARCHIVE) {\r\n                state = State.ARCHIVE_SELECTED;\r\n            } else if (state == State.SYMLINK) {\r\n                state = State.SYMLINK_SELECTED;\r\n            } else if (state == State.MARKED) {\r\n                state = State.MARKED_SELECTED;\r\n            } else if (state == State.EXECUTABLE) {\r\n                state = State.EXECUTABLE_SELECTED;\r\n            } else if (state == State.FILE) {\r\n                state = State.FILE_SELECTED;\r\n            } else if (state == State.TABLE) {\r\n                state = State.TABLE_SELECTED;\r\n            } else if (state == State.QUICK_LIST_ITEM) {\r\n                state = State.QUICK_LIST_ITEM_SELECTED;\r\n            } else if (state == State.HEX_VIEWER) {\r\n                state = State.HEX_VIEWER_SELECTED;\r\n            } else {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n        }\r\n\r\n        // Unfocused foreground color.\r\n        else if (ELEMENT_INACTIVE_FOREGROUND.equals(qName)) {\r\n            if (state == State.FILE_NORMAL) {\r\n                setColor(FILE_INACTIVE_FOREGROUND_COLOR, attributes);\r\n            } else if(state == State.FOLDER_NORMAL) {\r\n                setColor(FOLDER_INACTIVE_FOREGROUND_COLOR, attributes);\r\n            } else if(state == State.ARCHIVE_NORMAL) {\r\n                setColor(ARCHIVE_INACTIVE_FOREGROUND_COLOR, attributes);\r\n            } else if(state == State.SYMLINK_NORMAL) {\r\n                setColor(SYMLINK_INACTIVE_FOREGROUND_COLOR, attributes);\r\n            } else if(state == State.HIDDEN_FILE_NORMAL) {\r\n                setColor(HIDDEN_FILE_INACTIVE_FOREGROUND_COLOR, attributes);\r\n            } else if(state == State.HIDDEN_FOLDER_NORMAL) {\r\n                setColor(HIDDEN_FOLDER_INACTIVE_FOREGROUND_COLOR, attributes);\r\n            } else if(state == State.MARKED_NORMAL) {\r\n                setColor(MARKED_INACTIVE_FOREGROUND_COLOR, attributes);\r\n            } else if(state == State.EXECUTABLE_NORMAL) {\r\n                setColor(EXECUTABLE_INACTIVE_FOREGROUND_COLOR, attributes);\r\n            } else if(state == State.FILE_SELECTED) {\r\n                setColor(FILE_INACTIVE_SELECTED_FOREGROUND_COLOR, attributes);\r\n            } else if(state == State.FOLDER_SELECTED) {\r\n                setColor(FOLDER_INACTIVE_SELECTED_FOREGROUND_COLOR, attributes);\r\n            } else if(state == State.ARCHIVE_SELECTED) {\r\n                setColor(ARCHIVE_INACTIVE_SELECTED_FOREGROUND_COLOR, attributes);\r\n            } else if(state == State.SYMLINK_SELECTED) {\r\n                setColor(SYMLINK_INACTIVE_SELECTED_FOREGROUND_COLOR, attributes);\r\n            } else if(state == State.HIDDEN_FOLDER_SELECTED) {\r\n                setColor(HIDDEN_FOLDER_INACTIVE_SELECTED_FOREGROUND_COLOR, attributes);\r\n            } else if(state == State.HIDDEN_FILE_SELECTED) {\r\n                setColor(HIDDEN_FILE_INACTIVE_SELECTED_FOREGROUND_COLOR, attributes);\r\n            } else if(state == State.MARKED_SELECTED) {\r\n                setColor(MARKED_INACTIVE_SELECTED_FOREGROUND_COLOR, attributes);\r\n            } else if(state == State.EXECUTABLE_SELECTED) {\r\n                setColor(EXECUTABLE_INACTIVE_SELECTED_FOREGROUND_COLOR, attributes);\r\n            } else {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n        }\r\n\r\n        // Font creation.\r\n        else if (ELEMENT_FONT.equals(qName)) {\r\n            if (state == State.SHELL) {\r\n                setFont(SHELL_FONT, attributes);\r\n            } else if (state == State.EDITOR) {\r\n                setFont(EDITOR_FONT, attributes);\r\n            } else if (state == State.LOCATION_BAR) {\r\n                setFont(LOCATION_BAR_FONT, attributes);\r\n            } else if (state == State.SHELL_HISTORY) {\r\n                setFont(SHELL_HISTORY_FONT, attributes);\r\n            } else if (state == State.TERMINAL) {\r\n                setFont(TERMINAL_FONT, attributes);\r\n            } else if (state == State.STATUS_BAR) {\r\n                setFont(STATUS_BAR_FONT, attributes);\r\n            } else if (state == State.TABLE) {\r\n                setFont(FILE_TABLE_FONT, attributes);\r\n            } else if (state == State.QUICK_LIST_HEADER) {\r\n                setFont(QUICK_LIST_HEADER_FONT, attributes);\r\n            } else if (state == State.QUICK_LIST_ITEM) {\r\n                setFont(QUICK_LIST_ITEM_FONT, attributes);\r\n            } else if (state == State.HEX_VIEWER) {\r\n                setFont(ThemeId.HEX_VIEWER_FONT, attributes);\r\n            } else {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n        }\r\n\r\n        // XML root element.\r\n        else if (ELEMENT_ROOT.equals(qName)) {\r\n            if (state != State.UNKNOWN) {\r\n                traceIllegalDeclaration(ELEMENT_ROOT);\r\n            }\r\n            state = State.ROOT;\r\n        }\r\n\r\n        // File table declaration.\r\n        else if (ELEMENT_TABLE.equals(qName)) {\r\n            if (state != State.ROOT) {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n            state = State.TABLE;\r\n        }\r\n\r\n        else if (ELEMENT_FILE_GROUPS.equals(qName)) {\r\n            if (state != State.ROOT) {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n            state = State.FILE_GROUP;\r\n        }\r\n\r\n        // Shell declaration.\r\n        else if (ELEMENT_SHELL.equals(qName)) {\r\n            if (state != State.ROOT) {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n            state = State.SHELL;\r\n        }\r\n\r\n        // Editor declaration.\r\n        else if (ELEMENT_EDITOR.equals(qName)) {\r\n            if (state != State.ROOT) {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n            state = State.EDITOR;\r\n        }\r\n\r\n        // Location bar declaration.\r\n        else if (ELEMENT_LOCATION_BAR.equals(qName)) {\r\n            if (state != State.ROOT) {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n            state = State.LOCATION_BAR;\r\n        }\r\n        \r\n        // Quick list declaration.\r\n        else if (ELEMENT_QUICK_LIST.equals(qName)) {\r\n            if (state != State.ROOT) {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n            state = State.QUICK_LIST;\r\n        }\r\n\r\n        // Shell history declaration.\r\n        else if (ELEMENT_SHELL_HISTORY.equals(qName)) {\r\n            if (state != State.ROOT) {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n            state = State.SHELL_HISTORY;\r\n        }\r\n\r\n        else if (ELEMENT_TERMINAL.equals(qName)) {\r\n            if (state != State.ROOT) {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n            state = State.TERMINAL;\r\n        }\r\n\r\n        // Volume label declaration.\r\n        else if (ELEMENT_STATUS_BAR.equals(qName)) {\r\n            if (state != State.ROOT) {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n            state = State.STATUS_BAR;\r\n        }\r\n\r\n        else if (ELEMENT_HIDDEN_FILE.equals(qName)) {\r\n            if (state != State.TABLE) {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n            state = State.HIDDEN_FILE;\r\n        }\r\n\r\n        else if (ELEMENT_HIDDEN_FOLDER.equals(qName)) {\r\n            if (state != State.TABLE) {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n            state = State.HIDDEN_FOLDER;\r\n        }\r\n\r\n        else if (ELEMENT_FOLDER.equals(qName)) {\r\n            if (state != State.TABLE) {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n            state = State.FOLDER;\r\n        }\r\n\r\n        else if (ELEMENT_ARCHIVE.equals(qName)) {\r\n            if (state != State.TABLE) {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n            state = State.ARCHIVE;\r\n        }\r\n\r\n        else if (ELEMENT_SYMLINK.equals(qName)) {\r\n            if (state != State.TABLE) {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n            state = State.SYMLINK;\r\n        }\r\n\r\n        else if (ELEMENT_MARKED.equals(qName)) {\r\n            if (state != State.TABLE) {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n            state = State.MARKED;\r\n        }\r\n\r\n        else if (ELEMENT_EXECUTABLE.equals(qName)) {\r\n            if (state != State.TABLE) {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n            state = State.EXECUTABLE;\r\n        }\r\n\r\n        else if (ELEMENT_FILE.equals(qName)) {\r\n            if (state != State.TABLE) {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n            state = State.FILE;\r\n        }\r\n\r\n        else if (ELEMENT_ALTERNATE.equals(qName)) {\r\n            if (state != State.TABLE) {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n            state = State.TABLE_ALTERNATE;\r\n        }\r\n        \r\n        // Header declaration.\r\n        else if (ELEMENT_HEADER.equals(qName)) {\r\n        \tif (state == State.QUICK_LIST) {\r\n                state = State.QUICK_LIST_HEADER;\r\n            } else {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n        }\r\n        \r\n        // Item declaration.\r\n        else if (ELEMENT_ITEM.equals(qName)) {\r\n        \tif (state == State.QUICK_LIST) {\r\n                state = State.QUICK_LIST_ITEM;\r\n            } else {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n        }\r\n\r\n        else if(ELEMENT_TERMINAL.equals(qName)) {\r\n            if (state != State.ROOT) {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n            state = State.TERMINAL;\r\n        }\r\n\r\n\r\n        // Current element declaration.\r\n        else if (ELEMENT_CURRENT.equals(qName)) {\r\n            if (state == State.EDITOR) {\r\n                state = State.EDITOR_CURRENT;\r\n            } else {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n        }\r\n\r\n\r\n        // Unfocused background color.\r\n        else if (ELEMENT_INACTIVE_BACKGROUND.equals(qName)) {\r\n            if (state == State.TABLE_NORMAL) {\r\n                setColor(FILE_TABLE_INACTIVE_BACKGROUND_COLOR, attributes);\r\n            } else {\r\n                if (state == State.TABLE_SELECTED) {\r\n                    setColor(FILE_TABLE_INACTIVE_SELECTED_BACKGROUND_COLOR, attributes);\r\n                } else if (state == State.TABLE_ALTERNATE) {\r\n                    setColor(FILE_TABLE_INACTIVE_ALTERNATE_BACKGROUND_COLOR, attributes);\r\n                } else {\r\n                    traceIllegalDeclaration(qName);\r\n                }\r\n            }\r\n        }\r\n\r\n        // Secondary background.\r\n        else if (ELEMENT_SECONDARY_BACKGROUND.equals(qName)) {\r\n            if (state == State.TABLE_SELECTED) {\r\n                setColor(FILE_TABLE_SELECTED_SECONDARY_BACKGROUND_COLOR, attributes);\r\n            } else if (state == State.HEX_VIEWER_NORMAL) {\r\n                setColor(HEX_VIEWER_ALTERNATE_BACKGROUND_COLOR, attributes);\r\n            } else {\r\n                if (state == State.QUICK_LIST_HEADER) {\r\n                    setColor(QUICK_LIST_HEADER_SECONDARY_BACKGROUND_COLOR, attributes);\r\n                } else {\r\n                    traceIllegalDeclaration(qName);\r\n                }\r\n            }\r\n        }\r\n\r\n        // Inactive secondary background.\r\n        else if (ELEMENT_INACTIVE_SECONDARY_BACKGROUND.equals(qName)) {\r\n            if (state == State.TABLE_SELECTED) {\r\n                setColor(FILE_TABLE_INACTIVE_SELECTED_SECONDARY_BACKGROUND_COLOR, attributes);\r\n            } else {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n        }\r\n\r\n        // File table border color.\r\n        else if (ELEMENT_BORDER.equals(qName)) {\r\n            if (state == State.TABLE) {\r\n                setColor(FILE_TABLE_BORDER_COLOR, attributes);\r\n            } else {\r\n                if (state == State.STATUS_BAR) {\r\n                    setColor(STATUS_BAR_BORDER_COLOR, attributes);\r\n                } else {\r\n                    traceIllegalDeclaration(qName);\r\n                }\r\n            }\r\n        }\r\n\r\n        // File table inactive border color.\r\n        else if (ELEMENT_INACTIVE_BORDER.equals(qName)) {\r\n            if (state == State.TABLE) {\r\n                setColor(FILE_TABLE_INACTIVE_BORDER_COLOR, attributes);\r\n            } else {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n        }\r\n\r\n        // File table outline color.\r\n        else if (ELEMENT_OUTLINE.equals(qName)) {\r\n            if (state == State.TABLE) {\r\n                setColor(FILE_TABLE_SELECTED_OUTLINE_COLOR, attributes);\r\n            } else {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n        }\r\n\r\n        // File table inactive outline color.\r\n        else if (ELEMENT_INACTIVE_OUTLINE.equals(qName)) {\r\n            if (state == State.TABLE) {\r\n                setColor(FILE_TABLE_INACTIVE_SELECTED_OUTLINE_COLOR, attributes);\r\n            } else {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n        }\r\n\r\n        // Unmatched file table.\r\n        else if (ELEMENT_UNMATCHED.equals(qName)) {\r\n            if (state != State.TABLE) {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n            state = State.TABLE_UNMATCHED;\r\n        }\r\n\r\n        // Progress bar color.\r\n        else if (ELEMENT_PROGRESS.equals(qName)) {\r\n            if (state == State.LOCATION_BAR) {\r\n                setColor(LOCATION_BAR_PROGRESS_COLOR, attributes);\r\n            } else {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n        }\r\n\r\n        // 'OK' color.\r\n        else if (ELEMENT_OK.equals(qName)) {\r\n            if (state == State.STATUS_BAR) {\r\n                setColor(STATUS_BAR_OK_COLOR, attributes);\r\n            } else {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n        }\r\n\r\n        // 'WARNING' color.\r\n        else if (ELEMENT_WARNING.equals(qName)) {\r\n            if (state == State.STATUS_BAR) {\r\n                setColor(STATUS_BAR_WARNING_COLOR, attributes);\r\n            } else {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n        }\r\n\r\n        // 'CRITICAL' color.\r\n        else if (ELEMENT_CRITICAL.equals(qName)) {\r\n            if (state == State.STATUS_BAR) {\r\n                setColor(STATUS_BAR_CRITICAL_COLOR, attributes);\r\n            } else {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n        }\r\n\r\n        // Text color.\r\n        else if (ELEMENT_FOREGROUND.equals(qName)) {\r\n            if (state == State.HIDDEN_FILE_NORMAL) {\r\n                setColor(HIDDEN_FILE_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.HIDDEN_FILE_SELECTED) {\r\n                setColor(HIDDEN_FILE_SELECTED_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.HIDDEN_FOLDER_NORMAL) {\r\n                setColor(HIDDEN_FOLDER_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.HIDDEN_FOLDER_SELECTED) {\r\n                setColor(HIDDEN_FOLDER_SELECTED_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.TABLE_UNMATCHED) {\r\n                setColor(FILE_TABLE_UNMATCHED_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.FOLDER_NORMAL) {\r\n                setColor(FOLDER_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.FOLDER_SELECTED) {\r\n                setColor(FOLDER_SELECTED_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.ARCHIVE_NORMAL) {\r\n                setColor(ARCHIVE_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.ARCHIVE_SELECTED) {\r\n                setColor(ARCHIVE_SELECTED_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.SYMLINK_NORMAL) {\r\n                setColor(SYMLINK_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.SYMLINK_SELECTED) {\r\n                setColor(SYMLINK_SELECTED_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.MARKED_NORMAL) {\r\n                setColor(MARKED_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.MARKED_SELECTED) {\r\n                setColor(MARKED_SELECTED_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.EXECUTABLE_NORMAL) {\r\n                setColor(EXECUTABLE_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.EXECUTABLE_SELECTED) {\r\n                setColor(EXECUTABLE_SELECTED_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.FILE_NORMAL) {\r\n                setColor(FILE_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.FILE_SELECTED) {\r\n                setColor(FILE_SELECTED_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.SHELL_NORMAL) {\r\n                setColor(SHELL_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.SHELL_SELECTED) {\r\n                setColor(SHELL_SELECTED_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.SHELL_HISTORY_NORMAL) {\r\n                setColor(SHELL_HISTORY_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.SHELL_HISTORY_SELECTED) {\r\n                setColor(SHELL_HISTORY_SELECTED_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.TERMINAL_NORMAL) {\r\n                setColor(TERMINAL_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.TERMINAL_SELECTED) {\r\n                setColor(TERMINAL_SELECTED_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.EDITOR_NORMAL) {\r\n                setColor(EDITOR_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.EDITOR_SELECTED) {\r\n                setColor(EDITOR_SELECTED_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.LOCATION_BAR_NORMAL) {\r\n                setColor(LOCATION_BAR_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.LOCATION_BAR_SELECTED) {\r\n                setColor(LOCATION_BAR_SELECTED_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.STATUS_BAR) {\r\n                setColor(STATUS_BAR_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.QUICK_LIST_HEADER) {\r\n                setColor(QUICK_LIST_HEADER_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.QUICK_LIST_ITEM_NORMAL) {\r\n                setColor(QUICK_LIST_ITEM_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.QUICK_LIST_ITEM_SELECTED) {\r\n                setColor(QUICK_LIST_SELECTED_ITEM_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.HEX_VIEWER_NORMAL) {\r\n                setColor(HEX_VIEWER_HEX_FOREGROUND_COLOR, attributes);\r\n            } else if (state == State.HEX_VIEWER_SELECTED) {\r\n                setColor(HEX_VIEWER_SELECTED_DUMP_FOREGROUND_COLOR, attributes);\r\n            } else {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n        }\r\n\r\n        else if (qName.startsWith(ELEMENT_GROUP)) {\r\n            if (state != State.FILE_GROUP) {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n            String groupStr = qName.substring(ELEMENT_GROUP.length());\r\n\r\n            state = State.values()[State.GROUP_1.ordinal() + Integer.parseInt(groupStr) - 1];\r\n\r\n        } else if (qName.equals(ELEMENT_HEX_VIEWER)) {\r\n            state = State.HEX_VIEWER;\r\n\r\n        } else if (ELEMENT_ASCII_FOREGROUND.equals(qName)) {\r\n            if (state == State.HEX_VIEWER_NORMAL) {\r\n                setColor(HEX_VIEWER_ASCII_FOREGROUND_COLOR, attributes);\r\n            } else {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n\r\n        } else if (ELEMENT_ASCII_BACKGROUND.equals(qName)) {\r\n            if (state == State.HEX_VIEWER_SELECTED) {\r\n                setColor(HEX_VIEWER_SELECTED_ASCII_BACKGROUND_COLOR, attributes);\r\n            } else {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n        }\r\n\r\n        else if (qName.equals(ELEMENT_OFFSET_FOREGROUND)) {\r\n            if (state == State.HEX_VIEWER_NORMAL) {\r\n                setColor(HEX_VIEWER_OFFSET_FOREGROUND_COLOR, attributes);\r\n            } else {\r\n                traceIllegalDeclaration(qName);\r\n            }\r\n        }\r\n\r\n        else {\r\n            traceIllegalDeclaration(qName);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Notifies the reader that the current element declaration is over.\r\n     */\r\n    @Override\r\n    public void endElement(String uri, String localName, String qName) {\r\n        // If we're in an unknown element....\r\n        if (unknownElement != null) {\r\n            // If it just closed, resume normal parsing.\r\n            if (qName.equals(unknownElement)) {\r\n                unknownElement = null;\r\n            } else {\r\n                return; // Ignores all other tags.\r\n            }\r\n        }\r\n\r\n        // XML root element.\r\n        if (ELEMENT_ROOT.equals(qName)) {\r\n            state = State.UNKNOWN;\r\n        }// File table declaration.\r\n        else if (ELEMENT_TABLE.equals(qName)) {\r\n            state = State.ROOT;\r\n        } else if (ELEMENT_ALTERNATE.equals(qName)) {\r\n            state = State.TABLE;\r\n        } else if (ELEMENT_UNMATCHED.equals(qName)) {\r\n            state = State.TABLE;\r\n        } else if (qName.equals(ELEMENT_HIDDEN_FILE)) {\r\n            state = State.TABLE;\r\n        } else if (qName.equals(ELEMENT_HIDDEN_FOLDER)) {\r\n            state = State.TABLE;\r\n        } else if (ELEMENT_FOLDER.equals(qName)) {\r\n            state = State.TABLE;\r\n        } else if (ELEMENT_ARCHIVE.equals(qName)) {\r\n            state = State.TABLE;\r\n        } else if (ELEMENT_SYMLINK.equals(qName)) {\r\n            state = State.TABLE;\r\n        } else if (ELEMENT_MARKED.equals(qName)) {\r\n            state = State.TABLE;\r\n        } else if (ELEMENT_EXECUTABLE.equals(qName)) {\r\n            state = State.TABLE;\r\n        } else if (ELEMENT_FILE.equals(qName)) {\r\n            state = State.TABLE;\r\n        } else if (ELEMENT_SHELL.equals(qName)) {\r\n            state = State.ROOT;\r\n        } else if (ELEMENT_SHELL_HISTORY.equals(qName)) {\r\n            state = State.ROOT;\r\n        } else if (ELEMENT_TERMINAL.equals(qName)) {\r\n            state = State.ROOT;\r\n        } else if (ELEMENT_EDITOR.equals(qName)) {\r\n            state = State.ROOT;\r\n        } else if (ELEMENT_LOCATION_BAR.equals(qName)) {\r\n            state = State.ROOT;\r\n        } else if (ELEMENT_QUICK_LIST.equals(qName)) {\r\n            state = State.ROOT;\r\n        } else if (ELEMENT_STATUS_BAR.equals(qName)) {\r\n            state = State.ROOT;\r\n        } else if (ELEMENT_HEADER.equals(qName)) {\r\n        \tif (state == State.QUICK_LIST_HEADER) {\r\n                state = State.QUICK_LIST;\r\n            }\r\n        } else if (ELEMENT_ITEM.equals(qName)) {\r\n        \tif (state == State.QUICK_LIST_ITEM) {\r\n                state = State.QUICK_LIST;\r\n            }\r\n        } else if (ELEMENT_NORMAL.equals(qName)) {\r\n            if (state == State.SHELL_NORMAL) {\r\n                state = State.SHELL;\r\n            } else if (state == State.SHELL_HISTORY_NORMAL) {\r\n                state = State.SHELL_HISTORY;\r\n            } else if (state == State.TERMINAL_NORMAL) {\r\n                state = State.TERMINAL;\r\n            } else if (state == State.HIDDEN_FILE_NORMAL) {\r\n                state = State.HIDDEN_FILE;\r\n            } else if (state == State.HIDDEN_FOLDER_NORMAL) {\r\n                state = State.HIDDEN_FOLDER;\r\n            } else if (state == State.FOLDER_NORMAL) {\r\n                state = State.FOLDER;\r\n            } else if (state == State.ARCHIVE_NORMAL) {\r\n                state = State.ARCHIVE;\r\n            } else if (state == State.SYMLINK_NORMAL) {\r\n                state = State.SYMLINK;\r\n            } else if (state == State.MARKED_NORMAL) {\r\n                state = State.MARKED;\r\n            } else if (state == State.EXECUTABLE_NORMAL) {\r\n                state = State.EXECUTABLE;\r\n            } else if (state == State.FILE_NORMAL) {\r\n                state = State.FILE;\r\n            } else if (state == State.EDITOR_NORMAL) {\r\n                state = State.EDITOR;\r\n            } else if (state == State.LOCATION_BAR_NORMAL) {\r\n                state = State.LOCATION_BAR;\r\n            } else if (state == State.TABLE_NORMAL) {\r\n                state = State.TABLE;\r\n            } else if (state == State.QUICK_LIST_ITEM_NORMAL) {\r\n                state = State.QUICK_LIST_ITEM;\r\n            } else if (state == State.HEX_VIEWER_NORMAL) {\r\n                state = State.HEX_VIEWER;\r\n            }\r\n        }\r\n\r\n        // Selected element declaration.\r\n        else if (ELEMENT_SELECTED.equals(qName)) {\r\n            if (state == State.SHELL_SELECTED) {\r\n                state = State.SHELL;\r\n            } else if (state == State.SHELL_HISTORY_SELECTED) {\r\n                state = State.SHELL_HISTORY;\r\n            } else if (state == State.TERMINAL_SELECTED) {\r\n                state = State.TERMINAL;\r\n            } else if (state == State.HIDDEN_FILE_SELECTED) {\r\n                state = State.HIDDEN_FILE;\r\n            } else if (state == State.HIDDEN_FOLDER_SELECTED) {\r\n                state = State.HIDDEN_FOLDER;\r\n            } else if (state == State.FOLDER_SELECTED) {\r\n                state = State.FOLDER;\r\n            } else if (state == State.ARCHIVE_SELECTED) {\r\n                state = State.ARCHIVE;\r\n            } else if (state == State.SYMLINK_SELECTED) {\r\n                state = State.SYMLINK;\r\n            } else if (state == State.MARKED_SELECTED) {\r\n                state = State.MARKED;\r\n            } else if (state == State.EXECUTABLE_SELECTED) {\r\n                state = State.EXECUTABLE;\r\n            } else if (state == State.FILE_SELECTED) {\r\n                state = State.FILE;\r\n            } else if (state == State.EDITOR_SELECTED) {\r\n                state = State.EDITOR;\r\n            } else if (state == State.LOCATION_BAR_SELECTED) {\r\n                state = State.LOCATION_BAR;\r\n            } else if (state == State.TABLE_SELECTED) {\r\n                state = State.TABLE;\r\n            } else if (state == State.QUICK_LIST_ITEM_SELECTED) {\r\n                state = State.QUICK_LIST_ITEM;\r\n            } else if (state == State.HEX_VIEWER_SELECTED) {\r\n                state = State.HEX_VIEWER;\r\n            }\r\n        } else if (qName.startsWith(ELEMENT_GROUP)) {\r\n            state = State.FILE_GROUP;\r\n        } else if (ELEMENT_HEX_VIEWER.equals(qName)) {\r\n            state = State.ROOT;\r\n        } else if (ELEMENT_FILE_GROUPS.equals(qName)) {\r\n            state = State.ROOT;\r\n        }\r\n    }\r\n\r\n\r\n\r\n    // - Helper methods ------------------------------------------------------\r\n    // -----------------------------------------------------------------------\r\n    /**\r\n     * Checks whether the specified font is available on the system.\r\n     * @param  font name of the font to check for.\r\n     * @return <code>true</code> if the font is available, <code>false</code> otherwise.\r\n     */\r\n    private static boolean isFontAvailable(String font)  {\r\n        // Looks for the specified font.\r\n        // TODO very slow operation (for first execution) !!!!\r\n        String[] availableFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();\r\n\r\n        for (String availableFont : availableFonts) {\r\n            if (availableFont.equalsIgnoreCase(font)) {\r\n                return true;\r\n            }\r\n        }\r\n        // Font doesn't exist on the system.\r\n        return false;\r\n    }\r\n\r\n    /**\r\n     * Creates a font from the specified XML attributes.\r\n     * <p>\r\n     * Ignored attributes will be set to their default values.\r\n     *\r\n     * @param  attributes XML attributes describing the font to use.\r\n     * @return            the resulting Font instance.\r\n     */\r\n    private static Font createFont(Attributes attributes) {\r\n        // Computes the font style.\r\n        int style = getFontStyle(attributes);\r\n\r\n        // Computes the font size.\r\n        String buffer = attributes.getValue(ATTRIBUTE_SIZE);\r\n        if (buffer == null) {\r\n            getLogger().debug(\"Missing font size attribute in theme, ignoring.\");\r\n            return null;\r\n\t    }\r\n        int size = Integer.parseInt(buffer);\r\n\r\n        // Computes the font family.\r\n        buffer = attributes.getValue(ATTRIBUTE_FAMILY);\r\n        if (buffer == null) {\r\n            getLogger().debug(\"Missing font family attribute in theme, ignoring.\");\r\n            return null;\r\n        }\r\n\r\n        // Looks through the list of declared fonts to find one that is installed on the system.\r\n        StringTokenizer parser = new StringTokenizer(buffer, \",\");\r\n        while (parser.hasMoreTokens()) {\r\n            buffer = parser.nextToken().trim();\r\n\r\n            // Font was found, use it.\r\n            if (isFontAvailable(buffer)) {\r\n                return new Font(buffer, style, size);\r\n            }\r\n        }\r\n\r\n        // No font was found, instructs the ThemeManager to use the system default.\r\n        getLogger().debug(\"Requested font families are not installed on the system, using default.\");\r\n        return null;\r\n    }\r\n\r\n    @MagicConstant(flags = {Font.BOLD, Font.ITALIC})\r\n    private static int getFontStyle(Attributes attributes) {\r\n        int style = 0;\r\n        String buffer = attributes.getValue(ATTRIBUTE_BOLD);\r\n        if (VALUE_TRUE.equals(buffer)) {\r\n            style |= Font.BOLD;\r\n        }\r\n        buffer = attributes.getValue(ATTRIBUTE_ITALIC);\r\n        if (VALUE_TRUE.equals(buffer)) {\r\n            style |= Font.ITALIC;\r\n        }\r\n        return style;\r\n    }\r\n\r\n    /**\r\n     * Creates a color from the specified XML attributes.\r\n     * @param  attributes XML attributes describing the font to use.\r\n     * @return            the resulting Color instance.\r\n     */\r\n    private static Color createColor(Attributes attributes) {\r\n        String buffer = attributes.getValue(ATTRIBUTE_COLOR);\r\n\r\n        // Retrieves the color attribute's value.\r\n        if (buffer == null) {\r\n            getLogger().debug(\"Missing color attribute in theme, ignoring.\");\r\n            return null;\r\n        }\r\n        int color = Integer.parseInt(buffer, 16);\r\n\r\n        // Retrieves the transparency attribute's value..\r\n        buffer = attributes.getValue(ATTRIBUTE_ALPHA);\r\n        if (buffer == null) {\r\n            return new Color(color);\r\n        }\r\n        return new Color(color | (Integer.parseInt(buffer, 16) << 24), true);\r\n    }\r\n\r\n    /**\r\n     * Set color helper method\r\n     * @param id color id\r\n     * @param attributes xml attributes\r\n     */\r\n    private void setColor(int id, Attributes attributes) {\r\n        template.setColorFast(id, createColor(attributes));\r\n    }\r\n\r\n    /**\r\n     * Set font helper method\r\n     * @param id font id\r\n     * @param attributes xml attributes\r\n     */\r\n    private void setFont(int id, Attributes attributes) {\r\n        template.setFontFast(id, createFont(attributes));\r\n    }\r\n\r\n\r\n    // - Error generation methods --------------------------------------------\r\n    // -----------------------------------------------------------------------\r\n    private void traceIllegalDeclaration(String element) {\r\n        System.out.println(\"Unexpected start of element \" + element + \" for state \" + state + \", ignoring.\");\r\n        new Exception().printStackTrace();\r\n        unknownElement = element;\r\n        getLogger().debug(\"Unexpected start of element \" + element + \" for state \" + state + \", ignoring.\");\r\n    }\r\n\r\n\r\n    private static Logger getLogger() {\r\n        if (logger == null) {\r\n            logger = LoggerFactory.getLogger(ThemeReader.class);\r\n        }\r\n        return logger;\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/theme/ThemeWriter.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.theme;\n\nimport com.mucommander.utils.xml.XmlAttributes;\nimport com.mucommander.utils.xml.XmlWriter;\n\nimport java.awt.Color;\nimport java.awt.Font;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * Class used to save themes in XML format.\n * @author Nicolas Rinaudo\n */\nclass ThemeWriter implements ThemeXmlConstants, ThemeId {\n    private ThemeWriter() {}\n\n\n    /**\n     * Saves the specified theme to the specified output stream.\n     * @param  theme       theme to save.\n     * @param  stream      where to write the theme to.\n     * @throws IOException thrown if any IO related error occurs.\n     */\n    public static void write(ThemeData theme, OutputStream stream) throws IOException {\n        XmlWriter out = new XmlWriter(stream);\n        out.startElement(ELEMENT_ROOT);\n        out.println();\n\n        // - File table description ------------------------------------------------------\n        // -------------------------------------------------------------------------------\n        out.startElement(ELEMENT_TABLE);\n        out.println();\n\n        // Global values.\n        writeColor(theme, out, FILE_TABLE_BORDER_COLOR, ELEMENT_BORDER);\n        writeColor(theme, out, FILE_TABLE_INACTIVE_BORDER_COLOR, ELEMENT_INACTIVE_BORDER);\n        writeColor(theme, out, FILE_TABLE_SELECTED_OUTLINE_COLOR, ELEMENT_OUTLINE);\n        writeColor(theme, out, FILE_TABLE_INACTIVE_SELECTED_OUTLINE_COLOR, ELEMENT_INACTIVE_OUTLINE);\n        writeFont(theme, out, FILE_TABLE_FONT, ELEMENT_FONT);\n\n        // Normal background colors.\n        out.startElement(ELEMENT_NORMAL);\n        out.println();\n        writeColor(theme, out, FILE_TABLE_BACKGROUND_COLOR, ELEMENT_BACKGROUND);\n        writeColor(theme, out, FILE_TABLE_INACTIVE_BACKGROUND_COLOR, ELEMENT_INACTIVE_BACKGROUND);\n        out.endElement(ELEMENT_NORMAL);\n\n        // Selected background colors.\n        out.startElement(ELEMENT_SELECTED);\n        out.println();\n        writeColor(theme, out, FILE_TABLE_SELECTED_BACKGROUND_COLOR, ELEMENT_BACKGROUND);\n        writeColor(theme, out, FILE_TABLE_INACTIVE_SELECTED_BACKGROUND_COLOR, ELEMENT_INACTIVE_BACKGROUND);\n        writeColor(theme, out, FILE_TABLE_INACTIVE_SELECTED_SECONDARY_BACKGROUND_COLOR, ELEMENT_INACTIVE_SECONDARY_BACKGROUND);\n        writeColor(theme, out, FILE_TABLE_SELECTED_SECONDARY_BACKGROUND_COLOR, ELEMENT_SECONDARY_BACKGROUND);\n\n        out.endElement(ELEMENT_SELECTED);\n\n        // Alternate background colors.\n        out.startElement(ELEMENT_ALTERNATE);\n        out.println();\n        writeColor(theme, out, FILE_TABLE_ALTERNATE_BACKGROUND_COLOR, ELEMENT_BACKGROUND);\n        writeColor(theme, out, FILE_TABLE_INACTIVE_ALTERNATE_BACKGROUND_COLOR, ELEMENT_INACTIVE_BACKGROUND);\n        out.endElement(ELEMENT_ALTERNATE);\n\n        // Unmatched colors.\n        out.startElement(ELEMENT_UNMATCHED);\n        out.println();\n        writeColor(theme, out, FILE_TABLE_UNMATCHED_BACKGROUND_COLOR, ELEMENT_BACKGROUND);\n        writeColor(theme, out, FILE_TABLE_UNMATCHED_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_UNMATCHED);\n\n        // Hidden folders.\n        out.startElement(ELEMENT_HIDDEN_FOLDER);\n        out.println();\n        out.startElement(ELEMENT_NORMAL);\n        out.println();\n        writeColor(theme, out, HIDDEN_FOLDER_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        writeColor(theme, out, HIDDEN_FOLDER_INACTIVE_FOREGROUND_COLOR, ELEMENT_INACTIVE_FOREGROUND);\n        out.endElement(ELEMENT_NORMAL);\n        out.startElement(ELEMENT_SELECTED);\n        out.println();\n        writeColor(theme, out, HIDDEN_FOLDER_SELECTED_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        writeColor(theme, out, HIDDEN_FOLDER_INACTIVE_SELECTED_FOREGROUND_COLOR, ELEMENT_INACTIVE_FOREGROUND);\n        out.endElement(ELEMENT_SELECTED);\n        out.endElement(ELEMENT_HIDDEN_FOLDER);\n\n        // Hidden files.\n        out.startElement(ELEMENT_HIDDEN_FILE);\n        out.println();\n        out.startElement(ELEMENT_NORMAL);\n        out.println();\n        writeColor(theme, out, HIDDEN_FILE_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        writeColor(theme, out, HIDDEN_FILE_INACTIVE_FOREGROUND_COLOR, ELEMENT_INACTIVE_FOREGROUND);\n        out.endElement(ELEMENT_NORMAL);\n        out.startElement(ELEMENT_SELECTED);\n        out.println();\n        writeColor(theme, out, HIDDEN_FILE_SELECTED_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        writeColor(theme, out, HIDDEN_FILE_INACTIVE_SELECTED_FOREGROUND_COLOR, ELEMENT_INACTIVE_FOREGROUND);\n        out.endElement(ELEMENT_SELECTED);\n        out.endElement(ELEMENT_HIDDEN_FILE);\n\n        // Folders.\n        out.startElement(ELEMENT_FOLDER);\n        out.println();\n        out.startElement(ELEMENT_NORMAL);\n        out.println();\n        writeColor(theme, out, FOLDER_INACTIVE_FOREGROUND_COLOR, ELEMENT_INACTIVE_FOREGROUND);\n        writeColor(theme, out, FOLDER_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_NORMAL);\n        out.startElement(ELEMENT_SELECTED);\n        out.println();\n        writeColor(theme, out, FOLDER_INACTIVE_SELECTED_FOREGROUND_COLOR, ELEMENT_INACTIVE_FOREGROUND);\n        writeColor(theme, out, FOLDER_SELECTED_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_SELECTED);\n        out.endElement(ELEMENT_FOLDER);\n\n        // Archives.\n        out.startElement(ELEMENT_ARCHIVE);\n        out.println();\n        out.startElement(ELEMENT_NORMAL);\n        out.println();\n        writeColor(theme, out, ARCHIVE_INACTIVE_FOREGROUND_COLOR, ELEMENT_INACTIVE_FOREGROUND);\n        writeColor(theme, out, ARCHIVE_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_NORMAL);\n        out.startElement(ELEMENT_SELECTED);\n        out.println();\n        writeColor(theme, out, ARCHIVE_INACTIVE_SELECTED_FOREGROUND_COLOR, ELEMENT_INACTIVE_FOREGROUND);\n        writeColor(theme, out, ARCHIVE_SELECTED_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_SELECTED);\n        out.endElement(ELEMENT_ARCHIVE);\n\n        // Symlink.\n        out.startElement(ELEMENT_SYMLINK);\n        out.println();\n        out.startElement(ELEMENT_NORMAL);\n        out.println();\n        writeColor(theme, out, SYMLINK_INACTIVE_FOREGROUND_COLOR, ELEMENT_INACTIVE_FOREGROUND);\n        writeColor(theme, out, SYMLINK_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_NORMAL);\n        out.startElement(ELEMENT_SELECTED);\n        out.println();\n        writeColor(theme, out, SYMLINK_INACTIVE_SELECTED_FOREGROUND_COLOR, ELEMENT_INACTIVE_FOREGROUND);\n        writeColor(theme, out, SYMLINK_SELECTED_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_SELECTED);\n        out.endElement(ELEMENT_SYMLINK);\n\n        // Marked files.\n        out.startElement(ELEMENT_MARKED);\n        out.println();\n        out.startElement(ELEMENT_NORMAL);\n        out.println();\n        writeColor(theme, out, MARKED_INACTIVE_FOREGROUND_COLOR, ELEMENT_INACTIVE_FOREGROUND);\n        writeColor(theme, out, MARKED_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_NORMAL);\n        out.startElement(ELEMENT_SELECTED);\n        out.println();\n        writeColor(theme, out, MARKED_INACTIVE_SELECTED_FOREGROUND_COLOR, ELEMENT_INACTIVE_FOREGROUND);\n        writeColor(theme, out, MARKED_SELECTED_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_SELECTED);\n        out.endElement(ELEMENT_MARKED);\n\n        // Executable files.\n        out.startElement(ELEMENT_EXECUTABLE);\n        out.println();\n        out.startElement(ELEMENT_NORMAL);\n        out.println();\n        writeColor(theme, out, EXECUTABLE_INACTIVE_FOREGROUND_COLOR, ELEMENT_INACTIVE_FOREGROUND);\n        writeColor(theme, out, EXECUTABLE_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_NORMAL);\n        out.startElement(ELEMENT_SELECTED);\n        out.println();\n        writeColor(theme, out, EXECUTABLE_INACTIVE_SELECTED_FOREGROUND_COLOR, ELEMENT_INACTIVE_FOREGROUND);\n        writeColor(theme, out, EXECUTABLE_SELECTED_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_SELECTED);\n        out.endElement(ELEMENT_EXECUTABLE);\n\n        // Plain files.\n        out.startElement(ELEMENT_FILE);\n        out.println();\n        out.startElement(ELEMENT_NORMAL);\n        out.println();\n        writeColor(theme, out, FILE_INACTIVE_FOREGROUND_COLOR, ELEMENT_INACTIVE_FOREGROUND);\n        writeColor(theme, out, FILE_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_NORMAL);\n        out.startElement(ELEMENT_SELECTED);\n        out.println();\n        writeColor(theme, out, FILE_INACTIVE_SELECTED_FOREGROUND_COLOR, ELEMENT_INACTIVE_FOREGROUND);\n        writeColor(theme, out, FILE_SELECTED_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_SELECTED);\n        out.endElement(ELEMENT_FILE);\n        out.endElement(ELEMENT_TABLE);\n\n        // File groups\n        out.startElement(ELEMENT_FILE_GROUPS);\n        out.println();\n        for (int i = 0; i < 10; i++) {\n            out.startElement(ELEMENT_GROUP + (i+1));\n            out.println();\n            writeColor(theme, out, FILE_GROUP_1_FOREGROUND_COLOR+i, ELEMENT_NORMAL);\n            out.endElement(ELEMENT_GROUP + (i+1));\n        }\n        out.endElement(ELEMENT_FILE_GROUPS);\n\n\n        // - Shell description ----------------------------------------------------------\n        // -------------------------------------------------------------------------------\n        out.startElement(ELEMENT_SHELL);\n        out.println();\n        writeFont(theme, out, SHELL_FONT, ELEMENT_FONT);\n\n        // Normal colors.\n        out.startElement(ELEMENT_NORMAL);\n        out.println();\n        writeColor(theme, out, SHELL_BACKGROUND_COLOR, ELEMENT_BACKGROUND);\n        writeColor(theme, out, SHELL_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_NORMAL);\n\n        // Selected colors.\n        out.startElement(ELEMENT_SELECTED);\n        out.println();\n        writeColor(theme, out, SHELL_SELECTED_BACKGROUND_COLOR, ELEMENT_BACKGROUND);\n        writeColor(theme, out, SHELL_SELECTED_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_SELECTED);\n        out.endElement(ELEMENT_SHELL);\n\n\n        // - Shell history description ---------------------------------------------------\n        // -------------------------------------------------------------------------------\n        out.startElement(ELEMENT_SHELL_HISTORY);\n        out.println();\n        writeFont(theme, out, SHELL_HISTORY_FONT, ELEMENT_FONT);\n\n        // Normal colors.\n        out.startElement(ELEMENT_NORMAL);\n        out.println();\n        writeColor(theme, out, SHELL_HISTORY_BACKGROUND_COLOR, ELEMENT_BACKGROUND);\n        writeColor(theme, out, SHELL_HISTORY_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_NORMAL);\n\n        // Selected colors.\n        out.startElement(ELEMENT_SELECTED);\n        out.println();\n        writeColor(theme, out, SHELL_HISTORY_SELECTED_BACKGROUND_COLOR, ELEMENT_BACKGROUND);\n        writeColor(theme, out, SHELL_HISTORY_SELECTED_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_SELECTED);\n        out.endElement(ELEMENT_SHELL_HISTORY);\n\n        // - Terminal description ---------------------------------------------------\n        // -------------------------------------------------------------------------------\n        out.startElement(ELEMENT_TERMINAL);\n        out.println();\n        writeFont(theme, out, TERMINAL_FONT, ELEMENT_FONT);\n\n        // Normal colors.\n        out.startElement(ELEMENT_NORMAL);\n        out.println();\n        writeColor(theme, out, TERMINAL_BACKGROUND_COLOR, ELEMENT_BACKGROUND);\n        writeColor(theme, out, TERMINAL_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_NORMAL);\n\n        // Selected colors.\n        out.startElement(ELEMENT_SELECTED);\n        out.println();\n        writeColor(theme, out, TERMINAL_SELECTED_BACKGROUND_COLOR, ELEMENT_BACKGROUND);\n        writeColor(theme, out, TERMINAL_SELECTED_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_SELECTED);\n        out.endElement(ELEMENT_TERMINAL);\n\n\n        // - Editor description ----------------------------------------------------------\n        // -------------------------------------------------------------------------------\n        out.startElement(ELEMENT_EDITOR);\n        out.println();\n        writeFont(theme, out, EDITOR_FONT, ELEMENT_FONT);\n\n        // Normal colors.\n        out.startElement(ELEMENT_NORMAL);\n        out.println();\n        writeColor(theme, out, EDITOR_BACKGROUND_COLOR, ELEMENT_BACKGROUND);\n        writeColor(theme, out, EDITOR_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_NORMAL);\n\n        // Selected colors.\n        out.startElement(ELEMENT_SELECTED);\n        out.println();\n        writeColor(theme, out, EDITOR_SELECTED_BACKGROUND_COLOR, ELEMENT_BACKGROUND);\n        writeColor(theme, out, EDITOR_SELECTED_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_SELECTED);\n\n        // Current line\n        out.startElement(ELEMENT_CURRENT);\n        out.println();\n        writeColor(theme, out, EDITOR_CURRENT_BACKGROUND_COLOR, ELEMENT_BACKGROUND);\n        out.endElement(ELEMENT_CURRENT);\n        out.endElement(ELEMENT_EDITOR);\n\n        // - Hex viewer description ------------------------------------------------------\n        // -------------------------------------------------------------------------------\n        out.startElement(ELEMENT_HEX_VIEWER);\n        out.println();\n        writeFont(theme, out, HEX_VIEWER_FONT, ELEMENT_FONT);\n\n        // Normal colors\n        out.startElement(ELEMENT_NORMAL);\n        out.println();\n        writeColor(theme, out, HEX_VIEWER_BACKGROUND_COLOR, ELEMENT_BACKGROUND);\n        writeColor(theme, out, HEX_VIEWER_HEX_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        writeColor(theme, out, HEX_VIEWER_ALTERNATE_BACKGROUND_COLOR, ELEMENT_SECONDARY_BACKGROUND);\n        writeColor(theme, out, HEX_VIEWER_ASCII_FOREGROUND_COLOR, ELEMENT_ASCII_FOREGROUND);\n        writeColor(theme, out, HEX_VIEWER_OFFSET_FOREGROUND_COLOR, ELEMENT_OFFSET_FOREGROUND);\n        out.endElement(ELEMENT_NORMAL);\n\n        // Selected colors\n        out.startElement(ELEMENT_SELECTED);\n        out.println();\n        writeColor(theme, out, HEX_VIEWER_SELECTED_BACKGROUND_COLOR, ELEMENT_BACKGROUND);\n        writeColor(theme, out, HEX_VIEWER_SELECTED_DUMP_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        writeColor(theme, out, HEX_VIEWER_SELECTED_ASCII_BACKGROUND_COLOR, ELEMENT_ASCII_BACKGROUND);\n        out.endElement(ELEMENT_SELECTED);\n\n        out.endElement(ELEMENT_HEX_VIEWER);\n\n        // - Location bar description ----------------------------------------------------\n        // -------------------------------------------------------------------------------\n        out.startElement(ELEMENT_LOCATION_BAR);\n        out.println();\n        writeFont(theme, out, LOCATION_BAR_FONT, ELEMENT_FONT);\n        writeColor(theme, out, LOCATION_BAR_PROGRESS_COLOR, ELEMENT_PROGRESS);\n\n        // Normal colors.\n        out.startElement(ELEMENT_NORMAL);\n        out.println();\n        writeColor(theme, out, LOCATION_BAR_BACKGROUND_COLOR, ELEMENT_BACKGROUND);\n        writeColor(theme, out, LOCATION_BAR_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_NORMAL);\n\n        // Selected colors.\n        out.startElement(ELEMENT_SELECTED);\n        out.println();\n        writeColor(theme, out, LOCATION_BAR_SELECTED_BACKGROUND_COLOR, ELEMENT_BACKGROUND);\n        writeColor(theme, out, LOCATION_BAR_SELECTED_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        out.endElement(ELEMENT_SELECTED);\n        out.endElement(ELEMENT_LOCATION_BAR);\n\n\n        // - Volume label description ----------------------------------------------------\n        // -------------------------------------------------------------------------------\n        out.startElement(ELEMENT_STATUS_BAR);\n        out.println();\n        // Font.\n        writeFont(theme, out, STATUS_BAR_FONT, ELEMENT_FONT);\n\n        // Colors.\n        writeColor(theme, out, STATUS_BAR_BACKGROUND_COLOR, ELEMENT_BACKGROUND);\n        writeColor(theme, out, STATUS_BAR_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        writeColor(theme, out, STATUS_BAR_BORDER_COLOR, ELEMENT_BORDER);\n        writeColor(theme, out, STATUS_BAR_OK_COLOR, ELEMENT_OK);\n        writeColor(theme, out, STATUS_BAR_WARNING_COLOR, ELEMENT_WARNING);\n        writeColor(theme, out, STATUS_BAR_CRITICAL_COLOR, ELEMENT_CRITICAL);\n        out.endElement(ELEMENT_STATUS_BAR);\n\n\n        \n        // - Quick list label description ----------------------------------------------------\n        // -------------------------------------------------------------------------------\n        out.startElement(ELEMENT_QUICK_LIST);\n        out.println();\n        \n        // Quick list header\n        out.startElement(ELEMENT_HEADER);\n        out.println();\n        // Font.\n        writeFont(theme, out, QUICK_LIST_HEADER_FONT, ELEMENT_FONT);\n        // Colors.\n        writeColor(theme, out, QUICK_LIST_HEADER_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        writeColor(theme, out, QUICK_LIST_HEADER_BACKGROUND_COLOR, ELEMENT_BACKGROUND);\n        writeColor(theme, out, QUICK_LIST_HEADER_SECONDARY_BACKGROUND_COLOR, ELEMENT_SECONDARY_BACKGROUND);\n        out.endElement(ELEMENT_HEADER);\n        \n        // Quick list item\n        out.startElement(ELEMENT_ITEM);\n        out.println();\n        // Font.\n        writeFont(theme, out, QUICK_LIST_ITEM_FONT, ELEMENT_FONT);\n        // Colors.\n        // Normal colors.\n        out.startElement(ELEMENT_NORMAL);\n        out.println();\n        writeColor(theme, out, QUICK_LIST_ITEM_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        writeColor(theme, out, QUICK_LIST_ITEM_BACKGROUND_COLOR, ELEMENT_BACKGROUND);\n        out.endElement(ELEMENT_NORMAL);\n        // Selected colors.\n        out.startElement(ELEMENT_SELECTED);\n        out.println();\n        writeColor(theme, out, QUICK_LIST_SELECTED_ITEM_FOREGROUND_COLOR, ELEMENT_FOREGROUND);\n        writeColor(theme, out, QUICK_LIST_SELECTED_ITEM_BACKGROUND_COLOR, ELEMENT_BACKGROUND);\n        out.endElement(ELEMENT_SELECTED);\n        out.endElement(ELEMENT_ITEM);\n        out.endElement(ELEMENT_QUICK_LIST);\n\n        out.endElement(ELEMENT_ROOT);\n    }\n\n\n\n    // - Helper methods ------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------\n    /**\n     * Returns the XML attributes describing the specified font.\n     * @param  font font to described as XML attributes.\n     * @return      the XML attributes describing the specified font.\n     */\n    private static XmlAttributes getFontAttributes(Font font) {\n        XmlAttributes attributes = new XmlAttributes(); // Stores the font's description.\n\n        // Font family and size.\n        attributes.add(ATTRIBUTE_FAMILY, font.getFamily());\n        attributes.add(ATTRIBUTE_SIZE, Integer.toString(font.getSize()));\n\n        // Font style.\n        if (font.isBold()) {\n            attributes.add(ATTRIBUTE_BOLD, VALUE_TRUE);\n        }\n        if (font.isItalic()) {\n            attributes.add(ATTRIBUTE_ITALIC, VALUE_TRUE);\n        }\n\n        return attributes;\n    }\n\n    /**\n     * Returns the XML attributes describing the specified color.\n     * @param  color color to described as XML attributes.\n     * @return       the XML attributes describing the specified color.\n     */\n    private static XmlAttributes getColorAttributes(Color color) {\n        StringBuilder buffer = new StringBuilder(); // Used to build the color's string representation.\n\n        // Red component.\n        if (color.getRed() < 16) {\n            buffer.append('0');\n        }\n        buffer.append(Integer.toString(color.getRed(), 16));\n\n        // Green component.\n        if (color.getGreen() < 16) {\n            buffer.append('0');\n        }\n        buffer.append(Integer.toString(color.getGreen(), 16));\n\n        // Blue component.\n        if (color.getBlue() < 16) {\n            buffer.append('0');\n        }\n        buffer.append(Integer.toString(color.getBlue(), 16));\n\n        // Builds the XML attributes.\n        XmlAttributes attributes = new XmlAttributes(); // Stores the color's description.\n        attributes.add(ATTRIBUTE_COLOR, buffer.toString());\n\n        if (color.getAlpha() != 255) {\n            buffer.setLength(0);\n            if (color.getAlpha() < 16) {\n                buffer.append('0');\n            }\n            buffer.append(Integer.toString(color.getAlpha(), 16));\n            attributes.add(ATTRIBUTE_ALPHA, buffer.toString());\n        }\n\n        return attributes;\n    }\n\n    private static void writeColor(ThemeData theme, XmlWriter out, int colorId, String name) throws IOException {\n        if (theme.isColorSet(colorId)) {\n            out.writeStandAloneElement(name, getColorAttributes(theme.getColor(colorId)));\n        }\n    }\n\n    private static void writeFont(ThemeData theme, XmlWriter out, int fontId, String name) throws IOException {\n        if (theme.isFontSet(fontId)) {\n            out.writeStandAloneElement(name, getFontAttributes(theme.getFont(fontId)));\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/theme/ThemeXmlConstants.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.theme;\r\n\r\n/**\r\n * Defines the format of the XML theme files.\r\n * @author Nicolas Rinaudo\r\n */\r\ninterface ThemeXmlConstants {\r\n    // - Main elements -------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /** XML theme file root element. */\r\n    String ELEMENT_ROOT                 = \"theme\";\r\n    /** File table description element. */\r\n    String ELEMENT_TABLE                = \"file_table\";\r\n    /** Shell description element. */\r\n    String ELEMENT_SHELL                = \"shell\";\r\n    /** File editor description element. */\r\n    String ELEMENT_EDITOR               = \"editor\";\r\n    /** Location bar description element. */\r\n    String ELEMENT_LOCATION_BAR         = \"location_bar\";\r\n    /** Shell history description element. */\r\n    String ELEMENT_SHELL_HISTORY        = \"shell_history\";\r\n    /** Volume label description element. */\r\n    String ELEMENT_STATUS_BAR           = \"status_bar\";\r\n    /** Quick list label description element. */\r\n    String ELEMENT_QUICK_LIST           = \"quick_list\";\r\n\r\n    String ELEMENT_FILE_GROUPS          = \"file_groups\";\r\n    String ELEMENT_GROUP                = \"group\";\r\n    String ELEMENT_TERMINAL             = \"terminal\";\r\n    String ELEMENT_HEX_VIEWER           = \"hex_viewer\";\r\n\r\n\r\n\r\n\r\n    // - Status element ------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /** Item normal state description element. */\r\n    String ELEMENT_NORMAL               = \"normal\";\r\n    /** Item selected state description element. */\r\n    String ELEMENT_SELECTED             = \"selected\";\r\n    /** Item alternate state description element. */\r\n    String ELEMENT_ALTERNATE            = \"alternate\";\r\n    /** Item unmatched state description element. */\r\n    String ELEMENT_UNMATCHED            = \"unmatched\";\r\n    /** Item current line state description element. */\r\n    String ELEMENT_CURRENT               = \"current\";\r\n    \r\n    \r\n    \r\n    // - Quick list element ------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /** Quick list header state description element. */\r\n    String ELEMENT_HEADER               = \"header\";\r\n    /** Quick list item state description element. */\r\n    String ELEMENT_ITEM\t\t            = \"item\";\r\n\r\n\r\n\r\n    // - Font element --------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    /** Font description element. */\r\n    String ELEMENT_FONT                 = \"font\";\r\n    /** Font family attribute. */\r\n    String ATTRIBUTE_FAMILY             = \"family\";\r\n    /** Font size attribute. */\r\n    String ATTRIBUTE_SIZE               = \"size\";\r\n    /** Font bold attribute. */\r\n    String ATTRIBUTE_BOLD               = \"bold\";\r\n    /** Font italic attribute. */\r\n    String ATTRIBUTE_ITALIC             = \"italic\";\r\n    /** <i>true</i> value. */\r\n    String VALUE_TRUE                   = \"true\";\r\n    /** <i>false</i> value. */\r\n    String VALUE_FALSE                  = \"false\";\r\n\r\n\r\n\r\n\r\n    // - Color elements ------------------------------------------------------------------\r\n    // -----------------------------------------------------------------------------------\r\n    String ELEMENT_INACTIVE_BACKGROUND           = \"inactive_background\";\r\n    String ELEMENT_INACTIVE_SECONDARY_BACKGROUND = \"inactive_secondary_background\";\r\n    String ELEMENT_INACTIVE_FOREGROUND           = \"inactive_foreground\";\r\n    String ELEMENT_BACKGROUND                    = \"background\";\r\n    String ELEMENT_SECONDARY_BACKGROUND          = \"secondary_background\";\r\n    String ELEMENT_FOREGROUND                    = \"foreground\";\r\n    String ELEMENT_HIDDEN_FILE                   = \"hidden_file\";\r\n    String ELEMENT_HIDDEN_FOLDER                 = \"hidden_folder\";\r\n    String ELEMENT_FOLDER                        = \"folder\";\r\n    String ELEMENT_ARCHIVE                       = \"archive\";\r\n    String ELEMENT_SYMLINK                       = \"symlink\";\r\n    String ELEMENT_MARKED                        = \"marked\";\r\n    String ELEMENT_EXECUTABLE                    = \"executable\";\r\n    String ELEMENT_FILE                          = \"file\";\r\n    String ELEMENT_PROGRESS                      = \"progress\";\r\n    String ELEMENT_BORDER                        = \"border\";\r\n    String ELEMENT_INACTIVE_BORDER               = \"inactive_border\";\r\n    String ELEMENT_OUTLINE                       = \"outline\";\r\n    String ELEMENT_INACTIVE_OUTLINE              = \"inactive_outline\";\r\n    String ELEMENT_OK                            = \"ok\";\r\n    String ELEMENT_WARNING                       = \"warning\";\r\n    String ELEMENT_CRITICAL                      = \"critical\";\r\n    String ELEMENT_ASCII_FOREGROUND              = \"ascii_foreground\";\r\n    String ELEMENT_ASCII_BACKGROUND              = \"ascii_background\";\r\n    String ELEMENT_OFFSET_FOREGROUND             = \"offset_foreground\";\r\n    String ATTRIBUTE_COLOR                       = \"color\";\r\n    String ATTRIBUTE_ALPHA                       = \"alpha\";\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/theme/package.html",
    "content": "<body>\n  API used to customise the fonts and colors of a Swing UI.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/tools/ToolsEnvironment.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.tools;\n\nimport com.mucommander.PlatformManager;\nimport com.mucommander.commons.file.AbstractFile;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Properties;\n\n/**\n * @author Oleg Trifonov\n * Created on 13/05/16.\n */\npublic class ToolsEnvironment {\n    private static final Map<String, String> customEnvironment = new HashMap<>();\n\n    public static void load() throws IOException {\n        AbstractFile configPath = PlatformManager.getPreferencesFolder().getChild(\"env.properties\");\n        if (configPath != null && configPath.exists()) {\n            try (InputStream is = configPath.getInputStream()) {\n                Properties properties = new Properties();\n                properties.load(is);\n                for (Object key: properties.keySet()) {\n                    String name = key.toString();\n                    customEnvironment.put(name, properties.getProperty(name));\n                }\n            }\n        }\n    }\n\n\n    public static Map<String, String> getCustomEnvironment() {\n        return customEnvironment;\n    }\n\n    public static String getEnv(String name) {\n        String result = customEnvironment.get(name);\n        if (result != null) {\n            return result;\n        }\n        return System.getenv(name);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/tools/ToolsSetupDialog.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.tools;\n\nimport com.mucommander.ui.dialog.FocusDialog;\n\nimport java.awt.Frame;\n\n/**\n * @author Oleg Trifonov\n * Created on 09/02/16.\n */\npublic class ToolsSetupDialog extends FocusDialog {\n    public ToolsSetupDialog(Frame owner) {\n        super(owner, i18n(\"tools_setup_dialog.title\"), null);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/EditorFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.viewer;\n\nimport com.mucommander.commons.file.AbstractFile;\n\n/**\n * A common interface for instantiating {@link FileEditor} implementations, and finding out if a editor is capable\n * of editing a particular file.\n *\n * @author Nicolas Rinaudo, Maxence Bernard\n */\npublic interface EditorFactory {\n    /**\n     * Returns <code>true</code> if this factory can create a file editor for the specified file.\n     * <p>\n     * The FileEditor may base its decision strictly upon the file's name and its extension or may wish to read some of\n     * the file and compare it to a magic number.\n     *\n     * @param  file file for which a editor must be created.\n     * @throws WarnUserException if the specified file can be edited after the warning message contained in the\n     * exception is displayed to the end user.\n     * @return      <code>true</code> if this factory can create a file editor for the specified file.\n     */\n    boolean canEditFile(AbstractFile file) throws WarnUserException;\n\n    /**\n     * Returns a new instance of {@link FileEditor}.\n     * @return a new instance of {@link FileEditor}.\n     */\n    FileEditor createFileEditor();\n\n    /**\n     * Returns a name for EditAs list\n     *\n     */\n    String getName();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/EditorFrame.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.viewer;\n\nimport java.awt.Dimension;\nimport java.awt.Image;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.main.MainFrame;\n\n\n/**\n * A specialized <code>JFrame</code> that displays a {@link FileEditor} for a given file and provides some common\n * editing functionalities. The {@link FileEditor} instance is provided by {@link EditorRegistrar}.\n *\n * @author Maxence Bernard, Arik Hadas\n */\npublic class EditorFrame extends FileFrame {\n\n    private FileEditor editor;\n\t\n    private final static Dimension MIN_DIMENSION = new Dimension(500, 360);\n\n    /**\n     * Creates a new EditorFrame to start viewing the given file.\n     *\n     * <p>This constructor has package access only, EditorFrame can to be created by\n     * {@link EditorRegistrar#createEditorFrame(MainFrame,AbstractFile,Image)}.\n     */\n    EditorFrame(MainFrame mainFrame, AbstractFile file, Image icon) {\n        super(mainFrame, icon);\n        initContentPane(file);\n    }\n\n    @Override\n    public Dimension getMinimumSize() {\n        return MIN_DIMENSION;\n    }\n\n    @Override\n    public void dispose() {\n        // Returns true if the file does not have any unsaved change or if the user refused to save the changes\n    \tif (editor == null || editor.askSave()) {\n            super.dispose();\n        }\n    }\n    \n    @Override\n\tprotected FilePresenter createFilePresenter(AbstractFile file) throws UserCancelledException {\n\t\treturn editor = EditorRegistrar.createFileEditor(file, EditorFrame.this);\n\t}\n\n    @Override\n    protected String getGenericErrorDialogTitle() {\n    \treturn Translator.get(\"file_editor.edit_error_title\");\n    }\n\n    @Override\n    protected String getGenericErrorDialogMessage() {\n    \treturn Translator.get(\"file_editor.edit_error\");\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/EditorRegistrar.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.viewer;\n\nimport java.awt.Frame;\nimport java.awt.Image;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileProtocols;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.commons.runtime.OsVersion;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.QuestionDialog;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.WindowManager;\nimport com.mucommander.ui.viewer.text.TextEditor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jetbrains.annotations.Nullable;\n\nimport javax.swing.*;\n\n/**\n * EditorRegistrar maintains a list of registered file editors and provides methods to dynamically register file editors\n * and create appropriate FileEditor (Panel) and EditorFrame (Window) instances for a given AbstractFile.\n *\n * @author Maxence Bernard\n */\n@Slf4j\npublic class EditorRegistrar {\n\t\n    /** List of registered file editors */ \n    private final static List<EditorFactory> editorFactories = new ArrayList<>();\n\n    static {\n        registerFileEditor(new com.mucommander.ui.viewer.text.TextFactory());\n    }\n\n    /**\n     * Registers a FileEditor.\n     * @param factory file editor factory to register.\n     */\n    private static void registerFileEditor(EditorFactory factory) {\n        editorFactories.add(factory);\n    }\n\n    /**\n     * Creates and returns an EditorFrame to start viewing the given file. The EditorFrame will be monitored\n     * so that if it is the last window on screen when it is closed by the user, it will trigger the shutdown sequence.\n     *\n     * @param mainFrame the parent MainFrame instance\n     * @param file the file that will be displayed by the returned EditorFrame \n     * @param icon editor frame's icon.\n     * @param createListener postponed action\n     */\n    public static void createEditorFrame(MainFrame mainFrame, AbstractFile file, Image icon, FileFrameCreateListener createListener) {\n        // Check if this file is already opened\n        if (showEditorIfAlreadyOpen(file, createListener)) {\n            return;\n        }\n        new FilePreloadWorker(file, mainFrame, () -> {\n            EditorFrame frame = new EditorFrame(mainFrame, file, icon);\n            // Use new Window decorations introduced in Mac OS X 10.5 (Leopard)\n            if (OsFamily.MAC_OS_X.isCurrent() && OsVersion.MAC_OS_X_10_5.isCurrentOrHigher()) {\n                // Displays the document icon in the window title bar, works only for local files\n                if (file.getURL().getScheme().equals(FileProtocols.FILE)) {\n                    frame.getRootPane().putClientProperty(\"Window.documentFile\", file.getUnderlyingFileObject());\n                }\n            }\n            // WindowManager will listen to window closed events to trigger shutdown sequence\n            // if it is the last window visible\n            frame.addWindowListener(WindowManager.getInstance());\n            if (createListener != null) {\n                createListener.onCreate(frame);\n            }\n        }).execute();\n    }\n\n    private static boolean showEditorIfAlreadyOpen(AbstractFile file, FileFrameCreateListener createListener) {\n        for (FileViewersList.FileRecord fr: FileViewersList.getFiles()) {\n            if (fr.fileName.equals(file.getAbsolutePath()) && fr.viewerClass != null) {\n                Class viewerClass = fr.viewerClass;\n                if (viewerClass.equals(TextEditor.class)) {\n                    FileFrame openedFrame = fr.fileFrameRef.get();\n                    if (openedFrame != null) {\n                        openedFrame.toFront();\n                        if (createListener != null) {\n                            createListener.onCreate(openedFrame);\n                        }\n                    }\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    public static void createEditorFrame(MainFrame mainFrame, AbstractFile file, Image icon) {\n        createEditorFrame(mainFrame, file, icon, null);\n    }\n\n    \n    /**\n     * Creates and returns an appropriate FileEditor for the given file type.\n     *\n     * @param file the file that will be displayed by the returned FileEditor\n     * @param frame the frame in which the FileEditor is shown\n     * @return the created FileEditor, or null if no suitable editor was found\n     * @throws UserCancelledException if the user has been asked to confirm the operation and canceled\n     */\n    static FileEditor createFileEditor(AbstractFile file, EditorFrame frame) throws UserCancelledException {\n        FileEditor editor = createFileEditor(file);\n\n        if (editor != null) {\n            editor.setFrame(frame);\n        }\n    \t\n    \treturn editor;\n    }\n\n    @Nullable\n    private static FileEditor createFileEditor(AbstractFile file) throws UserCancelledException {\n        for (EditorFactory factory : editorFactories) {\n            try {\n                if (factory.canEditFile(file)) {\n                    return factory.createFileEditor();\n                }\n            } catch (WarnUserException e) {\n                showQuestionDialog(file, e);\n\n                // User confirmed the operation\n                return factory.createFileEditor();\n            }\n        }\n        return null;\n    }\n\n    private static void showQuestionDialog(AbstractFile file, WarnUserException e) throws UserCancelledException {\n        QuestionDialog dialog = new QuestionDialog((Frame)null,\n                Translator.get(\"warning\"), Translator.get(e.getMessage()), null,\n                new String[] {Translator.get(\"file_editor.open_anyway\"), Translator.get(\"cancel\")},\n                new int[]  {0, 1},\n                0);\n\n        int ret = dialog.getActionValue();\n        if (ret == 1 || ret == -1) {   // User canceled the operation\n            try {\n                file.closePushbackInputStream();\n            } catch (IOException e1) {\n                log.error(\"IO error\", e);\n            }\n            throw new UserCancelledException();\n        }\n    }\n\n    public static List<EditorFactory> getAllEditors(AbstractFile file) {\n        List<EditorFactory> result = new ArrayList<>();\n        for (EditorFactory factory : editorFactories) {\n            try {\n                if (!factory.canEditFile(file)) {\n                    continue;\n                }\n            } catch (WarnUserException ignore) {}\n            result.add(factory);\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/FileEditor.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.viewer;\n\nimport com.mucommander.commons.file.*;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.job.FileCollisionChecker;\nimport com.mucommander.ui.dialog.InformationDialog;\nimport com.mucommander.ui.dialog.QuestionDialog;\nimport com.mucommander.ui.dialog.file.FileCollisionDialog;\nimport com.mucommander.ui.helper.MenuToolkit;\nimport com.mucommander.ui.helper.MnemonicHelper;\nimport com.mucommander.utils.text.Translator;\nimport ru.trolsoft.ui.TMenuSeparator;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.awt.event.KeyEvent;\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\n\n\n/**\n * An abstract class to be subclassed by file editor implementations.\n *\n * <p><b>Warning:</b> the file viewer/editor API may soon receive a major overhaul.\n *\n * @author Maxence Bernard, Arik Hadas\n */\npublic abstract class FileEditor extends FilePresenter implements ActionListener {\n\n    /** Menu items */\n    protected JMenu menuFile;\n    private JMenuItem miSave;\n    private JMenuItem miSaveAs;\n    private JMenuItem miClose;\n\n    /** Serves to indicate if saving is needed before closing the window, value should only be modified using the setSaveNeeded() method */\n    private boolean saveNeeded;\n\n    /**\n     * Creates a new FileEditor.\n     */\n    protected FileEditor() {\n\n    }\n\t\n\n    protected void setSaveNeeded(boolean saveNeeded) {\n    \tif (getFrame() == null || this.saveNeeded == saveNeeded) {\n            return;\n        }\n        if (!this.saveNeeded) {\n            getStatusBar().setStatusMessage(Translator.get(\"text_editor.modified\"));\n        }\n        this.saveNeeded = saveNeeded;\n\n        // Marks/unmarks the window as dirty under Mac OS X (symbolized by a dot in the window closing icon)\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            getFrame().getRootPane().putClientProperty(\"windowModified\", saveNeeded);\n        }\n    }\n    \n    private boolean trySaveAs() {\n        AbstractFile destFile = choiceFileToSave();\n        if (destFile != null && checkCollision(destFile) && trySave(destFile)) {\n            setCurrentFile(destFile);\n            return true;\n        }\n        return false;\n    }\n\n    private AbstractFile choiceFileToSave() {\n        JFileChooser fileChooser = new JFileChooser();\n        AbstractFile currentFile = getCurrentFile();\n        // Sets selected file in JFileChooser to current file\n        if (currentFile.getURL().getScheme().equals(FileProtocols.FILE)) {\n            fileChooser.setSelectedFile(new File(currentFile.getAbsolutePath()));\n        }\n        fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);\n        int ret = fileChooser.showSaveDialog(getFrame());\n\n        if (ret == JFileChooser.APPROVE_OPTION) {\n            AbstractFile destFile;\n            try {\n                destFile = FileFactory.getFile(fileChooser.getSelectedFile().getAbsolutePath(), true);\n            } catch (IOException e) {\n                InformationDialog.showErrorDialog(getFrame(), Translator.get(\"write_error\"), Translator.get(\"file_editor.cannot_write\"));\n                return null;\n            }\n            if (checkCollision(destFile) && trySave(destFile)) {\n                setCurrentFile(destFile);\n                return destFile;\n            }\n        }\n        return null;\n    }\n\n    private boolean checkCollision(AbstractFile destFile) {\n        // Check for file collisions, i.e. if the file already exists in the destination\n        int collision = FileCollisionChecker.checkForCollision(null, destFile);\n        if (collision != FileCollisionChecker.NO_COLLISION) {\n            // File already exists in destination, ask the user what to do (cancel, overwrite,...) but\n            // do not offer the multiple files mode options such as 'skip' and 'apply to all'.\n            int action = new FileCollisionDialog(getFrame(), getFrame()/*mainFrame*/, collision, null, destFile, false, false).getActionValue();\n\n            // User chose to overwrite the file\n            return action == FileCollisionDialog.OVERWRITE_ACTION;   // User chose to cancel or closed the dialog\n        }\n        return true;\n    }\n\n    // Returns false if an error occurred while saving the file.\n    private boolean trySave(AbstractFile destFile) {\n        try {\n            saveAs(destFile);\n            return true;\n        } catch (IOException e) {\n            if (e instanceof FileNotFoundException) {\n                if (!destFile.getPermissions().getBitValue(PermissionAccesses.USER_ACCESS, PermissionTypes.WRITE_PERMISSION)) {\n                    return trySaveReadOnly(destFile);\n                }\n            }\n            getStatusBar().setStatusMessage(Translator.get(\"text_editor.cant_save_file\"));\n            InformationDialog.showErrorDialog(getFrame(), Translator.get(\"write_error\"), Translator.get(\"file_editor.cannot_write\"));\n            return false;\n        }\n    }\n\n    private void forceOverwriteFile(AbstractFile file) throws IOException {\n        FilePermissions savedPermissions = file.getPermissions();\n        file.changePermission(PermissionAccesses.USER_ACCESS, PermissionTypes.WRITE_PERMISSION, true);\n        saveAs(file);\n        file.changePermissions(savedPermissions);\n    }\n\n\n    private boolean trySaveReadOnly(AbstractFile destFile) {\n        QuestionDialog dialog = new QuestionDialog((Frame)null, Translator.get(\"warning\"), Translator.get(\"file_editor.overwrite_readonly\"), getFrame(),\n                new String[] {Translator.get(\"file_editor.save_anyway\"), Translator.get(\"file_editor.save_as\"), Translator.get(\"cancel\")},\n                new int[]  {0, 1, JOptionPane.CLOSED_OPTION},\n                0);\n        int ret = dialog.getActionValue();\n        if (ret == 0) { // Overwrite\n            try {\n                forceOverwriteFile(destFile);\n                return true;\n            } catch (IOException e) {\n                getStatusBar().setStatusMessage(Translator.get(\"text_editor.cant_save_file\"));\n                InformationDialog.showErrorDialog(getFrame(), Translator.get(\"write_error\"), Translator.get(\"file_editor.cannot_write\"));\n            }\n        } else if (ret == 1) {  // Save as...\n            if (trySaveAs() ) {\n                return true;\n            }\n        }\n        getStatusBar().setStatusMessage(Translator.get(\"text_editor.cant_save_file\"));\n        return false;\n    }\n\n    // Returns true if the file does not have any unsaved change or if the user refused to save the changes,\n    // false if the user canceled the dialog or the save failed.\n    protected boolean askSave() {\n        if (!saveNeeded) {\n            return true;\n        }\n\n        QuestionDialog dialog = new QuestionDialog(getFrame(), null, Translator.get(\"file_editor.save_warning\"), getFrame(),\n                                                   new String[] {Translator.get(\"save\"), Translator.get(\"dont_save\"), Translator.get(\"cancel\")},\n                                                   new int[]  {JOptionPane.YES_OPTION, JOptionPane.NO_OPTION, JOptionPane.CANCEL_OPTION},\n                                                   0);\n        int ret = dialog.getActionValue();\n\n        if ((ret == JOptionPane.YES_OPTION && trySave(getCurrentFile())) || ret == JOptionPane.NO_OPTION) {\n            setSaveNeeded(false);\n            return true;\n        }\n\n        return false;       // User canceled or the file couldn't be properly saved\n    }\n    \n    /**\n     * Returns the menu bar that controls the editor's frame. The menu bar should be retrieved using this method and\n     * not by calling {@link JFrame#getJMenuBar()}, which may return <code>null</code>.\n     *\n     * @return the menu bar that controls the editor's frame.\n     */\n    public JMenuBar getMenuBar() {\n        JMenuBar menuBar = new JMenuBar();\n        MnemonicHelper mnemonicHelper = new MnemonicHelper();\n\n        // File menu\n        menuFile = MenuToolkit.addMenu(Translator.get(\"file_editor.file_menu\"), mnemonicHelper, null);\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            miSave = MenuToolkit.addMenuItem(menuFile, Translator.get(\"file_editor.save\"), mnemonicHelper, KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.META_DOWN_MASK), this);\n        } else {\n            miSave = MenuToolkit.addMenuItem(menuFile, Translator.get(\"file_editor.save\"), mnemonicHelper, KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_DOWN_MASK), this);\n        }\n        miSaveAs = MenuToolkit.addMenuItem(menuFile, Translator.get(\"file_editor.save_as\"), mnemonicHelper, null, this);\n        menuFile.add(new TMenuSeparator());\n        miClose = MenuToolkit.addMenuItem(menuFile, Translator.get(\"file_editor.close\"), mnemonicHelper, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), this);\n\t\t\n        menuBar.add(menuFile);\n\n        return menuBar;\n    }\n\n\n    public void actionPerformed(ActionEvent e) {\n        Object source = e.getSource();\n\n        // File menu\n        if (source == miSave) {\n            trySave(getCurrentFile());\n        } else if (source == miSaveAs) {\n            trySaveAs();\n        } else if (source == miClose) {\n            getFrame().dispose();\n        }\n    }\n\n\n    /**\n     * This method is invoked when the user asked to save current file to the specified file.\n     * \n     *\n     * @param saveAsFile the file which should be used to save the file currently being edited\n     * (path can be different from current file if the user chose 'Save as').\n     * @throws IOException if an I/O error occurs.\n     */\n    protected abstract void saveAs(AbstractFile saveAsFile) throws IOException;\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/FileFrame.java",
    "content": "package com.mucommander.ui.viewer;\r\n\r\nimport java.awt.*;\r\nimport java.awt.event.KeyEvent;\r\nimport java.io.IOException;\r\nimport javax.swing.*;\r\n\r\nimport com.mucommander.cache.WindowsStorage;\r\nimport com.mucommander.ui.macosx.IMacOsWindow;\r\nimport com.mucommander.ui.quicklist.QuickListContainer;\r\nimport org.fife.ui.StatusBar;\r\nimport org.jetbrains.annotations.NotNull;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.ui.dialog.DialogToolkit;\r\nimport com.mucommander.ui.dialog.InformationDialog;\r\nimport com.mucommander.ui.helper.FocusRequester;\r\nimport com.mucommander.ui.layout.AsyncPanel;\r\nimport com.mucommander.ui.main.MainFrame;\r\n\r\n/**\r\n * This class is used as an abstraction for the {@link EditorFrame} and {@link ViewerFrame}.\r\n * \r\n * @author Arik Hadas\r\n */\r\npublic abstract class FileFrame extends JFrame implements QuickListContainer, IMacOsWindow {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(FileFrame.class);\r\n\r\n    /**\r\n     * The file presenter within this frame\r\n     */\r\n\tprivate FilePresenter filePresenter;\r\n\r\n    /**\r\n     * The main frame from which this frame was initiated\r\n     */\r\n\tprivate final MainFrame mainFrame;\r\n\t\r\n    private Component returnFocusTo;\r\n\r\n\r\n    FileFrame(MainFrame mainFrame, Image icon) {\r\n        this.mainFrame = mainFrame;\r\n\r\n\t\tsetIconImage(icon);\r\n\r\n        initLookAndFeel();\r\n\t\t\r\n        setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r\n        \r\n        setResizable(true);\r\n\r\n        //FileViewersList.update();\r\n        //initContentPane(file);\r\n\t}\r\n\r\n    void initContentPane(final AbstractFile file) {\r\n        try {\r\n            filePresenter = createFilePresenter(file);\r\n        } catch (UserCancelledException e) {\r\n            LOGGER.error(\"Operation canceled by user\", e);\r\n\t\t\t// May get a UserCancelledException if the user canceled (refused to confirm the operation after a warning)\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t// If not suitable presenter was found for the given file\r\n        if (filePresenter == null) {\r\n            showGenericErrorDialog();\r\n            return;\r\n        }\r\n        JComponent asyncPanel = createAsyncPanel(file);\r\n\r\n        // Add the AsyncPanel to the content pane\r\n        JPanel contentPane = new JPanel(new BorderLayout());\r\n        contentPane.add(asyncPanel, BorderLayout.CENTER);\r\n//contentPane.add(filePresenter, BorderLayout.CENTER);\r\n\r\n        // Add status bar if exists\r\n        StatusBar statusBar = filePresenter.getStatusBar();\r\n        if (statusBar != null) {\r\n            contentPane.add(statusBar, BorderLayout.SOUTH);\r\n        }\r\n\r\n        setContentPane(contentPane);\r\n        //setSize(WAIT_DIALOG_SIZE);\r\n        //setFullScreenSize();\r\n        //setFullScreen(true);\r\n        if (!WindowsStorage.getInstance().init(this, filePresenter.getClass().getCanonicalName(), true)) {\r\n            setSize(800, 600);\r\n            DialogToolkit.centerOnWindow(this, mainFrame.getJFrame());\r\n        }\r\n\r\n        setVisible(true);\r\n        FileViewersList.update();\r\n    }\r\n\r\n\r\n\r\n    @NotNull\r\n    private JComponent createAsyncPanel(AbstractFile file) {\r\n        if (file.isLocalFile()) {\r\n            try {\r\n                filePresenter.open(file);\r\n                setJMenuBar(filePresenter.getMenuBar());\r\n                initializeAfterLoad();\r\n            } catch (Exception e) {\r\n                LOGGER.debug(\"Exception caught\", e);\r\n                showGenericErrorDialog();\r\n                dispose();\r\n            }\r\n            return filePresenter;\r\n        }\r\n\r\n        return new AsyncPanel() {\r\n                @Override\r\n                public void initTargetComponent() throws Exception {\r\n                    openPresenter(file, this::cancel);\r\n                }\r\n\r\n                @Override\r\n                public JComponent getTargetComponent(Exception e) {\r\n                    if (e != null) {\r\n                        LOGGER.debug(\"Exception caught\", e);\r\n                        showGenericErrorDialog();\r\n                        dispose();\r\n                        return filePresenter == null ? new JPanel() : filePresenter;\r\n                    }\r\n                    setJMenuBar(filePresenter.getMenuBar());\r\n                    return filePresenter;\r\n                }\r\n\r\n\r\n                @Override\r\n                protected void updateLayout() {\r\n                    super.updateLayout();\r\n                    initializeAfterLoad();\r\n                }\r\n            };\r\n    }\r\n\r\n    private void openPresenter(AbstractFile file, Runnable onCancel) throws IOException {\r\n        final KeyEventDispatcher keyEventDispatcher = e -> {\r\n            if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {\r\n                e.consume();\r\n                if (onCancel != null) {\r\n                    onCancel.run();\r\n                }\r\n                setVisible(false);\r\n                dispose();\r\n            }\r\n            return false;\r\n        };\r\n\r\n        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(keyEventDispatcher);\r\n        try {\r\n            filePresenter.open(file);\r\n        } finally {\r\n            KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(keyEventDispatcher);\r\n        }\r\n    }\r\n\r\n    private void initializeAfterLoad() {\r\n        // Sets panel to preferred size, without exceeding a maximum size and with a minimum size\r\n        //pack();\r\n        WindowsStorage.getInstance().init(FileFrame.this, filePresenter.getClass().getCanonicalName(), true);\r\n        // Request focus on the viewer when it is visible\r\n        FocusRequester.requestFocus(filePresenter);\r\n\r\n        // Restore (caret position, scroll position etc.)\r\n        filePresenter.restoreStateOnStartup();\r\n    }\r\n\r\n\r\n\r\n\r\n    private void showGenericErrorDialog() {\r\n        InformationDialog.showErrorDialog(mainFrame.getJFrame(), getGenericErrorDialogTitle(), getGenericErrorDialogMessage());\r\n    }\r\n\r\n//\t/**\r\n//\t * Sets this file presenter to full screen\r\n//\t */\r\n//\tpublic void setFullScreen(boolean on) {\r\n//\t\tint currentExtendedState = getExtendedState();\r\n//\t\tsetExtendedState(on ? currentExtendedState | Frame.MAXIMIZED_BOTH : currentExtendedState & ~Frame.MAXIMIZED_BOTH);\r\n//}\r\n\r\n\t/**\r\n\t * Returns whether this frame is set to be displayed in full screen mode\r\n\t * \r\n\t * @return true if the frame is set to full screen, false otherwise\r\n\t */\r\n    private boolean isFullScreen() {\r\n\t\treturn (getExtendedState() & Frame.MAXIMIZED_BOTH) != 0;\r\n\t}\r\n\r\n\r\n\r\n    @Override\r\n    public void pack() {\r\n    \tif (!isFullScreen()) {\r\n    \t\tsuper.pack();\r\n            DialogToolkit.fitToScreen(this);\r\n            DialogToolkit.fitToMinDimension(this, getMinimumSize());\r\n            DialogToolkit.centerOnWindow(this, mainFrame.getJFrame());\r\n    \t}\r\n    }\r\n\r\n    @Override\r\n    public void dispose() {\r\n        try {\r\n            filePresenter.saveStateOnClose();\r\n            WindowsStorage.getInstance().put(this, filePresenter.getClass().getCanonicalName());\r\n        } catch (Throwable ignore) {}\r\n        super.dispose();\r\n        try {\r\n            if (returnFocusTo != null) {\r\n                FocusRequester.requestFocus(returnFocusTo);\r\n                if (returnFocusTo instanceof FileFrame) {\r\n                    //SwingUtilities.invokeLater(() -> FocusRequester.requestFocus(((FileFrame)returnFocusTo).filePresenter));\r\n                    FocusRequester.requestFocus(((FileFrame)returnFocusTo).filePresenter);\r\n                    //((FileFrame)returnFocusTo).filePresenter\r\n                }\r\n            }\r\n            FileViewersList.update();\r\n        } catch (Throwable ignore) {}\r\n    }\r\n\r\n    public FileFrame returnFocusTo(Component returnFocusTo) {\r\n        this.returnFocusTo = returnFocusTo;\r\n        return this;\r\n    }\r\n\r\n\r\n    public Component getReturnFocusTo() {\r\n        return returnFocusTo;\r\n    }\r\n\r\n    public void setSearchedText(String searchedText) {\r\n        filePresenter.setSearchedText(searchedText);\r\n    }\r\n\r\n    public void setSearchedBytes(byte[] searchedBytes) {\r\n        filePresenter.setSearchedBytes(searchedBytes);\r\n    }\r\n\r\n    public FilePresenter getFilePresenter() {\r\n        return filePresenter;\r\n    }\r\n\r\n\r\n    @Override\r\n    public void setTitle(String title) {\r\n        super.setTitle(title);\r\n        FileViewersList.update();\r\n    }\r\n\r\n    public MainFrame getMainFrame() {\r\n        return mainFrame;\r\n    }\r\n\r\n    public Point calcQuickListPosition(Dimension dim) {\r\n        int x = Math.max((getWidth() - (int)dim.getWidth()) / 2, 0);\r\n        int y = Math.max((getHeight() - (int)dim.getHeight()) / 2, 0);\r\n        return new Point(x, y);\r\n    }\r\n\r\n    public Component containerComponent() {\r\n        return this;\r\n    }\r\n\r\n    public Component nextFocusableComponent() {\r\n        return this;\r\n    }\r\n\r\n\r\n\r\n\r\n    protected abstract String getGenericErrorDialogTitle();\r\n\r\n    protected abstract String getGenericErrorDialogMessage();\r\n\r\n    protected abstract FilePresenter createFilePresenter(AbstractFile file) throws UserCancelledException;\r\n\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/FileFrameCreateListener.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer;\n\n/**\n * @author Oleg Trifonov\n * Created on 05/07/16.\n */\npublic interface FileFrameCreateListener {\n    void onCreate(FileFrame fileFrame);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/FilePreloadWorker.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer;\n\nimport com.mucommander.commons.HasProgress;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.io.EncodingDetector;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.statusbar.TaskWidget;\nimport org.jetbrains.annotations.NotNull;\n\nimport javax.swing.SwingWorker;\nimport java.io.PushbackInputStream;\nimport java.util.List;\n\n/**\n * @author Oleg Trifonov\n * Created on 07/07/16.\n */\npublic class FilePreloadWorker extends SwingWorker<Void, Void> {\n    private final AbstractFile file;\n    private final MainFrame mainFrame;\n    private final TaskWidget taskWidget;\n    private final Runnable onFinish;\n    private volatile Throwable readException;\n    private volatile int progress;\n    private boolean taskWidgetAttached;\n\n\n    FilePreloadWorker(AbstractFile file, MainFrame mainFrame, Runnable onFinish) {\n        this.file = file;\n        this.mainFrame = mainFrame;\n        this.onFinish = onFinish;\n        this.taskWidget = new TaskWidget();\n        taskWidget.setText(file.getName());\n    }\n\n    @Override\n    protected Void doInBackground() {\n        try {\n            publish();\n            final PushbackInputStream is = file.getPushBackInputStream(EncodingDetector.MAX_RECOMMENDED_BYTE_SIZE);\n            if (is instanceof HasProgress) {\n                buildProgressThread((HasProgress) is).start();\n            }\n        } catch (Throwable e) {\n            readException = e;\n        }\n        return null;\n    }\n\n    @NotNull\n    private Thread buildProgressThread(HasProgress is) {\n        Thread progressThread = new Thread(() -> {\n            while (true) {\n                progress = is.getProgress();\n                publish();\n                if (progress >= 100 || readException != null || progress < 0) {\n                    progress = -1;\n                    publish();\n                    break;\n                }\n                try {\n                    Thread.sleep(10);\n                } catch (InterruptedException ignored) {}\n            }\n        });\n        progressThread.setName(\"ProgressInputStreamThread\");\n        return progressThread;\n    }\n\n    @Override\n    protected void process(List<Void> chunks) {\n        if (!taskWidgetAttached) {\n            mainFrame.getStatusBar().getTaskPanel().addTask(taskWidget);\n            mainFrame.getStatusBar().revalidate();\n            mainFrame.getStatusBar().repaint();\n            taskWidgetAttached = true;\n        }\n        taskWidget.setProgress(progress);\n    }\n\n    @Override\n    protected void done() {\n        taskWidget.removeFromPanel();\n        if (readException != null) {\n            mainFrame.getStatusBar().setStatusInfo(Translator.get(\"text_viewer.open_file_error\"));\n        } else  if (onFinish != null) {\n            onFinish.run();\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/FilePresenter.java",
    "content": "package com.mucommander.ui.viewer;\n\nimport java.awt.Component;\nimport java.awt.event.*;\nimport java.io.IOException;\n\nimport javax.swing.*;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.runtime.OsFamily;\nimport org.fife.ui.StatusBar;\n\n/**\n * Abstract class that serves as a common base for the file presenter objects (FileViewer, FileEditor).\n * \n * @author Arik Hadas\n */\npublic abstract class FilePresenter extends JScrollPane {\n\t\n\t/** FileFrame instance that contains this presenter (maybe null). */\n    private FileFrame frame;\n    \n    /** File currently being presented. */\n    private AbstractFile file;\n\n    protected final static String CUSTOM_FULL_SCREEN_EVENT = \"CUSTOM_FULL_SCREEN_EVENT\";\n    private final static String CUSTOM_DISPOSE_EVENT = \"CUSTOM_DISPOSE_EVENT\";\n\n\tFilePresenter() {\n\t\tsuper(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);\n\n\t\taddFocusListener(new FocusListener() {\n\n\t\t\tpublic void focusLost(FocusEvent e) {}\n\n\t\t\tpublic void focusGained(FocusEvent e) {\n\t\t\t\t// Delegate the focus to the JComponent that actually present the file\n\t\t\t\tComponent component = FilePresenter.this.getViewport().getComponent(0);\n\t\t\t\tif (component != null) {\n                    component.requestFocus();\n                }\n\t\t\t}\n\t\t});\n\n\t\t// Catch Apple+W keystrokes under Mac OS X to close the window\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n        \tgetInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.META_DOWN_MASK), CUSTOM_DISPOSE_EVENT);\n        \tgetActionMap().put(CUSTOM_DISPOSE_EVENT, new AbstractAction() {\n        \t\tpublic void actionPerformed(ActionEvent e){\n        \t\t\tgetFrame().dispose();\n        \t\t}\n        \t});\n        }\n\t}\n\n\t/**\n\t * Set component to be presented in the ScrollPane viewport\n\t * \n\t * @param component the component to be presented\n\t */\n\tpublic void setComponentToPresent(JComponent component) {\n\t\tgetViewport().removeAll();\n\t\tgetViewport().add(component);\n\t}\n\t\n\t/**\n     * Returns the frame which contains this presenter.\n     * <p>\n     * This method may return <code>null</code>if the presenter is not inside a FileFrame.\n     *\n     * @return the frame which contains this presenter.\n     * @see    #setFrame(FileFrame)\n     */\n    protected FileFrame getFrame() {\n        return frame;\n    }\n\n    /**\n     * Sets the FileFrame (separate window) that contains this FilePresenter.\n     * @param frame frame that contains this <code>FilePresenter</code>.\n     * @see         #getFrame()\n     */\n    public void setFrame(FileFrame frame) {\n        this.frame = frame;\n    }\n    \n    /**\n     * Returns a description of the file currently being presented which will be used as a window title.\n     * This method returns the file's name, but it can be overridden to provide more information.\n     * @return this dialog's title.\n     */\n    protected String getTitle() {\n        return file.getAbsolutePath();\n    }\n\n    /**\n     * Returns the file that is being presented.\n     *\n     * @return the file that is being presented.\n     */\n    public AbstractFile getCurrentFile() {\n        return file;\n    }\n\n    /**\n     * Sets the file that is to be presented.\n     * This method will automatically be called after a file presenter is created and should not be called directly.\n     * \n     * @param file file that is to be presented.\n     */\n    protected final void setCurrentFile(AbstractFile file) {\n        this.file = file;\n        // Update frame's title\n        FileFrame frame = getFrame();\n        if (frame != null) {\n            frame.setTitle(getTitle());\n        }\n    }\n\t\n\t/**\n\t * Open a given AbstractFile for display.\n\t * \n\t * @param file the file to be presented\n\t * @throws IOException in case of an I/O problem\n\t */\n    public void open(AbstractFile file) throws IOException {\n    \tshow(file);\n    \tsetCurrentFile(file);\n    }\n    \n\n    /**\n     * This method is invoked when the specified file is about to be opened.\n     * This method should retrieve the file and do the necessary so that this component can be displayed.\n     *\n     * @param  file        the file that is about to be viewed.\n     * @throws IOException if an I/O error occurs.\n     */\n    protected abstract void show(AbstractFile file) throws IOException;\n\t\n\t/**\n     * Returns the menu bar that controls the presenter's frame. The menu bar should be retrieved using this method and\n     * not by calling {@link JFrame#getJMenuBar()}, which may return <code>null</code>.\n     *\n     * @return the menu bar that controls the presenter's frame.\n     */\n    protected abstract JMenuBar getMenuBar();\n\n    /**\n     * Returns the status bar for presenter frame. Can return null if the viewer doesn't have status bar.\n     */\n    protected abstract StatusBar getStatusBar();\n\n    /**\n     * Executed before editor/viewer closed to save sate (cursor position, syntax type etc.)\n     */\n    protected abstract void saveStateOnClose();\n\n    /**\n     * Executed before editor/viewer shows to restore saved state\n     */\n    protected abstract void restoreStateOnStartup();\n\n    public void setSearchedText(String searchedText) {\n    }\n\n    public void setSearchedBytes(byte[] searchedBytes) {\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/FileViewer.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.viewer;\n\nimport com.mucommander.ui.helper.MenuToolkit;\nimport com.mucommander.ui.helper.MnemonicHelper;\nimport com.mucommander.utils.text.Translator;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.*;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * An abstract class to be subclassed by file viewer implementations.\n *\n * <p>\n * <b>Warning:</b> the file viewer/editor API may soon receive a major overhaul.\n *\n * @author Maxence Bernard, Arik Hadas\n */\npublic abstract class FileViewer extends FilePresenter implements ActionListener {\n\n    /**\n     * This map used to fix java issues with some menu hot-keys - some accelerators (etc. F2, arrows, Enter, Escape) doesn't work\n     * properly in menu\n     */\n    private Map<KeyStroke, JMenuItem> menuKeyStrokes;\n\n    protected JMenu menuFile;\n    /** Close menu item */\n    private JMenuItem miClose;\n\n    /**\n     * Creates a new FileViewer.\n     */\n    public FileViewer() {}\n\t\n    /**\n     * Returns the menu bar that controls the viewer's frame. The menu bar should be retrieved using this method and\n     * not by calling {@link JFrame#getJMenuBar()}, which may return <code>null</code>.\n     *\n     * @return the menu bar that controls the viewer's frame.\n     */\n    public JMenuBar getMenuBar() {\n        JMenuBar menuBar = new JMenuBar();\n        MnemonicHelper mnemonicHelper = new MnemonicHelper();\n\n        // File menu\n        menuFile = MenuToolkit.addMenu(i18n(\"file_viewer.file_menu\"), mnemonicHelper, null);\n\n        miClose = MenuToolkit.addMenuItem(menuFile, i18n(\"file_viewer.close\"), mnemonicHelper, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), this);\n        menuFile.add(miClose);\n\n        menuBar.add(menuFile);\n\n        return menuBar;\n    }\n\n    public void actionPerformed(ActionEvent e) {\n        Object source = e.getSource();\n        if (source == miClose) {\n            getFrame().dispose();\n        }\n    }\n\n\n    private final KeyListener mainKeyListener = new KeyAdapter() {\n        @Override\n        public void keyPressed(KeyEvent e) {\n            KeyStroke keyStroke = KeyStroke.getKeyStroke(e.getKeyCode(), e.getModifiersEx(), false);\n            JMenuItem menuItem = menuKeyStrokes.get(keyStroke);\n            if (menuItem != null) {\n                actionPerformed(new ActionEvent(menuItem, 0, null));\n                e.consume();\n                return;\n            }\n            super.keyPressed(e);\n        }\n    };\n\n    /**\n     * Set main component that will be listen key codes to fix issue with not workings menu accelerators\n     *\n     * @param comp main component for listing\n     * @param menuBar menu bar\n     */\n    public void setMainKeyListener(Component comp, JMenuBar menuBar) {\n        fillMenuKeyStrokes(menuBar);\n        comp.addKeyListener(mainKeyListener);\n    }\n\n    private static boolean isProblemKey(KeyStroke keyStroke) {\n        if (keyStroke == null) {\n            return false;\n        }\n        int keyCode = keyStroke.getKeyCode();\n        return (keyCode >= KeyEvent.VK_F1 && keyCode <= KeyEvent.VK_F12) ||\n                (keyCode >= KeyEvent.VK_LEFT && keyCode <= KeyEvent.VK_DOWN) ||\n                keyCode == KeyEvent.VK_ENTER || keyCode == KeyEvent.VK_TAB;\n    }\n\n    private static boolean isProblemMenuItem(JMenuItem menuItem) {\n        return menuItem != null && isProblemKey(menuItem.getAccelerator());\n    }\n\n\n    /**\n     * Fills map for all keycodes that can be not processed properly in swing\n     *\n     * @param menuBar menu bar\n     */\n    private void fillMenuKeyStrokes(JMenuBar menuBar) {\n        menuKeyStrokes = new HashMap<>();\n        for (int menuIndex = 0; menuIndex < menuBar.getMenuCount(); menuIndex++) {\n            JMenu menu = menuBar.getMenu(menuIndex);\n            for (int itemIndex = 0; itemIndex < menu.getItemCount(); itemIndex++) {\n                JMenuItem menuItem = menu.getItem(itemIndex);\n                if (isProblemMenuItem(menuItem)) {\n                    menuKeyStrokes.put(menuItem.getAccelerator(), menuItem);\n                }\n            }\n        }\n    }\n\n    protected static String i18n(String key, String... params) {\n        return Translator.get(key, params);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/FileViewersList.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer;\n\nimport com.mucommander.ui.action.TcAction;\nimport com.mucommander.ui.action.impl.EditAction;\nimport com.mucommander.ui.action.impl.ViewAction;\nimport com.mucommander.ui.viewer.text.TextEditor;\n\nimport javax.swing.Icon;\nimport java.awt.Frame;\nimport java.io.File;\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Created on 09/06/16.\n * @author Oleg Trifonov\n */\npublic class FileViewersList {\n    private static final List<FileRecord> files = new ArrayList<>();\n    private static long lastUpdateTime;\n\n    /**\n     *\n     */\n    public static class FileRecord {\n        final public String fileName;\n        final public String shortName;\n        final public Class viewerClass;\n        final public WeakReference<FileFrame> fileFrameRef;\n\n        FileRecord(String fileName, FileFrame fileFrame) {\n            this.fileName = fileName;\n            this.shortName = new File(fileName).getName();\n            this.viewerClass = fileFrame.getFilePresenter().getClass();\n            this.fileFrameRef = new WeakReference<>(fileFrame);\n        }\n\n        @Override\n        public String toString() {\n            return fileName;\n        }\n\n        public Icon getIcon() {\n            //Icon icon = FileIconsCache.getInstance().getIcon(fileRecord.fileName);\n            if (viewerClass == TextEditor.class) {\n                return TcAction.getStandardIcon(EditAction.class);\n            } else {\n                return TcAction.getStandardIcon(ViewAction.class);\n            }\n        }\n\n    }\n\n    private static void buildFilesList(List<FileRecord> fileNames) {\n        fileNames.clear();\n        Frame frames[] = Frame.getFrames();\n        for (Frame frame : frames) {\n            // Test if Frame is not hidden (disposed), Frame.getFrames() returns both active and disposed frames\n            if (frame.isShowing() && (frame instanceof FileFrame)) {\n                // Use frame's window title\n                fileNames.add(new FileRecord(frame.getTitle(), (FileFrame)frame));\n            }\n        }\n    }\n\n\n\n    public static void update() {\n        buildFilesList(files);\n        lastUpdateTime = System.currentTimeMillis();\n    }\n\n    public static List<FileRecord> getFiles() {\n        return files;\n    }\n\n    public static long getLastUpdateTime() {\n        return lastUpdateTime;\n    }\n\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/UserCancelledException.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.viewer;\n\n/**\n * This exception is thrown by {@link com.mucommander.ui.viewer.ViewerRegistrar} and\n * {@link com.mucommander.ui.viewer.EditorRegistrar} when the user has cancelled the view/edit operation.\n *\n * @author Maxence Bernard\n */\npublic class UserCancelledException extends Exception {\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/ViewerFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.viewer;\n\nimport com.mucommander.commons.file.AbstractFile;\n\n/**\n * A common interface for instanciating {@link FileViewer} implementations, and finding out if a viewer is capable\n * of viewing a particular file.\n *\n * @author Nicolas Rinaudo, Maxence Bernard\n */\npublic interface ViewerFactory {\n    /**\n     * Returns <code>true</code> if this factory can create a file viewer for the specified file.\n     * <p>\n     * The FileEditor may base its decision strictly upon the file's name and its extension or may wish to read some of\n     * the file and compare it to a magic number.\n     *\n     * @param  file file for which a viewer must be created.\n     * @throws WarnUserException if the specified file can be viewed after the warning message contained in the\n     * exception is displayed to the end user.\n     * @return      <code>true</code> if this factory can create a file viewer for the specified file.\n     */\n    boolean canViewFile(AbstractFile file) throws WarnUserException;\n\n    /**\n     * Returns a new instance of {@link FileViewer}.\n     * \n     * @return a new instance of {@link FileViewer}.\n     */\n    FileViewer createFileViewer();\n\n    /**\n     * Returns a name for ViewAs list\n     *\n     * @return\n     */\n    String getName();\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/ViewerFrame.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n \npackage com.mucommander.ui.viewer;\n\nimport java.awt.Dimension;\nimport java.awt.Image;\nimport java.io.IOException;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.main.MainFrame;\n\n\n/**\n * A specialized <code>JFrame</code> that displays a {@link FileViewer} for a given file.\n * The {@link FileViewer} instance is provided by {@link ViewerRegistrar}.\n *\n * @author Maxence Bernard, Arik Hadas\n */\npublic class ViewerFrame extends FileFrame {\n\t\n    private final static Dimension MIN_DIMENSION = new Dimension(500, 360);\n\n    private final ViewerFactory defaultFactory;\n\n    private AbstractFile file;\n\n    /**\n     * Creates a new ViewerFrame to start viewing the given file.\n     *\n     * <p>This constructor has package access only, ViewerFrame need to be created can\n     * {@link ViewerRegistrar#createViewerFrame(MainFrame, AbstractFile, Image)}.\n     */\n    ViewerFrame(MainFrame mainFrame, AbstractFile file, Image icon, ViewerFactory defaultFactory) {\n        super(mainFrame, icon);\n        this.defaultFactory = defaultFactory;\n        initContentPane(file);\n    }\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    @Override\n    public Dimension getMinimumSize() {\n    \treturn MIN_DIMENSION;\n    }\n\n\t@Override\n\tprotected FilePresenter createFilePresenter(AbstractFile file) throws UserCancelledException {\n\t    this.file = file;\n\t\treturn ViewerRegistrar.createFileViewer(file, ViewerFrame.this, defaultFactory);\n\t}\n\n\t@Override\n\tprotected String getGenericErrorDialogTitle() {\n\t\treturn Translator.get(\"file_viewer.view_error_title\");\n\t}\n\n\t@Override\n    protected String getGenericErrorDialogMessage() {\n\t\treturn Translator.get(\"file_viewer.view_error\");\n\t}\n\n    @Override\n    public void dispose() {\n        if (file != null) {\n            try {\n                file.closePushbackInputStream();\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n            file = null;\n        }\n        super.dispose();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/ViewerRegistrar.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.viewer;\n\nimport java.awt.Cursor;\nimport java.awt.Frame;\nimport java.awt.Image;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileProtocols;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.commons.runtime.OsVersion;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.dialog.QuestionDialog;\nimport com.mucommander.ui.main.MainFrame;\nimport com.mucommander.ui.main.WindowManager;\nimport com.mucommander.ui.viewer.audio.AudioFactory;\nimport com.mucommander.ui.viewer.hex.HexFactory;\nimport com.mucommander.ui.viewer.hex.HexViewer;\nimport com.mucommander.ui.viewer.html.HtmlViewer;\nimport com.mucommander.ui.viewer.pdf.PdfViewer;\nimport com.mucommander.ui.viewer.text.TextViewer;\nimport net.sf.jftp.gui.tasks.ImageViewer;\n\n\n/**\n * ViewerRegistrar maintains a list of registered file viewers and provides methods to dynamically register file viewers\n * and create appropriate FileViewer (Panel) and ViewerFrame (Window) instances for a given AbstractFile.\n *\n * @author Maxence Bernard, Arik Hadas\n */\npublic class ViewerRegistrar {\n\t\n    /** List of registered file viewers */ \n    private final static List<ViewerFactory> viewerFactories = new ArrayList<>();\n\n    static {\n        registerFileViewer(new com.mucommander.ui.viewer.pdf.PdfFactory());\n        registerFileViewer(new com.mucommander.ui.viewer.djvu.DjvuFactory());\n        registerFileViewer(new com.mucommander.ui.viewer.image.ImageFactory());\n        registerFileViewer(new AudioFactory());\n        registerFileViewer(new com.mucommander.ui.viewer.html.HtmlFactory());\n        registerFileViewer(new com.mucommander.ui.viewer.text.TextFactory());\n        // The HexFactory must be the last FileViewer to be registered (otherwise it would open other factories file types)\n        registerFileViewer(new com.mucommander.ui.viewer.hex.HexFactory());\n    }\n    \n    \n    /**\n     * Registers a FileViewer.\n     * @param factory file viewer factory to register.\n     */\n    private static void registerFileViewer(ViewerFactory factory) {\n        viewerFactories.add(factory);\n    }\n        \n\t\n    /**\n     * Creates and returns a ViewerFrame to start viewing the given file. The ViewerFrame will be monitored\n     * so that if it is the last window on screen when it is closed by the user, it will trigger the shutdown sequence.\n     *\n     * @param mainFrame the parent MainFrame instance\n     * @param file the file that will be displayed by the returned ViewerFrame\n     * @param icon window's icon.\n     * @param defaultFactory postponed action\n     * @param createListener this lambda will be executed after viewer frame creation\n     */\n    public static void createViewerFrame(MainFrame mainFrame, AbstractFile file, Image icon, ViewerFactory defaultFactory, FileFrameCreateListener createListener) {\n        // Check if this file is already opened\n        for (FileViewersList.FileRecord fr: FileViewersList.getFiles()) {\n            if (fr.fileName.equals(file.getAbsolutePath()) && fr.viewerClass != null) {\n                Class viewerClass = fr.viewerClass;\n                if (viewerClass.equals(TextViewer.class) || viewerClass.equals(HexViewer.class) || viewerClass.equals(HtmlViewer.class) ||\n                        viewerClass.equals(ImageViewer.class) || viewerClass.equals(PdfViewer.class)) {\n                    FileFrame openedFrame = fr.fileFrameRef.get();\n                    if (openedFrame != null) {\n                        openedFrame.toFront();\n                    }\n                    if (createListener != null) {\n                        createListener.onCreate(openedFrame);\n                    }\n                    return;\n                }\n            }\n        }\n        new FilePreloadWorker(file, mainFrame, () -> {\n            ViewerFrame frame = new ViewerFrame(mainFrame, file, icon, defaultFactory);\n\n            // Use new Window decorations introduced in Mac OS X 10.5 (Leopard)\n            if (OsFamily.MAC_OS_X.isCurrent() && OsVersion.MAC_OS_X_10_5.isCurrentOrHigher()) {\n                // Displays the document icon in the window title bar, works only for local files\n                if (file.getURL().getScheme().equals(FileProtocols.FILE)) {\n                    frame.getRootPane().putClientProperty(\"Window.documentFile\", file.getUnderlyingFileObject());\n                }\n            }\n\n            // WindowManager will listen to window closed events to trigger shutdown sequence\n            // if it is the last window visible\n            frame.addWindowListener(WindowManager.getInstance());\n\n            if (createListener != null) {\n                createListener.onCreate(frame);\n            }\n\n        }).execute();\n/*\n        TaskWidget taskWidget = new TaskWidget();\n        taskWidget.setText(file.getName());\n        mainFrame.getStatusBar().getTaskPanel().addTask(taskWidget);\n        mainFrame.getStatusBar().revalidate();\n        mainFrame.getStatusBar().repaint();\n\n        new SwingWorker<Void, Void>() {\n            volatile Throwable readException;\n            volatile int progress;\n            @Override\n            protected Void doInBackground() throws Exception {\n\n                try {\n                    publish();\n                    final PushbackInputStream is = file.getPushBackInputStream(EncodingDetector.MAX_RECOMMENDED_BYTE_SIZE);\n                    if (is instanceof HasProgress) {\n                        Thread progressThread = new Thread() {\n                            @Override\n                            public void run() {\n                                while (true) {\n                                    progress = ((HasProgress) is).getProgress();\n                                    publish();\n                                    if (progress >= 100 || readException != null) {\n                                        progress = -1;\n                                        publish();\n                                        break;\n                                    }\n                                    try {\n                                        Thread.sleep(100);\n                                    } catch (InterruptedException ignored) {}\n                                }\n                            }\n                        };\n                        progressThread.setName(\"ProgressInputStreamThread\");\n                        progressThread.start();\n\n                    }\n                    is.read();\n                    is.unread(1);\n                } catch (Throwable e) {\n                    readException = e;\n                }\n                return null;\n            }\n\n            @Override\n            protected void process(List<Void> chunks) {\n                taskWidget.setProgress(progress);\n            }\n\n            @Override\n            protected void done() {\n                taskWidget.removeFromPanel();\n                if (readException != null) {\n                    mainFrame.getStatusBar().setStatusInfo(Translator.get(\"text_viewer.open_file_error\"));\n                }\n                ViewerFrame frame = new ViewerFrame(mainFrame, file, icon, defaultFactory);\n\n                // Use new Window decorations introduced in Mac OS X 10.5 (Leopard)\n                if (OsFamily.MAC_OS_X.isCurrent() && OsVersion.MAC_OS_X_10_5.isCurrentOrHigher()) {\n                    // Displays the document icon in the window title bar, works only for local files\n                    if (file.getURL().getScheme().equals(FileProtocols.FILE)) {\n                        frame.getRootPane().putClientProperty(\"Window.documentFile\", file.getUnderlyingFileObject());\n                    }\n                }\n\n                // WindowManager will listen to window closed events to trigger shutdown sequence\n                // if it is the last window visible\n                frame.addWindowListener(WindowManager.getInstance());\n\n                if (createListener != null) {\n                    createListener.onCreate(frame);\n                }\n            }\n        }.execute();\n*/\n    }\n\n    public static void createViewerFrame(MainFrame mainFrame, AbstractFile file, Image icon) {\n        createViewerFrame(mainFrame, file, icon, null, null);\n    }\n\n    public static void createViewerFrame(MainFrame mainFrame, AbstractFile file, Image icon, FileFrameCreateListener createListener) {\n        createViewerFrame(mainFrame, file, icon, null, createListener);\n    }\n    \n    /**\n     * Creates and returns an appropriate FileViewer for the given file type.\n     *\n     * @param file the file that will be displayed by the returned FileViewer\n     * @param frame the frame in which the FileViewer is shown\n     * @return the created FileViewer, or null if no suitable viewer was found\n     * @throws UserCancelledException if the user has been asked to confirm the operation and canceled\n     */\n    public static FileViewer createFileViewer(AbstractFile file, ViewerFrame frame, ViewerFactory defaultFactory) throws UserCancelledException {\n    \tFileViewer viewer = null;\n        MainFrame mainFrame = frame != null ? frame.getMainFrame() : null;\n        for (ViewerFactory factory : viewerFactories) {\n            if (defaultFactory != null && !factory.getName().equals(defaultFactory.getName())) {\n                continue;\n            }\n            try {\n                if (mainFrame != null) {\n                    mainFrame.getJFrame().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));\n                }\n                if (factory.canViewFile(file)) {\n                    viewer = factory.createFileViewer();\n                    if (mainFrame != null) {\n                        mainFrame.getJFrame().setCursor(Cursor.getDefaultCursor());\n                    }\n                    break;\n                }\n            } catch (WarnUserException e) {\n                if (mainFrame != null) {\n                    mainFrame.getJFrame().setCursor(Cursor.getDefaultCursor());\n                }\n            \t// TODO: question the user how does he want to open the file (as image, text..)\n                // Todo: display a proper warning dialog with the appropriate icon\n            \t\n                QuestionDialog dialog = new QuestionDialog((Frame)null, Translator.get(\"warning\"), Translator.get(e.getMessage()), frame,\n                                                           new String[] {Translator.get(\"file_viewer.open_anyway\"), Translator.get(\"file_viewer.open_hex\"), Translator.get(\"cancel\")},\n                                                           new int[]  {0, 1, 2},\n                                                           0);\n\n                int ret = dialog.getActionValue();\n                if (ret == 0) {\n                    // User confirmed the operation\n                    viewer = factory.createFileViewer();\n                    break;\n                } else if (ret == 1) {\n                    viewer = new HexFactory().createFileViewer();\n                    break;\n                } else {\n                    // User canceled the operation\n                    throw new UserCancelledException();\n                }\n            } catch (Exception e) {\n                if (mainFrame != null) {\n                    mainFrame.getJFrame().setCursor(Cursor.getDefaultCursor());\n                }\n            }\n        }\n        if (viewer != null) {\n            viewer.setFrame(frame);\n        }\n        \n        return viewer;\n    }\n\n\n    public static List<ViewerFactory> getAllViewers(AbstractFile file) {\n        List<ViewerFactory> result = new ArrayList<>();\n        for (ViewerFactory factory : viewerFactories) {\n            try {\n                if (!factory.canViewFile(file)) {\n                    continue;\n                }\n            } catch (WarnUserException ignore) {}\n            result.add(factory);\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/WarnUserException.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.viewer;\n\n/**\n * This exception is thrown by {@link com.mucommander.ui.viewer.ViewerFactory} and\n * {@link com.mucommander.ui.viewer.EditorFactory} when the user should be warned about something before going ahead\n * with viewing/editing a file. {@link #getMessage()} contains the message to display to the user.\n *\n * @author Maxence Bernard\n */\npublic class WarnUserException extends Exception {\n\n    public WarnUserException(String localizedMessage) {\n        super(localizedMessage);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/audio/AudioFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2014 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.audio;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.viewer.FileViewer;\nimport com.mucommander.ui.viewer.ViewerFactory;\n\n\npublic class AudioFactory implements ViewerFactory {\n\n    public final static ExtensionFilenameFilter AUDIO_FILTER = new ExtensionFilenameFilter(new String[] {\".wav\", \".mp3\", \".ogg\", \".mid\"});\n\n    static {\n        AUDIO_FILTER.setCaseSensitive(false);\n    }\n\n\n    @Override\n    public boolean canViewFile(AbstractFile file) {\n        return false;\n//        if (file.isDirectory()) {\n//            return false;\n//        }\n//        return AUDIO_FILTER.accept(file);\n    }\n\n    @Override\n    public FileViewer createFileViewer() {\n        return new AudioViewer();\n    }\n\n    @Override\n    public String getName() {\n        return Translator.get(\"viewer_type.audio\");\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/audio/AudioPlayer.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.audio;\n\nimport javax.swing.JButton;\nimport javax.swing.JPanel;\n\n/**\n * Created on 14/03/14.\n */\npublic class AudioPlayer extends JPanel {\n\n    public AudioPlayer() {\n        super();\n\n        JButton btnPrev = new JButton(\"<<\");\n        JButton btnPlay = new JButton(\">\");\n        JButton btnStop = new JButton(\"x\");\n        JButton btnPause = new JButton(\"||\");\n        JButton btnNext = new JButton(\">>\");\n\n        add(btnPrev);\n        add(btnPlay);\n        add(btnStop);\n        add(btnPause);\n        add(btnNext);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/audio/AudioViewer.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2014 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.audio;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.viewer.FileViewer;\nimport org.fife.ui.StatusBar;\n\nimport javax.sound.sampled.*;\n\n\npublic class AudioViewer extends FileViewer {\n\n    /**\n     * The audio line we'll output sound to; it'll be the default audio device on your system if available\n     */\n    private static SourceDataLine mLine;\n\n    @Override\n    protected void show(AbstractFile file) {\n        setComponentToPresent(new AudioPlayer());\n        System.out.println(\"PLAY \" + file.getURL().toString());\n\n//        ToolFactory.setTurboCharged(true);\n//        IMediaReader reader = ToolFactory.makeReader(file.getAbsolutePath());\n//        reader.addListener(ToolFactory.makeViewer(IMediaViewer.Mode.AUDIO_ONLY));\n//        while (reader.readPacket() == null) {\n//System.out.println('.');\n//            do {} while(false);\n//        }\n//        play(file.getAbsolutePath());\n        System.out.println(\"STOP \" + file.getURL().toString());\n\n\n    }\n\n\n//    private void play(String filename) {\n//        // create a Xuggler container object\n//        IContainer container = IContainer.make();\n//\n//        // Open up the container\n//        if (container.open(filename, IContainer.Type.READ, null) < 0) {\n//            throw new IllegalArgumentException(\"could not open file: \" + filename);\n//        }\n//\n//        // query how many streams the call to open found\n//        int numStreams = container.getNumStreams();\n//System.out.println(\"numStreams \" + numStreams);\n//        // and iterate through the streams to find the first audio stream\n//        int audioStreamId = -1;\n//        IStreamCoder audioCoder = null;\n//        for (int i = 0; i < numStreams; i++) {\n//            // Find the stream object\n//            IStream stream = container.getStream(i);\n//            // Get the pre-configured decoder that can decode this stream;\n//            IStreamCoder coder = stream.getStreamCoder();\n//\n//            if (coder.getCodecType() == ICodec.Type.CODEC_TYPE_AUDIO) {\n//                audioStreamId = i;\n//                audioCoder = coder;\n//                break;\n//            }\n//        }\n//        if (audioStreamId == -1) {\n//            throw new RuntimeException(\"could not find audio stream in container: \" + filename);\n//        }\n//\n//        // Now we have found the audio stream in this file.  Let's open up our decoder so it can do work.\n//        if (audioCoder.open(null, null  ) < 0) {\n//            throw new RuntimeException(\"could not open audio decoder for container: \" + filename);\n//        }\n//\n//        // And once we have that, we ask the Java Sound System to get itself ready.\n//        openJavaSound(audioCoder);\n//\n//        // Now, we start walking through the container looking at each packet.\n//        IPacket packet = IPacket.make();\n//        while (container.readNextPacket(packet) >= 0) {\n//            // Now we have a packet, let's see if it belongs to our audio stream\n//            if (packet.getStreamIndex() == audioStreamId) {\n//                // We allocate a set of samples with the same number of channels as the coder tells us is in this buffer\n//                // We also pass in a buffer size (1024 in our example), although Xuggler will probably allocate more space\n//                // than just the 1024 (it's not important why)\n//                IAudioSamples samples = IAudioSamples.make(1024, audioCoder.getChannels());\n//\n//                // A packet can actually contain multiple sets of samples (or frames of samples\n//                // in audio-decoding speak).  So, we may need to call decode audio multiple\n//                // times at different offsets in the packet's data.  We capture that here.\n//                int offset = 0;\n//\n//                // Keep going until we've processed all data\n//                while (offset < packet.getSize()) {\n//                    int bytesDecoded = audioCoder.decodeAudio(samples, packet, offset);\n//                    if (bytesDecoded < 0) {\n//                        throw new RuntimeException(\"got error decoding audio in: \" + filename);\n//                    }\n//                    offset += bytesDecoded;\n//\n//                    // Some decoder will consume data in a packet, but will not be able to construct a full set of samples yet.\n//                    // Therefore you should always check if you got a complete set of samples from the decoder\n//                    if (samples.isComplete()) {\n//                        playJavaSound(samples);\n//                    }\n//                }\n//            } else {\n//                // This packet isn't part of our audio stream, so we just silently drop it.\n//                do {} while(false);\n//            }\n//\n//        }\n//        // Technically since we're exiting anyway, these will be cleaned up by the garbage collector... but because we're\n//        // nice people and want to be invited places for Christmas, we're going to show how to clean up.\n//        closeJavaSound();\n//\n//        if (audioCoder != null) {\n//            audioCoder.close();\n//            audioCoder = null;\n//        }\n//        if (container != null) {\n//            container.close();\n//            container = null;\n//        }\n//    }\n//\n//\n//    private static void openJavaSound(IStreamCoder aAudioCoder) {\n//        AudioFormat audioFormat = new AudioFormat(aAudioCoder.getSampleRate(),\n//                (int)IAudioSamples.findSampleBitDepth(aAudioCoder.getSampleFormat()),\n//                aAudioCoder.getChannels(),\n//                true, // xuggler defaults to signed 16 bit samples\n//                false);\n//        DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);\n//        try {\n//            mLine = (SourceDataLine) AudioSystem.getLine(info);\n//            // if that succeeded, try opening the line\n//            mLine.open(audioFormat);\n//            // And if that succeed, start the line\n//            mLine.start();\n//        } catch (LineUnavailableException e) {\n//            throw new RuntimeException(\"could not open audio line\");\n//        }\n//    }\n//\n//    private static void playJavaSound(IAudioSamples aSamples) {\n//        // We're just going to dump all the samples into the line.\n//        byte[] rawBytes = aSamples.getData().getByteArray(0, aSamples.getSize());\n//        mLine.write(rawBytes, 0, aSamples.getSize());\n//    }\n\n    private static void closeJavaSound() {\n        if (mLine != null) {\n            // Wait for the line to finish playing\n            mLine.drain();\n            // Close the line\n            mLine.close();\n            mLine = null;\n        }\n    }\n\n    @Override\n    protected StatusBar getStatusBar() {\n        return new StatusBar();\n    }\n\n    @Override\n    protected void saveStateOnClose() {\n\n    }\n\n    @Override\n    protected void restoreStateOnStartup() {\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/audio/StatusBar.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.audio;\n\nimport org.fife.ui.StatusBarPanel;\n\nimport javax.swing.JLabel;\nimport java.awt.BorderLayout;\nimport java.awt.GridBagConstraints;\n\n/**\n * Created on 14/03/14.\n */\npublic class StatusBar extends org.fife.ui.StatusBar {\n    private final JLabel lbl;\n\n    public StatusBar() {\n        super(\"\");\n\n        lbl = new JLabel();\n        StatusBarPanel panel = new StatusBarPanel(new BorderLayout(), lbl);\n\n        // Make the layout such that different items can be different sizes.\n        GridBagConstraints c = new GridBagConstraints();\n        c.fill = GridBagConstraints.BOTH;\n\n        c.weightx = 0.0;\n        addStatusBarComponent(panel, c);\n    }\n\n    public void set(String s) {\n        lbl.setText(s);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/djvu/DjvuFactory.java",
    "content": "/*\r\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\r\n * Copyright (C) 2013-2016 Oleg Trifonov\r\n *\r\n * trolCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * trolCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\npackage com.mucommander.ui.viewer.djvu;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.viewer.FileViewer;\r\nimport com.mucommander.ui.viewer.ViewerFactory;\r\n\r\n/**\r\n * @author Oleg Trifonov\r\n * Created on 04/08/14.\r\n */\r\npublic class DjvuFactory implements ViewerFactory {\r\n\r\n    public final static ExtensionFilenameFilter DJVU_FILTER = new ExtensionFilenameFilter(new String[]{\".djvu\", \".djv\"});\r\n\r\n    static {\r\n        DJVU_FILTER.setCaseSensitive(false);\r\n    }\r\n\r\n    @Override\r\n    public boolean canViewFile(AbstractFile file) {\r\n        return !file.isDirectory() && DJVU_FILTER.accept(file);\r\n    }\r\n\r\n    @Override\r\n    public FileViewer createFileViewer() {\r\n        return new DjvuViewer();\r\n    }\r\n\r\n    @Override\r\n    public String getName() {\r\n        return Translator.get(\"viewer_type.djvu\");\r\n    }\r\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/djvu/DjvuViewer.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.djvu;\n\nimport com.lizardtech.djvubean.DjVuBean;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.viewer.FileViewer;\nimport org.fife.ui.StatusBar;\n\nimport javax.swing.*;\nimport java.awt.event.KeyAdapter;\nimport java.awt.event.KeyEvent;\nimport java.io.IOException;\n\n/**\n * Created on 04/08/14.\n */\npublic class DjvuViewer extends FileViewer {\n\n    private final DjVuBean djvuBean;\n\n    private final KeyAdapter keyAdapter = new KeyAdapter() {\n        @Override\n        public void keyPressed(KeyEvent e) {\n            boolean shift = e.isShiftDown();\n            switch (e.getKeyCode()) {\n                case KeyEvent.VK_LEFT:\n                    djvuBean.setPage(djvuBean.getPage() - (shift ? 10 : 1));\n                    break;\n                case KeyEvent.VK_RIGHT:\n                    djvuBean.setPage(djvuBean.getPage() + (shift ? 10 : 1));\n                    break;\n            }\n        }\n    };\n\n    DjvuViewer() {\n        super();\n        djvuBean = new DjVuBean();\n        JScrollPane scrollPane = new JScrollPane(djvuBean);\n        djvuBean.addKeyListener(keyAdapter);\n        setComponentToPresent(scrollPane);\n    }\n    @Override\n    protected void show(AbstractFile file) throws IOException {\n        djvuBean.setURL(file.getURL().getJavaNetURL());\n    }\n\n    @Override\n    protected StatusBar getStatusBar() {\n        return null;\n    }\n\n    @Override\n    protected void saveStateOnClose() {\n\n    }\n\n    @Override\n    protected void restoreStateOnStartup() {\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/hex/FindDialog.kt",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2020 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.hex\n\nimport com.jidesoft.hints.ListDataIntelliHints\nimport com.mucommander.cache.TextHistory\nimport com.mucommander.ui.dialog.DialogToolkit\nimport com.mucommander.ui.dialog.FocusDialog\nimport com.mucommander.ui.layout.XAlignedComponentPanel\nimport ru.trolsoft.ui.InputField\nimport java.awt.BorderLayout\nimport java.awt.event.ActionEvent\nimport java.awt.event.ActionListener\nimport javax.swing.JButton\nimport javax.swing.JFrame\n\n/**\n * This dialog allows the user to enter a string or hex value to be searched for in the hex editor.\n * \n * @author Oleg Trifonov\n */\nabstract class FindDialog internal constructor(frame: JFrame?, encoding: String?) :\n    FocusDialog(frame, i18n(\"hex_viewer.find\"), frame), ActionListener {\n    /** The text field where a search dump can be entered  */\n    private val hexField: InputField\n\n    /** The text field where a search string can be entered  */\n    private val textField: InputField\n\n    /** The 'OK' button  */\n    private val okButton: JButton\n\n    init {\n        val contentPane = getContentPane()\n\n        // Text fields panel\n        val compPanel = XAlignedComponentPanel()\n\n        textField = InputField(60, InputField.FilterType.ANY_TEXT).also {\n            it.addActionListener(this)\n            compPanel.addRow(i18n(\"hex_view.text\") + \":\", it, 5)\n            val historyText = TextHistory.getInstance().getList(TextHistory.Type.TEXT_SEARCH)\n            //        new AutoCompletion(textField, historyText).setStrict(false);\n            ListDataIntelliHints(it, historyText).isCaseSensitive = true\n            it.text = \"\"\n        }\n\n        hexField = InputField(60, InputField.FilterType.HEX_DUMP).also {\n            it.addActionListener(this)\n            compPanel.addRow(i18n(\"hex_viewer.hex\") + \":\", it, 10)\n            val historyHex: MutableList<String?>? = TextHistory.getInstance().getList(TextHistory.Type.HEX_DATA_SEARCH)\n            //        new AutoCompletion(hexField, historyHex).setStrict(false);\n            ListDataIntelliHints<String?>(it, historyHex).isCaseSensitive = false\n            it.text = \"\"\n        }\n\n        textField.bindField(hexField)\n        hexField.bindField(textField)\n        setEncoding(encoding)\n\n        contentPane.add(compPanel, BorderLayout.CENTER)\n\n\n        okButton = JButton(i18n(\"ok\"))\n        val cancelButton = JButton(i18n(\"cancel\"))\n        contentPane.add(\n            DialogToolkit.createOKCancelPanel(okButton, cancelButton, getRootPane(), this),\n            BorderLayout.SOUTH\n        )\n\n        // The text field will receive initial focus\n        setInitialFocusComponent(textField)\n    }\n\n    private fun setEncoding(encoding: String?) {\n        textField.textEncoding = encoding\n        hexField.textEncoding = encoding\n    }\n\n    var searchBytes: ByteArray?\n        get() = hexField.bytes\n        set(searchBytes) {\n            hexField.setBytes(searchBytes)\n        }\n\n\n    override fun actionPerformed(e: ActionEvent) {\n        val source = e.getSource()\n        dispose()\n        doSearch(if (source === okButton || source === hexField || source === textField) this.searchBytes else null)\n    }\n\n    override fun saveState() {\n        super.saveState()\n        TextHistory.getInstance().add(TextHistory.Type.TEXT_SEARCH, textField.getText(), true)\n        TextHistory.getInstance().add(TextHistory.Type.HEX_DATA_SEARCH, hexField.getText(), true)\n    }\n\n\n    /**\n     * Search operation listener\n     * @param bytes nul if the dialog was cancelled\n     */\n    protected abstract fun doSearch(bytes: ByteArray?)\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/hex/GotoDialog.kt",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.hex\n\nimport com.mucommander.ui.dialog.DialogToolkit\nimport com.mucommander.ui.dialog.FocusDialog\nimport ru.trolsoft.ui.InputField\nimport java.awt.BorderLayout\nimport java.awt.Frame\nimport java.awt.event.ActionEvent\nimport java.awt.event.ActionListener\nimport java.util.function.LongConsumer\nimport javax.swing.JButton\nimport javax.swing.JLabel\n\n/**\n * Goto address dialog.\n * @author Oleg Trifonov\n */\nclass GotoDialog internal constructor(owner: Frame?, private val maxOffset: Long, private val action: LongConsumer) :\n    FocusDialog(owner, i18n(\"hex_viewer.goto\"), owner), ActionListener {\n    private val edtOffset: InputField\n    private val btnOk: JButton\n\n    init {\n        val contentPane = getContentPane()\n        contentPane.add(JLabel(i18n(\"hex_viewer.goto.offset\") + \":\"), BorderLayout.NORTH)\n\n        edtOffset = object : InputField(16, FilterType.HEX_LONG) {\n            override fun onChange() {\n                val enabled = !edtOffset.isEmpty && edtOffset.getValue() <= this@GotoDialog.maxOffset\n                btnOk.setEnabled(enabled)\n            }\n        }.also {\n            it.text = \"1\"\n            it.addActionListener(this)\n            contentPane.add(it, BorderLayout.CENTER)\n        }\n        btnOk = JButton(i18n(\"ok\"))\n        val cancelButton = JButton(i18n(\"cancel\"))\n        contentPane.add(DialogToolkit.createOKCancelPanel(btnOk, cancelButton, getRootPane(), this), BorderLayout.SOUTH)\n\n        // The text field will receive initial focus\n        setInitialFocusComponent(edtOffset)\n    }\n\n    override fun actionPerformed(e: ActionEvent) {\n        val source = e.getSource()\n        if ((source === btnOk || source === edtOffset) && btnOk.isEnabled) {\n            action.accept(edtOffset.getValue())\n            dispose()\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/hex/HexFactory.kt",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.hex\n\nimport com.mucommander.commons.file.AbstractFile\nimport com.mucommander.ui.viewer.FileViewer\nimport com.mucommander.ui.viewer.ViewerFactory\nimport com.mucommander.utils.text.Translator\n\n/**\n * `ViewerFactory` implementation for creating hex viewers.\n * \n * @author Oleg Trifonov\n */\nclass HexFactory : ViewerFactory {\n    override fun canViewFile(file: AbstractFile): Boolean =\n        !file.isDirectory()\n\n    override fun createFileViewer(): FileViewer =\n        HexViewer()\n\n    override fun getName(): String =\n        Translator.get(\"viewer_type.hex\")\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/hex/HexViewer.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.hex;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.ui.helper.MenuToolkit;\nimport com.mucommander.ui.helper.MnemonicHelper;\nimport com.mucommander.ui.theme.ThemeId;\nimport com.mucommander.ui.viewer.FileViewer;\nimport lombok.extern.slf4j.Slf4j;\nimport ru.trolsoft.calculator.CalculatorDialog;\nimport ru.trolsoft.hexeditor.data.AbstractByteBuffer;\nimport ru.trolsoft.hexeditor.data.TrolCommanderByteBuffer;\nimport ru.trolsoft.hexeditor.events.OffsetChangeListener;\nimport ru.trolsoft.hexeditor.search.ByteBufferSearchUtils;\nimport ru.trolsoft.hexeditor.ui.HexTable;\nimport ru.trolsoft.hexeditor.ui.ViewerHexTableModel;\n\nimport javax.swing.*;\nimport java.awt.Font;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.KeyEvent;\nimport java.io.IOException;\nimport java.io.UnsupportedEncodingException;\nimport static com.mucommander.ui.theme.ThemeManager.getCurrentColor;\nimport static com.mucommander.ui.theme.ThemeManager.getCurrentFont;\n\n/**\n * Hex dump viewer\n * @author Oleg Trifonov\n */\n@Slf4j\npublic class HexViewer extends FileViewer implements ThemeId {\n\n    private static final String DEFAULT_ENCODING = \"windows-1252\";\n\n    private HexTable hexTable;\n    private ViewerHexTableModel model;\n    private AbstractByteBuffer byteBuffer;\n    private StatusBar statusBar;\n    private final String encoding = DEFAULT_ENCODING;\n    private byte[] lastSearchBytes;\n\n    private final JMenu menuView;\n    private final JMenuItem gotoItem;\n    private final JMenuItem findItem;\n    private final JMenuItem findNextItem;\n    private final JMenuItem findPrevItem;\n    private final JMenuItem calculatorItem;\n\n    private GotoDialog dlgGoto;\n    private FindDialog dlgFind;\n\n\n    HexViewer() {\n        super();\n\n        MnemonicHelper menuMnemonicHelper = new MnemonicHelper();\n        menuView = MenuToolkit.addMenu(i18n(\"hex_viewer.view\"), menuMnemonicHelper, null);\n\n        gotoItem = MenuToolkit.addMenuItem(menuView, i18n(\"hex_viewer.goto\"), menuMnemonicHelper, KeyStroke.getKeyStroke(KeyEvent.VK_G, getCtrlOrMetaMask()), this);\n        findItem = MenuToolkit.addMenuItem(menuView, i18n(\"hex_viewer.search\"), menuMnemonicHelper, KeyStroke.getKeyStroke(KeyEvent.VK_F, getCtrlOrMetaMask()), this);\n        findNextItem = MenuToolkit.addMenuItem(menuView, i18n(\"hex_viewer.searchNext\"), menuMnemonicHelper, KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0), this);\n        findPrevItem = MenuToolkit.addMenuItem(menuView, i18n(\"hex_viewer.searchPrev\"), menuMnemonicHelper, KeyStroke.getKeyStroke(KeyEvent.VK_F3, KeyEvent.SHIFT_DOWN_MASK), this);\n        menuView.addSeparator();\n        calculatorItem = MenuToolkit.addMenuItem(menuView, i18n(\"Calculator.label\"), menuMnemonicHelper, KeyStroke.getKeyStroke(KeyEvent.VK_F6, 0), this);\n    }\n\n    private int getCtrlOrMetaMask() {\n        return OsFamily.MAC_OS_X.isCurrent() ? KeyEvent.META_DOWN_MASK : KeyEvent.CTRL_DOWN_MASK;\n    }\n\n    private final OffsetChangeListener offsetChangeListener = new OffsetChangeListener() {\n        @Override\n        public void onChange(long offset) {\n            if (statusBar != null) {\n                statusBar.setOffset(offset);\n                try {\n                    if (byteBuffer.getFileSize() > 0 && offset < byteBuffer.getFileSize()) {\n                        statusBar.setByteValue(byteBuffer.getByte(offset));\n                    }\n                } catch (IOException e) {\n                    log.error(\"Can't show offset\", e);\n                }\n            }\n        }\n    };\n\n\n    @Override\n    protected void show(AbstractFile file) {\n        try {\n            byteBuffer = new TrolCommanderByteBuffer(file);\n            model = new ViewerHexTableModel(byteBuffer);\n            model.load();\n            hexTable = new HexTable(model);\n            hexTable.setBackground(getCurrentColor(HEX_VIEWER_BACKGROUND_COLOR));\n            hexTable.setForeground(getCurrentColor(HEX_VIEWER_HEX_FOREGROUND_COLOR));\n            hexTable.setAlternateBackground(getCurrentColor(HEX_VIEWER_ALTERNATE_BACKGROUND_COLOR));\n            hexTable.setOffsetColumnColor(getCurrentColor(HEX_VIEWER_OFFSET_FOREGROUND_COLOR));\n            hexTable.setAsciiColumnColor(getCurrentColor(HEX_VIEWER_ASCII_FOREGROUND_COLOR));\n            hexTable.setAsciiSelectionBackgroundColor(getCurrentColor(HEX_VIEWER_SELECTED_ASCII_BACKGROUND_COLOR));\n            hexTable.setSelectionBackground(getCurrentColor(HEX_VIEWER_SELECTED_BACKGROUND_COLOR));\n            hexTable.setFont(getCurrentFont(HEX_VIEWER_FONT));\n            hexTable.setAlternateRowBackground(true);\n\n            hexTable.getTableHeader().setFont(new Font(\"Monospaced\", Font.PLAIN, 12));\n            hexTable.setSelectionChangeListener((fromAddress, toAddress) -> {\n                long bytesSelected = Math.abs(toAddress - fromAddress) + 1;\n                if (bytesSelected > 1) {\n                    statusBar.setStatusMessage(\"Selected \" +bytesSelected + \" bytes\");\n                } else {\n                    statusBar.setStatusMessage(\"\");\n                }\n            });\n\n\n            hexTable.setOffsetChangeListener(offsetChangeListener);\n            offsetChangeListener.onChange(0);\n\n            if (statusBar != null) {\n                statusBar.maxOffset = file.getSize() - 1;\n                statusBar.setOffset(hexTable.getCurrentAddress());\n            }\n\n            setComponentToPresent(hexTable);\n            getViewport().setBackground(hexTable.getBackground());\n        } catch (Exception e) {\n            log.error(\"Init error\", e);\n        }\n    }\n\n    @Override\n    protected StatusBar getStatusBar() {\n        if (statusBar == null) {\n            statusBar = new StatusBar();\n            statusBar.setEncoding(encoding);\n        }\n        return statusBar;\n    }\n\n    @Override\n    public JMenuBar getMenuBar() {\n        JMenuBar menuBar = super.getMenuBar();\n        menuBar.add(menuView);\n        setMainKeyListener(this, menuBar);\n        return menuBar;\n    }\n\n    @Override\n    protected void saveStateOnClose() {\n        try {\n            byteBuffer.close();\n        } catch (IOException e) {\n            log.error(\"Close buffer error\", e);\n        }\n        try {\n            getCurrentFile().closePushbackInputStream();\n        } catch (IOException e) {\n            log.error(\"Close stream error\", e);\n        }\n    }\n\n    @Override\n    protected void restoreStateOnStartup() {\n\n    }\n\n\n    public void actionPerformed(ActionEvent e) {\n        Object source = e.getSource();\n\n        if (source == gotoItem && gotoItem.isEnabled()) {\n            gotoOffset();\n        } else if (source == findItem && findItem.isEnabled()) {\n            findFirst();\n        } else if (source == findNextItem && findNextItem.isEnabled()) {\n            findNext();\n        } else if (source == findPrevItem && findPrevItem.isEnabled()) {\n            findPrev();\n        } else if (source == calculatorItem && calculatorItem.isEnabled()) {\n            new CalculatorDialog(getFrame()).showDialog();\n        } else {\n            super.actionPerformed(e);\n        }\n    }\n\n\n    private void findFirst() {\n        if (dlgFind != null && dlgFind.isVisible()) {\n            return;\n        }\n        dlgFind = new FindDialog(getFrame(), encoding) {\n            @Override\n            protected void doSearch(byte[] bytes) {\n                doSearchFromPos(bytes, 0, true);\n            }\n        };\n        dlgFind.setSearchBytes(lastSearchBytes);\n        dlgFind.showDialog();\n    }\n\n    private void doSearchFromPos(byte[] bytes, long pos, boolean next) {\n        lastSearchBytes = bytes;\n        try {\n            long lastSearchResult;\n            if (next) {\n                lastSearchResult = ByteBufferSearchUtils.indexOf(byteBuffer, bytes, pos);\n            } else {\n                lastSearchResult = ByteBufferSearchUtils.indexOfBackward(byteBuffer, bytes, pos);\n            }\n            if (lastSearchResult >= 0) {\n                hexTable.gotoOffset(lastSearchResult);\n                clearStatusMessage();\n            } else {\n                if (statusBar != null) {\n                    statusBar.setStatusMessage(i18n(\"hex_viewer.search_not_found\"));\n                }\n            }\n        } catch (IOException e) {\n            log.error(\"Search error\", e);\n        }\n    }\n\n\n    private void findNext() {\n        if (lastSearchBytes != null && lastSearchBytes.length > 0) {\n            long pos = hexTable.getCurrentAddress()+1;\n            doSearchFromPos(lastSearchBytes, pos, true);\n        }\n    }\n\n    private void findPrev() {\n        if (lastSearchBytes != null && lastSearchBytes.length > 0) {\n            long pos = hexTable.getCurrentAddress()-1;\n            doSearchFromPos(lastSearchBytes, pos, false);\n        }\n    }\n\n    private void gotoOffset() {\n        if (dlgGoto == null) {\n            dlgGoto = new GotoDialog(getFrame(), model.getSize() - 1, (offset) -> hexTable.gotoOffset(offset));\n        }\n        if (!dlgGoto.isVisible()) {\n            dlgGoto.showDialog();\n        }\n    }\n\n\n    @Override\n    public void setSearchedText(String searchedText) {\n        try {\n            lastSearchBytes = searchedText.getBytes(encoding);\n        } catch (UnsupportedEncodingException e) {\n            log.error(\"setSearchedText\", e);\n        }\n    }\n\n    @Override\n    public void setSearchedBytes(byte[] searchedBytes) {\n        if (searchedBytes != null) {\n            lastSearchBytes = searchedBytes;\n        }\n    }\n\n\n    private void setStatusMessage(String msg) {\n        if (statusBar != null) {\n            statusBar.setStatusMessage(msg);\n        }\n    }\n\n    private void clearStatusMessage() {\n        setStatusMessage(\"\");\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/hex/StatusBar.kt",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.hex\n\nimport com.mucommander.ui.main.statusbar.FileWindowsListButton\nimport org.fife.ui.StatusBar\nimport org.fife.ui.StatusBarPanel\nimport ru.trolsoft.utils.StrUtils\nimport java.awt.BorderLayout\nimport java.awt.Font\nimport java.awt.GridBagConstraints\nimport javax.swing.JLabel\n\n/**\n * \n */\nclass StatusBar : StatusBar(\"\") {\n    private val lbFiles: FileWindowsListButton = FileWindowsListButton(true)\n\n    private val lblOffset: JLabel\n    private val lblEncoding: JLabel\n    private val lblValue: JLabel\n\n    @JvmField\n    var maxOffset: Long = -1\n\n\n    init {\n        val panelWindows = StatusBarPanel(BorderLayout())\n        panelWindows.add(lbFiles)\n\n        lblOffset = createLabel()\n        val panelOffset = StatusBarPanel(BorderLayout(), lblOffset)\n\n        lblEncoding = createLabel()\n        val panelEncoding = StatusBarPanel(BorderLayout(), lblEncoding)\n\n        lblValue = createLabel()\n        val panelValue = StatusBarPanel(BorderLayout(), lblValue)\n\n        // Make the layout such that different items can be different sizes.\n        val c = GridBagConstraints().apply {\n            fill = GridBagConstraints.BOTH\n            weightx = 0.0\n        }\n        addStatusBarComponent(panelWindows, c)\n        addStatusBarComponent(panelOffset, c)\n        addStatusBarComponent(panelValue, c)\n        addStatusBarComponent(panelEncoding, c)\n    }\n\n    private fun createLabel(): JLabel {\n        val lbl = JLabel()\n        val fnt = lbl.getFont()\n        lbl.setFont(Font(Font.MONOSPACED, fnt.getStyle(), fnt.getSize()))\n        return lbl\n    }\n\n    fun setOffset(offset: Long) {\n        var str = StrUtils.dwordToHexStr(offset)\n        if (maxOffset >= 0) {\n            str += \" / \" + StrUtils.dwordToHexStr(maxOffset)\n        }\n        lblOffset.setText(str)\n    }\n\n    fun setEncoding(encoding: String?) {\n        lblEncoding.setText(encoding)\n    }\n\n    fun setByteValue(v: Byte) {\n        val s = StrUtils.byteToBinaryStr(v) + \" - \" + StrUtils.byteToOctalStr(v)\n        lblValue.setText(s)\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/html/HtmlFactory.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.html;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.viewer.FileViewer;\nimport com.mucommander.ui.viewer.ViewerFactory;\n\n/**\n * <code>ViewerFactory</code> implementation for creating html viewers.\n *\n * @author Oleg Trifonov\n */\npublic class HtmlFactory implements ViewerFactory {\n\n    public final static ExtensionFilenameFilter HTML_FILTER = new ExtensionFilenameFilter(new String[] {\n            \".htm\", \".html\"\n    });\n\n    public static Boolean webViewIsAvailable;\n\n    static {\n        HTML_FILTER.setCaseSensitive(false);\n    }\n\n\n    @Override\n    public boolean canViewFile(AbstractFile file) {\n        if (webViewIsAvailable == null) {\n            webViewIsAvailable = isWebViewIsAvailable();\n        }\n        return webViewIsAvailable && !file.isDirectory() && HTML_FILTER.accept(file);\n    }\n\n    @Override\n    public FileViewer createFileViewer() {\n        return new HtmlViewer();\n    }\n\n    @Override\n    public String getName() {\n        return Translator.get(\"viewer_type.html\");\n    }\n\n\n    private static boolean isWebViewIsAvailable() {\n        try {\n            Class.forName(\"javafx.application.Platform\");\n            Class.forName(\"javafx.embed.swing.JFXPanel\");\n            Class.forName(\"javafx.scene.Group\");\n            Class.forName(\"javafx.scene.Scene\");\n            Class.forName(\"javafx.scene.web.WebView\");\n            return true;\n        } catch (Throwable e) {\n            return false;\n        }\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/html/HtmlViewer.java",
    "content": "/*\r\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\r\n * Copyright (C) 2014-2016 Oleg Trifonov\r\n *\r\n * trolCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * trolCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\npackage com.mucommander.ui.viewer.html;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.impl.local.LocalFile;\r\nimport com.mucommander.commons.io.StreamUtils;\r\nimport com.mucommander.ui.viewer.FileViewer;\r\nimport javafx.application.Platform;\r\nimport javafx.embed.swing.JFXPanel;\r\nimport javafx.scene.Group;\r\nimport javafx.scene.Scene;\r\nimport javafx.scene.web.WebEngine;\r\nimport javafx.scene.web.WebView;\r\nimport org.fife.ui.StatusBar;\r\n\r\nimport java.io.ByteArrayOutputStream;\r\nimport java.io.IOException;\r\nimport java.io.InputStream;\r\n\r\n/**\r\n * @author Oleg Trifonov\r\n */\r\npublic class HtmlViewer extends FileViewer {\r\n    private WebView webView;\r\n    private String url;\r\n    private String content;\r\n\r\n    HtmlViewer() {\r\n        super();\r\n    }\r\n\r\n    @Override\r\n    protected void show(final AbstractFile file) throws IOException {\r\n        boolean localFile = file.getTopAncestor() instanceof LocalFile;\r\n        if (localFile) {\r\n            url = file.getJavaNetURL().toString();\r\n        } else {\r\n            InputStream is = file.getInputStream();\r\n            ByteArrayOutputStream out = new ByteArrayOutputStream();\r\n            StreamUtils.copyStream(is, out);\r\n            content = out.toString();\r\n        }\r\n        try {\r\n            final JFXPanel jfxPanel = new JFXPanel();\r\n            setComponentToPresent(jfxPanel);\r\n            getViewport().addChangeListener(e -> {\r\n                if (webView != null) {\r\n                    int w = getViewport().getWidth();\r\n                    int h = getViewport().getHeight();\r\n                    webView.setMinWidth(w);\r\n                    webView.setMaxWidth(w);\r\n                    webView.setMinHeight(h);\r\n                    webView.setMaxHeight(h);\r\n                }\r\n            });\r\n\r\n            Platform.setImplicitExit(false);\r\n            Platform.runLater(() -> initFX(jfxPanel));\r\n        } catch (Throwable e) {\r\n            e.printStackTrace();\r\n        }\r\n    }\r\n\r\n\r\n    /* Creates a WebView and fires up google.com */\r\n    private void initFX(final JFXPanel fxPanel) {\r\n        try {\r\n            Group group = new Group();\r\n\r\n            Scene scene = new Scene(group);\r\n            fxPanel.setScene(scene);\r\n\r\n            webView = new WebView();\r\n            group.getChildren().add(webView);\r\n\r\n            WebEngine webEngine = webView.getEngine();\r\n            if (url != null) {\r\n                webEngine.load(url);\r\n            } else {\r\n                webEngine.loadContent(content);\r\n            }\r\n        } catch (Throwable t) {\r\n            t.printStackTrace();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    protected StatusBar getStatusBar() {\r\n        return null;\r\n    }\r\n\r\n    @Override\r\n    protected void saveStateOnClose() {\r\n    }\r\n\r\n    @Override\r\n    protected void restoreStateOnStartup() {\r\n\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/image/ImageFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.viewer.image;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.viewer.FileViewer;\nimport com.mucommander.ui.viewer.ViewerFactory;\n\n/**\n * <code>ViewerFactory</code> implementation for creating image viewers.\n *\n * @author Nicolas Rinaudo\n */\npublic class ImageFactory implements ViewerFactory {\n    /** Used to IMAGE_FILTER out file extensions that the image viewer cannot open. */\n    public final static ExtensionFilenameFilter IMAGE_FILTER = new ExtensionFilenameFilter(new String[] {\n            \".png\", \".gif\", \".jpg\", \".jpeg\", \".bmp\", \".wbmp\",   // java built in formats\n            \".ico\", \".psd\", \".tga\", \".tiff\", \".tif\", \".pnm\", \".pbm\", \".pgm\", \".ppm\", \".svg\" // additional formats\n    });\n    static {\n        IMAGE_FILTER.setCaseSensitive(false);\n    }\n\n    public ImageFactory() {\n    }\n\n    @Override\n    public boolean canViewFile(AbstractFile file) {\n        // Do not allow directories\n        if (file.isDirectory()) {\n            return false;\n        }\n        if (\"scr\".equalsIgnoreCase(file.getExtension()) && file.getSize() == ZxSpectrumScrImage.SCR_IMAGE_FILE_SIZE ) {\n            return true;\n        }\n        return IMAGE_FILTER.accept(file);\n    }\n\n    public FileViewer createFileViewer() {\n        return new ImageViewer();\n    }\n\n    @Override\n    public String getName() {\n        return Translator.get(\"viewer_type.image\");\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/image/ImageViewer.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.ui.viewer.image;\n\nimport java.awt.*;\nimport java.awt.event.*;\nimport java.awt.image.BufferedImage;\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\n\nimport javax.imageio.ImageIO;\nimport javax.imageio.spi.IIORegistry;\nimport javax.swing.*;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.conf.TcSnapshot;\nimport com.mucommander.ui.dialog.InformationDialog;\nimport com.mucommander.ui.helper.MenuToolkit;\nimport com.mucommander.ui.helper.MnemonicHelper;\nimport com.mucommander.ui.theme.ColorChangedEvent;\nimport com.mucommander.ui.theme.FontChangedEvent;\nimport com.mucommander.ui.theme.Theme;\nimport com.mucommander.ui.theme.ThemeListener;\nimport com.mucommander.ui.theme.ThemeManager;\nimport com.mucommander.ui.viewer.FileFrame;\nimport com.mucommander.ui.viewer.FileViewer;\nimport com.twelvemonkeys.imageio.plugins.bmp.BMPImageReaderSpi;\nimport com.twelvemonkeys.imageio.plugins.bmp.CURImageReaderSpi;\nimport com.twelvemonkeys.imageio.plugins.dds.DDSImageReaderSpi;\nimport com.twelvemonkeys.imageio.plugins.hdr.HDRImageReaderSpi;\nimport com.twelvemonkeys.imageio.plugins.icns.ICNSImageReaderSpi;\nimport com.twelvemonkeys.imageio.plugins.iff.IFFImageReaderSpi;\nimport com.twelvemonkeys.imageio.plugins.jpeg.JPEGImageReaderSpi;\nimport com.twelvemonkeys.imageio.plugins.pcx.PCXImageReaderSpi;\nimport com.twelvemonkeys.imageio.plugins.pnm.PNMImageReaderSpi;\nimport com.twelvemonkeys.imageio.plugins.psd.PSDImageReaderSpi;\nimport com.twelvemonkeys.imageio.plugins.sgi.SGIImageReaderSpi;\nimport com.twelvemonkeys.imageio.plugins.tga.TGAImageReaderSpi;\nimport com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi;\nimport com.twelvemonkeys.imageio.plugins.webp.WebPImageReaderSpi;\nimport com.twelvemonkeys.imageio.plugins.xwd.XWDImageReaderSpi;\nimport lombok.extern.slf4j.Slf4j;\nimport net.sf.image4j.codec.ico.ICODecoder;\nimport org.apache.batik.transcoder.Transcoder;\nimport org.apache.batik.transcoder.TranscoderException;\nimport org.apache.batik.transcoder.TranscoderInput;\nimport org.apache.batik.transcoder.TranscoderOutput;\nimport org.apache.batik.transcoder.image.PNGTranscoder;\nimport org.apache.commons.imaging.ImageReadException;\nimport ru.trolsoft.ui.TMenuSeparator;\n\n\n/**\n * A simple image viewer, capable of displaying <code>PNG</code>, <code>GIF</code> and <code>JPEG</code> images. \n *\n * @author Maxence Bernard, Arik Hadas, Oleg Trifonov\n */\n@Slf4j\nclass ImageViewer extends FileViewer implements ActionListener {\n    private static final Cursor CURSOR_WAIT = new Cursor(Cursor.WAIT_CURSOR);\n    private static final Cursor CURSOR_DEFAULT = Cursor.getDefaultCursor();\n    private static final Cursor CURSOR_CROSS = new Cursor(Cursor.CROSSHAIR_CURSOR);\n\n    private static final Color TRANSPARENT_COLOR_1 = new Color(0x666666);\n    private static final Color TRANSPARENT_COLOR_2 = new Color(0x999999);\n    private static final int TRANSPARENT_GRID_STEP = 8;\n\n    private BufferedImage image;\n    //private BufferedImage scaledImage;\n    private double zoomFactor;\n    private boolean vectorImage;\n    /** Menu bar */\n    private final JMenu controlsMenu;\n    // Items //\n    private final JMenuItem prevImageItem;\n    private final JMenuItem nextImageItem;\n    private final JMenuItem zoomInItem;\n    private final JMenuItem zoomOutItem;\n\n    private final ImageViewerImpl imageViewerImpl;\n    private List<AbstractFile> filesInDirectory;\n    private int indexInDirectory = -1;\n\n    private StatusBar statusBar;\n\n    private boolean waitCursorMode = false;\n    private boolean hasTransparentPixels;\n\n    /**\n     * Unknown swing issue = MouseMovement events doesn't received on windows show.\n     * To fix it we make windows resize and undo it after start.\n     */\n    private boolean mouseMovementIssueFixed = false;\n\n    private static boolean initiated = false;\n\n    public static void init() {\n        if (initiated) {\n            return;\n        }\n        try {\n            IIORegistry registry = IIORegistry.getDefaultInstance();\n            registry.registerServiceProvider(new JPEGImageReaderSpi());\n            registry.registerServiceProvider(new PSDImageReaderSpi());\n            registry.registerServiceProvider(new TIFFImageReaderSpi());\n            registry.registerServiceProvider(new BMPImageReaderSpi());\n            registry.registerServiceProvider(new CURImageReaderSpi());\n            registry.registerServiceProvider(new DDSImageReaderSpi());\n            registry.registerServiceProvider(new HDRImageReaderSpi());\n            registry.registerServiceProvider(new ICNSImageReaderSpi());\n            registry.registerServiceProvider(new IFFImageReaderSpi());\n            registry.registerServiceProvider(new PCXImageReaderSpi());\n            registry.registerServiceProvider(new PNMImageReaderSpi());\n            registry.registerServiceProvider(new SGIImageReaderSpi());\n            registry.registerServiceProvider(new TGAImageReaderSpi());\n            registry.registerServiceProvider(new WebPImageReaderSpi());\n            registry.registerServiceProvider(new XWDImageReaderSpi());\n        } catch (Exception e) {\n            log.error(\"Error registering additional image service providers\", e);\n        }\n        initiated = true;\n    }\n\n    ImageViewer() {\n        init();\n    \timageViewerImpl = new ImageViewerImpl();\n\n    \tsetComponentToPresent(imageViewerImpl);\n\n    \t// create Go menu\n    \tMnemonicHelper menuMnemonicHelper = new MnemonicHelper();\n        controlsMenu = MenuToolkit.addMenu(i18n(\"image_viewer.controls_menu\"), menuMnemonicHelper, null);\n\n        nextImageItem = MenuToolkit.addMenuItem(controlsMenu, i18n(\"image_viewer.next_image\"), menuMnemonicHelper, KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), this);\n        prevImageItem = MenuToolkit.addMenuItem(controlsMenu, i18n(\"image_viewer.previous_image\"), menuMnemonicHelper, KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), this);\n        controlsMenu.add(new TMenuSeparator());\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            zoomInItem = MenuToolkit.addMenuItem(controlsMenu, i18n(\"image_viewer.zoom_in\"), menuMnemonicHelper, KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), this);\n            zoomOutItem = MenuToolkit.addMenuItem(controlsMenu, i18n(\"image_viewer.zoom_out\"), menuMnemonicHelper, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), this);\n        } else {\n            zoomInItem = MenuToolkit.addMenuItem(controlsMenu, i18n(\"image_viewer.zoom_in\"), menuMnemonicHelper, KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0), this);\n            zoomOutItem = MenuToolkit.addMenuItem(controlsMenu, i18n(\"image_viewer.zoom_out\"), menuMnemonicHelper, KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0), this);\n        }\n    }\n\n    @Override\n    public JMenuBar getMenuBar() {\n    \tJMenuBar menuBar = super.getMenuBar();\n        menuBar.add(controlsMenu);\n        setMainKeyListener(imageViewerImpl, menuBar);\n    \treturn menuBar;\n    }\n\n    @Override\n    protected StatusBar getStatusBar() {\n        if (statusBar == null) {\n            statusBar = new StatusBar();\n        }\n        return statusBar;\n    }\n\n    @Override\n    protected void saveStateOnClose() {\n        // Run GC for big images\n        if (image != null && image.getWidth()*image.getHeight() > 1024*200) {\n            System.gc();\n        }\n    }\n\n    @Override\n    protected void restoreStateOnStartup() {\n\n    }\n\n    private synchronized void loadImage(AbstractFile file) throws IOException, ImageReadException {\n        setFrameCursor(CURSOR_WAIT);\n\n        if (statusBar != null) {\n            statusBar.setFileSize(file.getSize());\n            statusBar.setDateTime(file.getLastModifiedDate());\n        }\n        loadImageFile(file);\n        int imageWidth = image.getWidth();\n        int imageHeight = image.getHeight();\n        this.hasTransparentPixels = image.getColorModel().hasAlpha();\n\n        if (statusBar != null) {\n            statusBar.setImageSize(imageWidth, imageHeight);\n        }\n\n        this.zoomFactor = 1.0;\n        Dimension screen = TcSnapshot.getScreenSize();\n\n        double zoomFactorX = 1.0 * screen.width / imageWidth;\n        double zoomFactorY = 1.0 * screen.height / imageHeight;\n        zoomFactor = Math.min(zoomFactorX, zoomFactorY);\n        if (zoomFactor > 1.0) {\n            zoomFactor = 1.0;\n        }\n\n        zoom(zoomFactor);\n        fixMouseMovementEventsIssue();\n\n        checkNextPrev();\n        setFrameCursor(CURSOR_DEFAULT);\n\n        try {\n            file.closePushbackInputStream();\n        } catch (IOException e) {\n            log.error(\"Stream close error\", e);\n        }\n    }\n\n    private void loadImageFile(AbstractFile file) throws IOException {\n        final String ext = file.getExtension().toLowerCase();\n        if (\"scr\".equals(ext) && file.getSize() == ZxSpectrumScrImage.SCR_IMAGE_FILE_SIZE) {\n            this.image = ZxSpectrumScrImage.load(file.getInputStream());\n            if (statusBar != null) {\n                statusBar.setImageBpp(4);\n            }\n        } else if (\"ico\".equals(ext)) {\n            this.image = ICODecoder.read(file.getInputStream()).getFirst();\n        } else if (\"svg\".equals(ext)) {\n            this.image = transcodeSvgDocument(file, 0, 0);\n        } else {\n            try (InputStream is = file.getInputStream()) {\n                this.image = ImageIO.read(is);\n            }\n            if (image == null) {\n                throw new IllegalArgumentException(\"No reader for a given file: \" + file);\n            }\n            if (statusBar != null) {\n                statusBar.setImageBpp(image.getColorModel().getPixelSize());\n            }\n        }\n        vectorImage = \"svg\".equalsIgnoreCase(ext);\n    }\n\n\n    private static byte[] loadFile(AbstractFile file) {\n        byte[] data = new byte[(int) file.getSize()];\n        try (InputStream is = file.getInputStream()) {\n            int readTotal = 0;\n            while (readTotal < data.length) {\n                int bytesRead = is.read(data, readTotal, data.length - readTotal);\n                if (bytesRead < 0) {\n                    break;\n                }\n                readTotal += bytesRead;\n            }\n        } catch (IOException e) {\n            log.error(\"File load error\", e);\n        }\n        return data;\n    }\n\n\n    private void setFrameCursor(Cursor cursor) {\n        if (cursor == CURSOR_WAIT) {\n            waitCursorMode = true;\n        } else if (cursor == CURSOR_DEFAULT) {\n            waitCursorMode = false;\n        } else if (cursor == CURSOR_CROSS && waitCursorMode) {\n            return;\n        }\n        FileFrame frame = getFrame();\n        if (frame != null && frame.getCursor() != cursor) {\n            frame.setCursor(cursor);\n        }\n    }\n\n\n    private synchronized void zoom(double factor) {\n        setFrameCursor(CURSOR_WAIT);\n\n        final int srcWidth = image.getWidth(null);\n        final int srcHeight = image.getHeight(null);\n        final int scaledWidth = (int)(srcWidth*factor);\n        final int scaledHeight = (int)(srcHeight*factor);\n\n        if (factor != 1.0) {\n            AbstractFile file = filesInDirectory.get(indexInDirectory);\n            if (\"svg\".equalsIgnoreCase(file.getExtension())) {\n                try {\n                    this.image = transcodeSvgDocument(file, scaledWidth, scaledHeight);\n                } catch (IOException e) {\n                    log.error(\"Transcode error\", e);\n                }\n//            } else {\n//                this.scaledImage = new BufferedImage(scaledWidth, scaledHeight, BufferedImage.TYPE_INT_ARGB);\n//                AffineTransform at = new AffineTransform();\n//                at.scale(factor, factor);\n//                AffineTransformOp scaleOp = new AffineTransformOp(at, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);\n//                this.scaledImage = scaleOp.filter(this.image, this.scaledImage);\n//                this.scaledImage = image;\n            }\n//        } else {\n//            this.scaledImage = image;\n        }\n\n        if (statusBar != null) {\n            statusBar.setZoom(factor);\n        }\n        checkZoom();\n        setFrameCursor(CURSOR_DEFAULT);\n    }\n\n\n    private void fixMouseMovementEventsIssue() {\n        if (mouseMovementIssueFixed) {\n            return;\n        }\n        mouseMovementIssueFixed = true;\n        Runnable task = () -> {\n                try {\n                    int w = getFrame().getWidth();\n                    int h = getFrame().getHeight();\n                    getFrame().setSize(w, h-1);\n                    getFrame().setSize(w, h);\n                } catch (Exception ignore) {\n                }\n        };\n        try (var executor = Executors.newSingleThreadScheduledExecutor()) {\n            executor.schedule(task, 1000, TimeUnit.MILLISECONDS);\n        }\n    }\n\n    private void updateFrame() {\n    \tFileFrame frame = getFrame();\n\n        // Revalidate, pack and repaint should be called in this order\n        frame.setTitle(this.getTitle());\n        imageViewerImpl.revalidate();\n        //frame.pack();\n        frame.getContentPane().repaint();\n    }\n\n    private void checkZoom() {\n//        Dimension d = MuSnapshot.getScreenSize();\n\n//        zoomInItem.setEnabled(zoomFactor < 1.0 || (2*zoomFactor*image.getWidth(null) < d.width\n//                                                 && 2*zoomFactor*image.getHeight(null) < d.height));\n//\n//        zoomOutItem.setEnabled(zoomFactor > 1.0 || (zoomFactor / 2 * image.getWidth(null) > 160\n//                && zoomFactor / 2 * image.getHeight(null) > 120));\n\n        zoomInItem.setEnabled(zoomFactor < 8);\n        zoomOutItem.setEnabled( zoomFactor > 0.1);\n    }\n\n    private void checkNextPrev() {\n        prevImageItem.setEnabled(getPrevFileIndex() >= 0);\n        nextImageItem.setEnabled(getNextFileIndex() >= 0);\n    }\n\n    @Override\n    public void show(AbstractFile file) throws IOException {\n        if (filesInDirectory == null) {\n            filesInDirectory = new ArrayList<>();\n            AbstractFile[] ls = file.getParent().ls();\n            ImageFactory imageFactory = new ImageFactory();\n            for (AbstractFile f : ls) {\n                if (imageFactory.canViewFile(f)) {\n                    filesInDirectory.add(f);\n                }\n            }\n            for (int i = 0 ; i < filesInDirectory.size(); i++) {\n                AbstractFile f = filesInDirectory.get(i);\n                if (f.equals(file)) {\n                    indexInDirectory = i;\n                    break;\n                }\n            }\n            if (statusBar != null) {\n                statusBar.setFileNumber(indexInDirectory + 1, filesInDirectory.size());\n            }\n        }\n        try {\n            loadImage(file);\n        } catch (ImageReadException e) {\n            log.error(\"Load error\", e);\n            throw new IOException(\"Image parsing error\", e);\n        }\n    }\n\n    @Override\n    public String getTitle() {\n        return filesInDirectory.get(indexInDirectory).toString();\n        //return file.getAbsolutePath()+\" - \"+image.getWidth(null)+\"x\"+image.getHeight(null)+\" - \"+((int)(zoomFactor*100))+\"%\";\n    }\n\n    @Override\n    public void actionPerformed(ActionEvent e) {\n        Object source = e.getSource();\n\n        if (source == zoomInItem && zoomInItem.isEnabled()) {\n            zoomFactor *= 2;\n            zoom(zoomFactor);\n            updateFrame();\n        } else if(source == zoomOutItem && zoomOutItem.isEnabled()) {\n            zoomFactor /= 2;\n            zoom(zoomFactor);\n            updateFrame();\n        } else if (source == nextImageItem && nextImageItem.isEnabled()) {\n            gotoNextFile();\n        } else if (source == prevImageItem && prevImageItem.isEnabled()) {\n            gotoPrevFile();\n        } else {\n        \tsuper.actionPerformed(e);\n        }\n    }\n\n    private int getNextFileIndex() {\n        return indexInDirectory < filesInDirectory.size() - 1 ? indexInDirectory + 1 : -1;\n    }\n\n    private void gotoNextFile() {\n        int index = getNextFileIndex();\n        if (index >= 0) {\n            indexInDirectory = index;\n            gotoFile();\n        }\n    }\n\n    private int getPrevFileIndex() {\n        return indexInDirectory > 0 ? indexInDirectory - 1 : -1;\n    }\n\n    private void gotoPrevFile() {\n        int index = getPrevFileIndex();\n        if (index >= 0) {\n            indexInDirectory = index;\n            gotoFile();\n        }\n    }\n\n    private void gotoFile() {\n        try {\n            show(filesInDirectory.get(indexInDirectory));\n            if (statusBar != null) {\n                statusBar.setFileNumber(indexInDirectory + 1, filesInDirectory.size());\n            }\n            updateFrame();\n        } catch (IOException e) {\n            InformationDialog.showErrorDialog(this, i18n(\"file_viewer.view_error_title\"), i18n(\"file_viewer.view_error\"));\n            log.error(\"Update close error\", e);\n        }\n    }\n\n\n    private static String colorToRgbStr(int color) {\n        int a = (color >> 24) & 0xff;\n        int r = (color >> 16) & 0xff;\n        int g = (color >> 8) & 0xff;\n        int b = (color) & 0xff;\n        String result = r + \", \" + g + \", \" + b;\n        if (a == 0xff) {\n            result = \"RGB: (\" + result + \")\";\n        } else {\n            result = a + \", \" + result;\n            result = \"ARGB: (\" + result + \")\";\n        }\n        return result;\n    }\n\n    private static String colorToHexStr(int color) {\n        int a = (color >> 24) & 0xff;\n        if (a == 0xff) {\n            color &= 0x00ffffff;\n        }\n        String result = Integer.toHexString(color);\n        while (result.length() < 6) {\n            result = '0' + result;\n        }\n        if (result.length() == 7) {\n            result = '0' + result;\n        }\n        return '#' + result.toUpperCase();\n    }\n\n\n    private static BufferedImage transcodeSvgDocument(AbstractFile file, float width, float height) throws IOException {\n        // create a PNG transcoder.\n        Transcoder t = new PNGTranscoder();\n        // Set the transcoding hints.\n        if (width > 0) {\n            t.addTranscodingHint(PNGTranscoder.KEY_WIDTH, width);\n        }\n        if (height > 0) {\n            t.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, height);\n        }\n        t.addTranscodingHint(PNGTranscoder.KEY_XML_PARSER_VALIDATING, false);\n\n        try (InputStream istream = file.getInputStream(); ByteArrayOutputStream ostream = new ByteArrayOutputStream()) {\n            TranscoderInput input = new TranscoderInput(istream);\n            TranscoderOutput output = new TranscoderOutput(ostream);\n            // Save the image.\n            t.transcode(input, output);\n\n            // Flush and close the stream.\n            ostream.flush();\n\n            byte[] imgData = ostream.toByteArray();\n\n            // Return the newly rendered image.\n            return ImageIO.read(new ByteArrayInputStream(imgData));\n        } catch (TranscoderException e) {\n            log.error(\"SVG transcode error\", e);\n            return null;\n        }\n    }\n\n\n    private int getScaledWidth() {\n        if (image == null) {\n            return 0;\n        }\n        return vectorImage ? image.getWidth() : (int)(zoomFactor*image.getWidth());\n    }\n\n    private int getScaledHeight() {\n        if (image == null) {\n            return 0;\n        }\n        return vectorImage ? image.getHeight() : (int)(zoomFactor*image.getHeight());\n    }\n\n\n    /**\n     * Image viewer panel\n     */\n    private class ImageViewerImpl extends JPanel implements MouseMotionListener, MouseListener, ThemeListener {\n    \tprivate Color backgroundColor;\n\n    \tImageViewerImpl() {\n    \t\tbackgroundColor = ThemeManager.getCurrentColor(Theme.EDITOR_BACKGROUND_COLOR);\n            ThemeManager.addCurrentThemeListener(this);\n            addMouseListener(this);\n            addMouseMotionListener(this);\n        }\n\n\n        @Override\n        public void paint(Graphics g) {\n            int frameWidth = getWidth();\n            int frameHeight = getHeight();\n\n            g.setColor(backgroundColor);\n            g.fillRect(0, 0, frameWidth, frameHeight);\n\n            final int imageWidth = getScaledWidth();\n            final int imageHeight = getScaledHeight();\n            final int x0 = Math.max(0, (frameWidth-imageWidth)/2);\n            final int y0 = Math.max(0, (frameHeight-imageHeight)/2);\n            if (hasTransparentPixels) {\n                int cellW = imageWidth/TRANSPARENT_GRID_STEP;\n                if (imageWidth % TRANSPARENT_GRID_STEP > 0) {\n                    cellW++;\n                }\n                int cellH = imageHeight/TRANSPARENT_GRID_STEP;\n                if (imageHeight % TRANSPARENT_GRID_STEP > 0) {\n                    cellH++;\n                }\n                int x = x0;\n                int w = TRANSPARENT_GRID_STEP;\n                for (int cellX = 0; cellX < cellW; cellX++) {\n                    if (cellX == cellW-1) {\n                        w = (imageWidth % TRANSPARENT_GRID_STEP == 0) ? TRANSPARENT_GRID_STEP : (imageWidth % TRANSPARENT_GRID_STEP);\n                    }\n                    int y = y0;\n                    int h = TRANSPARENT_GRID_STEP;\n                    for (int cellY = 0; cellY < cellH; cellY++) {\n                        g.setColor((cellX + cellY) % 2 == 0 ? TRANSPARENT_COLOR_1 : TRANSPARENT_COLOR_2);\n                        if (cellY == cellH-1) {\n                            h = (imageHeight % TRANSPARENT_GRID_STEP == 0) ? TRANSPARENT_GRID_STEP : (imageHeight % TRANSPARENT_GRID_STEP);\n                        }\n                        g.fillRect(x, y, w, h);\n                        y += TRANSPARENT_GRID_STEP;\n                    }\n                    x += TRANSPARENT_GRID_STEP;\n                }\n            }\n            if (image != null) {\n                if (vectorImage) {\n                    g.drawImage(image, x0, y0, null);\n                } else {\n                    g.drawImage(image, x0, y0, x0 + imageWidth, y0 + imageHeight, 0, 0, image.getWidth(), image.getHeight(), null, null);\n                }\n            }\n        }\n\n        @Override\n        public synchronized Dimension getPreferredSize() {\n            return image == null ? new Dimension(320, 200) : new Dimension(getScaledWidth(), getScaledHeight());\n        }\n\n\n        /**\n         * Receives theme color changes notifications.\n         */\n        @Override\n        public void colorChanged(ColorChangedEvent event) {\n            if (event.getColorId() == Theme.EDITOR_BACKGROUND_COLOR) {\n                backgroundColor = event.getColor();\n                repaint();\n            }\n        }\n\n\n        @Override\n        public void mouseMoved(MouseEvent e) {\n            if (image == null) {\n                return;\n            }\n            int x = e.getX();\n            int y = e.getY();\n\n            final int imageOffsetX = Math.max(0, (imageViewerImpl.getWidth() - getScaledWidth())/2);\n            final int imageOffsetY = Math.max(0, (imageViewerImpl.getHeight() - getScaledHeight())/2);\n\n            boolean inImageArea = x >= imageOffsetX && y >= imageOffsetY && x < imageOffsetX + getScaledWidth() && y < imageOffsetY + getScaledHeight();\n            if (inImageArea) {\n                setFrameCursor(CURSOR_CROSS);\n            } else {\n                setFrameCursor(CURSOR_DEFAULT);\n            }\n        }\n\n\n        @Override\n        public void mouseClicked(MouseEvent e) {\n            if (image == null) {\n                return;\n            }\n            int x = e.getX();\n            int y = e.getY();\n\n            final int w = getScaledWidth();\n            final int h = getScaledHeight();\n            final int imageOffsetX = Math.max(0, (imageViewerImpl.getWidth()-w)/2);\n            final int imageOffsetY = Math.max(0, (imageViewerImpl.getHeight()-h)/2);\n\n            int pixelX = x - imageOffsetX;\n            if (pixelX < 0 || pixelX >= w) {\n                return;\n            }\n            int pixelY = y - imageOffsetY;\n            if (pixelY < 0 || pixelY >= h) {\n                return;\n            }\n            pixelX = (int)(pixelX/zoomFactor);\n            pixelY = (int)(pixelY/zoomFactor);\n            int color = image.getRGB(pixelX, pixelY);\n//            int r = (color >> 16) & 0xff;\n//            int g = (color >> 8) & 0xff;\n//            int b = (color) & 0xff;\n            if (statusBar != null) {\n                statusBar.setStatusMessage(\"XY: (\" + pixelX + \", \" + pixelY + \")  \" + colorToRgbStr(color) + \"  HTML: (\" + colorToHexStr(color) + \")\");\n            }\n        }\n\n        @Override\n        public void mouseExited(MouseEvent e) {\n            setFrameCursor(CURSOR_DEFAULT);\n        }\n\n\n        /**\n         * Not used, implemented as a no-op.\n         */\n        @Override\n        public void fontChanged(FontChangedEvent event) {}\n\n        @Override\n        public void mouseDragged(MouseEvent e) {\n//            mouseMoved(e);\n        }\n\n\n        @Override\n        public void mousePressed(MouseEvent e) {}\n\n        @Override\n        public void mouseReleased(MouseEvent e) {}\n\n        @Override\n        public void mouseEntered(MouseEvent e) {}\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/image/StatusBar.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.image;\n\nimport com.mucommander.ui.main.statusbar.FileWindowsListButton;\nimport org.fife.ui.StatusBarPanel;\n\nimport javax.swing.JLabel;\nimport java.awt.BorderLayout;\nimport java.awt.GridBagConstraints;\nimport java.util.Date;\n\n/**\n * Created on 11/03/14.\n */\npublic class StatusBar extends org.fife.ui.StatusBar {\n    private FileWindowsListButton lbFiles;\n\n    private JLabel lblImageSize;\n    private JLabel lblFileNumber;\n    private JLabel lblZoom;\n    private JLabel lblFileSize;\n    private JLabel lblDateTime;\n\n    private int imageWidth, imageHeight, imageBpp;\n\n    public StatusBar() {\n        super(\"\");\n\n        lbFiles = new FileWindowsListButton(true);\n        StatusBarPanel panelWindows = new StatusBarPanel(new BorderLayout());\n        panelWindows.add(lbFiles);\n\n        lblImageSize = new JLabel();\n        StatusBarPanel panelImageSize = new StatusBarPanel(new BorderLayout(), lblImageSize);\n\n        lblFileNumber = new JLabel();\n        StatusBarPanel panelFileNumber = new StatusBarPanel(new BorderLayout(), lblFileNumber);\n\n        lblZoom = new JLabel();\n        StatusBarPanel panelZoom = new StatusBarPanel(new BorderLayout(), lblZoom);\n\n        lblFileSize = new JLabel();\n        StatusBarPanel panelFileSize = new StatusBarPanel(new BorderLayout(), lblFileSize);\n\n        lblDateTime = new JLabel();\n        StatusBarPanel panelDateTime = new StatusBarPanel(new BorderLayout(), lblDateTime);\n\n        // Make the layout such that different items can be different sizes.\n        GridBagConstraints c = new GridBagConstraints();\n        c.fill = GridBagConstraints.BOTH;\n\n        c.weightx = 0.0;\n        addStatusBarComponent(panelWindows, c);\n        addStatusBarComponent(panelImageSize, c);\n        addStatusBarComponent(panelFileNumber, c);\n        addStatusBarComponent(panelZoom, c);\n        addStatusBarComponent(panelFileSize, c);\n        addStatusBarComponent(panelDateTime, c);\n    }\n\n    public void setImageSize(int width, int height) {\n        imageWidth = width;\n        imageHeight = height;\n        updateSizePanel();\n    }\n\n    public void setImageBpp(int bpp) {\n        imageBpp = bpp;\n        updateSizePanel();\n    }\n\n    private void updateSizePanel() {\n        lblImageSize.setText(imageWidth + \" x \" + imageHeight + \" x \" + imageBpp + \" BPP\");\n    }\n\n    public void setFileNumber(int current, int total) {\n        lblFileNumber.setText(current + \" / \" + total);\n    }\n\n    public void setZoom(double zoom) {\n        long zoomInt = Math.round(zoom*100);\n        lblZoom.setText(zoomInt + \" %\");\n    }\n\n    public void setFileSize(long fileSize) {\n        lblFileSize.setText(fileSize + \" bytes\");\n    }\n\n    public void setDateTime(long date) {\n        lblDateTime.setText(new Date(date).toString());\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/image/ZxSpectrumScrImage.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.image;\n\n\nimport java.awt.Color;\nimport java.awt.Graphics2D;\nimport java.awt.image.BufferedImage;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * Created on 23/02/14.\n */\npublic class ZxSpectrumScrImage {\n    private static final int WIDTH = 256;\n    private static final int HEIGHT = 192;\n\n    private static final int DEFAULT_ZOOM = 1;\n\n    //private static final int ROWS =\n\n    static final int SCR_IMAGE_FILE_SIZE = 6912;\n\n    private static final Color[] PALETTE_BRIGHT_0 = {\n        new Color(0x000000),\n        new Color(0x0000cd),\n        new Color(0xcd0000),\n        new Color(0xff00ff),\n        new Color(0x00cd00),\n        new Color(0x00cdcd),\n        new Color(0xcdcd00),\n        new Color(0xcdcdcd)\n    };\n    private static final Color[] PALETTE_BRIGHT_1 = {\n        new Color(0x000000),\n        new Color(0x0000ff),\n        new Color(0xff0000),\n        new Color(0xff00ff),\n        new Color(0x00ff00),\n        new Color(0x00ffff),\n        new Color(0xffff00),\n        new Color(0xffffff)\n    };\n\n\n    public static BufferedImage load(InputStream is, int zoomFactor) {\n        if (is == null) {\n            return null;\n        }\n        byte[] data = new byte[SCR_IMAGE_FILE_SIZE];\n        int readTotal = 0;\n        try {\n            while (readTotal < SCR_IMAGE_FILE_SIZE) {\n                int bytesRead = is.read(data, readTotal, SCR_IMAGE_FILE_SIZE - readTotal);\n                if (bytesRead < 0) {\n                    break;\n                }\n                readTotal += bytesRead;\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        try {\n            is.close();\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        if (readTotal != SCR_IMAGE_FILE_SIZE) {\n            return null;\n        }\n        return load(data, zoomFactor);\n    }\n\n\n\n    public static BufferedImage load(byte[] data, int zoomFactor) {\n        if (data == null || data.length != SCR_IMAGE_FILE_SIZE) {\n            return null;\n        }\n        if (zoomFactor < 1) {\n            zoomFactor = 1;\n        }\n        BufferedImage result = new BufferedImage(WIDTH*zoomFactor, HEIGHT*zoomFactor, BufferedImage.TYPE_INT_RGB);\n        Graphics2D g = result.createGraphics();\n\n\n        // read colour data (attributes)\n        byte[] ink = new byte[32*24];\n        byte[] paper = new byte[32*24];\n        boolean[] bright = new boolean[32*24];\n        for (int row = 0; row < 24; row++) {\n            for (int col = 0; col < 32; col++) {\n                int offset = WIDTH * HEIGHT / 8 + (row*32)+col;\n                byte val = data[offset];\n\n                ink[row*32+col]   = (byte)( val & 0b00000111);\n                paper[row*32+col] = (byte)((val & 0b00111000)>>3);\n                bright[row*32+col] = ((val & 0b11000000)>>6) != 0;\n            }\n        }\n\n        // render pixels\n        for (int y = 0; y < HEIGHT; y++) {\n            // display address 010[L4][L3][R2][R1][R0][L2][L1][L0][C4][C3][C2][C1][C0]\n            int line = y / 8;       // line number (0..23, 5 bits)\n            int row = y % 8;        // pixel row in line (0..8, 3 bits)\n            final int lPart = ((line & 0x18)<<8)|((line & 0x7)<<5);\n            final int rPart = (row & 0x7)<<8;\n            for (int x = 0; x < WIDTH; x++) {\n                int col = x / 8;    // column number (5 bits)\n                int cPart = col & 0x1F;\n                int bit = 7 - (x % 8);\n                int address = (((0x4000 | lPart) | rPart) | cPart) - 16384;\n                int attrIndex = ((y/8)*32)+(x/8);\n                int inkIndex = ink[attrIndex];\n                int paperIndex = paper[attrIndex];\n                boolean isBright = bright[attrIndex];\n                if (((data[address]&(0x1<<bit))>>bit) != 0) {\n                    g.setColor(PALETTE_BRIGHT_0[inkIndex]);\n                } else {\n                    g.setColor(isBright ? PALETTE_BRIGHT_1[paperIndex] : PALETTE_BRIGHT_0[paperIndex]);\n                }\n                g.fillRect(x*zoomFactor, y*zoomFactor, zoomFactor, zoomFactor);\n            }\n        }\n        return result;\n    }\n\n\n    public static BufferedImage load(byte[] data) {\n        return load(data, DEFAULT_ZOOM);\n    }\n\n    public static BufferedImage load(InputStream is) {\n        return load(is, DEFAULT_ZOOM);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/image/package.html",
    "content": "<body>\n  Provides image viewing classes.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/package.html",
    "content": "<body>\n  Generic classes used to deal with file viewing / editing.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/pdf/PdfFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2014 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.pdf;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.filter.ExtensionFilenameFilter;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.viewer.FileViewer;\nimport com.mucommander.ui.viewer.ViewerFactory;\n\n/**\n * <code>ViewerFactory</code> implementation for creating pdf viewers.\n *\n * @author Oleg Trifonov\n */\npublic class PdfFactory implements ViewerFactory {\n\n    public final static ExtensionFilenameFilter PDF_FILTER = new ExtensionFilenameFilter(new String[] {\".pdf\"});\n\n    static {\n        PDF_FILTER.setCaseSensitive(false);\n    }\n\n    @Override\n    public boolean canViewFile(AbstractFile file) {\n        return !file.isDirectory() && PDF_FILTER.accept(file);\n    }\n\n    @Override\n    public FileViewer createFileViewer() {\n        return new PdfViewer();\n    }\n\n    @Override\n    public String getName() {\n        return Translator.get(\"viewer_type.pdf\");\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/pdf/PdfViewer.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/trolcommander\n * Copyright (C) 2014-2020 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.pdf;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.ui.viewer.FileViewer;\nimport org.fife.ui.StatusBar;\nimport org.icepdf.ri.common.MyAnnotationCallback;\nimport org.icepdf.ri.common.SwingController;\nimport org.icepdf.ri.common.SwingViewBuilder;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * A simple pdf viewer\n *\n * @author Oleg Trifonov\n */\npublic class PdfViewer extends FileViewer {\n\n    private final SwingController controller;\n\n    PdfViewer() {\n        // create a controller and a swing factory\n        controller = new SwingController();\n        SwingViewBuilder factory = new SwingViewBuilder(controller);\n        // add interactive mouse link annotation support via callback\n        controller.getDocumentViewController().setAnnotationCallback(\n                new org.icepdf.ri.common.MyAnnotationCallback(controller.getDocumentViewController()));\n\n        // build viewer component and add it to the applet content pane.\n        MyAnnotationCallback myAnnotationCallback = new MyAnnotationCallback(\n                controller.getDocumentViewController());\n        controller.getDocumentViewController().setAnnotationCallback(myAnnotationCallback);\n\n        // build the viewer with a menubar\n        //getContentPane().setLayout(new BorderLayout());\n        //getContentPane().add(factory.buildViewerPanel(), BorderLayout.CENTER);\n        //getContentPane().add(factory.buildCompleteMenuBar(), BorderLayout.NORTH);\n        setComponentToPresent(factory.buildViewerPanel());\n    }\n\n    @Override\n    protected void show(AbstractFile file) throws IOException {\n        String description = \"\";\n        String path = file.getPath();\n        try (InputStream is = file.getInputStream()) {\n            controller.openDocument(is, description, path);\n        }\n    }\n\n        @Override\n    protected StatusBar getStatusBar() {\n        return null;\n    }\n\n    @Override\n    protected void saveStateOnClose() {\n        org.icepdf.core.util.Library.shutdownThreadPool();\n    }\n\n    @Override\n    protected void restoreStateOnStartup() {\n        org.icepdf.core.util.Library.initializeThreadPool();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/FileType.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2023 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.text;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport lombok.Getter;\nimport org.apache.commons.io.IOCase;\nimport org.apache.commons.io.filefilter.WildcardFileFilter;\nimport org.fife.ui.rsyntaxtextarea.SyntaxConstants;\n\nimport java.io.File;\n\n/**\n * @author Oleg Trifonov\n * Created on 04/01/14.\n */\npublic enum FileType {\n    NONE(\"None\", SyntaxConstants.SYNTAX_STYLE_NONE),\n    ACTIONSCRIPT(\"ActionScript\", SyntaxConstants.SYNTAX_STYLE_ACTIONSCRIPT, \"*.as\"),\n    ASSEMBLER_X86(\"Assembler x86\", SyntaxConstants.SYNTAX_STYLE_ASSEMBLER_X86, \"*.asm\"),\n    ASSEMBLER_AVR(\"Assembler AVR\", SyntaxConstants.SYNTAX_STYLE_ASSEMBLER_AVR, \"*.lss,*.s\"),\n    ASSEMBLER_6502(\"Assembler 6502\", SyntaxConstants.SYNTAX_STYLE_ASSEMBLER_6502, \"*.asm\"),\n    ASSEMBLER_RISCV(\"Assembler Risc-V\", SyntaxConstants.SYNTAX_STYLE_ASSEMBLER_RISCV, \"*.lss,*.s\"),\n    AVR_RAT(\"AVR Rat\", SyntaxConstants.SYNTAX_STYLE_AVR_RAT, \"*.art,*.arth\"),\n    BBCODE(\"BBCode\", SyntaxConstants.SYNTAX_STYLE_BBCODE),\n    C(\"C\", SyntaxConstants.SYNTAX_STYLE_C, \"*.c,*.m\"),\n    CHIP_TEST(\"Chip test\", SyntaxConstants.SYNTAX_STYLE_CHIP_TEST, \"*.ic\"),\n    CLOJURE(\"Clojure\", SyntaxConstants.SYNTAX_STYLE_CLOJURE, \"*.clj\"),\n    CPP(\"C++\", SyntaxConstants.SYNTAX_STYLE_CPLUSPLUS, \"*.cpp,*.cc,*.h,*.hpp,*.ino\"),\n    CSHARP(\"C#\", SyntaxConstants.SYNTAX_STYLE_CSHARP, \"*.cs\"),\n    CSS(\"CSS\", SyntaxConstants.SYNTAX_STYLE_CSS, \"*.css\"),\n    CSV(\"CSV\", SyntaxConstants.SYNTAX_STYLE_CSV, \"*.csv\"),\n    D(\"D\", SyntaxConstants.SYNTAX_STYLE_D, \"*.d\"),\n    DOCKERFILE(\"Dockerfile\", SyntaxConstants.SYNTAX_STYLE_DOCKERFILE, \"Dockerfile\"),\n    DART(\"Dart\", SyntaxConstants.SYNTAX_STYLE_DART, \"*.dart\"),\n    DTD(\"DTD\", SyntaxConstants.SYNTAX_STYLE_DTD, \"*.dtd\"),\n    FORTRAN(\"Fortran\", SyntaxConstants.SYNTAX_STYLE_FORTRAN, \"*.f,*.for,*.ftn,*.i\"),\n    GO(\"Go\", SyntaxConstants.SYNTAX_STYLE_GO, \"*.go\"),\n    GROOVY(\"Groovy\", SyntaxConstants.SYNTAX_STYLE_GROOVY,\"*.groovy,*.gvy,*.gy,*.gsh,*.gradle\"),\n    HANDLEBARS(\"Handlebars\", SyntaxConstants.SYNTAX_STYLE_HANDLEBARS),\n    HOSTS(\"Hosts\", SyntaxConstants.SYNTAX_STYLE_HOSTS, \"hosts\"),\n    HEX(\"Intel HEX\", SyntaxConstants.SYNTAX_STYLE_HEX, \"*.hex,*.ihex\"),\n    HTACCESS(\"htaccess\", SyntaxConstants.SYNTAX_STYLE_HTACCESS, \".htaccess\"),\n    HTML(\"HTML\", SyntaxConstants.SYNTAX_STYLE_HTML, \"*.html,*.htm\"),\n    INI(\"ini\", SyntaxConstants.SYNTAX_STYLE_INI, \"*.ini\"),\n    JAVA(\"Java\", SyntaxConstants.SYNTAX_STYLE_JAVA, \"*.java\"),\n    JAVASCRIPT(\"JavaScript\", SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT, \"*.js,*.ts\"),\n    JSON(\"Json\", SyntaxConstants.SYNTAX_STYLE_JSON, \"*.json\"),\n    JSON_WITH_COMMENTS(\"Json with comments\", SyntaxConstants.SYNTAX_STYLE_JSON_WITH_COMMENTS, \"*.json\"),\n    JSP(\"JSP\", SyntaxConstants.SYNTAX_STYLE_JSP, \"*.jsp\"),\n    KOTLIN(\"Kotlin\", SyntaxConstants.SYNTAX_STYLE_KOTLIN, \"*.kt,*.kts\"),\n    LATEX(\"Latex\", SyntaxConstants.SYNTAX_STYLE_LATEX, \"*.tex\"),\n    LISP(\"Lisp\", SyntaxConstants.SYNTAX_STYLE_LISP, \"*.lisp,*.lsp\"),\n    LUA(\"Lua\", SyntaxConstants.SYNTAX_STYLE_LUA, \"*.lua\"),\n    MAKEFILE(\"Makefile\", SyntaxConstants.SYNTAX_STYLE_MAKEFILE, \"Makefile,*.mk\"),\n    MARKDOWN(\"Markdown\", SyntaxConstants.SYNTAX_STYLE_MARKDOWN, \"*.md\"),\n    MXML(\"MXML\", SyntaxConstants.SYNTAX_STYLE_MXML, \"*.mxml\"),\n    NSIS(\"Nsis\", SyntaxConstants.SYNTAX_STYLE_NSIS, \"*.nsi\"),\n    PASCAL(\"Pascal\", SyntaxConstants.SYNTAX_STYLE_DELPHI, \"*.pas,*.dpr,*.pp,*.lpr\"),\n    PERL(\"Perl\", SyntaxConstants.SYNTAX_STYLE_PERL, \"*.pl\"),\n    PHP(\"PHP\", SyntaxConstants.SYNTAX_STYLE_PHP, \"*.php\"),\n    PROTOBUF(\"Protobuf\", SyntaxConstants.SYNTAX_STYLE_PROTO, \"*.proto\"),\n    PROPERTIES_FILE(\"Properties file\", SyntaxConstants.SYNTAX_STYLE_PROPERTIES_FILE, \"*.properties,*.prop,*.conf\"),\n    PYTHON(\"Python\", SyntaxConstants.SYNTAX_STYLE_PYTHON, \"*.py,make.builder\"),\n    RUBY(\"Ruby\", SyntaxConstants.SYNTAX_STYLE_RUBY, \"*.rb\"),\n    RUST(\"Rust\", SyntaxConstants.SYNTAX_STYLE_RUST, \"*.rs,*.rs.in,*.rlib\"),\n    SAS(\"SAS\", SyntaxConstants.SYNTAX_STYLE_SAS, \"*.sas\"),\n    SCALA(\"Scala\", SyntaxConstants.SYNTAX_STYLE_SCALA, \"*.scala\"),\n    SQL(\"SQL\", SyntaxConstants.SYNTAX_STYLE_SQL, \"*.sql\"),\n    TCL(\"TCL\", SyntaxConstants.SYNTAX_STYLE_TCL, \"*.tcl\"),\n    TYPESCRIPT(\"TypeScript\", SyntaxConstants.SYNTAX_STYLE_TYPESCRIPT, \"*.ts\"),\n    UNIX_SHELL(\"Unix Shell\", SyntaxConstants.SYNTAX_STYLE_UNIX_SHELL, \"*.sh,*.zsh,.zshrc,.profile,.bash_profile\"),\n    VISUAL_BASIC(\"VisualBasic\", SyntaxConstants.SYNTAX_STYLE_VISUAL_BASIC, \"*.bas,*.vb\"),\n    WINDOWS_BATCH(\"Windows bath\", SyntaxConstants.SYNTAX_STYLE_WINDOWS_BATCH, \"*.bat,*.cmd\"),\n    XML(\"XML\", SyntaxConstants.SYNTAX_STYLE_XML, \"*.xml,Info.plist,*.jnlp,*.svg\"),\n    YAML(\"YAML\", SyntaxConstants.SYNTAX_STYLE_YAML, \"*.yml,*.yaml\");\n\n\n\n    @Getter\n    private final String contentType;\n\n    private final WildcardFileFilter[] fileFilters;\n    @Getter\n    private final String name;\n\n    FileType(String name, String contentType, String fileMasks) {\n        this.name = name;\n        this.contentType = contentType;\n        this.fileFilters = buildFileFilters(fileMasks);\n    }\n\n\n    FileType(String name, String contentType) {\n        this(name, contentType, null);\n    }\n\n    public static FileType getFileType(AbstractFile file) {\n        return getFileType(file.toString());\n    }\n\n    public static FileType getFileType(String fileName) {\n        File f = new File(fileName);\n        for (FileType ft : FileType.values()) {\n            if (ft.checkFile(f))  {\n                return ft;\n            }\n        }\n        return NONE;\n    }\n\n    public boolean checkFile(File file) {\n        for (WildcardFileFilter filter : fileFilters) {\n            if (filter.accept(file)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n\n    private static WildcardFileFilter[] buildFileFilters(String fileMasks) {\n        if (fileMasks == null) {\n            return new WildcardFileFilter[0];\n        }\n        String[] masks = fileMasks.split(\",\");\n        WildcardFileFilter[] result = new WildcardFileFilter[masks.length];\n        for (int i = 0; i < masks.length; i++) {\n            result[i] = WildcardFileFilter.builder().setWildcards(masks[i]).setIoCase(IOCase.INSENSITIVE).get();\n        }\n        return result;\n    }\n\n    public static FileType getByName(String name) {\n        for (FileType fileType : FileType.values()) {\n            if (fileType.getName().equals(name)) {\n                return fileType;\n            }\n        }\n        return null;\n    }\n\n\n    public static FileType getByContentType(String contentType) {\n        for (FileType fileType : FileType.values()) {\n            if (fileType.getContentType().equals(contentType)) {\n                return fileType;\n            }\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/GotoLineDialog.java",
    "content": "/*\n * This file is part of trolCommander, http://www.mucommander.com\n * Copyright (C) 2014 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.text;\n\nimport com.mucommander.ui.dialog.DialogToolkit;\nimport com.mucommander.ui.dialog.FocusDialog;\nimport ru.trolsoft.ui.InputField;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.util.function.IntConsumer;\n\n/**\n * This dialog allows the user to enter a line number to be jumped for in the text editor.\n *\n * @author Oleg Trifonov\n */\npublic class GotoLineDialog extends FocusDialog implements ActionListener {\n\n    /** The text field where a search string can be entered */\n    private InputField edtLineNumber;\n\n    /** The 'OK' button */\n    private final JButton btnOk;\n    private final JButton btnCancel;\n    private final IntConsumer action;\n\n\n    /**\n     * Creates a new FindDialog and shows it to the screen.\n     *\n     * @param editorFrame the parent editor frame\n     */\n    GotoLineDialog(JFrame editorFrame, int maxLines, IntConsumer action) {\n        super(editorFrame, i18n(\"text_viewer.goto_line\"), editorFrame);\n        this.action = action;\n\n        Container contentPane = getContentPane();\n        contentPane.add(new JLabel(i18n(\"text_viewer.line\")+\":\"), BorderLayout.NORTH);\n\n        edtLineNumber = new InputField(16, InputField.FilterType.DEC_LONG) {\n            @Override\n            public void onChange() {\n                boolean enabled = !edtLineNumber.isEmpty() && edtLineNumber.getValue() <= maxLines;\n                btnOk.setEnabled(enabled);\n            }\n        };\n        edtLineNumber.setText(\"1\");\n        edtLineNumber.addActionListener(this);\n\n        contentPane.add(edtLineNumber, BorderLayout.CENTER);\n\n        btnOk = new JButton(i18n(\"ok\"));\n        btnCancel = new JButton(i18n(\"cancel\"));\n        contentPane.add(DialogToolkit.createOKCancelPanel(btnOk, btnCancel, getRootPane(), this), BorderLayout.SOUTH);\n\n        // The text field will receive initial focus\n        setInitialFocusComponent(edtLineNumber);\n\n        fixHeight();\n    }\n\n\n    ///////////////////////////////////\n    // ActionListener implementation //\n    ///////////////////////////////////\n\n    public void actionPerformed(ActionEvent e) {\n        Object source = e.getSource();\n\n        if ( (source == btnOk || source == edtLineNumber) && btnOk.isEnabled() ) {\n            int line = (int)edtLineNumber.getValue();\n            action.accept(line);\n            dispose();\n        } else if (source == btnCancel) {\n            cancel();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/StatusBar.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2017 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.text;\n\nimport com.mucommander.ui.main.statusbar.FileWindowsListButton;\nimport org.fife.ui.StatusBarPanel;\n\nimport javax.swing.JLabel;\nimport java.awt.BorderLayout;\nimport java.awt.Color;\nimport java.awt.GridBagConstraints;\n\n/**\n *\n * @author Oleg Trifonov\n * Created on 25/09/14.\n */\npublic class StatusBar extends org.fife.ui.StatusBar {\n\n    private StatusBarPanel panelColor;\n    private StatusBarPanel panelPosition;\n    private StatusBarPanel panelEncoding;\n    private StatusBarPanel panelSyntax;\n    private StatusBarPanel panelWindows;\n\n    private JLabel lblColor;\n    private JLabel lblPosition;\n    private JLabel lblEncoding;\n    private JLabel lblSyntax;\n    private FileWindowsListButton lbFiles;\n\n    private int color = -1;\n\n    private String forcedStatusMessage;\n    private long forcedStatusMessageTimeout;\n\n\n    public StatusBar() {\n        super(\"\");\n\n        lbFiles = new FileWindowsListButton(true);\n        panelWindows = new StatusBarPanel(new BorderLayout());\n        panelWindows.add(lbFiles);\n\n        lblColor = new JLabel(\"          \");\n        panelColor = new StatusBarPanel(new BorderLayout(), lblColor);\n\n        lblPosition = new JLabel();\n        panelPosition = new StatusBarPanel(new BorderLayout(), lblPosition);\n\n        lblSyntax = new JLabel();\n        panelSyntax = new StatusBarPanel(new BorderLayout(), lblSyntax);\n\n        lblEncoding = new JLabel();\n        panelEncoding = new StatusBarPanel(new BorderLayout(), lblEncoding);\n\n        // Make the layout such that different items can be different sizes.\n        GridBagConstraints c = new GridBagConstraints();\n        c.fill = GridBagConstraints.BOTH;\n        c.weightx = 0.0;\n\n        addStatusBarComponent(panelWindows, c);\n        addStatusBarComponent(panelPosition, c);\n        addStatusBarComponent(panelColor, c);\n        addStatusBarComponent(panelSyntax, c);\n        addStatusBarComponent(panelEncoding, c);\n    }\n\n    public void setColor(int color) {\n        if (this.color == color) {\n            return;\n        }\n        this.color = color;\n        panelColor.setVisible(color >= 0);\n        panelColor.setBackground(new Color(color));\n    }\n\n    void setPosition(int line, int column) {\n        lblPosition.setText(line + \" : \" + column);\n    }\n\n    public void setEncoding(String encoding) {\n        lblEncoding.setText(encoding);\n    }\n\n    public void setSyntax(String syntax) {\n        lblSyntax.setText(syntax);\n    }\n\n    public void clearStatusMessage() {\n        if (forcedStatusMessage != null) {\n            if (System.currentTimeMillis() > forcedStatusMessageTimeout) {\n                forcedStatusMessage = null;\n            }\n        }\n        setStatusMessage(forcedStatusMessage != null ? forcedStatusMessage : \"\");\n    }\n\n    public void showMessage(String msg, long timeInMillis) {\n        forcedStatusMessage = msg;\n        forcedStatusMessageTimeout = System.currentTimeMillis() + timeInMillis;\n        setStatusMessage(msg);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/TextArea.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.text;\n\nimport org.fife.ui.rsyntaxtextarea.RSyntaxDocument;\nimport org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;\nimport org.fife.ui.rsyntaxtextarea.TokenMaker;\nimport org.fife.ui.rtextarea.RTextAreaEditorKit;\n\nimport javax.swing.*;\nimport javax.swing.event.DocumentEvent;\nimport javax.swing.event.DocumentListener;\nimport javax.swing.text.BadLocationException;\nimport javax.swing.text.Document;\nimport javax.swing.text.Element;\nimport java.awt.*;\nimport java.awt.geom.Rectangle2D;\nimport java.io.IOException;\nimport java.io.Reader;\n\n/**\n * @author Oleg Trifonov\n * Created on 08/01/14.\n */\npublic class TextArea extends RSyntaxTextArea implements DocumentListener {\n    private static final String DIRTY_PROPERTY\t= \"TextEditorPane.dirty\";\n\n    private boolean painted = false;\n\n    /**\n     * The #gotoLine(int) method can't be executed successfully if the model is not painted.\n     * In this case the operation wll be postponed after calling #paint() method\n     */\n    private int postponedCaretPosition = -1;\n\n    /**\n     * Whether the file is dirty.\n     */\n    private boolean dirty;\n\n    TextArea() {\n        dirty = false;\n        getDocument().addDocumentListener(this);\n    }\n\n    /**\n     *\n     * @param line line number (started from 1)\n     * @param column cursor position in the line\n     * @return true on success\n     */\n    boolean gotoLine(int line, int column) {\n        try {\n            int pos = getLineStartOffset(line - 1) + column - 1;\n            setCaretPosition(pos);\n            Rectangle2D temp = modelToView2D(pos);\n            if (temp == null) {\n                postponedCaretPosition = pos;\n            } else {\n                forceCurrentLineHighlightRepaint();\n            }\n            return true;\n        } catch (IllegalArgumentException | BadLocationException e) {\n            System.out.println(\"Invalid line: \" + line + \":\" + column + \" (\" + e.getMessage() + \")\");\n            return false;\n        }\n    }\n\n    boolean gotoLine(int line) {\n        return gotoLine(line, 1);\n    }\n\n\n    /**\n     *\n     * @return current line number (started from 1)\n     */\n    public int getLine() {\n        int dot = getCaretPosition();\n        Element map = getDocument().getDefaultRootElement();\n        return map.getElementIndex(dot) + 1;\n    }\n\n    void setFileType(FileType fileType) {\n        setSyntaxEditingStyle(fileType.getContentType());\n    }\n\n    FileType getFileType() {\n        return FileType.getByContentType(getSyntaxEditingStyle());\n    }\n\n\n    /**\n     *\n     * @return current cursor position in line (started from 1)\n     */\n    public int getColumn() {\n        Element map = getDocument().getDefaultRootElement();\n        int dot = getCaretPosition();\n        int line = map.getElementIndex(dot);\n        int lineStartOffset = map.getElement(line).getStartOffset();\n        return dot - lineStartOffset + 1;\n    }\n\n\n    @Override\n    public void paint(Graphics g) {\n        super.paint(g);\n\n        // if gotoLine method executed before that model was painted then recall it\n        if (postponedCaretPosition >= 0) {\n            final int pos = postponedCaretPosition;\n            postponedCaretPosition = -1;\n            SwingUtilities.invokeLater(() -> {\n                setCaretPosition(pos);\n                forceCurrentLineHighlightRepaint();\n            });\n        }\n        if (!painted) {\n            painted = true;\n        }\n    }\n\n    @Override\n    public void read(Reader in, Object desc) throws IOException {\n        super.read(in, desc);\n    }\n\n\n    @Override\n    public void insertUpdate(DocumentEvent e) {\n        if (!dirty) {\n            setDirty(true);\n        }\n    }\n\n    @Override\n    public void removeUpdate(DocumentEvent e) {\n        if (!dirty) {\n            setDirty(true);\n        }\n    }\n\n    @Override\n    public void changedUpdate(DocumentEvent e) {\n\n    }\n\n    /**\n     * Returns whether the text in this editor has unsaved changes.\n     *\n     * @return Whether the text has unsaved changes.\n     * @see #setDirty(boolean)\n     */\n    public boolean isDirty() {\n        return dirty;\n    }\n\n    /**\n     * Sets whether this text in this editor has unsaved changes.\n     * This fires a property change event of type {@link #DIRTY_PROPERTY}.<p>\n     *\n     * Applications will usually have no need to call this method directly; the\n     * only time you might have a need to call this method directly is if you\n     * have to initialize an instance of TextEditorPane with content that does\n     * not come from a file. <code>TextEditorPane</code> automatically sets its\n     * own dirty flag when its content is edited, when its encoding is changed,\n     * or when its line ending property is changed.  It is cleared whenever\n     * <code>load()</code>, <code>reload()</code>, <code>save()</code>, or\n     * <code>saveAs()</code> are called.\n     *\n     * @param dirty Whether the text has been modified.\n     * @see #isDirty()\n     */\n    public void setDirty(boolean dirty) {\n        if (this.dirty != dirty) {\n            this.dirty = dirty;\n            firePropertyChange(DIRTY_PROPERTY, !dirty, dirty);\n        }\n    }\n\n    /**\n     * Sets the document for this editor.\n     *\n     * @param doc The new document.\n     */\n    @Override\n    public void setDocument(Document doc) {\n        Document old = getDocument();\n        if (old != null) {\n            old.removeDocumentListener(this);\n        }\n        try {\n            super.setDocument(doc);\n        } catch (Exception e) {\n            e.printStackTrace();\n            // Sometime RSyntaxTextArea can crash for python files on code folding parsing\n            setCodeFoldingEnabled(false);\n            super.setDocument(doc);\n        }\n        doc.addDocumentListener(this);\n    }\n\n\n    /**\n     * Sets the line separator sequence to use when this file is saved (e.g.\n     * \"<code>\\n</code>\", \"<code>\\r\\n</code>\" or \"<code>\\r</code>\").\n     * Besides parameter checking, this method is preferred over\n     * <code>getDocument().putProperty()</code> because can set the editor's\n     * dirty flag when the line separator is changed.\n     *\n     * @param separator The new line separator.\n     * @param setDirty Whether the dirty flag should be set if the line separator is changed.\n     * @throws NullPointerException If <code>separator</code> is <code>null</code>.\n     * @throws IllegalArgumentException If <code>separator</code> is not one  of \"<code>\\n</code>\", \"<code>\\r\\n</code>\" or \"<code>\\r</code>\".\n     * @see #getLineSeparator()\n     */\n    public void setLineSeparator(String separator, boolean setDirty) {\n        if (separator == null) {\n            throw new NullPointerException(\"terminator cannot be null\");\n        }\n        if (!\"\\r\\n\".equals(separator) && !\"\\n\".equals(separator) && !\"\\r\".equals(separator)) {\n            throw new IllegalArgumentException(\"Invalid line terminator\");\n        }\n        Document doc = getDocument();\n        Object old = doc.getProperty(RTextAreaEditorKit.EndOfLineStringProperty);\n        if (!separator.equals(old)) {\n            doc.putProperty(RTextAreaEditorKit.EndOfLineStringProperty, separator);\n            if (setDirty) {\n                setDirty(true);\n            }\n        }\n    }\n\n    /**\n     * Returns the line separator used when writing this file (e.g.\n     * \"<code>\\n</code>\", \"<code>\\r\\n</code>\", or \"<code>\\r</code>\").<p>\n     *\n     * Note that this value is an <code>Object</code> and not a\n     * <code>String</code> as that is the way the {@link Document} interface\n     * defines its property values.  If you always use\n     * {@link #setLineSeparator(String)} to modify this value, then the value\n     * returned from this method will always be a <code>String</code>.\n     *\n     * @return The line separator.  If this value is <code>null</code>, then the system default line separator is used\n     * (usually the value of <code>System.getProperty(\"line.separator\")</code>).\n     * @see #setLineSeparator(String)\n     * @see #setLineSeparator(String, boolean)\n     */\n    public Object getLineSeparator() {\n        return getDocument().getProperty(RTextAreaEditorKit.EndOfLineStringProperty);\n    }\n\n\n    /**\n     * Sets the line separator sequence to use when this file is saved (e.g.\n     * \"<code>\\n</code>\", \"<code>\\r\\n</code>\" or \"<code>\\r</code>\").\n     *\n     * Besides parameter checking, this method is preferred over\n     * <code>getDocument().putProperty()</code> because it sets the editor's\n     * dirty flag when the line separator is changed.\n     *\n     * @param separator The new line separator.\n     * @throws NullPointerException If <code>separator</code> is <code>null</code>.\n     * @throws IllegalArgumentException If <code>separator</code> is not one of \"<code>\\n</code>\", \"<code>\\r\\n</code>\" or \"<code>\\r</code>\".\n     * @see #getLineSeparator()\n     */\n    public void setLineSeparator(String separator) {\n        setLineSeparator(separator, true);\n    }\n\n\n    String getLineStr(int line) {\n        try {\n            int posStart = getLineStartOffset(line - 1);\n            int posEnd = getLineEndOffset(line - 1);\n            int len = posEnd - posStart;\n            if (len > 2048) {\n                return null;\n            }\n            return getDocument().getText(posStart, len);\n        } catch (Exception ignore) {\n            return null;\n        }\n    }\n\n    @Override\n    public void setSyntaxEditingStyle(String styleKey) {\n        if (!painted && (styleKey == null || SYNTAX_STYLE_NONE.equals(styleKey))) {\n            return;\n        }\n        super.setSyntaxEditingStyle(styleKey);\n    }\n\n\n    @Override\n    // Model that ignores syntax type changes and no mark file as \"dirty\"\n    protected Document createDefaultModel() {\n        return new RSyntaxDocument(SYNTAX_STYLE_NONE) {\n            private boolean ignoreChangeUpdate;\n\n            @Override\n            public void setSyntaxStyle(String styleKey) {\n                ignoreChangeUpdate = true;\n                super.setSyntaxStyle(styleKey);\n                ignoreChangeUpdate = false;\n            }\n            @Override\n            protected void fireChangedUpdate(DocumentEvent e) {\n                if (!ignoreChangeUpdate) {\n                    super.fireChangedUpdate(e);\n                } else {\n                    SwingUtilities.invokeLater(() -> repaint());\n                }\n            }\n\n        };\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/TextEditor.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.viewer.text;\r\n\r\nimport java.awt.Color;\r\nimport java.awt.Component;\r\nimport java.awt.Container;\r\nimport java.awt.event.ActionEvent;\r\nimport java.awt.event.InputEvent;\r\nimport java.awt.event.KeyEvent;\r\nimport java.io.*;\r\nimport java.util.Arrays;\r\nimport java.util.Stack;\r\n\r\nimport javax.swing.*;\r\nimport javax.swing.event.DocumentEvent;\r\nimport javax.swing.event.DocumentListener;\r\n\r\nimport org.fife.ui.rtextarea.Gutter;\r\nimport org.fife.ui.rtextarea.GutterEx;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.FileOperation;\r\nimport com.mucommander.utils.text.Translator;\r\nimport com.mucommander.ui.dialog.DialogOwner;\r\nimport com.mucommander.ui.dialog.InformationDialog;\r\nimport com.mucommander.ui.encoding.EncodingListener;\r\nimport com.mucommander.ui.encoding.EncodingMenu;\r\nimport com.mucommander.ui.viewer.FileEditor;\r\nimport com.mucommander.ui.viewer.FileFrame;\r\n\r\n\r\n/**\r\n * A simple text editor.\r\n *\r\n * @author Maxence Bernard, Nicolas Rinaudo, Arik Hadas\r\n */\r\npublic class TextEditor extends FileEditor implements DocumentListener, EncodingListener {\r\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(TextEditor.class);\r\n\r\n    //private TextMenuHelper menuHelper;\r\n    private final TextEditorImpl textEditorImpl;\r\n    private final TextViewer textViewerDelegate;\r\n    private StatusBar statusBar;\r\n\r\n    private GutterEx gutter;\r\n\r\n\r\n    TextEditor() {\r\n        textEditorImpl = new TextEditorImpl(true, getStatusBar());\r\n//        Font defaultFont = new Font(\"Monospaced\", Font.PLAIN, 12);\r\n        initGutter();\r\n        //gutter.setActiveLineRangeColor(new Color(0,0,255));\r\n        setLineNumbersEnabled(TextViewer.isLineNumbers());\r\n\r\n        // Set miscellaneous properties.\r\n        setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_ALWAYS);\r\n        setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);\r\n\r\n    \ttextViewerDelegate = new TextViewer(textEditorImpl) {\r\n    \t\t\r\n    \t\t@Override\r\n            public void setComponentToPresent(JComponent component) {\r\n    \t\t\tTextEditor.this.setComponentToPresent(component);\r\n    \t\t}\r\n    \t\t\r\n    \t\t@Override\r\n    \t\tprotected void showLineNumbers(boolean show) {\r\n                setLineNumbersEnabled(show);\r\n                TextViewer.setLineNumbers(show);\r\n    \t\t\t//TextEditor.this.setRowHeaderView(show ? new TextLineNumbersPanel(textEditorImpl.getTextArea()) : null);\r\n    \t    }\r\n    \t\t\r\n    \t\t@Override\r\n    \t\tprotected void initMenuBarItems() {\r\n                menuHelper = new TextMenuHelper(textEditorImpl, true);\r\n                //menuHelper.initMenu(TextEditor.this, TextEditor.this.getRowHeader().getView() != null);\r\n                menuHelper.initMenu(TextEditor.this, TextViewer.isLineNumbers());\r\n                //menuHelper.setupFileMenu(menuFile, TextEditor.this, getCurrentFile());\r\n                textEditorImpl.setMenuHelper(menuHelper);\r\n    \t\t}\r\n    \t};\r\n\r\n        //setComponentToPresent(textEditorImpl.getTextArea());\r\n        setComponentToPresent(textEditorImpl.getEditorComponent());\r\n    }\r\n\r\n    private void initGutter() {\r\n        gutter = new GutterEx(textEditorImpl.getTextArea());\r\n        gutter.setLineNumberFont(textEditorImpl.getTextArea().getFont());\r\n        // TODO\r\n        gutter.setBackground(Color.LIGHT_GRAY);\r\n        gutter.setForeground(Color.black);\r\n    }\r\n\r\n    @Override\r\n    public void setComponentToPresent(JComponent component) {\r\n\t\tgetViewport().add(component);\r\n\t}\r\n\r\n    private void loadDocument(InputStream in, String encoding, DocumentListener documentListener) throws IOException {\r\n    \ttextViewerDelegate.loadDocument(in, encoding, documentListener);\r\n        if (getStatusBar() != null) {\r\n            getStatusBar().setEncoding(encoding);\r\n        }\r\n    }\r\n    \r\n    private void write(OutputStream out) throws IOException {\r\n    \t//textEditorImpl.write(new BOMWriter(out, textViewerDelegate.getEncoding()));\r\n        textEditorImpl.write(new OutputStreamWriter(out, textViewerDelegate.getEncoding()));\r\n    }\r\n\r\n    @Override\r\n    public JMenuBar getMenuBar() {\r\n    \tJMenuBar menuBar = super.getMenuBar();\r\n\r\n    \t// Encoding menu\r\n        EncodingMenu encodingMenu = new EncodingMenu(new DialogOwner(getFrame()), textViewerDelegate.getEncoding());\r\n        encodingMenu.addEncodingListener(this);\r\n\r\n        menuBar.add(textViewerDelegate.menuHelper.getEditMenu());\r\n        menuBar.add(textViewerDelegate.menuHelper.getSearchMenu());\r\n        menuBar.add(textViewerDelegate.menuHelper.getViewMenu());\r\n        menuBar.add(textViewerDelegate.menuHelper.getToolsMenu());\r\n        menuBar.add(encodingMenu);\r\n\r\n        textEditorImpl.getTextArea().setFocusTraversalKeysEnabled(false);\r\n        textViewerDelegate.setMainKeyListener(textEditorImpl.getTextArea(), menuBar);\r\n        textViewerDelegate.menuHelper.setupFileMenu(menuFile, TextEditor.this, getCurrentFile());\r\n    \treturn menuBar;\r\n    }\r\n\r\n    @Override\r\n    public StatusBar getStatusBar() {\r\n        if (statusBar == null) {\r\n            statusBar = new StatusBar();\r\n        }\r\n        return statusBar;\r\n    }\r\n\r\n    @Override\r\n    protected void saveStateOnClose() {\r\n        textViewerDelegate.saveState(getVerticalScrollBar());\r\n        if (getCurrentFile() != null) { // possible if loading was interrupted by Esc\r\n            try {\r\n                getCurrentFile().closePushbackInputStream();\r\n            } catch (IOException e) {\r\n                LOGGER.error(\"IO Exception on save state\", e);\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    protected void restoreStateOnStartup() {\r\n        final TextArea textArea = textEditorImpl.getTextArea();\r\n        final TextFilesHistory.FileRecord historyRecord = textViewerDelegate.getHistoryRecord();\r\n        if (historyRecord != null) {\r\n            getViewport().setViewPosition(new java.awt.Point(0, historyRecord.getScrollPosition()));\r\n            textArea.gotoLine(historyRecord.getLine(), historyRecord.getColumn());\r\n        }\r\n    }\r\n\r\n\r\n\r\n    @Override\r\n    protected void saveAs(AbstractFile destFile) {\r\n//        OutputStream out = null;\r\n        getStatusBar().setStatusMessage(Translator.get(\"text_editor.writing\"));\r\n\r\n        //boolean error;\r\n        try (OutputStream out = destFile.getOutputStream()) {\r\n            write(out);\r\n        } catch (Throwable e) {\r\n            getStatusBar().setStatusMessage(Translator.get(\"text_editor.cant_save_file\"));\r\n            LOGGER.error(\"Exception on save\", e);\r\n            return;\r\n        }\r\n        // We get here only if the destination file was updated successfully\r\n        // so we can set that no further save is needed at this stage \r\n        setSaveNeeded(false);\r\n\r\n        // Change the parent folder's date to now, so that changes are picked up by folder auto-refresh (see ticket #258)\r\n        if (destFile.isFileOperationSupported(FileOperation.CHANGE_DATE)) {\r\n            try {\r\n                destFile.getParent().setLastModifiedDate(System.currentTimeMillis());\r\n            } catch (IOException e) {\r\n                LOGGER.debug(\"failed to change the date of {}\", destFile, e);\r\n                // Fail silently\r\n            }\r\n        }\r\n        getStatusBar().setStatusMessage(Translator.get(\"text_editor.saved\"));\r\n    }\r\n\r\n    @Override\r\n    public void setFrame(final FileFrame frame) {\r\n    \tsuper.setFrame(frame);\r\n    \ttextEditorImpl.setFrame(frame);\r\n    \t//frame.setFullScreen(TextViewer.isFullScreen());\r\n\r\n    \tgetInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_M, InputEvent.CTRL_DOWN_MASK), CUSTOM_FULL_SCREEN_EVENT);\r\n//    \tgetActionMap().put(CUSTOM_FULL_SCREEN_EVENT, new AbstractAction() {\r\n//    \t\tpublic void actionPerformed(ActionEvent e){\r\n//    \t\t\tTextViewer.setFullScreen(!frame.isFullScreen());\r\n//    \t\t\tframe.setFullScreen(TextViewer.isFullScreen());\r\n//    \t\t}\r\n//    \t});\r\n    }\r\n\r\n    @Override\r\n    public void show(AbstractFile file) {\r\n        TextArea textArea = textEditorImpl.getTextArea();\r\n        textArea.discardAllEdits();\r\n        textViewerDelegate.menuHelper.updateEditActions();\r\n\r\n        // TODO SHOULD BE IN SEPARATE THREAD !!!\r\n        TextFilesHistory.FileRecord historyRecord = textViewerDelegate.initHistoryRecord(file);\r\n        FileType type = historyRecord.getFileType();\r\n        if (type == null) {\r\n            type = FileType.getFileType(file);\r\n            historyRecord.setFileType(type);\r\n        }\r\n        if (type == FileType.NONE) {\r\n            type = TextEditorUtils.detectFileFormat(file);\r\n        }\r\n        textEditorImpl.prepareForEdit(file);\r\n        textEditorImpl.setSyntaxType(type);\r\n        textViewerDelegate.menuHelper.setSyntax(type);\r\n        textViewerDelegate.startEditing(file, this);\r\n        textArea.discardAllEdits();\r\n    }\r\n\r\n    @Override\r\n    public void changedUpdate(DocumentEvent e) {\r\n        textViewerDelegate.menuHelper.updateEditActions();\r\n        // ignore change event if it was caused by syntax change\r\n//        if (!textViewerDelegate.menuHelper.checkWaitChangeSyntaxEvent()) {\r\n            setSaveNeeded(true);\r\n //        }\r\n    }\r\n\r\n    @Override\r\n    public void insertUpdate(DocumentEvent e) {\r\n        setSaveNeeded(true);\r\n    }\r\n\r\n    @Override\r\n    public void removeUpdate(DocumentEvent e) {\r\n        setSaveNeeded(true);\r\n    }\r\n    \r\n    @Override\r\n    public void actionPerformed(ActionEvent e) {\r\n        if (textViewerDelegate.menuHelper.performAction(e, textViewerDelegate)) {\r\n            return;\r\n        }\r\n        super.actionPerformed(e);\r\n    }\r\n\r\n    @Override\r\n    public void encodingChanged(Object source, String oldEncoding, String newEncoding) {\r\n    \tif (!askSave()) {\r\n    \t\treturn;         // Abort if the file could not be saved\r\n        }\r\n\r\n        // Store caret and scrollbar position before change\r\n        TextArea textArea = textEditorImpl.getTextArea();\r\n        int line = textArea.getLine();\r\n        int column = textArea.getColumn();\r\n        int horizontalPos = getHorizontalScrollBar().getValue();\r\n        int verticalPos = getVerticalScrollBar().getValue();\r\n\r\n        try {\r\n    \t\t// Reload the file using the new encoding\r\n    \t\t// Note: loadDocument closes the InputStream\r\n    \t\tloadDocument(getCurrentFile().getInputStream(), newEncoding, null);\r\n\r\n            // Restore caret and scrollbar\r\n            textArea.gotoLine(line, column);\r\n            getViewport().setViewPosition(new java.awt.Point(horizontalPos, verticalPos));\r\n            setSaveNeeded(false);\r\n        } catch (IOException ex) {\r\n    \t\tInformationDialog.showErrorDialog(getFrame(), Translator.get(\"read_error\"), Translator.get(\"file_editor.cannot_read_file\", getCurrentFile().getName()));\r\n    \t}\r\n    }\r\n\r\n    /**\r\n     * Ensures the gutter is visible if it's showing anything.\r\n     */\r\n    private void checkGutterVisibility() {\r\n        int count = gutter.getComponentCount();\r\n        if (count == 0) {\r\n            if (getRowHeader() != null && getRowHeader().getView() == gutter) {\r\n                setRowHeaderView(null);\r\n            }\r\n        } else {\r\n            if (getRowHeader() == null || getRowHeader().getView() == null) {\r\n                setRowHeaderView(gutter);\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the gutter.\r\n     *\r\n     * @return The gutter.\r\n     */\r\n    public Gutter getGutter() {\r\n        return gutter;\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns <code>true</code> if the line numbers are enabled and visible.\r\n     *\r\n     * @return Whether line numbers are visible.\r\n     * @see #setLineNumbersEnabled(boolean)\r\n     */\r\n    private boolean getLineNumbersEnabled() {\r\n        return gutter.getLineNumbersEnabled();\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns whether the fold indicator is enabled.\r\n     *\r\n     * @return Whether the fold indicator is enabled.\r\n     * @see #setFoldIndicatorEnabled(boolean)\r\n     */\r\n    public boolean isFoldIndicatorEnabled() {\r\n        return gutter.isFoldIndicatorEnabled();\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns whether the icon row header is enabled.\r\n     *\r\n     * @return Whether the icon row header is enabled.\r\n     * @see #setIconRowHeaderEnabled(boolean)\r\n     */\r\n    public boolean isIconRowHeaderEnabled() {\r\n        return gutter.isIconRowHeaderEnabled();\r\n    }\r\n\r\n\r\n    /**\r\n     * Toggles whether the fold indicator is enabled.\r\n     *\r\n     * @param enabled Whether the fold indicator should be enabled.\r\n     * @see #isFoldIndicatorEnabled()\r\n     */\r\n    public void setFoldIndicatorEnabled(boolean enabled) {\r\n        gutter.setFoldIndicatorEnabled(enabled);\r\n        checkGutterVisibility();\r\n    }\r\n\r\n\r\n    /**\r\n     * Toggles whether the icon row header (used for breakpoints, bookmarks,\r\n     * etc.) is enabled.\r\n     *\r\n     * @param enabled Whether the icon row header is enabled.\r\n     * @see #isIconRowHeaderEnabled()\r\n     */\r\n    public void setIconRowHeaderEnabled(boolean enabled) {\r\n        gutter.setIconRowHeaderEnabled(enabled);\r\n        checkGutterVisibility();\r\n    }\r\n\r\n\r\n    /**\r\n     * Toggles whether line numbers are visible.\r\n     *\r\n     * @param enabled Whether line numbers should be visible.\r\n     * @see #getLineNumbersEnabled()\r\n     */\r\n    public void setLineNumbersEnabled(boolean enabled) {\r\n        gutter.setLineNumbersEnabled(enabled);\r\n        checkGutterVisibility();\r\n    }\r\n\r\n\r\n    /**\r\n     * Sets the view for this scroll pane.  This must be an {@link TextArea}.\r\n     *\r\n     * @param view The new view.\r\n     */\r\n    @Override\r\n    public void setViewportView(Component view) {\r\n        TextArea rtaCandidate;\r\n\r\n        if (!(view instanceof TextArea)) {\r\n            rtaCandidate = getFirstRTextAreaDescendant(view);\r\n            if (rtaCandidate == null) {\r\n                throw new IllegalArgumentException(\"view must be either an RTextArea or a JLayer wrapping one\");\r\n            }\r\n        } else {\r\n            rtaCandidate = (TextArea)view;\r\n        }\r\n        super.setViewportView(view);\r\n        if (gutter != null) {\r\n            gutter.setTextArea(rtaCandidate);\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Returns the first descendant of a component that is an\r\n     * <code>RTextArea</code>.  This is primarily here to support\r\n     * <code>javax.swing.JLayer</code>s that wrap <code>RTextArea</code>s.\r\n     *\r\n     * @param comp The component to recursively look through.\r\n     * @return The first descendant text area, or <code>null</code> if none\r\n     *         is found.\r\n     */\r\n    private static TextArea getFirstRTextAreaDescendant(Component comp) {\r\n        Stack<Component> stack = new Stack<>();\r\n        stack.add(comp);\r\n        while (!stack.isEmpty()) {\r\n            Component current = stack.pop();\r\n            if (current instanceof TextArea) {\r\n                return (TextArea)current;\r\n            }\r\n            if (current instanceof Container container) {\r\n                stack.addAll(Arrays.asList(container.getComponents()));\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n\r\n\r\n    @Override\r\n    public void setSearchedText(String searchedText) {\r\n        textEditorImpl.setupSearchContext(searchedText);\r\n    }\r\n\r\n\r\n    @Override\r\n    public void setSearchedBytes(byte[] searchedBytes) {\r\n        try {\r\n            textEditorImpl.setupSearchContext(new String(searchedBytes, textViewerDelegate.getEncoding()));\r\n        } catch (UnsupportedEncodingException e) {\r\n            e.printStackTrace();\r\n        }\r\n    }\r\n\r\n    public TextArea getTextArea() {\r\n        return textEditorImpl.getTextArea();\r\n    }\r\n\r\n    public void gotoLine(int line, int colump) {\r\n        getTextArea().gotoLine(line, colump);\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/TextEditorCaretListener.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2017 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.text;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.tools.AvrAssemblerCommandsHelper;\nimport com.mucommander.utils.text.Translator;\n\nimport javax.swing.event.CaretEvent;\nimport javax.swing.event.CaretListener;\nimport java.io.IOException;\nimport java.util.StringTokenizer;\n\npublic class TextEditorCaretListener implements CaretListener {\n    private final TextEditorImpl textEditor;\n\n    TextEditorCaretListener(TextEditorImpl textEditor) {\n        this.textEditor = textEditor;\n    }\n\n    @Override\n    public void caretUpdate(CaretEvent e) {\n        if (textEditor.replaceDialogMode) {\n            return;\n        }\n        StatusBar statusBar = textEditor.getStatusBar();\n        TextArea textArea = textEditor.getTextArea();\n        if (statusBar == null) {\n            return;\n        }\n        int line = textArea.getLine();\n        int col = textArea.getColumn();\n        statusBar.setPosition(line, col);\n        statusBar.clearStatusMessage();\n\n        // check if we have 6-digit hex-word on cursor (color)\n        String str = textArea.getLineStr(line);\n        if (str == null || str.isEmpty()) {\n            statusBar.setColor(-1);\n            textEditor.selectIncludeFile(null);\n            return;\n        }\n        checkAssemblerInstruction(str);\n        checkColorOnCursor(str, col);\n        checkIncludeInstruction(str, col);\n    }\n\n    private void checkIncludeInstruction(String str, int col) {\n        if (!str.toLowerCase().contains(\"include\")) {\n            textEditor.selectIncludeFile(null);\n            return;\n        }\n        if (col > 0) {\n            col--;\n        }\n        try {\n            int lastQuote2 = str.indexOf('\"', col);\n            int lastQuote1 = str.indexOf('\\'', col);\n            int lastBracket = str.indexOf(\">\", col);\n            String quotedName = null;\n            if (lastQuote2 > 0) {\n                int firstQuote2 = str.lastIndexOf('\"', col);\n                if (firstQuote2 > 0) {\n                    quotedName = str.substring(firstQuote2 + 1, lastQuote2);\n                    if (quotedName.trim().isEmpty()) {\n                        quotedName = null;\n                    }\n                }\n            }\n            if (quotedName == null && lastBracket > 0) {\n                int firstBracket = str.lastIndexOf('<', col);\n                if (firstBracket > 0) {\n                    quotedName = str.substring(firstBracket + 1, lastBracket);\n                    if (quotedName.trim().isEmpty()) {\n                        quotedName = null;\n                    }\n                }\n            }\n            if (quotedName == null && lastQuote1 > 0) {\n                int firstQuote1 = str.lastIndexOf('\\'', col);\n                if (firstQuote1 > 0) {\n                    quotedName = str.substring(firstQuote1 + 1, lastBracket);\n                    if (quotedName.trim().isEmpty()) {\n                        quotedName = null;\n                    }\n                }\n            }\n            AbstractFile includeFile = getIncludeFile(quotedName);\n            if (includeFile != null) {\n                setStatusMessage(\"<html>\" + Translator.get(\"text_editor.press_alt_enter_to_open_file\") + \" <b>\" + quotedName + \"</b>\");\n                textEditor.selectIncludeFile(includeFile);\n                return;\n            }\n        } catch (StringIndexOutOfBoundsException ignore) {}\n        setStatusMessage(null);\n    }\n\n    private void checkAssemblerInstruction(String str) {\n        if (!isAvrAssembler()) {\n            return;\n        }\n        StringTokenizer tokenizer = new StringTokenizer(str, \" \\t\\n\\r\");\n        boolean found = false;\n        while (tokenizer.hasMoreElements()) {\n            String instruction = tokenizer.nextToken();\n            if (instruction.endsWith(\":\") || instruction.startsWith(\";\") || instruction.startsWith(\"//\")) {\n                continue;\n            }\n            String description = AvrAssemblerCommandsHelper.getCommandDescription(instruction);\n            if (description != null) {\n                setStatusMessage(description);\n                found = true;\n                break;\n            }\n        }\n        if (!found) {\n            setStatusMessage(\"\");\n        }\n    }\n\n\n    private void checkColorOnCursor(String str, int col) {\n        if (str.length() < 6 || col >= str.length()) {\n            clearStatusColor();\n            return;\n        }\n        char ch = str.charAt(col);\n        if (isHexDigit(ch)) {\n            String word = \"\" + ch;\n            for (int pos = col-1; pos >= 0; pos--) {\n                char c = str.charAt(pos);\n                if (isHexDigit(c)) {\n                    word = c + word;\n                } else {\n                    break;\n                }\n            }\n            for (int pos = col+1; pos < str.length(); pos++) {\n                char c = str.charAt(pos);\n                if (isHexDigit(c)) {\n                    word = word + c;\n                } else {\n                    break;\n                }\n            }\n            if (word.length() == 8) {\n                word = word.substring(2);\n            }\n            if (word.length() == 6) {\n                try {\n                    setStatusColor(Integer.parseInt(word, 16));\n                    return;\n                } catch (Exception ignore) { }\n            }\n        }\n        clearStatusColor();\n    }\n\n\n    private static boolean isHexDigit(char ch) {\n        return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f');\n    }\n\n    private void setStatusMessage(String msg) {\n        StatusBar statusBar = textEditor.getStatusBar();\n        if (statusBar != null) {\n            statusBar.setStatusMessage(msg);\n        }\n    }\n\n    private void setStatusColor(int color) {\n        StatusBar statusBar = textEditor.getStatusBar();\n        if (statusBar != null) {\n            statusBar.setColor(color);\n        }\n    }\n\n    private void clearStatusColor() {\n        setStatusColor(-1);\n    }\n\n    private boolean isAvrAssembler() {\n        return textEditor.getTextArea().getFileType() == FileType.ASSEMBLER_AVR;\n    }\n\n    private AbstractFile getIncludeFile(String fileName) {\n        if (fileName == null || fileName.isEmpty()) {\n            return null;\n        }\n        try {\n            AbstractFile selectedFile = textEditor.getFile().getParent().getChild(fileName);\n            if (selectedFile != null && selectedFile.exists()) {\n                return selectedFile;\n            }\n        } catch (IOException ignore) {}\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/TextEditorImpl.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2012 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.viewer.text;\r\n\r\nimport com.mucommander.cache.TextHistory;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.ui.action.ActionProperties;\r\nimport com.mucommander.ui.action.impl.EditAction;\r\nimport com.mucommander.ui.action.impl.ViewAction;\r\nimport com.mucommander.ui.main.quicklist.ViewedAndEditedFilesQL;\r\nimport com.mucommander.ui.theme.*;\r\nimport com.mucommander.ui.viewer.EditorRegistrar;\r\nimport com.mucommander.ui.viewer.FileFrame;\r\nimport com.mucommander.ui.viewer.FileFrameCreateListener;\r\nimport com.mucommander.ui.viewer.ViewerRegistrar;\r\nimport com.mucommander.ui.viewer.text.search.FindDialog;\r\nimport com.mucommander.ui.viewer.text.search.ReplaceDialog;\r\nimport com.mucommander.ui.viewer.text.search.SearchEvent;\r\nimport com.mucommander.ui.viewer.text.search.SearchListener;\r\nimport com.mucommander.ui.viewer.text.tools.ExecPanel;\r\nimport com.mucommander.ui.viewer.text.tools.ExecUtils;\r\nimport com.mucommander.ui.viewer.text.tools.ProcessParams;\r\nimport com.mucommander.utils.text.Translator;\r\nimport lombok.Getter;\r\nimport lombok.Setter;\r\nimport lombok.extern.slf4j.Slf4j;\r\nimport org.fife.ui.rtextarea.SearchContext;\r\nimport org.fife.ui.rtextarea.SearchEngine;\r\nimport org.fife.ui.rtextarea.SearchResult;\r\n\r\nimport javax.swing.*;\r\nimport javax.swing.event.DocumentListener;\r\nimport javax.swing.text.BadLocationException;\r\nimport javax.swing.text.DefaultEditorKit;\r\nimport javax.swing.text.Document;\r\nimport java.awt.*;\r\nimport java.awt.event.KeyAdapter;\r\nimport java.awt.event.KeyEvent;\r\nimport java.awt.event.KeyListener;\r\nimport java.io.BufferedWriter;\r\nimport java.io.IOException;\r\nimport java.io.Reader;\r\nimport java.io.Writer;\r\nimport java.util.LinkedList;\r\n\r\n/**\r\n * Text editor implementation used by {@link TextViewer} and {@link TextEditor}.\r\n *\r\n * @author Maxence Bernard, Mariusz Jakubowski, Nicolas Rinaudo, Arik Hadas, Oleg Trifonov\r\n */\r\n@Slf4j\r\nclass TextEditorImpl implements ThemeListener, ThemeId {\r\n    private static final Insets INSETS = new Insets(4, 3, 4, 3);\r\n\r\n    private static String lastLoadedThemeName;\r\n    private static EditorTheme editorTheme;\r\n\r\n\t@Setter\r\n    FileFrame frame;\r\n\r\n    private final TextArea textArea;\r\n\r\n    private SearchContext searchContext;\r\n\r\n\t/** Indicates whether there is a line separator in the original file */\r\n\tprivate boolean lineSeparatorExists;\r\n\r\n    @Getter\r\n    private StatusBar statusBar;\r\n\r\n    /**\r\n     * Full path to editable file with slash at end\r\n     */\r\n    private AbstractFile file;\r\n\r\n    /**\r\n     * If not null then contains included file under cursor that will ber opened by Ctrl(Cmd)+Enter hotkey\r\n     */\r\n    private AbstractFile selectedIncludeFile;\r\n\r\n    private JSplitPane splitPane;\r\n    private ExecPanel pnlBuild;\r\n    private int storedSplitDividerSize;\r\n    private int storedSplitterPos;\r\n    private ProcessParams buildParams;\r\n\r\n    boolean replaceDialogMode = false;\r\n\r\n\r\n    private final KeyListener textAreaKeyListener = new KeyAdapter() {\r\n        @Override\r\n        public void keyPressed(KeyEvent e) {\r\n            if (e.getKeyCode() == KeyEvent.VK_ENTER && e.getModifiersEx() == KeyEvent.ALT_DOWN_MASK) {\r\n                if (selectedIncludeFile != null) {\r\n                    openOtherFile(selectedIncludeFile);\r\n                }\r\n                return;\r\n            }\r\n\r\n            int mask = OsFamily.MAC_OS_X.isCurrent() ? KeyEvent.ALT_DOWN_MASK : KeyEvent.CTRL_DOWN_MASK;\r\n            if (textArea.isEditable() && e.getKeyChar() == KeyEvent.VK_TAB && e.getModifiersEx() == mask) {\r\n                ViewedAndEditedFilesQL viewedAndEditedFilesQL = new ViewedAndEditedFilesQL(frame, frame.getFilePresenter().getCurrentFile());\r\n                viewedAndEditedFilesQL.show();\r\n                e.consume();\r\n            }\r\n        }\r\n    };\r\n\r\n    private TextMenuHelper menuHelper;\r\n\r\n\r\n\r\n\tTextEditorImpl(boolean isEditable, StatusBar statusBar) {\r\n\t\t// Initialize text area\r\n        this.textArea = createTextArea(isEditable);\r\n        this.statusBar = statusBar;\r\n        // Listen to theme changes to update the text area if it is visible\r\n\t\tThemeManager.addCurrentThemeListener(this);\r\n\t}\r\n\r\n\tprivate TextArea createTextArea(boolean isEditable) {\r\n        TextArea textArea = new TextArea() {\r\n            @Override\r\n            public Insets getInsets() {\r\n                return INSETS;\r\n            }\r\n        };\r\n        textArea.setCurrentLineHighlightColor(ThemeManager.getCurrentColor(EDITOR_CURRENT_BACKGROUND_COLOR));\r\n        textArea.setAntiAliasingEnabled(true);\r\n\t\ttextArea.setEditable(isEditable);\r\n        textArea.addKeyListener(textAreaKeyListener);\r\n\r\n        if (lastLoadedThemeName == null || !lastLoadedThemeName.equals(ThemeManager.getCurrentSyntaxThemeName())) {\r\n            try {\r\n                editorTheme = ThemeManager.readEditorTheme(ThemeManager.getCurrentSyntaxThemeName());\r\n                lastLoadedThemeName = ThemeManager.getCurrentSyntaxThemeName();\r\n            } catch (Exception e) {\r\n                log.error(\"Can't load editor theme\", e);\r\n            }\r\n        }\r\n        editorTheme.apply(textArea);\r\n\r\n\t\t// Use theme colors and font\r\n\t\ttextArea.setForeground(ThemeManager.getCurrentColor(EDITOR_FOREGROUND_COLOR));\r\n\t\ttextArea.setCaretColor(ThemeManager.getCurrentColor(EDITOR_FOREGROUND_COLOR));\r\n        Color background = ThemeManager.getCurrentColor(EDITOR_BACKGROUND_COLOR);\r\n        textArea.setBackground(background);\r\n\r\n        for (int i = 1; i <= textArea.getSecondaryLanguageCount(); i++) {\r\n            textArea.setSecondaryLanguageBackground(i, background);\r\n        }\r\n\t\ttextArea.setSelectedTextColor(ThemeManager.getCurrentColor(EDITOR_SELECTED_FOREGROUND_COLOR));\r\n\t\ttextArea.setSelectionColor(ThemeManager.getCurrentColor(EDITOR_SELECTED_BACKGROUND_COLOR));\r\n\t\ttextArea.setFont(ThemeManager.getCurrentFont(EDITOR_FONT));\r\n        textArea.setCodeFoldingEnabled(true);\r\n\r\n\t\ttextArea.setWrapStyleWord(true);\r\n\r\n\t\ttextArea.addMouseWheelListener(e -> {\r\n            boolean isCtrlPressed = (e.getModifiersEx() & KeyEvent.CTRL_DOWN_MASK) != 0;\r\n            if (isCtrlPressed) {\r\n                Font currentFont = textArea.getFont();\r\n                int currentFontSize = currentFont.getSize();\r\n                boolean rotationUp = e.getWheelRotation() < 0;\r\n                if (rotationUp || currentFontSize > 1) {\r\n                    Font newFont = new Font(currentFont.getName(), currentFont.getStyle(), currentFontSize + (rotationUp ? 1 : -1));\r\n                    textArea.setFont(newFont);\r\n                }\r\n            } else {\r\n                textArea.getParent().dispatchEvent(e);\r\n            }\r\n\t\t});\r\n\r\n        textArea.addCaretListener(new TextEditorCaretListener(this));\r\n        return textArea;\r\n\t}\r\n\r\n\r\n    void find() {\r\n        SearchListener searchListener = new SearchListener() {\r\n            @Override\r\n            public void searchEvent(SearchEvent e) {\r\n                searchContext = e.getSearchContext();\r\n                String searchString = searchContext.getSearchFor();\r\n                TextHistory.getInstance().add(TextHistory.Type.TEXT_SEARCH, searchString, true);\r\n                SearchResult result = SearchEngine.find(textArea, searchContext);\r\n                if (!result.wasFound()) {\r\n                    beep();\r\n                    setStatusMessage(Translator.get(\"text_editor.text_not_found\"));\r\n                } else {\r\n                    setStatusMessage(Translator.get(\"text_editor.found\") + \" \" + result.getMarkedCount() + \" \" + Translator.get(\"text_editor.matches\"));\r\n                    textArea.setCaretPosition(textArea.getSelectionStart());\r\n                }\r\n\r\n                // Request the focus on the text area which could be lost after the Find dialog was disposed\r\n                textArea.requestFocus();\r\n            }\r\n\r\n            @Override\r\n            public String getSelectedText() {\r\n                return textArea.getSelectedText();\r\n            }\r\n        };\r\n        FindDialog dlg = new FindDialog(frame, searchListener);\r\n        dlg.setSearchString(searchContext != null ? searchContext.getSearchFor() : \"\");\r\n        dlg.showDialog();\r\n\t}\r\n\r\n\r\n    private void findMore(boolean forward) {\r\n        if (searchContext == null) {\r\n            beep();\r\n            return;\r\n        }\r\n        if (!forward && textArea.getCaretPosition() == 0) {\r\n            beep();\r\n            return;\r\n        }\r\n        searchContext.setSearchForward(forward);\r\n        int savedCaretPosition = textArea.getCaretPosition();\r\n        try {\r\n            int pos = textArea.getSelectionStart();\r\n            if (forward) {\r\n                pos += searchContext.getSearchFor().length();\r\n            }\r\n            textArea.setCaretPosition(pos);\r\n        } catch (IllegalArgumentException ignore) {}\r\n        SearchResult result = SearchEngine.find(textArea, searchContext);\r\n        if (!result.wasFound()) {\r\n            textArea.setCaretPosition(savedCaretPosition);\r\n            setStatusMessage(Translator.get(\"text_editor.text_not_found\"));\r\n            beep();\r\n        } else {\r\n            setStatusMessage(\"\");\r\n            textArea.setCaretPosition(textArea.getSelectionStart());\r\n        }\r\n    }\r\n\r\n\tvoid findNext() {\r\n        if (searchContext != null) {\r\n            findMore(true);\r\n            return;\r\n        }\r\n        String last = FindDialog.getLastSearchStr();\r\n        if (last != null) {\r\n            setupSearchContext(FindDialog.getLastSearchStr());\r\n        }\r\n        find();\r\n\t}\r\n\r\n    void replace() {\r\n        SearchListener searchListener = new SearchListener() {\r\n            @Override\r\n            public void searchEvent(SearchEvent e) {\r\n                searchContext = e.getSearchContext();\r\n\r\n                String searchString = searchContext.getSearchFor();\r\n                TextHistory.getInstance().add(TextHistory.Type.TEXT_SEARCH, searchString, true);\r\n                //frame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));\r\n                search(e);\r\n            }\r\n\r\n            private void search(SearchEvent e) {\r\n                SearchResult result;\r\n                replaceDialogMode = true;\r\n                result = switch (e.getType()) {\r\n                    case FIND -> SearchEngine.find(textArea, searchContext);\r\n                    case REPLACE -> SearchEngine.replace(textArea, searchContext);\r\n                    case REPLACE_ALL -> SearchEngine.replaceAll(textArea, searchContext);\r\n                    default -> null;\r\n                };\r\n                replaceDialogMode = false;\r\n                if (result == null) {\r\n                    return;\r\n                }\r\n                if (!result.wasFound()) {\r\n                    beep();\r\n                    setStatusMessage(Translator.get(\"text_editor.text_not_found\"));\r\n                } else {\r\n                    if (e.getType() == SearchEvent.Type.REPLACE_ALL) {\r\n                        setStatusMessage(Translator.get(\"text_editor.replaced\") + \" \" + result.getCount() + \" \" + Translator.get(\"text_editor.occurrences\"));\r\n                    } else {\r\n                        setStatusMessage(Translator.get(\"text_editor.found\") + \" \" + result.getMarkedCount() + \" \" + Translator.get(\"text_editor.matches\"));\r\n                    }\r\n                }\r\n            }\r\n\r\n            @Override\r\n            public String getSelectedText() {\r\n                return textArea.getSelectedText();\r\n            }\r\n        };\r\n        ReplaceDialog dlg = new ReplaceDialog(frame, searchListener);\r\n        dlg.setSearchString(searchContext != null ? searchContext.getSearchFor() : \"\");\r\n        dlg.showDialog();\r\n    }\r\n\r\n\r\n\tvoid findPrevious() {\r\n        if (searchContext == null) {\r\n            String last = FindDialog.getLastSearchStr();\r\n            if (last != null) {\r\n                setupSearchContext(FindDialog.getLastSearchStr());\r\n            }\r\n            find();\r\n        } else {\r\n            findMore(false);\r\n        }\r\n\t}\r\n\r\n    void gotoLine() {\r\n        new GotoLineDialog(frame, textArea.getLineCount(), textArea::gotoLine).showDialog();\r\n    }\r\n\r\n\r\n\tpublic boolean isWrap() {\r\n\t\treturn textArea.getLineWrap();\r\n\t}\r\n\r\n\r\n\tvoid wrap(boolean isWrap) {\r\n\t\ttextArea.setLineWrap(isWrap);\r\n\t\ttextArea.repaint();\r\n\t}\r\n\r\n\tvoid copy() {\r\n\t\ttextArea.copy();\r\n\t}\r\n\r\n\tvoid cut() {\r\n\t\ttextArea.cut();\r\n\t}\r\n\r\n\tvoid paste() {\r\n\t\ttextArea.paste();\r\n\t}\r\n\r\n\tvoid selectAll() {\r\n\t\ttextArea.selectAll();\r\n\t}\r\n\r\n    void undo() {\r\n        textArea.undoLastAction();\r\n    }\r\n\r\n    void redo() {\r\n        textArea.redoLastAction();\r\n    }\r\n\r\n\tvoid requestFocus() {\r\n\t\ttextArea.requestFocus();\r\n\t}\r\n\r\n    TextArea getTextArea() {\r\n\t\treturn textArea;\r\n\t}\r\n\r\n    JComponent getEditorComponent() {\r\n//        if (splitPane == null) {\r\n//            splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\r\n//            splitPane.add(textArea);\r\n//        }\r\n//        return splitPane;\r\n        return textArea;\r\n    }\r\n\r\n\tvoid addDocumentListener(DocumentListener documentListener) {\r\n\t\ttextArea.getDocument().addDocumentListener(documentListener);\r\n\t}\r\n\r\n\tvoid read(Reader reader) throws IOException {\r\n\t\t// Feed the file's contents to text area\r\n\t\ttextArea.read(reader, null);\r\n\r\n\t\t// If there are more than one lines, there is a line separator\r\n\t\tlineSeparatorExists = textArea.getLineCount() > 1;\r\n\r\n\t\t// Move cursor to the top\r\n//\t\ttextArea.setCaretPosition(0);\r\n\r\n\t}\r\n\r\n\tvoid write(Writer writer) throws IOException {\r\n\t\tDocument document = textArea.getDocument();\r\n\r\n\t\t// According to the documentation in DefaultEditorKit, the line separator is set to be as the system property\r\n\t\t// if no other line separator exists in the file, but in practice it is not, so this is a workaround for it\r\n\t\tif (!lineSeparatorExists)\r\n\t\t\tdocument.putProperty(DefaultEditorKit.EndOfLineStringProperty, System.lineSeparator());\r\n\r\n\t\ttry {\r\n\t\t\ttextArea.getUI().getEditorKit(textArea).write(new BufferedWriter(writer), document, 0, document.getLength());\r\n\t\t} catch(BadLocationException e) {\r\n\t\t\tthrow new IOException(e.getMessage());\r\n\t\t}\r\n\t}\r\n\r\n\r\n\t/**\r\n\t * Receives theme color changes notifications.\r\n\t */\r\n\t@Override\r\n\tpublic void colorChanged(ColorChangedEvent event) {\r\n\t\tswitch (event.getColorId()) {\r\n            case EDITOR_FOREGROUND_COLOR:\r\n                textArea.setForeground(event.getColor());\r\n                break;\r\n\r\n            case EDITOR_BACKGROUND_COLOR:\r\n                textArea.setBackground(event.getColor());\r\n                break;\r\n\r\n            case EDITOR_SELECTED_FOREGROUND_COLOR:\r\n                textArea.setSelectedTextColor(event.getColor());\r\n                break;\r\n\r\n            case EDITOR_SELECTED_BACKGROUND_COLOR:\r\n                textArea.setSelectionColor(event.getColor());\r\n                break;\r\n\r\n            case EDITOR_CURRENT_BACKGROUND_COLOR:\r\n                textArea.setCurrentLineHighlightColor(event.getColor());\r\n                break;\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Receives theme font changes notifications.\r\n\t */\r\n\tpublic void fontChanged(FontChangedEvent event) {\r\n\t\tif (event.getFontId() == EDITOR_FONT) {\r\n            textArea.setFont(event.getFont());\r\n        }\r\n\t}\r\n\r\n\r\n\r\n    void build() {\r\n\t    if (buildParams == null) {\r\n\t        return;\r\n        }\r\n        showBuildPanel();\r\n\r\n        ProcessParams params = ExecUtils.getBuilderParams(getFile());\r\n        if (params != null) {\r\n            pnlBuild.runCommand(params.folder, params.command);\r\n        }\r\n    }\r\n\r\n    private void showBuildPanel() {\r\n        if (splitPane == null) {\r\n            splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\r\n            splitPane.add(frame.getFilePresenter());\r\n            splitPane.setDividerLocation(frame.getHeight()*2/3);\r\n            splitPane.setOneTouchExpandable(true);\r\n            pnlBuild = new ExecPanel(this::closeBuildPanel, this::gotoBuildError);\r\n\r\n            splitPane.add(pnlBuild);\r\n            frame.getContentPane().remove(frame.getFilePresenter());\r\n            frame.getContentPane().add(splitPane, BorderLayout.CENTER);\r\n            frame.getContentPane().doLayout();\r\n        }\r\n        if (splitPane.getDividerSize() <= 0) {\r\n            splitPane.setDividerSize(storedSplitDividerSize);\r\n        }\r\n        if (storedSplitterPos > 0) {\r\n\r\n            splitPane.setDividerLocation(storedSplitterPos);\r\n        }\r\n        pnlBuild.setVisible(true);\r\n        splitPane.doLayout();\r\n        pnlBuild.doLayout();\r\n    }\r\n\r\n\r\n    private void closeBuildPanel() {\r\n\t    if (splitPane == null || pnlBuild == null) {\r\n\t        return;\r\n        }\r\n        if (splitPane.getDividerSize() > 0) {\r\n\t        storedSplitDividerSize = splitPane.getDividerSize();\r\n            storedSplitterPos = splitPane.getDividerLocation();\r\n        }\r\n\t    if (pnlBuild != null) {\r\n\t        pnlBuild.setVisible(false);\r\n        }\r\n        splitPane.setDividerSize(0);\r\n    }\r\n\r\n    private void gotoBuildError(AbstractFile file, int line, int column) {\r\n\t    if (this.file.getAbsolutePath().equals(file.getAbsolutePath())) {\r\n\t        textArea.gotoLine(line, column);\r\n        } else {\r\n            TextFilesHistory.FileRecord historyRecord = TextFilesHistory.getInstance().get(file);\r\n            historyRecord.update(line, line, Math.max(column, 0), historyRecord.getFileType(), historyRecord.getEncoding());\r\n            openOtherFile(file, fileFrame -> {\r\n                fileFrame.returnFocusTo(fileFrame);\r\n                TextArea newTextArea = ((TextEditor)fileFrame.getFilePresenter()).getTextArea();\r\n                SwingUtilities.invokeLater(() -> newTextArea.gotoLine(line, column));\r\n            });\r\n        }\r\n    }\r\n\r\n\r\n    void setStatusBar(StatusBar statusBar) {\r\n        this.statusBar = statusBar;\r\n    }\r\n\r\n    void setSyntaxType(FileType fileType) {\r\n        textArea.setFileType(fileType);\r\n        if (statusBar != null) {\r\n            statusBar.setSyntax(fileType.getName());\r\n        }\r\n    }\r\n\r\n    void setStatusMessage(String message) {\r\n        if (getStatusBar() != null) {\r\n            getStatusBar().setStatusMessage(message);\r\n        }\r\n    }\r\n\r\n    private static void beep() {\r\n        // The beep method is called from a separate thread because this method seems to lock until the beep has\r\n        // been played entirely. If the 'Find next' shortcut is left pressed, a series of beeps will be played when\r\n        // the end of the file is reached, and we don't want those beeps to played one after the other as to:\r\n        // 1/ not lock the event thread\r\n        // 2/ have those beeps to end rather sooner than later\r\n        new Thread(() -> Toolkit.getDefaultToolkit().beep()).start();\r\n    }\r\n\r\n\r\n    void setupSearchContext(String searchStr) {\r\n        searchContext = new SearchContext(searchStr);\r\n    }\r\n\r\n    void prepareForEdit(AbstractFile file) {\r\n        this.file = file;\r\n        this.buildParams = ExecUtils.getBuilderParams(file);\r\n        if (menuHelper != null) {\r\n            menuHelper.setBuildable(buildParams != null);\r\n        }\r\n    }\r\n\r\n    void prepareForView(AbstractFile file) {\r\n        this.file = file;\r\n    }\r\n\r\n    void selectIncludeFile(AbstractFile file) {\r\n\t    this.selectedIncludeFile = file;\r\n    }\r\n\r\n    AbstractFile getFile() {\r\n\t    return file;\r\n    }\r\n\r\n\r\n    void setMenuHelper(TextMenuHelper menuHelper) {\r\n        this.menuHelper = menuHelper;\r\n    }\r\n\r\n    void addCurrentFileToBookmarks() {\r\n\t    AbstractFile currentFile = getFile();\r\n        getBookmarkFilesList().addLast(currentFile.getURL().toString());\r\n        TextHistory.getInstance().save(TextHistory.Type.EDITOR_BOOKMARKS);\r\n    }\r\n\r\n    void removeCurrentFileFromBookmarks() {\r\n        AbstractFile currentFile = getFile();\r\n        getBookmarkFilesList().remove(currentFile.getURL().toString());\r\n        TextHistory.getInstance().save(TextHistory.Type.EDITOR_BOOKMARKS);\r\n    }\r\n\r\n    void showFilesQuickList() {\r\n        AbstractFile currentFile = getFile();\r\n        ViewedAndEditedFilesQL viewedAndEditedFilesQL = new ViewedAndEditedFilesQL(frame, currentFile);\r\n        viewedAndEditedFilesQL.show();\r\n    }\r\n\r\n    private static LinkedList<String> getBookmarkFilesList() {\r\n        return TextHistory.getInstance().getList(TextHistory.Type.EDITOR_BOOKMARKS);\r\n    }\r\n\r\n\r\n    private static AbstractFile getFileWithSameNameAndExt(AbstractFile file, String ...extensions) {\r\n        String fileName = file.getNameWithoutExtension();\r\n\t    for (String ext : extensions) {\r\n            try {\r\n                AbstractFile selectedFile = file.getParent().getChild(fileName + ext);\r\n                if (selectedFile != null && selectedFile.exists()) {\r\n                    return selectedFile;\r\n                }\r\n            } catch (IOException ignore) {}\r\n        }\r\n        return null;\r\n    }\r\n\r\n\r\n    void switchBetweenHeaderAndSource() {\r\n\t    AbstractFile currentFile = getFile();\r\n        String ext = file.getExtension().toLowerCase();\r\n\t    boolean isSource = \"c\".equals(ext) || \"cpp\".equals(ext) || \"cc\".equals(ext);\r\n        boolean isHeader = \"h\".equals(ext) || \"hpp\".equals(ext);\r\n        AbstractFile fileToOpen;\r\n        if (isSource) {\r\n            fileToOpen = getFileWithSameNameAndExt(currentFile, \".h\", \".hpp\");\r\n        } else if (isHeader) {\r\n            fileToOpen = getFileWithSameNameAndExt(currentFile, \".c\", \".cpp\", \".cc\");\r\n        } else {\r\n            return;\r\n        }\r\n        if (fileToOpen != null) {\r\n            openOtherFile(fileToOpen);\r\n        }\r\n    }\r\n\r\n    private void openOtherFile(AbstractFile path, FileFrameCreateListener createListener) {\r\n        if (textArea.isEditable()) {\r\n            EditorRegistrar.createEditorFrame(frame.getMainFrame(), path,\r\n                    ActionProperties.getActionIcon(EditAction.Descriptor.ACTION_ID).getImage(), createListener);\r\n        } else {\r\n            ViewerRegistrar.createViewerFrame(frame.getMainFrame(), path,\r\n                    ActionProperties.getActionIcon(ViewAction.Descriptor.ACTION_ID).getImage(), createListener);\r\n        }\r\n    }\r\n\r\n    private void openOtherFile(AbstractFile path) {\r\n\t    openOtherFile(path, null);\r\n    }\r\n\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/TextEditorUtils.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2017 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.text;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.io.BufferPool;\nimport com.mucommander.commons.io.StreamUtils;\nimport com.mucommander.commons.io.bom.BOMInputStream;\nimport com.mucommander.ui.viewer.text.utils.CodeFormatException;\nimport com.mucommander.ui.viewer.text.utils.CodeFormatter;\nimport com.mucommander.utils.text.Translator;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.io.IOException;\nimport java.io.PushbackInputStream;\n\n@Slf4j\nclass TextEditorUtils {\n\n    static FileType detectFileFormat(AbstractFile file) {\n        byte[] bytes = BufferPool.getByteArray(256);\n        int readBytes;\n        try {\n            PushbackInputStream is = file.getPushBackInputStream(256);\n            BOMInputStream bomIs = new BOMInputStream(is);\n            readBytes = StreamUtils.readUpTo(bomIs, bytes);\n            is.unread(bytes, 0, readBytes);\n        } catch (IOException e) {\n            BufferPool.releaseByteArray(bytes);\n            log.error(\"detectFileFormat read error\", e);\n            try {\n                file.closePushbackInputStream();\n            } catch (IOException e1) {\n                log.error(\"detectFileFormat close error\", e);\n            }\n            return FileType.NONE;\n        }\n        String str = new String(bytes, 0, readBytes).trim().toLowerCase();\n        BufferPool.releaseByteArray(bytes);\n        if (readBytes < 5) {\n            return FileType.NONE;\n        }\n\n        if (str.startsWith(\"<?xml\")) {\n            return FileType.XML;\n        } else if (str.startsWith(\"<?php\")) {\n            return FileType.PHP;\n        } else if (str.startsWith(\"#!/usr/bin/python\") | str.startsWith(\"#! /usr/bin/python\")) {\n            return FileType.PYTHON;\n        } else if (str.startsWith(\"#!/bin/bash\") || str.startsWith(\"#!/bin/sh\") || str.startsWith(\"#!/usr/bin/env bash\") || str.startsWith(\"#! /bin/bash\") || str.startsWith(\"#! /bin/sh\")) {\n            return FileType.UNIX_SHELL;\n        } else if (str.startsWith(\"<!doctype html\")) {\n            return FileType.HTML;\n        } else if (str.startsWith(\"#!/usr/bin/ruby\") || str.startsWith(\"#!/usr/bin/env ruby\")) {\n            return FileType.RUBY;\n        }\n        return FileType.NONE;\n    }\n\n\n    static void formatTextArea(TextArea textArea) throws CodeFormatException {\n        String src = textArea.getText();\n        String formatted = switch (textArea.getFileType()) {\n            case XML -> CodeFormatter.formatXml(src);\n            case JSON -> CodeFormatter.formatJson(src);\n            default -> null;\n        };\n\n        if (formatted != null && !formatted.equals(src)) {\n            textArea.setText(formatted);\n        }\n\n    }\n\n    static void formatCode(TextEditorImpl textEditor) {\n        try {\n            formatTextArea(textEditor.getTextArea());\n            textEditor.setStatusMessage(\"\");\n        } catch (CodeFormatException e) {\n            if (e.getLine() > 0 ) {\n                if (e.getRow() > 0) {\n                    textEditor.getTextArea().gotoLine(e.getLine(), e.getRow());\n                } else {\n                    textEditor.getTextArea().gotoLine(e.getLine());\n                }\n            }\n            textEditor.setStatusMessage(e.getLocalizedMessage());\n        } catch (Exception e) {\n            textEditor.setStatusMessage(Translator.get(\"error\"));\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/TextFactory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.viewer.text;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.io.BinaryDetector;\nimport com.mucommander.commons.io.EncodingDetector;\nimport com.mucommander.utils.text.Translator;\nimport com.mucommander.ui.viewer.*;\n\nimport java.io.IOException;\nimport java.io.PushbackInputStream;\n\n/**\n * <code>ViewerFactory</code> and <code>EditorFactory</code> implementation for creating text viewers and editors.\n *\n * @author Nicolas Rinaudo\n */\npublic class TextFactory implements ViewerFactory, EditorFactory {\n\n    private static final long FILE_SIZE_WARNING_THRESHOLD = 10*1024*1024;\n\n    public boolean canViewFile(AbstractFile file) throws WarnUserException {\n        return doGenericChecks(file);\n    }\n\n    public boolean canEditFile(AbstractFile file) throws WarnUserException {\n        return doGenericChecks(file);\n    }\n\n    public FileViewer createFileViewer() {\n        return new TextViewer();\n    }\n\n    @Override\n    public String getName() {\n        return Translator.get(\"viewer_type.text\");\n    }\n\n    public FileEditor createFileEditor() {\n        return new TextEditor();\n    }\n\n    private boolean doGenericChecks(AbstractFile file) throws WarnUserException {\n        // Do not allow directories\n        if (file.isDirectory()) {\n            return false;\n        }\n        // Warn the user if the file looks like a binary file\n        if (checkBinaryFile(file)) {\n            return false;\n        }\n\n        // Warn the user if the file is large that a certain size as the whole file is loaded into memory\n        // (in a JTextArea)\n        if (file.getSize() > FILE_SIZE_WARNING_THRESHOLD) {\n            throw new WarnUserException(Translator.get(\"file_viewer.large_file_warning\"));\n        }\n        return true;\n    }\n\n    private boolean checkBinaryFile(AbstractFile file) {\n        try {\n            PushbackInputStream is = file.getPushBackInputStream(EncodingDetector.MAX_RECOMMENDED_BYTE_SIZE);\n            if (BinaryDetector.guessBinary(is)) {\n                return true;\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n            try {\n                file.closePushbackInputStream();\n            } catch (IOException e1) {\n                e1.printStackTrace();\n            }\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/TextFilesHistory.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2013-2014 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.text;\n\nimport com.mucommander.PlatformManager;\nimport com.mucommander.commons.DummyDecoratedFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileURL;\n\nimport java.io.*;\nimport java.lang.ref.WeakReference;\nimport java.net.MalformedURLException;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Stores history for viewed and edited files - last position\n */\npublic class TextFilesHistory {\n\n    /** Default text file history file name */\n    private static final String DEFAULT_HISTORY_FILE_NAME = \"textfiles.history\";\n\n    private static final int MAX_NUMBER_OF_RECORDS = 1000;\n\n\n    private static WeakReference<TextFilesHistory> instance;\n\n    private final List<FileRecord> records = new ArrayList<>();\n\n    public static class FileRecord {\n        private final String fileName;\n        private int scrollPosition;\n        private int line, column;\n        private FileType fileType;\n        private String encoding;\n\n        FileRecord(String fileName, int firstLine, int row, int column, FileType fileType, String encoding) {\n            this.fileName = fileName;\n            update(firstLine, row, column, fileType, encoding);\n        }\n\n        FileRecord(String fileName) {\n            this.fileName = fileName;\n        }\n\n        public void update(int firstLine, int line, int column, FileType fileType, String encoding) {\n            setScrollPosition(firstLine);\n            setLine(line);\n            setColumn(column);\n            setFileType(fileType);\n            setEncoding(encoding);\n        }\n\n        public void update(FileRecord source) {\n            this.scrollPosition = source.scrollPosition;\n            this.line = source.line;\n            this.column = source.column;\n            this.fileType = source.fileType;\n            this.encoding = source.encoding;\n        }\n\n        public int getLine() {\n            return line <= 0 ? 1 : line;\n        }\n\n        public void setLine(int line) {\n            this.line = line;\n        }\n\n        public FileType getFileType() {\n            return fileType;\n        }\n\n        public void setFileType(FileType fileType) {\n            this.fileType = fileType;\n        }\n\n        public int getScrollPosition() {\n            return scrollPosition;\n        }\n\n        public void setScrollPosition(int scrollPosition) {\n            this.scrollPosition = scrollPosition;\n        }\n\n        public int getColumn() {\n            return column > 0 ? column : 1;\n        }\n\n        public void setColumn(int column) {\n            this.column = column;\n        }\n\n        public String getEncoding() {\n            return encoding;\n        }\n\n        public void setEncoding(String encoding) {\n            this.encoding = \"null\".equals(encoding) ? null : encoding;\n        }\n\n        @Override\n        public String toString() {\n            return fileName + '=' + scrollPosition + ',' + getLine() + ',' + getColumn() + ',' + getFileType() + ',' + getEncoding();\n        }\n    }\n\n\n    public static TextFilesHistory getInstance() {\n        TextFilesHistory textFilesHistory = instance == null ? null : instance.get();\n        if (textFilesHistory == null) {\n            textFilesHistory = new TextFilesHistory();\n            instance = new WeakReference<>(textFilesHistory);\n            try {\n                textFilesHistory.load();\n            } catch (IOException ignore) {}\n        }\n        return textFilesHistory;\n    }\n\n    // - History file access --------------------------------------------------\n    // -------------------------------------------------------------------------\n    /**\n     * Returns the path to the history file.\n     * <p>\n     * Will return the default, system dependant bookmarks file.\n     *\n     * @return             the path to the bookmark file.\n     * @throws IOException if there was a problem locating the default history file.\n     */\n    private static synchronized AbstractFile getHistoryFile() throws IOException {\n        return PlatformManager.getPreferencesFolder().getChild(DEFAULT_HISTORY_FILE_NAME);\n    }\n\n\n    private void load() throws IOException {\n        load(getHistoryFile());\n    }\n\n    private void load(AbstractFile file) {\n        records.clear();\n        try (BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream()))) {\n            String line;\n            while ((line = reader.readLine()) != null) {\n                if (line.isEmpty() || line.startsWith(\"#\")) {\n                    continue;\n                }\n                FileRecord rec = parseRecord(line);\n                if (rec != null) {\n                    records.add(rec);\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    private static FileRecord parseRecord(String s) {\n        int index = s.indexOf('=');\n        if (index < 0) {\n            return null;\n        }\n        String fileName = s.substring(0, index);\n        String[] props = s.substring(index + 1).split(\",\");\n        try {\n            for (int i = 0; i < props.length; i++) {\n                props[i] = props[i].trim();\n            }\n            FileType type;\n            try {\n                type = FileType.valueOf(props[3]);\n            } catch (Exception e) {\n                type = null;\n            }\n            return new FileRecord(fileName, Integer.parseInt(props[0]), Integer.parseInt(props[1]), Integer.parseInt(props[2]),\n                    type, props[4]);\n        } catch (Exception e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    public void save() {\n        try {\n            save(getHistoryFile());\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n\n    public void save(AbstractFile file) throws  IOException {\n        try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(file.getOutputStream()))) {\n            for (FileRecord rec : records) {\n                writer.write(rec.toString());\n                writer.write('\\n');\n            }\n        }\n    }\n\n\n    public FileRecord get(AbstractFile file) {\n        return get(file.getAbsolutePath());\n    }\n\n\n    public FileRecord get(String fileName) {\n        int index = findRecord(fileName);\n        return index >= 0 ? records.get(index) : new FileRecord(fileName);\n    }\n\n    private int findRecord(String fileName) {\n        for (int i = 0; i < records.size(); i++) {\n            FileRecord rec = records.get(i);\n            if (rec.fileName.equals(fileName)) {\n                return i;\n            }\n        }\n        return -1;\n    }\n\n\n    TextFilesHistory updateRecord(FileRecord record) {\n        int index = findRecord(record.fileName);\n        if (index >= 0) {\n            records.remove(index);\n        }\n        records.addFirst(record);\n        while (records.size() > MAX_NUMBER_OF_RECORDS) {\n            records.removeLast();\n        }\n        return this;\n    }\n\n\n    public List<AbstractFile> getLastList(int maxCount) {\n        List<AbstractFile> result = new ArrayList<>();\n        for (FileRecord rec : records) {\n            try {\n//                result.add(FileFactory.getFile(rec.fileName));\n//                result.add(FileFactory.getFile(FileURL.getFileURL(rec.fileName)));\n                FileURL fileUrl = FileURL.getFileURL(rec.fileName);\n                result.add(new DummyDecoratedFile(fileUrl));\n            } catch (MalformedURLException e) {\n                e.printStackTrace();\n            }\n            if (result.size() >= maxCount) {\n                break;\n            }\n        }\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/TextLineNumbersPanel.java",
    "content": "package com.mucommander.ui.viewer.text;\n\nimport java.awt.Color;\nimport java.awt.Dimension;\nimport java.awt.Font;\nimport java.awt.FontMetrics;\nimport java.awt.Graphics;\nimport java.awt.Insets;\nimport java.awt.Point;\nimport java.awt.Rectangle;\nimport java.awt.geom.Rectangle2D;\nimport java.beans.PropertyChangeEvent;\nimport java.beans.PropertyChangeListener;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport javax.swing.JPanel;\nimport javax.swing.SwingUtilities;\nimport javax.swing.border.Border;\nimport javax.swing.border.CompoundBorder;\nimport javax.swing.border.EmptyBorder;\nimport javax.swing.event.CaretEvent;\nimport javax.swing.event.CaretListener;\nimport javax.swing.event.DocumentEvent;\nimport javax.swing.event.DocumentListener;\nimport javax.swing.text.AttributeSet;\nimport javax.swing.text.BadLocationException;\nimport javax.swing.text.Element;\nimport javax.swing.text.JTextComponent;\nimport javax.swing.text.StyleConstants;\nimport javax.swing.text.Utilities;\n\n/**\n * Panel in which the line numbers at a given text component are presented.\n * it is used in JScrollPane as a row header.\n * \n * @author Arik Hadas\n */\npublic class TextLineNumbersPanel extends JPanel implements CaretListener, DocumentListener, PropertyChangeListener {\n\t\n\tprivate final static int HEIGHT = Integer.MAX_VALUE - 1000000;\n\t\n\tpublic enum ALIGNMENT{ LEFT, CENTER, RIGHT }\n\t\n\t// Text component this TextTextLineNumber component is in sync with\n\tprivate final JTextComponent component;\n\n\t// Properties that can be changed\n\tprivate Color currentLineForeground;\n\tprivate int minimumDisplayDigits;\n\tprivate double digitAlignment;\n\n\t// Keep history information to reduce the number of times the component needs to be repainted\n    private int lastDigits;\n    private int lastHeight;\n    private int lastLine;\n    \n    private Map<String, FontMetrics> fonts;\n    \n    /**\n\t *\tcreate a line number component for a text component. This minimum\n\t *  display width will be based on 3 digits.\n\t *\n\t *  @param component  the related text component\n\t */\n\tpublic TextLineNumbersPanel(JTextComponent component) {\n\t\tthis(component, 3);\n\t}\n\n\t/**\n\t *\tcreate a line number component for a text component.\n\t *\n\t *  @param component  the related text component\n\t *  @param minimumDisplayDigits  the number of digits used to calculate\n\t *                               the minimum width of the component\n\t */\n\tpublic TextLineNumbersPanel(JTextComponent component, int minimumDisplayDigits) {\n\t\tthis(component, minimumDisplayDigits, new EmptyBorder(0, 0, 0, 2), 4, ALIGNMENT.CENTER);\n\t}\n\t\n\tpublic TextLineNumbersPanel(JTextComponent component, int minimumDisplayDigits, Border border, int borderGap, \n\t\t\tALIGNMENT alignment) {\n\t\tthis.component = component;\n\n\t\tsetBackground(Color.LIGHT_GRAY);\n\t\tsetForeground(Color.black);\n\t\tsetCurrentLineForeground(new Color(0,0,255));\n\t\tsetDigitAlignment(alignment);\n\t\tsetBorder(border, borderGap);\n\t\tsetMinimumDisplayDigits( minimumDisplayDigits);\n\t\tsetFont(component.getFont());\n\t\t\n\t\tcomponent.getDocument().addDocumentListener(this);\n\t\tcomponent.addPropertyChangeListener(\"font\", this);\n\t\tcomponent.addCaretListener(this);\n\t}\n\t\n\t/**\n\t * Set the alignment of the line numbers strings within the panel\n\t * \n\t * @param alignment the line numbers alignment\n\t */\n\tprivate void setDigitAlignment(ALIGNMENT alignment) {\n\t\tswitch(alignment) {\n            case LEFT:\n                digitAlignment = 0;\n            case RIGHT:\n                digitAlignment = 1;\n            case CENTER:\n                digitAlignment = 0.5;\n\t\t}\n\t}\n\t\n\t/**\n\t * If we'll want to highlight current line , we should\n\t * set the current line number color using this method\n\t * \n\t * @param currentLineForeground current line number color\n\t */\n\tprivate void setCurrentLineForeground( Color currentLineForeground ) {\n\t\tthis.currentLineForeground = currentLineForeground;\n\t}\n\t\n\t/**\n\t *  The border gap is used in calculating the left and right insets of the\n\t *  border. Default value is 5.\n\t *\n\t *  @param borderGap  the gap in pixels\n\t */\n\tprivate void setBorder(Border border, int borderGap) {\n\t\tBorder inner = new EmptyBorder(0, borderGap, 0, borderGap);\n\t\tsetBorder( new CompoundBorder(border, inner) );\n\t\tlastDigits = 0;\n\t\tsetPreferredWidth();\n\t}\n\t\n\t/**\n\t *  Specify the minimum number of digits used to calculate the preferred\n\t *  width of the component. Default is 3.\n\t *\n\t *  @param minimumDisplayDigits  the number digits used in the preferred\n\t *                               width calculation\n\t */\n\tprivate void setMinimumDisplayDigits(int minimumDisplayDigits) {\n\t\tthis.minimumDisplayDigits = minimumDisplayDigits;\n\t\tsetPreferredWidth();\n\t}\n\n\t/**\n\t *  Calculate the width needed to display the maximum line number\n\t */\n\tprivate void setPreferredWidth() {\n\t\tElement root = component.getDocument().getDefaultRootElement();\n\t\tint lines = root.getElementCount();\n\t\tint digits = Math.max(String.valueOf(lines).length(), minimumDisplayDigits);\n\n\t\t//  Update sizes when number of digits in the line number changes\n\t\tif (lastDigits != digits) {\n\t\t\tlastDigits = digits;\n\t\t\tFontMetrics fontMetrics = getFontMetrics( getFont() );\n\t\t\tint width = fontMetrics.charWidth( '0' ) * digits;\n\t\t\tInsets insets = getInsets();\n\t\t\tint preferredWidth = insets.left + insets.right + width;\n\n\t\t\tDimension d = getPreferredSize();\n\t\t\td.setSize(preferredWidth, HEIGHT);\n\t\t\tsetPreferredSize( d );\n\t\t\tsetSize( d );\n\t\t}\n\t}\n\t\n\t/*\n\t *  We need to know if the caret is currently positioned on the line we\n\t *  are about to paint so the line number can be highlighted.\n\t *  if the current line foreground is not set, just return false\n\t */\n\tprivate boolean isCurrentLine(int rowStartOffset)\n\t{\n\t\tif (currentLineForeground == null)\n\t\t\treturn false;\n\t\t\n\t\tint caretPosition = component.getCaretPosition();\n\t\tElement root = component.getDocument().getDefaultRootElement();\n\n\t\treturn root.getElementIndex( rowStartOffset ) == root.getElementIndex(caretPosition);\n\t}\n\n\t/**\n\t *  Draw the line numbers\n\t */\n\t@Override\n\tpublic void paintComponent(Graphics g) {\n\t\tsuper.paintComponent(g);\n\n\t\t//\tDetermine the width of the space available to draw the line number\n\t\tFontMetrics fontMetrics = component.getFontMetrics( component.getFont() );\n\t\tInsets insets = getInsets();\n\t\tint availableWidth = getSize().width - insets.left - insets.right;\n\n\t\t//  Determine the rows to draw within the clipped bounds.\n\t\tRectangle clip = g.getClipBounds();\n\t\tint rowStartOffset = component.viewToModel2D( new Point(0, clip.y) );\n\t\tint endOffset = component.viewToModel2D( new Point(0, clip.y + clip.height) );\n\n\t\twhile (rowStartOffset <= endOffset) {\n\t\t\ttry {\n    \t\t\tg.setColor(isCurrentLine(rowStartOffset) ? currentLineForeground : getForeground());\n\n    \t\t\t//  Get the line number as a string and then determine the\n    \t\t\t//  \"X\" and \"Y\" offsets for drawing the string.\n    \t\t\tString lineNumber = getTextLineNumber(rowStartOffset);\n    \t\t\tint stringWidth = fontMetrics.stringWidth( lineNumber );\n    \t\t\tint x = getOffsetX(availableWidth, stringWidth) + insets.left;\n\t\t\t\tint y = getOffsetY(rowStartOffset, fontMetrics);\n    \t\t\tg.drawString(lineNumber, x, y);\n\n    \t\t\t//  Move to the next row\n    \t\t\trowStartOffset = Utilities.getRowEnd(component, rowStartOffset) + 1;\n\t\t\t} catch(Exception e) {\n                e.printStackTrace();\n            }\n\t\t}\n\t}\n\t\n\t/*\n\t *\tGet the line number to be drawn. The empty string will be returned\n\t *  when a line of text has wrapped.\n\t */\n\tprotected String getTextLineNumber(int rowStartOffset) {\n\t\tElement root = component.getDocument().getDefaultRootElement();\n\t\tint index = root.getElementIndex( rowStartOffset );\n\t\tElement line = root.getElement( index );\n\n\t\treturn line.getStartOffset() == rowStartOffset ? String.valueOf(index + 1) : \"\";\n\t}\n\n\t/*\n\t *  Determine the X offset to properly align the line number when drawn\n\t */\n\tprivate int getOffsetX(int availableWidth, int stringWidth) {\n\t\treturn (int)((availableWidth - stringWidth) * digitAlignment);\n\t}\n\n\t/*\n\t *  Determine the Y offset for the current row\n\t */\n\tprivate int getOffsetY(int rowStartOffset, FontMetrics fontMetrics) throws BadLocationException {\n\t\t//  Get the bounding rectangle of the row\n\n\t\tRectangle2D r = component.modelToView2D( rowStartOffset );\n\t\tint lineHeight = fontMetrics.getHeight();\n\t\tdouble y = r.getY() + r.getHeight();\n\t\tint descent = 0;\n\n\t\t//  The text needs to be positioned above the bottom of the bounding\n\t\t//  rectangle based on the descent of the font(s) contained on the row.\n\n\n\t\tif (Math.round(r.getHeight()) == lineHeight)  {\t// default font is being used\n\t\t\tdescent = fontMetrics.getDescent();\n\t\t}\n\t\telse { // We need to check all the attributes for font changes\n\t\t\tif (fonts == null)\n\t\t\t\tfonts = new HashMap<>();\n\n\t\t\tElement root = component.getDocument().getDefaultRootElement();\n\t\t\tint index = root.getElementIndex( rowStartOffset );\n\t\t\tElement line = root.getElement( index );\n\n\t\t\tfor (int i = 0; i < line.getElementCount(); i++) {\n\t\t\t\tElement child = line.getElement(i);\n\t\t\t\tAttributeSet as = child.getAttributes();\n\t\t\t\tString fontFamily = (String)as.getAttribute(StyleConstants.FontFamily);\n\t\t\t\tInteger fontSize = (Integer)as.getAttribute(StyleConstants.FontSize);\n\t\t\t\tString key = fontFamily + fontSize;\n\n\t\t\t\tFontMetrics fm = fonts.get( key );\n\n\t\t\t\tif (fm == null)\n\t\t\t\t{\n\t\t\t\t\tFont font = new Font(fontFamily, Font.PLAIN, fontSize);\n\t\t\t\t\tfm = component.getFontMetrics( font );\n\t\t\t\t\tfonts.put(key, fm);\n\t\t\t\t}\n\n\t\t\t\tdescent = Math.max(descent, fm.getDescent());\n\t\t\t}\n\t\t}\n\n\t\treturn (int)Math.round(y - descent);\n\t}\n\t\n\t@Override\n\tpublic void changedUpdate(DocumentEvent e) {\n\t\tdocumentChanged();\n\t}\n\n\t@Override\n\tpublic void insertUpdate(DocumentEvent e) {\n\t\tdocumentChanged();\n\t}\n\n\t@Override\n\tpublic void removeUpdate(DocumentEvent e) {\n\t\tdocumentChanged();\n\t}\n\n\t/*\n\t *  A document change may affect the number of displayed lines of text.\n\t *  Therefore, the lines numbers will also change.\n\t */\n\tprivate void documentChanged() {\n\t\t//  Preferred size of the component has not been updated at the time\n\t\t//  the DocumentEvent is fired\n\t\tSwingUtilities.invokeLater(() -> {\n\t\t\tint preferredHeight;\n\t\t\ttry {\n\t\t\t\tpreferredHeight = component.getPreferredSize().height;\n\t\t\t} catch (Exception e) {\n\t\t\t\te.printStackTrace();\n\t\t\t\treturn;\n\t\t\t}\n\n            //  Document change has caused a change in the number of lines.\n            //  Repaint to reflect the new line numbers\n            if (lastHeight != preferredHeight) {\n                setPreferredWidth();\n                repaint();\n                lastHeight = preferredHeight;\n            }\n        });\n\t}\n\t\n\n\t@Override\n\tpublic void caretUpdate(CaretEvent e)\n\t{\n\t\tif (currentLineForeground == null)\n\t\t\treturn;\n\t\t\n\t\t//  Get the line the caret is positioned on\n\t\tint caretPosition = component.getCaretPosition();\n\t\tElement root = component.getDocument().getDefaultRootElement();\n\t\tint currentLine = root.getElementIndex( caretPosition );\n\n\t\t//  Need to repaint so the correct line number can be highlighted\n\n\t\tif (lastLine != currentLine)\n\t\t{\n\t\t\trepaint();\n\t\t\tlastLine = currentLine;\n\t\t}\n\t}\n\n\t@Override\n\tpublic void propertyChange(PropertyChangeEvent evt) {\n\t\tif (evt.getNewValue() instanceof Font) {\n\t\t\tsetFont((Font) evt.getNewValue());\n\t\t\tlastDigits = 0;\n\t\t\tsetPreferredWidth();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/TextMenuHelper.java",
    "content": "/*\r\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\r\n * Copyright (C) 2014-2018 Oleg Trifonov\r\n *\r\n * trolCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * trolCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\npackage com.mucommander.ui.viewer.text;\r\n\r\nimport com.mucommander.cache.TextHistory;\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.runtime.OsFamily;\r\nimport com.mucommander.ui.action.impl.UserMenuAction;\r\nimport com.mucommander.ui.helper.MenuToolkit;\r\nimport com.mucommander.ui.helper.MnemonicHelper;\r\nimport com.mucommander.ui.main.MainFrame;\r\nimport com.mucommander.ui.main.menu.UserPopupMenu;\r\nimport com.mucommander.utils.text.Translator;\r\nimport org.intellij.lang.annotations.MagicConstant;\r\nimport ru.trolsoft.calculator.CalculatorDialog;\r\nimport ru.trolsoft.ui.TMenuSeparator;\r\nimport ru.trolsoft.ui.TRadioButtonMenuItem;\r\n\r\nimport javax.swing.*;\r\nimport java.awt.event.ActionEvent;\r\nimport java.awt.event.ActionListener;\r\nimport java.awt.event.KeyEvent;\r\nimport java.util.LinkedList;\r\n\r\nimport static javax.swing.KeyStroke.getKeyStroke;\r\n/**\r\n * Helping class for menu creation in viewer and editor\r\n */\r\npublic class TextMenuHelper {\r\n    private final TextEditorImpl textEditorImpl;\r\n    private final boolean editMode;\r\n\r\n    /** Menu bar */\r\n    // Menus\r\n    private JMenu menuEdit;\r\n    private JMenu menuView;\r\n    private JMenu menuViewSyntax;\r\n    private JMenu menuSearch;\r\n    private JMenu menuTools;\r\n\r\n    // Items\r\n\r\n    private JMenuItem miFiles;\r\n    private JMenuItem miMainFrame;\r\n    private JMenuItem miAddToBookmarks;\r\n    private JMenuItem miRemoveFromBookmarks;\r\n    private JMenuItem miGotoHeaderSource;\r\n//    private JMenuItem miClose;\r\n\r\n    private JMenuItem miUndo;\r\n    private JMenuItem miRedo;\r\n    private JMenuItem miCopy;\r\n    private JMenuItem miCut;\r\n    private JMenuItem miPaste;\r\n    private JMenuItem miSelectAll;\r\n\r\n    private JMenuItem miFind;\r\n    private JMenuItem miFindNext;\r\n    private JMenuItem miFindPrevious;\r\n    private JMenuItem miReplace;\r\n\r\n    private JMenuItem miGotoLine;\r\n    private JMenuItem miToggleLineWrap;\r\n    private JMenuItem miToggleLineNumbers;\r\n    private JMenuItem miToggleInvisibleChars;\r\n\r\n    private JMenuItem miCalculator;\r\n    private JMenuItem miBuild;\r\n    private JMenuItem miUserMenu;\r\n    private JMenuItem miFormat;\r\n    private FileType fileType;\r\n\r\n    private static boolean showInvisibleChars = false;\r\n\r\n\r\n    TextMenuHelper(TextEditorImpl textEditorImpl, boolean editMode) {\r\n        this.textEditorImpl = textEditorImpl;\r\n        this.editMode = editMode;\r\n        updateInvisibleChars();\r\n    }\r\n\r\n    void initMenu(ActionListener actionListener, boolean lineNumbers) {\r\n        // Edit menu\r\n        menuEdit = new JMenu(Translator.get(\"text_editor.edit\"));\r\n        MnemonicHelper menuItemMnemonicHelper = new MnemonicHelper();\r\n\r\n        if (editMode) {\r\n            miUndo = MenuToolkit.addMenuItem(menuEdit, i18n(\"text_editor.undo\"), menuItemMnemonicHelper, null, actionListener);\r\n            miRedo = MenuToolkit.addMenuItem(menuEdit, i18n(\"text_editor.redo\"), menuItemMnemonicHelper, null, actionListener);\r\n            menuEdit.addSeparator();\r\n        }\r\n        miCopy = MenuToolkit.addMenuItem(menuEdit, i18n(\"text_editor.copy\"), menuItemMnemonicHelper, null, actionListener);\r\n        if (editMode) {\r\n            miCut = MenuToolkit.addMenuItem(menuEdit, i18n(\"text_editor.cut\"), menuItemMnemonicHelper, null, actionListener);\r\n            miPaste = MenuToolkit.addMenuItem(menuEdit, i18n(\"text_editor.paste\"), menuItemMnemonicHelper, null, actionListener);\r\n        }\r\n\r\n        miSelectAll = MenuToolkit.addMenuItem(menuEdit, i18n(\"text_editor.select_all\"), menuItemMnemonicHelper, null, actionListener);\r\n        menuEdit.addSeparator();\r\n\r\n        menuEdit.addSeparator();\r\n\r\n        if (editMode) {\r\n            miFormat = MenuToolkit.addMenuItem(menuEdit, i18n(\"text_editor.format\"), menuItemMnemonicHelper, getKeyStroke(KeyEvent.VK_F, KeyEvent.SHIFT_DOWN_MASK|getCtrlOrMetaMask()), actionListener);\r\n        }\r\n\r\n        // Search menu\r\n        menuSearch = new JMenu(Translator.get(\"text_editor.search\"));\r\n        miFind = MenuToolkit.addMenuItem(menuSearch, i18n(\"text_editor.find\"), menuItemMnemonicHelper, getKeyStroke(KeyEvent.VK_F, getCtrlOrMetaMask()), actionListener);\r\n        miFindNext = MenuToolkit.addMenuItem(menuSearch, i18n(\"text_editor.find_next\"), menuItemMnemonicHelper, getKeyStroke(KeyEvent.VK_F3, 0), actionListener);\r\n        miFindPrevious = MenuToolkit.addMenuItem(menuSearch, i18n(\"text_editor.find_previous\"), menuItemMnemonicHelper, getKeyStroke(KeyEvent.VK_F3, KeyEvent.SHIFT_DOWN_MASK), actionListener);\r\n        if (editMode) {\r\n            menuSearch.addSeparator();\r\n            miReplace = MenuToolkit.addMenuItem(menuSearch, i18n(\"text_editor.replace_menu\"), menuItemMnemonicHelper, getKeyStroke(KeyEvent.VK_F, getCtrlOrMetaMask()|KeyEvent.ALT_DOWN_MASK), actionListener);\r\n        }\r\n        menuSearch.addSeparator();\r\n        miGotoLine = MenuToolkit.addMenuItem(menuSearch, i18n(\"text_viewer.goto_line\"), menuItemMnemonicHelper, getKeyStroke(KeyEvent.VK_G, getCtrlOrMetaMask()), actionListener);\r\n\r\n        // View menu\r\n        menuView = new JMenu(i18n(\"text_editor.view\"));\r\n\r\n        miToggleLineWrap = MenuToolkit.addCheckBoxMenuItem(menuView, i18n(\"text_editor.line_wrap\"), menuItemMnemonicHelper, getKeyStroke(KeyEvent.VK_F2, 0), actionListener);\r\n        miToggleLineWrap.setSelected(textEditorImpl.isWrap());\r\n        miToggleLineNumbers = MenuToolkit.addCheckBoxMenuItem(menuView, i18n(\"text_editor.line_numbers\"), menuItemMnemonicHelper, null, actionListener);\r\n        miToggleLineNumbers.setSelected(lineNumbers);\r\n        miToggleInvisibleChars = MenuToolkit.addCheckBoxMenuItem(menuView, i18n(\"text_editor.invisible_chars\"), menuItemMnemonicHelper, null, actionListener);\r\n        miToggleInvisibleChars.setSelected(showInvisibleChars);\r\n\r\n        menuView.addSeparator();\r\n        menuViewSyntax = new JMenu(Translator.get(\"text_editor.syntax\"));\r\n\r\n        addSyntaxMenu(actionListener, menuItemMnemonicHelper);\r\n\r\n        // Tools menu\r\n        addToolsMenu(actionListener, menuItemMnemonicHelper);\r\n    }\r\n\r\n    void setupFileMenu(JMenu fileMenu, ActionListener actionListener, AbstractFile currentFile) {\r\n        MnemonicHelper mnemonicHelper = new MnemonicHelper();\r\n        JMenuItem lastItem = fileMenu.getItemCount() > 0 ? fileMenu.getItem(fileMenu.getItemCount()-1) : null;\r\n\r\n        int mask = OsFamily.MAC_OS_X.isCurrent() ? KeyEvent.ALT_DOWN_MASK : KeyEvent.CTRL_DOWN_MASK;\r\n        miFiles = MenuToolkit.addMenuItem(fileMenu, i18n(\"file_editor.files\"), mnemonicHelper, getKeyStroke(KeyEvent.VK_TAB, mask), actionListener);\r\n        miMainFrame = MenuToolkit.addMenuItem(fileMenu, i18n(\"file_editor.show_file_manager\"), mnemonicHelper, getKeyStroke(KeyEvent.VK_1, KeyEvent.CTRL_DOWN_MASK), actionListener);\r\n        miAddToBookmarks = MenuToolkit.addMenuItem(fileMenu, i18n(\"file_editor.add_to_bookmark\"), mnemonicHelper, null, actionListener);\r\n        miRemoveFromBookmarks = MenuToolkit.addMenuItem(fileMenu, i18n(\"file_editor.remove_from_bookmark\"), mnemonicHelper, null, actionListener);\r\n\r\n        mask = getCtrlOrMetaMask() | KeyEvent.SHIFT_DOWN_MASK;\r\n        miGotoHeaderSource = MenuToolkit.addMenuItem(fileMenu, i18n(\"file_editor.goto_header_source\"), mnemonicHelper, getKeyStroke(KeyEvent.VK_A, mask), actionListener);\r\n        fileMenu.add(new TMenuSeparator());\r\n        if (lastItem != null) {\r\n            fileMenu.add(lastItem);\r\n        }\r\n\r\n        updateFileBookmarksMenuItems(currentFile);\r\n        updateGotoHeaderSourceVisibility();\r\n    }\r\n\r\n    private void updateFileBookmarksMenuItems(AbstractFile currentFile) {\r\n        if (currentFile == null) {\r\n            miAddToBookmarks.setVisible(false);\r\n            miRemoveFromBookmarks.setVisible(false);\r\n            return;\r\n        }\r\n        boolean inBookmarks = getBookmarkFilesList().contains(currentFile.getURL().toString());\r\n        miAddToBookmarks.setVisible(!inBookmarks);\r\n        miRemoveFromBookmarks.setVisible(inBookmarks);\r\n    }\r\n\r\n    private void addToolsMenu(ActionListener actionListener, MnemonicHelper menuItemMnemonicHelper) {\r\n        menuTools = new JMenu(Translator.get(\"text_editor.tools\"));\r\n        miCalculator = MenuToolkit.addMenuItem(menuTools, Translator.get(\"Calculator.label\"), menuItemMnemonicHelper, getKeyStroke(KeyEvent.VK_F6, 0), actionListener);\r\n        miBuild = MenuToolkit.addMenuItem(menuTools, Translator.get(\"text_editor.build\"), menuItemMnemonicHelper, getKeyStroke(KeyEvent.VK_B, KeyEvent.META_DOWN_MASK), actionListener);\r\n        miUserMenu = MenuToolkit.addMenuItem(menuTools, Translator.get(\"UserMenu.label\"), menuItemMnemonicHelper, getKeyStroke(KeyEvent.VK_F1, 0), actionListener);\r\n    }\r\n\r\n    private void addSyntaxMenu(ActionListener actionListener, MnemonicHelper menuItemMnemonicHelper) {\r\n        menuView.add(menuViewSyntax);\r\n        ButtonGroup group = new ButtonGroup();\r\n        for (FileType fileType : FileType.values()) {\r\n            MenuToolkit.addRadioButtonMenuItem(menuViewSyntax, fileType.getName(), menuItemMnemonicHelper,null,\r\n                    actionListener, group);\r\n        }\r\n    }\r\n\r\n    @MagicConstant(flags = {KeyEvent.META_DOWN_MASK, KeyEvent.CTRL_DOWN_MASK})\r\n    private int getCtrlOrMetaMask() {\r\n        return OsFamily.MAC_OS_X.isCurrent() ? KeyEvent.META_DOWN_MASK : KeyEvent.CTRL_DOWN_MASK;\r\n    }\r\n\r\n\r\n    JMenu getEditMenu() {\r\n        return menuEdit;\r\n    }\r\n\r\n    JMenu getViewMenu() {\r\n        return menuView;\r\n    }\r\n\r\n    JMenu getSearchMenu() {\r\n        return menuSearch;\r\n    }\r\n\r\n    JMenu getToolsMenu() {\r\n        return menuTools;\r\n    }\r\n\r\n    public boolean performAction(ActionEvent e, TextViewer textViewerDelegate) {\r\n        Object source = e.getSource();\r\n        if (source == null) {\r\n            return false;\r\n        }\r\n        if (checkSyntaxChangeAction(source)) {\r\n            return true;\r\n        }\r\n        if (source == miFiles) {\r\n            textEditorImpl.showFilesQuickList();\r\n        } else if (source == miMainFrame) {\r\n            showMainFrame();\r\n        } else if (source == miAddToBookmarks) {\r\n            textEditorImpl.addCurrentFileToBookmarks();\r\n            miAddToBookmarks.setVisible(false);\r\n            miRemoveFromBookmarks.setVisible(true);\r\n        } else if (source == miRemoveFromBookmarks) {\r\n            textEditorImpl.removeCurrentFileFromBookmarks();\r\n            miAddToBookmarks.setVisible(true);\r\n            miRemoveFromBookmarks.setVisible(false);\r\n        } else if (source == miGotoHeaderSource) {\r\n            textEditorImpl.switchBetweenHeaderAndSource();\r\n//        } else if (source == miClose) {\r\n//            textEditorImpl.frame.dispose();\r\n        } else if (source == miCopy) {\r\n            textEditorImpl.copy();\r\n        } else if (source == miCut) {\r\n            textEditorImpl.cut();\r\n        } else if (source == miPaste) {\r\n            textEditorImpl.paste();\r\n        } else if (source == miSelectAll) {\r\n            textEditorImpl.selectAll();\r\n        } else if (source == miFind) {\r\n            textEditorImpl.find();\r\n        } else if (source == miReplace) {\r\n            textEditorImpl.replace();\r\n        } else if (source == miFindNext) {\r\n            textEditorImpl.findNext();\r\n        } else if (source == miFindPrevious) {\r\n            textEditorImpl.findPrevious();\r\n        } else if (source == miToggleLineWrap) {\r\n            if (e.getWhen() == 0) {  \r\n                miToggleLineWrap.setSelected(!miToggleLineWrap.isSelected());\r\n            }\r\n            textViewerDelegate.wrapLines(miToggleLineWrap.isSelected());\r\n        } else if (source == miToggleLineNumbers) {\r\n            textViewerDelegate.showLineNumbers(miToggleLineNumbers.isSelected());\r\n        } else if (source == miToggleInvisibleChars) {\r\n            showInvisibleChars = miToggleInvisibleChars.isSelected();\r\n            updateInvisibleChars();\r\n        } else if (source == miGotoLine) {\r\n            textEditorImpl.gotoLine();\r\n        } else if (source == miUndo) {\r\n            textEditorImpl.undo();\r\n        } else if (source == miRedo) {\r\n            textEditorImpl.redo();\r\n        } else if (source == miFormat) {\r\n            TextEditorUtils.formatCode(textEditorImpl);\r\n        } else if (source == miCalculator) {\r\n            new CalculatorDialog(textEditorImpl.frame).showDialog();\r\n        } else if (source == miBuild) {\r\n            textEditorImpl.build();\r\n        } else if (source == miUserMenu) {\r\n            UserPopupMenu menu = UserMenuAction.createMenu(getMainFrame());\r\n            if (menu != null) {\r\n                menu.show(textEditorImpl.frame);\r\n            }\r\n        } else {\r\n            return false;\r\n        }\r\n        updateEditActions();\r\n        return true;\r\n    }\r\n\r\n    private MainFrame getMainFrame() {\r\n        return textEditorImpl.frame.getMainFrame();\r\n    }\r\n\r\n    private void updateInvisibleChars() {\r\n        textEditorImpl.getTextArea().setWhitespaceVisible(showInvisibleChars);\r\n    }\r\n\r\n    private boolean checkSyntaxChangeAction(Object source) {\r\n        if (source instanceof TRadioButtonMenuItem) {\r\n            for (int i = 0; i < menuViewSyntax.getItemCount(); i++) {\r\n                JMenuItem item = menuViewSyntax.getItem(i);\r\n                if (source == item) {\r\n                    FileType fileType = FileType.getByName(item.getText());\r\n                    textEditorImpl.setSyntaxType(fileType);\r\n                    setSyntax(fileType);\r\n                    return true;\r\n                }\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public void setSyntax(FileType fileType) {\r\n        for (int i = 0; i < menuViewSyntax.getItemCount(); i++) {\r\n            JMenuItem item = menuViewSyntax.getItem(i);\r\n            item.setSelected(item.getText().equals(fileType.getName()));\r\n        }\r\n        updateEditActions();\r\n        this.fileType = fileType;\r\n        updateGotoHeaderSourceVisibility();\r\n    }\r\n\r\n    private void updateGotoHeaderSourceVisibility() {\r\n        if (miGotoHeaderSource != null) {\r\n            miGotoHeaderSource.setVisible(fileType == FileType.CPP || fileType == FileType.C);\r\n        }\r\n    }\r\n\r\n\r\n    /*\r\n     * Check if last editor change fired by syntax change event ant will be ignored in document listener\r\n     * @return true if if last editor change fired by syntax change event ant will be ignored in document listener\r\n     */\r\n//    public boolean checkWaitChangeSyntaxEvent() {\r\n//        boolean result = waitChangeSyntaxEvent;\r\n//        updateEditActions();\r\n//        waitChangeSyntaxEvent = false;\r\n//        return result;\r\n//    }\r\n\r\n\r\n    void updateEditActions() {\r\n        if (!editMode) {\r\n            return;\r\n        }\r\n        final TextArea textArea = textEditorImpl.getTextArea();\r\n        miUndo.setEnabled(textArea.canUndo());\r\n        miRedo.setEnabled(textArea.canRedo());\r\n        FileType ft = textArea.getFileType();\r\n        miFormat.setVisible(ft == FileType.XML || ft == FileType.JSON);\r\n    }\r\n\r\n    void setBuildable(boolean canBuild) {\r\n        miBuild.setEnabled(canBuild);\r\n    }\r\n\r\n    private static String i18n(String key, String... params) {\r\n        return Translator.get(key, params);\r\n    }\r\n\r\n    private static LinkedList<String> getBookmarkFilesList() {\r\n        return TextHistory.getInstance().getList(TextHistory.Type.EDITOR_BOOKMARKS);\r\n    }\r\n\r\n    private void showMainFrame() {\r\n        getMainFrame().toFront();\r\n    }\r\n\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/TextViewer.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.viewer.text;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.io.EncodingDetector;\nimport com.mucommander.commons.io.bom.BOMInputStream;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcSnapshot;\nimport com.mucommander.ui.dialog.DialogOwner;\nimport com.mucommander.ui.dialog.InformationDialog;\nimport com.mucommander.ui.encoding.EncodingListener;\nimport com.mucommander.ui.encoding.EncodingMenu;\nimport com.mucommander.ui.viewer.FileFrame;\nimport com.mucommander.ui.viewer.FileViewer;\nimport lombok.Getter;\nimport org.fife.ui.rtextarea.GutterEx;\n\nimport javax.swing.*;\nimport javax.swing.event.DocumentListener;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.InputEvent;\nimport java.awt.event.KeyEvent;\nimport java.io.*;\nimport java.nio.charset.Charset;\nimport java.util.Arrays;\nimport java.util.Stack;\nimport java.util.function.Consumer;\n\n\n/**\n * A simple text viewer. Most of the implementation is located in {@link TextEditorImpl}.\n *\n * @author Maxence Bernard, Arik Hadas\n */\npublic class TextViewer extends FileViewer implements EncodingListener {\n\n\tprivate final static String CUSTOM_FULL_SCREEN_EVENT = \"CUSTOM_FULL_SCREEN_EVENT\";\n\n    private final TextEditorImpl textEditorImpl;\n\n\t@Getter\n    private static boolean lineWrap = TcConfigurations.getSnapshot().getVariable(TcSnapshot.TEXT_FILE_PRESENTER_LINE_WRAP, TcSnapshot.DEFAULT_LINE_WRAP);\n\n\t@Getter\n    private static boolean lineNumbers = TcConfigurations.getSnapshot().getVariable(TcSnapshot.TEXT_FILE_PRESENTER_LINE_NUMBERS, TcSnapshot.DEFAULT_LINE_NUMBERS);\n\n    TextMenuHelper menuHelper;\n\n    private String encoding;\n\n    private TextFilesHistory.FileRecord historyRecord;\n    private GutterEx gutter;\n    private StatusBar statusBar;\n\n\n    TextViewer() {\n    \tthis(new TextEditorImpl(false, null));\n        textEditorImpl.setStatusBar(getStatusBar());\n    }\n    \n    TextViewer(TextEditorImpl textEditorImpl) {\n    \tthis.textEditorImpl = textEditorImpl;\n\n        initGutter();\n\n        setComponentToPresent(textEditorImpl.getTextArea());\n\n        showLineNumbers(lineNumbers);\n    \ttextEditorImpl.wrap(lineWrap);\n\n    \tinitMenuBarItems();\n    }\n\n\n    private void initGutter() {\n//        Font defaultFont = new Font(\"Monospaced\", Font.PLAIN, 12);\n        gutter = new GutterEx(textEditorImpl.getTextArea());\n        gutter.setLineNumberFont(textEditorImpl.getTextArea().getFont());\n        // TODO\n        gutter.setBackground(Color.LIGHT_GRAY);\n        gutter.setForeground(Color.black);\n//gutter.setActiveLineRangeColor(new Color(0,0,255));\n        showLineNumbers(lineNumbers);\n\n        // Set miscellaneous properties.\n        setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_ALWAYS);\n        setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);\n    }\n\n    @Override\n    public void setFrame(final FileFrame frame) {\n        super.setFrame(frame);\n        textEditorImpl.setFrame(frame);\n\n        getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_M, InputEvent.CTRL_DOWN_MASK), CUSTOM_FULL_SCREEN_EVENT);\n    }\n\n\n\tstatic void setLineWrap(boolean lineWrap) {\n\t\tTextViewer.lineWrap = lineWrap;\n\t}\n\n    static void setLineNumbers(boolean lineNumbers) {\n\t\tTextViewer.lineNumbers = lineNumbers;\n\t}\n\n    void startEditing(AbstractFile file, DocumentListener documentListener) {\n        //initHistoryRecord(file);\n        // Auto-detect encoding\n        try (PushbackInputStream in = file.getPushBackInputStream(EncodingDetector.MAX_RECOMMENDED_BYTE_SIZE)) {\n            String encoding = historyRecord.getEncoding() != null ? historyRecord.getEncoding() : EncodingDetector.detectEncoding(in);\n            if (textEditorImpl.getStatusBar() != null) {\n                textEditorImpl.getStatusBar().setEncoding(encoding);\n            }\n            // Load the file into the text area\n            loadDocument(in, encoding, documentListener);\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n\n\n    void loadDocument(InputStream in, final String encoding, DocumentListener documentListener) throws IOException {\n        // If the encoding is UTF-something, wrap the stream in a BOMInputStream to IMAGE_FILTER out the byte-order mark\n        // (see ticket #245)\n        if (encoding != null && encoding.toLowerCase().startsWith(\"utf\")) {\n            in = new BOMInputStream(in);\n        }\n\n        // If the given encoding is invalid (null or not supported), default to \"UTF-8\"\n        this.encoding = encoding == null || !Charset.isSupported(encoding) ? \"UTF-8\" : encoding;\n        if (getStatusBar() != null) {\n            getStatusBar().setEncoding(encoding);\n        }\n        textEditorImpl.read(new BufferedReader(new InputStreamReader(in, this.encoding)));\n\n        // Listen to document changes\n        if (documentListener != null) {\n            textEditorImpl.addDocumentListener(documentListener);\n        }\n    }\n    \n    @Override\n    public JMenuBar getMenuBar() {\n    \tJMenuBar menuBar = super.getMenuBar();\n    \t\n    \t// Encoding menu\n    \tEncodingMenu encodingMenu = new EncodingMenu(new DialogOwner(getFrame()), encoding);\n        encodingMenu.addEncodingListener(this);\n\n        menuBar.add(menuHelper.getEditMenu());\n        menuBar.add(menuHelper.getSearchMenu());\n        menuBar.add(menuHelper.getViewMenu());\n        menuBar.add(menuHelper.getToolsMenu());\n        menuBar.add(encodingMenu, menuBar);\n\n        textEditorImpl.getTextArea().setFocusTraversalKeysEnabled(false);\n        setMainKeyListener(textEditorImpl.getTextArea(), menuBar);\n        menuHelper.setupFileMenu(menuFile, TextViewer.this, getCurrentFile());\n        return menuBar;\n    }\n\n    @Override\n    protected StatusBar getStatusBar() {\n        if (statusBar == null) {\n            statusBar = new StatusBar();\n        }\n        return statusBar;\n    }\n\n    void saveState(JScrollBar scrollBar) {\n        final TextArea textArea = textEditorImpl.getTextArea();\n        historyRecord.setLine(textArea.getLine());\n        historyRecord.setColumn(textArea.getColumn());\n        historyRecord.setFileType(textArea.getFileType());\n        historyRecord.setScrollPosition(scrollBar.getValue());\n        historyRecord.setEncoding(encoding);\n        TextFilesHistory.getInstance().updateRecord(historyRecord).save();\n    }\n\n    @Override\n    protected void saveStateOnClose() {\n        saveState(getVerticalScrollBar());\n        try {\n            AbstractFile currentFile = getCurrentFile();\n            if (currentFile != null) {\n                currentFile.closePushbackInputStream();\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n\n\n    @Override\n    protected void restoreStateOnStartup() {\n        final TextArea textArea = textEditorImpl.getTextArea();\n        textArea.gotoLine(historyRecord.getLine(), historyRecord.getColumn());\n        getViewport().setViewPosition(new java.awt.Point(0, historyRecord.getScrollPosition()));\n    }\n\n    String getEncoding() {\n    \treturn encoding;\n    }\n    \n    protected void showLineNumbers(boolean show) {\n    \t//setRowHeaderView(show ? new TextLineNumbersPanel(textEditorImpl.getTextArea()) : null);\n        gutter.setLineNumbersEnabled(show);\n        checkGutterVisibility();\n        setLineNumbers(show);\n    }\n\n    void wrapLines(boolean wrap) {\n    \ttextEditorImpl.wrap(wrap);\n    \tsetLineWrap(wrap);\n    }\n\n    protected void initMenuBarItems() {\n        menuHelper = new TextMenuHelper(textEditorImpl, false);\n        //menuHelper.initMenu(TextViewer.this, getRowHeader().getView() != null);\n        menuHelper.initMenu(TextViewer.this, lineNumbers);\n        //menuHelper.setupFileMenu(menuFile, TextViewer.this, getCurrentFile());\n    }\n\n\n    @Override\n    public void show(AbstractFile file) {\n        // TODO SHOULD BE IN SEPARATE THREAD !!!\n        initHistoryRecord(file);\n        FileType type = historyRecord.getFileType();\n        if (type == null) {\n            type = FileType.getFileType(file);\n            historyRecord.setFileType(type);\n        }\n        // detect XML and PHP files\n        if (type == FileType.NONE) {\n            type = TextEditorUtils.detectFileFormat(file);\n        }\n        startEditing(file, null);\n        menuHelper.setSyntax(type);\n        textEditorImpl.prepareForView(file);\n        textEditorImpl.setSyntaxType(type);\n    }\n\n    @Override\n    public void actionPerformed(ActionEvent e) {\n        if (menuHelper.performAction(e, this)) {\n            return;\n        }\n      \tsuper.actionPerformed(e);\n    }\n\n    @Override\n    public void encodingChanged(Object source, String oldEncoding, String newEncoding) {\n        // Store caret and scrollbar position before change\n        TextArea textArea = textEditorImpl.getTextArea();\n        int line = textArea.getLine();\n        int column = textArea.getColumn();\n        int horizontalPos = getHorizontalScrollBar().getValue();\n        int verticalPos = getVerticalScrollBar().getValue();\n\n        try {\n    \t\t// Reload the file using the new encoding\n    \t\t// Note: loadDocument closes the InputStream\n    \t\tloadDocument(getCurrentFile().getInputStream(), newEncoding, null);\n            // Restore caret and scrollbar\n            textArea.gotoLine(line, column);\n            getViewport().setViewPosition(new java.awt.Point(horizontalPos, verticalPos));\n    \t} catch (IOException ex) {\n    \t\tInformationDialog.showErrorDialog(getFrame(), i18n(\"read_error\"), i18n(\"file_editor.cannot_read_file\", getCurrentFile().getName()));\n    \t}   \n    }\n\n    TextFilesHistory.FileRecord initHistoryRecord(AbstractFile file) {\n        historyRecord = TextFilesHistory.getInstance().get(file);\n        return historyRecord;\n    }\n\n\n    TextFilesHistory.FileRecord getHistoryRecord() {\n        return historyRecord;\n    }\n//\n//\n//    void initHistoryRecord(AbstractFile file, Consumer<TextFilesHistory.FileRecord> initializer) {\n//        new Thread(() -> {\n//            historyRecord = new TextFilesHistory.FileRecord(file.getAbsolutePath());\n//System.out.println(\"FILE RECORD LOADED \"  + gi);\n//            SwingUtilities.invokeLater(() -> initializer.accept(historyRecord));\n//        }).start();\n//    }\n\n\n\n    /**\n     * Ensures the gutter is visible if it's showing anything.\n     */\n    private void checkGutterVisibility() {\n        int count = gutter.getComponentCount();\n        if (count == 0) {\n            if (getRowHeader() != null && getRowHeader().getView() == gutter) {\n                setRowHeaderView(null);\n            }\n        } else {\n            if (getRowHeader() == null || getRowHeader().getView() == null) {\n                setRowHeaderView(gutter);\n            }\n        }\n    }\n\n\n    /**\n     * Returns the first descendant of a component that is an\n     * <code>RTextArea</code>.  This is primarily here to support\n     * <code>javax.swing.JLayer</code>s that wrap <code>RTextArea</code>s.\n     *\n     * @param comp The component to recursively look through.\n     * @return The first descendant text area, or <code>null</code> if none\n     *         is found.\n     */\n    private static TextArea getFirstRTextAreaDescendant(Component comp) {\n        Stack<Component> stack = new Stack<>();\n        stack.add(comp);\n        while (!stack.isEmpty()) {\n            Component current = stack.pop();\n            if (current instanceof TextArea) {\n                return (TextArea)current;\n            }\n            if (current instanceof Container container) {\n                stack.addAll(Arrays.asList(container.getComponents()));\n            }\n        }\n        return null;\n    }\n\n\n    /**\n     * Sets the view for this scroll pane.  This must be an {@link TextArea}.\n     *\n     * @param view The new view.\n     */\n    @Override\n    public void setViewportView(Component view) {\n        TextArea rtaCandidate;\n\n        if (!(view instanceof TextArea)) {\n            rtaCandidate = getFirstRTextAreaDescendant(view);\n            if (rtaCandidate == null) {\n                throw new IllegalArgumentException(\"view must be either an RTextArea or a JLayer wrapping one\");\n            }\n        } else {\n            rtaCandidate = (TextArea)view;\n        }\n        super.setViewportView(view);\n        if (gutter != null) {\n            gutter.setTextArea(rtaCandidate);\n        }\n    }\n\n    @Override\n    public void setSearchedText(String searchedText) {\n        textEditorImpl.setupSearchContext(searchedText);\n    }\n\n\n    @Override\n    public void setSearchedBytes(byte[] searchedBytes) {\n        try {\n            textEditorImpl.setupSearchContext(new String(searchedBytes, encoding));\n        } catch (UnsupportedEncodingException e) {\n            e.printStackTrace();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/package.html",
    "content": "<body>\n  Provides text files viewing and editing classes.\n</body>\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/search/AbstractSearchDialog.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.text.search;\n\nimport com.jidesoft.hints.ListDataIntelliHints;\nimport com.mucommander.cache.TextHistory;\nimport com.mucommander.ui.dialog.FocusDialog;\nimport org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;\nimport org.fife.ui.rtextarea.SearchContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javax.swing.*;\nimport javax.swing.event.EventListenerList;\nimport java.awt.*;\nimport java.awt.event.*;\nimport java.beans.PropertyChangeEvent;\nimport java.beans.PropertyChangeListener;\nimport java.util.List;\nimport java.util.regex.Pattern;\nimport java.util.regex.PatternSyntaxException;\n\n/**\n * Base class for all search dialogs (find and replace)\n *\n * @author Oleg Trifonov\n * Created on 21/06/16.\n */\npublic class AbstractSearchDialog extends FocusDialog implements ActionListener {\n\n    private static Logger logger;\n\n\n    /**\n     * Listens for properties changing in the search context.\n     */\n    private class SearchContextListener implements PropertyChangeListener {\n\n        public void propertyChange(PropertyChangeEvent e) {\n            handleSearchContextPropertyChanged(e);\n        }\n\n    }\n\n\n\n    protected SearchContext context;\n    private SearchContextListener contextListener;\n\n    // Conditions check boxes and the panel they go in.\n    // This should be added in the actual layout of the search dialog.\n    protected JCheckBox cbCaseSensitive;\n    protected JCheckBox cbWholeWord;\n    protected JCheckBox cbRegex;\n    protected JPanel pnlSearchConditions;\n\n    protected JTextField edtText;\n\n    protected JButton btnCancel;\n    protected JButton btnFind;\n\n    protected JRadioButton rbUp;\n    protected JRadioButton rbDown;\n    protected JPanel pnlDirection;\n    protected JLabel lblFind;\n\n    /**\n     * The \"mark all\" check box.\n     */\n    private JCheckBox markAllCheckBox;\n\n    /**\n     * Folks listening for events in this dialog.\n     */\n    private EventListenerList listenerList;\n\n\n\n    AbstractSearchDialog(Frame owner, String title, Component locationRelativeComp) {\n        super(owner, title, locationRelativeComp);\n        init();\n    }\n\n    private void init() {\n        // The user should set a shared instance between all subclass\n        // instances, but to be safe we set individual ones.\n        contextListener = new SearchContextListener();\n        setSearchContext(createDefaultSearchContext());\n\n        // Make a panel containing the option check boxes.\n        pnlSearchConditions = new JPanel();\n        pnlSearchConditions.setLayout(new BoxLayout(pnlSearchConditions, BoxLayout.Y_AXIS));\n        cbCaseSensitive = createCheckBox(i18n(\"text_viewer.find.case_sensitive\"));\n        pnlSearchConditions.add(cbCaseSensitive);\n        cbWholeWord = createCheckBox(i18n(\"text_viewer.find.whole_word\"));\n        pnlSearchConditions.add(cbWholeWord);\n        cbRegex = createCheckBox(i18n(\"text_viewer.find.regexp\"));\n        pnlSearchConditions.add(cbRegex);\n\n        // Initialize any text fields.\n        edtText = new JTextField(20);\n        edtText.addActionListener(this);\n        List<String> history = TextHistory.getInstance().getList(TextHistory.Type.TEXT_SEARCH);\n//        new AutoCompletion(findField, history).setStrict(false);\n        edtText.setText(\"\");\n        new ListDataIntelliHints<>(edtText, history).setCaseSensitive(true);\n\n        // Initialize other stuff.\n        btnCancel = new JButton(i18n(\"cancel\"));\n        btnCancel.addActionListener(this);\n\n\n\n        listenerList = new EventListenerList();\n\n        // Make a panel containing the \"search up/down\" radio buttons.\n        pnlDirection = new JPanel();\n        pnlDirection.setLayout(new BoxLayout(pnlDirection, BoxLayout.LINE_AXIS));\n        pnlDirection.setBorder(BorderFactory.createTitledBorder(i18n(\"text_viewer.find.direction\")));\n\n        ButtonGroup bg = new ButtonGroup();\n        rbUp = new JRadioButton(i18n(\"text_viewer.find.up\"), false);\n        rbDown = new JRadioButton(i18n(\"text_viewer.find.down\"), true);\n        rbUp.addActionListener(this);\n        rbDown.addActionListener(this);\n        bg.add(rbUp);\n        bg.add(rbDown);\n        pnlDirection.add(rbUp);\n        pnlDirection.add(rbDown);\n\n        // Initialize the \"mark all\" button.\n        markAllCheckBox = createCheckBox(i18n(\"text_viewer.find.mark_all\"));\n\n        // Rearrange the search conditions panel.\n        pnlSearchConditions.removeAll();\n        pnlSearchConditions.setLayout(new BorderLayout());\n        JPanel temp = new JPanel();\n        temp.setLayout(new BoxLayout(temp, BoxLayout.PAGE_AXIS));\n        temp.add(cbCaseSensitive);\n        temp.add(cbWholeWord);\n        pnlSearchConditions.add(temp, BorderLayout.LINE_START);\n        temp = new JPanel();\n        temp.setLayout(new BoxLayout(temp, BoxLayout.PAGE_AXIS));\n        temp.add(cbRegex);\n        temp.add(markAllCheckBox);\n        pnlSearchConditions.add(temp, BorderLayout.LINE_END);\n\n        lblFind = new JLabel(i18n(\"text_viewer.find_button\") + \":\");\n\n        btnFind = new JButton(i18n(\"text_viewer.find_button\"));\n        btnFind.addActionListener(this);\n        btnFind.setDefaultCapable(true);\n        btnFind.setEnabled(false);\n\n        installKeyboardActions();\n\n        fixHeight();\n    }\n\n\n\n    @Override\n    public void actionPerformed(ActionEvent e) {\n        Object src = e.getSource();\n\n        if (src == cbCaseSensitive) {\n            boolean matchCase = cbCaseSensitive.isSelected();\n            context.setMatchCase(matchCase);\n        } else if (src == cbWholeWord) {\n            boolean wholeWord = cbWholeWord.isSelected();\n            context.setWholeWord(wholeWord);\n        } else if (src == cbRegex) {\n            boolean useRegEx = cbRegex.isSelected();\n            context.setRegularExpression(useRegEx);\n        } else if (src == rbUp) {\n            context.setSearchForward(false);\n        } else if (src == rbDown) {\n            context.setSearchForward(true);\n        } else if (src == markAllCheckBox) {\n            boolean checked = markAllCheckBox.isSelected();\n            context.setMarkAll(checked);\n        } else if (src == btnCancel) {\n            dispose();\n        } else if (src == btnFind || src == edtText) {\n            // Add the item to the combo box's list, if it isn't already there.\n            context.setSearchFor(getSearchString());\n            fireSearchEvent(e); // Let parent application know\n        }\n    }\n\n    private JCheckBox createCheckBox(String name) {\n        JCheckBox cb = new JCheckBox(name);\n        cb.addActionListener(this);\n        return cb;\n    }\n\n\n    /**\n     * Returns the default search context to use for this dialog.  Applications\n     * that create new subclasses of this class can provide customized\n     * search contexts here.\n     *\n     * @return The default search context.\n     */\n    protected SearchContext createDefaultSearchContext() {\n        return new SearchContext();\n    }\n\n\n\n    /**\n     * Makes the \"Find text\" field active.\n     */\n    protected void focusFindTextField() {\n        edtText.requestFocusInWindow();\n        edtText.selectAll();\n    }\n\n\n\n\n    /**\n     * Returns the search context used by this dialog.\n     *\n     * @return The search context.\n     * @see #setSearchContext(SearchContext)\n     */\n    public SearchContext getSearchContext() {\n        return context;\n    }\n\n\n    /**\n     * Returns the text to search for.\n     *\n     * @return The text the user wants to search for.\n     */\n    public String getSearchString() {\n        return edtText.getText();\n    }\n\n    /**\n     * Called when the regex checkbox is clicked (or its value is modified\n     * via a change to the search context).  Subclasses can override\n     * to add custom behavior, but should call the super implementation.\n     */\n    protected void handleRegExCheckBoxClicked() {\n        handleToggleButtons();\n        // \"Content assist\" support\n        boolean b = cbRegex.isSelected();\n    }\n\n\n    /**\n     * Called whenever a property in the search context is modified.\n     * Subclasses should override if they listen for additional properties.\n     *\n     * @param e The property change event fired.\n     */\n    protected void handleSearchContextPropertyChanged(PropertyChangeEvent e) {\n\n        // A property changed on the context itself.\n        String prop = e.getPropertyName();\n\n        if (SearchContext.PROPERTY_SEARCH_FORWARD.equals(prop)) {\n            boolean newValue = (Boolean) e.getNewValue();\n            JRadioButton button = newValue ? rbDown : rbUp;\n            button.setSelected(true);\n        } else if (SearchContext.PROPERTY_MARK_ALL.equals(prop)) {\n            boolean newValue = (Boolean) e.getNewValue();\n            markAllCheckBox.setSelected(newValue);\n        } else if (SearchContext.PROPERTY_MATCH_CASE.equals(prop)) {\n            boolean newValue = (Boolean) e.getNewValue();\n            cbCaseSensitive.setSelected(newValue);\n        } else if (SearchContext.PROPERTY_MATCH_WHOLE_WORD.equals(prop)) {\n            boolean newValue = (Boolean) e.getNewValue();\n            cbWholeWord.setSelected(newValue);\n        } else if (SearchContext.PROPERTY_USE_REGEX.equals(prop)) {\n            boolean newValue = (Boolean) e.getNewValue();\n            cbRegex.setSelected(newValue);\n            handleRegExCheckBoxClicked();\n        } else if (SearchContext.PROPERTY_SEARCH_FOR.equals(prop)) {\n            String newValue = (String)e.getNewValue();\n            String oldValue = getSearchString();\n            // Prevents IllegalStateExceptions\n            if (!newValue.equals(oldValue)) {\n                setSearchString(newValue);\n            }\n        }\n\n    }\n\n\n    /**\n     * Returns whether any action-related buttons (Find Next, Replace, etc.)\n     * should be enabled.  Subclasses can call this method when the \"Find What\"\n     * or \"Replace With\" text fields are modified.  They can then\n     * enable/disable any components as appropriate.\n     *\n     * @return Whether the buttons should be enabled.\n     */\n    protected FindReplaceButtonsEnableResult handleToggleButtons() {\n        FindReplaceButtonsEnableResult er;\n\n        //String text = getSearchString();\n        String text = edtText.getText();\n        if (text.isEmpty()) {\n            er = new FindReplaceButtonsEnableResult(false, null);\n        } else if (cbRegex.isSelected()) {\n            try {\n                Pattern.compile(text);\n                er = new FindReplaceButtonsEnableResult(true, null);\n            } catch (PatternSyntaxException pse) {\n                er = new FindReplaceButtonsEnableResult(false, pse.getMessage());\n            }\n        } else {\n            er = new FindReplaceButtonsEnableResult(true, null);\n        }\n\n        boolean enable = er.getEnable();\n\n        btnFind.setEnabled(enable);\n\n        // setBackground doesn't show up with XP Look and Feel!\n        //findTextComboBox.setBackground(enable ?\n        //\t\tUIManager.getColor(\"ComboBox.background\") : Color.PINK);\n//        edtText.setForeground(enable ? UIManager.getColor(\"TextField.foreground\") : UIUtil.getErrorTextForeground());\n\n//        String tooltip = SearchUtil.getToolTip(er);\n//        edtText.setToolTipText(tooltip); // Always set, even if null\n\n        return er;\n\n    }\n\n\n\n    boolean matchesSearchFor(String text) {\n        if (text == null || text.isEmpty()) {\n            return false;\n        }\n        String searchFor = edtText.getText();\n        if (searchFor != null && !searchFor.isEmpty()) {\n            boolean matchCase = cbCaseSensitive.isSelected();\n            if (cbRegex.isSelected()) {\n                int flags = Pattern.MULTILINE; // '^' and '$' are done per line.\n                flags = RSyntaxUtilities.getPatternFlags(matchCase, flags);\n                Pattern pattern;\n                try {\n                    pattern = Pattern.compile(searchFor, flags);\n                } catch (PatternSyntaxException pse) {\n                    getLogger().error(\"Invalid regular expression: \" + searchFor, pse);\n                    return false;\n                }\n                return pattern.matcher(text).matches();\n            } else {\n                if (matchCase) {\n                    return searchFor.equals(text);\n                }\n                return searchFor.equalsIgnoreCase(text);\n            }\n        }\n        return false;\n    }\n\n\n\n    /**\n     * Returns whether the characters on either side of\n     * <code>substr(searchIn,startPos,startPos+searchStringLength)</code>\n     * are whitespace.\n     * While this isn't the best definition of \"whole word\", it's the one we're going to use for now.\n     */\n    public static boolean isWholeWord(CharSequence searchIn, int offset, int len) {\n        boolean wsBefore, wsAfter;\n\n        try {\n            wsBefore = Character.isWhitespace(searchIn.charAt(offset - 1));\n        } catch (IndexOutOfBoundsException e) {\n            wsBefore = true;\n        }\n        try {\n            wsAfter  = Character.isWhitespace(searchIn.charAt(offset + len));\n        } catch (IndexOutOfBoundsException e) {\n            wsAfter = true;\n        }\n        return wsBefore && wsAfter;\n    }\n\n\n    /**\n     * Initializes the UI in this tool bar from a search context.  This is\n     * called whenever a new search context is installed on this tool bar\n     * (which should practically be never).\n     */\n    protected void refreshUIFromContext() {\n        if (cbCaseSensitive == null || markAllCheckBox == null) {\n            return; // First time through, UI not realized yet\n        }\n        cbCaseSensitive.setSelected(context.getMatchCase());\n        cbRegex.setSelected(context.isRegularExpression());\n        cbWholeWord.setSelected(context.getWholeWord());\n\n        markAllCheckBox.setSelected(context.getMarkAll());\n        boolean searchForward = context.getSearchForward();\n        rbUp.setSelected(!searchForward);\n        rbDown.setSelected(searchForward);\n    }\n\n\n    /**\n     * Overridden to ensure the \"Find text\" field gets focused.\n     */\n    @Override\n    public void requestFocus() {\n        super.requestFocus();\n        focusFindTextField();\n    }\n\n\n    /**\n     * Sets the search context for this dialog.  You'll usually want to call\n     * this method for all search dialogs and give them the same search\n     * context, so that their options (match case, etc.) stay in sync with one\n     * another.\n     *\n     * @param context The new search context.  This cannot be <code>null</code>.\n     * @see #getSearchContext()\n     */\n    public void setSearchContext(SearchContext context) {\n        if (this.context != null) {\n            this.context.removePropertyChangeListener(contextListener);\n        }\n        this.context = context;\n        this.context.addPropertyChangeListener(contextListener);\n        refreshUIFromContext();\n    }\n\n\n    /**\n     * Sets the <code>java.lang.String</code> to search for.\n     *\n     * @param text The <code>String</code> to put into the search field.\n     */\n    public void setSearchString(String text) {\n        edtText.setText(text);\n        if (text != null) {\n            edtText.select(0, text.length());\n        }\n    }\n\n\n    /**\n     * Adds a {@link SearchListener} to this dialog.  This listener will\n     * be notified when find or replace operations are triggered.  For\n     * example, for a Replace dialog, a listener will receive notification\n     * when the user clicks \"Find\", \"Replace\", or \"Replace All\".\n     *\n     * @param l The listener to add.\n     */\n    public void addSearchListener(SearchListener l) {\n        listenerList.add(SearchListener.class, l);\n    }\n\n\n    /**\n     * Notifies all listeners that have registered interest for notification on\n     * this event type. The event instance is lazily created using the\n     * <code>event</code> parameter.\n     *\n     * @param event The <code>ActionEvent</code> object coming from a\n     *        child component.\n     */\n    void fireSearchEvent(ActionEvent event) {\n        // Guaranteed to return a non-null array\n        Object[] listeners = listenerList.getListenerList();\n        SearchEvent e = null;\n        // Process the listeners last to first, notifying\n        // those that are interested in this event\n        for (int i = listeners.length - 2; i >= 0; i -= 2) {\n            if (listeners[i] == SearchListener.class) {\n                // Lazily create the event:\n                if (e == null) {\n                    String command = event.getActionCommand();\n                    SearchEvent.Type type = SearchEvent.Type.valueOf(command);\n                    e = new SearchEvent(this, type, context);\n                }\n                ((SearchListener)listeners[i+1]).searchEvent(e);\n            }\n        }\n    }\n\n\n\n    /**\n     * Adds extra keyboard actions for Find and Replace dialogs.\n     */\n    private void installKeyboardActions() {\n        JRootPane rootPane = getRootPane();\n        InputMap im = rootPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);\n        ActionMap am = rootPane.getActionMap();\n\n        int modifier = getToolkit().getMenuShortcutKeyMaskEx();\n        KeyStroke ctrlF = KeyStroke.getKeyStroke(KeyEvent.VK_F, modifier);\n        im.put(ctrlF, \"focusSearchForField\");\n        am.put(\"focusSearchForField\", new AbstractAction() {\n            public void actionPerformed(ActionEvent e) {\n                AbstractSearchDialog.this.requestFocus();\n            }\n        });\n    }\n\n    /**\n     * Used by makeSpringCompactGrid.  This is ripped off directly from\n     * <code>SpringUtilities.java</code> in the Sun Java Tutorial.\n     *\n     * @param parent The container whose layout must be an instance of\n     *        <code>SpringLayout</code>.\n     * @return The spring constraints for the specified component contained\n     *         in <code>parent</code>.\n     */\n    private static SpringLayout.Constraints getConstraintsForCell(int row, int col, Container parent, int cols) {\n        SpringLayout layout = (SpringLayout) parent.getLayout();\n        Component c = parent.getComponent(row * cols + col);\n        return layout.getConstraints(c);\n    }\n\n\n    /**\n     * This method is ripped off from <code>SpringUtilities.java</code> found\n     * on Sun's Java Tutorial pages.  It takes a component whose layout is\n     * <code>SpringLayout</code> and organizes the components it contains into\n     * a nice grid.\n     * Aligns the first <code>rows</code> * <code>cols</code> components of\n     * <code>parent</code> in a grid. Each component in a column is as wide as\n     * the maximum preferred width of the components in that column; height is\n     * similarly determined for each row.  The parent is made just big enough\n     * to fit them all.\n     *\n     * @param parent The container whose layout is <code>SpringLayout</code>.\n     * @param rows The number of rows of components to make in the container.\n     * @param cols The number of columns of components to make.\n     * @param initialX The x-location to start the grid at.\n     * @param initialY The y-location to start the grid at.\n     * @param xPad The x-padding between cells.\n     * @param yPad The y-padding between cells.\n     */\n    protected static void makeSpringCompactGrid(Container parent, int rows, int cols, int initialX, int initialY, int xPad, int yPad) {\n        SpringLayout layout;\n        try {\n            layout = (SpringLayout)parent.getLayout();\n        } catch (ClassCastException cce) {\n            getLogger().error(\"The first argument to makeCompactGrid must use SpringLayout.\");\n            return;\n        }\n\n        // Align all cells in each column and make them the same width.\n        Spring x = Spring.constant(initialX);\n        for (int c = 0; c < cols; c++) {\n            Spring width = Spring.constant(0);\n            for (int r = 0; r < rows; r++) {\n                width = Spring.max(width, getConstraintsForCell(r, c, parent, cols).getWidth());\n            }\n            for (int r = 0; r < rows; r++) {\n                SpringLayout.Constraints constraints = getConstraintsForCell(r, c, parent, cols);\n                constraints.setX(x);\n                constraints.setWidth(width);\n            }\n            x = Spring.sum(x, Spring.sum(width, Spring.constant(xPad)));\n        }\n\n        // Align all cells in each row and make them the same height.\n        Spring y = Spring.constant(initialY);\n        for (int r = 0; r < rows; r++) {\n            Spring height = Spring.constant(0);\n            for (int c = 0; c < cols; c++) {\n                height = Spring.max(height, getConstraintsForCell(r, c, parent, cols).getHeight());\n            }\n            for (int c = 0; c < cols; c++) {\n                SpringLayout.Constraints constraints = getConstraintsForCell(r, c, parent, cols);\n                constraints.setY(y);\n                constraints.setHeight(height);\n            }\n            y = Spring.sum(y, Spring.sum(height, Spring.constant(yPad)));\n        }\n\n        // Set the parent's size.\n        SpringLayout.Constraints pCons = layout.getConstraints(parent);\n        pCons.setConstraint(SpringLayout.SOUTH, y);\n        pCons.setConstraint(SpringLayout.EAST, x);\n    }\n\n    private static Logger getLogger() {\n        if (logger == null) {\n            logger = LoggerFactory.getLogger(AbstractSearchDialog.class);\n        }\n        return logger;\n    }\n\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/search/FindDialog.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.text.search;\n\nimport org.fife.ui.rtextarea.SearchContext;\n\nimport javax.swing.*;\nimport javax.swing.event.DocumentEvent;\nimport javax.swing.event.DocumentListener;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\n\n/**\n * @author Oleg Trifonov\n * Created on 21/06/16.\n */\npublic class FindDialog extends AbstractSearchDialog {\n\n    /**\n     * Our search listener, cached so we can grab its selected text easily.\n     */\n    private SearchListener searchListener;\n\n    private static String lastSearchedStr;\n\n\n    public FindDialog(Frame owner, SearchListener listener) {\n        super(owner, i18n(\"text_viewer.find\"), null);\n\n        this.searchListener = listener;\n\n        btnFind.setActionCommand(SearchEvent.Type.FIND.toString());\n        edtText.setActionCommand(SearchEvent.Type.FIND.toString());\n\n        // Make a panel containing the \"Find\" edit box.\n        JPanel enterTextPane = new JPanel(new SpringLayout());\n        enterTextPane.setBorder(BorderFactory.createEmptyBorder(0,5,5,5));\n        edtText.getDocument().addDocumentListener(new FindDocumentListener());\n        JPanel temp = new JPanel(new BorderLayout());\n        temp.add(edtText, BorderLayout.CENTER);\n        enterTextPane.add(lblFind);\n        enterTextPane.add(temp);\n\n        makeSpringCompactGrid(enterTextPane, 1, 2,\t//rows, cols\n                0,0,\t\t//initX, initY\n                6, 6);\t//xPad, yPad\n\n        // Make a panel containing the inherited search direction radio buttons and the inherited search options.\n        JPanel bottomPanel = new JPanel(new BorderLayout());\n        temp = new JPanel(new BorderLayout());\n        bottomPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));\n        temp.add(pnlSearchConditions, BorderLayout.LINE_START);\n        temp.add(pnlDirection);\n        bottomPanel.add(temp, BorderLayout.LINE_START);\n\n        // Now, make a panel containing all the above stuff.\n        JPanel leftPanel = new JPanel();\n        leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.Y_AXIS));\n        leftPanel.add(enterTextPane);\n        leftPanel.add(bottomPanel);\n\n        // Make a panel containing the action buttons.\n        JPanel buttonPanel = new JPanel();\n        buttonPanel.setLayout(new GridLayout(2, 1, 5, 5));\n        buttonPanel.add(btnFind);\n        buttonPanel.add(btnCancel);\n        JPanel rightPanel = new JPanel();\n        rightPanel.setLayout(new BorderLayout());\n        rightPanel.add(buttonPanel, BorderLayout.NORTH);\n\n        // Put everything into a neat little package.\n        JPanel contentPane = new JPanel(new BorderLayout());\n        contentPane.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 5));\n        contentPane.add(leftPanel);\n        contentPane.add(rightPanel, BorderLayout.LINE_END);\n        getContentPane().add(contentPane);\n        getRootPane().setDefaultButton(btnFind);\n        setLocationRelativeTo(getParent());\n\n        setSearchContext(new SearchContext());\n        addSearchListener(listener);\n    }\n\n\n    @Override\n    public void actionPerformed(ActionEvent e) {\n        super.actionPerformed(e);\n        Object src = e.getSource();\n        if (src == btnFind || src == edtText) {\n            lastSearchedStr = edtText.getText();\n            dispose();\n        }\n    }\n\n    /**\n     * Overrides <code>JDialog</code>'s <code>setVisible</code> method; decides\n     * whether buttons are enabled.\n     *\n     * @param visible Whether the dialog should be visible.\n     */\n    @Override\n    public void setVisible(boolean visible) {\n        if (visible) {\n            // Select text entered in the UI\n            String text = searchListener.getSelectedText();\n            if (text != null) {\n                edtText.setText(text);\n            }\n\n            String selectedItem = edtText.getText();\n            btnFind.setEnabled(selectedItem != null && !selectedItem.isEmpty());\n            super.setVisible(true);\n            focusFindTextField();\n        } else {\n            super.setVisible(false);\n        }\n\n    }\n\n    /**\n     * Listens for changes in the text field (find search field).\n     */\n    private class FindDocumentListener implements DocumentListener {\n\n        public void insertUpdate(DocumentEvent e) {\n            handleToggleButtons();\n        }\n\n        public void removeUpdate(DocumentEvent e) {\n            if (edtText.getDocument().getLength() == 0) {\n                btnFind.setEnabled(false);\n            } else {\n                handleToggleButtons();\n            }\n        }\n\n        public void changedUpdate(DocumentEvent e) {\n        }\n\n    }\n\n    public static String getLastSearchStr() {\n        return lastSearchedStr;\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/search/FindReplaceButtonsEnableResult.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.text.search;\n\n/**\n * Returns the result of whether the \"action\" buttons such as \"Find\"\n * and \"Replace\" should be enabled.\n *\n * Created on 21/06/16.\n * @author Oleg Trifonov\n */\npublic class FindReplaceButtonsEnableResult {\n    private boolean enable;\n    private final String error;\n\n    FindReplaceButtonsEnableResult(boolean enable, String error) {\n        this.enable = enable;\n        this.error = error;\n    }\n\n    public boolean getEnable() {\n        return enable;\n    }\n\n    public String getError() {\n        return error;\n    }\n\n    public void setEnable(boolean enable) {\n        this.enable = enable;\n    }\n\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/search/ReplaceDialog.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.text.search;\n\nimport org.fife.ui.rtextarea.SearchContext;\n\nimport javax.swing.*;\nimport javax.swing.event.DocumentEvent;\nimport javax.swing.event.DocumentListener;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.beans.PropertyChangeEvent;\n\n/**\n * @author Oleg Trifonov\n * Created on 21/06/16.\n */\npublic class ReplaceDialog extends AbstractSearchDialog {\n    private final JButton btnReplace;\n    private final JButton btnReplaceAll;\n    private final JTextField edtReplace;\n\n\n    /**\n     * Our search listener, cached so we can grab its selected text easily.\n     */\n    private final SearchListener searchListener;\n\n\n    public ReplaceDialog(Frame owner, SearchListener listener) {\n        super(owner, i18n(\"text_editor.replace\"), null);\n\n        this.searchListener = listener;\n\n        btnFind.setActionCommand(SearchEvent.Type.FIND.toString());\n        edtText.setActionCommand(SearchEvent.Type.FIND.toString());\n\n        ReplaceDocumentListener replaceDocumentListener = new ReplaceDocumentListener();\n\n        // Create a panel for the \"Find what\" and \"Replace with\" text fields.\n        JPanel searchPanel = new JPanel(new SpringLayout());\n\n        edtText.getDocument().addDocumentListener(replaceDocumentListener);\n\n        // Create the \"Replace with\" text field.\n        edtReplace = new JTextField(20);\n        edtReplace.getDocument().addDocumentListener(replaceDocumentListener);\n\n        // Create the \"Replace with\" label.\n        JLabel lblReplace = new JLabel(i18n(\"text_editor.replace_with\") + \":\");\n\n        JPanel temp = new JPanel(new BorderLayout());\n        temp.add(edtReplace);\n        temp.add(edtText, BorderLayout.CENTER);\n        JPanel temp2 = new JPanel(new BorderLayout());\n        temp2.add(edtReplace);\n        temp2.add(edtReplace, BorderLayout.CENTER);\n\n        searchPanel.add(lblFind);\n        searchPanel.add(temp);\n        searchPanel.add(lblReplace);\n        searchPanel.add(temp2);\n\n        makeSpringCompactGrid(searchPanel, 2, 2,\n                5, 0,\n                6, 6);\n\n        // Make a panel containing the inherited search direction radio\n        // buttons and the inherited search options.\n        JPanel bottomPanel = new JPanel(new BorderLayout());\n        temp = new JPanel(new BorderLayout());\n        bottomPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));\n        temp.add(pnlSearchConditions, BorderLayout.LINE_START);\n        temp.add(pnlDirection);\n        bottomPanel.add(temp, BorderLayout.LINE_START);\n\n        // Now, make a panel containing all the above stuff.\n        JPanel leftPanel = new JPanel();\n        leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.Y_AXIS));\n        leftPanel.add(searchPanel);\n        leftPanel.add(bottomPanel);\n\n        // Make a panel containing the action buttons.\n        JPanel buttonPanel = new JPanel();\n        buttonPanel.setLayout(new GridLayout(4, 1, 5, 5));\n        btnReplace = new JButton(i18n(\"text_editor.replace_button\"));\n        btnReplace.setActionCommand(SearchEvent.Type.REPLACE.name());\n        btnReplace.addActionListener(this);\n        btnReplace.setEnabled(false);\n        btnReplaceAll = new JButton(i18n(\"text_editor.replace_all\"));\n        btnReplaceAll.setActionCommand(SearchEvent.Type.REPLACE_ALL.name());\n        btnReplaceAll.addActionListener(this);\n        btnReplaceAll.setEnabled(false);\n        buttonPanel.add(btnFind);\n        buttonPanel.add(btnReplace);\n        buttonPanel.add(btnReplaceAll);\n        buttonPanel.add(btnCancel);\n        JPanel rightPanel = new JPanel(new BorderLayout());\n        rightPanel.add(buttonPanel, BorderLayout.NORTH);\n\n        // Put it all together!\n        JPanel contentPane = new JPanel(new BorderLayout());\n        contentPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));\n        contentPane.add(leftPanel);\n        contentPane.add(rightPanel, BorderLayout.LINE_END);\n        getContentPane().add(contentPane);\n        getRootPane().setDefaultButton(btnFind);\n        setLocationRelativeTo(getParent());\n\n        setSearchContext(new SearchContext());\n        addSearchListener(listener);\n    }\n\n\n    @Override\n    public void actionPerformed(ActionEvent e) {\n        String command = e.getActionCommand();\n        if (SearchEvent.Type.REPLACE.name().equals(command) || SearchEvent.Type.REPLACE_ALL.name().equals(command)) {\n            context.setSearchFor(getSearchString());\n            context.setReplaceWith(edtReplace.getText());\n            fireSearchEvent(e); // Let parent application know\n        } else {\n            super.actionPerformed(e);\n            if (SearchEvent.Type.FIND.name().equals(command)) {\n                handleToggleButtons(); // Replace button could toggle state\n            }\n        }\n    }\n\n\n    @Override\n    protected void handleSearchContextPropertyChanged(PropertyChangeEvent e) {\n        String prop = e.getPropertyName();\n\n        if (SearchContext.PROPERTY_REPLACE_WITH.equals(prop)) {\n            String newValue = (String)e.getNewValue();\n            if (newValue == null) {\n                newValue = \"\";\n            }\n            String oldValue = edtReplace.getText();\n            // Prevents IllegalStateExceptions\n            if (!newValue.equals(oldValue)) {\n                edtReplace.setText(newValue);\n            }\n        } else {\n            super.handleSearchContextPropertyChanged(e);\n        }\n    }\n\n\n    @Override\n    protected FindReplaceButtonsEnableResult handleToggleButtons() {\n        FindReplaceButtonsEnableResult er = super.handleToggleButtons();\n        boolean shouldReplace = er.getEnable();\n        btnReplaceAll.setEnabled(shouldReplace);\n\n        // \"Replace\" is only enabled if text to search for is selected in the UI\n        if (shouldReplace) {\n            String text = searchListener.getSelectedText();\n            shouldReplace = matchesSearchFor(text);\n        }\n        btnReplace.setEnabled(shouldReplace);\n\n        return er;\n    }\n\n    /**\n     * Listens for changes in the text field (find search field).\n     */\n    private class ReplaceDocumentListener implements DocumentListener {\n        @Override\n        public void insertUpdate(DocumentEvent e) {\n            if (e.getDocument().equals(edtText.getDocument())) {\n                handleToggleButtons();\n            }\n        }\n\n        @Override\n        public void removeUpdate(DocumentEvent e) {\n            if (e.getDocument().equals(edtText.getDocument()) && e.getDocument().getLength() == 0) {\n                btnFind.setEnabled(false);\n                btnReplace.setEnabled(false);\n                btnReplaceAll.setEnabled(false);\n            } else {\n                handleToggleButtons();\n            }\n        }\n\n        @Override\n        public void changedUpdate(DocumentEvent e) {\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/search/SearchEvent.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.text.search;\n\nimport org.fife.ui.rtextarea.SearchContext;\n\nimport java.util.EventObject;\n\n/**\n * The event fired whenever a user wants to search for or replace text in a\n * Find or Replace dialog/tool bar.\n *\n * Created on 21/06/16.\n */\npublic class SearchEvent extends EventObject {\n\n    private SearchContext context;\n    private Type type;\n\n    SearchEvent(Object source, Type type, SearchContext context) {\n        super(source);\n        this.type = type;\n        this.context = context;\n    }\n\n\n    public Type getType() {\n        return type;\n    }\n\n\n    public SearchContext getSearchContext() {\n        return context;\n    }\n\n\n    /**\n     * Types of search events.\n     */\n    public enum Type {\n\n        /**\n         * The event fired when the text to \"mark all\" has changed.\n         */\n        MARK_ALL,\n\n        /**\n         * The event fired when the user wants to find text in the editor.\n         */\n        FIND,\n\n        /**\n         * The event fired when the user wants to replace text in the editor.\n         */\n        REPLACE,\n\n        /**\n         * The event fired when the user wants to replace all instances of\n         * specific text with new text in the editor.\n         */\n        REPLACE_ALL\n\n    }\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/search/SearchListener.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.text.search;\n\n\nimport java.util.EventListener;\n\n/**\n * Listens for events fired from a Find or Replace dialog/tool bar.\n *\n * Created on 21/06/16.\n *\n */\npublic interface SearchListener extends EventListener {\n\n\n    /**\n     * Callback called whenever a search event occurs.\n     *\n     * @param e The event.\n     */\n    void searchEvent(SearchEvent e);\n\n\n    String getSelectedText();\n\n\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/tools/ExecOutputTextPane.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2018 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.text.tools;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.util.StringUtils;\nimport com.mucommander.ui.theme.Theme;\nimport com.mucommander.ui.theme.ThemeManager;\n\nimport javax.swing.*;\nimport javax.swing.text.*;\nimport java.awt.*;\nimport java.awt.event.KeyAdapter;\nimport java.awt.event.KeyEvent;\nimport java.awt.event.MouseAdapter;\nimport java.awt.event.MouseEvent;\nimport java.io.File;\n\nimport static com.mucommander.commons.util.StringUtils.isNullOrBlank;\nimport static com.mucommander.commons.util.StringUtils.isNumber;\n\nclass ExecOutputTextPane extends JTextPane {\n    private final Style styleDefault = addStyle(\"Default\", null);\n    private final Style styleFilePath = addStyle(\"Path\", null);\n    private final Style stylePosition = addStyle(\"Position\", null);\n    private final Style styleMessage = addStyle(\"Message\", null);\n    private final Style styleError = addStyle(\"Error\", styleMessage);\n    private final Style styleWarning = addStyle(\"Warning\", styleMessage);\n    private final Style styleMarker = addStyle(\"Marker\", styleMessage);\n\n\n    ExecOutputTextPane(Runnable onClose, OnClickFileHandler onFileClickHandler) {\n        setCaretPosition(0);\n        setEditable(false);\n\n        setupKeyListener(onClose);\n        setupMouseListener(onFileClickHandler);\n        setupColors();\n        setupStyles();\n    }\n\n\n    private void setupKeyListener(Runnable onClose) {\n        addKeyListener(new KeyAdapter() {\n            private boolean pressed;\n            @Override\n            public void keyPressed(KeyEvent e) {\n                if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {\n                    pressed = true;\n                    e.consume();\n                } else {\n                    pressed = false;\n                }\n            }\n\n            @Override\n            public void keyReleased(KeyEvent e) {\n                if (e.getKeyCode() == KeyEvent.VK_ESCAPE && pressed) {\n                    if (onClose != null) {\n                        onClose.run();\n                    }\n                    e.consume();\n                }\n            }\n        });\n    }\n\n    private void setupMouseListener(OnClickFileHandler onFileClickHandler) {\n        addMouseListener(new MouseAdapter() {\n            @Override\n            public void mouseClicked(MouseEvent e) {\n                String str = getLineForMousePosition(e.getPoint());\n                if (isNullOrBlank(str)) {\n                    return;\n                }\n                String[] parts = str.split(\":\");\n                if (parts.length < 3) {\n                    return;\n                }\n                if (onFileClickHandler != null && isNumber(parts[1]) && isFilePath(parts[0])) {\n                    AbstractFile file = FileFactory.getFile(parts[0]);\n                    int line = Integer.parseInt(parts[1]);\n                    int column = parts.length > 3 && isNumber(parts[2]) ? Integer.parseInt(parts[2]) : 1;\n                    onFileClickHandler.onClick(file, line, column);\n                }\n\n            }\n        });\n    }\n\n    private void setupColors() {\n        setForeground(ThemeManager.getCurrentColor(Theme.SHELL_FOREGROUND_COLOR));\n        setCaretColor(ThemeManager.getCurrentColor(Theme.SHELL_FOREGROUND_COLOR));\n        setBackground(ThemeManager.getCurrentColor(Theme.SHELL_BACKGROUND_COLOR));\n        setSelectedTextColor(ThemeManager.getCurrentColor(Theme.SHELL_SELECTED_FOREGROUND_COLOR));\n        setSelectionColor(ThemeManager.getCurrentColor(Theme.SHELL_SELECTED_BACKGROUND_COLOR));\n        setFont(ThemeManager.getCurrentFont(Theme.SHELL_FONT));\n    }\n\n    private void setupStyles() {\n        StyleConstants.setForeground(styleFilePath, new Color(0x5555ff));\n        StyleConstants.setUnderline(styleFilePath, true);\n        StyleConstants.setForeground(stylePosition, Color.YELLOW);\n        StyleConstants.setForeground(styleError, Color.RED);\n        StyleConstants.setForeground(styleWarning, Color.CYAN);\n        StyleConstants.setForeground(styleMarker, Color.WHITE);\n    }\n\n\n    private void add(String s, Style style) {\n        StyledDocument doc = getStyledDocument();\n        try {\n            doc.insertString(doc.getLength(), s, style);\n        } catch (BadLocationException e) {\n            e.printStackTrace();\n        }\n    }\n\n    void addLine(String line) {\n        if (isMarker(line)) {\n            add(line, styleMarker);\n            update();\n            return;\n        }\n        String[] parts = line.split(\":\");\n        if (parts.length < 3 || !isNumber(parts[1]) || !isFilePath(parts[0])) {\n            add(line, styleDefault);\n            update();\n            return;\n        }\n        add(parts[0], styleFilePath);\n        add(\":\", styleDefault);\n        add(parts[1], stylePosition);\n        add(\":\", styleDefault);\n        int index;\n        if (isNumber(parts[2])) {\n            add(parts[2], stylePosition);\n            add(\":\", styleDefault);\n            index = 3;\n        } else {\n            index = 2;\n        }\n        Style defaultStyle = styleMessage;\n        for (int i = index; i < parts.length; i++) {\n            String part = parts[i];\n            String lower = part.trim().toLowerCase();\n            if (lower.equals(\"error\")) {\n                add(part, styleError);\n                defaultStyle = styleError;\n            } else if (lower.equals(\"warning\") || lower.equals(\"note\")) {\n                add(part, styleWarning);\n                defaultStyle = styleWarning;\n            } else {\n                add(part, defaultStyle);\n            }\n            if (i < parts.length-1 || line.endsWith(\":\")) {\n                add(\":\", defaultStyle);\n            }\n        }\n        update();\n    }\n\n    private void update() {\n        setCaretPosition(getText().length());\n        getCaret().setVisible(true);\n        repaint();\n    }\n\n    void clear() {\n        setText(\"\");\n        setCaretPosition(0);\n        getCaret().setVisible(true);\n        requestFocus();\n    }\n\n\n    private static boolean isMarker(String s) {\n        if (StringUtils.isNullOrBlank(s)) {\n            return false;\n        }\n        for (int i = 0; i < s.length(); i++) {\n            char ch = s.charAt(i);\n            if (\" \\t^~\\n\".indexOf(ch) < 0) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    private static boolean isFilePath(String s) {\n        return new File(s).exists();\n    }\n\n    private String getLineForMousePosition(Point p) {\n        if (p == null) {\n            return null;\n        }\n        int pos = viewToModel2D(p);\n        try {\n            int start = Utilities.getRowStart(this, pos);\n            int end = Utilities.getRowEnd(this, pos);\n            if (start < 0 || end < start) {\n                return null;\n            }\n            return getDocument().getText(start, end - start);\n        } catch (BadLocationException e) {\n            return null;\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/tools/ExecPanel.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2018 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.text.tools;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.process.AbstractProcess;\nimport com.mucommander.process.ProcessListener;\nimport com.mucommander.shell.Shell;\nimport com.mucommander.ui.icon.SpinningDial;\nimport ru.trolsoft.utils.StringStream;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.io.PrintStream;\n\n\npublic class ExecPanel extends JPanel implements ProcessListener {\n\n    private final ExecOutputTextPane outputPane;\n    private final SpinningDial dial;\n    private final JLabel lblDial;\n    private final StringStream stringStream = new StringStream();\n\n    /** Stream used to send characters to the process' stdin process. */\n    private PrintStream processInput;\n    /** Process currently running, <code>null</code> if none. */\n    private AbstractProcess currentProcess;\n\n\n    public ExecPanel(Runnable onClose, OnClickFileHandler onFileClickHandler) {\n        super();\n        setLayout(new BorderLayout());\n        outputPane = new ExecOutputTextPane(onClose, onFileClickHandler);\n        JScrollPane scrollPane = new JScrollPane(outputPane, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);\n        this.add(scrollPane, BorderLayout.CENTER);\n        lblDial = new JLabel(dial = new SpinningDial());\n        this.add(lblDial, BorderLayout.SOUTH);\n    }\n\n    public void runCommand(AbstractFile folder, String command) {\n        try {\n            // Starts the spinning dial.\n            dial.setAnimated(true);\n            lblDial.setVisible(true);\n\n            // Change 'Run' button to 'Stop'\n            //this.btnRunStop.setText(i18n(\"run_dialog.stop\"));\n\n            // Resets the process outputPane area.\n            outputPane.clear();\n            stringStream.clear();\n\n            // No new command can be entered while a process is running.\n            //inputCombo.setEnabled(false);\n            currentProcess = Shell.execute(command, folder, this);\n            processInput = new PrintStream(currentProcess.getOutputStream(), true);\n\n            // Repaints the dialog.\n            repaint();\n        } catch (Exception e) {\n            // Notifies the user that an error occurred and resets to normal state.\n            e.printStackTrace();\n            outputPane.addLine(\"generic_error \" + e.getMessage()); // TODO\n//            switchToRunState();\n        }\n    }\n\n\n    @Override\n    public void processDied(int returnValue) {\n        dial.setAnimated(false);\n        lblDial.setVisible(false);\n        if (stringStream.hasRemains()) {\n            outputPane.addLine(stringStream.getRemains());\n        }\n    }\n\n    @Override\n    public void processOutput(String output) {\n        stringStream.add(output);\n        while (stringStream.hasCompleted()) {\n            outputPane.addLine(stringStream.getNext());\n        }\n    }\n\n    @Override\n    public void processOutput(byte[] buffer, int offset, int length) {\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/tools/ExecUtils.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2017 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.text.tools;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.impl.local.LocalFile;\n\nimport java.io.IOException;\n\npublic class ExecUtils {\n\n\n    public static ProcessParams getBuilderParams(AbstractFile file) {\n        if (file == null || !file.exists() || !(file.getTopAncestor() instanceof LocalFile)) {\n            return null;\n        }\n        AbstractFile folder = file.getParent();\n        if (folder == null || !folder.exists()) {\n            return null;\n        }\n        if (\"py\".equalsIgnoreCase(file.getExtension())) {\n            return new ProcessParams(folder, \"python \" + file.getName());\n        }\n        while (true) {\n            if (folderContainsBuilder(folder, \"Makefile\")) {\n                return new ProcessParams(folder, \"make\");\n            } else if (folderContainsBuilder(folder, \"make.builder\")) {\n                return new ProcessParams(folder, \"builder\");\n            } else if (folderContainsBuilder(folder, \"build.xml\")) {\n                return new ProcessParams(folder, \"ant\");\n            }\n            if (folder.isRoot()) {\n                return null;\n            }\n            folder = folder.getParent();\n        }\n    }\n\n    private static boolean folderContainsBuilder(AbstractFile folder, String fileName) {\n        try {\n            AbstractFile child = folder.getChild(fileName);\n            return child != null && child.exists();\n        } catch (IOException ignore) {}\n        return false;\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/tools/OnClickFileHandler.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2018 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.text.tools;\n\nimport com.mucommander.commons.file.AbstractFile;\n\npublic interface OnClickFileHandler {\n    void onClick(AbstractFile file, int line, int column);\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/tools/ProcessParams.java",
    "content": "package com.mucommander.ui.viewer.text.tools;\n\nimport com.mucommander.commons.file.AbstractFile;\n\npublic class ProcessParams {\n\n    public final AbstractFile folder;\n    public final String command;\n\n    ProcessParams(AbstractFile folder, String command) {\n        this.folder = folder;\n        this.command = command;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/utils/CodeFormatException.java",
    "content": "/*\n * This file is part of trolCommander, http://www.mucommander.com\n * Copyright (C) 2013-2014 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.viewer.text.utils;\n\nimport lombok.Getter;\n\n/**\n * @author Oleg Trifonov\n */\n@Getter\npublic class CodeFormatException extends Exception {\n    private final int line;\n    private final int row;\n\n    CodeFormatException(String message, int line, int row, Throwable cause) {\n        super(message, cause);\n        this.line = line;\n        this.row = row;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/ui/viewer/text/utils/CodeFormatter.kt",
    "content": "/*\r\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\r\n * Copyright (C) 2013-2025 Oleg Trifonov\r\n *\r\n * trolCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * trolCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\npackage com.mucommander.ui.viewer.text.utils\r\n\r\nimport com.google.gson.Gson\r\nimport com.google.gson.GsonBuilder\r\nimport com.google.gson.JsonParser\r\nimport com.google.gson.JsonSyntaxException\r\nimport org.w3c.dom.Document\r\nimport org.w3c.dom.ls.DOMImplementationLS\r\nimport org.xml.sax.InputSource\r\nimport org.xml.sax.SAXException\r\nimport org.xml.sax.SAXParseException\r\nimport java.io.IOException\r\nimport java.io.StringReader\r\nimport java.io.StringWriter\r\nimport javax.xml.parsers.DocumentBuilderFactory\r\nimport javax.xml.parsers.ParserConfigurationException\r\n\r\n/**\r\n * Created by trol on 18/09/14.\r\n * \r\n */\r\nobject CodeFormatter {\r\n    private val GSON: Gson = GsonBuilder().setPrettyPrinting().create()\r\n    private val JSON_PARSER = JsonParser()\r\n\r\n    @JvmStatic\r\n    @Throws(CodeFormatException::class)\r\n    fun formatXml(unformattedXml: String): String {\r\n        if (unformattedXml.trim { it <= ' ' }.isEmpty()) {\r\n            throw CodeFormatException(\"XML input is null or empty\", 0, 0, null)\r\n        }\r\n\r\n        try {\r\n            val document = parseXmlFile(unformattedXml)\r\n\r\n            val domImpl = document.implementation as DOMImplementationLS\r\n            val serializer = domImpl.createLSSerializer().apply {\r\n                domConfig.setParameter(\"format-pretty-print\", true)\r\n            }\r\n\r\n            val writer = StringWriter()\r\n            val output = domImpl.createLSOutput().apply {\r\n                encoding = \"UTF-8\"\r\n                characterStream = writer\r\n            }\r\n            serializer.write(document, output)\r\n            return writer.toString()\r\n        } catch (e: CodeFormatException) {\r\n            throw e\r\n        } catch (e: Exception) {\r\n            throw CodeFormatException(\"Failed to format XML: \" + e.message, 0, 0, e)\r\n        }\r\n    }\r\n\r\n    @Throws(CodeFormatException::class)\r\n    private fun parseXmlFile(src: String): Document {\r\n        try {\r\n            val dbf = DocumentBuilderFactory.newInstance().apply {\r\n                setFeature(\"http://apache.org/xml/features/disallow-doctype-decl\", true)\r\n                setFeature(\"http://xml.org/sax/features/external-general-entities\", false)\r\n                setFeature(\"http://xml.org/sax/features/external-parameter-entities\", false)\r\n                isNamespaceAware = true\r\n            }\r\n\r\n            val stream = InputSource(StringReader(src))\r\n            return dbf.newDocumentBuilder().parse(stream)\r\n        } catch (e: SAXParseException) {\r\n            throw CodeFormatException(e.message, e.lineNumber, e.columnNumber, e)\r\n        } catch (e: ParserConfigurationException) {\r\n            throw CodeFormatException(\"XML Parser configuration error: \" + e.message, 0, 0, e)\r\n        } catch (e: SAXException) {\r\n            throw CodeFormatException(\"Failed to parse XML: \" + e.message, 0, 0, e)\r\n        } catch (e: IOException) {\r\n            throw CodeFormatException(\"Failed to parse XML: \" + e.message, 0, 0, e)\r\n        }\r\n    }\r\n\r\n\r\n    @JvmStatic\r\n    @Throws(CodeFormatException::class)\r\n    fun formatJson(json: String): String? {\r\n        if (json.trim { it <= ' ' }.isEmpty()) {\r\n            throw CodeFormatException(\"JSON input is null or empty\", 0, 0, null)\r\n        }\r\n\r\n        try {\r\n            val el = JSON_PARSER.parse(json)\r\n            return GSON.toJson(el)\r\n        } catch (e: JsonSyntaxException) {\r\n            throw CodeFormatException(\"Invalid JSON syntax: \" + e.message, 0, 0, e)\r\n        } catch (e: Exception) {\r\n            throw CodeFormatException(\"Failed to format JSON: \" + e.message, 0, 0, e)\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "src/main/java/com/mucommander/ui/widgets/render/BasicComboBoxRenderer.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2025 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.ui.widgets.render;\n\nimport javax.swing.Icon;\nimport javax.swing.JLabel;\nimport javax.swing.JList;\nimport javax.swing.ListCellRenderer;\nimport javax.swing.border.Border;\nimport javax.swing.border.EmptyBorder;\nimport java.awt.Component;\nimport java.awt.Dimension;\n\n/**\n * Renderer for ComboBox\n *\n * @author Oleg Trifonov\n * Created on 05/01/14.\n */\npublic class BasicComboBoxRenderer<T> extends JLabel implements ListCellRenderer<T> {\n    /**\n     * An empty <code>Border</code>. This field might not be used. To change the\n     * <code>Border</code> used by this renderer directly set it using\n     * the <code>setBorder</code> method.\n     */\n    protected static final Border NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1);\n\n    @Override\n    public Component getListCellRendererComponent(JList<? extends T> list, T value, int index, boolean isSelected, boolean cellHasFocus) {\n        if (isSelected) {\n            setBackground(list.getSelectionBackground());\n            setForeground(list.getSelectionForeground());\n        } else {\n            setBackground(list.getBackground());\n            setForeground(list.getForeground());\n        }\n\n        setFont(list.getFont());\n\n        if (value instanceof Icon) {\n            setIcon((Icon) value);\n        } else {\n            setText(value == null ? \"\" : value.toString());\n        }\n        return this;\n    }\n\n\n    public BasicComboBoxRenderer() {\n        super();\n        setOpaque(true);\n        setBorder(NO_FOCUS_BORDER);\n    }\n\n    public Dimension getPreferredSize() {\n        Dimension size;\n\n        if (getText() == null || getText().isEmpty()) {\n            setText(\" \");\n            size = super.getPreferredSize();\n            setText(\"\");\n        } else {\n            size = super.getPreferredSize();\n        }\n\n        return size;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/updates/NightlyChecker.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2021 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.updates;\n\nimport com.mucommander.RuntimeConstants;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\n\nimport java.io.DataInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\npublic class NightlyChecker {\n\n    public static final String ROOT_URL = \"http://trolsoft.ru/content/soft/trolcommander/\";\n    private static final String BUILD_CODE_URL = ROOT_URL + \"nightly/buildcode\";\n\n    private static String getNightlyBuildCode() {\n        AbstractFile file = FileFactory.getFile(BUILD_CODE_URL);\n        if (file == null) {\n            return null;\n        }\n        try (InputStream is = file.getInputStream(); DataInputStream reader = new DataInputStream(is)) {\n            return reader.readUTF();\n        } catch (IOException e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    public static boolean hasUpdates() {\n        String current = RuntimeConstants.BUILD_CODE;\n        if (current == null) {\n            return false;\n        }\n        String nightly = getNightlyBuildCode();\n        if (nightly == null) {\n            return false;\n        }\n        try {\n            int currentCode = Integer.parseInt(current);\n            int nightlyCode = Integer.parseInt(nightly);\n            return nightlyCode > currentCode;\n        } catch (NumberFormatException e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/updates/SelfUpdateUtils.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2021 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.mucommander.updates;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.FilePermissions;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.process.ExecutionFinishListener;\nimport com.mucommander.process.ExecutorUtils;\nimport ru.trolsoft.utils.FileUtils;\n\nimport java.io.IOException;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\npublic class SelfUpdateUtils {\n\n    private static final String RESTARTER_LOCAL_PATH = \"restarter\";\n\n    public static boolean isUpdaterAvailable() {\n        return getRestarterJarPath() != null;\n    }\n\n    private static boolean prepareUpdater() {\n        return extractRestarter() && checkRestarter();\n    }\n\n    private static String execRestarter(boolean waitComplete, String... args) {\nSystem.out.println(\"restarter exec '\" + (args != null && args.length > 0 ? args[0] : \"\") + \"'\");\n        String jarPath = FileUtils.getJarPath();\n        AbstractFile root = FileFactory.getFile(jarPath);\n        AtomicBoolean done = new AtomicBoolean(false);\n        StringBuffer outStr = new StringBuffer();\n        ExecutionFinishListener finishListener = (exitCode, output) -> {\n            done.set(true);\n            outStr.append(output);\n        };\n        try {\n            if (args == null || args.length == 0) {\n                ExecutorUtils.executeAndGetOutput(jarPath + \"/\" + RESTARTER_LOCAL_PATH, root, finishListener);\n            } else {\n                String[] cmd = new String[args.length+1];\n                cmd[0] = jarPath + \"/\" + RESTARTER_LOCAL_PATH;\n                System.arraycopy(args, 0, cmd, 1, args.length);\n                ExecutorUtils.executeAndGetOutput(cmd, root, finishListener);\n            }\n        } catch (IOException | InterruptedException e) {\n            e.printStackTrace();\n            return null;\n        }\n        if (!waitComplete) {\n            return null;\n        }\n        for (int i = 0; i < 20; i++) {\n            if (done.get()) {\n                break;\n            }\n            try {\n                Thread.sleep(100);\n            } catch (InterruptedException ignore) {}\n        }\nSystem.out.println(\"RESTARTER RESULT '\" + outStr + \"'\");\n        return outStr.toString();\n    }\n\n\n    public static boolean checkRestarter() {\n        String out = execRestarter(true);\n        return out != null && out.startsWith(\"restarter\");\n    }\n\n    private static int getPid() {\n        String out = execRestarter(true, \"ppid\");\n        if (out == null) {\n            return -1;\n        }\n        try {\n            return Integer.parseInt(out.trim());\n        } catch (NumberFormatException e) {\n            e.printStackTrace();\n            return -1;\n        }\n    }\n\n    public static String getTcExecutionCommand() {\n        if (!OsFamily.getCurrent().isUnixBased()) {\n            return null;\n        }\n        int pid = getPid();\n        String out = execRestarter(true, \"ps -p \" + pid + \" -o args\");\n        if (out == null) {\n            return out;\n        }\n        if (out.toLowerCase().startsWith(\"args\")) {\n            return out.substring(4).trim();\n        }\n        return out;\n    }\n\n    public static void updateAndRestart() {\n        String cmd = getTcExecutionCommand();\n        new Thread(() -> execRestarter(false, \"update\", cmd)).start();\n    }\n\n\n    private static String getRestarterJarPath() {\n        switch (OsFamily.getCurrent()) {\n            case MAC_OS_X:\n                return \"/bin/macos/restarter\";\n            case LINUX:\n                return \"/bin/linux/restarter\";\n        }\n        return null;\n    }\n\n\n    public static boolean extractRestarter() {\n        String jarPath = FileUtils.getJarPath();\n        String outPath = jarPath + \"/\" + RESTARTER_LOCAL_PATH;\n        try {\n            FileUtils.copyFileFromJar(getRestarterJarPath(), outPath, true);\n            AbstractFile file = FileFactory.getFile(outPath);\n            if (file == null) {\n                return false;\n            }\n            file.changePermissions(FilePermissions.DEFAULT_EXECUTABLE_PERMISSIONS);\n            return true;\n        } catch (IOException e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/updates/VersionChecker.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.updates;\n\nimport com.mucommander.RuntimeConstants;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport lombok.Getter;\nimport lombok.extern.slf4j.Slf4j;\nimport org.xml.sax.Attributes;\nimport org.xml.sax.helpers.DefaultHandler;\n\nimport javax.xml.parsers.SAXParserFactory;\nimport java.io.InputStream;\n\n/**\n * Retrieves information about the latest release of trolCommander.\n * <p>\n * The {@link com.mucommander.RuntimeConstants#VERSION_URL} URL contains information\n * about the latest release of trolCommander:<br>\n * - date of latest release.<br>\n * - latest official version.<br>\n * - where to download the latest version from.<br>\n * This class is used to access that information and compare them with what is known\n * of the current one, making it possible to notify users of new releases.\n * <p>\n * Checking for new releases is a fairly straightforward process, and can be done\n * with a few lines of code:\n * <pre>\n * VersionChecker version;\n *\n * try {\n *     version = VersionChecker.getInstance();\n *     if(version.isNewVersionAvailable())\n *         System.out.println(\"A new version of trolCommander is available\");\n *     else\n *         System.out.println(\"You've got the latest trolCommander version\");\n *    }\n * catch(Exception e) {System.err.println(\"An error occurred.\");}\n * </pre>\n *\n * <p>\n * trolCommander is considered up to date if:<br>\n * - the {@link com.mucommander.RuntimeConstants#VERSION local version} is\n *   not smaller than the remote one.<br>\n * - the {@link com.mucommander.RuntimeConstants#BUILD_DATE local release date} is\n *   not smaller than the remote one.<br>\n * While comparing release dates seems a bit odd - after all, if a new version is release,\n * a new version number should be created. However, it's possible to download development\n * versions of the current release, and those might be updated almost daily. Comparing dates\n * makes it possible to automate the whole process without having to worry about out version\n * numbers growing silly.\n *\n * @author Maxence Bernard, Nicolas Rinaudo\n */\n@Slf4j\npublic class VersionChecker extends DefaultHandler {\n    // - XML structure ----------------------------------------------------------\n    // --------------------------------------------------------------------------\n    /** Root XML element. */\n    public static final String ROOT_ELEMENT    = \"trolcommander\";\n    /** Version XML element. */\n    public static final String VERSION_ELEMENT = \"latest_version\";\n    /** Download URL XML element. */\n    public static final String DOWNLOAD_URL_ELEMENT = \"download_url\";\n    /** JAR URL XML element. */\n    public static final String JAR_URL_ELEMENT = \"jar_url\";\n    /** Date XML element. */\n    public static final String DATE_ELEMENT    = \"release_date\";\n\n\n\n    // - XML parsing states -----------------------------------------------------\n    // --------------------------------------------------------------------------\n    /** Currently parsing the version tag. */\n    private static final int STATE_VERSION      = 1;\n    /** Currently parsing the download URL tag. */\n    private static final int STATE_DOWNLOAD_URL = 2;\n    /** Currently parsing the download URL tag. */\n    private static final int STATE_JAR_URL      = 3;\n    /** Currently parsing the date tag. */\n    private static final int STATE_DATE         = 4;\n    /** We're not quite sure what we're parsing. */\n    private static final int STATE_UNKNOWN      = 5;\n\n\n    /** Remote version number.\n     * -- GETTER --\n     *  Returns the version number of the latest trolCommander release.\n     */\n    @Getter\n    private String latestVersion;\n    /** Where to download the latest version.\n     * -- GETTER --\n     *  Returns the URL at which the latest version of trolCommander can be downloaded.\n     */\n    @Getter\n    private String downloadURL;\n    /** URL to the latest JAR file.\n     * -- GETTER --\n     *  Returns the URL to the latest JAR file, <code>null</code> if not available.\n     */\n    @Getter\n    private String jarURL;\n\n    /** Remote release date.\n     * The date at which the latest version of trolCommander has been released.\n     * <p>\n     * The date format is YYYYMMDD.\n     */\n    @Getter\n    private String releaseDate;\n    /** Current state the parser is in. */\n    private int state;\n\n\n    /**\n     * Creates a new version checker instance.\n     */\n    private VersionChecker() {}\n\n    /**\n     * Retrieves a description of the latest released version of trolCommander.\n     * @return              a description of the latest released version of trolCommander.\n     * @exception Exception thrown if any error happens while retrieving the remote version.\n     */\n    public static VersionChecker getInstance() throws Exception {\n        log.info(\"Opening connection to {}\", RuntimeConstants.VERSION_URL);\n\n        // Parses the remote XML file using UTF-8 encoding.\n        AbstractFile file = FileFactory.getFile(RuntimeConstants.VERSION_URL);\n        if (file == null) {\n            return null;\n        }\n        VersionChecker instance;\n        try (InputStream in = file.getInputStream()) {\n\t        try {\n\t            SAXParserFactory.newInstance().newSAXParser().parse(in, instance = new VersionChecker());\n\t        } catch(Exception e) {\n\t            log.debug(\"Failed to read version XML file at {}\", RuntimeConstants.VERSION_URL, e);\n\t            throw e;\n\t        }\n        }\n\n        // Makes sure we retrieved the information we were looking for.\n        // We're not checking the release date as older version of trolCommander\n        // didn't use it.\n        if (instance.latestVersion == null || instance.latestVersion.isEmpty() || instance.downloadURL == null   || instance.downloadURL.isEmpty()) {\n            throw new Exception();\n        }\n\n        return instance;\n    }\n\n\n    // - Remote version information ---------------------------------------------\n    // --------------------------------------------------------------------------\n    /**\n     * Checks whether the remote version is newer than the current one.\n     * @return <code>true</code> if the remote version is newer than the current one, <code>false</code> otherwise.\n     */\n    public boolean isNewVersionAvailable() {\n        String buildDate = RuntimeConstants.BUILD_DATE.replace(\"-\", \"\");\n        // If the local and remote versions are the same, compares release dates.\n        if (latestVersion.equals(RuntimeConstants.VERSION.trim().toLowerCase())) {\n            // This ensures backward compatibility - if the remote version file does not contain release date information, ignore it.\n            if (releaseDate.isEmpty()) {\n                return true;\n            }\n\n            // Checks whether the remote release date is later than the current release date.\n            return releaseDate.compareTo(buildDate) > 0;\n        }\n        return !releaseDate.isEmpty() && releaseDate.compareTo(buildDate) > 0;\n    }\n\n\n    /**\n     * Called when the XML document parsing has started.\n     */\n    @Override\n    public void startDocument() {\n        latestVersion = \"\";\n        downloadURL   = \"\";\n        jarURL        = \"\";\n        releaseDate   = \"\";\n    }\n\n    /**\n     * Notifies the parser of CDATA.\n     */\n    @Override\n    public void characters(char[] ch, int offset, int length) {\n        if (state == STATE_VERSION) {\n            latestVersion += new String(ch, offset, length);\n        } else if (state == STATE_DOWNLOAD_URL) {\n            downloadURL += new String(ch, offset, length);\n        } else if (state == STATE_JAR_URL) {\n            jarURL += new String(ch, offset, length);\n        } else if (state == STATE_DATE) {\n            releaseDate += new String(ch, offset, length);\n        }\n    }\n\n    /**\n     * Notifies the parser that a new tag is starting.\n     */\n    @Override\n    public void startElement(String uri, String localName, String qName, Attributes attributes) {\n        // Checks whether we know the tag and updates the current state.\n        switch (qName) {\n            case VERSION_ELEMENT:\n                state = STATE_VERSION;\n                break;\n            case DOWNLOAD_URL_ELEMENT:\n                state = STATE_DOWNLOAD_URL;\n                break;\n            case JAR_URL_ELEMENT:\n                state = STATE_JAR_URL;\n                break;\n            case DATE_ELEMENT:\n                state = STATE_DATE;\n                break;\n            default:\n                state = STATE_UNKNOWN;\n                break;\n        }\n    }\n\n    /**\n     * Notifies the parser that the current element is finished.\n     */\n    @Override\n    public void endElement(String uri, String localName, String qName) {state = STATE_UNKNOWN;}\n\n    /**\n     * Notifies the parser that XML parsing is finished.\n     */\n    @Override\n    public void endDocument() {\n        // Make sure we're not keep meaningless whitespace characters in the data.\n        latestVersion = latestVersion.toLowerCase().trim();\n        downloadURL   = downloadURL.trim();\n        jarURL        = jarURL.trim();\n        if (jarURL.isEmpty()) {\n            jarURL = null;\n        }\n        releaseDate   = releaseDate.trim();\n\n        // Logs the data if in debug mode.\n        log.debug(\"download URL: {}\", downloadURL);\n        log.debug(\"jar URL: {}\", jarURL);\n        log.debug(\"latestVersion: {}\", latestVersion);\n        log.debug(\"releaseDate: {}\", releaseDate);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/utils/Convert.java",
    "content": "package com.mucommander.utils;\n\nimport java.text.DecimalFormat;\n\n/**\n * Created by snouhaud on 25/05/15.\n */\npublic class Convert {\n    private static final String[] UNITS = new String[] { \"B\", \"kB\", \"MB\", \"GB\", \"TB\" };\n\n    public static String readableFileSize(long size) {\n        if (size <= 0) {\n            return \"0\";\n        }\n        int digitGroups = (int) (Math.log10(size)/Math.log10(1024));\n        return new DecimalFormat(\"#,##0.#\").format(size/Math.pow(1024, digitGroups)) + \" \" + UNITS[digitGroups];\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/utils/FileIconsCache.kt",
    "content": "/*\r\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\r\n * Copyright (C) 2013-2016 Oleg Trifonov\r\n *\r\n * trolCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * trolCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\npackage com.mucommander.utils\r\n\r\nimport com.mucommander.commons.file.AbstractFile\r\nimport com.mucommander.commons.file.FileFactory\r\nimport com.mucommander.commons.file.FileURL\r\nimport com.mucommander.ui.icon.FileIcons\r\nimport ru.trolsoft.macosx.RetinaImageIcon\r\nimport java.awt.GraphicsEnvironment\r\nimport java.awt.Image\r\nimport java.net.MalformedURLException\r\nimport java.util.*\r\nimport javax.swing.Icon\r\nimport javax.swing.ImageIcon\r\nimport kotlin.concurrent.Volatile\r\n\r\n/**\r\n * Created on 07.01.15.\r\n * @author Oleg Trifonov\r\n * \r\n * Cache of system file icons\r\n */\r\nclass FileIconsCache {\r\n    private val icons: MutableMap<String?, Icon?> = HashMap<String?, Icon?>()\r\n    private val files = LinkedList<String?>()\r\n\r\n    /**\r\n     * Get icon from cache or get it from system and add to cache\r\n     */\r\n    fun getIcon(file: AbstractFile): Icon {\r\n        val path = file.absolutePath\r\n        val result = icons[path]\r\n        if (result != null) {\r\n            // move record to top\r\n            files.remove(path)\r\n            files.addFirst(path)\r\n            return result\r\n        }\r\n        return addIcon(file, path)\r\n    }\r\n\r\n    fun getIcon(path: String?): Icon {\r\n        val result = icons[path]\r\n        if (result != null) {\r\n            // move record to top\r\n            files.remove(path)\r\n            files.addFirst(path)\r\n            return result\r\n        }\r\n        var file: AbstractFile? = null\r\n        try {\r\n            file = FileFactory.getFile(FileURL.getFileURL(path))\r\n        } catch (e: MalformedURLException) {\r\n            e.printStackTrace()\r\n        }\r\n        return addIcon(file, path)\r\n    }\r\n\r\n\r\n    fun getImageIcon(file: AbstractFile): Image? =\r\n        when (val icon = getIcon(file)) {\r\n            is RetinaImageIcon -> icon.getImage()\r\n            is ImageIcon -> icon.getImage()\r\n            else -> iconToImage(icon)\r\n        }\r\n\r\n    /**\r\n     * Request file icon from OS\r\n     */\r\n    private fun loadIcon(file: AbstractFile?): Icon =\r\n        FileIcons.getFileIcon(file)\r\n\r\n\r\n    /**\r\n     * Loads icon and adds it to cache\r\n     * @param path absolute path of file\r\n     * @return loaded icon\r\n     */\r\n    private fun addIcon(file: AbstractFile?, path: String?): Icon {\r\n        val icon = loadIcon(file)\r\n        icons[path] = icon\r\n        files.addFirst(path)\r\n\r\n        // remove oldest record if the cache is full\r\n        if (files.size > CACHE_SIZE) {\r\n            icons.remove(files.removeLast())\r\n        }\r\n        return icon\r\n    }\r\n\r\n\r\n    fun clear() {\r\n        icons.clear()\r\n        files.clear()\r\n    }\r\n\r\n    companion object {\r\n        /**\r\n         * Default cache size\r\n         */\r\n        private const val CACHE_SIZE = 1000\r\n\r\n        @JvmStatic\r\n        @Volatile\r\n        var instance: FileIconsCache? = null\r\n            get() {\r\n                if (field == null) {\r\n                    synchronized(FileIconsCache::class.java) {\r\n                        if (field == null) {\r\n                            field = FileIconsCache()\r\n                        }\r\n                    }\r\n                }\r\n                return field\r\n            }\r\n            private set\r\n\r\n\r\n        private fun iconToImage(icon: Icon): Image {\r\n            val w = icon.iconWidth\r\n            val h = icon.iconHeight\r\n            val ge = GraphicsEnvironment.getLocalGraphicsEnvironment()\r\n            val gd = ge.defaultScreenDevice\r\n            val gc = gd.defaultConfiguration\r\n            val image = gc.createCompatibleImage(w, h)\r\n            val g = image.createGraphics()\r\n            icon.paintIcon(null, g, 0, 0)\r\n            g.dispose()\r\n            return image\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/com/mucommander/utils/TcLogging.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.utils;\n\nimport ch.qos.logback.classic.Level;\nimport ch.qos.logback.classic.LoggerContext;\nimport ch.qos.logback.classic.spi.ILoggingEvent;\nimport ch.qos.logback.classic.spi.StackTraceElementProxy;\nimport ch.qos.logback.core.*;\nimport ch.qos.logback.core.encoder.LayoutWrappingEncoder;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\nimport com.mucommander.ui.dialog.debug.DebugConsoleAppender;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\n\n/**\n * This class manages logging issues within trolCommander\n *\n * @author Maxence Bernard, Arik Hadas\n */\npublic class TcLogging {\n\n    /**\n     * Levels of log printings\n     */\n    public enum LogLevel {\n        OFF,\n        ERROR,\n        WARNING,\n        INFO,\n        CONFIG,\n        DEBUG,\n        FINER,\n        TRACE;\n\n        /**\n         * This method maps logback levels to trolCommander log levels\n         *\n         * @param logbackLevel logback log level\n         * @return <code>LogLevel</code> corresponding to the given logback log level\n         */\n        public static LogLevel valueOf(Level logbackLevel) {\n            return switch (logbackLevel.toInt()) {\n                case Level.OFF_INT -> LogLevel.OFF;\n                case Level.ERROR_INT -> LogLevel.ERROR;\n                case Level.WARN_INT -> LogLevel.WARNING;\n                case Level.INFO_INT -> LogLevel.INFO;\n                case Level.DEBUG_INT -> LogLevel.DEBUG;\n                case Level.TRACE_INT -> LogLevel.TRACE;\n                default -> LogLevel.OFF;\n            };\n        }\n\n        /**\n         * This method maps trolCommander log levels to logback levels\n         *\n         * @return logback level corresponding to this <code>LogLevel</code>\n         */\n        public Level toLogbackLevel() {\n            return switch (this) {\n                case ERROR -> Level.ERROR;\n                case WARNING -> Level.WARN;\n                case INFO, CONFIG -> Level.INFO;\n                case DEBUG, FINER -> Level.DEBUG;\n                case TRACE -> Level.TRACE;\n                default -> Level.OFF;\n            };\n        }\n    }\n\n    /**\n     * Appender that writes log printings to the standard console\n     */\n    private static ConsoleAppender<ILoggingEvent> consoleAppender;\n\n    /**\n     * Appender that writes log printings to the debug console dialog\n     */\n    private static DebugConsoleAppender debugConsoleAppender;\n\n    /**\n     * Sets the level of all muCommander loggers.\n     *\n     * @param level the new log level\n     */\n    private static void updateLogLevel(LogLevel level) {\n        ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);\n        logger.setLevel(level.toLogbackLevel());\n    }\n\n    /**\n     * Returns the log level, in mucommander terms, that match the level of a given logback logging event\n     *\n     * @param loggingEvent logback logging event\n     * @return log level, in mucommander terms, that match the level of the given logback logging event\n     */\n    public static LogLevel getLevel(ILoggingEvent loggingEvent) {\n        return LogLevel.valueOf(loggingEvent.getLevel());\n    }\n\n    /**\n     * Returns the current log level used by all <code>org.slf4j</code> loggers.\n     *\n     * @return the current log level used by all <code>org.slf4j</code> loggers.\n     */\n    public static LogLevel getLogLevel() {\n        return LogLevel.valueOf(TcConfigurations.getPreferences().getVariable(TcPreference.LOG_LEVEL, TcPreferences.DEFAULT_LOG_LEVEL));\n    }\n\n    /**\n     * Sets the new log level to be used by all <code>org.slf4j</code> loggers, and persists it in the\n     * application preferences.\n     *\n     * @param level the new log level to be used by all <code>org.slf4j</code> loggers.\n     */\n    public static void setLogLevel(LogLevel level) {\n        TcConfigurations.getPreferences().setVariable(TcPreference.LOG_LEVEL, level.toString());\n        updateLogLevel(level);\n    }\n\n    public static DebugConsoleAppender getDebugConsoleAppender() {\n        return debugConsoleAppender;\n    }\n\n    public static ConsoleAppender<ILoggingEvent> getConsoleAppender() {\n        return consoleAppender;\n    }\n\n    public static void configureLogging() {\n        // Get root logger\n        ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);\n\n        // we are not interested in auto-configuration\n        LoggerContext loggerContext = rootLogger.getLoggerContext();\n        loggerContext.reset();\n\n        // Remove default appenders\n        rootLogger.detachAndStopAllAppenders();\n\n        // and add custom\n        consoleAppender = createConsoleAppender(loggerContext, new CustomLoggingLayout(OsFamily.getCurrent().isUnixBased()));\n        debugConsoleAppender = createDebugConsoleAppender(loggerContext, new CustomLoggingLayout(false));\n\n        if (!\"false\".equals(System.getProperty(\"log.stdout\"))) {\n            rootLogger.addAppender(consoleAppender);\n        }\n        rootLogger.addAppender(debugConsoleAppender);\n\n        // Set the log level to the value defined in the configuration\n        updateLogLevel(getLogLevel());\n    }\n\n    private static ConsoleAppender<ILoggingEvent> createConsoleAppender(LoggerContext loggerContext, Layout<ILoggingEvent> layout) {\n        ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<>();\n\n        LayoutWrappingEncoder<ILoggingEvent> encoder = new LayoutWrappingEncoder<>();\n        encoder.setContext(loggerContext);\n        encoder.setLayout(layout);\n        encoder.start();\n\n        consoleAppender.setContext(loggerContext);\n        consoleAppender.setEncoder(encoder);\n        consoleAppender.start();\n\n        return consoleAppender;\n    }\n\n    private static DebugConsoleAppender createDebugConsoleAppender(LoggerContext loggerContext, Layout<ILoggingEvent> layout) {\n        DebugConsoleAppender debugConsoleAppender = new DebugConsoleAppender(layout);\n\n        debugConsoleAppender.setContext(loggerContext);\n        debugConsoleAppender.start();\n\n        return debugConsoleAppender;\n    }\n\n    private static class CustomLoggingLayout extends LayoutBase<ILoggingEvent> {\n        private final boolean colored;\n\n        CustomLoggingLayout(boolean colored) {\n            this.colored = colored;\n        }\n\n        private final static SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss.SSS\");\n\n        private static final String RESET = \"\\u001B[0m\";\n        private static final String RED = \"\\u001B[31m\";\n        private static final String YELLOW = \"\\u001B[33m\";\n        private static final String GREEN = \"\\u001B[32m\";\n        private static final String MAGENTA = \"\\u001B[35m\";\n        private static final String CYAN = \"\\u001B[36m\";\n        private static final String BLUE = \"\\u001B[34m\";\n\n        private static final String BOLD_RED = \"\\u001B[1;31m\";\n        private static final String BOLD_YELLOW = \"\\u001B[1;33m\";\n        private static final String BOLD_GREEN = \"\\u001B[1;32m\";\n        private static final String BOLD_WHITE = \"\\u001B[1;37m\";\n        private static final String BOLD_CYAN = \"\\u001B[1;36m\";\n        private static final String BOLD_BLUE = \"\\u001B[1;34m\";\n\n        private static final String HI_RED = \"\\u001B[0;91m\";\n        private static final String HI_GREEN = \"\\u001B[0;92m\";\n        private static final String HI_BLUE = \"\\u001B[0;94m\";\n        private static final String HI_WHITE = \"\\u001B[0;97m\";\n\n        private static final String BOLD_HI_WHITE = \"\\u001B[1;97m\";\n\n\n        public String doLayout(ILoggingEvent event) {\n            StackTraceElement stackTraceElement = event.getCallerData()[0];\n\n            StringBuilder sb = new StringBuilder(128);\n            sb.append(\"[\");\n            if (colored) sb.append(HI_BLUE);\n            sb.append(SIMPLE_DATE_FORMAT.format(new Date(event.getTimeStamp())));\n            if (colored) sb.append(RESET);\n            sb.append(\"] \");\n            if (colored) sb.append(getColorForLevel(getLevel(event)));\n            sb.append(getLevel(event));\n            if (colored) {\n                while (sb.length() < 50) {\n                    sb.append(\" \");\n                }\n            }\n\n            sb.append(\" \");\n            if (colored) sb.append(MAGENTA);\n            sb.append(stackTraceElement.getFileName());\n            if (colored) sb.append(RESET);\n            sb.append(\":\");\n            if (colored) sb.append(HI_WHITE);\n            sb.append(stackTraceElement.getLineNumber());\n            if (colored) sb.append(RESET);\n            sb.append(\"#\");\n            if (colored) sb.append(CYAN);\n            sb.append(stackTraceElement.getMethodName());\n            sb.append(\" \");\n            if (colored) {\n                while (sb.length() < 130) {\n                    sb.append(\" \");\n                }\n            }\n            if (colored) sb.append(BOLD_WHITE);\n            sb.append(event.getFormattedMessage());\n            if (colored) sb.append(RESET);\n            sb.append(CoreConstants.LINE_SEPARATOR);\n\n            if (event.getThrowableProxy() != null) {\n                if (colored) {\n                    sb.append(HI_RED);\n                }\n                convertThrowable(sb, event.getThrowableProxy(), \"\");\n                if (colored) {\n                    sb.append(RESET);\n                }\n            }\n            return sb.toString();\n        }\n\n        private String getColorForLevel(LogLevel level) {\n            return switch (level) {\n                case ERROR -> BOLD_RED;\n                case WARNING -> BOLD_YELLOW;\n                case INFO, CONFIG -> BOLD_GREEN;\n                case DEBUG -> BOLD_CYAN;\n                case FINER, TRACE -> BOLD_CYAN;\n                default -> \"\";\n            };\n        }\n\n        private void convertThrowable(StringBuilder sb, ch.qos.logback.classic.spi.IThrowableProxy throwableProxy, String prefix) {\n            sb.append(prefix);\n            sb.append(throwableProxy.getClassName());\n            sb.append(\": \");\n            sb.append(throwableProxy.getMessage());\n            sb.append(CoreConstants.LINE_SEPARATOR);\n\n            StackTraceElementProxy[] stackTraceElementProxies = throwableProxy.getStackTraceElementProxyArray();\n            if (stackTraceElementProxies != null) {\n                for (StackTraceElementProxy step : stackTraceElementProxies) {\n                    sb.append(prefix);\n                    sb.append(\"\\t\");\n                    String sstep = step.toString();\n                    if (colored) {\n                        sstep = sstep.replace(\"(\", BOLD_BLUE +\"(\" + MAGENTA).replace(\")\", BOLD_BLUE + \")\" + HI_RED);\n                    }\n                    sb.append(sstep);\n                    sb.append(CoreConstants.LINE_SEPARATOR);\n                }\n            }\n\n            ch.qos.logback.classic.spi.IThrowableProxy[] suppressed = throwableProxy.getSuppressed();\n            if (suppressed != null) {\n                for (ch.qos.logback.classic.spi.IThrowableProxy suppressedThrowable : suppressed) {\n                    sb.append(prefix);\n                    sb.append(\"\\t... suppressed exception(s):\");\n                    sb.append(CoreConstants.LINE_SEPARATOR);\n                    convertThrowable(sb, suppressedThrowable, prefix + \"\\t\");\n                }\n            }\n\n            ch.qos.logback.classic.spi.IThrowableProxy cause = throwableProxy.getCause();\n            if (cause != null) {\n                sb.append(prefix);\n                sb.append(\"\\t... caused by:\");\n                sb.append(CoreConstants.LINE_SEPARATOR);\n                convertThrowable(sb, cause, prefix);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/utils/text/CustomDateFormat.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.utils.text;\n\nimport com.mucommander.commons.conf.ConfigurationEvent;\nimport com.mucommander.commons.conf.ConfigurationListener;\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\nimport com.mucommander.conf.TcPreferences;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\n\n\n/**\n * CustomDateFormat allows custom date formatting, according to the date format stored in the preferences.\n *\n * @author Maxence Bernard\n */\npublic class CustomDateFormat implements ConfigurationListener {\n\n    /** Singleton instance */\n    private static CustomDateFormat singleton;\n\n    /** Custom SimpleDateFormat instance */\n    private static SimpleDateFormat dateFormat;\n\n\n    /**\n     * Creates a new CustomDateFormat instance.\n     */\n    private CustomDateFormat() {}\n\n\n    /**\n     * Forces static fields to be initialized\n     */\n    public static void init() {\n        // create a singleton instance and keep it as a static member of this class.\n        // Not doing it so would cause the garbage collector to GC it as MuConfiguration holds\n        // weak references of its listeners.\n        singleton = new CustomDateFormat();\n        TcConfigurations.addPreferencesListener(singleton);\n\n        dateFormat = createDateFormat();\n    }\n\n\n    /**\n     * Replace the default '/' separator in the given format string, by the given custom separator.\n     *\n     * @return the given format string with '/' separator characters replaced by the given separator character.\n     */\n    public static String replaceDateSeparator(String dateFormatString, String separator) {\n        if (separator == null || separator.equals(\"/\")) {\n            return dateFormatString;\n        } else {\n        \t  return dateFormatString.replace(\"/\",separator);\n        }\n    }\n\n\n    /**\n     * Returns the date format stored in the preferences and used by this class to format dates.\n     * The format of the returned string is the one used by the <code>java.text.SimpleDateFormat</code> class. \n     */\n    public static String getDateFormatString() {\n        return replaceDateSeparator(\n        \t\tTcConfigurations.getPreferences().getVariable(TcPreference.DATE_FORMAT, TcPreferences.DEFAULT_DATE_FORMAT),\n        \t\tTcConfigurations.getPreferences().getVariable(TcPreference.DATE_SEPARATOR, TcPreferences.DEFAULT_DATE_SEPARATOR))\n        + \" \" + TcConfigurations.getPreferences().getVariable(TcPreference.TIME_FORMAT, TcPreferences.DEFAULT_TIME_FORMAT);\n    }\n\n\n    /**\n     * Forces CustomDateFormat to update the date format by looking it up in the preferences.\n     */\n    public static synchronized void updateDateFormat() {\n        dateFormat = createDateFormat();\n    }\n\n\n    /**\n     * Creates and returns a SimpleDateFormat instance using the date format stored in the preferences.\n     */\n    private static SimpleDateFormat createDateFormat() {\n        return new SimpleDateFormat(getDateFormatString());\n    }\n\t\n\t\n    /**\n     * Formats the given with custom date format and returns a formatted date string. \n     *\n     * @return a formatted string representing the given date.\n     */\n    private static synchronized String format(Date date) {\n        // Calls to SimpleDateFormat MUST be synchronized otherwise it will start throwing exceptions (verified that!),\n        // that is why this method is synchronized.\n        // Quote from SimpleDateFormat's Javadoc: \"Date formats are not synchronized. It is recommended to create\n        // separate format instances for each thread. If multiple threads access a format concurrently, \n        // it must be synchronized externally.\"\n        return dateFormat.format(date);\n    }\n\n    /**\n     * Formats the given timestamp with custom date format and returns a formatted date string.\n     *\n     * @return a formatted string representing the given date.\n     */\n    public static String format(long date) {\n        return format(new Date(date));\n    }\n\t\n\n\n    /**\n     * Listens to some configuration variables.\n     */\n    @Override\n    public void configurationChanged(ConfigurationEvent event) {\n        String var = event.getVariable();\n\n        if (var.equals(TcPreferences.TIME_FORMAT) || var.equals(TcPreferences.DATE_FORMAT) || var.equals(TcPreferences.DATE_SEPARATOR))\n            updateDateFormat();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/utils/text/DurationFormat.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.utils.text;\n\n/**\n * DurationFormat formats duration in milliseconds into localized string representations.\n *\n * @author Maxence Bernard\n */\npublic class DurationFormat {\n\n    private final static String SECONDS_KEY = \"duration.seconds\";\n    private final static String MINUTES_KEY = \"duration.minutes\";\n    private final static String HOURS_KEY = \"duration.hours\";\n    private final static String DAYS_KEY = \"duration.days\";\n    private final static String MONTHS_KEY = \"duration.months\";\n    private final static String YEARS_KEY = \"duration.years\";\n\n    private final static String INFINITE = Translator.get(\"duration.infinite\");\n\n    private final static int SECONDS_IN_MINUTE = 60;\n    private final static int SECONDS_IN_HOUR = 3600;\n    private final static int SECONDS_IN_DAY = 86400;\n    private final static int SECONDS_IN_MONTH = 2592000;\n    private final static int SECONDS_IN_YEAR = 31104000;\n\n    private static final String[] KEYS = new String[] {\n            YEARS_KEY, MONTHS_KEY, DAYS_KEY, HOURS_KEY, MINUTES_KEY\n    };\n    private static final int[] SECONDS = new int[] {\n            SECONDS_IN_YEAR, SECONDS_IN_MONTH, SECONDS_IN_DAY, SECONDS_IN_HOUR, SECONDS_IN_MINUTE\n    };\n\n\n\n    public static String format(long durationMs) {\n        if (durationMs/1000 > Integer.MAX_VALUE) {\n            return INFINITE;\n        }\n\n        int remainderSec = Math.round(((float)durationMs)/1000);\n        StringBuilder s = new StringBuilder();\n\n\n        for (int i = 0; i < SECONDS.length; i++) {\n            int n = remainderSec/SECONDS[i];\n            if (n > 0) {\n                if (!s.isEmpty()) {\n                    s.append(' ');\n                }\n                s.append(Translator.get(KEYS[i], \"\"+n));\n                remainderSec = remainderSec % SECONDS[i];\n            }\n        }\n\n        // Don't add second part if equal to 0, unless this is the only part\n        if (remainderSec > 0 || s.isEmpty()) {\n            if (remainderSec == 0) {\n//                if (s.length() > 0) {\n//                    s.delete(0, s.length() - 1);\n//                }\n                s.append('<').append(Translator.get(SECONDS_KEY, \"1\"));\n                //s = \"<\" + Translator.get(SECONDS_KEY, \"1\");\n            } else {\n                s.append(s.isEmpty() ? \"\" : \" \").append(Translator.get(SECONDS_KEY, \"\" + remainderSec));\n            }\n        }\n        return s.toString();\n    }\n\n\n    /**\n     * Returns the infinite symbol string.\n     */\n    public static String getInfiniteSymbol() {\n        return INFINITE;\n    }\n    \n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/utils/text/SizeFormat.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\npackage com.mucommander.utils.text;\n\nimport java.text.DecimalFormat;\nimport java.text.NumberFormat;\n\n\n/**\n * SizeFormat formats byte sizes into localized string representations.\n *\n * @author Maxence Bernard.\n */\npublic class SizeFormat {\n\n    /** Bitmask for short digits, e.g. \"15\" */\n    public final static int DIGITS_SHORT = 1;\n    /** Bitmask for medium digits, e.g. \"15,2\" */\n    public final static int DIGITS_MEDIUM = 2;\n    /** Bitmask for full digits, e.g. \"15,204,405\" */\n    public final static int DIGITS_FULL = 4;\n\n    /** Bitmask for no unit string */\n    public final static int UNIT_NONE = 0;\n    /** Bitmask for short unit string, e.g. \"b\" */\n    public final static int UNIT_SHORT = 8;\n    /** Bitmask for short unit string, e.g. \"bytes\" */\n    public final static int UNIT_LONG = 16;\n\n    /** Byte unit */\n    public final static int BYTE_UNIT = 0;\n    /** Kilobyte unit */\n    public final static int KILOBYTE_UNIT = 1;\n    /** Megabyte unit */\n    public final static int MEGABYTE_UNIT = 2;\n    /** Gigabyte unit */\n    public final static int GIGABYTE_UNIT = 3;\n    /** Terabyte unit */\n    public final static int TERABYTE_UNIT = 4;\n\n    /** Bitmask to add '/s' (per second) to the returned String */\n    public final static int UNIT_SPEED = 32;\n    \n    /** Bitmask to include a space character to separate the digits and unit parts */\n    public final static int INCLUDE_SPACE = 64;\n\n    /** Bitmask to round any size &lt; 1KB to 1KB (except 0 which will be 0 KB) */\n    public final static int ROUND_TO_KB = 128;\n\n    /** One kilobyte: 2^10 */\n    private final static int KB_1 = 1024;\n    /** Ten kilobytes: (2^10)*10 */\n    private final static int KB_10 = 10240;\n    /** One megabyte: 2^20 */\n    private final static int MB_1 = 1048576;\n    /** Ten megabytes: (2^20)*10 */\n    private final static int MB_10 = 10485760;\n    /** One gigabyte: 2^30 */\n    private final static int GB_1 = 1073741824;\n    /** Ten gigabytes: (2^10)*10 */\n    private final static long GB_10 = 10737418240L;\n    /** One terabyte: 2^40 */\n    private final static long TB_1 = 1099511627776L;\n    /** Ten terabytes: (2^40)*10 */\n    private final static long TB_10 = 10995116277760L;\n\n    /** DecimalFormat instance to localize thousands separators */\n    private final static DecimalFormat DECIMAL_FORMAT = (DecimalFormat)NumberFormat.getInstance();\n\n    /** Localized decimal separator */\n    private final static String DECIMAL_SEPARATOR = \"\"+DECIMAL_FORMAT.getDecimalFormatSymbols().getDecimalSeparator();\n\n\n    private final static String BYTE = Translator.get(\"unit.byte\");\n    private final static String BYTES = Translator.get(\"unit.bytes\");\n    private final static String B = Translator.get(\"unit.bytes_short\");\n    private final static String KB = Translator.get(\"unit.kb\");\n    private final static String MB = Translator.get(\"unit.mb\");\n    private final static String GB = Translator.get(\"unit.gb\");\n    private final static String TB = Translator.get(\"unit.tb\");\n\n    private final static String SPEED_KEY = \"unit.speed\";\n\n\n    /**\n     * Returns a String representation of the given byte size.\n     *\n     * @param size the size to format\n     * @param format format bitmask, see constant fields for allowed values\n     * @return a String representation of the given byte size\n     */\n    public static String format(long size, int format) {\n        if (size < 0) {\n            return \"?\";\n        }\n\n        String digitsString;\n        String unitString;\n\t\t\n        // Whether the unit string should be long or not\n        boolean unitLong = (format&UNIT_LONG)!=0;\n        // Whether the unit string should be short or not\n        boolean unitShort = (format&UNIT_SHORT)!=0;\n        // Whether the unit string should be short or not\n        boolean noUnit = !(unitLong||unitShort);\n        // Whether the digits string should be short or not\n        boolean digitsShort = (format&DIGITS_SHORT)!=0;\n        // Whether any size < 1024 bytes should be rounded to a kilobyte\n        boolean roundToKb = (format&ROUND_TO_KB)!=0;\n\t\t\n        // size < 1KB\n        if (size < KB_1) {\n            if (roundToKb) {\n                // Note: ROUND_TO_KB must have precedence over DIGITS_FULL\n                digitsString = size == 0 ? \"0\" : \"1\";\n                unitString = noUnit ? \"\" : KB;\n            } else {\n                digitsString = \"\" + size;\n                unitString = unitLong?(size<=1?BYTE:BYTES):unitShort?B:\"\";\n            }\n        }\n        else if ((format & DIGITS_FULL)!=0) {\n            // DecimalFormat localizes thousands separators\n\n            // Calls to DecimalFormat must be synchronized.\n            // Quote from DecimalFormat's Javadoc: \"Decimal formats are generally not synchronized. It is recommended \n            // to create separate format instances for each thread. If multiple threads access a format concurrently,\n            // it must be synchronized externally.\"\n            synchronized(DECIMAL_FORMAT) {\n                digitsString = DECIMAL_FORMAT.format(size);\n            }\n            unitString = unitLong ? BYTES : unitShort ? B :\"\";\n        }\n        else {\n            // size < 10KB\t-> \"9.6 KB\"\n            if (size < KB_10 && !digitsShort) {\n                int nKB = (int)size/KB_1;\n                digitsString = nKB + DECIMAL_SEPARATOR+(int)((size-nKB*KB_1)/(float)KB_1*10);\n                unitString = noUnit ? \"\" : KB;\n            }\n            // size < 1MB -> \"436 KB\"\n            else if (size < MB_1) {\n                digitsString = \"\" + size/KB_1;\n                unitString = noUnit ? \"\" : KB;\n            }\n            // size < 10MB -> \"4.3 MB\"\n            else if(size < MB_10 && !digitsShort) {\n                int nMB = (int)size/MB_1;\n                digitsString = nMB + DECIMAL_SEPARATOR+(int)((size-nMB*MB_1)/(float)MB_1*10);\n                unitString = noUnit? \"\" : MB;\n            }\n            // size < 1GB -> \"548 MB\"\n            else if(size < GB_1) {\n                digitsString = \"\" + size/MB_1;\n                unitString = noUnit ? \"\" : MB;\n            }\t\n            // size < 10GB -> \"4.8 GB\"\n            else if(size < GB_10 && !digitsShort) {\n                long nGB = size / GB_1;\n                digitsString = nGB + DECIMAL_SEPARATOR+(int)((size-nGB*GB_1)/(double)GB_1*10);\n                unitString = noUnit ? \"\" : GB;\n            }\n            // size < 1TB -> \"216 GB\"\n            else if(size < TB_1) {\n                digitsString = \"\" + size/GB_1;\n                unitString = noUnit ? \"\" : GB;\n            }\n            // size < 10TB -> \"4.8 TB\"\n            else if(size < TB_10 && !digitsShort) {\n                long nTB = size/TB_1;\n                digitsString = nTB + DECIMAL_SEPARATOR+(int)((size-nTB*TB_1)/(double)TB_1*10);\n                unitString = noUnit ? \"\" : TB;\n            }\n            else {\n                // Will I live long enough to see files that large ??\n                digitsString = \"\" + size/TB_1;\n                unitString = noUnit ? \"\" : TB;\n            }\n        }\n\n        // Add localized '/s' to unit string if unit is speed\n        if ((format & UNIT_SPEED) != 0) {\n            unitString = Translator.get(SPEED_KEY, unitString);\n        }\n\n        return digitsString + ((format&INCLUDE_SPACE) !=0 ? \" \" : \"\") + unitString;\n    }\n    \n\n\n    public static String getUnitString(int unit, boolean speedUnit) {\n// TODO use array\n        String unitString;\n\n        switch(unit) {\n            case BYTE_UNIT:\n                unitString = B;\n                break;\n            case KILOBYTE_UNIT:\n                unitString = KB;\n                break;\n            case MEGABYTE_UNIT:\n                unitString = MB;\n                break;\n            case GIGABYTE_UNIT:\n                unitString = GB;\n                break;\n            case TERABYTE_UNIT:\n                unitString = TB;\n                break;\n            default:\n                return \"\";\n        }\n\n        return speedUnit?Translator.get(SPEED_KEY, unitString):unitString;\n    }\n\n\n    /**\n     * Returns the size in bytes of the given byte unit, e.g. <code>1024</code> for {@link #KILOBYTE_UNIT}.\n     *\n     * @param unit a unit constant, see constant fields for allowed values\n     * @return the size in bytes of the given byte unit\n     */\n    public static long getUnitBytes(int unit) {\n        long bytes;\n// TODO use array\n        switch(unit) {\n            case BYTE_UNIT:\n                bytes = 1;\n                break;\n            case KILOBYTE_UNIT:\n                bytes = KB_1;\n                break;\n            case MEGABYTE_UNIT:\n                bytes = MB_1;\n                break;\n            case GIGABYTE_UNIT:\n                bytes = GB_1;\n                break;\n            case TERABYTE_UNIT:\n                bytes = TB_1;\n                break;\n            default:\n                return 0;\n        }\n\n        return bytes;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/utils/text/Translator.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2012 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.utils.text;\n\nimport java.io.*;\nimport java.net.URL;\nimport java.net.URLConnection;\nimport java.nio.charset.StandardCharsets;\nimport java.util.*;\n\nimport org.jetbrains.annotations.Nullable;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.mucommander.conf.TcConfigurations;\nimport com.mucommander.conf.TcPreference;\n\n\n/**\n * This class takes care of all text localization issues by loading all text entries from a dictionary file on startup\n * and translating them into the current language on demand.\n *\n * <p>All public methods are static to make it easy to call them throughout the application.\n *\n * <p>See dictionary file for more information about th dictionary file format.\n *\n * @author Maxence Bernard, Arik Hadas\n */\npublic class Translator {\n\tprivate static Logger logger;\n\t\n    /** List of all available languages in the dictionary file */\n    private static final List<Locale> availableLanguages = new ArrayList<>();\n\n    /** Current language */\n    private static Locale language;\n\n    private static ResourceBundle bundle;\n\n    private Translator() {\n    }\n\n    static {\n        registerLocale(Locale.forLanguageTag(\"ar-SA\"));\n        registerLocale(Locale.forLanguageTag(\"be-BY\"));\n        registerLocale(Locale.forLanguageTag(\"ca-ES\"));\n        registerLocale(Locale.forLanguageTag(\"cs-CZ\"));\n        registerLocale(Locale.forLanguageTag(\"da-DA\"));\n        registerLocale(Locale.forLanguageTag(\"de-DE\"));\n        registerLocale(Locale.forLanguageTag(\"en-GB\"));\n        registerLocale(Locale.forLanguageTag(\"en-US\"));\n        registerLocale(Locale.forLanguageTag(\"es-ES\"));\n        registerLocale(Locale.forLanguageTag(\"fr-FR\"));\n        registerLocale(Locale.forLanguageTag(\"hu-HU\"));\n        registerLocale(Locale.forLanguageTag(\"it-IT\"));\n        registerLocale(Locale.forLanguageTag(\"ja-JP\"));\n        registerLocale(Locale.forLanguageTag(\"ko-KR\"));\n        registerLocale(Locale.forLanguageTag(\"no-NO\"));\n        registerLocale(Locale.forLanguageTag(\"nl-NL\"));\n        registerLocale(Locale.forLanguageTag(\"pl-PL\"));\n        registerLocale(Locale.forLanguageTag(\"pt-BR\"));\n        registerLocale(Locale.forLanguageTag(\"ro-RO\"));\n        registerLocale(Locale.forLanguageTag(\"ru-RU\"));\n        registerLocale(Locale.forLanguageTag(\"sk-SK\"));\n        registerLocale(Locale.forLanguageTag(\"sl-SL\"));\n        registerLocale(Locale.forLanguageTag(\"sv-SV\"));\n        registerLocale(Locale.forLanguageTag(\"uk-UA\"));\n        registerLocale(Locale.forLanguageTag(\"zh-CN\"));\n        registerLocale(Locale.forLanguageTag(\"zh-TW\"));\n    }\n\n\n    private static void registerLocale(Locale locale) {\n        availableLanguages.add(locale);\n    }\n\n\n    private static Locale loadLocale() {\n        String localeNameFromConf = TcConfigurations.getPreferences().getVariable(TcPreference.LANGUAGE);\n        if (localeNameFromConf == null) {\n            // language is not set in preferences, use system's language\n            // Try to match language with the system's language, only if the system's language\n            // has values in dictionary, otherwise use default language (English).\n            Locale defaultLocale = Locale.getDefault();\n            getLogger().info(\"Language not set in preferences, trying to match system's language ({})\", defaultLocale);\n            return defaultLocale;\n        }\n\n        getLogger().info(\"Using language set in preferences: \" + localeNameFromConf);\n        return switch (localeNameFromConf) {\n            // for backward compatibility\n            case \"EN\" -> Locale.forLanguageTag(\"en-US\");\n            case \"en_GB\" -> Locale.forLanguageTag(\"en-GB\");\n            case \"FR\" -> Locale.forLanguageTag(\"fr-FR\");\n            case \"DE\" -> Locale.forLanguageTag(\"de-DE\");\n            case \"ES\" -> Locale.forLanguageTag(\"es-ES\");\n            case \"CS\" -> Locale.forLanguageTag(\"cs-CZ\");\n            case \"zh_CN\" -> Locale.forLanguageTag(\"zh-CN\");\n            case \"zh_TW\" -> Locale.forLanguageTag(\"zh-TW\");\n            case \"PL\" -> Locale.forLanguageTag(\"pl-PL\");\n            case \"HU\" -> Locale.forLanguageTag(\"hu-HU\");\n            case \"RU\" -> Locale.forLanguageTag(\"ru-RU\");\n            case \"SL\" -> Locale.forLanguageTag(\"sl-SL\");\n            case \"RO\" -> Locale.forLanguageTag(\"ro-RO\");\n            case \"IT\" -> Locale.forLanguageTag(\"it-IT\");\n            case \"KO\" -> Locale.forLanguageTag(\"ko-KR\");\n            case \"pt_BR\" -> Locale.forLanguageTag(\"pt-BR\");\n            case \"NL\" -> Locale.forLanguageTag(\"nl-NL\");\n            case \"SK\" -> Locale.forLanguageTag(\"sk-SK\");\n            case \"JA\" -> Locale.forLanguageTag(\"ja-JP\");\n            case \"SV\" -> Locale.forLanguageTag(\"sv-SV\");\n            case \"DA\" -> Locale.forLanguageTag(\"da-DA\");\n            case \"UA\" -> Locale.forLanguageTag(\"uk-UA\");\n            case \"AR\" -> Locale.forLanguageTag(\"ar-SA\");\n            case \"BE\" -> Locale.forLanguageTag(\"be-BY\");\n            case \"NB\" -> Locale.forLanguageTag(\"no-NO\");\n            case \"CA\" -> Locale.forLanguageTag(\"ca-ES\");\n            default -> Locale.forLanguageTag(localeNameFromConf);\n        };\n    }\n\n    private static Locale matchLocale(Locale loadedLocale) {\n        final String lang = loadedLocale.getLanguage();\n        for (Locale locale : availableLanguages) {\n            if (lang.equals(loadedLocale.getLanguage()) && Objects.equals(locale.getCountry(), loadedLocale.getCountry())) {\n                getLogger().info(\"Found exact match (language+country) for locale {}\", locale);\n                return locale;\n            }\n        }\n\n        for (Locale locale : availableLanguages) {\n            if (lang.equals(loadedLocale.getLanguage())) {\n                getLogger().info(\"Found close match (language) for locale {}\", loadedLocale);\n                return locale;\n            }\n        }\n\n        getLogger().info(\"Locale {} is not available, falling back to English\", loadedLocale);\n        return Locale.ENGLISH;\n    }\n\n    public static void init() {\n        Locale locale = matchLocale(loadLocale());\n\n        // Determines if language is one of the languages declared as available\n        if (availableLanguages.contains(locale)) {\n            // Language is available\n            bundle = ResourceBundle.getBundle(\"dictionary\", locale, new UTF8Control());\n            getLogger().debug(\"Language {} is available.\", locale);\n        } else {\n            // Language is not available, fall back to default language\n            bundle = ResourceBundle.getBundle(\"dictionary\", new UTF8Control());\n            getLogger().debug(\"Language {} is not available, falling back to English\", locale);\n        }\n        // Set preferred language in configuration file\n        TcConfigurations.getPreferences().setVariable(TcPreference.LANGUAGE, locale.toLanguageTag());\n\n        Translator.language = locale;\n        getLogger().debug(\"Current language has been set to {}\", Translator.language);\n    }\n\n    /**\n     * Returns the current language as a language code (\"EN\", \"FR\", \"pt_BR\", ...).\n     *\n     * @return lang a language code\n     */\n    public static String getLanguage() {\n        return language.getLanguage();\n    }\n\t\n\t\n    /**\n     * Returns an array of available languages, expressed as language codes (\"EN\", \"FR\", \"pt_BR\"...).\n     * The returned array is sorted by language codes in case insensitive order.\n     *\n     * @return an list of language codes.\n     */\n    public static List<Locale> getAvailableLanguages() {\n        return availableLanguages;\n    }\n\n\n    /**\n     * Returns <code>true</code> if the given entry's key has a value in the current language.\n     * If the <code>useDefaultLanguage</code> parameter is <code>true</code>, entries that have no value in the\n     * {@link #getLanguage() current language} but one in English will be considered as having\n     * a value (<code>true</code> will be returned).\n     *\n     * @param key key of the requested dictionary entry (case-insensitive)\n     * @param useDefaultLanguage if <code>true</code>, entries that have no value in the {@link #getLanguage() current\n     * language} but one in English will be considered as having a value\n     * @return <code>true</code> if the given key has a corresponding value in the current language.\n     */\n    public static boolean hasValue(String key, boolean useDefaultLanguage) {\n        return bundle.containsKey(key);\n    }\n\n    /**\n     * Returns the localized text String for the given key expressed in the current language, or in the default language\n     * if there is no value for the current language. Entry parameters (%1, %2, ...), if any, are replaced by the\n     * specified values.\n     *\n     * @param key key of the requested dictionary entry (case-insensitive)\n     * @param paramValues array of parameters which will be used as values for variables.\n     * @return the localized text String for the given key expressed in the current language\n     */\n    public static String get(String key, String... paramValues) {\n        String text;\n        try {\n            text = bundle.getString(key);\n        } catch (Exception e) {\n            if (key == null) {\n                return null;\n            }\n            if (key.isEmpty()) {\n                return \"\";\n            }\n            text = key;\n            System.out.println(\"No value for \" + key +\" in language \" + language + \", using English value\");\n            getLogger().debug(\"No value for {}  in language {}, using English value\", key, language);\n        }\n\n        // Replace %1, %2 ... parameters by their value\n        if (paramValues != null) {\n            int pos = -1;\n            for (int i = 0; i<paramValues.length; i++) {\n                while (++pos < text.length()-1 && (pos = text.indexOf(\"%\"+(i+1), pos)) != -1) {\n                    text = text.substring(0, pos) + paramValues[i] + text.substring(pos + 2);\n                }\n            }\n        }\n\n        return text;\n    }\n\n\n\n    public static class UTF8Control extends ResourceBundle.Control {\n        public ResourceBundle newBundle(String baseName, Locale locale, String format,\n                                        ClassLoader loader, boolean reload) throws IOException {\n            // The below is a copy of the default implementation.\n            String bundleName = toBundleName(baseName, locale);\n            String resourceName = toResourceName(bundleName, \"properties\");\n            try (InputStream stream = getInputStream(loader, reload, resourceName)) {\n                if (stream != null) {\n                    return new PropertyResourceBundle(new InputStreamReader(stream, StandardCharsets.UTF_8));\n                }\n            }\n            return null;\n        }\n\n        @Nullable\n        private InputStream getInputStream(ClassLoader loader, boolean reload, String resourceName) throws IOException {\n            if (reload) {\n                URL url = loader.getResource(resourceName);\n                if (url != null) {\n                    URLConnection connection = url.openConnection();\n                    if (connection != null) {\n                        connection.setUseCaches(false);\n                        return connection.getInputStream();\n                    }\n                }\n            } else {\n                return loader.getResourceAsStream(resourceName);\n            }\n            return null;\n        }\n    }\n\n\n    private static Logger getLogger() {\n        if (logger == null) {\n            logger = LoggerFactory.getLogger(Translator.class);\n        }\n        return logger;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/utils/xml/XmlAttributes.java",
    "content": "package com.mucommander.utils.xml;\n\nimport java.util.*;\n\n/**\n * Container for XML attributes.\n * <p>\n * This class is meant for use with {@link com.mucommander.utils.xml.XmlWriter}.\n * It's used to hold a list of XML attributes that will be passed to one of\n * the {@link com.mucommander.utils.xml.XmlWriter#startElement(String,XmlAttributes) element opening} methods.\n *\n * @author Nicolas Rinaudo, Arik Hadas\n */\npublic class XmlAttributes {\n    /** Contains the XML attributes. */\n    private final Map<String, String> attributes = new HashMap<>();\n    /** Contains the XML attribute names in the order they were added */\n    private final LinkedList<String> names = new LinkedList<>();\n\n    /**\n     * Returns the value associated with the specified attribute name.\n     * @param name name of the attribute whose value should be retrieved.\n     * @return the value associated with the specified attribute name if found, <code>null</code> otherwise.\n     */\n    public String getValue(String name) {\n        return attributes.get(name);\n    }\n\n    /**\n     * Clears the list of all previously defined attributes.\n     */\n    public void clear() {\n        names.clear(); attributes.clear();\n    }\n\n    /**\n     * Adds the specified attribute to this container.\n     * @param name  name of the attribute to whose value should be set.\n     * @param value value to which the attribute should be set.\n     */\n    public void add(String name, String value) {\n        names.add(name); attributes.put(name, value);\n    }\n\n    /**\n     * Returns an iterator on the attributes contained by this instance.\n     * @return an iterator on the attributes contained by this instance.\n     */\n    public Iterator<String> names() {\n        return names.iterator();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/utils/xml/XmlWriter.java",
    "content": "package com.mucommander.utils.xml;\n\nimport java.io.*;\nimport java.util.Iterator;\n\n/**\n * Used to write pretty-printed XML content.\n * <p>\n * Application writers should keep in mind that this class does not perform any sort\n * of coherency check, and will not prevent them from closing elements they haven't opened yet,\n * or any other thing that would make the XML output invalid.\n *\n * @author Nicolas Rinaudo\n */\npublic class XmlWriter {\n    /** Number of space characters used for one level of indentation. */\n    private static final int    OFFSET_INCREMENT    = 4;\n    /** Identifier for publicly accessible objects. */\n    private static final String AVAILABILITY_PUBLIC = \"PUBLIC\";\n    /** Identifier for system resources. */\n    private static final String AVAILABILITY_SYSTEM = \"SYSTEM\";\n    /** Default output encoding. */\n    private static final String  DEFAULT_ENCODING    = \"UTF-8\";\n\n\n\n    // - XML standard entities -------------------------------------------\n    // -------------------------------------------------------------------\n    /** Forbidden XML characters. */\n    private final static String[] ENTITIES            = new String[] {\"&\", \"\\\"\" , \"'\", \"<\", \">\"};\n    /** What to replace forbidden XML characters with. */\n    private final static String[] ENTITY_REPLACEMENTS = new String[] {\"&amp;\", \"&quot;\", \"&apos;\", \"&lt;\", \"&gt;\"};\n\n\n\n    /** Where to write the XML content to. */\n    private PrintWriter out;\n    /** Current indentation offset. */\n    private int         offset;\n    /** Whether the next element opening or closing operation should be indented. */\n    private boolean     printIndentation;\n\n\n\n    /**\n     * Creates an <code>XmlWriter</code> that will write to the specified file.\n     * <p>\n     * This is a convenience constructor and is strictly equivalent to\n     * <code>{@link #XmlWriter(OutputStream,String) XmlWriter}(new FileOutputStream(file), {@link #DEFAULT_ENCODING})</code>.\n     *\n     * @param  file                  where to write XML output to.\n     * @throws FileNotFoundException if <code>file</code> could not be found.\n     * @throws IOException           if an I/O error occurs.\n     */\n    public XmlWriter(File file) throws IOException {this(new FileOutputStream(file));}\n\n    /**\n     * Creates an <code>XmlWriter</code> that will write to the specified file using the specified encoding.\n     * <p>\n     * This is a convenience constructor and is strictly equivalent to\n     * <code>{@link #XmlWriter(OutputStream,String) XmlWriter}(new FileOutputStream(file), encoding)</code>.\n     *\n     * @param  file                         where to write XML output to.\n     * @param  encoding                     encoding to use when writing the XML content.\n     * @throws FileNotFoundException        if <code>file</code> could not be found.\n     * @throws UnsupportedEncodingException if <code>encoding</code> is not supported.\n     * @throws IOException                  if an I/O error occurs.\n     */\n    public XmlWriter(File file, String encoding) throws IOException {this(new FileOutputStream(file), encoding);}\n\n    /**\n     * Creates an <code>XmlWriter</code> that will write to the specified output stream.\n     * <p>\n     * This is a convenience constructor and is strictly equivalent to\n     * <code>{@link #XmlWriter(OutputStream,String) XmlWriter}(stream, {@link #DEFAULT_ENCODING})</code>.\n     *\n     * @param  stream      where to write XML output to.\n     * @throws IOException if an I/O error occurs.\n     */\n    public XmlWriter(OutputStream stream) throws IOException {init(new OutputStreamWriter(stream, DEFAULT_ENCODING), DEFAULT_ENCODING);}\n\n    /**\n     * Creates an <code>XmlWriter</code> that will write to the specified stream using the specified encoding.\n     * @param  stream                       where to write XML output to.\n     * @param  encoding                     encoding to use when writing the XML content.\n     * @throws UnsupportedEncodingException if <code>encoding</code> is not supported.\n     * @throws IOException                  if an I/O error occurs.\n     */\n    public XmlWriter(OutputStream stream, String encoding) throws IOException {init(new OutputStreamWriter(stream, encoding), encoding);}\n\n    private void init(Writer writer, String encoding) throws IOException {\n        out = new PrintWriter(writer, true);\n        out.print(\"<?xml version=\\\"1.0\\\" encoding=\\\"\");\n        out.print(encoding);\n        out.println(\"\\\"?>\");\n        if (out.checkError()) {\n            throw new IOException();\n        }\n    }\n\n\n    // - Element operations --------------------------------------------------\n    // -------------------------------------------------------------------\n    /**\n     * Writes the document type declaration of the XML file.\n     * <p>\n     * For the generated XML content to be valid, application writers should make sure that this\n     * is the very first method they call. This class doesn't ensure coherency and won't\n     * complain if the <code>DOCTYPE</code> statement is the last in the file.\n     * <p>\n     * Both <code>description</code> and <code>url</code> can be set to <code>null</code>.\n     * If so, they will just be ignored.\n     *\n     * @param  topElement   label of the top element in the XML file.\n     * @param  availability availability of the document (expected to be either {@link #AVAILABILITY_PUBLIC}\n     *                      or {@link #AVAILABILITY_SYSTEM}, but this is not enforced).\n     * @param  description  description of the file (see DOCTYPE specifications for more information).\n     * @param  url          URL at which the DTD of the XML file can be downloaded.\n     * @throws IOException  if an I/O error occurs.\n     */\n    public void writeDocType(String topElement, String availability, String description, String url) throws IOException {\n        // Writes the compulsory bits.\n        out.print(\"<!DOCTYPE \");\n        out.print(topElement);\n        out.print(' ');\n        out.print(availability);\n\n        // Writes the description if present.\n        if (description != null) {\n            out.print(' ');\n            out.print('\\\"');\n            out.print(description);\n            out.print('\\\"');\n        }\n\n        // Writes the DTD url if present.\n        if (url != null) {\n            out.print(' ');\n            out.print('\\\"');\n            out.print(url);\n            out.print('\\\"');\n        }\n        out.println('>');\n        if (out.checkError()) {\n            throw new IOException();\n        }\n    }\n\n    /**\n     * Writes an element opening sequence.\n     * <p>\n     * This is a convenience method and is strictly equivalent to calling\n     * <code>{@link #startElement(String,boolean) startElement}(name, false)</code>.\n     *\n     * @param  name        name of the element to open.\n     * @throws IOException if an I/O error occurs.\n     * @see                #startElement(String,XmlAttributes)\n     * @see                #writeStandAloneElement(String)\n     * @see                #writeStandAloneElement(String,XmlAttributes)\n     */\n    public void startElement(String name) throws IOException {startElement(name, false, null, false);}\n    \n    /**\n     * Writes an element opening sequence.\n     * <p>\n     * Elements opened using this method will not have any attribute, and will\n     * need to be closed using an {@link #endElement(String) endElement} call.\n     *\n     * @param  name        name of the element to open.\n     * @param  lineBreak   if <code>true</code>, a line break will be printed after the element declaration.\n     * @throws IOException if an I/O error occurs.\n     * @see                #startElement(String,XmlAttributes)\n     * @see                #writeStandAloneElement(String)\n     * @see                #writeStandAloneElement(String,XmlAttributes)\n     */\n    public void startElement(String name, boolean lineBreak) throws IOException {startElement(name, false, null, lineBreak);}\n\n    /**\n     * Writes a stand-alone element.\n     * <p>\n     * Elements opened using this method will not have any attributes, and will be\n     * closed immediately.\n     * <p>\n     * A line break will always be printed after a stand-alone element.\n     *\n     * @param  name        name of the element to write.\n     * @throws IOException if an I/O error occurs.\n     * @see                #startElement(String,XmlAttributes)\n     * @see                #startElement(String)\n     * @see                #writeStandAloneElement(String)\n     */\n    public void writeStandAloneElement(String name) throws IOException {startElement(name, true, null, true);}\n\n    /**\n     * Writes a one-line comment.\n     * \n     * @param comment      comment description.\n     * @throws IOException if an I/O error occurs.\n     */\n    public void writeCommentLine(String comment) throws IOException {\n        out.print(\"<!-- \" + comment + \" -->\");\n        println();\n    }\n    \n    /**\n     * Writes an element opening sequence.\n     * <p>\n     * This is a convenience method and is strictly equivalent to calling\n     * <code>{@link #startElement(String,XmlAttributes,boolean) startElement}(name, attributes, false)</code>.\n     *\n     * @param  name        name of the element to open.\n     * @throws IOException if an I/O error occurs.\n     * @param  attributes  attributes that this element will have.\n     * @see                #startElement(String)\n     * @see                #writeStandAloneElement(String)\n     * @see                #writeStandAloneElement(String,XmlAttributes)\n     */\n    public void startElement(String name, XmlAttributes attributes) throws IOException {startElement(name, false, attributes, false);}\n\n    /**\n     * Writes an element opening sequence.\n     * <p>\n     * Elements opened using this method will need to be closed using an {@link #endElement(String) endElement} call.\n     *\n     * @param name         name of the element to open.\n     * @param  attributes  attributes that this element will have.\n     * @param  lineBreak   if <code>true</code>, a line break will be printed after the element declaration.\n     * @throws IOException if an I/O error occurs.\n     * @see                #startElement(String)\n     * @see                #writeStandAloneElement(String)\n     * @see                #writeStandAloneElement(String,XmlAttributes)\n     */\n    public void startElement(String name, XmlAttributes attributes, boolean lineBreak) throws IOException {startElement(name, false, attributes, lineBreak);}\n\n    /**\n     * Writes a stand-alone element.\n     * <p>\n     * Elements opened using this method will not need to be closed\n     * <p>\n     * A line break will always be printed after a stand-alone element.\n     *\n     * @param name         name of the element to write.\n     * @param attributes   attributes that this element will be closed immediately.\n     * @throws IOException if an I/O error occurs.\n     * @see                #startElement(String)\n     * @see                #startElement(String,XmlAttributes)\n     * @see                #writeStandAloneElement(String)\n     */\n    public void writeStandAloneElement(String name, XmlAttributes attributes) throws IOException {startElement(name, true, attributes, true);}\n\n    /**\n     * Writes an element opening sequence.\n     * @param name         name of the element to open.\n     * @param  isStandAlone whether this element should be closed immediately.\n     * @param  attributes   XML attributes for this element.\n     * @throws IOException  if an I/O error occurs.\n     */\n    private void startElement(String name, boolean isStandAlone, XmlAttributes attributes, boolean lineBreak) throws IOException {\n        // Prints indentation if necessary.\n        indent();\n\n        // Opens the element.\n        out.print('<');\n        out.print(name);\n\n        // Writes attributes, if any.\n        if (attributes != null) {\n            Iterator<String> names;\n            String           attName;\n\n            names = attributes.names();\n            while(names.hasNext()) {\n                attName = names.next();\n                out.print(' ');\n                out.print(attName);\n                out.print(\"=\\\"\");\n                out.print(escape(attributes.getValue(attName)));\n                out.print(\"\\\"\");\n            }\n        }\n\n        // Closes the element if necessary.\n        if (isStandAlone) {\n            out.print('/');\n        } else {\n            offset += OFFSET_INCREMENT;\n        }\n        // Finishes the element opening sequence.\n        out.print('>');\n\n        // Stand-alone elements are followed by a line break.\n        if (lineBreak) {\n            println();\n        }\n\n        if(out.checkError())\n            throw new IOException();\n    }\n\n    /**\n     * Writes an element closing sequence.\n     * @param  name        name of the element to close.\n     * @throws IOException if an I/O error occurs.\n     */\n    public void endElement(String name) throws IOException {\n        // Updates the indentation, and prints it if necessary.\n        offset -= OFFSET_INCREMENT;\n        indent();\n\n        // Writes the element closing sequence.\n        out.print(\"</\");\n        out.print(name);\n        out.print('>');\n        println();\n\n        if (out.checkError())\n            throw new IOException();\n    }\n\n\n\n    // - CDATA handling --------------------------------------------------\n    // -------------------------------------------------------------------\n    /**\n     * Writes the specified CDATA to the XML stream.\n     * @param  cdata       content to write to the XML stream.\n     * @throws IOException if an I/O error occurs.\n     */\n    public void writeCData(String cdata) throws IOException {\n        indent();\n        out.print(escape(cdata));\n        if (out.checkError()) {\n            throw new IOException();\n        }\n    }\n\n    /**\n     * Escapes XML content, replacing special characters by their proper value.\n     * @param   data       data to escape.\n     * @return             the escaped content.\n     */\n    public String escape(String data) {\n        for (int i = 0; i < ENTITIES.length; i++) {\n            int position = 0;\n            while ((position = data.indexOf(ENTITIES[i], position)) >= 0) {\n                data = data.substring(0, position) + ENTITY_REPLACEMENTS[i] +\n                    (position == data.length() - 1 ? \"\" : data.substring(position + 1));\n                position = position + ENTITY_REPLACEMENTS[i].length();\n            }\n        }\n\n        return data;\n    }\n\n\n\n    // - Indentation handling --------------------------------------------\n    // -------------------------------------------------------------------\n    /**\n     * Prints a line break.\n     * @throws IOException if an I/O error occurs.\n     */\n    public void println() throws IOException {\n        out.println();\n        printIndentation = true;\n        if (out.checkError()) {\n            throw new IOException();\n        }\n    }\n\n    /**\n     * If necessary, prints indentation.\n     * @throws IOException if an I/O error occurs.\n     */\n    private void indent() throws IOException {\n        if (printIndentation) {\n            for(int i = 0; i < offset; i++) {\n                out.print(' ');\n            }\n            printIndentation = false;\n        }\n        if (out.checkError()) {\n            throw new IOException();\n        }\n    }\n\n\n    /**\n     * Closes the XML stream.\n     * @throws IOException if an I/O error occurs.\n     */\n    public void close() throws IOException {\n        out.close();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mucommander/utils/xml/package.html",
    "content": "<body>\n  Provides classes to write formatted XML files.\n</body>\n"
  },
  {
    "path": "src/main/java/com/sshtools/sftp/SftpFileInputStreamEx.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.sshtools.sftp;\n\nimport com.sshtools.ssh.SshException;\n\n/**\n * Modified SftpFileInputStream with public getPosition() and setPosition()\n *\n * @author Oleg Trifonov\n * Created on 26/04/16.\n */\npublic class SftpFileInputStreamEx extends SftpFileInputStream {\n    public SftpFileInputStreamEx(SftpFile file) throws SftpStatusException, SshException {\n        super(file);\n    }\n\n    public SftpFileInputStreamEx(SftpFile file, long position) throws SftpStatusException, SshException {\n        super(file, position);\n    }\n\n    public long getPosition() {\n        return position;\n    }\n\n    public void setPosition(long position) {\n        this.position = position;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/sshtools/sftp/SftpFileOutputStreamEx.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.sshtools.sftp;\n\nimport com.sshtools.ssh.SshException;\n\n/**\n * Modified SftpFileOutputStream with position argument in constructor\n *\n * @author Oleg Trifonov\n * Created on 26/04/16.\n */\npublic class SftpFileOutputStreamEx extends SftpFileOutputStream {\n\n    protected SftpFileOutputStreamEx(SftpFile file, long pos) throws SftpStatusException, SshException {\n        super(file);\n        this.position = pos;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/sun/jna/platform/mac/XAttrUtils.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage com.sun.jna.platform.mac;\n\nimport com.sun.jna.Memory;\n\n/**\n * Utility methods that complement the JNA library.\n *\n * @author Arik Hadas\n */\npublic class XAttrUtils {\n    /** The key of the spotlight comment section */\n    public static final String COMMENT = \"com.apple.metadata:kMDItemFinderComment\";\n\n    /**\n     * Reads the content of section in file's metadata.\n     *\n     * @param path Path to a file\n     * @param key Key of a metadata section\n     * @return The value associated with the given key in the file's metadata\n     */\n    public static byte[] read(String path, String key) {\n        long bufferLength = XAttr.INSTANCE.getxattr(path, key, null, 0, 0, 0);\n        if (bufferLength <= 0) {\n            return null;\n        }\n\n        Memory valueBuffer = new Memory(bufferLength);\n        valueBuffer.clear();\n        long valueLength = XAttr.INSTANCE.getxattr(path, key, valueBuffer, bufferLength, 0, 0);\n\n        if (valueLength < 0) {\n            return null;\n        }\n\n        return valueBuffer.getByteArray(0, (int)valueLength);\n    }\n\n    /**\n     * Writes the content to section in file's metadata.\n     *\n     * @param path Path to a file\n     * @param key Key of a metadata section\n     * @param value Content to write to the metadata section\n     */\n    public static void write(String path, String key, byte[] value) {\n        Memory valueBuffer = new Memory(value.length);\n        valueBuffer.write(0, value, 0, value.length);\n        XAttr.INSTANCE.setxattr(path, key, valueBuffer, valueBuffer.size(), 0, 0);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/fife/ui/rtextarea/GutterEx.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2014-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage org.fife.ui.rtextarea;\n\n/**\n * Child of org.fife.ui.rtextarea.Gutter with some public methods\n *\n * @author Oleg Trifonov\n * Created on 19/04/16.\n */\npublic class GutterEx extends Gutter {\n\n    public GutterEx(RTextArea textArea) {\n        super(textArea);\n    }\n\n    public void setIconRowHeaderEnabled(boolean enabled) {\n        super.setIconRowHeaderEnabled(enabled);\n    }\n\n    public void setLineNumbersEnabled(boolean enabled) {\n        super.setLineNumbersEnabled(enabled);\n    }\n\n    public void setTextArea(RTextArea textArea) {\n        super.setTextArea(textArea);\n    }\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/Calculator.kt",
    "content": "package ru.trolsoft.calculator\n\nimport ru.trolsoft.calculator.eval.CustomOperator\nimport ru.trolsoft.calculator.eval.ExpressionBuilder\nimport kotlin.math.roundToInt\nimport kotlin.math.roundToLong\n\nclass Calculator {\n    fun calculate(expression: String): Double =\n        if (expression.isBlank()) {\n            return 0.0\n        } else {\n            ExpressionBuilder(expression).apply {\n                withOperations(OPERATORS)\n                withVariable(\"pi\", Math.PI)\n                withVariable(\"e\", Math.E)\n            }.build().calculate()\n        }\n\n    companion object {\n\n        private fun opLL(values: DoubleArray, operation: (Long, Long) -> Long): Double =\n            operation(values[0].roundToLong(), values[1].roundToLong()).toDouble()\n        private fun opLI(values: DoubleArray, operation: (Long, Int) -> Long): Double =\n            operation(values[0].roundToLong(), values[1].roundToInt()).toDouble()\n\n        private val OP_SHL = object : CustomOperator(\"<<\", true, 10, 2) {\n            override fun applyOperation(values: DoubleArray) =\n                opLI(values) { a: Long, b: Int -> a shl b }\n        }\n\n        private val OP_SHR = object : CustomOperator(\">>\", true, 11, 2) {\n            override fun applyOperation(values: DoubleArray) =\n                opLI(values) { a: Long, b: Int -> a shr b }\n        }\n\n        private val OP_AND = object : CustomOperator(\"&\", true, 8, 2) {\n            override fun applyOperation(values: DoubleArray) =\n                opLL(values) { a: Long, b: Long -> a and b }\n        }\n\n        private val OP_OR = object : CustomOperator(\"|\", true, 6, 2) {\n            override fun applyOperation(values: DoubleArray) =\n                opLL(values) { a: Long, b: Long -> a or b }\n        }\n\n        private val OP_NOT = object : CustomOperator(\"~\", true, 15, 1) {\n            override fun applyOperation(values: DoubleArray): Double {\n                val arg = values[0].roundToLong()\n                val mask = if (arg < 0xff) {\n                    0xffL\n                } else if (arg < 0xffff) {\n                    0xffffL\n                } else if (arg < 0xffffffff) {\n                    0xffffffffL\n                } else {\n                    (1L shl 32) - 1L\n                }\n                return (arg.inv() and mask).toDouble()\n            }\n        }\n\n        private val OP_XOR = object : CustomOperator(\"^^\", true, 7, 2) {\n            override fun applyOperation(values: DoubleArray) =\n                opLL(values) { a: Long, b: Long -> a xor b }\n        }\n\n        val OPERATORS = listOf(\n            OP_SHL, OP_SHR, OP_AND, OP_OR, OP_NOT, OP_XOR\n        )\n    }\n\n}"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/CalculatorDialog.kt",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2026 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.calculator\n\nimport com.mucommander.cache.TextHistory\nimport com.mucommander.ui.dialog.FocusDialog\nimport com.mucommander.ui.helper.MnemonicHelper\nimport com.mucommander.ui.layout.XAlignedComponentPanel\nimport com.mucommander.ui.layout.XBoxPanel\nimport com.mucommander.ui.layout.YBoxPanel\nimport org.slf4j.Logger\nimport org.slf4j.LoggerFactory\nimport ru.trolsoft.utils.StrUtils\nimport java.awt.*\nimport java.awt.datatransfer.StringSelection\nimport java.awt.event.ActionEvent\nimport java.awt.event.ActionListener\nimport java.awt.event.KeyEvent\nimport java.awt.event.KeyListener\nimport java.text.DecimalFormat\nimport java.util.*\nimport javax.swing.JButton\nimport javax.swing.JLabel\nimport javax.swing.JPanel\nimport javax.swing.JTextField\nimport kotlin.math.roundToLong\n\n/**\n * Created on 04/06/14.\n * @author Oleg Trifonov\n */\nclass CalculatorDialog(\n    owner: Frame\n) : FocusDialog(owner, i18n(\"calculator.calculator\"), null), ActionListener, KeyListener {\n    private val calculator = Calculator()\n    private val cbExpression: HistoryComboBox\n    private val edtDec: JTextField\n    private val edtHex: JTextField\n    private val edtBin: JTextField\n    private val edtOct: JTextField\n    private val edtExp: JTextField\n    private val btnDec: JButton\n    private val btnHex: JButton\n    private val btnBin: JButton\n    private val btnOct: JButton\n    private val btnExp: JButton\n    private val btnClose: JButton\n    private val lblError: JLabel\n\n    init {\n        val contentPane = getContentPane()\n\n        val yPanel = YBoxPanel(10)\n\n        // Text fields panel\n        val compPanel: XAlignedComponentPanel = object : XAlignedComponentPanel() {\n            override fun add(comp: Component, constraints: Any) {\n                (constraints as GridBagConstraints).fill = GridBagConstraints.HORIZONTAL\n                super.add(comp, constraints)\n            }\n        }\n\n        val calcHistory = TextHistory.getInstance().getList(TextHistory.Type.CALCULATOR)\n        cbExpression = HistoryComboBox(this, calcHistory).apply {\n            addActionListener(this@CalculatorDialog)\n            getEditor().editorComponent.addKeyListener(this@CalculatorDialog)\n        }\n        compPanel.addRow(i18n(\"calculator.expression\") + \":\", cbExpression, 5)\n\n        lblError = JLabel()\n        compPanel.addRow(\"\", lblError, 10)\n\n        val buttonFont = Font.getFont(Font.MONOSPACED)\n\n        btnDec = JButton(\"DEC\").apply {\n            addActionListener(this@CalculatorDialog)\n            setFont(buttonFont)\n        }\n        edtDec = JTextField().apply {\n            isEditable = false\n        }\n\n        compPanel.addRow(btnDec, edtDec, 0)\n\n        btnHex = JButton(\"HEX\").apply {\n            addActionListener(this@CalculatorDialog)\n            setFont(buttonFont)\n        }\n        edtHex = JTextField().apply {\n            isEditable = false\n        }\n        compPanel.addRow(btnHex, edtHex, 0)\n\n        btnBin = JButton(\"BIN\").apply {\n            addActionListener(this@CalculatorDialog)\n            setFont(buttonFont)\n        }\n        edtBin = JTextField().apply {\n            isEditable = false\n        }\n        compPanel.addRow(btnBin, edtBin, 0)\n\n        btnOct = JButton(\"OCT\").apply {\n            addActionListener(this@CalculatorDialog)\n            setFont(buttonFont)\n        }\n        edtOct = JTextField().apply {\n            isEditable = false\n        }\n        compPanel.addRow(btnOct, edtOct, 0)\n\n        btnExp = JButton(\"EXP\").apply {\n            addActionListener(this@CalculatorDialog)\n            setFont(buttonFont)\n        }\n        edtExp = JTextField().apply {\n            isEditable = false\n        }\n        compPanel.addRow(btnExp, edtExp, 0)\n\n        val mnemonicHelper = MnemonicHelper()\n\n        val buttonsPanel = XBoxPanel()\n        val buttonGroupPanel = JPanel(FlowLayout(FlowLayout.RIGHT))\n\n        btnClose = JButton(i18n(\"close\")).apply {\n            addActionListener(this@CalculatorDialog)\n            setMnemonic(mnemonicHelper.getMnemonic(this))\n        }\n        buttonGroupPanel.add(btnClose)\n\n        buttonsPanel.add(buttonGroupPanel)\n\n        contentPane.add(buttonsPanel, BorderLayout.SOUTH)\n\n        contentPane.add(yPanel, BorderLayout.NORTH)\n\n        yPanel.add(compPanel)\n\n        minimumSize = MIN_DIMENSION\n        setModal(false)\n\n        fixHeight()\n    }\n\n    private fun calculateAndShow(): Boolean {\n        val expression = this.getExpression() ?: return false\n        var success: Boolean\n        try {\n            val res = evaluate(expression)\n            TextHistory.getInstance().add(TextHistory.Type.CALCULATOR, expression, false)\n            cbExpression.addToHistory(expression)\n            showResult(res)\n            success = true\n        } catch (e: Exception) {\n            log.error(\"Calculation failed\", e)\n            clearResultFields()\n            success = false\n        }\n        enableControls(success)\n        lblError.setText(if (success) \"\" else i18n(\"calculator.error\"))\n        return success\n    }\n\n    private fun showResult(res: Double) {\n        val valLong = res.roundToLong()\n        val isDecimal = valLong.toDouble() == res\n        edtDec.text = if (isDecimal) valLong.toString() else FORMAT_DEC.format(res).replace(',', '.')\n        edtHex.text = java.lang.Long.toHexString(valLong)\n        edtOct.text = java.lang.Long.toOctalString(valLong)\n        edtBin.text = java.lang.Long.toBinaryString(valLong)\n        edtExp.text = formatExp(res)\n    }\n\n    private fun getExpression(): String? {\n        val selectedItem = cbExpression.selectedItem ?: return null\n        val result = selectedItem.toString().trim()\n        return StrUtils.removeUtfMarker(result).trim()\n    }\n\n\n    private fun enableControls(enable: Boolean) {\n        edtDec.setEnabled(enable)\n        edtHex.setEnabled(enable)\n        edtOct.setEnabled(enable)\n        edtBin.setEnabled(enable)\n        edtOct.setEnabled(enable)\n        edtExp.setEnabled(enable)\n        btnDec.setEnabled(enable)\n        btnHex.setEnabled(enable)\n        btnOct.setEnabled(enable)\n        btnBin.setEnabled(enable)\n        btnOct.setEnabled(enable)\n        btnExp.setEnabled(enable)\n    }\n\n    private fun clearResultFields() {\n        edtDec.text = \"\"\n        edtHex.text = \"\"\n        edtOct.text = \"\"\n        edtBin.text = \"\"\n        edtExp.text = \"\"\n    }\n\n    @Throws(Exception::class)\n    private fun evaluate(expression: String): Double =\n        calculator.calculate(expression)\n\n    private fun formatExp(v: Double): String {\n        var result = FORMAT_EXP.format(v).uppercase(Locale.getDefault())\n        val index = result.indexOf('E')\n        if (index > 0 && result[index + 1] != '-') {\n            result = result.substring(0, index) + '+' + result.substring(index)\n        }\n        return result\n    }\n\n    override fun actionPerformed(e: ActionEvent) {\n        val src = e.getSource()\n        if (src === cbExpression) {\n            calculateAndShow()\n        } else if (src === btnClose) {\n            cancel()\n        } else if (src === btnDec) {\n            toClipboard(edtDec.getText())\n        } else if (src === btnHex) {\n            toClipboard(edtHex.getText())\n        } else if (src === btnBin) {\n            toClipboard(edtBin.getText())\n        } else if (src === btnOct) {\n            toClipboard(edtOct.getText())\n        } else if (src === btnExp) {\n            toClipboard(edtExp.getText())\n        }\n    }\n\n    private fun toClipboard(s: String?) {\n        val data = StringSelection(s)\n        Toolkit.getDefaultToolkit().systemClipboard.setContents(data, data)\n    }\n\n\n    override fun saveState() {\n        super.saveState()\n        TextHistory.getInstance().save(TextHistory.Type.CALCULATOR)\n    }\n\n    override fun keyTyped(e: KeyEvent) {}\n\n    override fun keyPressed(e: KeyEvent) {}\n\n    override fun keyReleased(e: KeyEvent) {\n        if (e.getKeyCode() == KeyEvent.VK_ENTER && (e.modifiersEx and (KeyEvent.CTRL_DOWN_MASK or KeyEvent.META_DOWN_MASK)) != 0) {\n            if (calculateAndShow()) {\n                cbExpression.setSelectedItem(edtDec.getText())\n            }\n        }\n    }\n\n    companion object {\n        private val log: Logger = LoggerFactory.getLogger(CalculatorDialog::class.java)\n        private val MIN_DIMENSION = Dimension(520, 300)\n        private val FORMAT_DEC = DecimalFormat(\"#.##################\")\n        private val FORMAT_EXP = DecimalFormat(\"0.00000000000000E0000\")\n    }\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/HistoryComboBox.kt",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.calculator\n\nimport com.mucommander.ui.combobox.TcComboBox\nimport com.mucommander.ui.dialog.FocusDialog\nimport java.awt.event.KeyAdapter\nimport java.awt.event.KeyEvent\n\n/**\n * @author Oleg Trifonov\n * Created on 06/06/14.\n */\nclass HistoryComboBox(\n    private val parent: FocusDialog,\n    values: List<String>\n) : TcComboBox<String>(values.toTypedArray<String>()) {\n    init {\n        setEditable(true)\n        if (!values.isEmpty()) {\n            setSelectedItem(values.first())\n            getEditor().selectAll()\n        }\n\n        val keyAdapter: KeyAdapter = object : KeyAdapter() {\n            override fun keyPressed(e: KeyEvent) {\n                if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {\n                    if (isPopupVisible) {\n                        hidePopup()\n                        e.consume()\n                    } else {\n                        this@HistoryComboBox.parent.cancel()\n                    }\n                }\n            }\n        }\n        getEditor().editorComponent.addKeyListener(keyAdapter)\n    }\n\n\n    fun addToHistory(s: String) {\n        for (i in 0..< itemCount) {\n            val item = getItemAt(i)\n            if (item.equals(s, ignoreCase = true)) {\n                removeItem(item)\n                break\n            }\n        }\n        insertItemAt(s, 0)\n        setSelectedIndex(0)\n    }\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/eval/Calculable.kt",
    "content": "package ru.trolsoft.calculator.eval\n\n/**\n * This is the basic result class of the exp4j [ExpressionBuilder]\n * \n * @author frank asseg\n */\ninterface Calculable {\n    /**\n     * calculate the result of the expression\n     * \n     * @return the result of the calculation\n     */\n    fun calculate(): Double = 0.0\n\n    /**\n     * calculate the result of the expression\n     * \n     * @param variableValues\n     * the values of the variable. The values must be in the same order as the declaration of variables in\n     * the [ExpressionBuilder] used to construct this [Calculable] instance\n     * @return the result of the calculation\n     */\n    fun calculate(vararg variableValues: Double): Double\n\n    /**\n     * return the expression in reverse polish postfix notation\n     * \n     * @return the expression used to construct this [Calculable]\n     */\n    val expression: String?\n\n    /**\n     * set a variable value for the calculation\n     * \n     * @param name\n     * the variable name\n     * @param value\n     * the value of the variable\n     */\n    fun setVariable(name: String?, value: Double)\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/eval/CommandlineInterpreter.java",
    "content": "/*\n   Copyright 2011 frank asseg\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n */\npackage ru.trolsoft.calculator.eval;\n\nimport ru.trolsoft.calculator.eval.exceptions.UnknownFunctionException;\nimport ru.trolsoft.calculator.eval.exceptions.UnparsableExpressionException;\n\n/**\n * Simple commandline interpreter for mathematical expressions the interpreter takes a mathematical expressions as a\n * {@link String} argument, evaluates it and prints out the result.\n * \n * \n * <pre>\n * java de.congrace.exp4j.CommandlineInterpreter \"2 * log(2.2223) - ((2-3.221) * 14.232^2)\"\n * &gt; 248.91042049521056\n * </pre>\n * \n * @author fas@congrace.de\n * \n */\npublic class CommandlineInterpreter {\n\tprivate static void calculateExpression(String string) {\n\t\ttry {\n\t\t\tSystem.out.println(new ExpressionBuilder(string).build().calculate());\n\t\t} catch (UnparsableExpressionException | UnknownFunctionException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t}\n\n\tpublic static void main(String[] args) {\n\t\tif (args.length != 1) {\n\t\t\tprintUsage();\n\t\t} else {\n\t\t\tcalculateExpression(args[0]);\n\t\t}\n\t}\n\n\tprivate static void printUsage() {\n        System.err.println(\"Commandline Expression Parser\\n\\n\" + \"Example: \" + \"\\n\" + \"java -jar exp4j.jar \\\"2.12 * log(23) * (12 - 4)\\\"\\n\\n\" + \"written by fas@congrace.de\");\n\t}\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/eval/CustomFunction.java",
    "content": "package ru.trolsoft.calculator.eval;\n\nimport ru.trolsoft.calculator.eval.exceptions.InvalidCustomFunctionException;\n\n/**\n * This classed is used to create custom functions for exp4j<br>\n *\n * <b>Example</b><br>\n * <pre>{@code\n * CustomFunction fooFunc = new CustomFunction(\"foo\") {\n * \t\tpublic double applyFunction(double value) {\n * \t\t\treturn value*Math.E;\n * \t\t}\n * };\n * double varX=12d;\n * Calculable calc = new ExpressionBuilder(\"foo(x)\").withCustomFunction(fooFunc).withVariable(\"x\",varX).build();\n * assertTrue(calc.calculate() == Math.E * varX);\n * }</pre>\n * \n * @author frank asseg\n * \n */\npublic abstract class CustomFunction {\n\tpublic final int argc;\n\tpublic final String name;\n\n\t/**\n\t * create a new single value input CustomFunction with a set name\n\t * \n\t * @param name the name of the function (e.g. foo)\n\t */\n\tCustomFunction(String name) throws InvalidCustomFunctionException {\n\t\tthis.argc = 1;\n\t\tthis.name = name;\n\t\tint firstChar = name.charAt(0);\n\t\tif ((firstChar < 'A' || firstChar > 'Z') && (firstChar < 'a' || firstChar > 'z')) {\n\t\t\tthrow new InvalidCustomFunctionException(\"functions have to start with a lowercase or uppercase character\");\n\t\t}\n\t}\n\n\t/**\n\t * create a new single value input CustomFunction with a set name\n\t * \n\t * @param name the name of the function (e.g. foo)\n\t */\n\tprotected CustomFunction(String name, int argumentCount) {\n\t\tthis.argc = argumentCount;\n\t\tthis.name = name;\n\t}\n\n\tpublic int getArgumentCount(){\n\t\treturn argc;\n\t}\n\t\n\tpublic abstract double applyFunction(double... args);\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/eval/CustomOperator.java",
    "content": "package ru.trolsoft.calculator.eval;\n\n/**\n * This class is used to create custom operators for use in expressions<br>\n * The applyOperation(double[] values) will have to be implemented by users of this class. <br>\n * <b>Example</b><br>\n * <pre>{@code\n *      CustomOperator greaterEq = new CustomOperator(\"&gt;=\", true, 4, 2) {\n *            double applyOperation(double[] values) {\n *            \tif (values[0] &gt;= values[1]) {\n *            \t\treturn 1d;\n *            \t} else {\n *            \t\treturn 0d;\n *            \t}\n *            }\n *        };\n *       Calculable calc = new ExpressionBuilder(\"1&gt;=2\").withOperation(greaterEq).build();\n *       assertTrue(0d == calc.calculate());\n * }</pre> When constructing {@link CustomOperator} special attention has to be given to the precedence of the\n * operation. see http://en.wikipedia.org/wiki/Order_of_operations. The precedence values for the builtin operators are\n * as follows: <br>\n * Addition and Subtraction (+,-) have precedence 1<br>\n * Division Multiplication, and Modulo (/,*,%) have precedence 3<br>\n * Exponentiation (^) has precedence 5 <br>\n * Unary minus and plus (+1,-1) have precedence 7\n * \n * @author frank asseg\n * \n */\npublic abstract class CustomOperator {\n\n\tpublic final boolean leftAssociative;\n\n\tpublic final String symbol;\n\n\tpublic final int precedence;\n\n\tpublic final int operandCount;\n\n\t/**\n\t * create a new {@link CustomOperator} for two operands\n\t * \n\t * @param symbol\n\t *            the symbol to be used in expressions to identify this operation\n\t * @param leftAssociative\n\t *            true is the operation is left associative\n\t * @param precedence\n\t *            the precedence of the operation\n\t */\n\tCustomOperator(final String symbol, final boolean leftAssociative, final int precedence) {\n\t\tsuper();\n\t\tthis.leftAssociative = leftAssociative;\n\t\tthis.symbol = symbol;\n\t\tthis.precedence = precedence;\n\t\tthis.operandCount = 2;\n\t}\n\n\t/**\n\t * create a new {@link CustomOperator}\n\t * \n\t * @param symbol\n\t *            the symbol to be used in expressions to identify this operation\n\t * @param leftAssociative\n\t *            true is the operation is left associative\n\t * @param precedence\n\t *            the precedence of the operation\n\t * @param operandCount\n\t *            the number of operands of the operation. A value of 1 means the operation takes one operand. Any other\n\t *            value means the operation takes 2 arguments.\n\t */\n\n\tprotected CustomOperator(final String symbol, final boolean leftAssociative, final int precedence,\n\t\t\tfinal int operandCount) {\n\t\tsuper();\n\t\tthis.leftAssociative = leftAssociative;\n\t\tthis.symbol = symbol;\n\t\tthis.precedence = precedence;\n\t\tthis.operandCount = operandCount == 1 ? 1 : 2;\n\t}\n\n\t/**\n\t * create a left associative {@link CustomOperator} with precedence value of 1 that uses two operands\n\t * \n\t * @param symbol\n\t *            the {@link String} to use a symbol for this operation\n\t */\n\tCustomOperator(final String symbol) {\n\t\tsuper();\n\t\tthis.leftAssociative = true;\n\t\tthis.symbol = symbol;\n\t\tthis.precedence = 1;\n\t\tthis.operandCount = 2;\n\t}\n\n\t/**\n\t * create a left associative {@link CustomOperator} for two operands\n\t * \n\t * @param symbol the {@link String} to use a symbol for this operation\n\t * @param precedence the precedence of the operation\n\t */\n\tCustomOperator(final String symbol, final int precedence) {\n\t\tsuper();\n\t\tthis.leftAssociative = true;\n\t\tthis.symbol = symbol;\n\t\tthis.precedence = precedence;\n\t\tthis.operandCount = 2;\n\t}\n\n\t/**\n\t * Apply the custom operation on the two operands and return the result as an double An example implementation for a\n\t * multiplication could look like this:\n\t * \n\t * <pre>\n\t * {@code\n\t *       double applyOperation(double[] values) {\n\t *           return values[0]*values[1];\n\t *       }\n\t * }</pre>\n\t *\n\t * @param values\n\t *            the operands for the operation. If the {@link CustomOperator} uses only one operand such as a\n\t *            factorial the operation has to be applied to the first element of the values array. If the\n\t *            {@link CustomOperator} uses two operands the operation has to be applied to the first two items in the\n\t *            values array, with special care given to the operator associativity. The operand to the left of the\n\t *            symbol is the first element in the array while the operand to the right is the second element of the\n\t *            array.\n\t * @return the result of the operation\n\t */\n\tpublic abstract double applyOperation(double[] values);\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/eval/ExpressionBuilder.java",
    "content": "package ru.trolsoft.calculator.eval;\n\nimport ru.trolsoft.calculator.eval.exceptions.InvalidCustomFunctionException;\nimport ru.trolsoft.calculator.eval.exceptions.UnknownFunctionException;\nimport ru.trolsoft.calculator.eval.exceptions.UnparsableExpressionException;\n\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static ru.trolsoft.calculator.eval.RPNConverterKt.toRPNExpression;\n\n/**\n * This is a Builder implementation for the exp4j API used to create a Calculable instance for the user\n * \n * @author frank asseg\n * \n */\npublic class ExpressionBuilder {\n\n\t/**\n\t * Property name for unary precedence choice. You can set System.getProperty(PROPERTY_UNARY_HIGH_PRECEDENCE,\"false\")\n\t * in order to change evaluation from an expression like \"-3^2\" from \"(-3)^2\" to \"-(3^2)\"\n\t */\n\tpublic static final String PROPERTY_UNARY_HIGH_PRECEDENCE = \"exp4j.unary.precedence.high\";\n\n\tprivate final Map<String, Double> variables = new LinkedHashMap<>();\n\n\tprivate final Map<String, CustomFunction> customFunctions;\n\n\tprivate final Map<String, CustomOperator> builtInOperators;\n\n\tprivate final Map<String, CustomOperator> customOperators = new HashMap<>();\n\n\tprivate final List<Character> validOperatorSymbols;\n\n\tprivate final boolean highUnaryPrecedence;\n\n\tprivate String expression;\n\n\t/**\n\t * create a new ExpressionBuilder\n\t * \n\t * @param expression\n\t *            the expression to evaluate \n\t */\n\tpublic ExpressionBuilder(String expression) {\n\t\tif (expression.trim().isEmpty()) {\n\t\t\tthrow new IllegalArgumentException(\"Expression can not be empty!.\");\n\t\t}\n\t\tthis.expression = expression;\n\t\thighUnaryPrecedence = System.getProperty(PROPERTY_UNARY_HIGH_PRECEDENCE) == null\n\t\t\t\t|| !System.getProperty(PROPERTY_UNARY_HIGH_PRECEDENCE).equals(\"false\");\n\t\tcustomFunctions = getBuiltinFunctions();\n\t\tbuiltInOperators = getBuiltinOperators();\n\t\tvalidOperatorSymbols = getValidOperators();\n\t}\n\n\tprivate List<Character> getValidOperators() {\n\t\treturn Arrays.asList('!', '#', '§', '$', '&', ';', ':', '~', '<', '>', '|', '=', '^');\n\t}\n\n\tprivate Map<String, CustomOperator> getBuiltinOperators() {\n\t\tCustomOperator add = new CustomOperator(\"+\") {\n\t\t\t@Override\n\t\t\tpublic double applyOperation(double[] values) {\n\t\t\t\treturn values[0] + values[1];\n\t\t\t}\n\t\t};\n\t\tCustomOperator sub = new CustomOperator(\"-\") {\n\t\t\t@Override\n\t\t\tpublic double applyOperation(double[] values) {\n\t\t\t\treturn values[0] - values[1];\n\t\t\t}\n\t\t};\n\t\tCustomOperator div = new CustomOperator(\"/\", 3) {\n\t\t\t@Override\n\t\t\tpublic double applyOperation(double[] values) {\n\t\t\t\tif (values[1] == 0d) {\n\t\t\t\t\tthrow new ArithmeticException(\"Division by zero!\");\n\t\t\t\t}\n\t\t\t\treturn values[0] / values[1];\n\t\t\t}\n\t\t};\n\t\tCustomOperator mul = new CustomOperator(\"*\", 3) {\n\t\t\t@Override\n\t\t\tpublic double applyOperation(double[] values) {\n\t\t\t\treturn values[0] * values[1];\n\t\t\t}\n\t\t};\n\t\tCustomOperator mod = new CustomOperator(\"%\", true, 3) {\n\t\t\t@Override\n\t\t\tpublic double applyOperation(double[] values) {\n\t\t\t\tif (values[1] == 0d){\n\t\t\t\t\tthrow new ArithmeticException(\"Division by zero!\");\n\t\t\t\t}\n\t\t\t\treturn values[0] % values[1];\n\t\t\t}\n\t\t};\n\t\tCustomOperator umin = new CustomOperator(\"'\", false, this.highUnaryPrecedence ? 7 : 5, 1) {\n\t\t\t@Override\n\t\t\tpublic double applyOperation(double[] values) {\n\t\t\t\treturn -values[0];\n\t\t\t}\n\t\t};\n\t\tCustomOperator pow = new CustomOperator(\"^\", false, 5, 2) {\n\t\t\t@Override\n\t\t\tpublic double applyOperation(double[] values) {\n\t\t\t\treturn Math.pow(values[0], values[1]);\n\t\t\t}\n\t\t};\n\t\tMap<String, CustomOperator> operations = new HashMap<>();\n\t\toperations.put(\"+\", add);\n\t\toperations.put(\"-\", sub);\n\t\toperations.put(\"*\", mul);\n\t\toperations.put(\"/\", div);\n\t\toperations.put(\"'\", umin);\n\t\toperations.put(\"^\", pow);\n\t\toperations.put(\"%\", mod);\n\t\treturn operations;\n\t}\n\n\tprivate Map<String, CustomFunction> getBuiltinFunctions() {\n\t\ttry {\n\t\t\tCustomFunction abs = new CustomFunction(\"abs\") {\n\t\t\t\t@Override\n\t\t\t\tpublic double applyFunction(double... args) {\n\t\t\t\t\treturn Math.abs(args[0]);\n\t\t\t\t}\n\t\t\t};\n\t\t\tCustomFunction acos = new CustomFunction(\"acos\") {\n\t\t\t\t@Override\n\t\t\t\tpublic double applyFunction(double... args) {\n\t\t\t\t\treturn Math.acos(args[0]);\n\t\t\t\t}\n\t\t\t};\n\t\t\tCustomFunction asin = new CustomFunction(\"asin\") {\n\t\t\t\t@Override\n\t\t\t\tpublic double applyFunction(double... args) {\n\t\t\t\t\treturn Math.asin(args[0]);\n\t\t\t\t}\n\t\t\t};\n\t\t\tCustomFunction atan = new CustomFunction(\"atan\") {\n\t\t\t\t@Override\n\t\t\t\tpublic double applyFunction(double... args) {\n\t\t\t\t\treturn Math.atan(args[0]);\n\t\t\t\t}\n\t\t\t};\n\t\t\tCustomFunction cbrt = new CustomFunction(\"cbrt\") {\n\t\t\t\t@Override\n\t\t\t\tpublic double applyFunction(double... args) {\n\t\t\t\t\treturn Math.cbrt(args[0]);\n\t\t\t\t}\n\t\t\t};\n\t\t\tCustomFunction ceil = new CustomFunction(\"ceil\") {\n\t\t\t\t@Override\n\t\t\t\tpublic double applyFunction(double... args) {\n\t\t\t\t\treturn Math.ceil(args[0]);\n\t\t\t\t}\n\t\t\t};\n\t\t\tCustomFunction cos = new CustomFunction(\"cos\") {\n\t\t\t\t@Override\n\t\t\t\tpublic double applyFunction(double... args) {\n\t\t\t\t\treturn Math.cos(args[0]);\n\t\t\t\t}\n\t\t\t};\n\t\t\tCustomFunction cosh = new CustomFunction(\"cosh\") {\n\t\t\t\t@Override\n\t\t\t\tpublic double applyFunction(double... args) {\n\t\t\t\t\treturn Math.cosh(args[0]);\n\t\t\t\t}\n\t\t\t};\n\t\t\tCustomFunction exp = new CustomFunction(\"exp\") {\n\t\t\t\t@Override\n\t\t\t\tpublic double applyFunction(double... args) {\n\t\t\t\t\treturn Math.exp(args[0]);\n\t\t\t\t}\n\t\t\t};\n\t\t\tCustomFunction expm1 = new CustomFunction(\"expm1\") {\n\t\t\t\t@Override\n\t\t\t\tpublic double applyFunction(double... args) {\n\t\t\t\t\treturn Math.expm1(args[0]);\n\t\t\t\t}\n\t\t\t};\n\t\t\tCustomFunction floor = new CustomFunction(\"floor\") {\n\t\t\t\t@Override\n\t\t\t\tpublic double applyFunction(double... args) {\n\t\t\t\t\treturn Math.floor(args[0]);\n\t\t\t\t}\n\t\t\t};\n\t\t\tCustomFunction log = new CustomFunction(\"log\") {\n\t\t\t\t@Override\n\t\t\t\tpublic double applyFunction(double... args) {\n\t\t\t\t\treturn Math.log(args[0]);\n\t\t\t\t}\n\t\t\t};\n\t\t\tCustomFunction sine = new CustomFunction(\"sin\") {\n\t\t\t\t@Override\n\t\t\t\tpublic double applyFunction(double... args) {\n\t\t\t\t\treturn Math.sin(args[0]);\n\t\t\t\t}\n\t\t\t};\n\t\t\tCustomFunction sinh = new CustomFunction(\"sinh\") {\n\t\t\t\t@Override\n\t\t\t\tpublic double applyFunction(double... args) {\n\t\t\t\t\treturn Math.sinh(args[0]);\n\t\t\t\t}\n\t\t\t};\n\t\t\tCustomFunction sqrt = new CustomFunction(\"sqrt\") {\n\t\t\t\t@Override\n\t\t\t\tpublic double applyFunction(double... args) {\n\t\t\t\t\treturn Math.sqrt(args[0]);\n\t\t\t\t}\n\t\t\t};\n\t\t\tCustomFunction tan = new CustomFunction(\"tan\") {\n\t\t\t\t@Override\n\t\t\t\tpublic double applyFunction(double... args) {\n\t\t\t\t\treturn Math.tan(args[0]);\n\t\t\t\t}\n\t\t\t};\n\t\t\tCustomFunction tanh = new CustomFunction(\"tanh\") {\n\t\t\t\t@Override\n\t\t\t\tpublic double applyFunction(double... args) {\n\t\t\t\t\treturn Math.tanh(args[0]);\n\t\t\t\t}\n\t\t\t};\n\t\t\tMap<String, CustomFunction> customFunctions = new HashMap<>();\n\t\t\tcustomFunctions.put(\"abs\", abs);\n\t\t\tcustomFunctions.put(\"acos\", acos);\n\t\t\tcustomFunctions.put(\"asin\", asin);\n\t\t\tcustomFunctions.put(\"atan\", atan);\n\t\t\tcustomFunctions.put(\"cbrt\", cbrt);\n\t\t\tcustomFunctions.put(\"ceil\", ceil);\n\t\t\tcustomFunctions.put(\"cos\", cos);\n\t\t\tcustomFunctions.put(\"cosh\", cosh);\n\t\t\tcustomFunctions.put(\"exp\", exp);\n\t\t\tcustomFunctions.put(\"expm1\", expm1);\n\t\t\tcustomFunctions.put(\"floor\", floor);\n\t\t\tcustomFunctions.put(\"log\", log);\n\t\t\tcustomFunctions.put(\"sin\", sine);\n\t\t\tcustomFunctions.put(\"sinh\", sinh);\n\t\t\tcustomFunctions.put(\"sqrt\", sqrt);\n\t\t\tcustomFunctions.put(\"tan\", tan);\n\t\t\tcustomFunctions.put(\"tanh\", tanh);\n\t\t\treturn customFunctions;\n\t\t} catch (InvalidCustomFunctionException e) {\n\t\t\t// this should not happen...\n\t\t\tthrow new RuntimeException(e);\n\t\t}\n\t}\n\n\t/**\n\t * build a new {@link Calculable} from the expression using the supplied variables\n\t * \n\t * @return the {@link Calculable} which can be used to evaluate the expression\n\t * @throws UnknownFunctionException\n\t *             when an unrecognized function name is used in the expression\n\t * @throws UnparsableExpressionException\n\t *             if the expression could not be parsed\n\t */\n\tpublic Calculable build() throws UnknownFunctionException, UnparsableExpressionException {\n\t\tfor (CustomOperator op : customOperators.values()) {\n\t\t\tfor (int i = 0; i < op.symbol.length(); i++) {\n\t\t\t\tif (!validOperatorSymbols.contains(op.symbol.charAt(i))) {\n\t\t\t\t    //TODO - change non-ascii paragraph sign to '§'\n\t\t\t\t\tthrow new UnparsableExpressionException(op.symbol\n\t\t\t\t\t\t\t+ \" is not a valid symbol for an operator please choose from: !,#,§,$,&,;,:,~,<,>,|,=\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor (String varName : variables.keySet()) {\n\t\t\tcheckVariableName(varName);\n\t\t\tif (customFunctions.containsKey(varName)) {\n\t\t\t\tthrow new UnparsableExpressionException(\"Variable '\" + varName+ \"' cannot have the same name as a function\");\n\t\t\t}\n\t\t}\n\t\tbuiltInOperators.putAll(customOperators);\n\t\treturn toRPNExpression(expression, variables, customFunctions, builtInOperators);\n\t}\n\n\tprivate void checkVariableName(String varName) throws UnparsableExpressionException {\n\t\tchar[] name = varName.toCharArray();\n\t\tfor (int i = 0; i < name.length; i++) {\n\t\t\tif (i == 0) {\n\t\t\t\tif (!Character.isLetter(name[i]) && name[i] != '_') {\n\t\t\t\t\tthrow new UnparsableExpressionException(varName + \" is not a valid variable name: character '\"\n\t\t\t\t\t\t\t+ name[i] + \" at \" + i);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (!Character.isLetter(name[i]) && !Character.isDigit(name[i]) && name[i] != '_') {\n\t\t\t\t\tthrow new UnparsableExpressionException(varName + \" is not a valid variable name: character '\"\n\t\t\t\t\t\t\t+ name[i] + \" at \" + i);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Add a custom function instance for the evaluator to recognize\n\t * \n\t * @param function the {@link CustomFunction} to add\n\t * @return the {@link ExpressionBuilder} instance\n\t */\n\tpublic ExpressionBuilder withCustomFunction(CustomFunction function) {\n\t\tcustomFunctions.put(function.name, function);\n\t\treturn this;\n\t}\n\n\tpublic ExpressionBuilder withCustomFunctions(Collection<CustomFunction> functions) {\n\t\tfor (CustomFunction f : functions) {\n\t\t\twithCustomFunction(f);\n\t\t}\n\t\treturn this;\n\t}\n\n\t/**\n\t * Set the value for a variable\n\t * \n\t * @param variableName the variable name e.g. \"x\"\n\t * @param value the value e.g. 2.32d\n\t * @return the {@link ExpressionBuilder} instance\n\t */\n\tpublic ExpressionBuilder withVariable(String variableName, double value) {\n\t\tvariables.put(variableName, value);\n\t\treturn this;\n\t}\n\n\t/**\n\t * set the variables names used in the expression without setting their values\n\t * \n\t * @param variableNames vararg {@link String} of the variable names used in the expression\n\t * @return the ExpressionBuilder instance\n\t */\n\tpublic ExpressionBuilder withVariableNames(String... variableNames) {\n\t\tfor (String variable : variableNames) {\n\t\t\tvariables.put(variable, null);\n\t\t}\n\t\treturn this;\n\t}\n\n\t/**\n\t * set the values for variables\n\t * \n\t * @param variableMap\n\t *            a map of variable names to variable values\n\t * @return the {@link ExpressionBuilder} instance\n\t */\n\tpublic ExpressionBuilder withVariables(Map<String, Double> variableMap) {\n        variables.putAll(variableMap);\n\t\treturn this;\n\t}\n\n\t/**\n\t * set a {@link CustomOperator} to be used in the expression\n\t * \n\t * @param operation the {@link CustomOperator} to be used\n\t * @return the {@link ExpressionBuilder} instance\n\t */\n\tpublic ExpressionBuilder withOperation(CustomOperator operation) {\n\t\tcustomOperators.put(operation.symbol, operation);\n\t\treturn this;\n\t}\n\n\t/**\n\t * set a {@link Collection} of {@link CustomOperator} to use in the expression\n\t * \n\t * @param operations the {@link Collection} of {@link CustomOperator} to use\n\t * @return the {@link ExpressionBuilder} instance\n\t */\n\tpublic ExpressionBuilder withOperations(Collection<CustomOperator> operations) {\n\t\tfor (CustomOperator op : operations) {\n\t\t\twithOperation(op);\n\t\t}\n\t\treturn this;\n\t}\n\n\t/**\n\t * set the mathematical expression for parsing\n\t * \n\t * @param expression\n\t *            a mathematical expression\n\t * @return the {@link ExpressionBuilder} instance\n\t */\n\tpublic ExpressionBuilder withExpression(String expression) {\n\t\tthis.expression = expression;\n\t\treturn this;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/eval/RPNConverter.kt",
    "content": "package ru.trolsoft.calculator.eval\n\nimport ru.trolsoft.calculator.eval.exceptions.UnknownFunctionException\nimport ru.trolsoft.calculator.eval.exceptions.UnparsableExpressionException\nimport ru.trolsoft.calculator.eval.tokens.*\nimport java.util.*\n\n//internal object RPNConverter {\n\n    private fun substituteUnaryOperators(expr: String, operators: MutableMap<String, CustomOperator?>): String {\n        val resultBuilder = StringBuilder()\n        var whitespaceCount = 0\n        for (i in 0..<expr.length) {\n            var afterOperator = false\n            var afterParantheses = false\n            var expressionStart = false\n            val c = expr.get(i)\n            if (Character.isWhitespace(c)) {\n                whitespaceCount++\n                resultBuilder.append(c)\n                continue\n            }\n            if (resultBuilder.length == whitespaceCount) {\n                expressionStart = true\n            }\n            // check if last char in the result is an operator\n            if (resultBuilder.length > whitespaceCount) {\n                if (isOperatorCharacter(resultBuilder.get(resultBuilder.length - 1 - whitespaceCount), operators)) {\n                    afterOperator = true\n                } else if (resultBuilder.get(resultBuilder.length - 1 - whitespaceCount) == '(') {\n                    afterParantheses = true\n                }\n            }\n            when (c) {\n                '+' -> if (!resultBuilder.isEmpty() && !afterOperator && !afterParantheses && !expressionStart) {\n                    // not an unary plus so append the char\n                    resultBuilder.append(c)\n                }\n\n                '-' -> if (!resultBuilder.isEmpty() && !afterOperator && !afterParantheses && !expressionStart) {\n                    // not unary\n                    resultBuilder.append(c)\n                } else {\n                    // unary so we substitute it\n                    resultBuilder.append('\\'')\n                }\n\n                else -> resultBuilder.append(c)\n            }\n            whitespaceCount = 0\n        }\n        return resultBuilder.toString()\n    }\n\n\n    @Throws(UnknownFunctionException::class, UnparsableExpressionException::class)\n    fun toRPNExpression(\n        infix: String, variables: MutableMap<String?, Double?>,\n        customFunctions: MutableMap<String?, CustomFunction?>, operators: MutableMap<String, CustomOperator?>\n    ): RPNExpression {\n        val tokenizer = Tokenizer(variables.keys, customFunctions, operators)\n        val output = StringBuilder(infix.length)\n        val operatorStack = ArrayDeque<Token>()\n        var tokens: MutableList<Token> = tokenizer.getTokens(substituteUnaryOperators(infix, operators))\n        validateRPNExpression(tokens, operators)\n        for (token in tokens) {\n            token.mutateStackForInfixTranslation(operatorStack, output)\n        }\n        // all tokens read, put the rest of the operations on the output;\n        while (!operatorStack.isEmpty()) {\n            output.append(operatorStack.pop().value).append(\" \")\n        }\n        val postfix = output.toString().trim { it <= ' ' }\n        tokens = tokenizer.getTokens(postfix)\n        return RPNExpression(tokens, postfix, variables)\n    }\n\n    @Throws(UnparsableExpressionException::class)\n    private fun validateRPNExpression(tokens: MutableList<Token>, operators: MutableMap<String, CustomOperator?>?) {\n        for (i in 1..<tokens.size) {\n            val t = tokens[i]\n            if (tokens[i - 1] is NumberToken) {\n                if (t is VariableToken || (t is ParenthesesToken && t.isOpen()) || t is FunctionToken) {\n                    throw UnparsableExpressionException(\"Implicit multiplication is not supported. E.g. always use '2*x' instead of '2x'\")\n                }\n            }\n        }\n    }\n\n    private fun isOperatorCharacter(c: Char, operators: MutableMap<String, CustomOperator?>): Boolean {\n        for (symbol in operators.keys) {\n            if (symbol.indexOf(c) != -1) {\n                return true\n            }\n        }\n        return false\n    }\n//}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/eval/RPNExpression.java",
    "content": "package ru.trolsoft.calculator.eval;\n\nimport ru.trolsoft.calculator.eval.tokens.CalculationToken;\nimport ru.trolsoft.calculator.eval.tokens.Token;\n\nimport java.util.ArrayDeque;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Stack;\n\npublic class RPNExpression implements Calculable {\n\tfinal List<Token> tokens;\n\n\tfinal String expression;\n\n\tfinal Map<String, Double> variables;\n\n\tRPNExpression(List<Token> tokens, String expression, final Map<String, Double> variables) {\n\t\tsuper();\n\t\tthis.tokens = tokens;\n\t\tthis.expression = expression;\n\t\tthis.variables = variables;\n\t}\n\n\t/**\n\t * calculate the result of the expression and substitute the variables by their values beforehand\n\t *\n\t * @param values the variable values to be substituted\n\t * @return the result of the calculation\n\t * @throws IllegalArgumentException if the variables are invalid\n\t */\n\tpublic double calculate(double... values) throws IllegalArgumentException {\n\t\tif (variables.isEmpty() && values != null) {\n\t\t\tthrow new IllegalArgumentException(\"there are no variables to set values\");\n\t\t} else if (values != null && values.length != variables.size()) {\n\t\t\tthrow new IllegalArgumentException(\"The are an unequal number of variables and arguments\");\n\t\t}\n\t\tint i = 0;\n\t\tif (!variables.isEmpty() && values != null) {\n\t\t\tfor (Map.Entry<String, Double> entry : variables.entrySet()) {\n\t\t\t\tentry.setValue(values[i++]);\n\t\t\t}\n\t\t}\n\t\tfinal ArrayDeque<Double> stack = new ArrayDeque<>();\n\t\tfor (final Token t : tokens) {\n\t\t\t((CalculationToken) t).mutateStackForCalculation(stack, variables);\n\t\t}\n\t\treturn stack.pop();\n\t}\n\n\tpublic String getExpression() {\n\t\treturn expression;\n\t}\n\n\tpublic void setVariable(String name, double value) {\n\t\tthis.variables.put(name, value);\n\t}\n\n\tpublic double calculate() {\n\t\treturn calculate(null);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/eval/Tokenizer.kt",
    "content": "package ru.trolsoft.calculator.eval\n\nimport ru.trolsoft.calculator.eval.exceptions.UnknownFunctionException\nimport ru.trolsoft.calculator.eval.exceptions.UnparsableExpressionException\nimport ru.trolsoft.calculator.eval.tokens.FunctionSeparatorToken\nimport ru.trolsoft.calculator.eval.tokens.FunctionToken\nimport ru.trolsoft.calculator.eval.tokens.NumberToken\nimport ru.trolsoft.calculator.eval.tokens.OperatorToken\nimport ru.trolsoft.calculator.eval.tokens.ParenthesesToken\nimport ru.trolsoft.calculator.eval.tokens.Token\nimport ru.trolsoft.calculator.eval.tokens.VariableToken\nimport kotlin.math.abs\n\ninternal class Tokenizer(\n    private val variableNames: MutableSet<String?>?,\n    private val functions: MutableMap<String?, CustomFunction?>,\n    private val operators: MutableMap<String, CustomOperator?>\n) {\n    private fun isVariable(name: String): Boolean =\n        variableNames?.contains(name) ?: false\n\n    private fun isFunction(name: String): Boolean\n    = functions.containsKey(name)\n\n    private fun isOperatorCharacter(c: Char): Boolean {\n        for (symbol in operators.keys) {\n            if (symbol.indexOf(c) != -1) {\n                return true\n            }\n        }\n        return false\n    }\n\n    @Throws(UnparsableExpressionException::class, UnknownFunctionException::class)\n    fun getTokens(expression: String): MutableList<Token> {\n        val tokens = mutableListOf<Token>()\n        val chars = expression.toCharArray()\n        var openBraces = 0\n        var openCurly = 0\n        var openSquare = 0\n        // iterate over the chars and fork on different types of input\n        var lastToken: Token?\n        var i = 0\n        while (i < chars.size) {\n            val c = chars[i]\n            if (c == ' ') {\n                i++\n                continue\n            }\n            if (Character.isDigit(c)) {\n                val valueBuilder = StringBuilder(1)\n                // handle the numbers of the expression\n                valueBuilder.append(c)\n                var numberLen = 1\n                var lastCharExpNotationSeparator =\n                    false // needed to determine if a + or - following an e/E is a unary operation\n                var expNotationSeparatorOccurred = false // to check if only one E/e notation separator has occurred\n                var hexNotationPrefixOccurred = false // to check if only one '0x' notation prefix has occurred\n                var binNotationPrefixOccurred = false // to check if only one '0b' notation prefix has occurred\n                var octNotationPrefixOccurred = false // to check if only one '0' notation prefix has occurred\n                while (i + numberLen < chars.size) {\n                    val cc = chars[i + numberLen]\n                    if (c == '0' && numberLen == 1) {   // possibly binary, hex or octal notations\n                        if (cc == 'x' || cc == 'X') {           // hex\n                            hexNotationPrefixOccurred = true\n                            valueBuilder.append(cc)\n                            numberLen++\n                            continue\n                        } else if (cc == 'b' || cc == 'B') {    // binary\n                            binNotationPrefixOccurred = true\n                            valueBuilder.append(cc)\n                            numberLen++\n                            continue\n                        } else if (cc != '.') {                  // octal\n                            octNotationPrefixOccurred = true\n                        }\n                    }\n                    if (cc == '.') {\n                        if (hexNotationPrefixOccurred || binNotationPrefixOccurred || octNotationPrefixOccurred) {\n                            throw UnparsableExpressionException(\"Unexpected decimal separator\")\n                        }\n                        valueBuilder.append(cc)\n                        lastCharExpNotationSeparator = false\n                    } else if (Character.isDigit(cc)) {\n                        valueBuilder.append(cc)\n                        lastCharExpNotationSeparator = false\n                    } else if ((cc >= 'a' && cc <= 'f') || (cc >= 'A' && cc <= 'F')) {\n                        if (!hexNotationPrefixOccurred && (cc == 'e' || cc == 'E')) {\n                            if (expNotationSeparatorOccurred) {\n                                throw UnparsableExpressionException(\"Number can have only one notation separator 'e/E'\")\n                            }\n                            valueBuilder.append(cc)\n                            lastCharExpNotationSeparator = true\n                            expNotationSeparatorOccurred = true\n                        } else if (hexNotationPrefixOccurred) {\n                            valueBuilder.append(cc)\n                        } else {\n                            throw UnparsableExpressionException(\"Digit expected\")\n                        }\n                    } else if (lastCharExpNotationSeparator && (cc == '-' || cc == '+')) {\n                        valueBuilder.append(chars[i + numberLen])\n                        lastCharExpNotationSeparator = false\n                    } else if (cc != '_') {\n                        break // break out of the while loop here, since the number seem finished\n                    }\n                    numberLen++\n                }\n                i += numberLen - 1\n                lastToken = NumberToken(valueBuilder.toString())\n            } else if (Character.isLetter(c) || c == '_') {\n                // can be a variable or function\n                val nameBuilder = StringBuilder()\n                nameBuilder.append(c)\n                var offset = 1\n                while (i + offset < chars.size && (Character.isLetter(chars[i + offset]) || Character.isDigit(chars[i + offset]) || chars[i + offset] == '_')) {\n                    nameBuilder.append(chars[i + offset++])\n                }\n                val name = nameBuilder.toString()\n                if (isVariable(name)) {\n                    // a variable\n                    i += offset - 1\n                    lastToken = VariableToken(name)\n                } else if (this.isFunction(name)) {\n                    // might be a function\n                    i += offset - 1\n                    lastToken = FunctionToken(name, functions.get(name)!!)\n                } else {\n                    // an unknown symbol was encountered\n                    throw UnparsableExpressionException(expression, c, i + 1)\n                }\n            } else if (c == ',') {\n                // a function separator, hopefully\n                lastToken = FunctionSeparatorToken()\n            } else if (isOperatorCharacter(c)) {\n                // might be an operation\n                val symbolBuilder = StringBuilder()\n                symbolBuilder.append(c)\n                var offset = 1\n                while (chars.size > i + offset && (isOperatorCharacter(chars[i + offset]))\n                    && isOperatorStart(symbolBuilder.toString() + chars[i + offset])\n                ) {\n                    symbolBuilder.append(chars[i + offset])\n                    offset++\n                }\n                val symbol = symbolBuilder.toString()\n                if (operators.containsKey(symbol)) {\n                    i += offset - 1\n                    lastToken = OperatorToken(symbol, operators.get(symbol)!!)\n                } else {\n                    throw UnparsableExpressionException(expression, c, i + 1)\n                }\n            } else if (c == '(') {\n                openBraces++\n                lastToken = ParenthesesToken(c.toString())\n            } else if (c == '{') {\n                openCurly++\n                lastToken = ParenthesesToken(c.toString())\n            } else if (c == '[') {\n                openSquare++\n                lastToken = ParenthesesToken(c.toString())\n            } else if (c == ')') {\n                openBraces--\n                lastToken = ParenthesesToken(c.toString())\n            } else if (c == '}') {\n                openCurly--\n                lastToken = ParenthesesToken(c.toString())\n            } else if (c == ']') {\n                openSquare--\n                lastToken = ParenthesesToken(c.toString())\n            } else {\n                // an unknown symbol was encountered\n                throw UnparsableExpressionException(expression, c, i + 1)\n            }\n            tokens.add(lastToken)\n            i++\n        }\n        if (openCurly != 0 || (openBraces != 0) or (openSquare != 0)) {\n            val errorBuilder = StringBuilder()\n            errorBuilder.append(\"There are \")\n            var first = true\n            if (openBraces != 0) {\n                errorBuilder.append(abs(openBraces)).append(\" unmatched parantheses \")\n                first = false\n            }\n            if (openCurly != 0) {\n                if (!first) {\n                    errorBuilder.append(\" and \")\n                }\n                errorBuilder.append(abs(openCurly)).append(\" unmatched curly brackets \")\n                first = false\n            }\n            if (openSquare != 0) {\n                if (!first) {\n                    errorBuilder.append(\" and \")\n                }\n                errorBuilder.append(abs(openSquare)).append(\" unmatched square brackets \")\n                first = false\n            }\n            errorBuilder.append(\"in expression '\").append(expression).append(\"'\")\n            throw UnparsableExpressionException(errorBuilder.toString())\n        }\n        return tokens\n    }\n\n    private fun isOperatorStart(op: String): Boolean {\n        operators.keys.forEach {\n            if (it.startsWith(op)) {\n                return true\n            }\n        }\n        return false\n    }\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/eval/exceptions/InvalidCustomFunctionException.kt",
    "content": "package ru.trolsoft.calculator.eval.exceptions\n\nclass InvalidCustomFunctionException(message: String?) : Exception(message)\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/eval/exceptions/UnknownFunctionException.java",
    "content": "/*\n   Copyright 2011 frank asseg\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n */\npackage ru.trolsoft.calculator.eval.exceptions;\n\nimport ru.trolsoft.calculator.eval.tokens.FunctionToken;\n\n/**\n * Exception for handling unknown Functions.\n * \n * @see FunctionToken\n * @author fas@congrace.de\n */\npublic class UnknownFunctionException extends Exception {\n\t/**\n\t * construct a new {@link UnknownFunctionException}\n\t * \n\t * @param functionName the function name which could not be found\n\t */\n\tpublic UnknownFunctionException(String functionName) {\n\t\tsuper(\"Unknown function: \" + functionName);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/eval/exceptions/UnparsableExpressionException.java",
    "content": "/*\n   Copyright 2011 frank asseg\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n */\npackage ru.trolsoft.calculator.eval.exceptions;\n\n/**\n * Exception for invalid expressions\n * \n * @author fas@congrace.de\n */\npublic class UnparsableExpressionException extends Exception {\n\t/**\n\t * construct a new {@link UnparsableExpressionException}\n\t * \n\t * @param c\n\t *            the character which could not be parsed\n\t * @param pos\n\t *            the position of the character in the expression\n\t */\n\tpublic UnparsableExpressionException(String expression, char c, int pos) {\n\t\tsuper(\"Unable to parse character '\" + c + \"' at position \" + pos + \" in expression '\" + expression + \"'\");\n\t}\n\n\t/**\n\t * construct a new {@link UnparsableExpressionException}\n\t * \n\t * @param msg\n\t *            the error message\n\t */\n\tpublic UnparsableExpressionException(String msg) {\n\t\tsuper(msg);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/eval/tokens/CalculationToken.kt",
    "content": "package ru.trolsoft.calculator.eval.tokens\n\nimport java.util.*\n\nabstract class CalculationToken internal constructor(value: String) : Token(value) {\n    abstract fun mutateStackForCalculation(stack: ArrayDeque<Double?>, variableValues: MutableMap<String?, Double?>)\n}"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/eval/tokens/FunctionSeparatorToken.kt",
    "content": "package ru.trolsoft.calculator.eval.tokens\n\nimport java.util.*\n\nclass FunctionSeparatorToken : Token(\",\") {\n    override fun mutateStackForInfixTranslation(operatorStack: ArrayDeque<Token>, output: StringBuilder) {\n        var token: Token\n        while ((operatorStack.peek().also { token = it }) !is ParenthesesToken && token.value != \"(\") {\n            output.append(operatorStack.pop().value).append(\" \")\n        }\n    }\n}"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/eval/tokens/FunctionToken.kt",
    "content": "package ru.trolsoft.calculator.eval.tokens\n\nimport ru.trolsoft.calculator.eval.CustomFunction\nimport ru.trolsoft.calculator.eval.exceptions.UnknownFunctionException\nimport ru.trolsoft.calculator.eval.reverseArray\nimport java.util.*\n\nclass FunctionToken(value: String, function: CustomFunction) : CalculationToken(value) {\n    val name: String\n    val function: CustomFunction\n\n    init {\n        try {\n            this.name = function.name\n            this.function = function\n        } catch (_: IllegalArgumentException) {\n            throw UnknownFunctionException(value)\n        }\n    }\n\n    override fun equals(other: Any?): Boolean {\n        if (other is FunctionToken) {\n            return this.name == other.name\n        }\n        return false\n    }\n\n    override fun hashCode(): Int = name.hashCode()\n\n    override fun mutateStackForCalculation(stack: ArrayDeque<Double?>, variableValues: MutableMap<String?, Double?>) {\n        val args = DoubleArray(function.argc)\n        for (i in 0..< function.argc) {\n            args[i] = stack.pop()!!\n        }\n        stack.push(this.function.applyFunction(*reverseArray(args)))\n    }\n\n    override fun mutateStackForInfixTranslation(operatorStack: ArrayDeque<Token>, output: StringBuilder) {\n        operatorStack.push(this)\n    }\n}"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/eval/tokens/NumberToken.kt",
    "content": "/*\n   Copyright 2011 frank asseg\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n */\npackage ru.trolsoft.calculator.eval.tokens\n\nimport java.util.*\nimport kotlin.math.pow\n\n/**\n * A [Token] for Numbers\n * \n * @author fas@congrace.de\n */\nclass NumberToken(value: String) : CalculationToken(value) {\n    private val doubleValue: Double\n\n    init {\n        var value = value\n        var isHex = false\n        var isBin = false\n        var isOct = false\n        if (value.length > 1 && value[0] == '0') {\n            val ch2 = value[1]\n            if (ch2 == 'x' || ch2 == 'X') {\n                isHex = true\n                value = value.substring(2)\n            } else if (ch2 == 'b' || ch2 == 'B') {\n                isBin = true\n                value = value.substring(2)\n            } else if (ch2 != '.') {\n                isOct = true\n                value = value.substring(1)\n            }\n        }\n        if (isHex) {\n            if (value.indexOf('_') >= 0) {\n                value = value.replace(\"_\", \"\")\n            }\n            this.doubleValue = value.toLong(16).toDouble()\n        } else if (isBin) {\n            if (value.indexOf('_') >= 0) {\n                value = value.replace(\"_\", \"\")\n            }\n            this.doubleValue = value.toLong(2).toDouble()\n        } else if (isOct) {\n            if (value.indexOf('_') >= 0) {\n                value = value.replace(\"_\", \"\")\n            }\n            this.doubleValue = value.toLong(8).toDouble()\n        } else if (value.indexOf('E') > 0 || value.indexOf('e') > 0) {\n            //scientific notation as requested in EXP-17\n            value = value.lowercase(Locale.getDefault())\n            val pos = value.indexOf('e')\n            val mantissa = value.substring(0, pos).toDouble()\n            val exponent = value.substring(pos + 1).toDouble()\n            this.doubleValue = mantissa * 10.0.pow(exponent)\n        } else {\n            if (value.indexOf('_') >= 0) {\n                value = value.replace(\"_\", \"\")\n            }\n            this.doubleValue = value.toDouble()\n        }\n    }\n\n    override fun equals(other: Any?): Boolean {\n        if (other is NumberToken) {\n            return other.value == this.value\n        }\n        return false\n    }\n\n    override fun hashCode(): Int = value.hashCode()\n\n\n    override fun mutateStackForCalculation(stack: ArrayDeque<Double?>, variableValues: MutableMap<String?, Double?>) {\n        stack.push(this.doubleValue)\n    }\n\n    override fun mutateStackForInfixTranslation(operatorStack: ArrayDeque<Token>, output: StringBuilder) {\n        output.append(this.value).append(' ')\n    }\n}"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/eval/tokens/OperatorToken.kt",
    "content": "/*\n   Copyright 2011 frank asseg\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n */\npackage ru.trolsoft.calculator.eval.tokens\n\nimport ru.trolsoft.calculator.eval.CustomOperator\nimport java.util.*\n\n/**\n * [Token] for Operations like +,-,*,/,% and ^\n * \n * @author fas@congrace.de\n */\nclass OperatorToken(\n    value: String,\n    var operation: CustomOperator\n) : CalculationToken(value) {\n    fun applyOperation(vararg values: Double): Double =\n        operation.applyOperation(values)\n\n    override fun equals(other: Any?): Boolean {\n        if (other is OperatorToken) {\n            return other.value == this.value\n        }\n        return false\n    }\n\n    override fun hashCode(): Int = value.hashCode()\n\n    override fun mutateStackForCalculation(stack: ArrayDeque<Double?>, variableValues: MutableMap<String?, Double?>) {\n        val operands = DoubleArray(operation.operandCount)\n        for (i in 0..< operation.operandCount) {\n            operands[operation.operandCount - i - 1] = stack.pop()!!\n        }\n        stack.push(operation.applyOperation(operands))\n    }\n\n    override fun mutateStackForInfixTranslation(operatorStack: ArrayDeque<Token>, output: StringBuilder) {\n        while (!operatorStack.isEmpty()) {\n            val before = operatorStack.peek()\n            if (before is FunctionToken) {\n                operatorStack.pop()\n                output.append(before.value).append(\" \")\n            } else if (before is OperatorToken) {\n                if (isLeftAssociative() && precedence() <= before.precedence()) {\n                    output.append(operatorStack.pop().value).append(\" \")\n                } else if (!isLeftAssociative() && precedence() < before.precedence()) {\n                    output.append(operatorStack.pop().value).append(\" \")\n                } else {\n                    break\n                }\n            } else {\n                break\n            }\n        }\n        operatorStack.push(this)\n    }\n\n    private fun isLeftAssociative(): Boolean = operation.leftAssociative\n    private fun precedence(): Int = operation.precedence\n}"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/eval/tokens/ParenthesesToken.kt",
    "content": "/*\n   Copyright 2011 frank asseg\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n */\npackage ru.trolsoft.calculator.eval.tokens\n\nimport java.util.*\n\n/**\n * Token for parenthesis\n * \n * @author fas@congrace.de\n */\nclass ParenthesesToken(value: String) : Token(value) {\n    override fun equals(other: Any?): Boolean {\n        if (other is ParenthesesToken) {\n            return other.value == this.value\n        }\n        return false\n    }\n\n    override fun hashCode(): Int = value.hashCode()\n\n    fun isOpen(): Boolean = value == \"(\" || value == \"[\" || value == \"{\"\n\n\n    override fun mutateStackForInfixTranslation(operatorStack: ArrayDeque<Token>, output: StringBuilder) {\n        if (isOpen()) {\n            operatorStack.push(this)\n        } else {\n            var next: Token?\n            while ((operatorStack.peek().also { next = it }) is OperatorToken || next is FunctionToken || (next is ParenthesesToken && !next.isOpen())) {\n                output.append(operatorStack.pop().value).append(\" \")\n            }\n            operatorStack.pop()\n        }\n    }\n}"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/eval/tokens/Token.kt",
    "content": "package ru.trolsoft.calculator.eval.tokens\n\nimport java.util.*\n\nabstract class Token\n    internal constructor(\n        @JvmField val value: String\n    )\n{\n    abstract fun mutateStackForInfixTranslation(operatorStack: ArrayDeque<Token>, output: StringBuilder)\n}"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/eval/tokens/VariableToken.kt",
    "content": "/*\n   Copyright 2011 frank asseg\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n */\npackage ru.trolsoft.calculator.eval.tokens\n\nimport java.util.*\n\nclass VariableToken(value: String) : CalculationToken(value) {\n    override fun equals(other: Any?): Boolean {\n        if (other is VariableToken) {\n            return super.value == other.value\n        }\n        return false\n    }\n\n    override fun hashCode(): Int = super.value.hashCode()\n\n    override fun mutateStackForCalculation(stack: ArrayDeque<Double?>, variableValues: MutableMap<String?, Double?>) {\n        val value = variableValues[value]!!\n        stack.push(value)\n    }\n\n    override fun mutateStackForInfixTranslation(operatorStack: ArrayDeque<Token>, output: StringBuilder) {\n        output.append(this.value).append(\" \")\n    }\n}"
  },
  {
    "path": "src/main/java/ru/trolsoft/calculator/eval/utils.kt",
    "content": "package ru.trolsoft.calculator.eval\n\nimport java.util.Locale\n\nfun reverseArray(data: DoubleArray): DoubleArray {\n    var left = 0\n    var right = data.size - 1\n\n    while (left < right) {\n        // swap the values at the left and right indices\n        val temp = data[left]\n        data[left] = data[right]\n        data[right] = temp\n\n        // move the left and right index pointers in toward the center\n        left++\n        right--\n    }\n    return data\n}\n\n\n/**\n * normalize a number to an acceptable format for exp4j e.g. normalizing \"314e-2\" yields \"3.14\"\n */\nfun normalizeNumber(number: String, loc: Locale? = Locale.getDefault()): String =\n    number.replace(\"e|E\".toRegex(), \"*10^\")\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/hexeditor/data/AbstractByteBuffer.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2017 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.hexeditor.data;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.io.IOException;\n\n/**\n * Buffered file reader\n */\npublic abstract class AbstractByteBuffer {\n\n    /**\n     * Стратегия кеширования при чтении\n     */\n    public enum CacheStrategy {\n        FORWARD,\n        BACKWARD,\n        CENTER\n    }\n\n    /**\n     *\n     */\n    static final int DEFAULT_CAPACITY = 1024*256;\n\n\n    /**\n     * Size of buffer\n     */\n    @Getter\n    protected final int capacity;\n\n    /**\n     * Number of bytes in buffer\n     */\n    protected int bufferSize;\n    @Getter\n    protected long offset;\n    protected byte[] buffer;\n\n    /**\n     * Size of file\n     */\n    protected long streamSize;\n\n\n    @Getter\n    @Setter\n    private CacheStrategy cacheStrategy = CacheStrategy.CENTER;\n\n    public AbstractByteBuffer(int capacity) {\n        this.capacity = capacity;\n        buffer = new byte[capacity];\n        this.offset = 0;\n        this.bufferSize = 0;\n        this.streamSize = -1;\n    }\n\n    public byte getByte(long fileOffset) throws IOException {\n        long index = fileOffset - offset;\n        if (index < 0 || index >= bufferSize) {\n            if (fileOffset < 0 || fileOffset >= getFileSize()) {\n                throw new IndexOutOfBoundsException(\"Position: \" + fileOffset + \", file size = \" + getFileSize());\n            }\n            offset = calcOffset(fileOffset, supportRandomAccess());\n            // FIXME if offset > size\n            if (offset < 0) {\n                offset = 0;\n            }\n            loadBuffer();\n            index = fileOffset - offset;\n        }\n        try {\n            return buffer[(int)index];\n        } catch (ArrayIndexOutOfBoundsException e) {\n            System.err.println(\"\\nERROR\\nfile offset: \" + fileOffset + \", file size: \" + getFileSize());\n            System.err.println(\"offset: \" + offset + \", buffer size: \" + buffer.length);\n            e.printStackTrace();\n            return 0;   // TODO !!!!\n        }\n    }\n\n\n    private long calcOffset(long fileOffset, boolean randomAccessStream) {\n        if (randomAccessStream) {\n            return switch (cacheStrategy) {\n                case FORWARD -> fileOffset;\n                case BACKWARD -> fileOffset - buffer.length + 1;\n                case CENTER -> fileOffset - buffer.length / 2;\n            };\n        } else {\n            // TODO что-то странное !\n            switch (cacheStrategy) {\n                case FORWARD:\n                    return fileOffset;\n                case BACKWARD:\n                    return fileOffset - buffer.length + 1;\n                case CENTER:\n                    return fileOffset;\n            }\n        }\n        return fileOffset;\n    }\n\n    public long getFileSize() throws IOException {\n        if (streamSize < 0) {\n            streamSize = getStreamSize();\n        }\n        return streamSize;\n    }\n\n    public void close() throws IOException {\n        bufferSize = 0;\n        buffer = null;\n        closeStream();\n    }\n\n    abstract protected void closeStream() throws IOException;\n\n    abstract protected long getStreamSize() throws IOException;\n\n\n    /**\n     * Load file data from #offset and fills #buffer\n     */\n    abstract protected void loadBuffer() throws IOException;\n\n    abstract protected boolean supportRandomAccess();\n\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/hexeditor/data/FileByteBuffer.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2017 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.hexeditor.data;\n\nimport java.io.*;\n\n\npublic class FileByteBuffer extends AbstractByteBuffer {\n\n    private final String filePath;\n    private final String fileMode;\n    private RandomAccessFile file;\n\n    public FileByteBuffer(String filePath, String fileMode, int capacity) {\n        super(capacity);\n        this.filePath = filePath;\n        this.fileMode = fileMode;\n    }\n\n\n    public FileByteBuffer(String filePath, String fileMode) {\n        this(filePath, fileMode, DEFAULT_CAPACITY);\n    }\n\n    private RandomAccessFile getFile() throws FileNotFoundException {\n        if (file == null) {\n            file = new RandomAccessFile(filePath, fileMode);\n        }\n        return file;\n    }\n\n\n    @Override\n    protected void closeStream() throws IOException {\n        if (file != null) {\n            file.close();\n        }\n    }\n\n    @Override\n    protected long getStreamSize() throws IOException {\n        return getFile().length();\n    }\n\n    @Override\n    protected void loadBuffer() throws IOException {\n        getFile().seek(offset);\n        bufferSize = getFile().read(buffer);\n    }\n\n    @Override\n    protected boolean supportRandomAccess() {\n        return true;\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/hexeditor/data/MemoryByteBuffer.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2017 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.hexeditor.data;\n\n/**\n * @author Oleg Trifonov\n * Created on 08/02/17.\n */\npublic class MemoryByteBuffer extends AbstractByteBuffer {\n    public MemoryByteBuffer(int capacity) {\n        super(capacity);\n        bufferSize = capacity;\n        streamSize = capacity;\n    }\n\n    @Override\n    protected void closeStream() {\n\n    }\n\n    @Override\n    protected long getStreamSize() {\n        return capacity;\n    }\n\n    @Override\n    protected void loadBuffer() {\n    }\n\n    @Override\n    protected boolean supportRandomAccess() {\n        return true;\n    }\n\n    @Override\n    public long getFileSize() {\n        return capacity;\n    }\n\n    @Override\n    public byte getByte(long offset) {\n        return buffer[(int)offset];\n    }\n\n    public void setByte(long offset, int val) {\n        buffer[(int)offset] = (byte)val;\n    }\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/hexeditor/data/TrolCommanderByteBuffer.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2017 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.hexeditor.data;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileOperation;\nimport com.mucommander.commons.io.RandomAccessInputStream;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n\npublic class TrolCommanderByteBuffer extends AbstractByteBuffer {\n    private final AbstractFile file;\n    private InputStream is;\n    private long lastOffset;\n\n\n    public TrolCommanderByteBuffer(AbstractFile file) {\n        super(DEFAULT_CAPACITY);\n        this.file = file;\n    }\n\n    @Override\n    protected void closeStream() throws IOException {\n        if (is != null) {\n            is.close();\n        }\n    }\n\n    @Override\n    protected long getStreamSize() {\n        return file.getSize();\n    }\n\n    @Override\n    protected void loadBuffer() throws IOException {\n        getInputStream();\n        if (is instanceof RandomAccessInputStream rndIs) {\n            // Seek and reuse the stream\n            rndIs.seek(offset);\n//System.out.println(\"RANDOM ACCESS \" + offset);\n        } else {\n            // TODO: it would be more efficient to use some sort of PushBackInputStream, though we can't use PushBackInputStream because we don't want to keep pushing back for the whole InputStream lifetime\n            // Close the InputStream and open a new one\n            // Note: we could use mark/reset if the InputStream supports it, but it is almost never implemented by\n            // InputStream subclasses and a broken by design anyway.\n            if (lastOffset > offset) {\n//System.out.println(\"GENERAL ACCESS WITH RECREATE \" + offset + \"   \" + lastOffset + \"    \" + (lastOffset - offset));\n                is.close();\n                is = file.getInputStream();\n                is.skip(offset);\n            } else if (lastOffset != offset) {\n//System.out.println(\"GENERAL ACCESS WITH SKIP \" + offset + \"   \" + (offset - lastOffset));\n                is.skip(offset - lastOffset);\n            }\n        }\n        int bufPos = 0;\n        bufferSize = 0;\n        while (bufferSize < capacity) {\n            int read = is.read(buffer, bufPos, capacity- bufferSize);\n            if (read < 0) {\n                break;\n            }\n            bufPos += read;\n            bufferSize += read;\n        }\n        lastOffset = offset + bufferSize;\n    }\n\n    @Override\n    protected boolean supportRandomAccess() {\n        return file.isFileOperationSupported(FileOperation.RANDOM_READ_FILE);\n    }\n\n\n    protected InputStream getInputStream() throws IOException {\n        if (is == null) {\n            if (file.isFileOperationSupported(FileOperation.RANDOM_READ_FILE)) {\n                try {\n                    is = file.getRandomAccessInputStream();\n                } catch(IOException e) {\n                    // In that case we simply get an InputStream\n                }\n            }\n            if (is == null) {\n                is = file.getPushBackInputStream(1024);\n            }\n            lastOffset = 0;\n        }\n        return is;\n    }\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/hexeditor/events/OffsetChangeListener.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.hexeditor.events;\n\n/**\n * @author Oleg Trifonov\n * Created on 02/04/14.\n */\npublic interface OffsetChangeListener {\n    void onChange(long offset);\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/hexeditor/events/SelectionChangeListener.java",
    "content": "package ru.trolsoft.hexeditor.events;\n\npublic interface SelectionChangeListener {\n    void onSelectionChanged(long fromAddress, long toAddress);\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/hexeditor/search/ByteBufferSearchUtils.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.hexeditor.search;\n\nimport ru.trolsoft.hexeditor.data.AbstractByteBuffer;\n\nimport java.io.IOException;\n\n/**\n * Search in AbstractByteBuffer\n */\npublic class ByteBufferSearchUtils {\n\n    /**\n     * Returns the offset within the ByteBuffer of the first occurrence of the specified data, starting at the specified offset.\n     *\n     * @param data buffer for search\n     * @param pattern the data to search for\n     * @param fromOffset the offset from which to start the search\n     * @return the offset of the first occurrence of the specified data, at the specified offset, or -1 if there is no such occurrence\n     */\n    public static long indexOf(AbstractByteBuffer data, byte[] pattern, long fromOffset) throws IOException {\n        if (data == null || pattern == null || fromOffset < 0) {\n            return -1;\n        }\n        long fileSize = data.getFileSize();\n        if (fileSize <= 0 || pattern.length == 0 || pattern.length > fileSize) {\n            return -1;\n        }\n        fromOffset = Math.min(fromOffset, fileSize - 1);\n\n        int[] failure = computeFailure(pattern);\n        AbstractByteBuffer.CacheStrategy cacheStrategy = data.getCacheStrategy();\n        data.setCacheStrategy(AbstractByteBuffer.CacheStrategy.FORWARD);\n\n        try {\n            int j = 0;\n            for (long i = fromOffset; i <= fileSize - 1; i++) {\n                byte currentByte = data.getByte(i);\n                while (j > 0 && pattern[j] != currentByte) {\n                    j = failure[j - 1];\n                }\n                if (pattern[j] == currentByte) {\n                    j++;\n                }\n                if (j == pattern.length) {\n                    return i - pattern.length + 1;\n                }\n            }\n            return -1;\n        } finally {\n            data.setCacheStrategy(cacheStrategy);\n        }\n    }\n\n    /**\n     * Searches for the first occurrence of the specified patterns in the buffer,\n     * starting at the specified offset.\n     *\n     * @param data buffer for search\n     * @param patterns array of patterns to search for\n     * @param fromOffset the offset from which to start the search\n     * @return the offset of the first occurrence of any pattern, or -1 if none found\n     */\n    public static long indexOf(AbstractByteBuffer data, byte[][] patterns, long fromOffset) throws IOException {\n        if (data == null || patterns == null || fromOffset < 0) {\n            return -1;\n        }\n        long fileSize = data.getFileSize();\n        if (fileSize <= 0) {\n            return -1;\n        }\n//        fromOffset = Math.min(fromOffset, fileSize - 1);\n        long earliestMatch = -1;\n\n        AbstractByteBuffer.CacheStrategy originalStrategy = data.getCacheStrategy();\n        data.setCacheStrategy(AbstractByteBuffer.CacheStrategy.FORWARD);\n\n        try {\n            for (byte[] pattern : patterns) {\n                if (pattern == null || pattern.length == 0 || pattern.length > fileSize) {\n                    continue;\n                }\n                long match = indexOf(data, pattern, fromOffset);\n\n                if (match != -1) {\n                    if (earliestMatch == -1 || match < earliestMatch) {\n                        earliestMatch = match;\n                        // Оптимизация: если нашли в starting offset, раньше быть не может\n                        if (earliestMatch == fromOffset) {\n                            return earliestMatch;\n                        }\n                    }\n                }\n            }\n        } finally {\n            data.setCacheStrategy(originalStrategy);\n        }\n\n        return earliestMatch;\n    }\n\n    public static long indexOfBackward(AbstractByteBuffer data, byte[] pattern, long fromOffset) throws IOException {\n        if (data == null || pattern == null || fromOffset < 0) {\n            return -1;\n        }\n        long fileSize = data.getFileSize();\n        if (fileSize <= 0 || pattern.length == 0 || pattern.length > fileSize) {\n            return -1;\n        }\n        fromOffset = Math.min(fromOffset, fileSize - 1);\n        byte[] patternInvert = new byte[pattern.length];\n        for (int i = 0; i < pattern.length; i++) {\n            patternInvert[i] = pattern[pattern.length-i-1];\n        }\n        int[] failure = computeFailure(patternInvert);\n        AbstractByteBuffer.CacheStrategy cacheStrategy = data.getCacheStrategy();\n        data.setCacheStrategy(AbstractByteBuffer.CacheStrategy.BACKWARD);\n        try {\n            int j = 0;\n            for (long i = fromOffset; i >= 0; i--) {\n                while (j > 0 && patternInvert[j] != data.getByte(i)) {\n                    j = failure[j - 1];\n                }\n                if (patternInvert[j] == data.getByte(i)) {\n                    j++;\n                }\n                if (j == pattern.length) {\n                    return i;\n                }\n            }\n            return -1;\n        } finally {\n            data.setCacheStrategy(cacheStrategy);\n        }\n    }\n\n\n\n    /**\n     * Knuth-Morris-Pratt Algorithm for Pattern Matching\n     * Finds the first occurrence of the pattern in the text.\n     */\n    public static int indexOf(byte[] data, byte[] pattern) {\n        if (data == null || pattern == null || pattern.length == 0) {\n            return -1;\n        }\n        if (pattern.length > data.length) {\n            return -1;\n        }\n        int[] failure = computeFailure(pattern);\n\n        int j = 0;\n\n        for (int i = 0; i < data.length; i++) {\n            while (j > 0 && pattern[j] != data[i]) {\n                j = failure[j - 1];\n            }\n            if (pattern[j] == data[i]) {\n                j++;\n            }\n            if (j == pattern.length) {\n                return i - pattern.length + 1;\n            }\n        }\n        return -1;\n    }\n\n    /**\n     * Computes the failure function using a bootstrapping process,\n     * where the pattern is matched against itself.\n     */\n    private static int[] computeFailure(byte[] pattern) {\n        int[] failure = new int[pattern.length];\n\n        int j = 0;\n        for (int i = 1; i < pattern.length; i++) {\n            while (j > 0 && pattern[j] != pattern[i]) {\n                j = failure[j - 1];\n            }\n            if (pattern[j] == pattern[i]) {\n                j++;\n            }\n            failure[i] = j;\n        }\n        return failure;\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/hexeditor/ui/HexTable.java",
    "content": "package ru.trolsoft.hexeditor.ui;\n\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport ru.trolsoft.hexeditor.events.OffsetChangeListener;\nimport ru.trolsoft.hexeditor.events.SelectionChangeListener;\n\nimport javax.swing.*;\nimport javax.swing.table.DefaultTableCellRenderer;\nimport javax.swing.table.TableColumn;\nimport javax.swing.table.TableColumnModel;\nimport java.awt.*;\nimport java.awt.datatransfer.Clipboard;\nimport java.awt.datatransfer.DataFlavor;\nimport java.awt.datatransfer.StringSelection;\nimport java.awt.datatransfer.Transferable;\nimport java.awt.datatransfer.UnsupportedFlavorException;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.awt.event.KeyEvent;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.util.Map;\n\n/**\n * The table displaying the hex data\n */\npublic class HexTable extends JTable {\n    private static final Dimension ZERO_DIMENSION = new Dimension(0, 0);\n    @Setter\n    private SelectionChangeListener selectionChangeListener;\n\n    /**\n     * ASCII characters. Used to prevent strings creation on repainting cell\n     */\n    private static final String[] CHARACTERS = new String[256];\n\n    static {\n        for (char i = 0; i < CHARACTERS.length; i++) {\n            CHARACTERS[i] = Character.toString(i);\n        }\n    }\n\n\n    private ViewerHexTableModel model;\n    private final CellRenderer cellRenderer = new CellRenderer();\n    private final HeaderRenderer headerRenderer = new HeaderRenderer();\n    private final Rectangle repaintRect = new Rectangle();\n\n\n    private int charWidth;\n    private int fontHeight;\n    private int fontAscent;\n\n    private Color alternateCellColor;\n    private Color offsetColor;\n    private Color asciiDumpColor;\n    private Color selectionAsciiBackgroundColor;\n    private boolean alternateRowBackground;\n    private boolean alternateColumnBackground;\n    private long leadSelectionIndex;\n    private long anchorSelectionIndex;\n\n    @Setter\n    @Getter\n    private OffsetChangeListener offsetChangeListener;\n\n    private JPopupMenu popupMenu;\n    private JMenuItem copyBinaryItem;\n    private JMenuItem copyHexItem;\n    private JMenuItem saveSelectedItem;\n\n    public HexTable(ViewerHexTableModel model) {\n        super(model);\n        this.model = model;\n        enableEvents(AWTEvent.KEY_EVENT_MASK);\n        setAutoResizeMode(JTable.AUTO_RESIZE_OFF);\n\n        setShowGrid(false);\n        setIntercellSpacing(ZERO_DIMENSION);\n        alternateCellColor = getBackground();\n        offsetColor = getForeground();\n        asciiDumpColor = getForeground();\n\n        setCellSelectionEnabled(true);\n        setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);\n        setDefaultEditor(Object.class, cellEditor);\n        setDefaultRenderer(Object.class, cellRenderer);\n\n        for (int i = getColumnModel().getColumnCount()-1; i >= 0; i--) {\n            TableColumn col = getColumnModel().getColumn(i);\n            col.setHeaderRenderer(headerRenderer);\n        }\n        getTableHeader().setReorderingAllowed(false);\n        setShowGrid(false);\n\n        setFont(getFont());\n\n        initContextMenu();\n    }\n\n    private void initContextMenu() {\n        popupMenu = new JPopupMenu();\n\n        copyBinaryItem = new JMenuItem(\"Copy selected as binary\");\n        copyBinaryItem.addActionListener(e -> copySelectedAsBinary());\n        popupMenu.add(copyBinaryItem);\n\n        copyHexItem = new JMenuItem(\"Copy selected as hex\");\n        copyHexItem.addActionListener(e -> copySelectedAsHex());\n        popupMenu.add(copyHexItem);\n\n        saveSelectedItem = new JMenuItem(\"Save selected\");\n        saveSelectedItem.addActionListener(e -> saveSelected());\n        popupMenu.add(saveSelectedItem);\n\n        addMouseListener(new java.awt.event.MouseAdapter() {\n            @Override\n            public void mousePressed(java.awt.event.MouseEvent e) {\n                if (e.isPopupTrigger()) {\n                    showContextMenu(e);\n                }\n            }\n\n            @Override\n            public void mouseReleased(java.awt.event.MouseEvent e) {\n                if (e.isPopupTrigger()) {\n                    showContextMenu(e);\n                }\n            }\n        });\n    }\n\n    private void showContextMenu(java.awt.event.MouseEvent e) {\n        long from = getSmallestSelectionIndex();\n        long to = getLargestSelectionIndex();\n        boolean hasSelection = from >= 0 && to >= 0 && from <= to;\n\n        copyBinaryItem.setEnabled(hasSelection);\n        copyHexItem.setEnabled(hasSelection);\n        saveSelectedItem.setEnabled(hasSelection);\n\n        popupMenu.show(e.getComponent(), e.getX(), e.getY());\n    }\n\n\n    /**\n     * Changes the selected byte range.\n     *\n     * @param row row\n     * @param col column\n     * @param toggle see description for {@link JTable#changeSelection(int, int, boolean, boolean)}\n     * @param extend if true, extend the current selection\n     * @see #changeSelectionByOffset(long, boolean)\n     * @see #setSelectedRows(int, int)\n     * @see #setSelectionByOffsets(long, long)\n     */\n    @Override\n    public void changeSelection(int row, int col, boolean toggle, boolean extend) {\n        // remind previous selection range\n        //long prevSmallest = getSmallestSelectionIndex();\n        //long prevLargest = getLargestSelectionIndex();\n\n        // Don't allow the user to select the \"ascii dump\" or any\n        // empty cells in the last row of the table.\n        col = adjustColumn(row, col);\n        if (row < 0) {\n            row = 0;\n        }\n\n        final long prevSelectionIndexFrom = anchorSelectionIndex;\n        final long prevSelectionIndexTo = leadSelectionIndex;\n\n        if (extend) {\n            leadSelectionIndex = cellToOffset(row, col);\n        } else {\n            anchorSelectionIndex = leadSelectionIndex = cellToOffset(row, col);\n        }\n        if (offsetChangeListener != null) {\n            offsetChangeListener.onChange(anchorSelectionIndex);\n        }\n\n        // Scroll after changing the selection as blit scrolling is immediate, so that if we cause the repaint after the\n        // scroll we end up painting everything!\n        if (getAutoscrolls()) {\n            ensureCellIsVisible(row, col);\n        }\n\n        // Draw the new selection.\n        repaintSelection(prevSelectionIndexFrom, prevSelectionIndexTo);\n        if (selectionChangeListener != null) {\n            selectionChangeListener.onSelectionChanged(anchorSelectionIndex, leadSelectionIndex);\n        }\n    }\n\n    private static int min(int x1, int x2, int x3, int x4) {\n        int min = Math.min(x1, x2);\n        if (x3 < min) {\n            min = x3;\n        }\n        if (x4 < min) {\n            min = x4;\n        }\n        return min;\n    }\n\n    private static int max(int x1, int x2, int x3, int x4) {\n        int max = Math.max(x1, x2);\n        if (x3 > max) {\n            max = x3;\n        }\n        if (x4 > max) {\n            max = x4;\n        }\n        return max;\n    }\n\n\n    @Override\n    public boolean isCellEditable(int row, int col) {\n        return false;//cellToOffset(row, col) > -1;\n    }\n\n    @Override\n    public boolean isCellSelected(int row, int col) {\n        // Offset and ASCII dump\n        if (col == 0 || col == model.getColumnCount()-1) {\n            return false;\n        }\n        long offset = cellToOffset(row, col);\n        final long start = getSmallestSelectionIndex();\n        final long end = getLargestSelectionIndex();\n        return offset >= start && offset <= end;\n    }\n\n    @Override\n    public void setFont(Font font) {\n        super.setFont(font);\n        calculateSizes(font);\n        if (cellRenderer != null) { // check for NPE prevent, because constructor of JTable calls setFont()\n            cellRenderer.setFont(font);\n        }\n    }\n\n\n    public void setModel(ViewerHexTableModel model) {\n        super.setModel(model);\n        this.model = model;\n        calculateSizes(getFont());\n    }\n\n    /**\n     * Calculates table column sizes after change of font\n     */\n    private void calculateSizes(Font font) {\n        if (model == null) {\n            return;\n        }\n\n        FontMetrics fm = getFontMetrics(font);\n        FontMetrics fmHeader = getFontMetrics(getTableHeader().getFont());\n\n        charWidth = max(fm.charWidth('W'), fm.charWidth('_'), fm.charWidth('8'), fm.charWidth('@'));\n        fontHeight = fm.getHeight();\n        fontAscent = fm.getAscent();\n\n        int width = 0;\n        final int hexColumns = model.getNumberOfHexColumns();\n        int w = Math.max(charWidth * 3, fmHeader.stringWidth(\"+9D9\")+2);   // +9W9\n        for (int i = 1; i <= hexColumns; i++) {\n            setColumnWidth(i, w);\n            width += w;\n        }\n\n        // Offset\n        setColumnWidth(0, charWidth * 10);\n        width += charWidth * 10;\n\n        // Hex dump\n        w = charWidth * (hexColumns + 1);\n        width += w;\n\n        // ASCII dump\n        setColumnWidth(hexColumns + 1, w);\n\n        model.setAsciiCharVisible((char) 0, false);\n        for (char ch = 1; ch <= 0xff; ch++) {\n            model.setAsciiCharVisible(ch, fm.charWidth(ch) <= charWidth);\n        }\n\n        setRowHeight(fontHeight);\n        setPreferredScrollableViewportSize(new Dimension(width, 25 * getRowHeight()));\n    }\n\n    /**\n     * Set preferred, minimum and maximum width of column\n     * @param index index of column\n     * @param width column width\n     */\n    private void setColumnWidth(int index, int width) {\n        TableColumn column = getColumnModel().getColumn(index);\n        column.setPreferredWidth(width);\n        column.setMinWidth(width);\n        column.setMaxWidth(width);\n    }\n\n    /**\n     * Returns the column for the cell containing data that is the closest\n     * to the specified cell.  This is used when, for example, the user clicks\n     * on an \"empty\" cell in the last row of the table.\n     *\n     * @param row The row of the cell clicked on.\n     * @param col The column of the cell clicked on.\n     * @return The column of the closest cell containing data.\n     */\n    private int adjustColumn(int row, int col) {\n        if (col < 0) {\n            return 0;\n        }\n        // last row\n        if (row == getRowCount() - 1) {\n            final long fileSize = model.getSize();\n            final int hexColumns = model.getNumberOfHexColumns();\n            int lastRowCount = (int)(fileSize % hexColumns);\n            if (lastRowCount > 0) {\n                return Math.min(col, lastRowCount);\n            }\n        }\n        return Math.min(col, getColumnCount()-2);\n    }\n\n\n    /**\n     * Returns the cell representing the specified offset into the hex document.\n     *\n     * @param offset The offset into the document.\n     * @return The cell, in the form <code>(row, col)</code>.  If the specified offset is invalid, <code>(-1, -1)</code> is returned.\n     * @see #cellToOffset(int, int)\n     */\n    public Point offsetToCell(long offset) {\n        if (offset < 0 || offset >= model.getSize()) {\n            return new Point(-1, -1);\n        }\n        final int hexColumns = model.getNumberOfHexColumns();\n        int row = (int)(offset / hexColumns);\n        int col = (int)(offset % hexColumns);\n        return new Point(row, col);\n    }\n\n\n    /**\n     * Returns the offset into the bytes being edited represented at the\n     * specified cell in the table, if any.\n     *\n     * @param row The row in the table.\n     * @param col The column in the table.\n     * @return The offset into the byte array, or <code>-1</code> if the cell does not represent part of the byte array\n     *         (such as the tailing \"ASCII dump\" column's cells).\n     * @see #offsetToCell(long)\n     */\n    public long cellToOffset(int row, int col) {\n        // Check row and column individually to prevent them being invalid values but still pointing to a valid offset in the buffer.\n        final int hexColumns = model.getNumberOfHexColumns();\n        // Don't include last column (ASCII dump)\n        if (row < 0 || row >= getRowCount() || col < 1 || col > hexColumns) {\n            return -1;\n        }\n        long offs = (long)row * hexColumns + col - 1;\n        return (offs >= 0 && offs < model.getSize()) ? offs : -1;\n    }\n\n\n    /**\n     * Returns the largest selection index.\n     *\n     * @return The largest selection index.\n     * @see #getSmallestSelectionIndex()\n     */\n    public long getLargestSelectionIndex() {\n        long index = Math.max(leadSelectionIndex, anchorSelectionIndex);\n        return index < 0 ? 0 : index; // Don't return -1 if table is empty\n    }\n\n\n    /**\n     * Returns the smallest selection index.\n     *\n     * @return The smallest selection index.\n     * @see #getLargestSelectionIndex()\n     */\n    public long getSmallestSelectionIndex() {\n        long index = Math.min(leadSelectionIndex, anchorSelectionIndex);\n        return index < 0 ? 0 : index; // Don't return -1 if table is empty\n    }\n\n\n\n    /**\n     * Changes the selection by an offset into the bytes being edited.\n     *\n     * @param offset offset\n     * @param extend if true, extend the current selection\n     * @see #changeSelection(int, int, boolean, boolean)\n     * @see #setSelectedRows(int, int)\n     * @see #setSelectionByOffsets(long, long)\n     */\n    public void changeSelectionByOffset(long offset, boolean extend) {\n        final long fileSize = model.getSize();\n        if (offset < 0) {\n            offset = 0;\n        } else if (offset >= fileSize) {\n            offset = fileSize - 1;\n        }\n        final int hexColumns = model.getNumberOfHexColumns();\n        int row = (int)(offset / hexColumns);\n        int col = (int)(offset % hexColumns) + 1;\n        changeSelection(row, col, false, extend);\n    }\n\n\n    /**\n     * Clears the selection.  The \"lead\" of the selection is set back to the position of the \"anchor.\"\n     */\n    @Override\n    public void clearSelection() {\n        final long prevSelectionIndexFrom = anchorSelectionIndex;\n        final long prevSelectionIndexTo = leadSelectionIndex;\n\n        if (anchorSelectionIndex >= 0) { // Always true unless an error\n            leadSelectionIndex = anchorSelectionIndex;\n        } else {\n            anchorSelectionIndex = leadSelectionIndex = 0;\n        }\n        repaintSelection(prevSelectionIndexFrom, prevSelectionIndexTo);\n    }\n\n\n    public void setSelectedRows(int min, int max) {\n        if (min < 0 || min >= getRowCount() || max < 0 || max >= getRowCount()) {\n            throw new IllegalArgumentException();\n        }\n        final int hexColumns = model.getNumberOfHexColumns();\n        int startOffs = min * hexColumns;\n        int endOffs = max*hexColumns + hexColumns-1;\n        // TODO: Have a single call to change selection by a range.\n        changeSelectionByOffset(startOffs, false);\n        changeSelectionByOffset(endOffs, true);\n    }\n\n\n    /**\n     * Selects the specified range of bytes in the table.\n     *\n     * @param startOffs The \"anchor\" byte of the selection.\n     * @param endOffs The \"lead\" byte of the selection.\n     * @see #changeSelection(int, int, boolean, boolean)\n     * @see #changeSelectionByOffset(long, boolean)\n     */\n    public void setSelectionByOffsets(long startOffs, long endOffs) {\n        final long prevSelectionIndexFrom = anchorSelectionIndex;\n        final long prevSelectionIndexTo = leadSelectionIndex;\n\n        if (startOffs < 0) {\n            startOffs = 0;\n        } else if (startOffs >= model.getSize()) {\n            startOffs = model.getSize()-1;\n        }\n\n        // Clear the old selection (may not be necessary).\n        //repaintSelection();\n\n        anchorSelectionIndex = startOffs;\n        leadSelectionIndex = endOffs;\n\n        // Scroll after changing the selection as blit scrolling is\n        // immediate, so that if we cause the repaint after the scroll we\n        // end up painting everything!\n        if (getAutoscrolls()) {\n            final int hexColumns = model.getNumberOfHexColumns();\n            int endRow = (int)(endOffs / hexColumns);\n            int endCol = (int)(endOffs % hexColumns);\n            // Don't allow the user to select the \"ascii dump\" or any empty cells in the last row of the table.\n            endCol = adjustColumn(endRow, endCol);\n            if (endRow < 0) {\n                endRow = 0;\n            }\n            ensureCellIsVisible(endRow, endCol);\n        }\n\n        // Draw the new selection.\n        repaintSelection(prevSelectionIndexFrom, prevSelectionIndexTo);\n        if (offsetChangeListener != null) {\n            offsetChangeListener.onChange(anchorSelectionIndex);\n        }\n    }\n\n    public void gotoOffset(long offset) {\n        setSelectionByOffsets(offset, offset);\n    }\n\n\n    /**\n     * Ensures the specified cell is visible.\n     *\n     * @param row The row of the cell.\n     * @param col The column of the cell.\n     */\n    private void ensureCellIsVisible(int row, int col) {\n        Rectangle cellRect = getCellRect(row, col, false);\n        scrollRectToVisible(cellRect);\n    }\n\n    private void repaintSelection(long indexFromBefore, long indexFromAfter) {\n        if (model == null) {\n            return;\n        }\n        // calculate area for repainting\n        final int hexColumns = model.getNumberOfHexColumns();\n        final int row1 = (int)(indexFromBefore / hexColumns);\n        final int row2 = (int)(indexFromAfter / hexColumns);\n        final int row3 = (int)(anchorSelectionIndex / hexColumns);\n        final int row4 = (int)(leadSelectionIndex / hexColumns);\n        final int rowFrom = min(row1, row2, row3, row4);\n        final int rowTo = max(row1, row2, row3, row4);\n\n        int colFrom, colTo;\n        if (rowFrom == rowTo) {\n            final int col1 = (int)(indexFromBefore % hexColumns);\n            final int col2 = (int)(indexFromAfter % hexColumns);\n            final int col3 = (int)(anchorSelectionIndex % hexColumns);\n            final int col4 = (int)(leadSelectionIndex % hexColumns);\n\n            colFrom = min(col1, col2, col3, col4);\n            colTo = max(col1, col2, col3, col4);\n        } else {\n            colFrom = 0;\n            colTo = hexColumns-1;\n        }\n\n        //System.out.println(\"> \" + colFrom + \", \" + rowFrom + \" -> \" + colTo + \", \" + rowTo);\n        // repaint hex columns\n        final int rowHeight = getRowHeight();\n        final TableColumnModel cm = getColumnModel();\n        final int offsetColumnWidth = cm.getColumn(0).getWidth();\n        final int hexColumnWidth = cm.getColumn(1).getWidth();\n        final int dumpColumnWidth = cm.getColumn(cm.getColumnCount()-1).getWidth();\n        //repaintRect.setBounds(offsetColumnWidth, rowHeight*rowFrom, getWidth()-offsetColumnWidth, (rowTo-rowFrom+1)*rowHeight);\n        //repaint(repaintRect);\n        repaintRect.setBounds(offsetColumnWidth + colFrom*hexColumnWidth, rowHeight*rowFrom, (colTo-colFrom+1)*hexColumnWidth, (rowTo-rowFrom+1)*rowHeight);\n        repaint(repaintRect);\n        repaintRect.setBounds(getWidth() - dumpColumnWidth, rowHeight*rowFrom, dumpColumnWidth, (rowTo-rowFrom+1)*rowHeight);\n        repaint(repaintRect);\n    }\n\n    /**\n     * Current offset in file\n     * @return current offset in file\n     */\n    public long getCurrentAddress() {\n        return anchorSelectionIndex;\n    }\n\n    /**\n     * Set alternate background color\n     * @param color alternate background color\n     */\n    public void setAlternateBackground(Color color) {\n        this.alternateCellColor = color;\n    }\n\n    /**\n     * Get alternate background color\n     * @return alternate background color\n     */\n    public Color getAlternateBackground() {\n        return alternateCellColor;\n    }\n\n    /**\n     * Set color to render the first offset column\n     * @param color color to render the first offset column\n     */\n    public void setOffsetColumnColor(Color color) {\n        this.offsetColor = color;\n    }\n\n    /**\n     * Get color to render the first offset column\n     * @return color to render the first offset column\n     */\n    public Color getOffsetColumnColor() {\n        return offsetColor;\n    }\n\n    /**\n     * Set color to render the last ASCII dump column\n     * @param color color for ASCII dump text\n     */\n    public void setAsciiColumnColor(Color color) {\n        this.asciiDumpColor = color;\n    }\n\n    /**\n     * Get color to render the last ASCII dump column\n     * @return color to render the ASCII dump column\n     */\n    public Color getAsciiColumnColor() {\n        return asciiDumpColor;\n    }\n\n\n    /**\n     *\n     * @param color background color of highlighted section in ascii dump\n     */\n    public void setAsciiSelectionBackgroundColor(Color color) {\n        this.selectionAsciiBackgroundColor = color;\n    }\n\n    /**\n     *\n     * @return background color of highlighted section in ascii dump\n     */\n    public Color getSelectionAsciiBackgroundColor() {\n        return selectionAsciiBackgroundColor;\n    }\n\n\n\n\n    /**\n     *\n     * @param enable true if used alternate background color for odd rows\n     */\n    public void setAlternateRowBackground(boolean enable) {\n        this.alternateRowBackground = enable;\n    }\n\n    /**\n     *\n     * @return true if used alternate background color for odd rows\n     */\n    public boolean isAlternateRowBackground() {\n        return alternateRowBackground;\n    }\n\n    /**\n     *\n     * @param enable true if used alternate background color for odd columns\n     */\n    public void setAlternateColumnBackground(boolean enable) {\n        this.alternateColumnBackground = enable;\n    }\n\n    /**\n     *\n     * @return true if used alternate background color for odd columns\n     */\n    public boolean isAlternateColumnBackground() {\n        return alternateColumnBackground;\n    }\n\n\n    /**\n     * Returns the rendering hints for text that will most accurately reflect\n     * those of the native windowing system.\n     *\n     * @return The rendering hints, or <code>null</code> if they cannot be\n     *         determined.\n     */\n    private Map<?, ?> getDesktopAntiAliasHints() {\n        return (Map<?, ?>)getToolkit().getDesktopProperty(\"awt.font.desktophints\");\n    }\n\n    @Override\n    protected void processKeyEvent (KeyEvent e) {\n        // TODO: Convert into Actions and put into InputMap/ActionMap?\n        final int hexColumns = model.getNumberOfHexColumns();\n        final long lastOffset = model.getSize() - 1;\n        final boolean extend = e.isShiftDown();\n        if (e.getID() == KeyEvent.KEY_PRESSED) {\n            switch (e.getKeyCode()) {\n                case KeyEvent.VK_LEFT:\n                    long offs = leadSelectionIndex > 0 ? leadSelectionIndex-1 : 0;\n                    changeSelectionByOffset(offs, extend);\n                    e.consume();\n                    return;\n                case KeyEvent.VK_RIGHT:\n                    offs = Math.min(leadSelectionIndex+1, lastOffset);\n                    changeSelectionByOffset(offs, extend);\n                    e.consume();\n                    return;\n                case KeyEvent.VK_UP:\n                    offs = Math.max(leadSelectionIndex - hexColumns, 0);\n                    changeSelectionByOffset(offs, extend);\n                    e.consume();\n                    return;\n                case KeyEvent.VK_DOWN:\n                    offs = Math.min(leadSelectionIndex + hexColumns, lastOffset);\n                    changeSelectionByOffset(offs, extend);\n                    e.consume();\n                    return;\n                case KeyEvent.VK_PAGE_DOWN:\n                    int visibleRowCount = getVisibleRect().height/getRowHeight();\n                    offs = Math.min(leadSelectionIndex + visibleRowCount * hexColumns, lastOffset);\n                    changeSelectionByOffset(offs, extend);\n                    e.consume();\n                    break;\n                case KeyEvent.VK_PAGE_UP:\n                    visibleRowCount = getVisibleRect().height / getRowHeight();\n                    offs = Math.max(leadSelectionIndex - visibleRowCount*hexColumns, 0);\n                    changeSelectionByOffset(offs, extend);\n                    e.consume();\n                    return;\n                case KeyEvent.VK_HOME:\n                    offs = (leadSelectionIndex/hexColumns)*hexColumns;\n                    changeSelectionByOffset(offs, extend);\n                    e.consume();\n                    return;\n                case KeyEvent.VK_END:\n                    offs = (leadSelectionIndex/hexColumns)*hexColumns + hexColumns-1;\n                    offs = Math.min(offs, lastOffset);\n                    changeSelectionByOffset(offs, extend);\n                    e.consume();\n                    return;\n                case KeyEvent.VK_BACK_SPACE:\n                    //System.out.println(Profiler.getTime());\n                    return;\n            }\n        }\n        super.processKeyEvent(e);\n    }\n\n    private void copySelectedAsBinary() {\n        long from = getSmallestSelectionIndex();\n        long to = getLargestSelectionIndex();\n        if (from < 0 || to < 0 || from > to) {\n            return;\n        }\n\n        try {\n            int length = (int)(to - from + 1);\n            byte[] data = new byte[length];\n            for (int i = 0; i < length; i++) {\n                data[i] = model.getByteAt(from + i);\n            }\n\n            Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();\n            Transferable transferable = new Transferable() {\n                @Override\n                public DataFlavor[] getTransferDataFlavors() {\n                    return new DataFlavor[]{DataFlavor.stringFlavor};\n                }\n\n                @Override\n                public boolean isDataFlavorSupported(DataFlavor flavor) {\n                    return true;\n                }\n\n                @Override\n                public Object getTransferData(DataFlavor flavor) {\n                    return data;\n                }\n            };\n            clipboard.setContents(transferable, null);\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n\n    private void copySelectedAsHex() {\n        long from = getSmallestSelectionIndex();\n        long to = getLargestSelectionIndex();\n        if (from < 0 || to < 0 || from > to) {\n            return;\n        }\n\n        try {\n            StringBuilder sb = new StringBuilder();\n            for (long i = from; i <= to; i++) {\n                byte b = model.getByteAt(i);\n                if (!sb.isEmpty()) {\n                    sb.append(' ');\n                }\n                sb.append(String.format(\"%02X\", b & 0xFF));\n            }\n\n            StringSelection stringSelection = new StringSelection(sb.toString());\n            Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();\n            clipboard.setContents(stringSelection, null);\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n\n    private void saveSelected() {\n        long from = getSmallestSelectionIndex();\n        long to = getLargestSelectionIndex();\n        if (from < 0 || to < 0 || from > to) {\n            return;\n        }\n\n        JFileChooser fileChooser = new JFileChooser();\n        fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);\n        fileChooser.setDialogTitle(\"Save selected bytes\");\n\n        if (fileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {\n            File file = fileChooser.getSelectedFile();\n            try (FileOutputStream fos = new FileOutputStream(file)) {\n                for (long i = from; i <= to; i++) {\n                    fos.write(model.getByteAt(i));\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n                JOptionPane.showMessageDialog(this,\n                        \"Error saving file: \" + e.getMessage(),\n                        \"Error\",\n                        JOptionPane.ERROR_MESSAGE);\n            }\n        }\n    }\n\n\n    private class CellRenderer extends DefaultTableCellRenderer {\n        private final Point highlight;\n        private final Map desktopAAHints;\n        private boolean hasSeparatorLine;\n\n        CellRenderer() {\n            highlight = new Point();\n            desktopAAHints = getDesktopAntiAliasHints();\n            setFont(HexTable.this.getFont());\n        }\n\n        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {\n//            Component result = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);\n            setValue(value);\n\n            highlight.setLocation(-1, -1);\n            hasSeparatorLine = column > 1 && column % 4 == 1;\n            // ASCII dump\n            if (column == table.getColumnCount() - 1) {\n                long selStart = getSmallestSelectionIndex();\n                long selEnd = getLargestSelectionIndex();\n\n                final int hexColumns = model.getNumberOfHexColumns();\n                int b1 = row * hexColumns;\n                int b2 = b1 + hexColumns - 1;\n                if (selStart <= b2 && selEnd >= b1) {\n                    long start = Math.max(selStart, b1) - b1;\n                    long end = Math.min(selEnd, b2) - b1;\n                    highlight.setLocation(start, end);\n                }\n                boolean alternateColor = alternateRowBackground && (row & 1) > 0;\n                setBackground(alternateColor ? alternateCellColor : table.getBackground());\n                setForeground(asciiDumpColor);\n                hasSeparatorLine = false;\n            }  else {\n                if (!isSelected) {\n                    if (shouldUseAlternateBackground(row, column)) {\n                        setBackground(alternateCellColor);\n                    } else {\n                        setBackground(table.getBackground());\n                    }\n                } else {\n                    setBackground(table.getSelectionBackground());\n                }\n                // Offset column\n                if (column == 0) {\n                    setForeground(offsetColor);\n                } else {\n                    setForeground(table.getForeground());\n                }\n            }\n\n            return this;\n        }\n\n        private boolean shouldUseAlternateBackground(int row, int column) {\n            return (alternateRowBackground && (row & 1) > 0) ^ (alternateColumnBackground && (column & 1) > 0);\n        }\n\n        @Override\n        protected void paintComponent(Graphics g) {\n            g.setColor(getBackground());\n            g.fillRect(0, 0, getWidth(), getHeight());\n\n            final String text = getText();\n            final int len = text.length();\n            int x = (getWidth() - charWidth *len)/2;\n            int y = (getHeight() - fontHeight)/2 + fontAscent;\n\n            if (highlight.x >= 0) {\n                g.setColor(selectionAsciiBackgroundColor);\n                g.fillRect(x + highlight.x * charWidth, 0, (highlight.y - highlight.x + 1) * charWidth, getRowHeight());\n            }\n\n            Graphics2D g2d = (Graphics2D)g;\n            Object oldHints = null;\n            if (desktopAAHints != null) {\n                oldHints = g2d.getRenderingHints();\n                g2d.addRenderingHints(desktopAAHints);\n            }\n\n            g.setColor(getForeground());\n            // not padding low bytes, and this one is in range 00-0f.\n            if (len == 1) {\n                x += charWidth;\n            }\n            for (int i = 0; i < len; i++) {\n                char ch = text.charAt(i);\n                if (ch != ' ') {\n                    g.drawString(CHARACTERS[ch], x, y);\n                }\n                x += charWidth;\n            }\n            //g.drawString(text, x, y);\n\n            // Restore rendering hints appropriately.\n            if (desktopAAHints != null) {\n                g2d.addRenderingHints((Map)oldHints);\n            }\n\n            if (hasSeparatorLine) {\n                g.setColor(Color.GRAY);\n                g.drawLine(0, 0, 0, getHeight());\n            }\n        }\n    }\n\n\n    private class HeaderRenderer extends DefaultTableCellRenderer {\n\n        private static final long serialVersionUID = 1L;\n\n        private final Map<?, ?> desktopAAHints;\n        private boolean centerText;\n\n        HeaderRenderer() {\n            desktopAAHints = getDesktopAntiAliasHints();\n            setFont(HexTable.this.getFont());\n        }\n\n        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {\n            setValue(value);\n            centerText = column != 0 && column != getColumnCount()-1;\n            return this;\n        }\n\n        @Override\n        protected void paintComponent(Graphics g) {\n            g.setColor(getBackground());\n            g.fillRect(0, 0, getWidth(), getHeight());\n\n            final String text = getText();\n            final int len = text.length();\n            int x;\n            if (centerText) {\n                x = (getWidth() - charWidth *len)/2 + 1;\n            } else {\n                x = 5;\n            }\n            int y = (getHeight() - fontHeight)/2 + fontAscent;\n\n            Graphics2D g2d = (Graphics2D)g;\n            Object oldHints = null;\n            if (desktopAAHints != null) {\n                oldHints = g2d.getRenderingHints();\n                g2d.addRenderingHints(desktopAAHints);\n            }\n\n            g.setColor(getForeground());\n            g2d.drawString(text, x, y);\n\n            // Restore rendering hints appropriately.\n            if (desktopAAHints != null) {\n                g2d.addRenderingHints((Map)oldHints);\n            }\n\n            // separator\n            g.setColor(Color.GRAY);\n            g.drawLine(0, 0, 0, getHeight());\n        }\n    }\n\n}"
  },
  {
    "path": "src/main/java/ru/trolsoft/hexeditor/ui/ViewerHexTableModel.java",
    "content": "package ru.trolsoft.hexeditor.ui;\n\nimport ru.trolsoft.hexeditor.data.AbstractByteBuffer;\nimport ru.trolsoft.utils.StrUtils;\n\nimport javax.swing.table.AbstractTableModel;\nimport java.io.IOException;\n\n/**\n * The table model used by the <code>JTable</code> in the hex viewer/editor.\n */\npublic class ViewerHexTableModel extends AbstractTableModel {\n\n    protected final boolean[] VISIBLE_SYMBOLS = new boolean[256];\n    protected long fileSize;\n\n    private final int hexDataColumns;\n    protected final AbstractByteBuffer buffer;\n\n    public ViewerHexTableModel(AbstractByteBuffer byteBuffer, int columns) {\n        this.buffer = byteBuffer;\n        for (int i = 0; i < VISIBLE_SYMBOLS.length; i++) {\n            VISIBLE_SYMBOLS[i] = i > 0;\n        }\n        this.hexDataColumns = columns;\n    }\n\n    public ViewerHexTableModel(AbstractByteBuffer byteBuffer) {\n        this(byteBuffer, 32);\n    }\n\n\n    public void load() throws IOException {\n        this.fileSize = buffer.getFileSize();\n        if (fileSize > 0) {\n            buffer.getByte(0);\n        }\n    }\n\n\n    public long getSize() {\n        return fileSize;\n    }\n\n\n    @Override\n    public int getRowCount() {\n        long fs = getSize();\n        int rows = (int)(fs / hexDataColumns);\n        if (fs % hexDataColumns > 0) {\n            rows++;\n        }\n        return rows;\n    }\n\n    @Override\n    public int getColumnCount() {\n        return hexDataColumns + 2;  // offset, data columns, ASCII dump\n    }\n\n    @Override\n    public Object getValueAt(int rowIndex, int columnIndex) {\n        if (columnIndex == 0) {\n            // offset\n            return StrUtils.dwordToHexStr(getRowOffset(rowIndex));\n        } else if (columnIndex == hexDataColumns + 1) {\n            // dump\n            try {\n                return getAsciiDump(rowIndex * hexDataColumns);\n            } catch (IOException e) {\n                e.printStackTrace();\n                return \"\";\n            }\n        } else {\n            long fileOffset = rowIndex * hexDataColumns + columnIndex - 1;\n            if (fileOffset >= fileSize) {\n                return \"\";\n            }\n            try {\n                return StrUtils.byteToHexStr(buffer.getByte(fileOffset));\n            } catch (ArrayIndexOutOfBoundsException | IOException e) {\n                e.printStackTrace();\n                return \"xx\";\n            }\n        }\n    }\n\n\n    protected long getRowOffset(int row) {\n        return row * hexDataColumns;\n    }\n\n    private String getAsciiDump(long fileOffset) throws IOException {\n        StringBuilder sb = new StringBuilder();\n        for (int i = 0; i <  hexDataColumns; i++) {\n            long offset = fileOffset + i;\n            if (offset >= fileSize) {\n                sb.append(' ');\n            } else {\n                int b = buffer.getByte(offset) & 0xff;// getData()[i + bufferOffset] & 0xff;\n                char ch = (char)b;\n                if (!VISIBLE_SYMBOLS[b]) {\n                    ch = ' ';\n                }\n                sb.append(ch);\n            }\n        }\n\n        return sb.toString();\n    }\n\n\n    public int getNumberOfHexColumns() {\n        return hexDataColumns;\n    }\n\n    @Override\n    public String getColumnName(int column) {\n        if (column == 0) {\n            return \"Offset\";\n        } else if (column == hexDataColumns + 1) {\n            return \"ASCII dump\";\n        }\n        return \"+\" + Integer.toHexString(column-1).toUpperCase();\n    }\n\n    void setAsciiCharVisible(char ch, boolean visible) {\n        if (ch < VISIBLE_SYMBOLS.length) {\n            VISIBLE_SYMBOLS[ch] = visible;\n        }\n    }\n\n    public byte getByteAt(long offset) throws IOException {\n        return buffer.getByte(offset);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/jni/NativeFileUtils.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2018 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.jni;\n\nimport ru.trolsoft.utils.FileUtils;\n\nimport java.io.File;\nimport java.io.IOException;\n\npublic class NativeFileUtils {\n\n    private static final int VERSION = 2;\n\n    public static final int FA_MASK_EXISTS = 1;\n    public static final int FA_MASK_DIRECTORY =\t2;\n    public static final int FA_MASK_HIDDEN = 4;\n\n\n    private static boolean init = false;\n    private static boolean installed = false;\n\n    private static String getJnilibFileName() {\n        return \"libtrolsoft-\" + System.getProperty(\"os.arch\") + \".jnilib\";\n    }\n\n    private static void prepareLibrary(boolean overwrite) {\n        String jarPath = FileUtils.getJarPath();\n        try {\n            FileUtils.copyFromJarFile(getJnilibFileName(), jarPath, overwrite);\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n\n    public static boolean init() {\n        if (!init) {\n            init = true;\n            prepareLibrary(false);\n            try {\n                String path = FileUtils.getJarPath() + File.separator;\n                System.load(path + getJnilibFileName());\n                installed = true;\n                if (getLibraryVersion() != VERSION) {\n                    prepareLibrary(true);\n                }\n            } catch (Throwable t) {\n                t.printStackTrace();\n                System.out.println(\"java.library.path=\" + System.getenv(\"java.library.path\"));\n                installed = false;\n            }\n        }\n        return installed;\n    }\n\n    native private static int getLibraryVersion();\n\n    native public static int getLocalFileAttributes(String path);\n\n    native public static boolean isLocalFileHidden(String path);\n\n    native public static boolean isLocalDirectory(String path);\n\n    native public static boolean isLocalFileExecutable(String path);\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/macosx/FileLabelCache.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.macosx;\n\nimport ch.randelshofer.quaqua.osx.OSXFile;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.impl.CachedFile;\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.utils.FileIconsCache;\n\nimport java.awt.*;\nimport java.io.File;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.Map;\n\n/**\n * Created on 27/12/16.\n * @author Oleg Trifonov\n */\npublic class FileLabelCache {\n\n    private static final int CACHE_SIZE = 1000;\n    private static final Color TRANSPARENT_COLOR = new Color(0, 0, 0, 0);\n\n    private static volatile FileLabelCache instance;\n    private final Map<String, Color> colors = new HashMap<>();\n    private final LinkedList<String> files = new LinkedList<>();\n\n    public static FileLabelCache getInstance() {\n        if (instance == null) {\n            synchronized (FileIconsCache.class) {\n                if (instance == null) {\n                    if (OsFamily.MAC_OS_X.isCurrent()) {\n                        instance = new FileLabelCache();\n                    } else {\n                        instance = new FileLabelCache() {\n                            @Override\n                            public Color getLabelColor(AbstractFile file) {\n                                return null;\n                            }\n                        };\n                    }\n                }\n            }\n        }\n        return instance;\n    }\n\n    public Color getLabelColor(AbstractFile file) {\n        String path = file.getAbsolutePath();\n        Color result = colors.get(path);\n        if (result != null) {\n            // move record to top\n            files.remove(path);\n            files.addFirst(path);\n            return result == TRANSPARENT_COLOR ? null : result;\n        }\n        return addColor(file);\n    }\n\n    private Color addColor(AbstractFile file) {\n        Color color = getFileLabel(file);\n        if (color == null) {\n            color = TRANSPARENT_COLOR;\n        }\n        String path = file.getAbsolutePath();\n        colors.put(path, color);\n        files.addFirst(path);\n\n        // remove oldest record if the cache is full\n        if (files.size() > CACHE_SIZE) {\n            colors.remove(files.removeLast());\n        }\n        return color == TRANSPARENT_COLOR ? null : color;\n    }\n\n    private Color getFileLabel(AbstractFile file) {\n        if (file instanceof CachedFile) {\n            file = ((CachedFile) file).getProxiedFile();\n        }\n        if (file instanceof LocalFile) {\n            File f = (File) file.getUnderlyingFileObject();\n            return OSXFile.getLabelColor(OSXFile.getLabel(f), 0);\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/macosx/RetinaImageIcon.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.macosx;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.image.BufferedImage;\nimport java.awt.image.ImageObserver;\nimport java.lang.reflect.Field;\nimport java.net.URL;\n\n/**\n * Created on 23/12/16.\n * @author Oleg Trifonov\n */\npublic class RetinaImageIcon extends ImageIcon {\n    public static final boolean IS_RETINA = checkRetina();\n\n    /**\n     * Creates an RetinaImageIcon from the specified URL.\n     *\n     * @param location the URL for the image\n     */\n    public RetinaImageIcon(URL location) {\n        super(location);\n    }\n\n    public RetinaImageIcon(Image img) {\n        super(img);\n    }\n\n    /**\n     *\n     * @return true if Retina display found\n     */\n    private static boolean checkRetina() {\n        try {\n            GraphicsDevice graphicsDevice = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();\n            Field field = graphicsDevice.getClass().getDeclaredField(\"scale\");\n            field.setAccessible(true);\n            Object scale = field.get(graphicsDevice);\n            if (scale instanceof Integer && (Integer) scale == 2) {\n                return true;\n            }\n        } catch (Exception ignore) {\n        }\n        return false;\n    }\n\n\n    @Override\n    public int getIconWidth() {\n        return IS_RETINA ? super.getIconWidth() / 2 : super.getIconWidth();\n    }\n\n    @Override\n    public int getIconHeight() {\n        return IS_RETINA ? super.getIconHeight() / 2 : super.getIconHeight();\n    }\n\n\n    @Override\n    public synchronized void paintIcon(Component c, Graphics g, int x, int y) {\n        if (IS_RETINA) {\n            ImageObserver observer = getImageObserver();\n            if (observer == null) {\n                observer = c;\n            }\n            Image image = getImage();\n            int width = image.getWidth(observer);\n            int height = image.getHeight(observer);\n            final Graphics2D g2d = (Graphics2D)g.create(x, y, width, height);\n            g2d.scale(0.5, 0.5);\n            g2d.drawImage(image, 0, 0, observer);\n            g2d.scale(1, 1);\n            g2d.dispose();\n        } else {\n            super.paintIcon(c, g, x, y);\n        }\n    }\n\n    public ImageIcon buildDisabledIcon() {\n        return new RetinaImageIcon(GrayFilter.createDisabledImage(getImage()));\n    }\n\n    public static ImageIcon buildPaddedIcon(ImageIcon icon, Insets insets) {\n        int scale = IS_RETINA ? 2 : 1;\n        BufferedImage bi = new BufferedImage(\n                scale*icon.getIconWidth() + scale*insets.left + scale*insets.right,\n                scale*icon.getIconHeight() + scale*insets.top + scale*insets.bottom,\n                BufferedImage.TYPE_INT_ARGB);\n\n        Graphics g = bi.getGraphics();\n        g.drawImage(icon.getImage(), scale*insets.left, scale*insets.top, null);\n\n        return new RetinaImageIcon(bi);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/ui/InputField.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2014 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.ui;\n\nimport ru.trolsoft.utils.StrUtils;\n\nimport javax.swing.JTextField;\nimport javax.swing.event.DocumentEvent;\nimport javax.swing.event.DocumentListener;\nimport java.awt.EventQueue;\nimport java.awt.Toolkit;\nimport java.io.UnsupportedEncodingException;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Input filed for text and hex values\n */\npublic class InputField extends JTextField implements DocumentListener {\n\n    public enum FilterType {\n        ANY_TEXT,\n        HEX_DUMP,\n        HEX_LONG,\n        DEC_LONG\n    }\n\n    private static final String HEX_SYMBOLS = \"0123456789ABCDEF\";\n\n    private static final String DEFAULT_ENCODING = \"windows-1252\";\n\n    private FilterType filterType = FilterType.HEX_DUMP;\n\n    private int maxLength = 0xff;\n\n    private boolean filtering = false;\n\n    private String textEncoding;\n\n    private List<InputField> bindedFields;\n\n    public InputField() {\n        super();\n        init();\n    }\n\n    public InputField(int columns) {\n        super(columns);\n        init();\n    }\n\n    public InputField(int columns, FilterType filterType) {\n        super(columns);\n        this.filterType = filterType;\n        init();\n    }\n\n    public InputField(FilterType filterType) {\n        super();\n        this.filterType = filterType;\n        init();\n    }\n\n    private void init() {\n        getDocument().addDocumentListener(this);\n        this.textEncoding = DEFAULT_ENCODING;\n    }\n\n    @Override\n    public void insertUpdate(DocumentEvent e) {\n        filterText();\n    }\n\n    @Override\n    public void removeUpdate(DocumentEvent e) {\n        filterText();\n    }\n\n    @Override\n    public void changedUpdate(DocumentEvent e) {\n        filterText();\n    }\n\n    public FilterType getFilterType() {\n        return filterType;\n    }\n\n    public void setFilterType(FilterType filterType) {\n        this.filterType = filterType;\n    }\n\n    public int getMaxLength() {\n        return maxLength;\n    }\n\n    public void setMaxLength(int maxLength) {\n        this.maxLength = maxLength;\n    }\n\n\n\n\n    private void filterText() {\n        if (filtering) {\n            return;\n        }\n        // don't filter text value\n        if (filterType == null || filterType == FilterType.ANY_TEXT) {\n            onChange();\n            updateBindFields();\n            return;\n        }\n        filtering = true;\n\n        EventQueue.invokeLater(() -> {\n            String input = getText().toUpperCase();\n            String filtered = filterInput(input, maxLength);\n            setText(filtered);\n            updateBindFields();\n            onChange();\n            filtering = false;\n        });\n    }\n\n\n\n    private String filterInput(String input, int maxLength) {\n        switch (filterType) {\n            case HEX_DUMP:\n                return filterHexString(input, maxLength);\n            case DEC_LONG:\n                return filterDecLong(input);\n            case HEX_LONG:\n                return filterHexLong(input);\n        }\n        return input;\n    }\n\n\n    private static String filterHexString(String input, int maxBytes) {\n        StringBuilder filtered = new StringBuilder();\n        int index = 0;\n\n        // filter\n        for (int i = 0; i < input.length(); i++) {\n            char c = input.charAt(i);\n            if (HEX_SYMBOLS.indexOf(c) >= 0) {\n                filtered.append(c);\n                if (index++ % 2 == 1 && i != input.length() - 1)\n                    filtered.append(' '); // whitespace after each byte\n            }\n        }\n        // limit size\n        if (maxBytes > 0) {\n            if (filtered.length() > 3 * maxBytes) {\n                filtered.setLength(3 * maxBytes);\n                beep();\n            }\n        }\n        return filtered.toString();\n    }\n\n\n    private String filterDecLong(String input) {\n        if (input == null) {\n            return null;\n        }\n        StringBuilder filtered = new StringBuilder();\n        for (int i = 0; i < input.length(); i++) {\n            char ch = input.charAt(i);\n            if (Character.isDigit(ch)) {\n                filtered.append(ch);\n            }\n        }\n        if (filtered.length() != input.length()) {\n            beep();\n        }\n        return filtered.toString();\n    }\n\n    private String filterHexLong(String input) {\n        if (input == null) {\n            return null;\n        }\n        StringBuilder filtered = new StringBuilder();\n        for (int i = 0; i < input.length(); i++) {\n            char ch = input.charAt(i);\n            if (HEX_SYMBOLS.indexOf(ch) >= 0) {\n                filtered.append(ch);\n            }\n        }\n        if (filtered.length() != input.length()) {\n            beep();\n        }\n        return filtered.toString();\n    }\n\n\n\n    private static void beep() {\n        Toolkit.getDefaultToolkit().beep();\n    }\n\n\n\n    public byte[] getBytes() {\n        return StrUtils.hexStringToBytes(getText());\n    }\n\n    public void setBytes(byte[] bytes) {\n        if (bytes == null) {\n            setText(\"\");\n        } else {\n            setText(StrUtils.bytesToHexStr(bytes, 0, bytes.length));\n        }\n    }\n\n    public long getValue() {\n        switch (filterType) {\n            case DEC_LONG:\n                return Long.parseLong(getText());\n            case HEX_LONG:\n                return Long.parseLong(getText(), 16);\n        }\n        return 0;\n    }\n\n    public void setValue(long val) {\n        setText(Long.toString(val));\n    }\n\n\n    public void bindField(InputField field) {\n        if (bindedFields == null) {\n            bindedFields = new ArrayList<>();\n        }\n        bindedFields.add(field);\n    }\n\n    private void setTextWithoutFilter(String text) {\n        filtering = true;\n        setText(text);\n        filtering = false;\n    }\n\n\n    private void updateBindFields() {\n        if (bindedFields == null) {\n            return;\n        }\n        final String src = getText();\n        for (InputField field : bindedFields) {\n            String text;\n            try {\n                text = convert(src, filterType, field.getFilterType());\n            } catch (UnsupportedEncodingException e) {\n                e.printStackTrace();\n                text = \"\";\n            }\n            if (!text.equals(field.getText())) {\n                field.setTextWithoutFilter(text);\n            }\n        }\n    }\n\n\n    private String convert(String text, FilterType from, FilterType to) throws UnsupportedEncodingException {\n        if (from == to) {\n            return text;\n        }\n        if (from == FilterType.ANY_TEXT && to == FilterType.HEX_DUMP) {\n            return StrUtils.bytesToHexString(text.getBytes(textEncoding));\n        } else if (from == FilterType.HEX_DUMP && to == FilterType.ANY_TEXT) {\n            return new String(StrUtils.hexStringToBytes(text), textEncoding);\n        }\n        return text;\n    }\n\n    public String getTextEncoding() {\n        return textEncoding;\n    }\n\n    public void setTextEncoding(String textEncoding) {\n        this.textEncoding = textEncoding;\n    }\n\n\n    public boolean isEmpty() {\n        return getText().isEmpty();\n    }\n\n    public void onChange() {\n\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/ui/TCheckBoxMenuItem.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.ui;\n\nimport com.mucommander.commons.runtime.OsFamily;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n/**\n * JCheckBoxMenuItem wrapper with fixed color (fix issues on linux with Cinnamon)\n *\n * @author Oleg Trifonov\n * Created on 21.09.16.\n */\npublic class TCheckBoxMenuItem extends JCheckBoxMenuItem {\n\n\n    public TCheckBoxMenuItem() {\n        super();\n        init();\n    }\n\n    public TCheckBoxMenuItem(Icon icon) {\n        super(icon);\n        init();\n    }\n\n    public TCheckBoxMenuItem(String text) {\n        super(text);\n        init();\n    }\n\n    public TCheckBoxMenuItem(Action a) {\n        super(a);\n        init();\n    }\n\n    public TCheckBoxMenuItem(String text, Icon icon) {\n        super(text, icon);\n        init();\n    }\n\n    public TCheckBoxMenuItem(String text, boolean b) {\n        super(text, b);\n        init();\n    }\n\n    public TCheckBoxMenuItem(String text, Icon icon, boolean b) {\n        super(text, icon, b);\n        init();\n    }\n\n\n    private void init() {\n        if (OsFamily.LINUX.isCurrent()) {\n            setOpaque(true);\n            setForeground(Color.BLACK);\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/ui/TMenuSeparator.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.ui;\n\nimport com.mucommander.commons.runtime.OsFamily;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n/**\n * JSeparator that fixed visibility issue on linux with Cinnamon\n *\n * @author Oleg Trifonov\n * Created on 21.09.16.\n */\npublic class TMenuSeparator extends JSeparator {\n\n    public TMenuSeparator() {\n        super();\n        if (OsFamily.LINUX.isCurrent() && getPreferredSize().height <= 0) {\n            Dimension d = getPreferredSize();\n            d.height = 1;\n            setPreferredSize(d);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/ui/TProgressBar.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.ui;\n\nimport javax.swing.JProgressBar;\nimport java.awt.*;\n\n/**\n * @author Oleg Trifonov\n * Created on 06/07/16.\n */\npublic class TProgressBar extends JProgressBar {\n\n    private static final Color GRADIENT_ENDING_COLOR = new Color(0xc0c0c0);\n    private static final Color BORDER_COLOR = new Color(0x736a60);\n    private static final Color DISABLED_BORDER_COLOR = new Color(0xbebebe);\n    public static final Color PREFERRED_PROGRESS_COLOR = new Color(0x1869A6);//new Color(0x3889F6);\n\n    private static final Composite TRANSPARENT = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.45f);\n    private static final Composite VERY_TRANSPARENT = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.25f);\n    private static final Composite NOT_TRANSPARENT = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f);\n\n    private static GradientPaint gradient;\n\n\n    public TProgressBar() {\n        setFont(getFont().deriveFont(Font.BOLD));\n    }\n\n\n    @Override\n    protected void paintComponent(Graphics g) {\n        int w = getWidth() - 1;\n        int h = getHeight() - 1;\n\n        if (gradient == null) {\n            gradient = new GradientPaint(0.0f, 0.0f, Color.WHITE, 0.0f, h, GRADIENT_ENDING_COLOR);\n        }\n        Graphics2D g2d = (Graphics2D) g;\n        // Clean background\n        if (isOpaque()) {\n            g2d.setColor(getBackground());\n            g2d.fillRect(0, 0, getWidth(), getHeight());\n        }\n\n        // Control Border\n        g2d.setColor(isEnabled() ? BORDER_COLOR : DISABLED_BORDER_COLOR);\n        g2d.drawLine(1, 0, w - 1, 0);\n        g2d.drawLine(1, h, w - 1, h);\n        g2d.drawLine(0, 1, 0, h - 1);\n        g2d.drawLine(w, 1, w, h - 1);\n\n        // Fill in the progress\n        int min = getMinimum();\n        int max = getMaximum();\n        int total = max - min;\n        float dx = (float) (w - 2) / (float) total;\n        int value = getValue();\n        int progress = value >= max ? w - 1 : (int) (dx * value);\n\n        g2d.setColor(PREFERRED_PROGRESS_COLOR);\n        g2d.fillRect(1, 1, progress, h - 1);\n\n        // A gradient over the progress fill\n        g2d.setPaint(gradient);\n        g2d.setComposite(TRANSPARENT);\n        g2d.fillRect(1, 1, w - 1, (h >> 1));\n        final float FACTOR = 0.20f;\n        g2d.fillRect(1, h - (int) (h * FACTOR), w - 1, (int) (h * FACTOR));\n\n        g2d.setComposite(VERY_TRANSPARENT);\n        final int n = 10;\n        int delta = w/n;\n        int i = 0;\n        if (isEnabled()) {\n            for (int xx = delta; xx < w; xx += delta) {\n                i++;\n                if (value > i*n) {\n                    //g2d.setColor(getBackground());\n                    //g2d.drawLine(xx-1, 1, xx-1, h - 1);\n                    g2d.setColor(Color.GRAY);\n                    g2d.drawLine(xx, 1, xx, h - 1);\n                }\n                g2d.setColor(Color.WHITE);\n                g2d.drawLine(xx + 1, 1, xx + 1, h - 1);\n            }\n        } else {\n            for (int xx = 0; xx < w; xx += delta) {\n                i++;\n                if (value > i*n) {\n                    g2d.setColor(Color.RED);\n                    g2d.drawLine(xx, h - 1, xx + h, 1);\n                }\n                g2d.setColor(Color.WHITE);\n                g2d.drawLine(xx + 1, h - 1, xx + 1 + h, 1);\n            }\n        }\n        FontMetrics fm = g.getFontMetrics();\n        int stringW = fm.stringWidth(getString());\n        int stringH = ((h - fm.getHeight()) / 2) + fm.getAscent();\n\n        g2d.setComposite(NOT_TRANSPARENT);\n        g2d.setColor(getForeground());\n        g2d.setFont(getFont());\n        g2d.drawString(getString(), (w - stringW)/2, stringH);\n    }\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/ui/TRadioButtonMenuItem.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.ui;\n\nimport com.mucommander.commons.runtime.OsFamily;\n\nimport javax.swing.Action;\nimport javax.swing.Icon;\nimport javax.swing.JRadioButtonMenuItem;\nimport java.awt.Color;\n\npublic class TRadioButtonMenuItem extends JRadioButtonMenuItem {\n\n    public TRadioButtonMenuItem() {\n        init();\n    }\n\n    public TRadioButtonMenuItem(Icon icon) {\n        super(icon);\n        init();\n    }\n\n    public TRadioButtonMenuItem(String text) {\n        super(text);\n        init();\n    }\n\n    public TRadioButtonMenuItem(Action a) {\n        super(a);\n        init();\n    }\n\n    public TRadioButtonMenuItem(String text, Icon icon) {\n        super(text, icon);\n        init();\n    }\n\n    public TRadioButtonMenuItem(String text, boolean selected) {\n        super(text, selected);\n        init();\n    }\n\n    public TRadioButtonMenuItem(Icon icon, boolean selected) {\n        super(icon, selected);\n        init();\n    }\n\n    public TRadioButtonMenuItem(String text, Icon icon, boolean selected) {\n        super(text, icon, selected);\n        init();\n    }\n\n    private void init() {\n        if (OsFamily.LINUX.isCurrent()) {\n            setOpaque(true);\n            setForeground(Color.BLACK);\n        }\n    }\n\n}"
  },
  {
    "path": "src/main/java/ru/trolsoft/ui/ZxSpectrumLoadPane.java",
    "content": "package ru.trolsoft.ui;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.ActionListener;\nimport java.util.Random;\n\npublic class ZxSpectrumLoadPane extends JPanel {\n    private static final Color COLOR_BLUE = new Color(0x0001c8);\n    private static final Color COLOR_YELLOW = new Color(0xbbbf0a);\n//    private final Color COLOR_RED = new Color(0xaa0000);\n//    private final Color COLOR_CYAN = new Color(0x00aaaa);\n    private static final Random r = new Random();\n    private int randomPrev;\n    private final Timer timer;\n\n    public ZxSpectrumLoadPane() {\n        this(null);\n    }\n\n    public ZxSpectrumLoadPane(LayoutManager layout) {\n        super(layout);\n        timer = initTimer();\n    }\n\n    private Timer initTimer() {\n        ActionListener updater = evt -> repaint();\n        return new Timer(30, updater);\n    }\n\n    public void stop() {\n        timer.stop();\n        SwingUtilities.invokeLater(this::repaint);\n    }\n\n    public void start() {\n        timer.start();\n    }\n\n    @Override\n    public void paint(Graphics g) {\n        if (timer.isRunning()) {\n            final int dx = 100;\n            final int dy = 50;\n            final int stripeH = 6;\n            final int w = getWidth();\n            final int h = getHeight();\n\n\n            g.setClip(4, 4, w - 8, h - 8);\n            int y = 0;\n            boolean firstColor = true;\n            while (y < h) {\n                g.setColor(firstColor ? COLOR_BLUE : COLOR_YELLOW);\n                int rectH = nextBit() ? 2 * stripeH : stripeH;\n                g.fillRect(0, y, w, rectH);\n                y += rectH;\n                firstColor = !firstColor;\n            }\n            g.setClip(dx, dy, w - 2 * dx, h - 2 * dy);\n        }\n        super.paint(g);\n    }\n\n    private boolean nextBit() {\n        if (randomPrev == 0) {\n            randomPrev = r.nextBoolean() ? 2 : 1;\n            return randomPrev == 2;\n        }\n        boolean res = randomPrev == 2;\n        randomPrev = 0;\n        return res;\n    }\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/utils/FileUtils.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2020 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.utils;\n\nimport com.mucommander.commons.io.StreamUtils;\nimport java.io.*;\nimport java.net.URISyntaxException;\n\n/**\n * Created on 08.01.15.\n * @author Oleg Trifonov\n */\npublic class FileUtils {\n\n    public static void copyFileFromJar(String src, String dest, boolean overwrite) throws IOException {\n        File fileDest = new File(dest);\n        if (!overwrite && fileDest.exists() && fileDest.length() > 0) {\n            return;\n        }\n        fileDest.getParentFile().mkdirs();\n        InputStream is = FileUtils.class.getResourceAsStream(src);\n        OutputStream os = new FileOutputStream(dest);\n        StreamUtils.copyStream(is, os);\n        is.close();\n        os.close();\n    }\n\n    public static void copyFromJarFile(String name, String jarPath, boolean overwrite) throws IOException {\n        final String outFile = jarPath + File.separatorChar + name;\n        if (overwrite || !new File(outFile).exists()) {\n            copyFileFromJar('/' + name, outFile, overwrite);\n        }\n    }\n\n    public static void copyFromJarFile(String name, String jarPath) throws IOException {\n        copyFromJarFile(name, jarPath, false);\n    }\n\n\n\n    public static String getJarPath() {\n        try {\n            return new File(FileUtils.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()).getParent();\n        } catch (URISyntaxException e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/utils/ImageSizeDetector.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2017 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.utils;\n\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * @author Oleg Trifonov\n * Created on 11/01/17.\n */\npublic class ImageSizeDetector {\n\n    public enum ImageType {\n        GIF,\n        PNG,\n        JPEG,\n        BMP,\n        TIFF\n    }\n\n    private int height;\n    private int width;\n    private ImageType type;\n\n    public ImageSizeDetector(InputStream is) throws IOException {\n        process(is);\n    }\n\n    private void process(InputStream is) throws IOException {\n        int c1 = is.read();\n        int c2 = is.read();\n        int c3 = is.read();\n\n        width = height = -1;\n\n        if (c1 == 'G' && c2 == 'I' && c3 == 'F') { // GIF\n            is.skip(3);\n            width = readWordLH(is);\n            height = readWordLH(is);\n            type = ImageType.GIF;\n        } else if (c1 == 0xFF && c2 == 0xD8) { // JPG\n            while (c3 == 255) {\n                int marker = is.read();\n                int len = readWordHL(is);\n                if (marker == 192 || marker == 193 || marker == 194) {\n                    is.skip(1);\n                    height = readWordHL(is);\n                    width = readWordHL(is);\n                    type = ImageType.JPEG;\n                    break;\n                }\n                is.skip(len - 2);\n                c3 = is.read();\n            }\n        } else if (c1 == 137 && c2 == 80 && c3 == 78) { // PNG\n            is.skip(15);\n            width = readWordHL(is);\n            is.skip(2);\n            height = readWordHL(is);\n            type = ImageType.PNG;\n        } else if (c1 == 66 && c2 == 77) { // BMP\n            is.skip(15);\n            width = readWordLH(is);\n            is.skip(2);\n            height = readWordLH(is);\n            type = ImageType.BMP;\n        } else {\n            int c4 = is.read();\n            if ((c1 == 'M' && c2 == 'M' && c3 == 0 && c4 == 42) || (c1 == 'I' && c2 == 'I' && c3 == 42 && c4 == 0)) { //TIFF\n                boolean bigEndian = c1 == 'M';\n                int entries;\n                int ifd = readDword(is, bigEndian);\n                is.skip(ifd - 8);\n                entries = readWord(is, bigEndian);\n                for (int i = 1; i <= entries; i++) {\n                    int tag = readWord(is, bigEndian);\n                    int fieldType = readWord(is, bigEndian);\n                    //long count =\n                    readDword(is, bigEndian);\n                    int valOffset;\n                    if ((fieldType == 3 || fieldType == 8)) {\n                        valOffset = readWord(is, bigEndian);\n                        is.skip(2);\n                    } else {\n                        valOffset = readDword(is, bigEndian);\n                    }\n                    if (tag == 256) {\n                        width = valOffset;\n                    } else if (tag == 257) {\n                        height = valOffset;\n                    }\n                    if (width != -1 && height != -1) {\n                        type = ImageType.TIFF;\n                        break;\n                    }\n                }\n            }\n        }\n    }\n\n\n    private static int readWordLH(InputStream is) throws IOException {\n        int b1 = is.read();\n        int b2 = is.read();\n        return b2 >= 0 ? b1 + (b2 << 8) : -1;\n    }\n\n    private static int readWordHL(InputStream is) throws IOException {\n        int b1 = is.read();\n        int b2 = is.read();\n        return b2 >= 0 ? b2 + (b1 << 8) : -1;\n    }\n\n    private static int readWord(InputStream is, boolean hiLo) throws IOException {\n        return hiLo ? readWordHL(is) : readWordLH(is);\n    }\n\n    private static int readDwordLH(InputStream is) throws IOException {\n        int b1 = is.read();\n        int b2 = is.read();\n        int b3 = is.read();\n        int b4 = is.read();\n        return b4 >= 0 ? b1 + (b2 << 8)  + (b3 << 16) + (b4 << 24): -1;\n    }\n\n    private static int readDwordHL(InputStream is) throws IOException {\n        int b1 = is.read();\n        int b2 = is.read();\n        int b3 = is.read();\n        int b4 = is.read();\n        return b4 >= 0 ? b4 + (b3 << 8)  + (b2 << 16) + (b1 << 24): -1;\n    }\n\n    private static int readDword(InputStream is, boolean hiLo) throws IOException {\n        return hiLo ? readDwordHL(is) : readDwordLH(is);\n    }\n\n\n    public int getWidth() {\n        return width;\n    }\n\n    public int getHeight() {\n        return height;\n    }\n\n    public ImageType getType() {\n        return type;\n    }\n\n    @Override\n    public String toString() {\n        return type + \"\\t Width : \" + width + \"\\t Height : \" + height;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/utils/JavaClassVersionDetector.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2026 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.utils;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * @author Oleg Trifonov\n * Created on 19/01/17.\n */\npublic class JavaClassVersionDetector {\n\n    private int major;\n    private int minor;\n    private final Version version;\n\n\n    public enum Version {\n        WRONG_FORMAT(-1, -1, \"wrong\"),\n        VER_1_0(45, 0, \"1.0\"),\n        VER_1_1(45, 3, \"1.1\"),\n        VER_1_2(46, 0, \"1.2\"),\n        VER_1_3(47, 0, \"1.3\"),\n        VER_1_4(48, 0, \"1.4\"),\n        VER_1_5(49, 0, \"1.5\"),\n        VER_1_6(50, 0, \"1.6\"),\n        VER_1_7(51, 0, \"1.7\"),\n        VER_1_8(52, 0, \"1.8\"),\n        VER_9(53, 0, \"9\"),\n        VER_10(54, 0, \"10\"),\n        VER_11(55, 0, \"11 (LTS)\"),\n        VER_12(56, 0, \"12\"),\n        VER_13(57, 0, \"13\"),\n        VER_14(58, 0, \"14\"),\n        VER_15(59, 0, \"15\"),\n        VER_16(60, 0, \"16\"),\n        VER_17(61, 0, \"17 (LTS)\"),\n        VER_18(62, 0, \"18\"),\n        VER_19(63, 0, \"19\"),\n        VER_20(64, 0, \"20\"),\n        VER_21(65, 0, \"21 (LTS)\"),\n        VER_22(66, 0, \"22\"),\n        VER_23(67, 0, \"23\"),\n        VER_24(68, 0, \"24\"),\n        VER_25(69, 0, \"25 (LTS)\"),\n        VER_26(70, 0, \"26\"),\n\n        UNKNOWN(-1, -1, \"unknown\");\n\n        private final int major;\n        private final int minor;\n        public final String name;\n\n        Version(int major, int minor, String name) {\n            this.major = major;\n            this.minor = minor;\n            this.name = name;\n        }\n    }\n\n\n    public JavaClassVersionDetector(InputStream is) throws IOException {\n        version = process(is);\n    }\n\n    private Version process(InputStream is) throws IOException {\n        int b1 = is.read();\n        int b2 = is.read();\n        int b3 = is.read();\n        int b4 = is.read();\n        if (b1 != 0xCA && b2 != 0xFE && b3 != 0xBA && b4 != 0xBE) {\n            return Version.WRONG_FORMAT;\n        }\n        minor = (is.read() << 8) + is.read();\n        major = (is.read() << 8) + is.read();\n\n        for (Version ver : Version.values()) {\n            if (ver.minor == minor && ver.major == major) {\n                return ver;\n            }\n        }\n        return Version.UNKNOWN;\n    }\n\n    public int getMajor() {\n        return major;\n    }\n\n    public int getMinor() {\n        return minor;\n    }\n\n    public Version getVersion() {\n        return version;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/utils/StrUtils.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2020 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.utils;\n\n/**\n * @author Oleg Trifonov\n * Created on 03/04/14.\n */\npublic class StrUtils {\n    private static final char[] HEX_CHAR_ARRAY = \"0123456789ABCDEF\".toCharArray();\n    private static final String[] STRING_OF_ZERO = {\"\", \"0\", \"00\", \"000\", \"0000\", \"00000\", \"000000\", \"0000000\", \"00000000\", \"000000000\", \"0000000000\"};\n\n    private static final String[] HEX_BYTE_STRINGS = new String[256];\n    private static final String[] BINARY_BYTE_STRINGS = new String[256];\n    private static final String[] OCTAL_BYTE_STRINGS = new String[256];\n\n    private static final char UTF_16_BE_MARKER = 0xFEFF;\n    private static final char UTF_16_LE_MARKER = 0xFFFE;\n\n\n    public static String dwordToHexStr(long val) {\n        String result = Long.toHexString(val);\n        int len = result.length();\n        if (len > 8) {\n            return result;\n        }\n        return STRING_OF_ZERO[8-len] + result;\n    }\n\n\n    public static String byteToHexStr(byte b) {\n        int v = b & 0xFF;\n        String result = HEX_BYTE_STRINGS[v];\n        if (result == null) {\n            result = Character.toString(HEX_CHAR_ARRAY[v >>> 4]) + HEX_CHAR_ARRAY[v & 0x0f];\n            HEX_BYTE_STRINGS[v] = result;\n        }\n        return result;\n    }\n\n\n    public static String bytesToHexStr(byte[] bytes, int offset, int size) {\n        char[] hexChars = new char[size * 2];\n        for (int i = offset; i < offset + size; i++) {\n            int v = bytes[i] & 0xFF;\n            hexChars[i * 2] = HEX_CHAR_ARRAY[v >>> 4];\n            hexChars[i * 2 + 1] = HEX_CHAR_ARRAY[v & 0x0F];\n        }\n        return new String(hexChars);\n    }\n\n\n    public static String byteToBinaryStr(byte b) {\n        int v = b & 0xFF;\n        return byteToBinaryStr(v);\n    }\n\n    public static String byteToBinaryStr(int v) {\n        String result = BINARY_BYTE_STRINGS[v];\n        if (result == null) {\n            result = Integer.toBinaryString(v);\n            result = STRING_OF_ZERO[8 - result.length()] + result;\n            BINARY_BYTE_STRINGS[v] = result;\n        }\n        return result;\n    }\n\n\n    public static String byteToOctalStr(byte b) {\n        int v = b & 0xFF;\n        return byteToOctalStr(v);\n    }\n\n    public static String byteToOctalStr(int v) {\n        String result = OCTAL_BYTE_STRINGS[v];\n        if (result == null) {\n            result = Integer.toOctalString(v);\n            result = STRING_OF_ZERO[3 - result.length()] + result;\n            OCTAL_BYTE_STRINGS[v] = result;\n        }\n        return result;\n    }\n\n\n    public static String bytesToHexString(byte[] bytes) {\n        StringBuilder s = new StringBuilder();\n        for (byte aByte : bytes) {\n            s.append(StrUtils.byteToHexStr(aByte));\n            s.append(' ');\n        }\n        return s.toString();\n    }\n\n\n    public static byte[] hexStringToBytes(String text) {\n        int len = text.length();\n        int i = 0;\n        char c1 = 0;\n        char c2 = 0;\n        int outPos = 0;\n        byte[] array = new byte[(len+1)/2];\n        while (i <= len) {\n            if (c2 != 0) {\n                int b1 = parseNibble(c1);\n                int b2 = parseNibble(c2);\n                if (b1 < 0 || b2 < 0) {\n                    return new byte[0];\n                }\n                array[outPos++] = (byte)((b1 << 4) + b2);\n                c1 = 0;\n                c2 = 0;\n            }\n            if (i == len) break;\n            char nextNibble = text.charAt(i++);\n            if (nextNibble == ' ' || nextNibble == '\\t') {\n                continue;\n            }\n            if (c1 == 0) {\n                c1 = nextNibble;\n            } else {\n                c2 = nextNibble;\n            }\n        }\n        if (c1 != 0) {\n            int b1 = parseNibble(c1);\n            if (b1 < 0) {\n                return new byte[0];\n            }\n            array[outPos++] = (byte)b1;\n        }\n        if (array.length == outPos) {\n            return array;\n        } else {\n            byte[] result = new byte[outPos];\n            System.arraycopy(array, 0, result, 0, outPos);\n            return result;\n        }\n    }\n\n    private static int parseNibble(char c) {\n        if (c >= '0' && c <= '9') {\n            return c - '0';\n        } else if (c >= 'A' && c <= 'F') {\n            return c - 'A' + 10;\n        } else if (c >= 'a' && c <= 'f') {\n            return c - 'a' + 10;\n        } else {\n            return -1;\n        }\n    }\n\n    public static boolean hasUtfMarker(String s) {\n        if (s == null || s.isEmpty()) {\n            return false;\n        }\n        char firstChar = s.charAt(0);\n        return firstChar == UTF_16_BE_MARKER || firstChar == UTF_16_LE_MARKER;\n    }\n\n    public static String removeUtfMarker(String s) {\n        if (s == null) {\n            return null;\n        }\n        return hasUtfMarker(s) ? s.substring(1) : s;\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/utils/StringStream.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2018 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.utils;\n\nimport java.util.LinkedList;\n\npublic class StringStream {\n    private final LinkedList<String> lines = new LinkedList<>();\n    private StringBuffer lastIncomplete;\n\n    public void add(String s) {\n        String[] array = s.split(\"(?<=\\n)\");\n        for (int i = 0; i < array.length; i++) {\n            String line = array[i];\n            if (i == array.length-1 && !line.endsWith(\"\\n\")) {\n                getLastIncomplete().append(line);\n            } else if (i == 0 && hasRemains()) {\n                lines.add(getRemains() + line);\n            } else {\n                lines.add(line);\n            }\n        }\n    }\n\n    public boolean hasCompleted() {\n        return !lines.isEmpty();\n    }\n\n    public String getNext() {\n        return lines.pollFirst();\n    }\n\n    public String getRemains() {\n        if (lastIncomplete != null) {\n            String result = lastIncomplete.toString();\n            lastIncomplete.delete(0, lastIncomplete.length());\n            return result;\n        } else {\n            return \"\";\n        }\n    }\n\n    public boolean hasRemains() {\n        return lastIncomplete != null && !lastIncomplete.isEmpty();\n    }\n\n    private StringBuffer getLastIncomplete() {\n        if (lastIncomplete == null) {\n            lastIncomplete = new StringBuffer();\n        }\n        return lastIncomplete;\n    }\n\n    public void clear() {\n        lines.clear();\n        lastIncomplete = null;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/utils/search/BytesSearchPattern.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.utils.search;\n\n/**\n * @author Oleg Trifonov\n * Created on 08/12/14.\n */\npublic class BytesSearchPattern implements SearchPattern {\n    private final byte[] bytes;\n\n    public BytesSearchPattern(byte[] bytes) {\n        this.bytes = bytes;\n    }\n\n    @Override\n    public int length() {\n        return bytes.length;\n    }\n\n    @Override\n    public boolean checkByte(int index, int val) {\n        return (bytes[index] & 0xff) == val;\n    }\n\n    @Override\n    public boolean checkSelf(int index1, int index2) {\n        return bytes[index1] == bytes[index2];\n    }\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/utils/search/InputStreamSource.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.utils.search;\n\nimport java.io.BufferedInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * @author Oleg Trifonov\n * Created on 18/11/14.\n */\npublic class InputStreamSource implements SearchSourceStream {\n\n    private final InputStream is;\n    private int next;\n    private boolean closed;\n\n    public InputStreamSource(InputStream is) {\n        this.is = new BufferedInputStream(is);\n    }\n\n    @Override\n    public boolean hasNext() throws SearchException {\n        try {\n            next = is.read();\n        } catch (IOException e) {\n            throw new SearchException(e);\n        }\n        return next >= 0;\n    }\n\n    @Override\n    public void close() {\n        if (closed) {\n            return;\n        }\n        try {\n            is.close();\n            closed = true;\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n\n    @Override\n    public int next() throws SearchException {\n        if (next < 0) {\n            throw new SearchException(\"next < 0\");\n        }\n        return next;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/utils/search/SearchException.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.utils.search;\n\n/**\n * @author Oleg Trifonov\n * Created on 18/11/14.\n */\npublic class SearchException extends Exception {\n    /**\n     * Constructs a new exception with {@code null} as its detail message.\n     * The cause is not initialized, and may subsequently be initialized by a\n     * call to {@link #initCause}.\n     */\n    public SearchException() {\n        super();\n    }\n\n    /**\n     * Constructs a new exception with the specified detail message.  The\n     * cause is not initialized, and may subsequently be initialized by\n     * a call to {@link #initCause}.\n     *\n     * @param   message   the detail message. The detail message is saved for\n     *          later retrieval by the {@link #getMessage()} method.\n     */\n    public SearchException(String message) {\n        super(message);\n    }\n\n    /**\n     * Constructs a new exception with the specified detail message and\n     * cause.  <p>Note that the detail message associated with\n     * {@code cause} is <i>not</i> automatically incorporated in\n     * this exception's detail message.\n     *\n     * @param  message the detail message (which is saved for later retrieval\n     *         by the {@link #getMessage()} method).\n     * @param  cause the cause (which is saved for later retrieval by the\n     *         {@link #getCause()} method).  (A <tt>null</tt> value is\n     *         permitted, and indicates that the cause is nonexistent or\n     *         unknown.)\n     */\n    public SearchException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    /**\n     * Constructs a new exception with the specified cause and a detail\n     * message of <tt>(cause==null ? null : cause.toString())</tt> (which\n     * typically contains the class and detail message of <tt>cause</tt>).\n     * This constructor is useful for exceptions that are little more than\n     * wrappers for other throwables (for example, {@link\n     * java.security.PrivilegedActionException}).\n     *\n     * @param  cause the cause (which is saved for later retrieval by the\n     *         {@link #getCause()} method).  (A <tt>null</tt> value is\n     *         permitted, and indicates that the cause is nonexistent or\n     *         unknown.)\n     */\n    public SearchException(Throwable cause) {\n        super(cause);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/utils/search/SearchPattern.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.utils.search;\n\n/**\n * @author Oleg Trifonov\n * Created on 16/11/14.\n */\npublic interface SearchPattern {\n\n    /**\n     *\n     * @return length of the search pattern\n     */\n    int length();\n\n    /**\n     *\n     * @param index offset in pattern\n     * @param val compared value\n     * @return true if pattern[index] == val\n     */\n    boolean checkByte(int index, int val);\n\n\n    boolean checkSelf(int index1, int index2);\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/utils/search/SearchSourceStream.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.utils.search;\n\n\n/**\n * @author Oleg Trifonov\n * Created on 16/11/14.\n */\npublic interface SearchSourceStream extends AutoCloseable {\n    int next() throws SearchException;\n\n    boolean hasNext() throws SearchException;\n\n    void close();\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/utils/search/SearchUtils.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.utils.search;\n\n\n/**\n * @author Oleg Trifonov\n * Created on 16/11/14.\n */\npublic class SearchUtils {\n\n\n    public static long indexOf(SearchSourceStream source, SearchPattern pattern) throws SearchException {\n        if (!source.hasNext() || pattern.length() == 0) {\n            return -1;\n        }\n        int[] failure = computeFailure(pattern);\n\n        int j = 0;\n        long i = 0;\n        while (source.hasNext()) {\n            int b = source.next();\n            i++;\n            while (j > 0 && !pattern.checkByte(j, b)) {\n                j = failure[j - 1];\n            }\n            if (pattern.checkByte(j, b)) {\n                j++;\n            }\n            if (j == pattern.length()) {\n                return i - pattern.length() + 1;\n            }\n        }\n        source.close();\n        return -1;\n    }\n\n    /**\n     * Computes the failure function using a boot-strapping process,\n     * where the pattern is matched against itself.\n     */\n    private static int[] computeFailure(SearchPattern pattern) {\n        int[] failure = new int[pattern.length()];\n\n        int j = 0;\n        for (int i = 1; i < pattern.length(); i++) {\n            while (j > 0 && !pattern.checkSelf(i, j)) {\n                j = failure[j - 1];\n            }\n            if (pattern.checkSelf(i, j)) {\n                j++;\n            }\n            failure[i] = j;\n        }\n        return failure;\n    }\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/utils/search/StringCaseInsensitiveSearchPattern.java",
    "content": "/*\n * This file is part of trolCommander, http://www.trolsoft.ru/en/soft/trolcommander\n * Copyright (C) 2013-2016 Oleg Trifonov\n *\n * trolCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * trolCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\npackage ru.trolsoft.utils.search;\n\n\nimport java.io.UnsupportedEncodingException;\n\n/**\n * @author Oleg Trifonov\n * Created on 19/11/14.\n */\npublic class StringCaseInsensitiveSearchPattern implements SearchPattern {\n    private final byte[] data;\n    private final byte[] dataAlt;\n\n    public StringCaseInsensitiveSearchPattern(String s, String charset) throws UnsupportedEncodingException {\n        this.data = s.toLowerCase().getBytes(charset);\n        this.dataAlt = s.toUpperCase().getBytes(charset);\n    }\n\n    @Override\n    public int length() {\n        return data.length;\n    }\n\n    @Override\n    public boolean checkByte(int index, int val) {\n        return (data[index] & 0xff) == val || (dataAlt[index] & 0xff) == val;\n    }\n\n    @Override\n    public boolean checkSelf(int index1, int index2) {\n        return data[index1] == data[index2] || dataAlt[index1] == dataAlt[index2] || data[index1] == dataAlt[index2] || dataAlt[index1] == data[index2];\n    }\n}\n"
  },
  {
    "path": "src/main/java/ru/trolsoft/utils/search/StringCaseSensitiveSearchPattern.java",
    "content": "package ru.trolsoft.utils.search;\n\nimport java.io.UnsupportedEncodingException;\n\n/**\n * @author Oleg Trifonov\n * Created on 19/11/14.\n */\npublic class StringCaseSensitiveSearchPattern implements SearchPattern {\n\n    private final byte[] data;\n\n    public StringCaseSensitiveSearchPattern(String s, String charset) throws UnsupportedEncodingException {\n        this.data = s.getBytes(charset);\n    }\n\n    @Override\n    public int length() {\n        return data.length;\n    }\n\n    @Override\n    public boolean checkByte(int index, int val) {\n        return (data[index] & 0xff) == val;\n    }\n\n    @Override\n    public boolean checkSelf(int index1, int index2) {\n        return data[index1] == data[index2];\n    }\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/AdbFilterInputStream.java",
    "content": "package se.vidstige.jadb;\n\nimport java.io.FilterInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\npublic class AdbFilterInputStream extends FilterInputStream {\n    public AdbFilterInputStream(InputStream inputStream) {\n        super(inputStream);\n    }\n\n    @Override\n    public int read() throws IOException {\n        int b1 = in.read();\n        if (b1 == 0x0d) {\n            in.mark(1);\n            int b2 = in.read();\n            if (b2 == 0x0a) {\n                return b2;\n            }\n            in.reset();\n        }\n        return b1;\n    }\n\n    @Override\n    public int read(byte[] buffer, int offset, int length) throws IOException {\n        int n = 0;\n        for (int i = 0; i < length; i++) {\n            int b = read();\n            if (b == -1) return n == 0 ? -1 : n;\n            buffer[offset + n] = (byte) b;\n            n++;\n\n            // Return as soon as no more data is available (and at least one byte was read)\n            if (in.available() <= 0) {\n                return n;\n            }\n        }\n        return n;\n    }\n\n    @Override\n    public int read(byte[] buffer) throws IOException {\n        return read(buffer, 0, buffer.length);\n    }\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/AdbFilterOutputStream.java",
    "content": "package se.vidstige.jadb;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\npublic class AdbFilterOutputStream extends LookBackFilteringOutputStream {\n    public AdbFilterOutputStream(OutputStream inner) {\n        super(inner, 1);\n    }\n\n    @Override\n    public void write(int c) throws IOException {\n        if (!lookback().isEmpty()) {\n            Byte last = lookback().getFirst();\n            if (last != null && last == 0x0d && c == 0x0a) {\n                unwrite();\n            }\n        }\n        super.write(c);\n    }\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/AdbServerLauncher.java",
    "content": "package se.vidstige.jadb;\n\nimport java.io.IOException;\nimport java.util.Map;\n\n/**\n * Launches the ADB server\n */\npublic class AdbServerLauncher {\n    private final String executable;\n    private final Subprocess subprocess;\n\n    /**\n     * Creates a new launcher loading ADB from the environment.\n     *\n     * @param subprocess the sub-process.\n     * @param environment the environment to use to locate the ADB executable.\n     */\n    public AdbServerLauncher(Subprocess subprocess, Map<String, String> environment) {\n        this.subprocess = subprocess;\n        this.executable = findAdbExecutable(environment);\n    }\n\n    /**\n     * Creates a new launcher with the specified ADB.\n     *\n     * @param subprocess the sub-process.\n     * @param executable the location of the ADB executable.\n     */\n    public AdbServerLauncher(Subprocess subprocess, String executable) {\n        this.subprocess = subprocess;\n        this.executable = executable;\n    }\n\n    private static String findAdbExecutable(Map<String, String> environment) {\n        String androidHome = environment.get(\"ANDROID_HOME\");\n        if (androidHome == null || androidHome.isEmpty()) {\n            return \"adb\";\n        }\n        return androidHome + \"/platform-tools/adb\";\n    }\n\n    public void launch() throws IOException, InterruptedException {\n        Process p = subprocess.execute(new String[]{executable, \"start-server\"});\n        p.waitFor();\n        int exitValue = p.exitValue();\n        if (exitValue != 0) {\n            throw new IOException(\"adb exited with exit code: \" + exitValue);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/ConnectionToRemoteDeviceException.java",
    "content": "package se.vidstige.jadb;\n\npublic class ConnectionToRemoteDeviceException extends Exception {\n    public ConnectionToRemoteDeviceException(String message) {\n        super(message);\n    }\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/DeviceDetectionListener.java",
    "content": "package se.vidstige.jadb;\n\nimport java.util.List;\n\npublic interface DeviceDetectionListener {\n    void onDetect(List<JadbDevice> devices);\n    void onException(Exception e);\n}\n\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/DeviceWatcher.java",
    "content": "package se.vidstige.jadb;\n\nimport java.io.IOException;\n\npublic class DeviceWatcher implements Runnable {\n    private Transport transport;\n    private final DeviceDetectionListener listener;\n    private final JadbConnection connection;\n\n    public DeviceWatcher(Transport transport, DeviceDetectionListener listener, JadbConnection connection) {\n        this.transport = transport;\n        this.listener = listener;\n        this.connection = connection;\n    }\n\n    @Override\n    public void run() {\n        watch();\n    }\n\n    public void watch() {\n        try {\n            while (true) {\n                listener.onDetect(connection.parseDevices(transport.readString()));\n            }\n        } catch (IOException ioe) {\n            synchronized(this) {\n                if (transport != null) {\n                    listener.onException(ioe);\n                }\n            }\n        } catch (Exception e) {\n            listener.onException(e);\n        }\n    }\n\n    public void stop() throws IOException {\n        synchronized(this) {\n            transport.close();\n            transport = null;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/HostConnectToRemoteTcpDevice.java",
    "content": "package se.vidstige.jadb;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\n\nclass HostConnectToRemoteTcpDevice {\n    private final Transport transport;\n    private final ResponseValidator responseValidator;\n\n    HostConnectToRemoteTcpDevice(Transport transport) {\n        this.transport = transport;\n        this.responseValidator = new ResponseValidatorImp();\n    }\n\n    //Visible for testing\n    HostConnectToRemoteTcpDevice(Transport transport, ResponseValidator responseValidator) {\n        this.transport = transport;\n        this.responseValidator = responseValidator;\n    }\n\n    InetSocketAddress connect(InetSocketAddress inetSocketAddress)\n            throws IOException, JadbException, ConnectionToRemoteDeviceException {\n        transport.send(String.format(\"host:connect:%s:%d\", inetSocketAddress.getHostString(), inetSocketAddress.getPort()));\n        verifyTransportLevel();\n        verifyProtocolLevel();\n\n        return inetSocketAddress;\n    }\n\n    private void verifyTransportLevel() throws IOException, JadbException {\n        transport.verifyResponse();\n    }\n\n    private void verifyProtocolLevel() throws IOException, ConnectionToRemoteDeviceException {\n        String status = transport.readString();\n        responseValidator.validate(status);\n    }\n\n    //@VisibleForTesting\n    interface ResponseValidator {\n        void validate(String response) throws ConnectionToRemoteDeviceException;\n    }\n\n    final static class ResponseValidatorImp implements ResponseValidator {\n        private final static String SUCCESSFULLY_CONNECTED = \"connected to\";\n        private final static String ALREADY_CONNECTED = \"already connected to\";\n\n\n        ResponseValidatorImp() {\n        }\n\n        public void validate(String response) throws ConnectionToRemoteDeviceException {\n            if (!checkIfConnectedSuccessfully(response) && !checkIfAlreadyConnected(response)) {\n                throw new ConnectionToRemoteDeviceException(extractError(response));\n            }\n        }\n\n        private boolean checkIfConnectedSuccessfully(String response) {\n            return response.startsWith(SUCCESSFULLY_CONNECTED);\n        }\n\n        private boolean checkIfAlreadyConnected(String response) {\n            return response.startsWith(ALREADY_CONNECTED);\n        }\n\n        private String extractError(String response) {\n            int lastColon = response.lastIndexOf(\":\");\n            if (lastColon != -1) {\n                return response.substring(lastColon);\n            } else {\n                return response;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/HostDisconnectFromRemoteTcpDevice.java",
    "content": "package se.vidstige.jadb;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\n\npublic class HostDisconnectFromRemoteTcpDevice {\n    private final Transport transport;\n    private final ResponseValidator responseValidator;\n\n    HostDisconnectFromRemoteTcpDevice(Transport transport) {\n        this.transport = transport;\n        this.responseValidator = new ResponseValidatorImp();\n    }\n\n    //Visible for testing\n    HostDisconnectFromRemoteTcpDevice(Transport transport, ResponseValidator responseValidator) {\n        this.transport = transport;\n        this.responseValidator = responseValidator;\n    }\n\n    InetSocketAddress disconnect(InetSocketAddress inetSocketAddress)\n            throws IOException, JadbException, ConnectionToRemoteDeviceException {\n        transport.send(String.format(\"host:disconnect:%s:%d\", inetSocketAddress.getHostString(), inetSocketAddress.getPort()));\n        verifyTransportLevel();\n        verifyProtocolLevel();\n\n        return inetSocketAddress;\n    }\n\n    private void verifyTransportLevel() throws IOException, JadbException {\n        transport.verifyResponse();\n    }\n\n    private void verifyProtocolLevel() throws IOException, ConnectionToRemoteDeviceException {\n        String status = transport.readString();\n        responseValidator.validate(status);\n    }\n\n    //@VisibleForTesting\n    interface ResponseValidator {\n        void validate(String response) throws ConnectionToRemoteDeviceException;\n    }\n\n    final static class ResponseValidatorImp implements ResponseValidator {\n        private final static String SUCCESSFULLY_DISCONNECTED = \"disconnected\";\n        private final static String ALREADY_DISCONNECTED = \"error: no such device\";\n\n\n        ResponseValidatorImp() {\n        }\n\n        public void validate(String response) throws ConnectionToRemoteDeviceException {\n            if (!checkIfConnectedSuccessfully(response) && !checkIfAlreadyConnected(response)) {\n                throw new ConnectionToRemoteDeviceException(extractError(response));\n            }\n        }\n\n        private boolean checkIfConnectedSuccessfully(String response) {\n            return response.startsWith(SUCCESSFULLY_DISCONNECTED);\n        }\n\n        private boolean checkIfAlreadyConnected(String response) {\n            return response.startsWith(ALREADY_DISCONNECTED);\n        }\n\n        private String extractError(String response) {\n            int lastColon = response.lastIndexOf(\":\");\n            if (lastColon != -1) {\n                return response.substring(lastColon);\n            } else {\n                return response;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/ITransportFactory.java",
    "content": "package se.vidstige.jadb;\n\nimport java.io.IOException;\n\n/**\n * Created by Törcsi on 2016. 03. 01..\n */\npublic interface ITransportFactory {\n    Transport createTransport() throws IOException;\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/JadbConnection.java",
    "content": "package se.vidstige.jadb;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.net.Socket;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class JadbConnection implements ITransportFactory {\n\n    private final String host;\n    private final int port;\n\n    private static final int DEFAULTPORT = 5037;\n\n    public JadbConnection() {\n        this(\"localhost\", DEFAULTPORT);\n    }\n\n    private JadbConnection(String host, int port) {\n        this.host = host;\n        this.port = port;\n    }\n\n    public Transport createTransport() throws IOException {\n        return new Transport(new Socket(host, port));\n    }\n\n    public String getHostVersion() throws IOException, JadbException {\n        Transport main = createTransport();\n        main.send(\"host:version\");\n        main.verifyResponse();\n        String version = main.readString();\n        main.close();\n        return version;\n    }\n\n    public InetSocketAddress connectToTcpDevice(InetSocketAddress inetSocketAddress)\n            throws IOException, JadbException, ConnectionToRemoteDeviceException {\n        Transport transport = createTransport();\n        try {\n            return new HostConnectToRemoteTcpDevice(transport).connect(inetSocketAddress);\n        } finally {\n            transport.close();\n        }\n    }\n\n    public InetSocketAddress disconnectFromTcpDevice(InetSocketAddress tcpAddressEntity)\n            throws IOException, JadbException, ConnectionToRemoteDeviceException {\n        Transport transport = createTransport();\n        try {\n            return new HostDisconnectFromRemoteTcpDevice(transport).disconnect(tcpAddressEntity);\n        } finally {\n            transport.close();\n        }\n    }\n\n\n    public List<JadbDevice> getDevices() throws IOException, JadbException {\n        Transport devices = createTransport();\n        devices.send(\"host:devices\");\n        devices.verifyResponse();\n        String body = devices.readString();\n        devices.close();\n        return parseDevices(body);\n    }\n\n    public DeviceWatcher createDeviceWatcher(DeviceDetectionListener listener) throws IOException, JadbException {\n        Transport transport = createTransport();\n        transport.send(\"host:track-devices\");\n        transport.verifyResponse();\n        return new DeviceWatcher(transport, listener, this);\n    }\n\n    List<JadbDevice> parseDevices(String body) {\n        String[] lines = body.split(\"\\n\");\n        ArrayList<JadbDevice> devices = new ArrayList<>(lines.length);\n        for (String line : lines) {\n            String[] parts = line.split(\"\\t\");\n            if (parts.length > 1) {\n                devices.add(new JadbDevice(parts[0], parts[1], this));\n            }\n        }\n        return devices;\n    }\n\n    public JadbDevice getAnyDevice() {\n        return JadbDevice.createAny(this);\n    }\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/JadbDevice.java",
    "content": "package se.vidstige.jadb;\n\nimport se.vidstige.jadb.managers.Bash;\n\nimport java.io.*;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class JadbDevice {\n    public enum State {\n        UNKNOWN,\n        OFFLINE,\n        DEVICE,\n        BOOTLOADER\n    }\n\n    private final String serial;\n    private final ITransportFactory transportFactory;\n\n    JadbDevice(String serial, String type, ITransportFactory tFactory) {\n        this.serial = serial;\n        this.transportFactory = tFactory;\n    }\n\n    static JadbDevice createAny(JadbConnection connection) {\n        return new JadbDevice(connection);\n    }\n\n    private JadbDevice(ITransportFactory tFactory) {\n        serial = null;\n        this.transportFactory = tFactory;\n    }\n\n    private State convertState(String type) {\n        switch (type) {\n            case \"device\":\n                return State.DEVICE;\n            case \"offline\":\n                return State.OFFLINE;\n            case \"bootloader\":\n                return State.BOOTLOADER;\n            default:\n                return State.UNKNOWN;\n        }\n    }\n\n    private Transport getTransport() throws IOException, JadbException {\n        Transport transport = transportFactory.createTransport();\n        if (serial == null) {\n            transport.send(\"host:transport-any\");\n            transport.verifyResponse();\n        } else {\n            transport.send(\"host:transport:\" + serial);\n            transport.verifyResponse();\n        }\n        return transport;\n    }\n\n    public String getSerial() {\n        return serial;\n    }\n\n    public State getState() throws IOException, JadbException {\n        Transport transport = transportFactory.createTransport();\n        if (serial == null) {\n            transport.send(\"host:get-state\");\n            transport.verifyResponse();\n        } else {\n            transport.send(\"host-serial:\" + serial + \":get-state\");\n            transport.verifyResponse();\n        }\n\n        State state = convertState(transport.readString());\n        transport.close();\n        return state;\n    }\n\n    /** <p>Execute a shell command.\n     *\n     * <p>For Lollipop and later see: {@link #execute(String, String...)}\n     *\n     * @param command main command to run. E.g. \"ls\"\n     * @param args arguments to the command.\n     * @return combined stdout/stderr stream.\n     * @throws IOException\n     * @throws JadbException\n     */\n    public InputStream executeShell(String command, String... args) throws IOException, JadbException {\n        Transport transport = getTransport();\n        StringBuilder shellLine = buildCmdLine(command, args);\n        send(transport, \"shell:\" + shellLine.toString());\n        return new AdbFilterInputStream(new BufferedInputStream(transport.getInputStream()));\n    }\n\n    /**\n     *\n     * @deprecated Use InputStream executeShell(String command, String... args) method instead. Together with\n     * Stream.copy(in, out), it is possible to achieve the same effect.\n     */\n    @Deprecated\n    public void executeShell(OutputStream output, String command, String... args) throws IOException, JadbException {\n        Transport transport = getTransport();\n        StringBuilder shellLine = buildCmdLine(command, args);\n        send(transport, \"shell:\" + shellLine.toString());\n        if (output != null) {\n        \ttry (AdbFilterOutputStream out = new AdbFilterOutputStream(output)) {\n        \t\ttransport.readResponseTo(out);\n        \t}\n        }\n    }\n\n    /** <p>Execute a command with raw binary output.\n     *\n     * <p>Support for this command was added in Lollipop (Android 5.0), and is the recommended way to transmit binary\n     * data with that version or later. For earlier versions of Android, use\n     * {@link #executeShell(String, String...)}.\n     *\n     * @param command main command to run, e.g. \"screencap\"\n     * @param args arguments to the command, e.g. \"-p\".\n     * @return combined stdout/stderr stream.\n     * @throws IOException\n     * @throws JadbException\n     */\n    public InputStream execute(String command, String... args) throws IOException, JadbException {\n        Transport transport = getTransport();\n        StringBuilder shellLine = buildCmdLine(command, args);\n        send(transport, \"exec:\" + shellLine.toString());\n        return new BufferedInputStream(transport.getInputStream());\n    }\n\n    /**\n     * Builds a command line string from the command and its arguments.\n     *\n     * @param command the command.\n     * @param args the list of arguments.\n     * @return the command line.\n     */\n    private StringBuilder buildCmdLine(String command, String... args) {\n        StringBuilder shellLine = new StringBuilder(command);\n        for (String arg : args) {\n            shellLine.append(\" \");\n            shellLine.append(Bash.quote(arg));\n        }\n        return shellLine;\n    }\n\n\n    public List<RemoteFile> list(String remotePath) throws IOException, JadbException {\n        Transport transport = getTransport();\n        SyncTransport sync = transport.startSync();\n        sync.send(\"LIST\", remotePath);\n\n        List<RemoteFile> result = new ArrayList<>();\n        for (RemoteFileRecord dent = sync.readDirectoryEntry(); dent != RemoteFileRecord.DONE; dent = sync.readDirectoryEntry()) {\n            result.add(dent);\n        }\n        return result;\n    }\n\n    private int getMode(File file) {\n        //noinspection OctalInteger\n        return 0664;\n    }\n\n    public void push(InputStream source, long lastModified, int mode, RemoteFile remote) throws IOException, JadbException {\n        Transport transport = getTransport();\n        SyncTransport sync = transport.startSync();\n        sync.send(\"SEND\", remote.getPath() + \",\" + Integer.toString(mode));\n\n        sync.sendStream(source);\n\n        sync.sendStatus(\"DONE\", (int) lastModified);\n        sync.verifyStatus();\n    }\n\n    public void push(File local, RemoteFile remote) throws IOException, JadbException {\n        FileInputStream fileStream = new FileInputStream(local);\n        push(fileStream, local.lastModified(), getMode(local), remote);\n        fileStream.close();\n    }\n\n    public void pull(RemoteFile remote, OutputStream destination) throws IOException, JadbException {\n        Transport transport = getTransport();\n        SyncTransport sync = transport.startSync();\n        sync.send(\"RECV\", remote.getPath());\n\n        sync.readChunksTo(destination);\n    }\n\n    public void pull(RemoteFile remote, File local) throws IOException, JadbException {\n        FileOutputStream fileStream = new FileOutputStream(local);\n        pull(remote, fileStream);\n        fileStream.close();\n    }\n\n    private void send(Transport transport, String command) throws IOException, JadbException {\n        transport.send(command);\n        transport.verifyResponse();\n    }\n\n    @Override\n    public String toString() {\n        return \"Android Device with serial \" + serial;\n    }\n\n    @Override\n    public int hashCode() {\n        final int prime = 31;\n        int result = 1;\n        result = prime * result + ((serial == null) ? 0 : serial.hashCode());\n        return result;\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (this == obj)\n            return true;\n        if (obj == null)\n            return false;\n        if (getClass() != obj.getClass())\n            return false;\n        JadbDevice other = (JadbDevice) obj;\n        if (serial == null) {\n            return other.serial == null;\n        } else return serial.equals(other.serial);\n    }\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/JadbException.java",
    "content": "package se.vidstige.jadb;\n\npublic class JadbException extends Exception {\n\n    public JadbException(String message) {\n        super(message);\n    }\n\n    private static final long serialVersionUID = -3879283786835654165L;\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/LookBackFilteringOutputStream.java",
    "content": "package se.vidstige.jadb;\n\nimport java.io.FilterOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.ArrayDeque;\n\npublic class LookBackFilteringOutputStream extends FilterOutputStream {\n    private final ArrayDeque<Byte> buffer;\n    private final int lookBackBufferSize;\n\n    LookBackFilteringOutputStream(OutputStream inner, int lookBackBufferSize)\n    {\n        super(inner);\n        this.lookBackBufferSize = lookBackBufferSize;\n        this.buffer = new ArrayDeque<>(lookBackBufferSize);\n    }\n\n    void unwrite() {\n        buffer.removeFirst();\n    }\n\n    ArrayDeque<Byte> lookback() {\n        return buffer;\n    }\n\n    @Override\n    public void write(int c) throws IOException {\n        buffer.addLast((byte) c);\n        flushBuffer(lookBackBufferSize);\n    }\n\n    @Override\n    public void flush() throws IOException {\n        flushBuffer(0);\n        out.flush();\n    }\n\n    private void flushBuffer(int size) throws IOException {\n        while (buffer.size() > size) {\n            Byte b = buffer.removeFirst();\n            out.write(b);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/RemoteFile.java",
    "content": "package se.vidstige.jadb;\n\n/**\n * Created by vidstige on 2014-03-20\n */\npublic class RemoteFile {\n    private final String path;\n\n    public RemoteFile(String path) {\n        this.path = path;\n    }\n\n    public String getName() {\n        throw new UnsupportedOperationException();\n    }\n\n    public int getSize() {\n        throw new UnsupportedOperationException();\n    }\n\n    public long getLastModified() {\n        throw new UnsupportedOperationException();\n    }\n\n    public boolean isDirectory() {\n        throw new UnsupportedOperationException();\n    }\n\n    public String getPath() {\n        return path;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) return false;\n\n        RemoteFile that = (RemoteFile) o;\n\n        return path.equals(that.path);\n    }\n\n    @Override\n    public int hashCode() {\n        return path.hashCode();\n    }\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/RemoteFileRecord.java",
    "content": "package se.vidstige.jadb;\n\n/**\n * Created by vidstige on 2014-03-19.\n */\nclass RemoteFileRecord extends RemoteFile {\n    public static final RemoteFileRecord DONE = new RemoteFileRecord(null, 0, 0, 0);\n\n    private final int mode;\n    private final int size;\n    private final long lastModified;\n\n    public RemoteFileRecord(String name, int mode, int size, long lastModified) {\n        super(name);\n        this.mode = mode;\n        this.size = size;\n        this.lastModified = lastModified;\n    }\n\n    @Override\n    public int getSize() {\n        return size;\n    }\n\n    @Override\n    public long getLastModified() {\n        return lastModified;\n    }\n\n    @Override\n    public boolean isDirectory() {\n        return (mode & (1 << 14)) == (1 << 14);\n    }\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/Stream.java",
    "content": "package se.vidstige.jadb;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.nio.charset.Charset;\n\npublic class Stream {\n    public static void copy(InputStream in, OutputStream out) throws IOException {\n        byte[] buffer = new byte[1024 * 10];\n        int len;\n        while ((len = in.read(buffer)) != -1) {\n            out.write(buffer, 0, len);\n        }\n    }\n\n    public static String readAll(InputStream input, Charset charset) throws IOException {\n        ByteArrayOutputStream tmp = new ByteArrayOutputStream();\n        Stream.copy(input, tmp);\n        return new String(tmp.toByteArray(), charset);\n    }\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/Subprocess.java",
    "content": "package se.vidstige.jadb;\n\nimport java.io.IOException;\n\npublic class Subprocess {\n    public Process execute(String[] command) throws IOException {\n        return Runtime.getRuntime().exec(command);\n    }\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/SyncTransport.java",
    "content": "package se.vidstige.jadb;\n\nimport java.io.*;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\n\n/**\n * Created by vidstige on 2014-03-19.\n */\npublic class SyncTransport {\n\n    private final DataOutput output;\n    private final DataInput input;\n\n    SyncTransport(OutputStream outputStream, InputStream inputStream) {\n        output = new DataOutputStream(outputStream);\n        input = new DataInputStream(inputStream);\n    }\n\n    public SyncTransport(DataOutput outputStream, DataInput inputStream) {\n        output = outputStream;\n        input = inputStream;\n    }\n\n    public void send(String syncCommand, String name) throws IOException {\n        if (syncCommand.length() != 4) {\n            throw new IllegalArgumentException(\"sync commands must have length 4\");\n        }\n        output.writeBytes(syncCommand);\n//        output.writeInt(Integer.reverseBytes(name.length()));\n//        output.writeBytes(name);\n        byte[] data = name.getBytes(StandardCharsets.UTF_8);\n        output.writeInt(Integer.reverseBytes(data.length));\n        output.write(data);\n    }\n\n    public void sendStatus(String statusCode, int length) throws IOException {\n        output.writeBytes(statusCode);\n        output.writeInt(Integer.reverseBytes(length));\n    }\n\n    void verifyStatus() throws IOException, JadbException {\n        String status = readString(4);\n        int length = readInt();\n        if (\"FAIL\".equals(status)) {\n            String error = readString(length);\n            throw new JadbException(error);\n        }\n        if (!\"OKAY\".equals(status)) {\n            throw new JadbException(\"Unknown error: \" + status);\n        }\n    }\n\n    private int readInt() throws IOException {\n        return Integer.reverseBytes(input.readInt());\n    }\n\n    private String readString(int length) throws IOException {\n        byte[] buffer = new byte[length];\n        input.readFully(buffer);\n        return new String(buffer, Charset.forName(\"utf-8\"));\n    }\n\n    RemoteFileRecord readDirectoryEntry() throws IOException {\n        String id = readString(4);\n        int mode = readInt();\n        int size = readInt();\n        int time = readInt();\n        int nameLength = readInt();\n        String name = readString(nameLength);\n\n        if (!\"DENT\".equals(id)) {\n            return RemoteFileRecord.DONE;\n        }\n        return new RemoteFileRecord(name, mode, size, time);\n    }\n\n    private void sendChunk(byte[] buffer, int offset, int length) throws IOException {\n        output.writeBytes(\"DATA\");\n        output.writeInt(Integer.reverseBytes(length));\n        output.write(buffer, offset, length);\n    }\n\n    private int readChunk(byte[] buffer) throws IOException, JadbException {\n        String id = readString(4);\n        int n = readInt();\n        if (\"FAIL\".equals(id)) {\n            throw new JadbException(readString(n));\n        }\n        if (!\"DATA\".equals(id)) return -1;\n        input.readFully(buffer, 0, n);\n        return n;\n    }\n\n    public void sendStream(InputStream in) throws IOException {\n        byte[] buffer = new byte[1024 * 64];\n        int n = in.read(buffer);\n        while (n != -1) {\n            sendChunk(buffer, 0, n);\n            n = in.read(buffer);\n        }\n    }\n\n    public void readChunksTo(OutputStream stream) throws IOException, JadbException {\n        byte[] buffer = new byte[1024 * 64];\n        int n = readChunk(buffer);\n        while (n != -1) {\n            stream.write(buffer, 0, n);\n            n = readChunk(buffer);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/Transport.java",
    "content": "package se.vidstige.jadb;\n\nimport java.io.*;\nimport java.net.Socket;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\n\nclass Transport {\n\n    private final OutputStream outputStream;\n    private final InputStream inputStream;\n\n    private Transport(OutputStream outputStream, InputStream inputStream) {\n        this.outputStream = outputStream;\n        this.inputStream = inputStream;\n    }\n\n    Transport(Socket socket) throws IOException {\n        this(socket.getOutputStream(), socket.getInputStream());\n    }\n\n    String readString() throws IOException {\n        String encodedLength = readString(4);\n        int length = Integer.parseInt(encodedLength, 16);\n        return readString(length);\n    }\n\n    void readResponseTo(OutputStream output) throws IOException {\n        Stream.copy(inputStream, output);\n    }\n\n    public InputStream getInputStream() {\n        return inputStream;\n    }\n\n    void verifyResponse() throws IOException, JadbException {\n        String response = readString(4);\n        if (!\"OKAY\".equals(response)) {\n            String error = readString();\n            throw new JadbException(\"command failed: \" + error);\n        }\n    }\n\n    private String readString(int length) throws IOException {\n        DataInput reader = new DataInputStream(inputStream);\n        byte[] responseBuffer = new byte[length];\n        reader.readFully(responseBuffer);\n        return new String(responseBuffer, Charset.forName(\"utf-8\"));\n    }\n\n//    private String getCommandLength(String command) {\n//        return String.format(\"%04x\", command.length());\n//    }\n\n    public void send(String command) throws IOException {\n        OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);\n        byte[] data = command.getBytes(StandardCharsets.UTF_8);\n        writer.write(String.format(\"%04x\", data.length));\n        //writer.write(getCommandLength(command));\n        writer.write(command);\n        writer.flush();\n    }\n\n    SyncTransport startSync() throws IOException, JadbException {\n        send(\"sync:\");\n        verifyResponse();\n        return new SyncTransport(outputStream, inputStream);\n    }\n\n    public void close() throws IOException {\n        inputStream.close();\n        outputStream.close();\n    }\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/managers/Bash.java",
    "content": "package se.vidstige.jadb.managers;\n\npublic class Bash {\n    public static String quote(String s) {\n        // TODO: Should also check other whitespace\n        if (!s.contains(\" \")) {\n            return s;\n        }\n        return \"'\" + s.replace(\"'\", \"'\\\\''\") + \"'\";\n    }\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/managers/Package.java",
    "content": "package se.vidstige.jadb.managers;\n\n/**\n * Android package\n */\npublic class Package {\n    private final String name;\n\n    public Package(String name) {\n        this.name = name;\n    }\n\n    @Override\n    public String toString() { return name; }\n\n    @Override\n    public boolean equals(Object o) { return name.equals(o); }\n\n    @Override\n    public int hashCode() { return name.hashCode(); }\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/managers/PackageManager.java",
    "content": "package se.vidstige.jadb.managers;\n\nimport se.vidstige.jadb.JadbDevice;\nimport se.vidstige.jadb.JadbException;\nimport se.vidstige.jadb.RemoteFile;\nimport se.vidstige.jadb.Stream;\n\nimport java.io.*;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Java interface to package manager. Launches package manager through jadb\n */\npublic class PackageManager {\n    private final JadbDevice device;\n\n    public PackageManager(JadbDevice device) {\n        this.device = device;\n    }\n\n    public List<Package> getPackages() throws IOException, JadbException {\n        ArrayList<Package> result = new ArrayList<>();\n        try (BufferedReader input = new BufferedReader(new InputStreamReader(device.executeShell(\"pm\", \"list\", \"packages\"), StandardCharsets.UTF_8))) {\n            String line;\n            while ((line = input.readLine()) != null) {\n                final String prefix = \"package:\";\n                if (line.startsWith(prefix)) {\n                    result.add(new Package(line.substring(prefix.length())));\n                }\n            }\n        }\n        return result;\n    }\n\n    private String getErrorMessage(String operation, String target, String errorMessage) {\n        return \"Could not \" + operation + \" \" + target + \": \" + errorMessage;\n    }\n\n    private void verifyOperation(String operation, String target, String result) throws JadbException {\n        if (!result.contains(\"Success\")) throw new JadbException(getErrorMessage(operation, target, result));\n    }\n\n    public void remove(RemoteFile file) throws IOException, JadbException {\n        InputStream s = device.executeShell(\"rm\", \"-f\", Bash.quote(file.getPath()));\n        Stream.readAll(s, StandardCharsets.UTF_8);\n    }\n\n    private void install(File apkFile, List<String> extraArguments) throws IOException, JadbException {\n        RemoteFile remote = new RemoteFile(\"/sdcard/tmp/\" + apkFile.getName());\n        device.push(apkFile, remote);\n        ArrayList<String> arguments = new ArrayList<>();\n        arguments.add(\"install\");\n        arguments.addAll(extraArguments);\n        arguments.add(remote.getPath());\n        InputStream s = device.executeShell(\"pm\", arguments.toArray(new String[0]));\n        String result = Stream.readAll(s, StandardCharsets.UTF_8);\n        remove(remote);\n        verifyOperation(\"install\", apkFile.getName(), result);\n    }\n\n    public void install(File apkFile) throws IOException, JadbException {\n        install(apkFile, new ArrayList<>(0));\n    }\n\n    private void installWithOptions(File apkFile, List<? extends InstallOption> options) throws IOException, JadbException {\n        List<String> optionsAsStr = new ArrayList<>(options.size());\n\n        for(InstallOption installOption : options) {\n            optionsAsStr.add(installOption.getStringRepresentation());\n        }\n        install(apkFile, optionsAsStr);\n    }\n\n    public void forceInstall(File apkFile) throws IOException, JadbException {\n        installWithOptions(apkFile, Collections.singletonList(REINSTALL_KEEPING_DATA));\n    }\n\n    public void uninstall(Package name) throws IOException, JadbException {\n        InputStream s = device.executeShell(\"pm\", \"uninstall\", name.toString());\n        String result = Stream.readAll(s, StandardCharsets.UTF_8);\n        verifyOperation(\"uninstall\", name.toString(), result);\n    }\n\n    public void launch(Package name) throws IOException, JadbException {\n        InputStream s = device.executeShell(\"monkey\", \"-p\", name.toString(), \"-c\", \"android.intent.category.LAUNCHER\", \"1\");\n        s.close();\n    }\n\n    //<editor-fold desc=\"InstallOption\">\n    public static class InstallOption {\n        InstallOption(String ... varargs) {\n            for(String str: varargs) {\n                stringBuilder.append(str).append(\" \");\n            }\n        }\n\n        private final StringBuilder stringBuilder = new StringBuilder();\n\n        private String getStringRepresentation() {\n            return stringBuilder.toString();\n        }\n    }\n\n    public static final InstallOption WITH_FORWARD_LOCK = new InstallOption(\"-l\");\n\n    private static final InstallOption REINSTALL_KEEPING_DATA =\n            new InstallOption(\"-r\");\n\n    public static final InstallOption ALLOW_TEST_APK =\n            new InstallOption(\"-t\");\n\n    public static InstallOption WITH_INSTALLER_PACKAGE_NAME(String name)\n    {\n        return new InstallOption(\"-t\", name);\n    }\n\n    public static InstallOption ON_SHARED_MASS_STORAGE(String name) {\n        return new InstallOption(\"-s\", name);\n    }\n\n    public static InstallOption ON_INTERNAL_SYSTEM_MEMORY(String name) {\n        return new InstallOption(\"-f\", name);\n    }\n\n    public static final InstallOption ALLOW_VERSION_DOWNGRADE =\n            new InstallOption(\"-d\");\n\n    /**\n     * This option is supported only from Android 6.X+\n     */\n    public static final InstallOption GRANT_ALL_PERMISSIONS = new InstallOption(\"-g\");\n\n    //</editor-fold>\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/managers/PropertyManager.java",
    "content": "package se.vidstige.jadb.managers;\n\nimport se.vidstige.jadb.JadbDevice;\nimport se.vidstige.jadb.JadbException;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * A class which works with properties, uses getprop and setprop methods of android shell\n */\npublic class PropertyManager {\n    private final Pattern pattern = Pattern.compile(\"^\\\\[([a-zA-Z0-9_.-]*)\\\\]:.\\\\[([^\\\\[\\\\]]*)\\\\]\");\n    private final JadbDevice device;\n\n    public PropertyManager(JadbDevice device) {\n        this.device = device;\n    }\n\n    public Map<String, String> getprop() throws IOException, JadbException {\n        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(device.executeShell(\"getprop\")))) {\n            return parseProp(bufferedReader);\n        }\n    }\n\n    private Map<String, String> parseProp(BufferedReader bufferedReader) throws IOException {\n        //final Pattern pattern = Pattern.compile(\"^\\\\[([a-zA-Z0-9_.-]*)\\\\]:.\\\\[([a-zA-Z0-9_.-]*)\\\\]\");\n        Map<String, String> result = new HashMap<>();\n\n        String line;\n        Matcher matcher = pattern.matcher(\"\");\n\n        while ((line = bufferedReader.readLine()) != null) {\n            matcher.reset(line);\n\n            if (matcher.find()) {\n                if (matcher.groupCount() < 2) {\n                    System.err.println(\"Property line: \" + line + \" does not match patter. Ignoring\");\n                    continue;\n                }\n                String key = matcher.group(1);\n                String value = matcher.group(2);\n                result.put(key, value);\n            }\n        }\n\n        return result;\n    }\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/server/AdbDeviceResponder.java",
    "content": "package se.vidstige.jadb.server;\n\nimport se.vidstige.jadb.JadbException;\nimport se.vidstige.jadb.RemoteFile;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.DataInput;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\n\n/**\n * Created by vidstige on 20/03/14.\n */\npublic interface AdbDeviceResponder {\n    String getSerial();\n    String getType();\n\n    void filePushed(RemoteFile path, int mode, ByteArrayOutputStream buffer) throws JadbException;\n    void filePulled(RemoteFile path, ByteArrayOutputStream buffer) throws JadbException, IOException;\n\n    void shell(String command, DataOutputStream stdout, DataInput stdin) throws IOException;\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/server/AdbProtocolHandler.java",
    "content": "package se.vidstige.jadb.server;\n\nimport se.vidstige.jadb.JadbException;\nimport se.vidstige.jadb.RemoteFile;\nimport se.vidstige.jadb.SyncTransport;\n\nimport java.io.*;\nimport java.net.ProtocolException;\nimport java.net.Socket;\nimport java.nio.charset.StandardCharsets;\n\nclass AdbProtocolHandler implements Runnable {\n    private final Socket socket;\n    private final AdbResponder responder;\n    private AdbDeviceResponder selected;\n\n    public AdbProtocolHandler(Socket socket, AdbResponder responder) {\n        this.socket = socket;\n        this.responder = responder;\n    }\n\n    private AdbDeviceResponder findDevice(String serial) throws ProtocolException {\n        for (AdbDeviceResponder d : responder.getDevices()) {\n            if (d.getSerial().equals(serial)) return d;\n        }\n        throw new ProtocolException(\"'\" + serial + \"' not connected\");\n    }\n\n    @Override\n    public void run() {\n        try {\n            runServer();\n        } catch (IOException e) {\n            if (e.getMessage() != null) // thrown when exiting for some reason\n                System.out.println(\"IO Error: \" + e.getMessage());\n        }\n    }\n\n    private void runServer() throws IOException {\n        DataInput input = new DataInputStream(socket.getInputStream());\n        DataOutputStream output = new DataOutputStream(socket.getOutputStream());\n\n        while (true) {\n            byte[] buffer = new byte[4];\n            input.readFully(buffer);\n            String encodedLength = new String(buffer, StandardCharsets.UTF_8);\n            int length = Integer.parseInt(encodedLength, 16);\n\n            buffer = new byte[length];\n            input.readFully(buffer);\n            String command = new String(buffer, StandardCharsets.UTF_8);\n\n            responder.onCommand(command);\n\n            try {\n                if (\"host:version\".equals(command)) {\n                    output.writeBytes(\"OKAY\");\n                    send(output, String.format(\"%04x\", responder.getVersion()));\n                } else if (\"host:transport-any\".equals(command)) {\n                    // TODO: Check so that exactly one device is selected.\n                    selected = responder.getDevices().getFirst();\n                    output.writeBytes(\"OKAY\");\n                } else if (\"host:devices\".equals(command)) {\n                    ByteArrayOutputStream tmp = new ByteArrayOutputStream();\n                    DataOutputStream writer = new DataOutputStream(tmp);\n                    for (AdbDeviceResponder d : responder.getDevices()) {\n                        writer.writeBytes(d.getSerial() + \"\\t\" + d.getType() + \"\\n\");\n                    }\n                    output.writeBytes(\"OKAY\");\n                    send(output, tmp.toString(StandardCharsets.UTF_8));\n                } else if (command.startsWith(\"host:transport:\")) {\n                    String serial = command.substring(\"host:transport:\".length());\n                    selected = findDevice(serial);\n                    output.writeBytes(\"OKAY\");\n                } else if (\"sync:\".equals(command)) {\n                    output.writeBytes(\"OKAY\");\n                    try {\n                        sync(output, input);\n                    } catch (JadbException e) { // sync response with a different type of fail message\n                        SyncTransport sync = new SyncTransport(output, input);\n                        sync.send(\"FAIL\", e.getMessage());\n                    }\n                } else if (command.startsWith(\"shell:\")) {\n                    String shellCommand = command.substring(\"shell:\".length());\n                    output.writeBytes(\"OKAY\");\n                    shell(shellCommand, output, input);\n                    output.close();\n                    return;\n                } else if (\"host:get-state\".equals(command)) {\n                    // TODO: Check so that exactly one device is selected.\n                    AdbDeviceResponder device = responder.getDevices().getFirst();\n                    output.writeBytes(\"OKAY\");\n                    send(output, device.getType());\n                } else if (command.startsWith(\"host-serial:\")) {\n                    String[] strs = command.split(\":\",0);\n                    if (strs.length != 3) {\n                        throw new ProtocolException(\"Invalid command: \" + command);\n                    }\n\n                    String serial = strs[1];\n                    boolean found = false;\n                    output.writeBytes(\"OKAY\");\n                    for (AdbDeviceResponder d : responder.getDevices()) {\n                        if (d.getSerial().equals(serial)) {\n                            send(output, d.getType());\n                            found = true;\n                            break;\n                        }\n                    }\n\n                    if (!found) {\n                        send(output, \"unknown\");\n                    }\n                } else {\n                    throw new ProtocolException(\"Unknown command: \" + command);\n                }\n            } catch (ProtocolException e) {\n                output.writeBytes(\"FAIL\");\n                send(output, e.getMessage());\n            }\n            output.flush();\n        }\n    }\n\n    private void shell(String command, DataOutputStream stdout, DataInput stdin) throws IOException {\n        selected.shell(command, stdout, stdin);\n    }\n\n    private int readInt(DataInput input) throws IOException {\n        return Integer.reverseBytes(input.readInt());\n    }\n\n    private String readString(DataInput input, int length) throws IOException {\n        byte[] responseBuffer = new byte[length];\n        input.readFully(responseBuffer);\n        return new String(responseBuffer, StandardCharsets.UTF_8);\n    }\n\n    private void sync(DataOutput output, DataInput input) throws IOException, JadbException {\n        String id = readString(input, 4);\n        int length = readInt(input);\n        if (\"SEND\".equals(id)) {\n            String remotePath = readString(input, length);\n            int idx = remotePath.lastIndexOf(',');\n            String path = remotePath;\n            int mode = 0666;\n            if (idx > 0) {\n                path = remotePath.substring(0, idx);\n                mode = Integer.parseInt(remotePath.substring(idx + 1));\n            }\n            SyncTransport transport = new SyncTransport(output, input);\n            ByteArrayOutputStream buffer = new ByteArrayOutputStream();\n            transport.readChunksTo(buffer);\n            selected.filePushed(new RemoteFile(path), mode, buffer);\n            transport.sendStatus(\"OKAY\", 0); // 0 = ignored\n        } else if (\"RECV\".equals(id)) {\n            String remotePath = readString(input, length);\n            SyncTransport transport = new SyncTransport(output, input);\n            ByteArrayOutputStream buffer = new ByteArrayOutputStream();\n            selected.filePulled(new RemoteFile(remotePath), buffer);\n            transport.sendStream(new ByteArrayInputStream(buffer.toByteArray()));\n            transport.sendStatus(\"DONE\", 0); // ignored\n        } else throw new JadbException(\"Unknown sync id \" + id);\n    }\n\n    private String getCommandLength(String command) {\n        return String.format(\"%04x\", command.length());\n    }\n\n    public void send(DataOutput writer, String response) throws IOException {\n        writer.writeBytes(getCommandLength(response));\n        writer.writeBytes(response);\n    }\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/server/AdbResponder.java",
    "content": "package se.vidstige.jadb.server;\n\nimport java.util.List;\n\n/**\n * Created by vidstige on 20/03/14.\n */\npublic interface AdbResponder {\n    void onCommand(String command);\n\n    int getVersion();\n\n    List<AdbDeviceResponder> getDevices();\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/server/AdbServer.java",
    "content": "package se.vidstige.jadb.server;\n\nimport java.net.Socket;\n\n/**\n * Created by vidstige on 2014-03-20\n */\npublic class AdbServer extends SocketServer {\n\n    public static final int DEFAULT_PORT = 15037;\n    private final AdbResponder responder;\n\n    public AdbServer(AdbResponder responder) {\n        this(responder, DEFAULT_PORT);\n    }\n\n    public AdbServer(AdbResponder responder, int port) {\n        super(port);\n        this.responder = responder;\n    }\n\n    @Override\n    protected Runnable createResponder(Socket socket) {\n        return new AdbProtocolHandler(socket, responder);\n    }\n}\n"
  },
  {
    "path": "src/main/java/se/vidstige/jadb/server/SocketServer.java",
    "content": "package se.vidstige.jadb.server;\n\nimport java.io.IOException;\nimport java.net.ServerSocket;\nimport java.net.Socket;\n\n// >set ANDROID_ADB_SERVER_PORT=15037\npublic abstract class SocketServer implements Runnable {\n\n    private final int port;\n    private ServerSocket socket;\n    private Thread thread;\n\n    private boolean isStarted = false;\n    private final Object lockObject = new Object();\n\n    protected SocketServer(int port) {\n        this.port = port;\n    }\n\n    public void start() throws InterruptedException {\n        thread = new Thread(this, \"Fake Adb Server\");\n        thread.setDaemon(true);\n        thread.start();\n        waitForServer();\n    }\n\n    public int getPort() {\n        return port;\n    }\n\n    @Override\n    public void run() {\n        try {\n            socket = new ServerSocket(port);\n            socket.setReuseAddress(true);\n\n            serverReady();\n\n            while (true) {\n                Socket c = socket.accept();\n                Thread clientThread = new Thread(createResponder(c), \"AdbClientWorker\");\n                clientThread.setDaemon(true);\n                clientThread.start();\n            }\n        } catch (IOException ignored) {\n        }\n    }\n\n    private void serverReady() {\n        synchronized (lockObject) {\n            isStarted = true;\n            lockObject.notify();\n        }\n    }\n\n    private void waitForServer() throws InterruptedException {\n        synchronized (lockObject) {\n            if (!isStarted) {\n                lockObject.wait();\n            }\n        }\n    }\n\n    protected abstract Runnable createResponder(Socket socket);\n\n    public void stop() throws IOException, InterruptedException {\n        socket.close();\n        thread.join();\n    }\n}\n"
  },
  {
    "path": "src/main/resources/META-INF/LICENSE",
    "content": "                   GNU LESSER GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\n  This version of the GNU Lesser General Public License incorporates\nthe terms and conditions of version 3 of the GNU General Public\nLicense, supplemented by the additional permissions listed below.\n\n  0. Additional Definitions. \n\n  As used herein, \"this License\" refers to version 3 of the GNU Lesser\nGeneral Public License, and the \"GNU GPL\" refers to version 3 of the GNU\nGeneral Public License.\n\n  \"The Library\" refers to a covered work governed by this License,\nother than an Application or a Combined Work as defined below.\n\n  An \"Application\" is any work that makes use of an interface provided\nby the Library, but which is not otherwise based on the Library.\nDefining a subclass of a class defined by the Library is deemed a mode\nof using an interface provided by the Library.\n\n  A \"Combined Work\" is a work produced by combining or linking an\nApplication with the Library.  The particular version of the Library\nwith which the Combined Work was made is also called the \"Linked\nVersion\".\n\n  The \"Minimal Corresponding Source\" for a Combined Work means the\nCorresponding Source for the Combined Work, excluding any source code\nfor portions of the Combined Work that, considered in isolation, are\nbased on the Application, and not on the Linked Version.\n\n  The \"Corresponding Application Code\" for a Combined Work means the\nobject code and/or source code for the Application, including any data\nand utility programs needed for reproducing the Combined Work from the\nApplication, but excluding the System Libraries of the Combined Work.\n\n  1. Exception to Section 3 of the GNU GPL.\n\n  You may convey a covered work under sections 3 and 4 of this License\nwithout being bound by section 3 of the GNU GPL.\n\n  2. Conveying Modified Versions.\n\n  If you modify a copy of the Library, and, in your modifications, a\nfacility refers to a function or data to be supplied by an Application\nthat uses the facility (other than as an argument passed when the\nfacility is invoked), then you may convey a copy of the modified\nversion:\n\n   a) under this License, provided that you make a good faith effort to\n   ensure that, in the event an Application does not supply the\n   function or data, the facility still operates, and performs\n   whatever part of its purpose remains meaningful, or\n\n   b) under the GNU GPL, with none of the additional permissions of\n   this License applicable to that copy.\n\n  3. Object Code Incorporating Material from Library Header Files.\n\n  The object code form of an Application may incorporate material from\na header file that is part of the Library.  You may convey such object\ncode under terms of your choice, provided that, if the incorporated\nmaterial is not limited to numerical parameters, data structure\nlayouts and accessors, or small macros, inline functions and templates\n(ten or fewer lines in length), you do both of the following:\n\n   a) Give prominent notice with each copy of the object code that the\n   Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the object code with a copy of the GNU GPL and this license\n   document.\n\n  4. Combined Works.\n\n  You may convey a Combined Work under terms of your choice that,\ntaken together, effectively do not restrict modification of the\nportions of the Library contained in the Combined Work and reverse\nengineering for debugging such modifications, if you also do each of\nthe following:\n\n   a) Give prominent notice with each copy of the Combined Work that\n   the Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the Combined Work with a copy of the GNU GPL and this license\n   document.\n\n   c) For a Combined Work that displays copyright notices during\n   execution, include the copyright notice for the Library among\n   these notices, as well as a reference directing the user to the\n   copies of the GNU GPL and this license document.\n\n   d) Do one of the following:\n\n       0) Convey the Minimal Corresponding Source under the terms of this\n       License, and the Corresponding Application Code in a form\n       suitable for, and under terms that permit, the user to\n       recombine or relink the Application with a modified version of\n       the Linked Version to produce a modified Combined Work, in the\n       manner specified by section 6 of the GNU GPL for conveying\n       Corresponding Source.\n\n       1) Use a suitable shared library mechanism for linking with the\n       Library.  A suitable mechanism is one that (a) uses at run time\n       a copy of the Library already present on the user's computer\n       system, and (b) will operate properly with a modified version\n       of the Library that is interface-compatible with the Linked\n       Version. \n\n   e) Provide Installation Information, but only if you would otherwise\n   be required to provide such information under section 6 of the\n   GNU GPL, and only to the extent that such information is\n   necessary to install and execute a modified version of the\n   Combined Work produced by recombining or relinking the\n   Application with a modified version of the Linked Version. (If\n   you use option 4d0, the Installation Information must accompany\n   the Minimal Corresponding Source and Corresponding Application\n   Code. If you use option 4d1, you must provide the Installation\n   Information in the manner specified by section 6 of the GNU GPL\n   for conveying Corresponding Source.)\n\n  5. Combined Libraries.\n\n  You may place library facilities that are a work based on the\nLibrary side by side in a single library together with other library\nfacilities that are not Applications and are not covered by this\nLicense, and convey such a combined library under terms of your\nchoice, if you do both of the following:\n\n   a) Accompany the combined library with a copy of the same work based\n   on the Library, uncombined with any other library facilities,\n   conveyed under the terms of this License.\n\n   b) Give prominent notice with the combined library that part of it\n   is a work based on the Library, and explaining where to find the\n   accompanying uncombined form of the same work.\n\n  6. Revised Versions of the GNU Lesser General Public License.\n\n  The Free Software Foundation may publish revised and/or new versions\nof the GNU Lesser General Public License from time to time. Such new\nversions will be similar in spirit to the present version, but may\ndiffer in detail to address new problems or concerns.\n\n  Each version is given a distinguishing version number. If the\nLibrary as you received it specifies that a certain numbered version\nof the GNU Lesser General Public License \"or any later version\"\napplies to it, you have the option of following the terms and\nconditions either of that published version or of any later version\npublished by the Free Software Foundation. If the Library as you\nreceived it does not specify a version number of the GNU Lesser\nGeneral Public License, you may choose any version of the GNU Lesser\nGeneral Public License ever published by the Free Software Foundation.\n\n  If the Library as you received it specifies that a proxy can decide\nwhether future versions of the GNU Lesser General Public License shall\napply, that proxy's public statement of acceptance of any version is\npermanent authorization for you to choose that version for the\nLibrary.\n"
  },
  {
    "path": "src/main/resources/avr/avr_commands.properties",
    "content": "ADC = Add with Carry\nADD = Add without Carry\nADIW = Add Immediate to Word\nAND = Logical AND\nANDI = Logical AND with Immediate\nASR = Arithmetic Shift Right\nBCLR = Bit Clear in SREG\nBLD = Bit Load from the T Flag in SREG to a Bit in Register\nBRBC = Branch if Bit in SREG is Cleared\nBRBS = Branch if Bit in SREG is Set\nBRCC = Branch if Carry Cleared\nBRCS = Branch if Carry Set\nBREAK = Break\nBREQ = Branch if Equal\nBRGE = Branch if Greater or Equal (Signed)\nBRHC = Branch if Half Carry Flag is Cleared\nBRHS = Branch if Half Carry Flag is Set\nBRID = Branch if Global Interrupt is Disabled\nBRIE = Branch if Global Interrupt is Enabled\nBRLO = Branch if Lower (Unsigned)\nBRLT = Branch if Less Than (Signed)\nBRMI = Branch if Minus\nBRNE = Branch if Not Equal\nBRPL = Branch if Plus\nBRSH = Branch if Same or Higher (Unsigned)\nBRTC = Branch if the T Flag is Cleared\nBRTS = Branch if the T Flag is Set\nBRVC = Branch if Overflow Cleared\nBRVS = Branch if Overflow Set\nBSET = Bit Set in SREG\nBST = Bit Store from Bit in Register to T Flag in SREG\nCALL = Long Call to a Subroutine\nCBI = Clear Bit in I/O Register\nCBR = Clear Bits in Register\nCLC = Clear Carry Flag\nCLH = Clear Half Carry Flag\nCLI = Clear Global Interrupt Flag\nCLN = Clear Negative Flag\nCLR = Clear Register\nCLS = Clear Signed Flag\nCLT = Clear T Flag\nCLV = Clear Overflow Flag\nCLZ = Clear Zero Flag\nCOM = One's Complement\nCP = Compare\nCPC = Compare with Carry\nCPI = Compare with Immediate\nCPSE = Compare Skip if Equal\nDEC = Decrement\nDES = Data Encryption Standard\nEICALL = Extended Indirect Call to Subroutine\nEIJMP = Extended Indirect Jump\nELPM = Extended Load Program Memory\nEOR = Exclusive OR\nFMUL = Fractional Multiply Unsigned\nFMULS = Fractional Multiply Signed\nFMULSU = Fractional Multiply Signed with Unsigned\nICALL = Indirect Call to Subroutine\nIJMP = Indirect Jump\nIN = Load an I/O Location to Register\nINC = Increment\nJMP = Jump\nLAC = Load and Clear\nLAS = Load and Set\nLAT = Load and Toggle\nLD = Load Indirect from Data Space to Register using Index X\nLDD = Load Indirect from Data Space to Register using Index X\nLDI = Loads an 8 bit constant directly to register 16 to 31.\nLDS = Load Direct from Data Space (Register File, I/O memory, SRAM)\nLDS = Load Direct from Data Space (Register File, I/O memory, SRAM)\nLPM = Load Program Memory, R0 <- (Z=R31:R30) \nLSL = Logical Shift Left\nLSR = Logical Shift Right\nMOV = Copy Register\nMOVW = Copy Register Word, R(d+1):R(d) <- R(r+1):R(r)\nMUL = Multiply Unsigned\nMULS = Multiply Signed\nMULSU = Multiply Signed with Unsigned\nNEG = Two's Complement (Rd <- 0x00-Rd)\nNOP = No Operation\nOR = Logical OR\nORI = Logical OR with Immediate\nOUT = Store Register to I/O Location\nPOP = Pop Register from Stack\nPUSH = Push Register on Stack\nRCALL = Relative Call to Subroutine\nRET = Return from Subroutine\nRETI = Return from Interrupt\nRJMP = Relative Jump\nROL = Rotate Left trough Carry\nROR = Rotate Right through Carry\nSBC = Subtract with Carry\nSBCI = Subtract Immediate with Carry\nSBI = Set Bit in I/O Register\nSBIC = Skip if Bit in I/O Registeris Cleared\nSBIS = Skip if Bit in I/O Register is Set\nSBIW = Subtract Immediate from Word\nSBR = Set Bits in Register\nSBRC = Skip if Bit in Register is Cleared\nSBRS = Skip if Bit in Register is Set\nSEC = Set Carry Flag\nSEH = Set Half Carry Flag\nSEI = Set Global Interrupt Flag\nSEN = Set Negative Flag\nSER = Set all Bits in Register\nSES = Set Signed Flag\nSET = Set T Flag\nSEV = Set Overflow Flag\nSEZ = Set Zero Flag\nSLEEP = Enter sleep mode\nSPM = Store Program Memory\nST = Store Indirect From Register to Data Space using Index X, Y or Z\nSTD = Store Indirect From Register to Data Space using Index X, Y or Z\nSTS = Store Direct to Data Space (Register File, I/O memory, SRAM)\nSUB = Subtract without Carry\nSUBI = Subtract Immediate\nSWAP = Swap Nibbles\nTST = Test for Zero or Minus\nWDR = Watchdog Reset\nXCH = Exchange\n"
  },
  {
    "path": "src/main/resources/avr/avrdude.csv",
    "content": "t11:ATtiny11:0x1e9004:calibration[1]:lock[1]:flash[1024]:fuse[1]:signature[3]:eeprom[64]\nt12:ATtiny12:0x1e9005:calibration[1]:lock[1]:flash[1024]:fuse[1]:signature[3]:eeprom[64]\nt13:ATtiny13:0x1e9007:calibration[2]:lock[1]:flash[1024]:eeprom[64]:signature[3]:hfuse[1]:lfuse[1]\nt15:ATtiny15:0x1e9006:calibration[1]:lock[1]:flash[1024]:fuse[1]:signature[3]:eeprom[64]\n1200:AT90S1200:0x1e9001:fuse[1]:lock[1]:flash[1024]:eeprom[64]:signature[3]\n4414:AT90S4414:0x1e9201:fuse[1]:lock[1]:flash[4096]:eeprom[256]:signature[3]\n2313:AT90S2313:0x1e9101:fuse[1]:lock[1]:flash[2048]:eeprom[128]:signature[3]\n2333:AT90S2333:0x1e9105:fuse[1]:lock[1]:flash[2048]:eeprom[128]:signature[3]\n2343:AT90S2343:0x1e9103:fuse[1]:lock[1]:flash[2048]:eeprom[128]:signature[3]\n4433:AT90S4433:0x1e9203:fuse[1]:lock[1]:flash[4096]:eeprom[256]:signature[3]\n4434:AT90S4434:0x1e9202:fuse[1]:lock[1]:flash[4096]:eeprom[256]:signature[3]\n8515:AT90S8515:0x1e9301:fuse[1]:lock[1]:flash[8192]:eeprom[512]:signature[3]\n8535:AT90S8535:0x1e9303:fuse[1]:lock[1]:flash[8192]:eeprom[512]:signature[3]\nm103:ATmega103:0x1e9701:fuse[1]:lock[1]:flash[131072]:eeprom[4096]:signature[3]\nm64:ATmega64:0x1e9602:flash[65536]:calibration[4]:lock[1]:efuse[1]:eeprom[2048]:signature[3]:hfuse[1]:lfuse[1]\nm128:ATmega128:0x1e9702:flash[131072]:calibration[4]:lock[1]:efuse[1]:eeprom[4096]:signature[3]:hfuse[1]:lfuse[1]\nc128:AT90CAN128:0x1e9781:flash[131072]:calibration[1]:lock[1]:efuse[1]:eeprom[4096]:signature[3]:hfuse[1]:lfuse[1]\nc64:AT90CAN64:0x1e9681:flash[65536]:calibration[1]:lock[1]:efuse[1]:eeprom[2048]:signature[3]:hfuse[1]:lfuse[1]\nc32:AT90CAN32:0x1e9581:flash[32768]:calibration[1]:lock[1]:efuse[1]:eeprom[1024]:signature[3]:hfuse[1]:lfuse[1]\nm16:ATmega16:0x1e9403:calibration[4]:lock[1]:flash[16384]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\nm164p:ATmega164P:0x1e940a:calibration[4]:lock[1]:flash[16384]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\nm324p:ATmega324P:0x1e9508:flash[32768]:calibration[1]:lock[1]:efuse[1]:eeprom[1024]:signature[3]:hfuse[1]:lfuse[1]\nm324pa:ATmega324PA:0x1e9511:flash[32768]:calibration[1]:lock[1]:efuse[1]:eeprom[1024]:signature[3]:hfuse[1]:lfuse[1]\nm644:ATmega644:0x1e9609:flash[65536]:calibration[1]:lock[1]:efuse[1]:eeprom[2048]:signature[3]:hfuse[1]:lfuse[1]\nm644p:ATmega644P:0x1e960a:flash[65536]:calibration[1]:lock[1]:efuse[1]:eeprom[2048]:signature[3]:hfuse[1]:lfuse[1]\nm1284p:ATmega1284P:0x1e9705:flash[131072]:calibration[1]:lock[1]:efuse[1]:eeprom[4096]:signature[3]:hfuse[1]:lfuse[1]\nm162:ATmega162:0x1e9404:flash[16384]:calibration[1]:lock[1]:efuse[1]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\nm163:ATmega163:0x1e9402:calibration[1]:lock[1]:flash[16384]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\nm169:ATmega169:0x1e9405:flash[16384]:calibration[1]:lock[1]:efuse[1]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\nm329:ATmega329:0x1e9503:flash[32768]:calibration[1]:lock[1]:efuse[1]:eeprom[1024]:signature[3]:hfuse[1]:lfuse[1]\nm329p:ATmega329P:0x1e950b:flash[32768]:calibration[1]:lock[1]:efuse[1]:eeprom[1024]:signature[3]:hfuse[1]:lfuse[1]\nm3290:ATmega3290:0x1e9504:flash[32768]:calibration[1]:lock[1]:efuse[1]:eeprom[1024]:signature[3]:hfuse[1]:lfuse[1]\nm3290p:ATmega3290P:0x1e950c:flash[32768]:calibration[1]:lock[1]:efuse[1]:eeprom[1024]:signature[3]:hfuse[1]:lfuse[1]\nm649:ATmega649:0x1e9603:flash[65536]:calibration[1]:lock[1]:efuse[1]:eeprom[2048]:signature[3]:hfuse[1]:lfuse[1]\nm6490:ATmega6490:0x1e9604:flash[65536]:calibration[1]:lock[1]:efuse[1]:eeprom[2048]:signature[3]:hfuse[1]:lfuse[1]\nm32:ATmega32:0x1e9502:calibration[4]:lock[1]:flash[32768]:eeprom[1024]:signature[3]:hfuse[1]:lfuse[1]\nm161:ATmega161:0x1e9401:fuse[1]:lock[1]:flash[16384]:eeprom[512]:signature[3]\nm8:ATmega8:0x1e9307:calibration[4]:lock[1]:flash[8192]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\nm8515:ATmega8515:0x1e9306:calibration[4]:lock[1]:flash[8192]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\nm8535:ATmega8535:0x1e9308:calibration[4]:lock[1]:flash[8192]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\nt26:ATtiny26:0x1e9109:calibration[4]:lock[1]:flash[2048]:eeprom[128]:signature[3]:hfuse[1]:lfuse[1]\nt261:ATtiny261:0x1e910c:efuse[1]:calibration[1]:lock[1]:flash[2048]:eeprom[128]:signature[3]:hfuse[1]:lfuse[1]\nt461:ATtiny461:0x1e9208:efuse[1]:calibration[1]:lock[1]:flash[4096]:eeprom[256]:signature[3]:hfuse[1]:lfuse[1]\nt861:ATtiny861:0x1e930d:efuse[1]:calibration[1]:lock[1]:flash[8192]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\nm48:ATmega48:0x1e9205:flash[4096]:calibration[1]:lock[1]:efuse[1]:eeprom[256]:signature[3]:hfuse[1]:lfuse[1]\nm48p:ATmega48P:0x1e920a:flash[4096]:calibration[1]:lock[1]:efuse[1]:eeprom[256]:signature[3]:hfuse[1]:lfuse[1]\nm88:ATmega88:0x1e930a:flash[8192]:calibration[1]:lock[1]:efuse[1]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\nm88p:ATmega88P:0x1e930f:flash[8192]:calibration[1]:lock[1]:efuse[1]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\nm168:ATmega168:0x1e9406:flash[16384]:calibration[1]:lock[1]:efuse[1]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\nm168p:ATmega168P:0x1e940b:flash[16384]:calibration[1]:lock[1]:efuse[1]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\nt88:ATtiny88:0x1e9311:flash[8192]:calibration[1]:lock[1]:efuse[1]:eeprom[64]:signature[3]:hfuse[1]:lfuse[1]\nm328:ATmega328:0x1e9514:flash[32768]:calibration[1]:lock[1]:efuse[1]:eeprom[1024]:signature[3]:hfuse[1]:lfuse[1]\nm328p:ATmega328P:0x1e950F:flash[32768]:calibration[1]:lock[1]:efuse[1]:eeprom[1024]:signature[3]:hfuse[1]:lfuse[1]\nt2313:ATtiny2313:0x1e910a:efuse[1]:calibration[2]:lock[1]:flash[2048]:eeprom[128]:signature[3]:hfuse[1]:lfuse[1]\nt4313:ATtiny4313:0x1e920d:efuse[1]:calibration[2]:lock[1]:flash[4096]:eeprom[256]:signature[3]:hfuse[1]:lfuse[1]\npwm2:AT90PWM2:0x1e9381:efuse[1]:calibration[1]:lock[1]:flash[8192]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\npwm2b:AT90PWM2B:0x1e9383:efuse[1]:calibration[1]:lock[1]:flash[8192]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\npwm316:AT90PWM316:0x1e9483:flash[16384]:efuse[1]:calibration[1]:lock[1]:flash[8192]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\nt25:ATtiny25:0x1e9108:efuse[1]:calibration[2]:lock[1]:flash[2048]:eeprom[128]:signature[3]:hfuse[1]:lfuse[1]\nt45:ATtiny45:0x1e9206:efuse[1]:calibration[2]:lock[1]:flash[4096]:eeprom[256]:signature[3]:hfuse[1]:lfuse[1]\nt85:ATtiny85:0x1e930b:efuse[1]:calibration[2]:lock[1]:flash[8192]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\nm640:ATmega640:0x1e9608:flash[65536]:calibration[1]:lock[1]:efuse[1]:eeprom[4096]:signature[3]:hfuse[1]:lfuse[1]\nm1280:ATmega1280:0x1e9703:flash[131072]:calibration[1]:lock[1]:efuse[1]:eeprom[4096]:signature[3]:hfuse[1]:lfuse[1]\nm1281:ATmega1281:0x1e9704:flash[131072]:calibration[1]:lock[1]:efuse[1]:eeprom[4096]:signature[3]:hfuse[1]:lfuse[1]\nm2560:ATmega2560:0x1e9801:flash[262144]:calibration[1]:lock[1]:efuse[1]:eeprom[4096]:signature[3]:hfuse[1]:lfuse[1]\nm2561:ATmega2561:0x1e9802:flash[262144]:calibration[1]:lock[1]:efuse[1]:eeprom[4096]:signature[3]:hfuse[1]:lfuse[1]\nm128rfa1:ATmega128RFA1:0x1ea701:flash[131072]:flash[262144]:calibration[1]:lock[1]:efuse[1]:eeprom[4096]:signature[3]:hfuse[1]:lfuse[1]\nm256rfr2:ATmega256RFR2:0x1ea802:flash[262144]:calibration[1]:lock[1]:efuse[1]:eeprom[4096]:signature[3]:hfuse[1]:lfuse[1]\nm128rfr2:ATmega128RFR2:0x1ea702:flash[131072]:flash[262144]:calibration[1]:lock[1]:efuse[1]:eeprom[4096]:signature[3]:hfuse[1]:lfuse[1]\nm64rfr2:ATmega64RFR2:0x1ea602:flash[65536]:flash[131072]:flash[262144]:calibration[1]:lock[1]:efuse[1]:eeprom[4096]:signature[3]:hfuse[1]:lfuse[1]\nm2564rfr2:ATmega2564RFR2:0x1ea803:flash[262144]:calibration[1]:lock[1]:efuse[1]:eeprom[4096]:signature[3]:hfuse[1]:lfuse[1]\nm1284rfr2:ATmega1284RFR2:0x1ea703:flash[131072]:flash[262144]:calibration[1]:lock[1]:efuse[1]:eeprom[4096]:signature[3]:hfuse[1]:lfuse[1]\nm644rfr2:ATmega644RFR2:0x1ea603:flash[65536]:flash[131072]:flash[262144]:calibration[1]:lock[1]:efuse[1]:eeprom[4096]:signature[3]:hfuse[1]:lfuse[1]\nt24:ATtiny24:0x1e910b:efuse[1]:calibration[1]:lock[1]:flash[2048]:eeprom[128]:signature[3]:hfuse[1]:lfuse[1]\nt44:ATtiny44:0x1e9207:efuse[1]:calibration[1]:lock[1]:flash[4096]:eeprom[256]:signature[3]:hfuse[1]:lfuse[1]\nt84:ATtiny84:0x1e930c:efuse[1]:calibration[1]:lock[1]:flash[8192]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\nt43u:ATtiny43u:0x1e920C:efuse[1]:calibration[2]:lock[1]:flash[4096]:eeprom[64]:signature[3]:hfuse[1]:lfuse[1]\nm32u4:ATmega32U4:0x1e9587:flash[32768]:calibration[1]:lock[1]:efuse[1]:eeprom[1024]:signature[3]:hfuse[1]:lfuse[1]\nusb646:AT90USB646:0x1e9682:flash[65536]:calibration[1]:lock[1]:efuse[1]:eeprom[2048]:signature[3]:hfuse[1]:lfuse[1]\nusb647:AT90USB647:0x1e9682:flash[65536]:calibration[1]:lock[1]:efuse[1]:eeprom[2048]:signature[3]:hfuse[1]:lfuse[1]\nusb1286:AT90USB1286:0x1e9782:flash[131072]:calibration[1]:lock[1]:efuse[1]:eeprom[4096]:signature[3]:hfuse[1]:lfuse[1]\nusb1287:AT90USB1287:0x1e9782:flash[131072]:calibration[1]:lock[1]:efuse[1]:eeprom[4096]:signature[3]:hfuse[1]:lfuse[1]\nusb162:AT90USB162:0x1e9482:flash[16384]:calibration[1]:lock[1]:efuse[1]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\nusb82:AT90USB82:0x1e9382:flash[8192]:calibration[1]:lock[1]:efuse[1]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\nm32u2:ATmega32U2:0x1e958a:flash[32768]:calibration[1]:lock[1]:efuse[1]:eeprom[1024]:signature[3]:hfuse[1]:lfuse[1]\nm16u2:ATmega16U2:0x1e9489:flash[16384]:calibration[1]:lock[1]:efuse[1]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\nm8u2:ATmega8U2:0x1e9389:flash[8192]:calibration[1]:lock[1]:efuse[1]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\nm325:ATmega325:0x1e9505:flash[32768]:calibration[1]:lock[1]:efuse[1]:eeprom[1024]:signature[3]:hfuse[1]:lfuse[1]\nm645:ATmega645:0x1E9605:flash[65536]:calibration[1]:lock[1]:efuse[1]:eeprom[2048]:signature[3]:hfuse[1]:lfuse[1]\nm3250:ATmega3250:0x1E9506:flash[32768]:calibration[1]:lock[1]:efuse[1]:eeprom[1024]:signature[3]:hfuse[1]:lfuse[1]\nm6450:ATmega6450:0x1E9606:flash[65536]:calibration[1]:lock[1]:efuse[1]:eeprom[2048]:signature[3]:hfuse[1]:lfuse[1]\nx16a4u:ATxmega16A4U:0x1e9441:flash[1280]:boot[256]:application[1024]:apptable[256]:usersig[16]:eeprom[64]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx16c4:ATxmega16C4:0x1e9544:flash[1280]:boot[256]:application[1024]:apptable[256]:usersig[16]:eeprom[64]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx16d4:ATxmega16D4:0x1e9442:flash[1280]:boot[256]:application[1024]:apptable[256]:usersig[16]:eeprom[64]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx16a4:ATxmega16A4:0x1e9441:fuse0[1]:flash[1280]:boot[256]:application[1024]:apptable[256]:usersig[16]:eeprom[64]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx32a4u:ATxmega32A4U:0x1e9541:flash[2304]:boot[256]:application[2048]:apptable[256]:usersig[16]:eeprom[64]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx32c4:ATxmega32C4:0x1e9443:flash[2304]:boot[256]:application[2048]:apptable[256]:usersig[16]:eeprom[64]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx32d4:ATxmega32D4:0x1e9542:flash[2304]:boot[256]:application[2048]:apptable[256]:usersig[16]:eeprom[64]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx32a4:ATxmega32A4:0x1e9541:fuse0[1]:flash[2304]:boot[256]:application[2048]:apptable[256]:usersig[16]:eeprom[64]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx64a4u:ATxmega64A4U:0x1e9646:flash[4352]:boot[256]:application[4096]:apptable[256]:usersig[16]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx64c3:ATxmega64C3:0x1e9649:flash[4352]:boot[256]:application[4096]:apptable[256]:usersig[16]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx64d3:ATxmega64D3:0x1e964a:flash[4352]:boot[256]:application[4096]:apptable[256]:usersig[16]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx64d4:ATxmega64D4:0x1e9647:flash[4352]:boot[256]:application[4096]:apptable[256]:usersig[16]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx64a1:ATxmega64A1:0x1e964e:fuse0[1]:flash[4352]:boot[256]:application[4096]:apptable[256]:usersig[16]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx64a1u:ATxmega64A1U:0x1e964e:fuse0[1]:flash[4352]:boot[256]:application[4096]:apptable[256]:usersig[16]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx64a3:ATxmega64A3:0x1e9642:fuse0[1]:flash[4352]:boot[256]:application[4096]:apptable[256]:usersig[16]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx64a3u:ATxmega64A3U:0x1e9642:fuse0[1]:flash[4352]:boot[256]:application[4096]:apptable[256]:usersig[16]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx64a4:ATxmega64A4:0x1e9646:fuse0[1]:flash[4352]:boot[256]:application[4096]:apptable[256]:usersig[16]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx64b1:ATxmega64B1:0x1e9652:fuse0[1]:flash[4352]:boot[256]:application[4096]:apptable[256]:usersig[16]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx64b3:ATxmega64B3:0x1e9651:fuse0[1]:flash[4352]:boot[256]:application[4096]:apptable[256]:usersig[16]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx128c3:ATxmega128C3:0x1e9752:flash[8704]:boot[512]:application[8192]:apptable[512]:usersig[32]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx128d3:ATxmega128D3:0x1e9748:flash[8704]:boot[512]:application[8192]:apptable[512]:usersig[32]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx128d4:ATxmega128D4:0x1e9747:flash[8704]:boot[512]:application[8192]:apptable[512]:usersig[32]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx128a1:ATxmega128A1:0x1e974c:fuse0[1]:flash[8704]:boot[512]:application[8192]:apptable[512]:usersig[32]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx128a1d:ATxmega128A1revD:0x1e9741:fuse0[1]:flash[8704]:boot[512]:application[8192]:apptable[512]:usersig[32]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx128a1u:ATxmega128A1U:0x1e974c:fuse0[1]:flash[8704]:boot[512]:application[8192]:apptable[512]:usersig[32]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx128a3:ATxmega128A3:0x1e9742:fuse0[1]:flash[8704]:boot[512]:application[8192]:apptable[512]:usersig[32]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx128a3u:ATxmega128A3U:0x1e9742:fuse0[1]:flash[8704]:boot[512]:application[8192]:apptable[512]:usersig[32]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx128a4:ATxmega128A4:0x1e9746:flash[8704]:boot[512]:fuse0[1]:application[8192]:apptable[256]:usersig[32]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx128a4u:ATxmega128A4U:0x1e9746:flash[8704]:boot[512]:application[8192]:apptable[256]:usersig[16]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx128b1:ATxmega128B1:0x1e974d:flash[8704]:boot[512]:fuse0[1]:application[8192]:apptable[512]:usersig[16]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx128b3:ATxmega128B3:0x1e974b:flash[8704]:boot[512]:fuse0[1]:application[8192]:apptable[512]:usersig[16]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx192c3:ATxmega192C3:0x1e9751:flash[12800]:boot[512]:application[12288]:apptable[512]:usersig[32]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx192d3:ATxmega192D3:0x1e9749:flash[12800]:boot[512]:application[12288]:apptable[512]:usersig[32]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx192a1:ATxmega192A1:0x1e974e:fuse0[1]:flash[12800]:boot[512]:application[12288]:apptable[512]:usersig[32]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx192a3:ATxmega192A3:0x1e9744:fuse0[1]:flash[12800]:boot[512]:application[12288]:apptable[512]:usersig[32]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx192a3u:ATxmega192A3U:0x1e9744:fuse0[1]:flash[12800]:boot[512]:application[12288]:apptable[512]:usersig[32]:eeprom[128]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx256c3:ATxmega256C3:0x1e9846:flash[16896]:boot[512]:application[16384]:apptable[512]:usersig[32]:eeprom[256]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx256d3:ATxmega256D3:0x1e9844:flash[16896]:boot[512]:application[16384]:apptable[512]:usersig[32]:eeprom[256]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx256a1:ATxmega256A1:0x1e9846:fuse0[1]:flash[16896]:boot[512]:application[16384]:apptable[512]:usersig[32]:eeprom[256]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx256a3:ATxmega256A3:0x1e9842:fuse0[1]:flash[16896]:boot[512]:application[16384]:apptable[512]:usersig[32]:eeprom[256]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx256a3u:ATxmega256A3U:0x1e9842:fuse0[1]:flash[16896]:boot[512]:application[16384]:apptable[512]:usersig[32]:eeprom[256]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx256a3b:ATxmega256A3B:0x1e9843:fuse0[1]:flash[16896]:boot[512]:application[16384]:apptable[512]:usersig[32]:eeprom[256]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx256a3bu:ATxmega256A3BU:0x1e9843:fuse0[1]:flash[16896]:boot[512]:application[16384]:apptable[512]:usersig[32]:eeprom[256]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx384c3:ATxmega384C3:0x1e9845:flash[25088]:boot[512]:application[24576]:apptable[512]:usersig[32]:eeprom[256]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx384d3:ATxmega384D3:0x1e9847:flash[25088]:boot[512]:application[24576]:apptable[512]:usersig[32]:eeprom[256]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx8e5:ATxmega8E5:0x1e9341:flash[640]:boot[128]:application[512]:apptable[128]:usersig[8]:eeprom[32]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx16e5:ATxmega16E5:0x1e9445:flash[1280]:boot[256]:application[1024]:apptable[256]:usersig[8]:eeprom[32]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nx32e5:ATxmega32E5:0x1e954c:flash[2304]:boot[256]:application[2048]:apptable[256]:usersig[8]:eeprom[64]:lock[1]:prodsig[3]:fuse1[1]:fuse2[1]:fuse4[1]:fuse5[1]:signature[3]\nuc3a0512:AT32UC3A0512:0xEDC03F:flash[524288]\nt1634:ATtiny1634:0x1e9412:flash[16384]:calibration[1]:lock[1]:efuse[1]:eeprom[256]:signature[3]:hfuse[1]:lfuse[1]\nt4:ATtiny4:0x1e8f0a:flash[512]:lockbits[1]:fuse[1]:calibration[1]:signature[3]\nt5:ATtiny5:0x1e8f09:flash[512]:lockbits[1]:fuse[1]:calibration[1]:signature[3]\nt9:ATtiny9:0x1e9008:flash[1024]:lockbits[1]:fuse[1]:calibration[1]:signature[3]\nt10:ATtiny10:0x1e9003:flash[1024]:lockbits[1]:fuse[1]:calibration[1]:signature[3]\nt20:ATtiny20:0x1e910F:flash[2048]:lockbits[1]:fuse[1]:calibration[1]:signature[3]\nt40:ATtiny40:0x1e920E:flash[4096]:lockbits[1]:fuse[1]:calibration[1]:signature[3]\nm406:ATMEGA406:0x1e9507:lockbits[1]:flash[40960]:eeprom[512]:signature[3]:hfuse[1]:lfuse[1]\n"
  },
  {
    "path": "src/main/resources/com/mucommander/commons/file/mime.types",
    "content": "application/andrew-inset\tez\napplication/mac-binhex40\thqx\napplication/mac-compactpro\tcpt\napplication/msword\t\tdoc\napplication/octet-stream\tbin dms lha lzh exe class so dll dmg\napplication/oda\t\t\toda\napplication/pdf\t\t\tpdf\napplication/postscript\t\tai eps ps\napplication/smil\t\tsmi smil\napplication/vnd.mif\t\tmif\napplication/vnd.ms-excel\txls\napplication/vnd.ms-powerpoint\tppt\napplication/vnd.wap.wbxml\twbxml\napplication/vnd.wap.wmlc\twmlc\napplication/vnd.wap.wmlscriptc\twmlsc\napplication/x-bcpio\t\tbcpio\napplication/x-cdlink\t\tvcd\napplication/x-chess-pgn\t\tpgn\napplication/x-cpio\t\tcpio\napplication/x-csh\t\tcsh\napplication/x-director\t\tdcr dir dxr\napplication/x-dvi\t\tdvi\napplication/x-futuresplash\tspl\napplication/x-gtar\t\tgtar\napplication/x-hdf\t\thdf\napplication/x-javascript\tjs\napplication/x-java-jnlp-file\tjnlp\napplication/x-koan\t\tskp skd skt skm\napplication/x-latex\t\tlatex\napplication/x-netcdf\t\tnc cdf\napplication/x-sh\t\tsh\napplication/x-shar\t\tshar\napplication/x-shockwave-flash\tswf\napplication/x-stuffit\t\tsit\napplication/x-sv4cpio\t\tsv4cpio\napplication/x-sv4crc\t\tsv4crc\napplication/x-tar\t\ttar\napplication/x-tcl\t\ttcl\napplication/x-tex\t\ttex\napplication/x-texinfo\t\ttexinfo texi\napplication/x-troff\t\tt tr roff\napplication/x-troff-man\t\tman\napplication/x-troff-me\t\tme\napplication/x-troff-ms\t\tms\napplication/x-ustar\t\tustar\napplication/x-wais-source\tsrc\napplication/xhtml+xml\t\txhtml xht\napplication/zip\t\t\tzip\naudio/basic\t\t\tau snd\naudio/midi\t\t\tmid midi kar\naudio/mpeg\t\t\tmpga mp2 mp3\naudio/x-aiff\t\t\taif aiff aifc\naudio/x-mpegurl\t\t\tm3u\naudio/x-pn-realaudio\t\tram rm\naudio/x-pn-realaudio-plugin\trpm\naudio/x-realaudio\t\tra\naudio/x-wav\t\t\twav\nchemical/x-pdb\t\t\tpdb\nchemical/x-xyz\t\t\txyz\nimage/bmp\t\t\tbmp\nimage/gif\t\t\tgif\nimage/ief\t\t\tief\nimage/jpeg\t\t\tjpeg jpg jpe\nimage/pict\t\t\tpict pic pct\nimage/png\t\t\tpng\nimage/tiff\t\t\ttiff tif\nimage/vnd.djvu\t\t\tdjvu djv\nimage/vnd.wap.wbmp\t\twbmp\nimage/x-cmu-raster\t\tras\nimage/x-macpaint\t\tpntg pnt mac\nimage/x-portable-anymap\t\tpnm\nimage/x-portable-bitmap\t\tpbm\nimage/x-portable-graymap\tpgm\nimage/x-portable-pixmap\t\tppm\nimage/x-quicktime\t\tqtif qti\nimage/x-rgb\t\t\trgb\nimage/x-xbitmap\t\t\txbm\nimage/x-xpixmap\t\t\txpm\nimage/x-xwindowdump\t\txwd\nmodel/iges\t\t\tigs iges\nmodel/mesh\t\t\tmsh mesh silo\nmodel/vrml\t\t\twrl vrml\ntext/css\t\t\tcss\ntext/html\t\t\thtml htm\ntext/plain\t\t\tasc txt\ntext/richtext\t\t\trtx\ntext/rtf\t\t\trtf\ntext/sgml\t\t\tsgml sgm\ntext/tab-separated-values\ttsv\ntext/vnd.wap.wml\t\twml\ntext/vnd.wap.wmlscript\t\twmls\ntext/x-setext\t\t\tetx\ntext/xml\t\t\txml xsl\nvideo/mp4\t\t\tmp4\nvideo/mpeg\t\t\tmpeg mpg mpe\nvideo/quicktime\t\t\tqt mov\nvideo/vnd.mpegurl\t\tmxu\nvideo/x-dv\t\t\tdv dif\nvideo/x-msvideo\t\t\tavi\nvideo/x-sgi-movie\t\tmovie\nx-conference/x-cooltalk\t\tice"
  },
  {
    "path": "src/main/resources/dictionary.properties",
    "content": "\r\nlanguage.en-US = English\r\nlanguage.en-GB = British English\r\nlanguage.fr-FR = Français\r\nlanguage.es-ES = Español\r\nlanguage.de-DE = Deutsch\r\nlanguage.cs-CZ = Čeština\r\nlanguage.zh-CN = 简体中文\r\nlanguage.zh-TW = 繁體中文\r\nlanguage.pl-PL = Polski\r\nlanguage.hu-HU = Magyar\r\nlanguage.ru-RU = Русский\r\nlanguage.sl-SL = Slovenščina\r\nlanguage.ro-RO = Română\r\nlanguage.it-IT = Italiano\r\nlanguage.ko-KR = 한국어\r\nlanguage.pt-BR = Português brasileiro\r\nlanguage.nl-NL = Nederlands\r\nlanguage.sk-SK = Slovenčina\r\nlanguage.ja-JP = 日本語\r\nlanguage.sv-SV = Svenska\r\nlanguage.da-DA = Dansk\r\nlanguage.uk-UA = Українська\r\nlanguage.ar-SA = العربية\r\nlanguage.be-BY = Беларуская\r\nlanguage.no-NO = Norsk bokmål\r\nlanguage.tr-TR = Türkçe\r\nlanguage.ca-ES = Català\r\n\r\n\r\n\r\nok = OK\r\nyes = Yes\r\nno = No\r\ncancel = Cancel\r\nedit = Edit\r\nclose = Close\r\nreset = Reset\r\nrename = Rename\r\napply = Apply\r\nchange = Change\r\nsave = Save\r\ndont_save = Don't save\r\nreplace = Replace\r\ndont_replace = Don't replace\r\ndelete = Delete\r\nskip = Skip\r\nskip_all = Skip all\r\noverwrite_all = Overwrite all\r\nretry = Retry\r\nresume = Resume\r\noverwrite = Overwrite\r\noverwrite_if_older = Overwrite if older\r\nduplicate = Duplicate\r\napply_to_all = Apply to all\r\ncopy = Copy\r\nmove = Move\r\npack = Pack\r\nunpack = Unpack\r\ndownload = Download\r\nsplit = Split\r\ncombine = Combine\r\nbrowse = Browse\r\nask = Ask\r\nstop = Stop\r\npause = Pause\r\nquick_search = Quick search\r\nfile_manager = File manager\r\ncreate = Create\r\ncreating_file = Creating %1\r\nchoose = Choose\r\ncustomize = Customize\r\nclean = Clean\r\nsearch = Search\r\nchoose_folder = Choose a folder\r\nlogin = Login\r\npassword = Password\r\nuser = User\r\nencoding = Encoding\r\npreferred_encodings = Preferred encodings\r\nlicense = License\r\nname = Name\r\nsize = Size\r\ndate = Date\r\nextension = Extension\r\npermissions = Permissions\r\nowner = Owner\r\ngroup = Group\r\nlocation = Location\r\nuntitled = Untitled\r\nsource = Source\r\ndestination = Destination\r\nrecurse_directories = Process selected directories recursively\r\ngo_to = Go to\r\nexample = Example\r\npreview = Preview\r\ncomment = Comment\r\nsample_text = Sample text\r\nnb_files = %1 file(s)\r\nnb_folders = %1 folder(s)\r\nloading = Loading...\r\nthis_operation_cannot_be_undone = This operation cannot be undone.\r\nremove = Remove\r\ndetails = Details\r\ntitle = Title\r\nwarning = Warning\r\nerror = Error\r\nfiles = files\r\nparent = Parent\r\ngeneric_error = An error has occurred while performing the requested operation.\r\nfolder_does_not_exist = This folder doesn't exist or is not available.\r\nthis_folder_does_not_exist = This folder doesn't exist or is not available: %1\r\nthis_file_does_not_exist = This file doesn't exist or is not available: %1\r\nfailed_to_read_folder = Failed to read this folder.\r\ninvalid_path = Invalid path: %1\r\ndirectory_already_exists = Directory %1 already exists.\r\nfile_exists_in_destination = File already exists in destination\r\nsource_parent_of_destination = Attempt to transfer a folder to one of its subfolders\r\ncannot_read_file = Cannot read file %1\r\ncannot_write_file = Cannot write file %1\r\noverwrite_readonly_file = File is read-only: %1\r\ncannot_create_folder = Unable to create directory %1\r\ncannot_read_folder = Unable to read contents of folder %1\r\ncannot_delete_file = Unable to delete file %1\r\ncannot_delete_folder = Unable to delete folder %1\r\nerror_while_transferring = Error while transferring file %1\r\nerror_unsupported_operation = Operation is not supported\r\nsame_source_destination = Source and destination folders are the same\r\nfile_already_exists = %1 already exists, do you want to replace it ?\r\nwrite_error = Write error\r\nread_error = Read error\r\nintegrity_check_error = Integrity check failed: source and destination don't match\r\nstartup_error = An error prevented trolCommander from starting.\r\nimage_size = Image size\r\ncannot_write_symlink=Cannot write symlink %1\r\ncannot_write_symlink_already_exists=Symlink already exists: %1\r\ncannot_write_symlink_access_denied=Cannot write symlink %1, access denied\r\npermissions.read = Read\r\npermissions.write = Write\r\npermissions.executable = Executable\r\npermissions.user = User\r\npermissions.group = Group\r\npermissions.other = Other\r\npermissions.octal_notation = Octal notation\r\naction_categories.all = All\r\naction_categories.navigation = Navigation\r\naction_categories.selection = Selection\r\naction_categories.view = View\r\naction_categories.file_operations = File operations\r\naction_categories.windows = Windows\r\naction_categories.tabs = Tabs\r\naction_categories.commands = Custom commands\r\naction_categories.misc = Misc\r\nTerminal.label = Terminal\r\nTerminal.tooltip = Run Terminal window in current directory\r\nTerminalPanel.label = Terminal\r\nTerminalPanel.tooltip = Show/hide Terminal panel\r\nUserMenu.label = User menu\r\nUserMenu.tooltip = Show global or local user menu\r\nUserMenu.press_f4_to_edit_menu = <html>Press <b>F4</b> to open menu editor\r\nUserMenu.command_not_defined = Command doesn't defined\r\nFindFile.label = Find file\r\nFindFile.tooltip = Find file\r\nAddBookmark.label = Add bookmark\r\nAddBookmark.tooltip = Add the current folder to the list of bookmarks\r\nBatchRename.label = Batch rename\r\nEditBookmarks.label = Edit bookmarks\r\nExploreBookmarks.label = Explore bookmarks\r\nEditCredentials.label = Edit credentials\r\nChangeLocation.label = Change current location\r\nChangeDate.label = Change date\r\nChangeDate.tooltip = Change date of selected file(s)\r\nChangePermissions.label = Change permissions\r\nChangePermissions.tooltip = Change permissions of selected file(s)\r\nCheckForUpdates.label = Check for updates\r\nCompareFolders.label = Compare folders\r\nCompareFolderFiles.label = Compare files\r\nCompareFolderFiles.tooltip = Compare files and mark changed in current folder\r\nConnectToServer.label = Connect to server\r\nConnectToServer.tooltip = Connect to a remote server\r\nTextEditorsList.label = Show text editors list\r\nTextEditorsList.tooltip = Show text editors list\r\nView.label = View\r\nViewAs.label = View as\r\nInternalView.label = View (internal)\r\nviewer_type.text = Text file\r\nviewer_type.hex = Binary file\r\nviewer_type.image = Image\r\nviewer_type.pdf = PDF document\r\nviewer_type.djvu = DjVu document\r\nviewer_type.audio = Audio file\r\nviewer_type.html = HTML document\r\nView.tooltip = View selected file\r\nEdit.label = Edit\r\nEditAs.label = Edit as\r\nInternalEdit.label = Edit (internal)\r\nCalculator.label = Calculator\r\nCreateSymlink.label = Create symlink\r\nLocateSymlink.label = Goto symlink target\r\nShowFoldersSize.label = Show folders size\r\nEditCommands.label = Edit commands\r\nEditCommands.group.view = Viewers\r\nEditCommands.group.edit = Editors\r\nEditCommands.group.others = Others\r\nCompareFiles.label = Compare files\r\nCompareFiles.tooltip = Compare text files (FileMerge)\r\nEditCommands.new = New\r\nEditCommands.alias = Alias\r\nEditCommands.command = Command\r\nEditCommands.display_name = Display name\r\nEditCommands.filemask = File mask\r\nsymboliclinkeditor.edit = Edit symlink\r\nsymboliclinkeditor.create = Symbolic link\r\nsymboliclinkeditor.target_file_create = Existing filename (filename symlink will point to)\r\nsymboliclinkeditor.target_file_edit = Symlink '%s' points to\r\nsymboliclinkeditor.link_name = Symbolic link filename\r\nEdit.tooltip = Edit selected file\r\nCopy.label = Copy\r\nCopy.tooltip = Copy marked files\r\nLocalCopy.label = Local Copy\r\nLocalCopy.tooltip = Copy selected file into current folder\r\nMove.label = Move\r\nMove.tooltip = Move marked files\r\nRename.label = Rename\r\nRename.tooltip = Rename selected file\r\nMkdir.label = Make dir\r\nMkdir.tooltip = Create a directory in current folder\r\nMkfile.label = Make file\r\nMkfile.tooltip = Create a file in current folder\r\nDelete.label = Delete\r\nDelete.tooltip = Delete marked files using the system trash when possible\r\nPermanentDelete.label = Delete permanently\r\nPermanentDelete.tooltip = Delete marked files without using the system trash\r\nRefresh.label = Refresh\r\nRefresh.tooltip = Refresh current folder\r\nCloseWindow.label = Close window\r\nCloseWindow.tooltip = Close this window\r\nCopyFileNames.label = Copy name(s)\r\nCopyFileBaseNames.label = Copy basename(s)\r\nCopyFilePaths.label = Copy path(s)\r\nCopyFilesToClipboard.label = Copy file(s)\r\nPasteClipboardFiles.label = Paste file(s)\r\nEmail.label = Send by email\r\nEmail.tooltip = Send marked files as email attachments\r\nGoBack.label = Go back\r\nGoBack.tooltip = Go to previous folder\r\nGoForward.label = Go forward\r\nGoForward.tooltip = Go to next folder\r\nGoToHome.label = Go to home folder\r\nGoToParent.label = Go to parent\r\nGoToParent.tooltip = Go to parent folder\r\nGoToParentInOtherPanel.label = Go to parent in other panel\r\nGoToParentInBothPanels.label = Go to parent in both panels\r\nGoToRoot.label = Go to root\r\nSortByName.label = Sort by Name\r\nSortByDate.label = Sort by Date\r\nSortBySize.label = Sort by Size\r\nSortByExtension.label = Sort by Extension\r\nSortByPermissions.label = Sort by Permissions\r\nSortByOwner.label = Sort by Owner\r\nSortByGroup.label = Sort by Group\r\nMarkGroup.label = Mark files\r\nMarkGroup.tooltip = Mark a group of files\r\nUnmarkGroup.label = Unmark files\r\nUnmarkGroup.tooltip = Unmark a group of files\r\nMarkAll.label = Mark all\r\nUnmarkAll.label = Unmark all\r\nMarkSelectedFile.label = Mark/unmark\r\nMarkSelectedFile.tooltip = Mark/Unmark selected file\r\nMarkNextBlock.label = Mark one block down\r\nMarkPreviousBlock.label = Mark one block up\r\nMarkNextRow.label = Mark one row down\r\nMarkPreviousRow.label = Mark one row up\r\nMarkNextPage.label = Mark one page up\r\nMarkPreviousPage.label = Mark one page down\r\nMarkToFirstRow.label = Mark files to the beginning\r\nMarkToLastRow.label = Mark files to the end\r\nMarkExtension.label = Mark extension\r\nMarkEmpty.label = Mark empty\r\nInvertSelection.label = Invert selection\r\nSwapFolders.label = Swap folders\r\nSwapFolders.tooltip = Swap left and right folders\r\nSetSameFolder.label = Set same folder\r\nSetSameFolder.tooltip = Set same directory to left and right panels\r\nToggleTableViewModeFull.label = Full Mode\r\nToggleTableViewModeFull.tooltip = Toggle Full View Mode\r\nToggleTableViewModeCompact.label = Compact Mode\r\nToggleTableViewModeCompact.tooltip = Toggle Compact View Mode\r\nToggleTableViewModeShort.label = Short Mode\r\nToggleTableViewModeShort.tooltip = Toggle Short View Mode\r\nTogglePanelPreviewMode.label = Quick view\r\nTogglePanelPreviewMode.tooltip = Toggle quick view mode\r\nNewWindow.label = New window\r\nNewWindow.tooltip = Open a new window\r\nOpen.label = Open\r\nOpen.tooltip = Enter folder / Enter archive / Execute\r\nOpenNatively.label = Open natively\r\nOpenNatively.tooltip = Execute selected file with system's file associations\r\nOpenInNewTab.label = Open in new tab\r\nOpenInOtherPanel.label = Open in other panel\r\nOpenInBothPanels.label = Open in both panels\r\nOpenLeftInRightPanel.label = Open left in right panel\r\nOpenRightInLeftPanel.label = Open right in left panel\r\nRevealInDesktop.label = Reveal in %1\r\nRunCommand.label = Run command\r\nRunCommand.tooltip = Run a command in current folder\r\nPack.label = Pack\r\nPack.tooltip = Pack marked files into an archive\r\nUnpack.label = Unpack\r\nUnpack.tooltip = Unpack marked archive files\r\nShowFileProperties.label = Properties\r\nShowFileProperties.tooltip = Show properties of marked files\r\nShowFilePopupMenu.label = Popup menu\r\nShowFilePopupMenu.tooltip = Show context popup menu for selected file\r\nShowPreferences.label = Preferences\r\nShowPreferences.tooltip = Configure trolCommander\r\nShowServerConnections.label = Show open connections\r\nQuit.label = Quit\r\nReverseSortOrder.label = Reverse order\r\nToggleAutoSize.label = Auto-size columns\r\nStop.label = Stop folder change\r\nToggleColumn.show = Show %1 column\r\nToggleColumn.hide = Hide %1 column\r\nToggleCommandBar.show = Show command bar\r\nToggleCommandBar.hide = Hide command bar\r\nToggleHiddenFiles.label = Show hidden files\r\nToggleToolBar.show = Show toolbar\r\nToggleToolBar.hide = Hide toolbar\r\nCustomizeCommandBar.label = Customize command bar\r\nToggleStatusBar.show = Show status bar\r\nToggleStatusBar.hide = Hide status bar\r\nToggleShowFoldersFirst.label = Show folders first\r\nToggleFoldersAlwaysAlphabetical.label = Sort Folders always alphabetical\r\nToggleTree.label = Show tree view\r\nToggleSinglePanel.label = Toggle single panel\r\nPopupLeftDriveButton.label = Change left folder\r\nPopupRightDriveButton.label = Change right folder\r\nRecallPreviousWindow.label = Recall previous window\r\nRecallNextWindow.label = Recall next window\r\nRecallWindow.label = Recall window #%1\r\nRecallWindow1.label = Recall window #%1\r\nRecallWindow2.label = Recall window #%1\r\nRecallWindow3.label = Recall window #%1\r\nRecallWindow4.label = Recall window #%1\r\nRecallWindow5.label = Recall window #%1\r\nRecallWindow6.label = Recall window #%1\r\nRecallWindow7.label = Recall window #%1\r\nRecallWindow8.label = Recall window #%1\r\nRecallWindow9.label = Recall window #%1\r\nRecallWindow10.label = Recall window #%1\r\nBringAllToFront.label = Bring all to front\r\nSwitchActiveTable.label = Switch between left and right panels\r\nSelectNextBlock.label = Jump down one block\r\nSelectPreviousBlock.label = Jump up one block\r\nSelectNextPage.label = Jump down one page\r\nSelectPreviousPage.label = Jump up one page\r\nSelectNextRow.label = Jump down one row\r\nSelectPreviousRow.label = Jump up one row\r\nSelectFirstRow.label = Select first file in current folder\r\nSelectLastRow.label = Select last file in current folder\r\nLeftArrowAction.label = Navigate left\r\nRightArrowAction.label = Navigate right\r\nSplitEqually.label = Split equally\r\nSplitVertically.label = Split vertically\r\nSplitHorizontally.label = Split horizontally\r\nShowKeyboardShortcuts.label = Keyboard shortcuts\r\nGoToWebsite.label = Go to website\r\nGoToForums.label = Go to forums\r\nReportBug.label = Report a bug\r\nDonate.label = Make a donation\r\nShowAbout.label = About trolCommander\r\nOpenTrash.label = Open Trash\r\nEmptyTrash.label = Empty Trash\r\nCalculateChecksum.label = Calculate checksum\r\nMaximizeWindow.label = Maximize\r\nMaximizeWindow.label.mac_os_x = Zoom\r\nMinimizeWindow.label = Minimize\r\nGoToDocumentation.label = Online documentation\r\nShowParentFoldersQL.label = Parent folders\r\nShowRecentLocationsQL.label = Recent locations\r\nShowRecentExecutedFilesQL.label = Recently executed files\r\nShowRootFoldersQL.label = Root folders\r\nShowTabsQL.label = Open tabs\r\nShowRecentViewedFilesQL.label = Recently viewed files\r\nShowRecentEditedFilesQL.label = Recently edited files\r\nSplitFile.label = Split\r\nSplitFile.tooltip = Split a file into multiple parts\r\nCombineFiles.label = Combine\r\nCombineFiles.tooltip = Combine split file parts to recreate the original file\r\nShowDebugConsole.label = Debug console\r\nFocusPrevious.label = Focus previous component\r\nFocusNext.label = Focus next component\r\nShowBookmarksQL.label = Bookmarks\r\nShowEditorBookmarksQL.label = File bookmarks\r\nEjectDrive.label = Eject drive\r\nEjectDrive.tooltip = Safely remove drive\r\nfile_menu = File\r\nfile_menu.open_with = Open with\r\nfile_menu.open_as = Open as\r\nmark_menu = Mark\r\nview_menu = View\r\nview_menu.show_hide_columns = Show/Hide columns\r\nview_menu.table_mode = Mode\r\ngo_menu = Go\r\ntools_menu = Tools\r\neject_menu = Eject drive\r\nbookmarks_menu = Bookmarks\r\nbookmarks_menu.no_bookmark = No bookmark\r\ndrive_popup.network_shares = Network shares\r\nquick_lists_menu = Quick lists\r\nwindow_menu = Window\r\nhelp_menu = Help\r\nstatus_bar.selected_files = %1 of %2 selected\r\nstatus_bar.connecting_to_folder = Connecting to folder, press ESCAPE to cancel.\r\nstatus_bar.volume_free = Free: %1\r\nstatus_bar.volume_capacity = Capacity: %1\r\nstatus_bar.quick_search.press_esc_to_stop_search = press Esc to stop search\r\nshortcuts_panel.title = Shortcuts\r\nshortcuts_panel.restore_defaults = Restore defaults\r\nshortcuts_panel.show = Show\r\nshortcuts_panel.search = Search\r\nshortcuts_panel.default_message = Press Enter or double-click the shortcut you wish to edit\r\nshortcuts_table.action_description = Action description\r\nshortcuts_table.shortcut = Shortcut\r\nshortcuts_table.alternate_shortcut = Alternate Shortcut\r\nshortcuts_table.type_in_a_shortcut = Type in a shortcut\r\ncommand_bar_dialog.help = Drag buttons to customize the command bar\r\ntable.folder_access_error_title = Folder access error\r\ntable.folder_access_error = Unable to read folder content\r\ntable.download_or_browse = Would you like to browse or download this file ?\r\nAddTab.label = Add tab\r\nAddTab.tooltip = Add new tab in the active panel\r\nToggleLockTab.lock = Lock\r\nToggleLockTab.unlock = Unlock\r\nToggleLockTab.label = Lock/Unlock\r\nToggleLockTab.tooltip = Change tab's locking state\r\nDuplicateTab.label = Duplicate\r\nDuplicateTab.tooltip = Duplicate active tab in the same panel\r\nCloseDuplicateTabs.label = Close duplicates\r\nCloseDuplicateTabs.tooltip = Close duplicate tabs\r\nCloseOtherTabs.label = Close others\r\nCloseOtherTabs.tooltip = Close other tabs\r\nCloseTab.label = Close\r\nCloseTab.tooltip = Close tab\r\nMoveTabToOtherPanel.label = Move to other panel\r\nMoveTabToOtherPanel.tooltip = Move tab to other panel\r\nCloneTabToOtherPanel.label = Clone to other panel\r\nCloneTabToOtherPanel.tooltip = Add similar tab in the other panel\r\nNextTab.label = Next tab\r\nNextTab.tooltip = Switch to the tab on the right\r\nPreviousTab.label = Previous tab\r\nPreviousTab.tooltip = Switch to the tab on the left\r\nSetTabTitle.label = Set title\r\nSetTabTitle.tooltip = Set fixed title for the tab\r\nversion_dialog.no_new_version_title = No new version\r\nversion_dialog.no_new_version = Congratulations, you already have the latest version.\r\nversion_dialog.new_version_title = New version available\r\nversion_dialog.new_version = A new version of trolCommander is available.\r\nversion_dialog.new_version_url = A new version of trolCommander is available at %1.\r\nversion_dialog.not_available_title = Server not reachable\r\nversion_dialog.not_available = Unable to get version information from server.\r\nversion_dialog.install_and_restart = Install and restart\r\nversion_dialog.preparing_for_update = Preparing for update...\r\nquit_dialog.title = Quit trolCommander\r\nquit_dialog.desc = You have %1 window(s) open. Are you sure you want to quit ?\r\nquit_dialog.show_next_time = Show next time\r\ndestination_dialog.file_exists_action = Default action when file exists\r\ndestination_dialog.verify_integrity = Verify data integrity\r\ndestination_dialog.skip_errors = Skip errors\r\ndestination_dialog.background_mode = In background\r\nfile_collision_dialog.title = File collision\r\nrename_dialog.new_name = New name\r\ncopy_dialog.destination = Copy selected file(s) to\r\ncopy_dialog.error_title = Copy error\r\ncopy_dialog.copying = Copying files\r\ncopy_dialog.copying_file = Copying %1\r\npack_dialog.packing = Packing files\r\npack_dialog.packing_file = Compressing %1\r\npack_dialog.error_title = Pack error\r\npack_dialog_description = Add selected files to\r\npack_dialog.archive_format = Archive format\r\nunpack_dialog.destination = Unpack selected file(s) to\r\nunpack_dialog.error_title = Unpack error\r\nunpack_dialog.unpacking = Unpacking files\r\nunpack_dialog.unpacking_file = Unpacking %1\r\noptimizing_archive = Optimizing archive %1\r\nerror_while_optimizing_archive = Error while optimizing archive %1\r\nmove_dialog.move_description = Move to\r\nmove_dialog.error_title = Move error\r\nmove_dialog.moving = Moving files\r\nmove_dialog.moving_file = Moving %1\r\ndownload_dialog.description = Download file to\r\ndownload_dialog.download = Download\r\ndownload_dialog.error_title = Download error\r\ndownload_dialog.downloading = Downloading\r\ndownload_dialog.downloading_file = Downloading %1\r\nmkfile_dialog.allocate_space = Allocate space\r\nmkfile_dialog.open_in_editor = Open in text editor\r\nmkfile_dialog.make_executable = Executable file\r\nmkfile_dialog.convert_whitespace = Convert whitespace\r\ndelete_dialog.permanently_delete.confirmation = Permanently delete selected files ?\r\ndelete_dialog.permanently_delete.confirmation_1 = Permanently delete selected file ?\r\ndelete_dialog.permanently_delete.symlink_confirmation_1 = Permanently delete selected symlink ?\r\ndelete_dialog.move_to_trash.confirmation = Delete selected files ?\r\ndelete_dialog.move_to_trash.confirmation_1 = Delete selected file ?\r\ndelete_dialog.move_to_trash.confirmation_details = Files will be moved to the trash.\r\ndelete_dialog.move_to_trash.confirmation_details_1 = File will be moved to the trash.\r\ndelete_dialog.move_to_trash.option = Move to trash\r\ndelete_dialog.move_to_trash.failed = One or more files could not be moved to the trash.\r\ndelete_dialog.deleting = Deleting\r\ndelete_dialog.error_title = Delete error\r\ndelete.deleting_file = Deleting %1\r\nemail_dialog.prefs_not_set_title = Mail not configured\r\nemail_dialog.prefs_not_set = You need to set your mail parameters first.\r\nemail_dialog.from = From\r\nemail_dialog.to = To\r\nemail_dialog.subject = Subject\r\nemail_dialog.send = Send\r\nemail_dialog.error_title = Email files error\r\nemail_dialog.read_error = Unable to read files in subfolders.\r\nemail_dialog.sending = Sending files\r\nemail.sending_file = Sending %1\r\nemail.connecting_to_server = Connecting to %1\r\nemail.server_unavailable = Unable to contact mail server %1, check your mail preferences or try again later.\r\nemail.connection_closed = Connection closed by server, mail not sent.\r\nemail.goodbye_failed = Error while closing connection, mail may not have been sent.\r\nemail.send_file_error = Unable to send file %1, mail not sent.\r\nsplit_file_dialog.error_title = Split file error\r\nsplit_file_dialog.file_to_split = File to split\r\nsplit_file_dialog.target_directory = Target directory\r\nsplit_file_dialog.part_size = Size of each part\r\nsplit_file_dialog.parts = Number of parts\r\nsplit_file_dialog.generate_CRC = Generate CRC file\r\nsplit_file_dialog.max_parts = Maximum allowed number of parts is %1\r\nsplit_file_dialog.auto = Auto\r\nsplit_file_dialog.insert_new_media = Insert new media\r\ncombine_files_dialog.error_title = Combine file error\r\ncombine_files_job.no_crc_file = Combine succeeded. No CRC file.\r\ncombine_files_job.crc_read_error = Error while reading CRC file.\r\ncombine_files_job.crc_check_failed = CRC mismatch: expected %2, found %1\r\ncombine_files_job.crc_ok = Combine succeeded. CRC checksum ok.\r\nfile_selection_dialog.mark = Mark\r\nfile_selection_dialog.unmark = Unmark\r\nfile_selection_dialog.mark_description = Mark files\r\nfile_selection_dialog.unmark_description = Unmark files\r\nfile_selection_dialog.case_sensitive = Case sensitive\r\nfile_selection_dialog.include_folders = Include folders\r\nprogress_dialog.starting = Transfer starting...\r\nprogress_dialog.transferred = Transferred %1 at %2\r\nprogress_dialog.elapsed_time = Elapsed time\r\nprogress_dialog.advanced = Advanced\r\nprogress_dialog.current_speed = Current speed\r\nprogress_dialog.limit_speed = Limit speed\r\nprogress_dialog.close_when_finished = Close window when finished\r\nprogress_dialog.processing_files = Processing files\r\nprogress_dialog.processing_file = Processing %1\r\nprogress_dialog.verifying_file = Verifying %1\r\nprogress_dialog.job_finished = Job finished\r\nprogress_dialog.job_error = Job error\r\nprogress_dialog.hide = Hide\r\nproperties_dialog.file_properties = %1 Properties\r\nproperties_dialog.contents = Contents\r\nproperties_dialog.calculating = Calculating...\r\ncalculate_checksum_dialog.checksum_algorithm = Checksum algorithm\r\ncalculate_checksum_dialog.temporary_file = Temporary file\r\nchange_date_dialog.now = Now\r\nchange_date_dialog.specific_date = Specific date\r\nrun_dialog.run_command_description = Run in current folder\r\nrun_dialog.run_in_home_description = Run in home folder\r\nrun_dialog.command_output = Command output\r\nrun_dialog.run = Run\r\nrun_dialog.stop = Stop\r\nrun_dialog.clear_history = Clear history\r\nserver_connect_dialog.server_type = Connection type\r\nserver_connect_dialog.server = Server\r\nserver_connect_dialog.share = Share\r\nserver_connect_dialog.domain = Domain\r\nserver_connect_dialog.username = Username\r\nserver_connect_dialog.initial_dir = Initial directory\r\nserver_connect_dialog.port = Port\r\nserver_connect_dialog.server_url = Server URL\r\nserver_connect_dialog.http_url = Web site URL\r\nserver_connect_dialog.connect = Connect\r\nserver_connect_dialog.protocol = Protocol\r\nserver_connect_dialog.nfs_version = NFS version\r\nserver_connect_dialog.private_key = Private key\r\nserver_connect_dialog.passphrase = Passphrase\r\nftp_connect.passive_mode = Enable passive mode\r\nftp_connect.anonymous_user = Anonymous user\r\nftp_connect.nb_connection_retries = Number of connection retries\r\nftp_connect.retry_delay = Delay between retries (in seconds)\r\nhttp_connect.basic_authentication = HTTP Basic Authentication (optional)\r\nserver_connections_dialog.disconnect = Disconnect\r\nserver_connections_dialog.connection_busy = Busy\r\nserver_connections_dialog.connection_idle = Idle\r\nvsphere_connections_dialog.guest_server = Guest Server %1\r\nvsphere_connections_dialog.guest_user = Guest Username\r\nvsphere_connections_dialog.guest_password = Guest Password\r\nbonjour.bonjour_services = Bonjour services\r\nbonjour.no_service_discovered = No service discovered\r\nbonjour.bonjour_disabled = Bonjour disabled\r\nauth_dialog.title = Authentication\r\nauth_dialog.desc = Please enter a login and password\r\nauth_dialog.server = Server\r\nauth_dialog.store_credentials = Store login and password (weak encryption)\r\nauth_dialog.connect_as = Connect as\r\nauth_dialog.authentication_failed = Authentication failed\r\nsortable_list.move_up = Move up\r\nsortable_list.move_down = Move down\r\nadd_bookmark_dialog.add = Add\r\nedit_bookmarks_dialog.location = Location\r\nedit_bookmarks_dialog.new = New\r\nedit_bookmarks_dialog.is_separator = The specified name defines a separator\r\nfile_viewer.view_error_title = View error\r\nfile_viewer.view_error = Unable to view file.\r\nfile_viewer.file_menu = File\r\nfile_viewer.close = Close\r\nfile_viewer.large_file_warning = This file may be too large for this operation.\r\nfile_viewer.open_anyway = Open anyway\r\nfile_viewer.open_hex = Hex view\r\ntext_viewer.edit = Edit\r\ntext_viewer.copy = Copy\r\ntext_viewer.select_all = Select All\r\ntext_viewer.find = Find\r\ntext_viewer.find_button = Find\r\ntext_viewer.find_next = Find next\r\ntext_viewer.find_previous = Find previous\r\ntext_viewer.find.case_sensitive = Case sensitive\r\ntext_viewer.find.whole_word = Whole word\r\ntext_viewer.find.regexp = Regexp\r\ntext_viewer.find.mark_all = Mark all\r\ntext_viewer.find.direction = Direction\r\ntext_viewer.find.up = Up\r\ntext_viewer.find.down = Down\r\ntext_viewer.view = View\r\ntext_viewer.line_wrap = Line wrap\r\ntext_viewer.line_numbers = Line numbers\r\ntext_viewer.binary_file_warning = This appears to be a binary file\r\ntext_viewer.goto_line = Goto line\r\ntext_viewer.line = Line\r\ntext_viewer.open_file_error = Can't open file\r\nimage_viewer.controls_menu = Controls\r\nimage_viewer.zoom_in = Zoom in\r\nimage_viewer.zoom_out = Zoom out\r\nfile_editor.edit_error_title = Edit error\r\nfile_editor.edit_error = Unable to edit file.\r\nfile_editor.save = Save\r\nfile_editor.save_as = Save as...\r\nfile_editor.save_warning = Save changes made to this file before closing ?\r\nfile_editor.cannot_write = Unable to write file.\r\nfile_editor.file_menu = File\r\nfile_editor.close = Close\r\nfile_editor.open_anyway = Open anyway\r\nfile_editor.save_anyway = Save anyway\r\nfile_editor.overwrite_readonly = The file is read-only\r\nfile_editor.files = Files\r\nfile_editor.files.list = Select file\r\nfile_editor.show_file_manager = Show file manager\r\nfile_editor.add_to_bookmark = Bookmark file\r\nfile_editor.remove_from_bookmark = Remove bookmark\r\nfile_editor.goto_header_source = Go to Header/Source\r\ntext_editor.cut = Cut\r\ntext_editor.paste = Paste\r\ntext_editor.undo = Undo\r\ntext_editor.redo = Redo\r\ntext_editor.edit = Edit\r\ntext_editor.copy = Copy\r\ntext_editor.select_all = Select all\r\ntext_editor.view = View\r\ntext_editor.find = Find\r\ntext_editor.find_next = Find next\r\ntext_editor.find_previous = Find previous\r\ntext_editor.search = Search\r\ntext_editor.replace = Replace\r\ntext_editor.replace_menu = Replace...\r\ntext_editor.replace_button = Replace\r\ntext_editor.replace_with = Replace with\r\ntext_editor.replace_all = Replace all\r\ntext_editor.replaced = Replaced\r\ntext_editor.occurrences = occurrences\r\ntext_editor.line_wrap = Line wrap\r\ntext_editor.line_numbers = Line numbers\r\ntext_editor.syntax = Syntax\r\ntext_editor.format = Format\r\ntext_editor.writing = Writing...\r\ntext_editor.modified = Modified\r\ntext_editor.saved = File saved\r\ntext_editor.text_not_found = Text not found\r\ntext_editor.found = Found\r\ntext_editor.matches = matches\r\ntext_editor.cant_save_file = Can't save file\r\ntext_editor.press_alt_enter_to_open_file = Press Alt+Enter to open file\r\ntext_editor.tools=Tools\r\ntext_editor.build=Build\r\ntext_editor.invisible_chars = Invisible chars\r\nshortcuts_dialog.quick_search = Quick search\r\nshortcuts_dialog.quick_search.start_search = Type in any character to start a quick search\r\nshortcuts_dialog.quick_search.cancel_search = Cancel quick search\r\nshortcuts_dialog.quick_search.remove_last_char = Remove last character from quick search string\r\nshortcuts_dialog.quick_search.jump_to_previous = Jump to previous quick search result\r\nshortcuts_dialog.quick_search.jump_to_next = Jump to next quick search result\r\nshortcuts_dialog.quick_search.mark_jump_next = Mark/Unmark current file and jump to next search result\r\ntheme_editor.title = Theme editor\r\ntheme_editor.folder_tab = Folder pane\r\ntheme_editor.shell_tab = Shell\r\ntheme_editor.shell_history_tab = Shell history\r\ntheme_editor.terminal_tab = Terminal\r\ntheme_editor.statusbar_tab = Status bar\r\ntheme_editor.free_space = Free space\r\ntheme_editor.free_space.ok = OK\r\ntheme_editor.free_space.warning = Warning\r\ntheme_editor.free_space.critical = Critical\r\ntheme_editor.locationbar_tab = Location bar\r\ntheme_editor.editor_tab = File editor\r\ntheme_editor.font = Font\r\ntheme_editor.active_panel = Active\r\ntheme_editor.inactive_panel = Inactive\r\ntheme_editor.general = General\r\ntheme_editor.theme_warning_predefined = Build-in themes cannot be modified. Do you whish to create a new theme?\r\ntheme_editor.could_not_save_theme = Unable to write theme %1\r\ntheme_editor.border = Border\r\ntheme_editor.background = Background\r\ntheme_editor.alternate_background = Alternate background\r\ntheme_editor.unfocused_background = Background (without focus)\r\ntheme_editor.copy_colors = Copy %1\r\ntheme_editor.quick_search = Quick search\r\ntheme_editor.quick_search.unmatched_file = Unmatched file\r\ntheme_editor.text = Text\r\ntheme_editor.progress = Progress\r\ntheme_editor.normal = Normal\r\ntheme_editor.normal_unfocused = Normal (without focus)\r\ntheme_editor.selected = Selected\r\ntheme_editor.selected_unfocused = Selected (without focus)\r\ntheme_editor.color = Color\r\ntheme_editor.colors = Colors\r\ntheme_editor.plain_file = Plain file\r\ntheme_editor.marked_file = Marked file\r\ntheme_editor.hidden_folder = Hidden folder\r\ntheme_editor.hidden_file = Hidden file\r\ntheme_editor.folder = Folder\r\ntheme_editor.archive_file = Archive\r\ntheme_editor.symbolic_link = Symbolic link\r\ntheme_editor.executable_file = Executable file\r\ntheme_editor.header = Header\r\ntheme_editor.current = Current line\r\ntheme_editor.file_groups = File groups\r\ntheme_editor.group_ = Group\r\ntheme_editor.normal_color = Normal color\r\ntheme_editor.selected_color = Selected color\r\ntheme_editor.filemask = File masks\r\ntheme_editor.group_file_ = File of group \r\ntheme_editor.item = Item\r\ntheme_editor.text_editor_tab = Text viewer and editor\r\ntheme_editor.hex_viewer_tab = Hex viewer\r\ntheme_editor.normal_hex = Hex\r\ntheme_editor.normal_offset = Offset\r\ntheme_editor.normal_ascii = ASCII\r\ntheme_editor.alternate = Alternate\r\ntheme_editor.selected_hex = Selected hex\r\ntheme_editor.selected_ascii = Selected ASCII\r\ncommand_bar_customize_dialog.available_actions = Available Actions\r\ncommand_bar_customize_dialog.modifier = Modifier\r\nprefs_dialog.title = Preferences\r\nprefs_dialog.general_tab = General\r\nprefs_dialog.day = Day\r\nprefs_dialog.month = Month\r\nprefs_dialog.year = Year\r\nprefs_dialog.language = Language (requires restart)\r\nprefs_dialog.date_time = Date & time format\r\nprefs_dialog.time = Time\r\nprefs_dialog.date = Date\r\nprefs_dialog.date_separator = Separator\r\nprefs_dialog.time_12_hour = 12-hour format\r\nprefs_dialog.time_24_hour = 24-hour format\r\nprefs_dialog.show_seconds = Show seconds\r\nprefs_dialog.show_century = Show century\r\nprefs_dialog.check_for_updates_on_startup = Check for updates on startup\r\nprefs_dialog.show_splash_screen = Show splash screen\r\nprefs_dialog.folders_tab = Folders\r\nprefs_dialog.startup_folders = Startup folders\r\nprefs_dialog.left_folder = Left folder\r\nprefs_dialog.right_folder = Right folder\r\nprefs_dialog.last_folder = Last visited folder\r\nprefs_dialog.custom_folder = Custom folder\r\nprefs_dialog.quick_search = Quick search\r\nprefs_dialog.quick_search_timeout = Stop quick search mode after inactivity\r\nprefs_dialog.quick_search_timeout_never = Never\r\nprefs_dialog.quick_search_timeout_sec = Sec\r\nprefs_dialog.show_quick_search_matches_first = Show matches first\r\nprefs_dialog.show_hidden_files = Show hidden files\r\nprefs_dialog.show_ds_store_files = Show .DS_Store files\r\nprefs_dialog.show_system_folders = Show system folders\r\nprefs_dialog.compact_file_size = Round displayed file sizes\r\nprefs_dialog.follow_symlinks_when_cd = Follow symlinks when changing current directory\r\nprefs_dialog.show_tab_header = Always show tab header\r\nprefs_dialog.appearance_tab = Appearance\r\nprefs_dialog.look_and_feel = Look & Feel\r\nprefs_dialog.icons_size = Icon size\r\nprefs_dialog.toolbar_icons = Toolbar\r\nprefs_dialog.command_bar_icons = Command bar\r\nprefs_dialog.file_icons = File types\r\nprefs_dialog.use_system_file_icons = Use system file icons\r\nprefs_dialog.use_system_file_icons.always = Always\r\nprefs_dialog.use_system_file_icons.never = Never\r\nprefs_dialog.use_system_file_icons.applications = For applications only\r\nprefs_dialog.edit_current_theme = Edit current theme...\r\nprefs_dialog.themes = Themes\r\nprefs_dialog.syntax_themes = Editor syntax coloring theme\r\nprefs_dialog.import_theme = Import theme\r\nprefs_dialog.import_look_and_feel = Import look & feel\r\nprefs_dialog.no_look_and_feel = No look & feel was found.\r\nprefs_dialog.error_in_import = Error while importing theme %1.\r\nprefs_dialog.cannot_read_theme = Cannot load theme from file %1\r\nprefs_dialog.export_theme = Export %1\r\nprefs_dialog.import = Import\r\nprefs_dialog.export = Export\r\nprefs_dialog.theme_type = Type: %1\r\nprefs_dialog.delete_theme = Permanently delete theme %1 ?\r\nprefs_dialog.delete_look_and_feel = Permanently delete look & feel %1 ?\r\nprefs_dialog.rename_failed = Failed to rename theme %1\r\nprefs_dialog.xml_file = XML file\r\nprefs_dialog.jar_file = JAR file\r\nprefs_dialog.mail_tab = Mail\r\nprefs_dialog.mail_settings = Outgoing mail settings\r\nprefs_dialog.mail_name = Your name\r\nprefs_dialog.mail_address = Your email address\r\nprefs_dialog.mail_server = SMTP server\r\nprefs_dialog.misc_tab = Misc\r\nprefs_dialog.use_brushed_metal = Use 'brushed metal' look (requires restart)\r\nprefs_dialog.confirm_on_quit = Show confirmation dialog on quit\r\nprefs_dialog.shell = Run command\r\nprefs_dialog.default_shell = Use default system shell\r\nprefs_dialog.custom_shell = Use custom shell\r\nprefs_dialog.shell_encoding = Shell encoding\r\nprefs_dialog.auto_detect_shell_encoding = Auto-detect\r\nprefs_dialog.external_terminal = External terminal\r\nprefs_dialog.default_terminal = Use default Terminal program\r\nprefs_dialog.custom_terminal = Use custom command\r\nprefs_dialog.iterm_terminal = Use iTerm\r\nprefs_dialog.builtin_terminal = Builtin terminal\r\nprefs_dialog.enable_bonjour_discovery = Enable Bonjour services discovery\r\nprefs_dialog.enable_system_notifications = Enable system notifications\r\nprefs_dialog.calculate_folder_size_on_mark = Calculate folder size on mark\r\ndebug_console_dialog.level = Level\r\ndebug_console_dialog.threads = Threads\r\ndebug_console_dialog.active_threads = Active threads\r\nunit.byte = byte\r\nunit.bytes = bytes\r\nunit.bytes_short = b\r\nunit.kb = KB\r\nunit.mb = MB\r\nunit.gb = GB\r\nunit.tb = TB\r\nunit.speed = %1/s\r\nduration.seconds = %1s\r\nduration.minutes = %1m\r\nduration.hours = %1h\r\nduration.days = %1d\r\nduration.months = %1mo\r\nduration.years = %1y\r\nduration.infinite = ∞\r\ntheme.custom_theme = Custom theme\r\ntheme.custom = Customized\r\ntheme.built_in = Built-in\r\ntheme.add_on = Add-on\r\ntheme.current = current\r\ntheme_could_not_be_loaded = An error occurred while loading this theme.\r\ncannot_open_cyclic_symlink = Cannot open the selected link because it is cyclic\r\nsetup.title = Welcome to trolCommander\r\nsetup.intro = Please choose the way you want trolCommander to behave.\r\nsetup.look_and_feel = Select your Look & feel\r\nsetup.theme = Select your theme\r\nfont_chooser.font_size = Size\r\nfont_chooser.font_bold = Bold\r\nfont_chooser.font_italic = Italic\r\ncolor_chooser.red = Red\r\ncolor_chooser.green = Green\r\ncolor_chooser.blue = Blue\r\ncolor_chooser.hue = Hue\r\ncolor_chooser.brightness = Brightness\r\ncolor_chooser.swatches = Swatches\r\ncolor_chooser.saturation = Saturation\r\ncolor_chooser.rgb = RGB\r\ncolor_chooser.hsb = HSB\r\ncolor_chooser.recent = Recent\r\ncolor_chooser.alpha = Alpha transparency\r\ncolor_chooser.title = Pick a color\r\nbatch_rename_dialog.mask = Rename pattern\r\nbatch_rename_dialog.search_replace = Search & Replace\r\nbatch_rename_dialog.search_for = Search for\r\nbatch_rename_dialog.replace_with = Replace with\r\nbatch_rename_dialog.counter = Counter\r\nbatch_rename_dialog.start_at = Start at\r\nbatch_rename_dialog.step_by = Step by\r\nbatch_rename_dialog.format = Format\r\nbatch_rename_dialog.upper_lower_case = Upper/Lowercase\r\nbatch_rename_dialog.no_change = Unchanged\r\nbatch_rename_dialog.lower_case = lowercase\r\nbatch_rename_dialog.upper_case = UPPERCASE\r\nbatch_rename_dialog.first_upper = First letter uppercase\r\nbatch_rename_dialog.word = First Of Each Word\r\nbatch_rename_dialog.regexp = RegExp\r\nbatch_rename_dialog.regexp_error = Syntax error in regexp\r\nbatch_rename_dialog.old_name = Old name\r\nbatch_rename_dialog.new_name = New name\r\nbatch_rename_dialog.block_name = Preserve\r\nbatch_rename_dialog.range = Range\r\nbatch_rename_dialog.proceed_renaming = %1 files out of %2 will be renamed. Do you wish to proceed?\r\nbatch_rename_dialog.duplicate_names = Duplicate names!\r\nbatch_rename_dialog.names_conflict = Names conflict! Same values in old and new names.\r\nparent_folders_quick_list.empty_message = Current location has no parent\r\nrecent_locations_quick_list.empty_message = No recent location\r\nrecent_executed_files_quick_list.empty_message = No recently executed file\r\nrecent_edited_files_quick_list.empty_message = No recently edited file\r\nrecent_viewed_files_quick_list.empty_message = No recently viewed file\r\nroots_quick_list.empty_message = No root folders available\r\ntabs_quick_list.empty_message = Only one tab is presented\r\neditor_bookmarks_quick_list.empty_message = No files\r\neditor_bookmarks_quick_list.file_not_found = File not found\r\neditor_bookmarks_quick_list.press_f4_to_edit_list = <html>Press <b>F4</b> to edit bookmarks list\r\n#server_connect_dialog.auth_error = Invalid login or password.\r\n#move_dialog.cannot_move_to_itself = Cannot move files to a subfolder.\r\n#pack.error_on_file = Error while compressing %1\r\n#pack_dialog.cannot_write = Unable to create file in destination folder.\r\n#unpack.unable_to_open_zip = Unable to open zip file %1.\r\n#image_viewer.previous_image = Previous image\r\n#image_viewer.next_image = Next image\r\n#mkdir_dialog.title = Make directory\r\n#mkdir_dialog.error_title = Creation error\r\n#edit_bookmarks_dialog.remove = Remove\r\n#mkdir_dialog.description = Create directory\r\n#mkfile_dialog.description = Create new empty file\r\n#done = Done\r\n#move_dialog.rename_description = Rename file to\r\n#theme_editor.shell_font = Shell font\r\n#theme_editor.history_font = History font\r\n#theme_editor.shell_colors = Shell colors\r\n#theme_editor.history_colors = History colors\r\n#ToggleHiddenFiles.hide = Do not show hidden files\r\n#auth_dialog.error_was = Error was: %1\r\n#table.hide_column = Hide column\r\n#delete.symlink_warning_title = Symlink found\r\n#delete.symlink_warning = This file looks like a symbolic link:\\n\\n  File: %1\\n  Links to: %2\\n\\nDelete symlink only or\\nFollow symlink and delete folder (CAUTION) ?\r\n#delete.delete_link_only = Delete link\r\n#delete.delete_linked_folder = Delete folder\r\n#Unpack.label = Unpack files\r\n#Pack.label = Pack files\r\nfind_dialog.name = File name\r\nfind_dialog.contains = Contains\r\nfind_dialog.initial_directory = Start at\r\nfind_dialog.search_subdirectories = Search subdirectories\r\nfind_dialog.search_archives = Search archives\r\nfind_dialog.case_sensitive = Case sensitive\r\nfind_dialog.ignore_hidden = Ignore hidden\r\nfind_dialog.search_results = Search results\r\nfind_dialog.found = Found files \r\nfind_dialog.encoding = Text encoding\r\nfind_dialog.search_hex = Search hex\r\nimage_viewer.next_image = Next image\r\nimage_viewer.previous_image = Previous image\r\nhex_viewer.offset = Offset\r\nhex_viewer.ascii_dump = ASCII dump\r\nhex_viewer.view = View\r\nhex_viewer.goto = Goto\r\nhex_viewer.goto.offset = Offset\r\nhex_viewer.search = Search\r\nhex_viewer.searchNext = Find next\r\nhex_viewer.searchPrev = Find previous\r\nhex_viewer.find = Find\r\nhex_view.text = Search for\r\nhex_viewer.hex = Hex\r\nhex_viewer.search_not_found = Pattern not found\r\ncalculator.calculator = Calculator\r\ncalculator.expression = Expression\r\ncalculator.error = Error in expression\r\nreplication = Replication factor\r\nblocksize = Block Size\r\nChangeReplication.label = Change Replication\r\nreplication.number = Replication factor\r\n\r\nadb.android_devices = Android\r\nadb.no_devices = No devices\r\neject.no_mounted_devices = No mounted devices\r\n\r\nretry_as_root = Retry as root"
  },
  {
    "path": "src/main/resources/dictionary_ar_SA.properties",
    "content": "ok = موافق\nyes = نعم\nno = لا\ncancel = إلغاء\nedit = تحرير\nclose = إغلاق\nreset = تصفير\nrename = إعادة تسمية\napply = تطبيق\nchange = تغيير\nsave = حفظ\ndont_save = عدم الحفظ\nreplace = استبدال\ndont_replace = عدم الاستبدال\ndelete = حذف\nskip = تخطي\nskip_all = تخطي الكل\nretry = حاول مجدداً\nresume = أكمل\noverwrite = كتابة على\noverwrite_if_older = الكتابة على القديم\nduplicate = تكرار\napply_to_all = تطبيق على الكل\ncopy = نسخ\nmove = نقل\npack = أرشفة\nunpack = استخراج\ndownload = تنزيل\nsplit = تفسيم\ncombine = دمج\nbrowse = تصفح\nask = اسأل\nstop = إيقاف\npause = إيقاف مؤقت\nquick_search = بحث سريع\nfile_manager = مدير الملفات\ncreate = إنشاء\ncreating_file = إنشاء %1\nchoose = اختيار\ncustomize = تخصيص\nchoose_folder = اختيار مجلد\nlogin = ولوج\npassword = كلمة السر\nuser = مستخدم\nencoding = ترميز\npreferred_encodings = الترميز المفضل\nlicense = ترخيص\nname = اسم\nsize = حجم\ndate = تاريخ\nextension = لاحقة\npermissions = صلاحيات\nowner = مالك\ngroup = مجموعة\nlocation = موقع\nuntitled = غير معنون\nsource = مصدر\ndestination = الوجهة\nrecurse_directories = تكرار معالجة الأدلة المحددة\ngo_to = الذهاب إلى\nexample = مثال\npreview = معاينة\ncomment = تعليق\nsample_text = عينة نص\nnb_files = %1 ملفـ(ات)\nnb_folders = %1 مجلد(ات)\nloading = تحميل...\nthis_operation_cannot_be_undone = هذه العملية لا يمكن إلغاؤها.\nremove = حذف\ndetails = تفاصيل\nwarning = تحذير\nerror = خطأ\ngeneric_error = حدث خطأ أثناء إنجاز العملية المطلوبة\nfolder_does_not_exist = هذا المجلد غير موجود أو أنه غير متاح.\nthis_folder_does_not_exist = المجلد التالي غير موجود أو أنه غير متاح: %1\nthis_file_does_not_exist = الملف التالي غير موجود أو أنه غير متاح: %1\ninvalid_path = مسار خاطئ: %1\ndirectory_already_exists = الدليل %1 موجود مسبقاً\nfile_exists_in_destination = الملف موجود مسبقاً في الوجهة المحددة\nsource_parent_of_destination = محاولة نقل مجلد إلى أحد مجلداته الفرعية\ncannot_read_file = لا يمكن قراءة الملف %1\ncannot_write_file = لا يمكن كتابة الملف %1\ncannot_create_folder = لا يمكن إنشاء الدليل %1\ncannot_read_folder = لا يمكن قراءة محتويات المجلد %1\ncannot_delete_file = لا يمكن حذف المجلد %1\ncannot_delete_folder = لا يمكن حذف المجلد %1\nerror_while_transferring = خطأ أثناء نقل الملف %1\nsame_source_destination = مجلد المصدر هو نفسه مجلد الوجهة.\nfile_already_exists = %1 موجود مسبقاً، هل تريد استبداله؟\nwrite_error = خطأ كتابة\nread_error = خطأ قراءة\nintegrity_check_error = فشل التحقق من التكامل: المصدر والوجهة غير متطابقين\nstartup_error = حدث خطأ منع من تشغيل trolCommander.\npermissions.read = قراءة\npermissions.write = كتابة\npermissions.executable = تنفيذ\npermissions.group = مجموعة\npermissions.other = آخر\npermissions.octal_notation = تدوين ثماني\naction_categories.all = الكل\naction_categories.navigation = تصفح\naction_categories.selection = تحديد\naction_categories.view = عرض\naction_categories.file_operations = عمليات الملفات\naction_categories.windows = نوافذ\nTerminal.label = Terminal\nTerminal.tooltip = Terminal\nFindFile.label = Find file\nFindFile.tooltip = Find file\nAddBookmark.label = إضافة علامة\nAddBookmark.tooltip = إضافة المجلد الحالي إلى قائمة العلامات\nBatchRename.label = إعادة تسمية بالملفات الدفعية\nEditBookmarks.label = تحرير العلامات\nExploreBookmarks.label = استكشاف العلامات\nEditCredentials.label = تحرير الاعتمادات\nChangeLocation.label = تغيير الموقع الحالي\nChangeDate.label = تغيير التاريخ\nChangeDate.tooltip = تغيير تاريخ الملفـ(ات) المحددة\nChangePermissions.label = تغيير الصلاحيات\nChangePermissions.tooltip = تغيير صلاحيات الملفـ(ات) المحددة.\nCheckForUpdates.label = التحقق من التحديثات\nCompareFolders.label = مقارنة المجلدات\nConnectToServer.label = الاتصال بالخادم\nConnectToServer.tooltip = الاتصال بخادم بعيد\nView.label = عرض\nInternalView.label = عرض (محلي)\nView.tooltip = عرض الملف المحدد\nInternalEdit.label = تحرير (محلي)\nEdit.tooltip = تحرير الملف المحدد\nCopy.tooltip = نسخ الملفات المحددة\nLocalCopy.label = نسخة محلية\nLocalCopy.tooltip = نسخ الملف المحدد في المجلد الحالي\nMove.tooltip = نقل الملفات المحددة\nRename.tooltip = إعادة تسمية الملف المحدد\nMkdir.label = إنشاء مجلد\nMkdir.tooltip = إنشاء دليل في المجلد الحالي\nMkfile.label = إنشاء ملف\nMkfile.tooltip = إنشاء ملف في المجلد الحالي\nDelete.tooltip = حذف الملفات المحددة باستخدام سلة المحذوفات إن أمكن\nPermanentDelete.label = حذف نهائي\nPermanentDelete.tooltip = حذف الملفات المحددة بدون استخدام سلة المحذوفات\nRefresh.label = تحديث\nRefresh.tooltip = تحديث المجلد الحالي\nCloseWindow.label = إغلاق النافذة\nCloseWindow.tooltip = إغلاق هذه النافذة\nCopyFileNames.label = نسخ الأسمـ(اء)\nCopyFilePaths.label = نسخ المسار(ات)\nCopyFilesToClipboard.label = نسخ الملفـ(ات)\nPasteClipboardFiles.label = إلصاق الملفـ(ات)\nEmail.label = إرسال بالبريد\nEmail.tooltip = إرسال الملفات المحددة كمرفقات بريد\nGoBack.label = ارجع للخلف\nGoBack.tooltip = ارجع للمجلد السابق\nGoForward.label = تقدم للأمام\nGoForward.tooltip = اذهب للمجلد التالي\nGoToHome.label = اذهب لمجلد المنزل\nGoToParent.label = اذهب للأب\nGoToParent.tooltip = اذهب للمجلد الأب\nGoToParentInOtherPanel.label = اذهب للأب في لوحة أخرى\nGoToParentInBothPanels.label = اذهب للأب في كلا اللوحتين\nGoToRoot.label = اذهب للجذر\nSortByName.label = فرز حسب الاسم\nSortByDate.label = فرز حسب التاريخ\nSortBySize.label = فرز حسب الحجم\nSortByExtension.label = فرز حسب اللاحقة\nSortByPermissions.label = فرز حسب الصلاحيات\nSortByOwner.label = فرز حسب المالك\nSortByGroup.label = فرز حسب المجموعة\nMarkGroup.label = تحديد ملفات\nMarkGroup.tooltip = تحديد مجموعة من الملفات\nUnmarkGroup.label = إلغاء تحديد ملفات\nUnmarkGroup.tooltip = إلغاء تحديد مجموعة من الملفات\nMarkAll.label = تحديد الكل\nUnmarkAll.label = إلغاء تحديد الكل\nMarkSelectedFile.label = تحديد/إلغاء\nMarkSelectedFile.tooltip = تحديد/إلغاء الملفات المعينة\nMarkNextBlock.label = تحديد كتلة واحدة للأسفل\nMarkPreviousBlock.label = تحديد كتلة واحدة للأعلى\nMarkNextRow.label = تحديد صف واحد للأسفل\nMarkPreviousRow.label = تحديد صف واحد للأعلى\nMarkNextPage.label = تحديد صفحة للأسفل\nMarkPreviousPage.label = تحديد صفحة للأعلى\nMarkToFirstRow.label = تحديد الملفات حتى البداية\nMarkToLastRow.label = تحديد الملفات حتى النهاية\nMarkExtension.label = تحديد لاحقة\nInvertSelection.label = عكس التحديد\nSwapFolders.label = تبديل المجلدات\nSwapFolders.tooltip = تبديل المجلدين الأيمن والأيسر\nSetSameFolder.label = تعيين نفس المجلد\nSetSameFolder.tooltip = تعيين نفس الدليل للمجلدين الأيمن والأيسر\nNewWindow.label = نافذة جديدة\nNewWindow.tooltip = فتح نافذة جديدة\nOpen.label = فتح\nOpen.tooltip = دخول المجلد / دخول الأرشيف / تنفيذ\nOpenNatively.label = فتح عادي\nOpenNatively.tooltip = تنفيذ الملف المحدد حسب إعدادات النظام\nOpenInOtherPanel.label = فتح في لوحة أخرى\nOpenInBothPanels.label = فتح في كلا اللوحتين\nRevealInDesktop.label = استكشاف في %1\nRunCommand.label = تنفيذ أمر\nRunCommand.tooltip = تنفيذ أمر في المجلد الحالي\nPack.tooltip = أرشفة الملفات المحددة في أرشيف\nUnpack.tooltip = استخراج الملفات المحددة من الأرشيف\nShowFileProperties.label = خصائص\nShowFileProperties.tooltip = إظهار خصائص الملفات المحددة\nShowPreferences.label = تفضيلات\nShowPreferences.tooltip = ضبط trolCommander\nShowServerConnections.label = إظهار الاتصالات المفتوحة\nQuit.label = إنهاء\nReverseSortOrder.label = عكس الترتيب\nToggleAutoSize.label = تحجيم الأعمدة تلقائياً\nStop.label = إيقاف تغيير الملفات\nToggleColumn.show = إظهار عمود %1\nToggleColumn.hide = إخفاء عمود %1\nToggleCommandBar.show = إظهار شريط الأوامر\nToggleCommandBar.hide = إخفاء شريط الأوامر\nToggleToolBar.show = إظهار شريط الأدوات\nToggleToolBar.hide = إخفاء شريط الأدوات\nCustomizeCommandBar.label = تخصيص\nToggleStatusBar.show = إظهار شريط الحالة\nToggleStatusBar.hide = إخفاء شريط الحالة\nToggleShowFoldersFirst.label = إظهار المجلدات أولاً\nToggleTree.label = إظهار العرض الشجري\nPopupLeftDriveButton.label = تغيير المجلد الأيسر\nPopupRightDriveButton.label = تغيير المجلد الأيمن\nRecallPreviousWindow.label = إعادة النافذة السابقة\nRecallNextWindow.label = إعادة النافذة التالية\nRecallWindow.label = إعادة النافذة #%1\nBringAllToFront.label = إحضار جميع النوافذ للمقدمة\nSwitchActiveTable.label = تبديل بين اللوحتين اليمنى واليسرى\nSelectNextBlock.label = قفز كتلة واحدة للأسفل\nSelectPreviousBlock.label = قفز كتلة واحدة للأعلى\nSelectNextPage.label = قفز صفحة واحدة للأسفل\nSelectPreviousPage.label = قفز صفحة واحدة للأعلى\nSelectNextRow.label = قفز صف واحد للأسفل\nSelectPreviousRow.label = قفز صف واحد للأعلى\nSelectFirstRow.label = اختيار الملف الأول في المجلد الحالي\nSelectLastRow.label = اختيار الملف الأخير في المجلد الحالي\nSplitEqually.label = تقسيم بشكل متساوي\nSplitVertically.label = تقسيم بشكل رأسي\nSplitHorizontally.label = تقسيم بشكل أفقي\nShowKeyboardShortcuts.label = اختصارات لوحة المفاتيح\nGoToWebsite.label = اذهب إلى الموقع\nGoToForums.label = اذهب إلى المنتديات\nReportBug.label = تبليغ عن خلل\nDonate.label = قم بالتبرع\nShowAbout.label = حول trolCommander\nOpenTrash.label = افتح سلة المحذوفات\nEmptyTrash.label = تفريغ سلة المحذوفات\nCalculateChecksum.label = حساب تدقيق المجموع\nMaximizeWindow.label = تكبير\nMinimizeWindow.label = تصغير\nGoToDocumentation.label = الاتصال بالوثائق\nShowParentFoldersQL.label = المجلدات الأب\nShowRecentLocationsQL.label = المواقع الأخيرة\nShowRecentExecutedFilesQL.label = آخر الملفات تشغيلاً\nSplitFile.tooltip = تقسيم ملف إلى عدة أجزاء\nCombineFiles.tooltip = إعادة دمج أقسام ملف وتجميع الملف الأصلي\nShowDebugConsole.label = لوحة التنقيح\nFocusPrevious.label = التركيز على المكون السابق\nFocusNext.label = التركيز على المكون التالي\nfile_menu = ملف\nfile_menu.open_with = فتح بواسطة\nmark_menu = تحديد\nview_menu = عرض\nview_menu.show_hide_columns = إظهار/إخفاء الأعمدة\ngo_menu = اذهب\nbookmarks_menu = علامات\nbookmarks_menu.no_bookmark = لا توجد علامات\nquick_lists_menu = القوائم السريعة\nwindow_menu = نوافذ\nhelp_menu = مساعدة\nstatus_bar.selected_files = %1 من %2 محدد\nstatus_bar.connecting_to_folder = جاري الاتصال بالمجلد، اضغط خروج للإلغاء.\nstatus_bar.volume_free = مساحة فارغة: %1\nstatus_bar.volume_capacity = السعة: %1\nshortcuts_panel.title = الاختصارات\nshortcuts_panel.restore_defaults = استعادة الافتراضيات\nshortcuts_panel.show = إظهار\nshortcuts_panel.default_message = اضغط إدخال أو مزدوجة بزر الفأرة على الاختصار الذي تود تعديله\nshortcuts_table.action_description = وصف الحدث\nshortcuts_table.shortcut = اختصار\nshortcuts_table.alternate_shortcut = اختصار بديل\nshortcuts_table.type_in_a_shortcut = إدخال اختصار\ncommand_bar_dialog.help = اسحب الأزرار لتخصيص شريط الأوامر\ntable.folder_access_error_title = خطأ في الوصول إلى المجلد\ntable.folder_access_error = لا مكن قراءة محتويات المجلد\ntable.download_or_browse = هل تريد تصفح أو تنزيل هذا الملف؟\nversion_dialog.no_new_version_title = لا إصدارات جديدة\nversion_dialog.no_new_version = تهانينا، أنت لديك الإصدار الأخير.\nversion_dialog.new_version_title = توفر إصدار جديد\nversion_dialog.new_version = توفر إصدار جديد من trolCommander.\nversion_dialog.new_version_url = توفر إصدار جديد من trolCommander على %1.\nversion_dialog.not_available_title = لا يمكن الوصول للخادم\nversion_dialog.not_available = لا يمكن الحصول على معلومات الإصدار من الخادم.\nversion_dialog.install_and_restart = تنصيب وإعادة تشغيل\nversion_dialog.preparing_for_update = التحضير للتحديثات...\nquit_dialog.title = إنهاء trolCommander\nquit_dialog.desc = لديك %1 من النوافذ المفتوحة، هل أنت متأكد أنك تريد الخروج؟\nquit_dialog.show_next_time = إظهار في المرة القادمة\ndestination_dialog.file_exists_action = التصرف الافتراضي عند وجود الملف\ndestination_dialog.verify_integrity = تدقيق تكامل البيانات\ndestination_dialog.skip_errors = تجاوز الأخطاء\nfile_collision_dialog.title = تصادم ملفات\nrename_dialog.new_name = الاسم الجديد\ncopy_dialog.destination = نسخ الملفـ(ات) المحددة إلى\ncopy_dialog.error_title = خطأ نسخ\ncopy_dialog.copying = نسخ الملفات\ncopy_dialog.copying_file = نسخ %1\npack_dialog.packing = أرشفة الملفات\npack_dialog.packing_file = ضغط %1\npack_dialog.error_title = خطأ أرشفة\npack_dialog_description = إضافة الملفات المحددة إلى\npack_dialog.archive_format = صيغة الأرشيف\nunpack_dialog.destination = استخراج الملفـ(ات) المحددة إلى\nunpack_dialog.error_title = خطأ استخراج\nunpack_dialog.unpacking = استخراج الملفات\nunpack_dialog.unpacking_file = استخراج %1\noptimizing_archive = تعديل الأرشيف %1\nerror_while_optimizing_archive = خطأ أثناء تعديل الأرشيف %1\nmove_dialog.move_description = نقل إلى\nmove_dialog.error_title = خطأ نقل\nmove_dialog.moving = نقل الملفات\nmove_dialog.moving_file = نقل %1\ndownload_dialog.description = تنزيل الملف إلى\ndownload_dialog.error_title = خطأ تنزيل\ndownload_dialog.downloading = تنزيل\ndownload_dialog.downloading_file = تنزيل %1\nmkfile_dialog.allocate_space = المساحة المحجوزة\ndelete_dialog.permanently_delete.confirmation = حذف الملفـ(ات) نهائياً؟\ndelete_dialog.move_to_trash.confirmation = حذف الملف(ات) المحددة؟\ndelete_dialog.move_to_trash.confirmation_details = الملفات سوف تنقل إلى سلة المحذوفات\ndelete_dialog.move_to_trash.option = نقل إلى سلة المحذوفات\ndelete_dialog.move_to_trash.failed = ملف أو أكثر لا يمكن نقله إلى سلة المحذوفات.\ndelete_dialog.deleting = حذف\ndelete_dialog.error_title = خطأ حذف\ndelete.deleting_file = حذف %1\nemail_dialog.prefs_not_set_title = لم يتم إعداد البريد\nemail_dialog.prefs_not_set = تحتاج أولاً إلى تعيين وسائط البريد.\nemail_dialog.from = المرسل\nemail_dialog.to = إلى\nemail_dialog.subject = العنوان\nemail_dialog.send = إرسال\nemail_dialog.error_title = خطأ في إرسال الملفات\nemail_dialog.read_error = لا يمكن قراءة الملفات في المجلدات الفرعية.\nemail_dialog.sending = إرسال الملفات\nemail.sending_file = إرسال %1\nemail.connecting_to_server = الاتصال بـ to %1\nemail.server_unavailable = غير قادر على الاتصال بخادم البريد %1، تأكد من إعدادات البريد أو حاول مرة أخرى.\nemail.connection_closed = الخادم أغلق الاتصال، لم يتم إرسال البريد.\nemail.goodbye_failed = خطأ أثناء إغلاق الاتصال، قد لا يكون البريد قد أرسل.\nemail.send_file_error = لا يمكن إرسال الملف %1، لم يتم إرسال البريد\nsplit_file_dialog.error_title = خطأ في تقسيم الملف\nsplit_file_dialog.file_to_split = تقسيم الملف\nsplit_file_dialog.target_directory = الدليل الهدف\nsplit_file_dialog.part_size = حجم الجزء الواحد\nsplit_file_dialog.parts = عدد الأجزاء\nsplit_file_dialog.generate_CRC = توليد ملف CRC\nsplit_file_dialog.max_parts = العدد الأكبر من الأجزاء هو %1\nsplit_file_dialog.auto = آلي\nsplit_file_dialog.insert_new_media = أدخل قرص جديد\ncombine_files_dialog.error_title = خطأ في دمج الملفات\ncombine_files_job.no_crc_file = نجح الدمج، لا يوجد ملف CRC.\ncombine_files_job.crc_read_error = خطأ أثناء قراءة ملف CRC.\ncombine_files_job.crc_check_failed = عدم تطابق CRC: الموجود %1، المتوقع %2\ncombine_files_job.crc_ok = تم الدمج. نجح اختبار تحقق CRC.\nfile_selection_dialog.mark = تحديد\nfile_selection_dialog.unmark = إلغاء تحديد\nfile_selection_dialog.mark_description = تحديد الملفات باسم\nfile_selection_dialog.unmark_description = إلغاء تحديد الملفات باسم\nfile_selection_dialog.case_sensitive = حساس لحالة الأحرف\nfile_selection_dialog.include_folders = تضمين المجلدات\nprogress_dialog.starting = يبدأ النقل...\nprogress_dialog.transferred = نقل %1 في %2\nprogress_dialog.elapsed_time = الوقت الماضي\nprogress_dialog.advanced = متقدم\nprogress_dialog.current_speed = السرعة الحالية\nprogress_dialog.limit_speed = تحديد السرعة\nprogress_dialog.close_when_finished = إغلاق النافذة عند الانتهاء\nprogress_dialog.processing_files = معالجة الملفات\nprogress_dialog.processing_file = معالجة %1\nprogress_dialog.verifying_file = تدقيق %1\nprogress_dialog.job_finished = انتهت المهمة\nprogress_dialog.job_error = خطأ في المهمة\nproperties_dialog.file_properties = %1 خصائص\nproperties_dialog.contents = محتويات\nproperties_dialog.calculating = حساب...\ncalculate_checksum_dialog.checksum_algorithm = خوارزمية تدقيق المجموع\ncalculate_checksum_dialog.temporary_file = ملف مؤقت\nchange_date_dialog.now = الآن\nchange_date_dialog.specific_date = تاريخ معين\nrun_dialog.run_command_description = تشغيل في المجلد الحالي\nrun_dialog.run_in_home_description = تشغل في مجلد المنزل\nrun_dialog.command_output = خرج الأمر\nrun_dialog.run = تشغيل\nrun_dialog.clear_history = مسح المحفوظات\nserver_connect_dialog.server_type = نوع الاتصال\nserver_connect_dialog.server = الخادم\nserver_connect_dialog.share = مشاركة\nserver_connect_dialog.domain = نطاق\nserver_connect_dialog.username = اسم المستخدم\nserver_connect_dialog.initial_dir = الدليل الأولي\nserver_connect_dialog.port = منفذ\nserver_connect_dialog.server_url = عنوان الخادم\nserver_connect_dialog.http_url = عنوان موقع الويب\nserver_connect_dialog.connect = اتصال\nserver_connect_dialog.protocol = بروتوكول\nserver_connect_dialog.nfs_version = إصدار NFS\nserver_connect_dialog.private_key = المفتاح الخاص\nserver_connect_dialog.passphrase = كلمة السر\nftp_connect.passive_mode = تفعيل النمط السلبي\nftp_connect.anonymous_user = مستخدم مجهول\nftp_connect.nb_connection_retries = عدد إعادة محاولات الاتصال\nftp_connect.retry_delay = المدة بين محاولتي الاتصال (بالثواني)\nhttp_connect.basic_authentication = مصادقة HTTP البدائية (اختياري)\nserver_connections_dialog.disconnect = قطع الاتصال\nserver_connections_dialog.connection_busy = مشغول\nserver_connections_dialog.connection_idle = ساكن\nbonjour.bonjour_services = خدمات Bonjour\nbonjour.no_service_discovered = خدمة بدون كلمة سر\nbonjour.bonjour_disabled = Bonjour معطلة\nauth_dialog.title = مصادقة\nauth_dialog.desc = الرجاء إدخال المعرف وكلمة السر\nauth_dialog.server = الخادم\nauth_dialog.store_credentials = تخزين المعرف وكلمة السر (تشفير ضعيف)\nauth_dialog.connect_as = الاتصال كـ\nauth_dialog.authentication_failed = فشل في المصادقة\nsortable_list.move_up = التحريك للأعلى\nsortable_list.move_down = التحريك للأسفل\nadd_bookmark_dialog.add = إضافة\nedit_bookmarks_dialog.new = جديد\nfile_viewer.view_error_title = خطأ عرض\nfile_viewer.view_error = لا يمكن عرض الملف\nfile_viewer.file_menu = ملف\nfile_viewer.close = إغلاق\nfile_viewer.large_file_warning = هذا الملف قد يكون كبيراً جداً على هذه العملية\nfile_viewer.open_anyway = فتح على أية حال\ntext_viewer.edit = تحرير\ntext_viewer.copy = نسخ\ntext_viewer.select_all = تحديد الكل\ntext_viewer.find = بحث\ntext_viewer.find_next = بحث عن التالي\ntext_viewer.find_previous = بحث عن السابق\ntext_viewer.binary_file_warning = يبدو وكأنه ملف ثنائي (Binary)\nimage_viewer.controls_menu = تحكمات\nimage_viewer.zoom_in = تكبير\nimage_viewer.zoom_out = تصغير\nfile_editor.edit_error_title = خطأ تحرير\nfile_editor.edit_error = لا يمكن تحرير الملف\nfile_editor.save = حفظ\nfile_editor.save_as = حفظ باسم...\nfile_editor.save_warning = حفظ التغييرات على هذا الملف قبل الإغلاق؟\nfile_editor.cannot_write = لا يمكن الكتابة للملف.\ntext_editor.cut = قص\ntext_editor.paste = إلصاق\nshortcuts_dialog.quick_search.start_search = اضغط أي محرف لتبدأ البحث السريع\nshortcuts_dialog.quick_search.cancel_search = إلغاء البحث السريع\nshortcuts_dialog.quick_search.remove_last_char = حذف آخر محرف من نص البحث السريع\nshortcuts_dialog.quick_search.jump_to_previous = استحضار نتيجة البحث السريع السابقة\nshortcuts_dialog.quick_search.jump_to_next = استحضار نتيجة البحث السريع التالية\nshortcuts_dialog.quick_search.mark_jump_next = تحديد/إلغاء الملف الحالي واستحضار نتيجة البحث التالية\ntheme_editor.title = محرر الثمة\ntheme_editor.folder_tab = لوح المجلد\ntheme_editor.shell_tab = صدفة\ntheme_editor.shell_history_tab = محفوظات الصدفة\ntheme_editor.statusbar_tab = شريط المعلومات\ntheme_editor.free_space = المساحة الحرة\ntheme_editor.free_space.ok = موافق\ntheme_editor.free_space.warning = تحذير\ntheme_editor.free_space.critical = خطير\ntheme_editor.locationbar_tab = شريط الموقع\ntheme_editor.editor_tab = محرر الملف\ntheme_editor.font = خط\ntheme_editor.active_panel = فعال\ntheme_editor.inactive_panel = غير فعال\ntheme_editor.general = عام\ntheme_editor.could_not_save_theme = لا يمكن كتابة الثمة %1\ntheme_editor.border = إطار\ntheme_editor.background = خلفية\ntheme_editor.alternate_background = خلفية بديلة\ntheme_editor.unfocused_background = الخلفية (بدون تركيز)\ntheme_editor.quick_search.unmatched_file = ملفان غير متطابقان\ntheme_editor.text = نص\ntheme_editor.progress = تقدم\ntheme_editor.normal = طبيعي\ntheme_editor.normal_unfocused = طبيعي (بدون تركيز)\ntheme_editor.selected = محدد\ntheme_editor.selected_unfocused = محدد (بدون تركيز)\ntheme_editor.color = لون\ntheme_editor.colors = ألوان\ntheme_editor.plain_file = ملف بسيط\ntheme_editor.marked_file = ملف محدد\ntheme_editor.hidden_file = ملف مخفي\ntheme_editor.folder = مجلد\ntheme_editor.archive_file = أرشيف\ntheme_editor.symbolic_link = وصلة رمزية\ntheme_editor.header = الرأس\ntheme_editor.item = عنصر\ncommand_bar_customize_dialog.available_actions = الأفعال المتاحة\ncommand_bar_customize_dialog.modifier = المعدل\nprefs_dialog.title = تفضيلات\nprefs_dialog.general_tab = عام\nprefs_dialog.day = يوم\nprefs_dialog.month = شهر\nprefs_dialog.year = سنة\nprefs_dialog.language = اللغة (تحتاج إلى إعادة تشغيل)\nprefs_dialog.date_time = صيغة الوقت والتاريخ\nprefs_dialog.time = الوقت\nprefs_dialog.date = التاريخ\nprefs_dialog.date_separator = فاصل\nprefs_dialog.time_12_hour = صيغة 12 ساعة\nprefs_dialog.time_24_hour = صيغة 24 ساعة\nprefs_dialog.show_seconds = إظهار الثواني\nprefs_dialog.show_century = إظهار القرن\nprefs_dialog.check_for_updates_on_startup = التحقق من التحديثات عند بدء التشغيل\nprefs_dialog.show_splash_screen = إظهار شاشة البداية\nprefs_dialog.folders_tab = المجلدات\nprefs_dialog.startup_folders = مجلدات بداية التشغيل\nprefs_dialog.left_folder = المجلد الأيسر\nprefs_dialog.right_folder = المجلد الأيمن\nprefs_dialog.last_folder = آخر المجلدات زيارة\nprefs_dialog.custom_folder = مجلد مخصص\nprefs_dialog.show_hidden_files = إظهار المجلدات المخفية\nprefs_dialog.show_ds_store_files = إظهار ملفات .DS_Store\nprefs_dialog.show_system_folders = إظهار مجلدات النظام\nprefs_dialog.compact_file_size = كتابة الصيغةالأقرب في حجم الملف (بدون فواصل)\nprefs_dialog.follow_symlinks_when_cd = اتبع الوصلات الرمزية عند تغيير المجلد\nprefs_dialog.appearance_tab = المظهر\nprefs_dialog.look_and_feel = المظهر والإحساس\nprefs_dialog.icons_size = حجم الأيقونات\nprefs_dialog.toolbar_icons = شريط الأدوات\nprefs_dialog.command_bar_icons = شريط الأوامر\nprefs_dialog.file_icons = أنواع الملفات\nprefs_dialog.use_system_file_icons = استخدام أيقونات النظام للملفات\nprefs_dialog.use_system_file_icons.always = دائماً\nprefs_dialog.use_system_file_icons.never = أبداً\nprefs_dialog.use_system_file_icons.applications = فقط للتطبيقات\nprefs_dialog.edit_current_theme = تعديل الثمة الحالية...\nprefs_dialog.themes = ثمات\nprefs_dialog.import_theme = إضافة ثمة\nprefs_dialog.import_look_and_feel = إضافة شكل وإحساس\nprefs_dialog.no_look_and_feel = لا يوجد شكل وإحساس\nprefs_dialog.error_in_import = خطأ عند إضافة الثمة %1.\nprefs_dialog.cannot_read_theme = لا يمكن تحميل الثمة من الملف %1\nprefs_dialog.export_theme = تصدير %1\nprefs_dialog.import = استيراد\nprefs_dialog.export = تصدير\nprefs_dialog.theme_type = نوع %1\nprefs_dialog.delete_theme = حذف الثمة %1 بشكل نهائي؟\nprefs_dialog.delete_look_and_feel = حذف المظهر والإحساس %1 بشكل نهائي؟\nprefs_dialog.rename_failed = فشل في إعادة تسمية الثمة %1\nprefs_dialog.xml_file = ملف XML\nprefs_dialog.jar_file = ملف JAR\nprefs_dialog.mail_tab = بريد\nprefs_dialog.mail_settings = إعدادات البريد الصادر\nprefs_dialog.mail_name = اسمك\nprefs_dialog.mail_address = بريدك\nprefs_dialog.mail_server = خادم SMTP\nprefs_dialog.misc_tab = منوعات\nprefs_dialog.use_brushed_metal = استعمال مظهر 'المعدن الممسوح' (يحتاج إلى إعادة تشغيل)\nprefs_dialog.confirm_on_quit = إظهار نافذة التحقق عند الإنهاء\nprefs_dialog.default_shell = استخدام صدفة النظام الافتراضية\nprefs_dialog.custom_shell = استخدام صدفة مخصصة\nprefs_dialog.shell_encoding = ترميز الصدفة\nprefs_dialog.auto_detect_shell_encoding = اكتشاف تلقائي\nprefs_dialog.enable_bonjour_discovery = تفعيل الكشف عن خدمات Bonjour\nprefs_dialog.enable_system_notifications = تفعيل تنبيهات النظام\ndebug_console_dialog.level = مرحلة\nunit.byte = بايت\nunit.bytes = بايتات\nunit.bytes_short = b\nunit.kb = KB\nunit.mb = MB\nunit.gb = GB\nunit.tb = TB\nunit.speed = %1/ثا\nduration.seconds = %1ثا\nduration.minutes = %1د\nduration.hours = %1سا\nduration.days = %1يوم\nduration.months = %1شهر\nduration.years = %1سنة\ntheme.custom_theme = ثمة مخصصة\ntheme.custom = مخصصة\ntheme.built_in = مضمنة\ntheme.add_on = إضافة\ntheme.current = الحالية\ntheme_could_not_be_loaded = حدث خطأ أثناء تحميل هذه الثمة\nsetup.title = مرحباً إلى trolCommander\nsetup.intro = رجاءً اختر سلوك trolCommander الذي تفضل.\nsetup.look_and_feel = اختر المظهر والإحساس الخاص بك\nsetup.theme = اختر ثمتك\nfont_chooser.font_size = الحجم\nfont_chooser.font_bold = عريض\nfont_chooser.font_italic = مائل\ncolor_chooser.red = أحمر\ncolor_chooser.green = أخضر\ncolor_chooser.blue = أزرق\ncolor_chooser.hue = تدرج اللون\ncolor_chooser.brightness = السطوع\ncolor_chooser.swatches = النماذج\ncolor_chooser.saturation = التشبع\ncolor_chooser.rgb = RGB\ncolor_chooser.hsb = HSB\ncolor_chooser.recent = الأخير\ncolor_chooser.alpha = الشفافية\ncolor_chooser.title = اختيار لون\nbatch_rename_dialog.mask = نمط إعادة التسمية\nbatch_rename_dialog.search_replace = بحث واستبدال\nbatch_rename_dialog.search_for = بحث عن\nbatch_rename_dialog.replace_with = استبدال بـ\nbatch_rename_dialog.counter = عداد\nbatch_rename_dialog.start_at = يبدأ بـ\nbatch_rename_dialog.step_by = خطوة بـ\nbatch_rename_dialog.format = الصيغة\nbatch_rename_dialog.upper_lower_case = حروف كبيرة/صغيرة\nbatch_rename_dialog.no_change = غير متغير\nbatch_rename_dialog.lower_case = حروف صغيرة\nbatch_rename_dialog.upper_case = حروف كبيرة\nbatch_rename_dialog.first_upper = أول حرف كبير\nbatch_rename_dialog.word = أول حرف من كل كلمة\nbatch_rename_dialog.old_name = الاسم القديم\nbatch_rename_dialog.new_name = الاسم الجديد\nbatch_rename_dialog.block_name = حفظ\nbatch_rename_dialog.range = مدى\nbatch_rename_dialog.proceed_renaming = %1 ملفات من %2 سيتم إعادة تسميتها. هل تريد التنفيذ؟\nbatch_rename_dialog.duplicate_names = أسماء متكررة!\nparent_folders_quick_list.empty_message = الموقع الحالي ليس لديه مجلد أب\nrecent_locations_quick_list.empty_message = لا يوجد مواقع أخيرة\nrecent_executed_files_quick_list.empty_message = لا يوجد ملفات مشغلة أخيراً\n#server_connect_dialog.auth_error = معرف خاطئ أو كلمة مرور خاطئة.\n#move_dialog.cannot_move_to_itself = لا يمكن نقل الملفات إلى المجلد الفرعي.\n#pack.error_on_file = خطأ أثناء الضغط %1\n#pack_dialog.cannot_write = لا يمكن إنشاء الملف في المجلد الوجهة.\n#unpack.unable_to_open_zip = يمكن فتح الملف المضغوط %1.\n#image_viewer.previous_image = الصورة السابقة\n#image_viewer.next_image = الصورة التالية\n#mkdir_dialog.title = إنشاء دليل\n#mkdir_dialog.error_title = خطأ إنشاء\n#edit_bookmarks_dialog.remove = حذف\n#mkdir_dialog.description = إنشاء دليل\n#mkfile_dialog.description = إنشاء ملف جديد فارغ\n#done = Done:تم\n#progress_dialog.hide = إخفاء\n#move_dialog.rename_description = إعادة التسمية إلى\n#theme_editor.shell_font = خط الصدفة\n#theme_editor.history_font = محفوظات الخط\n#theme_editor.shell_colors = ألوان الصدفة\n#theme_editor.history_colors = ألوان المحفوظات\n#ToggleHiddenFiles.hide = لا تظهر الملفات المخفية\t \n#auth_dialog.error_was = الخطأ كان: %1\n#table.hide_column = إخفاء عمود\n#delete.symlink_warning_title = تم العثور على وصلة رمزية\n#delete.symlink_warning = هذا الملف يبدو وكأنه وصلة رمزية:\\n\\n  الملف: %1\\n  مرتبط بـ: %2\\n\\nحذف الوصلة فقط أم\\nتتبع الوصلة وحذف المجلد (تحذير)؟\n#delete.delete_link_only = حذف الوصلة\n#delete.delete_linked_folder = حذف المجلد\n#Unpack.label = استخراج الملفات\n#Pack.label = أرشفة الملفات\n"
  },
  {
    "path": "src/main/resources/dictionary_be_BY.properties",
    "content": "ok = OK\nyes = Так\nno = Не\ncancel = Скасаваць\nedit = Рэдагаваць\nclose = Зачыніць\nreset = Скінуць\nrename = Зьмяніць назву\napply = Ужыць\nchange = Зьмяніць\nsave = Захаваць\ndont_save = Не захоўваць\nreplace = Замяніць\ndont_replace = Не замяняць\ndelete = Выдаліць\nskip = Прапусьціць\nskip_all = Прапусьціць усё\nretry = Паўтарыць\nresume = Працягнуць\noverwrite = Замяніць\noverwrite_if_older = Толькі старыя\nduplicate = Падвоіць\napply_to_all = Ужыць да ўсяго\ncopy = Капіяваць\nmove = Перанесьці\npack = Запакаваць\nunpack = Распакаваць\ndownload = Спампаваць\nsplit = Разьбіць\ncombine = Злучыць\nbrowse = Агляд\nask = Запытацца\nstop = Спыніць\npause = Прыпыніць\nquick_search = Хуткі пошук\nfile_manager = Мэнэджэр файлаў\ncreate = Стварыць\ncreating_file = Ствараецца %1\nchoose = Абярыце\ncustomize = Наладзіць\nchoose_folder = Абярыце тэчку\nlogin = Лагін\npassword = Пароль\nuser = Карыстальнік\nencoding = Кадыроўка\npreferred_encodings = Абраныя кадыроўкі\nlicense = Ліцэнзія\nname = Імя\nsize = Памер\ndate = Дата\nextension = Пашырэньне\npermissions = Прывілеі\nowner = Уладальнік\ngroup = Група\nlocation = Разьмяшчэньне\nuntitled = Без імя\nsource = Крыніца\ndestination = Атрымальнік\nrecurse_directories = Апрацоўваць падтэчкі\ngo_to = Перайсьці да\nexample = Прыклад\npreview = Папярэдні прагляд\ncomment = Камэнтары\nsample_text = Прыклад тэксту\nnb_files = %1 файл(аў)\nnb_folders = %1 тэчка(і)\nloading = Загрузка...\nthis_operation_cannot_be_undone = Немагчыма скасаваць дадзеную апэрацыю\nremove = Выдаліць\ndetails = Падрабязьней\nwarning = Папярэджаньне\nerror = Памылка\ngeneric_error = Пры выкананьні апэрацыі ўзьнікла памылка\nfolder_does_not_exist = Тэчка з такім імём не існуе альбо недасягальная\nthis_folder_does_not_exist = Тэчка %1 не існуе альбо недасягальная\nthis_file_does_not_exist = Файл %1 не існуе альбо недасягальны\ninvalid_path = Няслушны шлях: %1\ndirectory_already_exists = Тэчка %1 ужо існуе.\nfile_exists_in_destination = Файл ужо існуе\nsource_parent_of_destination = Спроба скапіяваць тэчку ў яе ўласную падтэчку\ncannot_read_file = Не атрымліваецца прачытаць файл %1\ncannot_write_file = Не атрымліваецца запісаць файл %1\ncannot_create_folder = Не атрымліваецца стварыць тэчку %1.\ncannot_read_folder = Не атрымліваецца прачытаць зьмест тэчкі %1\ncannot_delete_file = Не атрымліваецца выдаліць файл %1\ncannot_delete_folder = Не атрымліваецца стварыць тэчку %1\nerror_while_transferring = Памылка пры перадачы файлу %1\nsame_source_destination = Зыходная і канцавая тэчка - тая самая\nfile_already_exists = Файл %1 ужо існуе. Замяніць?\nwrite_error = Памылка запісу\nread_error = Памылка чытаньня\nintegrity_check_error = Ня пройдзена праверка цэласнасьці: файл-крыніца і файл-прымальнік не супадаюць\nstartup_error = Пры запуску trolCommander'у ўзьнікла памылка.\npermissions.read = Чытаньне\npermissions.write = Запіс\npermissions.executable = Выкананьне\npermissions.group = Група\npermissions.other = Іншае\npermissions.octal_notation = Лікавае значэньне\naction_categories.all = Усе\naction_categories.navigation = Навігацыя\naction_categories.selection = Выбар\naction_categories.view = Прагляд\naction_categories.file_operations = Файлавыя апэрацыі\naction_categories.windows = Вакенцы\nTerminal.label = Terminal\nTerminal.tooltip = Terminal\nFindFile.label = Find file\nFindFile.tooltip = Find file\nAddBookmark.label = Дадаць закладку\nAddBookmark.tooltip = Дадаць бягучую тэчку ў закладкі\nBatchRename.label = Групавая зьмена назвы\nEditBookmarks.label = Рэдагаваць закладкі\nExploreBookmarks.label = Праглядзець закладкі\nEditCredentials.label = Рэдагаваць рэквізыты\nChangeLocation.label = Зьмяніць месцазнаходжаньне\nChangeDate.label = Зьмяніць дату\nChangeDate.tooltip = Зьмяніць дату абраных файлаў\nChangePermissions.label = Зьмяніць прывілеі\nChangePermissions.tooltip = Зьмяніць прывілеі абраных файлаў\nCheckForUpdates.label = Праверыць абнаўленьні\nCompareFolders.label = Параўнаць тэчкі\nConnectToServer.label = Злучыцца з сэрвэрам\nConnectToServer.tooltip = Злучыцца з дыстанцыянаваным сэрвэрам\nView.label = Праглядзець\nInternalView.label = Прагляд (убудаваным)\nView.tooltip = Прагляд абранага файлу\nInternalEdit.label = Рэдагаваць (убудаваным)\nEdit.tooltip = Рэдагаваць абраны файл\nCopy.tooltip = Капіяваць вылучаныя файлы\nLocalCopy.label = Капіяваць лакальна\nLocalCopy.tooltip = Капіяваць абраны файл у дадзеную тэчку\nMove.tooltip = Перанесьці вылучаныя файлы\nRename.tooltip = Зьмяніць назву абранага файлу\nMkdir.label = Стварыць тэчку\nMkdir.tooltip = Стварыць падтэчку ў дадзенай тэчцы\nMkfile.label = Стварыць файл\nMkfile.tooltip = Стварыць файл у дадзенай тэчцы\nDelete.tooltip = Выдаліць вылучаныя файлы ў Сьметніцу, калі гэта магчыма\nPermanentDelete.label = Выдаліць адразу\nPermanentDelete.tooltip = Выдаліць вылучаныя файлы назаўсёды не выкарыстоўваючы Сьметніцу\nRefresh.label = Абнавіць\nRefresh.tooltip = Абнаўленьне сьпісу файлаў у дадзенай тэчцы\nCloseWindow.label = Зачыніць\nCloseWindow.tooltip = Зачыніць вакенца\nCopyFileNames.label = Капіяваць імёны\nCopyFilePaths.label = Капіяваць шляхі\nCopyFilesToClipboard.label = Капіяваць файл(ы)\nPasteClipboardFiles.label = Уставіць файл(ы)\nEmail.label = Даслаць файл(ы) па email\nEmail.tooltip = Даслаць вылучаныя файлы як дадаткі па email\nGoBack.label = Назад\nGoBack.tooltip = Перайсьці да папярэдняй тэчкі\nGoForward.label = Наперад\nGoForward.tooltip = Перайсьці да наступнай тэчкі\nGoToHome.label = Перайсьці ў хатнюю тэчку\nGoToParent.label = Перайсьці на ўзровень вышэй\nGoToParent.tooltip = Перайсьці ў бацькоўскую тэчку\nGoToParentInOtherPanel.label = Перайсьці на ўзровень вышэй у іншай панэлі\nGoToParentInBothPanels.label = Перайсьці на ўзровень вышэй у абедзьвюх панэлях\nGoToRoot.label = Перайсьці ў корань\nSortByName.label = Сартаваць па імю\nSortByDate.label = Сартаваць па даце\nSortBySize.label = Сартаваць па памеру\nSortByExtension.label = Сартаваць па пашырэньню\nSortByPermissions.label = Сартаваць па прывілеях\nSortByOwner.label = Сартаваць па ўладальніку\nSortByGroup.label = Сартаваць па групе\nMarkGroup.label = Вылучыць файлы\nMarkGroup.tooltip = Вылучыць групу файлаў\nUnmarkGroup.label = Зьняць вылучэньне з файлаў\nUnmarkGroup.tooltip = Скасаваць вылучэньне групы файлаў\nMarkAll.label = Вылучыць усе\nUnmarkAll.label = Зьняць вылучэньне са ўсяго\nMarkSelectedFile.label = Вылучыць/зьняць\nMarkSelectedFile.tooltip = Вылучыць/зьняць вылучэньне абранага файлу\nMarkNextBlock.label = Вылучыць блок радкоў уніз\nMarkPreviousBlock.label = Вылучыць блок радкоў уверх\nMarkNextRow.label = Парадковае вылучэньне ўніз\nMarkPreviousRow.label = Парадковае вылучэньне ўверх\nMarkNextPage.label = Вылучыць файлы на адну старонку ўніз\nMarkPreviousPage.label = Вылучыць файлы на адну старонку ўверх\nMarkToFirstRow.label = Вылучыць файлы да пачатку\nMarkToLastRow.label = Вылучыць файлы да канца\nMarkExtension.label = Скасаваць вылучэньне\nInvertSelection.label = Інвэртаваць вылучэньне\nSwapFolders.label = Памяняць месцамі тэчкі\nSwapFolders.tooltip = Памяняць месцамі панэлі\nSetSameFolder.label = Зрабіць тэчкі аднолькавымі\nSetSameFolder.tooltip = Аднолькавыя тэчкі ў абедзьвюх панэлях\nNewWindow.label = Новае вакенца\nNewWindow.tooltip = Адчыніць новае вакенца\nOpen.label = Адчыніць\nOpen.tooltip = Увайсьці ў тэчку / Увайсьці ў архіў / Выканаць\nOpenNatively.label = Адчыніць у прывязанай праграме\nOpenNatively.tooltip = Выканаць абраны файл з дапамогай праграмы, якая зьвязана з ім у наладках сыстэмы\nOpenInOtherPanel.label = Адчыніць у іншай панэлі\nOpenInBothPanels.label = Адчыніць у абедзьвюх панэлях\nRevealInDesktop.label = Адлюстраваць у %1\nRunCommand.label = Выканаць каманду\nRunCommand.tooltip = Выканаць каманду з дадзенай тэчкі\nPack.tooltip = Запакаваць вылучаныя файлы ў архіў\nUnpack.tooltip = Распакаваць вылучаныя архіўныя файлы\nShowFileProperties.label = Уласьцівасьці\nShowFileProperties.tooltip = Паказаць уласьцівасьці вылучаных файлаў\nShowPreferences.label = Наладкі\nShowPreferences.tooltip = Наладзіць trolCommander\nShowServerConnections.label = Адлюстраваць усталяваныя злучэньні\nQuit.label = Выхад\nReverseSortOrder.label = У адваротнай пасьлядоўнасьці\nToggleAutoSize.label = Аўтападбор памеру слупкоў\nStop.label = Спыніць зьмяненьне тэчкі\nToggleColumn.show = Паказаць слупок %1\nToggleColumn.hide = Прыбраць слупок %1\nToggleCommandBar.show = Паказаць панэль каманд\nToggleCommandBar.hide = Схаваць панэль каманд\nToggleToolBar.show = Паказаць панэль прылад\nToggleToolBar.hide = Схаваць панэль прылад\nCustomizeCommandBar.label = Наладка панэлі каманд\nToggleStatusBar.show = Паказаць панэль статусу\nToggleStatusBar.hide = Схаваць панэль статусу\nToggleShowFoldersFirst.label = Вынесьці тэчкі ў пачатак сьпісу\nToggleTree.label = Паказаць структуру ў выглядзе дрэва\nPopupLeftDriveButton.label = Зьмяніць левую тэчку\nPopupRightDriveButton.label = Зьмяніць правую тэчку\nRecallPreviousWindow.label = Перайсьці ў папярэдняе вакенца\nRecallNextWindow.label = Перайсьці ў наступнае вакенца\nRecallWindow.label = Узнавіць вакенца #%1\nBringAllToFront.label = Паказаць усе вакенцы\nSwitchActiveTable.label = Пераключэньне паміж левай і правай панэлямі\nSelectNextBlock.label = Спусьціцца на блок радкоў уніз\nSelectPreviousBlock.label = Падняцца на блок радкоў уверх\nSelectNextPage.label = Перайсьці ў канец старонкі\nSelectPreviousPage.label = Перайсьці ў пачатак старонкі\nSelectNextRow.label = Спусьціцца на адзін радок уніз\nSelectPreviousRow.label = Падняцца на адзін радок уверх\nSelectFirstRow.label = Перайсьці да першага файлу ў тэчцы\nSelectLastRow.label = Перайсьці да апошняга файлу ў тэчцы\nSplitEqually.label = Паздзяліць на роўныя часткі\nSplitVertically.label = Падзяліць вэртыкальна\nSplitHorizontally.label = Падзяліць гарызантальна\nShowKeyboardShortcuts.label = Хуткія клявішы\nGoToWebsite.label = Перайсьці на хатнюю старонку праграмы\nGoToForums.label = Перайсьці на форум\nReportBug.label = Паведаміць пра памылку\nDonate.label = Ахвяраваць на развіцьцё праграмы\nShowAbout.label = Пра праграму trolCommander\nOpenTrash.label = Адчыніць Сьметніцу\nEmptyTrash.label = Ачысьціць Сьметніцу\nCalculateChecksum.label = Разьлік кантрольнай сумы\nMaximizeWindow.label = Разгарнуць\nMaximizeWindow.label.mac_os_x = Павялічыць\nMinimizeWindow.label = Згарнуць\nMinimizeWindow.label.mac_os_x = У Док\nGoToDocumentation.label = Докумэнтацыя ў сеціве\nShowParentFoldersQL.label = Бацькоўскія тэчкі\nShowRecentLocationsQL.label = Апошнія наведаныя тэчкі\nShowRecentExecutedFilesQL.label = Апошнія выкарыстаныя файлы\nSplitFile.tooltip = Падзяліць файл на некалькі частак\nCombineFiles.tooltip = Сабраць падзелены на часткі файл у першапачатковы файл\nShowDebugConsole.label = Кансоль Debug\nFocusPrevious.label = Перайсьці да наступнага кампанэнту\nFocusNext.label = Перайсьці да наступнага кампанэнту\nfile_menu = Файл\nfile_menu.open_with = Адчыніць з дапамогай\nmark_menu = Вылучэньне\nview_menu = Прагляд\nview_menu.show_hide_columns = Паказаць/схаваць слупкі\ngo_menu = Перайсьці\nbookmarks_menu = Закладкі\nbookmarks_menu.no_bookmark = Няма закладак\ndrive_popup.network_shares = Абагульнены сеткавы рэсурс\nquick_lists_menu = Хуткі сьпіс\nwindow_menu = Вакенцы\nhelp_menu = Дапамога\nstatus_bar.selected_files = %1 з %2 файлаў абрана\nstatus_bar.connecting_to_folder = Падлучаемся да тэчкі, націсьніце ESC каб скасаваць\nstatus_bar.volume_free = Вольна: %1\nstatus_bar.volume_capacity = Ёмістасьць дыску: %1\nshortcuts_panel.title = Камбінацыі клявіш\nshortcuts_panel.restore_defaults = Вярнуць стандартныя\nshortcuts_panel.show = Адлюстраваць\nshortcuts_panel.default_message = Націснуўшы Enter альбо зрабіўшы падвойны клік вы зможаце рэдагаваць камбінацыі клявіш\nshortcuts_table.action_description = Апісаньне дзеяньня\nshortcuts_table.shortcut = Камбінацыі клявіш\nshortcuts_table.alternate_shortcut = Альтэрнатыўныя камбінацыі\nshortcuts_table.type_in_a_shortcut = Націсьніце камбінацыю клявіш\ncommand_bar_dialog.help = Перацягвайце кнопкі для наладкі панэлі каманд\ntable.folder_access_error_title = Памылка: няма доступу ў тэчку\ntable.folder_access_error = Не атрымліваецца прачытаць зьмест тэчкі\ntable.download_or_browse = Вы жадаеце праглядзець альбо спампаваць дадзены файл?\nversion_dialog.no_new_version_title = Няма новай вэрсіі\nversion_dialog.no_new_version = Вы карыстаецесь самай апошняй вэрсіяй\nversion_dialog.new_version_title = Даступна новая вэрсія\nversion_dialog.new_version = Даступна новая вэрсія trolCommander.\nversion_dialog.new_version_url = Новая вэрсія trolCommander даступна па адрэсе %1.\nversion_dialog.not_available_title = Сэрвэр недаступны\nversion_dialog.not_available = Не атрымліваецца атрымаць інфармацыю пра новыя вэрсіі з сэрвэру.\nversion_dialog.install_and_restart = Усталяваць і перазапусьціць\nversion_dialog.preparing_for_update = Ідзе падрыхтоўка да абнаўленьня...\nquit_dialog.title = Выхад з  trolCommander\nquit_dialog.desc = Зачыніць %1 вакенцаў і выйсьці з trolCommander ?\nquit_dialog.show_next_time = Паказаць у наступны раз\ndestination_dialog.file_exists_action = Дзеяньне па змоўчаньні калі файл ужо існуе\ndestination_dialog.verify_integrity = Зрабіць праверку цэласнасьці дадзеных\ndestination_dialog.skip_errors = Прапусьціць памылкі\nfile_collision_dialog.title = Супадзеньне файлаў\nrename_dialog.new_name = Новае імя\ncopy_dialog.destination = Капіяваць абраныя файлы ў\ncopy_dialog.error_title = Памылка пры капіяванні\ncopy_dialog.copying = Капіяванне файлаў\ncopy_dialog.copying_file = Капірую %1\npack_dialog.packing = Сьціскаюцца файлы\npack_dialog.packing_file = Сьціскаецца файл %1\npack_dialog.error_title = Памылка архівацыі\npack_dialog_description = Дадаць абраныя файлы ў\npack_dialog.archive_format = Фармат архіву\nunpack_dialog.destination = Распакаваць абраныя файлы ў\nunpack_dialog.error_title = Памылка пры распакаваньні\nunpack_dialog.unpacking = Распакаваньне файлаў\nunpack_dialog.unpacking_file = Распакоўваецца файл %1\noptimizing_archive = Аптымізацыя архіву %1\nerror_while_optimizing_archive = Памылка пры аптымізацыі архіву %1\nmove_dialog.move_description = Перанесьці ў\nmove_dialog.error_title = Памылка пры пераносе\nmove_dialog.moving = Перамяшчэньне файлаў\nmove_dialog.moving_file = Перамяшчаецца файл %1\ndownload_dialog.description = Запампаваць файл у\ndownload_dialog.error_title = Памылка пры запампоўцы\ndownload_dialog.downloading = Запампоўка\ndownload_dialog.downloading_file = Запампоўваецца файл %1\nmkfile_dialog.allocate_space = Вылучыць прастору\ndelete_dialog.permanently_delete.confirmation = Выдаліць абраныя файлы без магчымасьці ўзнаўленьня?\ndelete_dialog.move_to_trash.confirmation = Выдаліць абраныя файл(ы)?\ndelete_dialog.move_to_trash.confirmation_details = Файлы будуць перанесены ў Сьметніцу\ndelete_dialog.move_to_trash.option = Перанесьці ў Сьметніцу\ndelete_dialog.move_to_trash.failed = Адзін альбо некалькі файлаў ня могуць быць перанесены ў Сьметніцу.\ndelete_dialog.deleting = Выдаленьне файлаў\ndelete_dialog.error_title = Памылка пры выдаленьні файлу\ndelete.deleting_file = Выдаляецца файл %1\nemail_dialog.prefs_not_set_title = Пошта не наладжана\nemail_dialog.prefs_not_set = Вам неабходна наладзіць параметры пошты перад тым як нешта даслаць.\nemail_dialog.from = Ад каго\nemail_dialog.to = Каму\nemail_dialog.subject = Тэма\nemail_dialog.send = Даслаць\nemail_dialog.error_title = Памылка пры дасыланьні файлаў\nemail_dialog.read_error = Немагчыма прачытаць файлы ў падтэчках.\nemail_dialog.sending = Дасыланьне файлаў\nemail.sending_file = Дасылаецца %1\nemail.connecting_to_server = Злучэньне з %1\nemail.server_unavailable = Не атрымалася злучыцца з паштовым сэрвэрам %1, праверце наладкі пошты і паспрабуйце яшчэ раз.\nemail.connection_closed = Злучэньне зачынена сэрвэрам, пошта не была даслана.\nemail.goodbye_failed = Памылка пры завяршэньні злучэньня, магчыма, пошта не была даслана.\nemail.send_file_error = Немагчыма даслаць файл %1, пошта не была даслана.\nsplit_file_dialog.error_title = Памылка разбіцьця файлу\nsplit_file_dialog.file_to_split = Файл для разбіцьця\nsplit_file_dialog.target_directory = Выніковая тэчка\nsplit_file_dialog.part_size = Памер кожнай часткі\nsplit_file_dialog.parts = Колькасьць частак\nsplit_file_dialog.generate_CRC = Сфармаваць CRC файл\nsplit_file_dialog.max_parts = Максымальная колькасьць частак %1\nsplit_file_dialog.auto = Аўтаматычна\nsplit_file_dialog.insert_new_media = Устаўце новы носьбіт\ncombine_files_dialog.error_title = Памылка аб'яднаньня файлу\ncombine_files_job.no_crc_file = Аб'яднаньне прайшло пасьпяхова. Няма CRC файлу.\ncombine_files_job.crc_read_error = Памылка чытаньня CRC файлу.\ncombine_files_job.crc_check_failed = CRC разыходжаньні: чакаецца %2, атрымана %1\ncombine_files_job.crc_ok = Аб'яднаньне прайшло пасьпяхова. Праверка кантрольнай сумы CRC прайшла пасьпяхова.\nfile_selection_dialog.mark = Вылучэньне\nfile_selection_dialog.unmark = Скасаваньне вылучэньня\nfile_selection_dialog.mark_description = Вылучыць файлы па масцы\nfile_selection_dialog.unmark_description = Скасаваць вылучэньне па масцы\nfile_selection_dialog.case_sensitive = З улікам рэгістру\nfile_selection_dialog.include_folders = Уключаючы тэчкі\nprogress_dialog.starting = Перадача файлаў пачалася...\nprogress_dialog.transferred = Перададзена %1, у сярэднем %2\nprogress_dialog.elapsed_time = Спатрэбілася часу\nprogress_dialog.advanced = Дадаткова\nprogress_dialog.current_speed = Хуткасьць\nprogress_dialog.limit_speed = Абмежаваньне хуткасьці\nprogress_dialog.close_when_finished = Зачыніць вакенца пасля сканчэньня\nprogress_dialog.processing_files = Апрацоўка файлаў\nprogress_dialog.processing_file = Апрацоўваецца %1\nprogress_dialog.verifying_file = Ідзе праверка %1\nprogress_dialog.job_finished = Апрацоўка скончана\nprogress_dialog.job_error = Памылка апрацоўкі\nproperties_dialog.file_properties = Уласьцівасьці %1\nproperties_dialog.contents = Зьмест\nproperties_dialog.calculating = Ідзе падлік...\ncalculate_checksum_dialog.checksum_algorithm = Альгарытм падліку кантрольнай сумы\ncalculate_checksum_dialog.temporary_file = Часовы файл\nchange_date_dialog.now = Зараз\nchange_date_dialog.specific_date = Указаная дата\nrun_dialog.run_command_description = Запусьціць з гэтай тэчкі\nrun_dialog.run_in_home_description = Выканаць у хатняй тэчцы\nrun_dialog.command_output = Вынік выкананьня каманды\nrun_dialog.run = Запусьціць\nrun_dialog.clear_history = Ачысьціць буфэр каманд\nserver_connect_dialog.server_type = Тып злучэньня\nserver_connect_dialog.server = Сэрвэр\nserver_connect_dialog.share = Агульны рэсурс\nserver_connect_dialog.domain = Дамэн\nserver_connect_dialog.username = Імя карыстальніка\nserver_connect_dialog.initial_dir = Пачатковая тэчка\nserver_connect_dialog.port = Порт\nserver_connect_dialog.server_url = Адрэса сэрвэру\nserver_connect_dialog.http_url = Адрэса інтэрнэт-старонкі\nserver_connect_dialog.connect = Злучыцца\nserver_connect_dialog.protocol = Пратакол\nserver_connect_dialog.nfs_version = Вэрсія NFS\nserver_connect_dialog.private_key = Прыватны ключ\nserver_connect_dialog.passphrase = Ключавая фраза\nftp_connect.passive_mode = Выкарыстоўваць пасыўны рэжым\nftp_connect.anonymous_user = Ананімнае злучэньне\nftp_connect.nb_connection_retries = Колькасьць спроб усталяваньня злучэньня\nftp_connect.retry_delay = Затрымка паміж паўторамі (у сэкундах)\nhttp_connect.basic_authentication = Базавая аўтэнтыфікацыя HTTP (не абавязкова)\nserver_connections_dialog.disconnect = Адлучыцца\nserver_connections_dialog.connection_busy = Занята\nserver_connections_dialog.connection_idle = Вольна\nbonjour.bonjour_services = Служба Bonjour\nbonjour.no_service_discovered = Служб не знойдзена\nbonjour.bonjour_disabled = Служба Bonjour адключана\nauth_dialog.title = Аўтэнтыфікацыя\nauth_dialog.desc = Увядзіце імя карыстальніка і пароль\nauth_dialog.server = Сэрвэр\nauth_dialog.store_credentials = Захаваць імя карыстальніка і пароль (са слабым шыфраваньнем)\nauth_dialog.connect_as = Далучыцца як\nauth_dialog.authentication_failed = Аўтэнтыфікацыя ня пройдзена\nsortable_list.move_up = Вышэй\nsortable_list.move_down = Ніжэй\nadd_bookmark_dialog.add = Дадаць\nedit_bookmarks_dialog.new = Новая\nfile_viewer.view_error_title = Памылка пры праглядзе\nfile_viewer.view_error = Немагчыма праглядзець файл.\nfile_viewer.file_menu = Файл\nfile_viewer.close = Зачыніць\nfile_viewer.large_file_warning = Гэты файл можа быць занадта вялікім для прагляду.\nfile_viewer.open_anyway = Адчыніць усё-адно\ntext_viewer.edit = Рэдагаваць\ntext_viewer.copy = Капіяваць\ntext_viewer.select_all = Абраць усе\ntext_viewer.find = Пошук\ntext_viewer.find_next = Шукаць далей\ntext_viewer.find_previous = Шукаць папярэдні\ntext_viewer.binary_file_warning = Гэты файл хутчэй за ўсё двойкавы\nimage_viewer.controls_menu = Элементы кіравання\nimage_viewer.zoom_in = Павялічыць\nimage_viewer.zoom_out = Паменшыць\nfile_editor.edit_error_title = Памылка пры рэдагаваньні\nfile_editor.edit_error = Немагчыма рэдагаваць файл.\nfile_editor.save = Захаваць\nfile_editor.save_as = Захаваць як...\nfile_editor.save_warning = Захаваць зробленыя зьмены перад выхадам ?\nfile_editor.cannot_write = Не атрымліваецца запісаць файл.\ntext_editor.cut = Выразаць\ntext_editor.paste = Уставіць\nshortcuts_dialog.quick_search.start_search = Увядзіце адвольны сымбаль для пачатку хуткага пошуку\nshortcuts_dialog.quick_search.cancel_search = Скасаваньне хуткага пошуку\nshortcuts_dialog.quick_search.remove_last_char = Выдаліць апошні сымбаль з радку хуткага пошуку\nshortcuts_dialog.quick_search.jump_to_previous = Перайсьці да папярэдняга выніку хуткага пошуку\nshortcuts_dialog.quick_search.jump_to_next = Перайсьці да наступнага выніку хуткага пошуку\nshortcuts_dialog.quick_search.mark_jump_next = Вылучыць/скасаваць вылучэньне з файлу і перайсьці да наступнага выніку пошуку\ntheme_editor.title = Рэдактар тэмы\ntheme_editor.folder_tab = Панэль тэчкі\ntheme_editor.shell_tab = Абалонка\ntheme_editor.shell_history_tab = Гісторыя каманд\ntheme_editor.statusbar_tab = Радок стану\ntheme_editor.free_space = Вольная прастора\ntheme_editor.free_space.ok = OK\ntheme_editor.free_space.warning = Увага\ntheme_editor.free_space.critical = Вельмі мала\ntheme_editor.locationbar_tab = Указальнік месцазнаходжаньня\ntheme_editor.editor_tab = Рэдактар файлаў\ntheme_editor.font = Шрыфт\ntheme_editor.active_panel = Актыўная\ntheme_editor.inactive_panel = Неактыўная\ntheme_editor.general = Агульны\ntheme_editor.could_not_save_theme = Не атрымліваецца захаваць тэму %1\ntheme_editor.border = Рамка\ntheme_editor.background = Фон\ntheme_editor.alternate_background = Іншы фон\ntheme_editor.unfocused_background = Фон (без фокусу)\ntheme_editor.quick_search.unmatched_file = Файлы, што не супадаюць\ntheme_editor.text = Тэкст\ntheme_editor.progress = Індыкатар выкананьня\ntheme_editor.normal = Звычайны\ntheme_editor.normal_unfocused = Звычайны (без фокусу)\ntheme_editor.selected = Абраны\ntheme_editor.selected_unfocused = Абраны (без фокусу)\ntheme_editor.color = Колер\ntheme_editor.colors = Колеры\ntheme_editor.plain_file = Звычайны файл\ntheme_editor.marked_file = Вылучаны файл\ntheme_editor.hidden_file = Схаваны файл\ntheme_editor.folder = Тэчка\ntheme_editor.archive_file = Архіў\ntheme_editor.symbolic_link = Сымбальная спасылка\ntheme_editor.header = Загаловак\ntheme_editor.item = Элемент\ncommand_bar_customize_dialog.available_actions = Даступныя дзеяньні\ncommand_bar_customize_dialog.modifier = Пераключальнік\nprefs_dialog.title = Наладкі\nprefs_dialog.general_tab = Агульныя\nprefs_dialog.day = Дзень\nprefs_dialog.month = Месяц\nprefs_dialog.year = Год\nprefs_dialog.language = Мова (патрабуецца перазапуск)\nprefs_dialog.date_time = Фармат даты і часу\nprefs_dialog.time = Час\nprefs_dialog.date = Дата\nprefs_dialog.date_separator = Разьдзяляльнік\nprefs_dialog.time_12_hour = 12-гадзінавы фармат\nprefs_dialog.time_24_hour = 24-гадзінавы фармат\nprefs_dialog.show_seconds = Паказваць сэкунды\nprefs_dialog.show_century = Паказваць усе 4 лічбы году\nprefs_dialog.check_for_updates_on_startup = Правяраць наяўнасьць новых вэрсій пры запуску\nprefs_dialog.show_splash_screen = Паказваць застаўку\nprefs_dialog.folders_tab = Тэчкі\nprefs_dialog.startup_folders = Тэчкі пры запуску\nprefs_dialog.left_folder = Тэчка ў левай панэлі\nprefs_dialog.right_folder = Тэчка ў правай панэлі\nprefs_dialog.last_folder = Апошняя наведаная тэчка\nprefs_dialog.custom_folder = Зададзеная тэчка\nprefs_dialog.show_hidden_files = Паказваць схаваныя файлы\nprefs_dialog.show_ds_store_files = Паказваць файлы .DS_Store\nprefs_dialog.show_system_folders = Паказваць сыстэмныя тэчкі\nprefs_dialog.compact_file_size = Акругляць паказаныя памеры файлаў\nprefs_dialog.follow_symlinks_when_cd = Пераходзіць па спасылках пры зьмене тэчкі\nprefs_dialog.appearance_tab = Зьнешні выгляд\nprefs_dialog.look_and_feel = Наладка выгляду\nprefs_dialog.icons_size = Памер значак\nprefs_dialog.toolbar_icons = Панэль прыладаў\nprefs_dialog.command_bar_icons = Панэль каманд\nprefs_dialog.file_icons = Тыпы файлаў\nprefs_dialog.use_system_file_icons = Выкарыстоўваць сыстэмныя значкі файлаў\nprefs_dialog.use_system_file_icons.always = Заўсёды\nprefs_dialog.use_system_file_icons.never = Ніколі\nprefs_dialog.use_system_file_icons.applications = Толькі для праграм\nprefs_dialog.edit_current_theme = Рэдагаваць тэму аздабленьня...\nprefs_dialog.themes = Тэмы\nprefs_dialog.import_theme = Імпартаваць тэму\nprefs_dialog.import_look_and_feel = Імпарт наладак зьнешняга выгляду\nprefs_dialog.no_look_and_feel = Наладак зьнешняга выгляду ня знойдзена.\nprefs_dialog.error_in_import = Памылка імпарту тэмы %1.\nprefs_dialog.cannot_read_theme = Не атрымліваецца загрузіць тэму з файлу %1\nprefs_dialog.export_theme = Экспарт %1\nprefs_dialog.import = Імпарт\nprefs_dialog.export = Экспарт\nprefs_dialog.theme_type = Тып: %1\nprefs_dialog.delete_theme = Выдаліць тэму %1 цалкам?\nprefs_dialog.delete_look_and_feel = Выдаліць наладкі %1 назаўсёды?\nprefs_dialog.rename_failed = Не атрымалася зьмяніць назву тэмы %1\nprefs_dialog.xml_file = XML-файл\nprefs_dialog.jar_file = JAR-файл\nprefs_dialog.mail_tab = Email\nprefs_dialog.mail_settings = Наладкі дасыланьня пошты\nprefs_dialog.mail_name = Вашае імя\nprefs_dialog.mail_address = Ваша адрэса Email\nprefs_dialog.mail_server = SMTP-сэрвэр\nprefs_dialog.misc_tab = Дадаткова\nprefs_dialog.use_brushed_metal = Выкарыстоўваць тэму 'шліфаваны мэтал' (патрэбны перазапуск)\nprefs_dialog.confirm_on_quit = Запытваць пацьверджаньне пры выхадзе\nprefs_dialog.default_shell = Выкарыстоўваць сыстэмную абалонку па змоўчаньні\nprefs_dialog.custom_shell = Выкарыстоўваць указаную абалонку\nprefs_dialog.shell_encoding = Кадыроўка каманднага радку\nprefs_dialog.auto_detect_shell_encoding = Аўтавызначэньне\nprefs_dialog.enable_bonjour_discovery = Уключыць службу знаходжаньня Bonjour\nprefs_dialog.enable_system_notifications = Дазволіць сыстэмныя паведамленьні\ndebug_console_dialog.level = Узровень\nunit.byte = байт\nunit.bytes = байтаў\nunit.bytes_short = б\nunit.kb = Кб\nunit.mb = Мб\nunit.gb = Гб\nunit.tb = Тб\nunit.speed = %1/сэк\nduration.seconds = %1с\nduration.minutes = %1хв\nduration.hours = %1г\nduration.days = %1д\nduration.months = %1мес\nduration.years = %1год\ntheme.custom_theme = Уласная тэма\ntheme.custom = Тэма карыстальніка\ntheme.built_in = Убудаваная\ntheme.add_on = Дадатак\ntheme.current = бягучая\ntheme_could_not_be_loaded = Пры запампоўцы тэмы адбылася памылка.\nsetup.title = Вітаем у trolCommander\nsetup.intro = Абярыце асноўныя наладкі trolCommander.\nsetup.look_and_feel = Абярыце інтэрфэйс карыстальніка\nsetup.theme = Абярыце тэму аздабленьня\nfont_chooser.font_size = Памер\nfont_chooser.font_bold = Паўтлусты\nfont_chooser.font_italic = Курсыў\ncolor_chooser.red = Чырвоны\ncolor_chooser.green = Зялёны\ncolor_chooser.blue = Сіні\ncolor_chooser.hue = Адценьне\ncolor_chooser.brightness = Яркасьць\ncolor_chooser.swatches = Узоры\ncolor_chooser.saturation = Насычанасьць\ncolor_chooser.rgb = RGB\ncolor_chooser.hsb = HSB\ncolor_chooser.recent = Папярэдні\ncolor_chooser.alpha = Празрыстасьць\ncolor_chooser.title = Выбар колеру\nbatch_rename_dialog.mask = Шаблон зьмяненьня назвы\nbatch_rename_dialog.search_replace = Пошук і замена\nbatch_rename_dialog.search_for = Шукаць\nbatch_rename_dialog.replace_with = Замяніць на\nbatch_rename_dialog.counter = Лічыльнік\nbatch_rename_dialog.start_at = Пачаць з\nbatch_rename_dialog.step_by = Крок\nbatch_rename_dialog.format = Фармат\nbatch_rename_dialog.upper_lower_case = Верхні/ніжні рэгістр\nbatch_rename_dialog.no_change = Без зьмяненьняў\nbatch_rename_dialog.lower_case = ніжні рэгістр\nbatch_rename_dialog.upper_case = ВЕРХНІ РЭГІСТР\nbatch_rename_dialog.first_upper = Першая літара вялікая\nbatch_rename_dialog.word = Першы ад кожнага слова\nbatch_rename_dialog.old_name = Старое імя\nbatch_rename_dialog.new_name = Новае імя\nbatch_rename_dialog.block_name = Без зьмяненьняў\nbatch_rename_dialog.range = Інтэрвал\nbatch_rename_dialog.proceed_renaming = Будуць зьменены назвы ў %1 файлаў з %2. Пачаць працэс?\nbatch_rename_dialog.duplicate_names = Супадзеньне імёнаў!\nbatch_rename_dialog.names_conflict = Канфлікт імёнаў! Некаторыя значэньні супадаюць у старых і новых імёнах.\nparent_folders_quick_list.empty_message = Бягучае месцазнаходжаньне ня мае бацькоўскага\nrecent_locations_quick_list.empty_message = Не знойдзена апошніх месцазнаходжаньняў\nrecent_executed_files_quick_list.empty_message = Не знойдзена апошніх выкарыстаных файлаў\n"
  },
  {
    "path": "src/main/resources/dictionary_ca_ES.properties",
    "content": "ok = Accepta\nyes = Sí\nno = No\ncancel = Cancel·la\nedit = Edita\nclose = Tanca\nreset = Reinicia\nrename = Reanomena\napply = Aplica\nchange = Canvia\nsave = Desa\ndont_save = No desis\nreplace = Substitueix\ndont_replace = No substitueixis\ndelete = Esborra\nskip = Salta\nskip_all = Omet-los tots\nretry = Reintenta\nresume = Continua\noverwrite = Sobreescriu\noverwrite_if_older = Sobreescriu si és més antic\nduplicate = Duplica\napply_to_all = Aplica a tot\ncopy = Copia\nmove = Mou\npack = Comprimeix\nunpack = Descomprimeix\ndownload = Descarrega\nsplit = Divideix\ncombine = Combina\nbrowse = Navega\nask = Pregunta\nstop = Para\npause = Pausa\nquick_search = Cerca ràpida\nfile_manager = Gestor de fitxers\ncreate = Crea\ncreating_file = Creant %1\nchoose = Selecciona\ncustomize = Personalitza\nchoose_folder = Selecciona un directori\nlogin = Identificació\npassword = Contrasenya\nuser = Usuari\nencoding = Codificació\npreferred_encodings = Codificacions preferides\nlicense = Llicència\nname = Nom\nsize = Mida\ndate = Data\nextension = Extensió\npermissions = Permisos\nowner = Propietari\ngroup = Grup\nlocation = Localització\nuntitled = Sense nom\nsource = Origen\ndestination = Destí\nrecurse_directories = Processar els directoris seleccionats recursivament\ngo_to = Ves a\nexample = Exemple\npreview = Previsualitza\ncomment = Comenta\nsample_text = Text d'exemple\nnb_files = %1 fitxer(s)\nnb_folders = %1 directori(s)\nloading = Carregant...\nthis_operation_cannot_be_undone = Aquesta operació no es pot desfer\nremove = Esborrar\ndetails = Detalls\nwarning = Atenció\nerror = Error\ngeneric_error = S'ha produit un error mentre es realitzava l'operació sol·licitada\nfolder_does_not_exist = El directori no existeix o no està disponible\nthis_folder_does_not_exist = Aquest directori no existeix o no està disponible\nthis_file_does_not_exist = Aquest fitxer no existeix o no està disponible\ninvalid_path = Ruta incorrecta\ndirectory_already_exists = El directori %1 ja existeix\nfile_exists_in_destination = El fitxer ja existeix al destí\nsource_parent_of_destination = Intent de transferir un directori a un dels seus subdirectoris\ncannot_read_file = No es pot llegir el fitxer %1\ncannot_write_file = No es pot escriure al fitxer %1\ncannot_create_folder = No es pot crear el directori %1\ncannot_read_folder = No es poden llegir els continguts del directori %1\ncannot_delete_file = No es pot esborrar el fitxer %1\ncannot_delete_folder = No es pot esborrar el directori %1\nerror_while_transferring = Error transferint el fitxer %1\nsame_source_destination = Els directoris d'origen i destí són els mateixos\nfile_already_exists = %1 ja existeix, el vol substituir?\nwrite_error = Error d'escriptura\nread_error = Error de lectura\nintegrity_check_error = Comprovació d'integritat fallada\nstartup_error = Un error ha impedit iniciar l'trolCommander\npermissions.read = Lectura\npermissions.write = Escriptura\npermissions.executable = Executable\npermissions.group = Grup\npermissions.other = Altre\npermissions.octal_notation = Notació octal\naction_categories.all = Tots\naction_categories.navigation = Navegació\naction_categories.selection = Selecció\naction_categories.view = Visualitza\naction_categories.file_operations = Operacions amb fitxers\naction_categories.windows = Finestres\nTerminal.label = Terminal\nTerminal.tooltip = Terminal\nFindFile.label = Find file\nFindFile.tooltip = Find file\nAddBookmark.label = Afegir preferit\nAddBookmark.tooltip = Afegir el directori actual als preferits\nBatchRename.label = Reanomena en bloc\nEditBookmarks.label = Edita els preferits\nExploreBookmarks.label = Explora els preferits\nEditCredentials.label = Edita les credencials\nChangeLocation.label = Canvia la localització actual\nChangeDate.label = Canvia la data\nChangeDate.tooltip = Canvia la data del(s) fitxer(s) seleccionat(s)\nChangePermissions.label = Canvia els permisos\nChangePermissions.tooltip = Canvia els permisos del(s) fitxer(s) seleccionat(s)\nCheckForUpdates.label = Comprova les actualitzacions\nCompareFolders.label = Compara directoris\nConnectToServer.label = Connecta't a un servidor\nConnectToServer.tooltip = Connecta't a un servidor remot\nView.label = Visualitza\nInternalView.label = Visualitza (intern)\nView.tooltip = Visualitza el fitxer seleccionat\nInternalEdit.label = Edita (intern)\nEdit.tooltip = Edita el fitxer seleccionat\nCopy.tooltip = Copia els fitxers seleccionats\nLocalCopy.label = Còpia local\nLocalCopy.tooltip = Copia el fitxer seleccionat al directori actual\nMove.tooltip = Mou els fitxers seleccionats\nRename.tooltip = Reanomena els fitxers seleccionats\nMkdir.label = Crea un directori\nMkdir.tooltip = Crea un subdirectori al subdirectori actual\nMkfile.label = Crea un fitxer\nMkfile.tooltip = Crea un fitxer al directori actual\nDelete.tooltip = Esborra els fitxers seleccionats\nPermanentDelete.label = Esborra permanentment\nPermanentDelete.tooltip = Esborra els fitxers seleccionats sense utilitzar la paperera de reciclatge\nRefresh.label = Actualitza\nRefresh.tooltip = Actualitza el directori actual\nCloseWindow.label = Tanca la finestra\nCloseWindow.tooltip = Tanca aquesta finestra\nCopyFileNames.label = Copia el(s) nom(s)\nCopyFilePaths.label = Copia la(es) ruta(es)\nCopyFilesToClipboard.label = Copia el(s) fitxer(s)\nPasteClipboardFiles.label = Enganxa el(s) fitxer(s)\nEmail.label = Envia per correu electrònic\nEmail.tooltip = Envia els fitxers seleccionats en un correu electrònic\nGoBack.label = Ves enrere\nGoBack.tooltip = Ves al directori anterior\nGoForward.label = Ves endavant\nGoForward.tooltip = Ves al directori següent\nGoToHome.label = Ves al directori d'inici\nGoToParent.label = Ves al directori pare\nGoToParent.tooltip = Ves al directori pare\nGoToParentInOtherPanel.label = Ves al directori pare en un altre tauler\nGoToParentInBothPanels.label = Ves al directori pare a ambdós taulers\nGoToRoot.label = Ves a l'arrel\nSortByName.label = Ordena per nom\nSortByDate.label = Ordena per data\nSortBySize.label = Ordena per mida\nSortByExtension.label = Ordena per extensió\nSortByPermissions.label = Ordena per permisos\nSortByOwner.label = Ordena per propietari\nSortByGroup.label = Ordena per grup\nMarkGroup.label = Selecciona fitxers\nMarkGroup.tooltip = Selecciona un grup de fitxers\nUnmarkGroup.label = Deselecciona fitxers\nUnmarkGroup.tooltip = Deselecciona un gruup de fitxers\nMarkAll.label = Selecciona'ls tots\nUnmarkAll.label = Deselecciona'ls tots\nMarkSelectedFile.label = Selecciona/Deselecciona\nMarkSelectedFile.tooltip = Selecciona/Deselecciona el fitxer actual\nMarkNextBlock.label = Selecciona un bloc\nMarkPreviousBlock.label = Selecciona un bloc\nMarkNextRow.label = Selecciona una fila cap avall\nMarkPreviousRow.label = Selecciona una fila cap amunt\nMarkNextPage.label = Selecciona una pàgina amunt\nMarkPreviousPage.label = Selecciona una pàgina avall\nMarkToFirstRow.label = Selecciona els fitxers des del començament\nMarkToLastRow.label = Selecciona els fitxers fins al final\nMarkExtension.label = Selecciona les extensions\nInvertSelection.label = Inverteix la selecció\nSwapFolders.label = Intercanvia els directoris\nSwapFolders.tooltip = Intercanvia els directoris de l'esquerra i de la dreta\nSetSameFolder.label = Mostra el mateix directori\nSetSameFolder.tooltip = Mostra el mateix directori als taulers de l'esquerra i de la dreta\nNewWindow.label = Nova finestra\nNewWindow.tooltip = Obre una nova finestra\nOpen.label = Obre\nOpen.tooltip = Entra al directori / Entra al fitxer / Executa\nOpenNatively.label = Obre nativament\nOpenNatively.tooltip = Executa el fitxer seleccionat amb el programa associat al sistema\nOpenInOtherPanel.label = Obre en a l'altre tauler\nOpenInBothPanels.label = Obre a ambdós taulers\nRevealInDesktop.label = Mostra a %1\nRunCommand.label = Executa una comanda\nRunCommand.tooltip = Executa una comanda en el directori actual\nPack.tooltip = Comprimeix els fitxers seleccionats en un fitxer\nUnpack.tooltip = Descomprimeix el fitxer seleccionat\nShowFileProperties.label = Propietats\nShowFileProperties.tooltip = Mostra les propietats dels fitxers seleccionats\nShowPreferences.label = Preferències\nShowPreferences.tooltip = Configura trolCommander\nShowServerConnections.label = Mostra les connexions obertes\nQuit.label = Surt\nReverseSortOrder.label = Inverteix l'ordre\nToggleAutoSize.label = Autodimensiona les columnes\nStop.label = Atura el canvi de directori\nToggleColumn.show = Mostra la columna %1\nToggleColumn.hide = Amaga la columna %1\nToggleCommandBar.show = Mostra la barra de comandes\nToggleCommandBar.hide = Amaga la barra de comandes\nToggleToolBar.show = Mostra la barra d'eines\nToggleToolBar.hide = Amaga la barra d'eines\nCustomizeCommandBar.label = Personalitza la barra de comandes\nToggleStatusBar.show = Mostra la barra d'estat\nToggleStatusBar.hide = Amaga la barra d'estat\nToggleShowFoldersFirst.label = Mostra els directoris primer\nToggleTree.label = Mostra l'arbre\nPopupLeftDriveButton.label = Canvia el directori esquerre\nPopupRightDriveButton.label = Canvia el directori dret\nRecallPreviousWindow.label = Ves a la finestra anterior\nRecallNextWindow.label = Ves a la finestra següent\nRecallWindow.label = Ves a la finestra %1\nBringAllToFront.label = Mostra-ho tot\nSwitchActiveTable.label = Intercanvia els taulers esquerre i dret\nSelectNextBlock.label = Salta un bloc avall\nSelectPreviousBlock.label = Salta un bloc amunt\nSelectNextPage.label = Salta una pàgina avall\nSelectPreviousPage.label = Salta una pàgina amunt\nSelectNextRow.label = Salta una fila avall\nSelectPreviousRow.label = Salta una fila amunt\nSelectFirstRow.label = Selecciona el primer fitxer del directori actual\nSelectLastRow.label = Selecciona l'últim fitxer del directori actual\nSplitEqually.label = Divideix equitativament\nSplitVertically.label = Divideix verticalment\nSplitHorizontally.label = Divideix horitzontalment\nShowKeyboardShortcuts.label = Dreceres de teclat\nGoToWebsite.label = Ves al lloc web\nGoToForums.label = Ves als fòrums\nReportBug.label = Informa d'un error\nDonate.label = Fes una donació\nShowAbout.label = Sobre trolCommander\nOpenTrash.label = Obre la paperera\nEmptyTrash.label = Buida la paperera\nCalculateChecksum.label = Calcula el checksum\nMaximizeWindow.label = Maximitza\nMaximizeWindow.label.mac_os_x = Zoom\nMinimizeWindow.label = Minimitza\nMinimizeWindow.label.mac_os_x = \nGoToDocumentation.label = Documentació en línia\nShowParentFoldersQL.label = Directoris pare\nShowRecentLocationsQL.label = Localitzacions recents\nShowRecentExecutedFilesQL.label = Fitxers executats recentment\nSplitFile.tooltip = Divideix un fitxer en múltiples parts\nCombineFiles.tooltip = Torna a crear un fitxer dividit en múltiples parts\nShowDebugConsole.label = Consola de depuració\nFocusPrevious.label = Marca el component anterior\nFocusNext.label = Marca el component següent\nfile_menu = Fitxer\nfile_menu.open_with = Obre amb\nmark_menu = Selecciona\nview_menu = Visualitza\nview_menu.show_hide_columns = Mostra/Amaga columnes\ngo_menu = Ves\nbookmarks_menu = Preferits\nbookmarks_menu.no_bookmark = No hi ha cap preferit\ndrive_popup.network_shares = Xarxa compartida\nquick_lists_menu = Llistes ràpides\nwindow_menu = Finestra\nhelp_menu = Ajuda\nstatus_bar.selected_files = %1 de %2 seleccionats\nstatus_bar.connecting_to_folder = Connectant al directori, prem ESCAPE per a cancel·lar\nstatus_bar.volume_free = Lliure\nstatus_bar.volume_capacity = Capacitat\nshortcuts_panel.title = Dreceres\nshortcuts_panel.restore_defaults = Restaura per defecte\nshortcuts_panel.show = Mostra\nshortcuts_panel.default_message = Prem Enter o fes doble clic sobre la drecera per editar\nshortcuts_table.action_description = Descripció de l'acció\nshortcuts_table.shortcut = Drecera\nshortcuts_table.alternate_shortcut = Drecera alternativa\nshortcuts_table.type_in_a_shortcut = Escriu una drecera\ncommand_bar_dialog.help = Arrossega els botons per personalitzar la barra de comandes\ntable.folder_access_error_title = Error en accedir al directori\ntable.folder_access_error = No s'ha pogut llegir el contingut del directori\ntable.download_or_browse = Vol explorar o descarregar aquest fitxer?\nversion_dialog.no_new_version_title = No hi ha una versió nova\nversion_dialog.no_new_version = Felicitats, ja té l'última versió\nversion_dialog.new_version_title = Nova versió disponible\nversion_dialog.new_version = Una nova versió d'trolCommander està disponible\nversion_dialog.new_version_url = Una nova versió d'trolCommander està disponible a %1\nversion_dialog.not_available_title = No s'ha pogut accedir al servidor\nversion_dialog.not_available = No s'ha pogut obtenir la informació del servidor\nversion_dialog.install_and_restart = Instal·la i reinicia\nversion_dialog.preparing_for_update = Preparant l'actualització...\nquit_dialog.title = Surt de l'trolCommander\nquit_dialog.desc = Té %1 finestra(es) oberta(es). N'està segur?\nquit_dialog.show_next_time = Mostra la propera vegada\ndestination_dialog.file_exists_action = Acció per defecte quan el fitxer existeix\ndestination_dialog.verify_integrity = Comprova la integritat de les dades\ndestination_dialog.skip_errors = Salta els errors\nfile_collision_dialog.title = Col·lisió de fitxers\nrename_dialog.new_name = Nou nom\ncopy_dialog.destination = Copia el(s) fitxer(s) seleccionat(s) a\ncopy_dialog.error_title = Error en copiar\ncopy_dialog.copying = Copiant fitxers\ncopy_dialog.copying_file = Copiant %1\npack_dialog.packing = Comprimint\npack_dialog.packing_file = Comprimint %1\npack_dialog.error_title = Error de compressió\npack_dialog_description = Afegeix els fitxers seleccionats a\npack_dialog.archive_format = Format del fitxer\nunpack_dialog.destination = Descomprimeix el(s) fitxer(s) seleccionat(s) a\nunpack_dialog.error_title = Error en descomprimir\nunpack_dialog.unpacking = Descomprimint\nunpack_dialog.unpacking_file = Descomprimint %1\noptimizing_archive = Optimitzant el fitxer %1\nerror_while_optimizing_archive = Error en comprimir el fitxer %1\nmove_dialog.move_description = Mou a\nmove_dialog.error_title = Error en moure\nmove_dialog.moving = Movent els fitxers\nmove_dialog.moving_file = Movent %1\ndownload_dialog.description = Baixar fitxer a\ndownload_dialog.error_title = Error de baixada\ndownload_dialog.downloading = Baixant\ndownload_dialog.downloading_file = Baixant %1\nmkfile_dialog.allocate_space = Mida\ndelete_dialog.permanently_delete.confirmation = Voleu eliminar permanentment el/s fitxer/s seleccionat/s?\ndelete_dialog.move_to_trash.confirmation = Eliminar fitxer/s seleccionats?\ndelete_dialog.move_to_trash.confirmation_details = Els fitxers s'enviaran a la paperera.\ndelete_dialog.move_to_trash.option = Enviar a la paperera\ndelete_dialog.move_to_trash.failed = Algun/s fitxer/s no s'han pogut enviar a la paperera.\ndelete_dialog.deleting = Eliminant\ndelete_dialog.error_title = Error eliminant\ndelete.deleting_file = Eliminant %1\nemail_dialog.prefs_not_set_title = Correu electrònic sense configurar\nemail_dialog.prefs_not_set = Primer heu de configurar el correu electrònic.\nemail_dialog.from = De\nemail_dialog.to = Per\nemail_dialog.subject = Assumpte\nemail_dialog.send = Enviar\nemail_dialog.error_title = Error enviant\nemail_dialog.read_error = No es poden llegir els fitxers dels dubdirectoris.\nemail_dialog.sending = Enviant fitxers\nemail.sending_file = Enviant %1\nemail.connecting_to_server = Connectant amb %1\nemail.server_unavailable = No es pot connectar amb el servidor %1, verifiqueu la vostra configuració de correu electrònic o intenteu-ho més tard.\nemail.connection_closed = Connexió tancada pel servidor, el missatge no s'ha enviat.\nemail.goodbye_failed = Error tancant la conneció, el missatge no s'ha enviat.\nemail.send_file_error = No s'ha pogut enviar el fitxer %1, el missatge no s'ha enviat.\nsplit_file_dialog.error_title = Error en partir el fitxer\nsplit_file_dialog.file_to_split = Fitxer a trossejar\nsplit_file_dialog.target_directory = Directori de destí\nsplit_file_dialog.part_size = Mida de cada tros\nsplit_file_dialog.parts = Nombre de parts\nsplit_file_dialog.generate_CRC = Generar fitxer CRC\nsplit_file_dialog.max_parts = El màxim nombre de parts permés és %1\nsplit_file_dialog.auto = Automàtic\nsplit_file_dialog.insert_new_media = Inserteu un nou suport\ncombine_files_dialog.error_title = Error combinant fitxer\ncombine_files_job.no_crc_file = Combinació finalitzada. No s'ha creat fitxer CRC.\ncombine_files_job.crc_read_error = Error llegint fitxer CRC.\ncombine_files_job.crc_check_failed = Desajust en CRC\ncombine_files_job.crc_ok = Combinació finalitzada. Suma de verificació de CRC correcta.\nfile_selection_dialog.mark = Marca\nfile_selection_dialog.unmark = Desmarca\nfile_selection_dialog.mark_description = Marca fitxers amb el nom\nfile_selection_dialog.unmark_description = Desmarca fitxers amb el nom\nfile_selection_dialog.case_sensitive = Diferencia Majúscules / minúscules\nfile_selection_dialog.include_folders = Inclou directoris\nprogress_dialog.starting = Iniciant la transferència...\nprogress_dialog.transferred = %1 transferits a %2\nprogress_dialog.elapsed_time = Temps transcorregut\nprogress_dialog.advanced = Avançades\nprogress_dialog.current_speed = Velocitat actual\nprogress_dialog.limit_speed = Limitar la velocitat\nprogress_dialog.close_when_finished = Tancar la finestra en acabar\nprogress_dialog.processing_files = Processant fitxers\nprogress_dialog.processing_file = Processant %1\nprogress_dialog.verifying_file = Verificant %1\nprogress_dialog.job_finished = Tasca finalitzada\nprogress_dialog.job_error = Error en tasca\nproperties_dialog.file_properties = %1 Propietats\nproperties_dialog.contents = Contingut\nproperties_dialog.calculating = Calculant...\ncalculate_checksum_dialog.checksum_algorithm = Algorisme de suma de verificació\ncalculate_checksum_dialog.temporary_file = Fitxer temporal\nchange_date_dialog.now = Ara\nchange_date_dialog.specific_date = Data concreta\nrun_dialog.run_command_description = Executar en el directori actual\nrun_dialog.run_in_home_description = Executar en el directori del usuari\nrun_dialog.command_output = Sortida de l'ordre\nrun_dialog.run = Executar\nrun_dialog.clear_history = Buidar l'històric\nserver_connect_dialog.server_type = Tipus de connexió\nserver_connect_dialog.server = Servidor\nserver_connect_dialog.share = Compartir\nserver_connect_dialog.domain = Domini\nserver_connect_dialog.username = Nom d'usuari\nserver_connect_dialog.initial_dir = Directori d'inici\nserver_connect_dialog.port = Port\nserver_connect_dialog.server_url = URL del servidor\nserver_connect_dialog.http_url = URL de la pàgina web\nserver_connect_dialog.connect = Connectar\nserver_connect_dialog.protocol = Protocol\nserver_connect_dialog.nfs_version = Versió de NFS\nserver_connect_dialog.private_key = Clau privada\nserver_connect_dialog.passphrase = Contrasenya\nftp_connect.passive_mode = Activar en mode passiu\nftp_connect.anonymous_user = Usuari anònim\nftp_connect.nb_connection_retries = Nombre de intents de connexió\nftp_connect.retry_delay = Retard entre intents (en segons)\nhttp_connect.basic_authentication = Autenticació bàsica d'HTTP (opcional)\nserver_connections_dialog.disconnect = Desconnecta\nserver_connections_dialog.connection_busy = Ocupat\nserver_connections_dialog.connection_idle = Inactiu\nbonjour.bonjour_services = Serveis Bonjour\nbonjour.no_service_discovered = No s'han trobat serveis\nbonjour.bonjour_disabled = Bonjour deshabilitat\nauth_dialog.title = Autenticació\nauth_dialog.desc = Entreu un nom de usuari i una contrasenya\nauth_dialog.server = Servidor\nauth_dialog.store_credentials = Emmagatzema nom de usuari i contrasenya (encriptació feble)\nauth_dialog.connect_as = Connecta com\nauth_dialog.authentication_failed = Error autenticant\nsortable_list.move_up = Puja\nsortable_list.move_down = Baixa\nadd_bookmark_dialog.add = Afegeix\nedit_bookmarks_dialog.new = Nou\nfile_viewer.view_error_title = Error de visualització\nfile_viewer.view_error = No es pot visualitzar el fitxer\nfile_viewer.file_menu = Fitxer\nfile_viewer.close = Tanca\nfile_viewer.large_file_warning = Fitxer molt llarg; pot donar problemes durant la operació\nfile_viewer.open_anyway = Obrir igualment\ntext_viewer.edit = Edita\ntext_viewer.copy = Còpia\ntext_viewer.select_all = Selecciona-ho tot\ntext_viewer.find = Busca\ntext_viewer.find_next = Busca el següent\ntext_viewer.find_previous = Busca l'anterior\ntext_viewer.view = Vista\ntext_viewer.line_wrap = Ajusta el text\ntext_viewer.line_numbers = Numera línies\ntext_viewer.binary_file_warning = El fitxer sembla binari\nimage_viewer.controls_menu = Controls\nimage_viewer.zoom_in = Apropar\nimage_viewer.zoom_out = Allunyar\nfile_editor.edit_error_title = Error d'edició\nfile_editor.edit_error = No es pot editar l'arxiu.\nfile_editor.save = Desa\nfile_editor.save_as = Anomena i desa...\nfile_editor.save_warning = Vols desar els canvis fets abans de tancar el fitxer?\nfile_editor.cannot_write = No es pot escriure el fitxer\ntext_editor.cut = Retalla\ntext_editor.paste = Enganxa\nshortcuts_dialog.quick_search.start_search = Premeu qualsevol tecla per iniciar una cerca ràpida\nshortcuts_dialog.quick_search.cancel_search = Cancelar cerca ràpida\nshortcuts_dialog.quick_search.remove_last_char = Eliminar l'últim caràter de la cadena de la cerca ràpida\nshortcuts_dialog.quick_search.jump_to_previous = Anar al anterior resultat de la cerca ràpida\nshortcuts_dialog.quick_search.jump_to_next = Anar al resultat següent de la cerca ràpida\nshortcuts_dialog.quick_search.mark_jump_next = Marca/Desmarca fitxer actual i passa al següent resultat de la cerca\ntheme_editor.title = Editor de temes\ntheme_editor.folder_tab = Subfinestra de directori\ntheme_editor.shell_tab = intèrpret d'ordres\ntheme_editor.shell_history_tab = Historial del intèrpret d'ordres\ntheme_editor.statusbar_tab = Barra d'estat\ntheme_editor.free_space = Espai lliure\ntheme_editor.free_space.ok = D'acord\ntheme_editor.free_space.warning = Avís\ntheme_editor.free_space.critical = Crític\ntheme_editor.locationbar_tab = Barra de localització\ntheme_editor.editor_tab = Editor de fitxers\ntheme_editor.font = Tipus de lletra\ntheme_editor.active_panel = Actiu\ntheme_editor.inactive_panel = Inactiu\ntheme_editor.general = General\ntheme_editor.could_not_save_theme = No es pot escriure el tema %1\ntheme_editor.border = Vora\ntheme_editor.background = Fons\ntheme_editor.alternate_background = Alternar fons\ntheme_editor.unfocused_background = Fons (sense focus)\ntheme_editor.quick_search.unmatched_file = Fitxer sense coincidència\ntheme_editor.text = Text\ntheme_editor.progress = Realitzat\ntheme_editor.normal = Normal\ntheme_editor.normal_unfocused = Normal (sense focus)\ntheme_editor.selected = Seleccionat\ntheme_editor.selected_unfocused = Seleccionat (sense focus)\ntheme_editor.color = Color\ntheme_editor.colors = Colors\ntheme_editor.plain_file = Fitxer text net\ntheme_editor.marked_file = Fitxer marcat\ntheme_editor.hidden_file = Fitxer ocult\ntheme_editor.folder = Directori\ntheme_editor.archive_file = Arxiva fitxer\ntheme_editor.symbolic_link = Enllaç simbòlic\ntheme_editor.header = Capçalera\ntheme_editor.item = Element\ncommand_bar_customize_dialog.available_actions = Accions disponibles\ncommand_bar_customize_dialog.modifier = Modificador\nprefs_dialog.title = Preferències\nprefs_dialog.general_tab = General\nprefs_dialog.day = Dia\nprefs_dialog.month = Mes\nprefs_dialog.year = Any\nprefs_dialog.language = Idioma (s'haurà de reiniciar)\nprefs_dialog.date_time = Format horari i de data\nprefs_dialog.time = Hora\nprefs_dialog.date = Data\nprefs_dialog.date_separator = Separador\nprefs_dialog.time_12_hour = Format 12 hores\nprefs_dialog.time_24_hour = Format 24 hores\nprefs_dialog.show_seconds = Mostrar segons\nprefs_dialog.show_century = Mostrar segle\nprefs_dialog.check_for_updates_on_startup = Buscar actualitzacions al iniciar\nprefs_dialog.show_splash_screen = Mostrar pantalla de presentació\nprefs_dialog.folders_tab = Directoris\nprefs_dialog.startup_folders = Directoris de inici\nprefs_dialog.left_folder = Directori esquerra\nprefs_dialog.right_folder = Directori dret\nprefs_dialog.last_folder = Últim directori visitat\nprefs_dialog.custom_folder = Directori personalitzat\nprefs_dialog.show_hidden_files = Mostrar fitxers ocults\nprefs_dialog.show_ds_store_files = Mostrar fitxers .DS_Store\nprefs_dialog.show_system_folders = Mostrar directoris de sistema\nprefs_dialog.compact_file_size = Arrodonir les mides dels fitxers mostrades\nprefs_dialog.follow_symlinks_when_cd = Seguir els enllaços simbòlics en canviar el directori actual\nprefs_dialog.appearance_tab = Aspecte\nprefs_dialog.look_and_feel = Look & Feel\nprefs_dialog.icons_size = Mida de les icones\nprefs_dialog.toolbar_icons = Barra d'eines\nprefs_dialog.command_bar_icons = Barra de comendes\nprefs_dialog.file_icons = Tipus de fitxers\nprefs_dialog.use_system_file_icons = Utilitzar les icones del sistema de fitxers\nprefs_dialog.use_system_file_icons.always = Sempre\nprefs_dialog.use_system_file_icons.never = Mai\nprefs_dialog.use_system_file_icons.applications = Només per programes\nprefs_dialog.edit_current_theme = Edita el tema actual...\nprefs_dialog.themes = Temes\nprefs_dialog.import_theme = Importa tema\nprefs_dialog.import_look_and_feel = Importa Look & Feel\nprefs_dialog.no_look_and_feel = No s'ha trobat cap Look & Feel.\nprefs_dialog.error_in_import = Error important el tema %1\nprefs_dialog.cannot_read_theme = No s'ha pogut llegir el tema del fitxer %1\nprefs_dialog.export_theme = Exporta %1\nprefs_dialog.import = Importa\nprefs_dialog.export = Exporta\nprefs_dialog.theme_type = Tipus\nprefs_dialog.delete_theme = Eliminar el tema %1 permanentment?\nprefs_dialog.delete_look_and_feel = Eliminar el Look & Feel %1 permanentment?\nprefs_dialog.rename_failed = Error renombrant el tema %1\nprefs_dialog.xml_file = Fitxer XML\nprefs_dialog.jar_file = Fitxer JAR\nprefs_dialog.mail_tab = Correu electrònic\nprefs_dialog.mail_settings = Configuració del correu de sortida\nprefs_dialog.mail_name = El vostre nom\nprefs_dialog.mail_address = El vostre correu electrònic\nprefs_dialog.mail_server = Servidor SMTP\nprefs_dialog.misc_tab = Varis\nprefs_dialog.use_brushed_metal = Utilitza l'aspecte 'raspatllat metàl·lic' (s'ha de reiniciar)\nprefs_dialog.confirm_on_quit = Demanar confirmació en sortir\nprefs_dialog.default_shell = Utilitzar l'intèrpret d'ordres del sistema\nprefs_dialog.custom_shell = Utilitzar un intèrpret d'ordres específic\nprefs_dialog.shell_encoding = Codificació del interpret d'ordres\nprefs_dialog.auto_detect_shell_encoding = Detecció automàtica\nprefs_dialog.enable_bonjour_discovery = Activar cerca de Serveis Bonjour\nprefs_dialog.enable_system_notifications = Activar les notificacions del sistema\ndebug_console_dialog.level = Nivell\nunit.byte = byte\nunit.bytes = bytes\nunit.bytes_short = b\nunit.kb = kB\nunit.mb = Mb\nunit.gb = Gb\nunit.tb = Tb\nunit.speed = %1/s\nduration.seconds = %1s\nduration.minutes = %1m\nduration.hours = %1h\nduration.days = %1d\nduration.months = %1me\nduration.years = %1a\ntheme.custom_theme = Tema personalitzat\ntheme.custom = Personalitzat\ntheme.built_in = Integrat\ntheme.add_on = Complement\ntheme.current = actual\ntheme_could_not_be_loaded = S'ha produït un error en carregar aquest tema.\nsetup.title = Benvinguts a trolCommander\nsetup.intro = Trieu el comportament desitjat de trolCommander.\nsetup.look_and_feel = Seleccioneu el vostre Look & Feel\nsetup.theme = Seleccioneu el vostre tema\nfont_chooser.font_size = Mida\nfont_chooser.font_bold = Negreta\nfont_chooser.font_italic = Cursiva\ncolor_chooser.red = Vermell\ncolor_chooser.green = Verd\ncolor_chooser.blue = Blau\ncolor_chooser.hue = To\ncolor_chooser.brightness = Brillantor\ncolor_chooser.swatches = Mostres\ncolor_chooser.saturation = Saturació\ncolor_chooser.rgb = RGB\ncolor_chooser.hsb = HSB\ncolor_chooser.recent = Recent\ncolor_chooser.alpha = Transparència alpha\ncolor_chooser.title = Seleccioneu un color\nbatch_rename_dialog.mask = Renombra patró\nbatch_rename_dialog.search_replace = Cerca i reemplaça\nbatch_rename_dialog.search_for = Cerca\nbatch_rename_dialog.replace_with = Reemplaça amb\nbatch_rename_dialog.counter = Comptador\nbatch_rename_dialog.start_at = Comença a\nbatch_rename_dialog.step_by = Salta'n\nbatch_rename_dialog.format = Format\nbatch_rename_dialog.upper_lower_case = Majúscules/Minúscules\nbatch_rename_dialog.no_change = Sense canvis\nbatch_rename_dialog.lower_case = minúscules\nbatch_rename_dialog.upper_case = MAJÚSCULES\nbatch_rename_dialog.first_upper = Primera lletra en majúscula\nbatch_rename_dialog.word = Primera Lletra De Cada Paraula\nbatch_rename_dialog.old_name = Nom antic\nbatch_rename_dialog.new_name = Nom nou\nbatch_rename_dialog.block_name = Protegeix\nbatch_rename_dialog.range = Abast\nbatch_rename_dialog.proceed_renaming = Es canviarà el nom de %1 fitxers de %2. Vols continuar?\nbatch_rename_dialog.duplicate_names = Noms duplicats!\nbatch_rename_dialog.names_conflict = Conflicte de noms! Coïncideixen alguns noms antics i nous.\nparent_folders_quick_list.empty_message = L'emplaçament actual no te pare\nrecent_locations_quick_list.empty_message = No hi ha emplaçaments recents\nrecent_executed_files_quick_list.empty_message = No hi ha fitxers executats recentment\n"
  },
  {
    "path": "src/main/resources/dictionary_cs_CZ.properties",
    "content": "ok = OK\r\nyes = Ano\r\nno = Ne\r\ncancel = Zrušit\r\nedit = Upravit\r\nclose = Zavřít\r\nreset = Resetovat\r\nrename = Přejmenovat\r\napply = Použít\r\nchange = Změnit\r\nsave = Uložit\r\ndont_save = Neukládat\r\nreplace = Nahradit\r\ndont_replace = Nenahrazovat\r\ndelete = Smazat\r\nskip = Přeskočit\r\nskip_all = Přeskočit vše\r\noverwrite_all = Přepsat vše\r\nretry = Opakovat\r\nresume = Pokračovat\r\noverwrite = Přepsat\r\noverwrite_if_older = Přepsat starší\r\nduplicate = Duplikovat\r\napply_to_all = Použít na všechny\r\ncopy = Kopírovat\r\nmove = Přesunout\r\npack = Komprimovat\r\nunpack = Dekomprimovat\r\ndownload = Stáhnout\r\nsplit = Rozdělit\r\ncombine = Sloučit\r\nbrowse = Procházet\r\nask = Dotázat se\r\nstop = Zastavit\r\npause = Pauza\r\nquick_search = Rychlé hledání\r\nfile_manager = Správce souborů\r\ncreate = Vytvořit\r\ncreating_file = Vytváří se %1\r\nchoose = Vybrat\r\ncustomize = Přizpůsobit\r\nclean = Vyčistit\r\nsearch = Vyhledávat\r\nchoose_folder = Vyberte složku\r\nlogin = Přihlašovací jméno\r\npassword = Heslo\r\nuser = Uživatel\r\nencoding = Kódování\r\npreferred_encodings = Předvolená kódování\r\nlicense = Licence\r\nname = Jméno\r\nsize = Velikost\r\ndate = Datum\r\nextension = Přípona\r\npermissions = Oprávnění\r\nowner = Vlastník\r\ngroup = Skupina\r\nlocation = Umístění\r\nuntitled = Beze jména\r\nsource = Zdroj\r\ndestination = Cíl\r\nrecurse_directories = Použít i vnořené složky\r\ngo_to = Přejít na\r\nexample = Příklad\r\npreview = Náhled\r\ncomment = Komentář\r\nsample_text = Ukázkový text\r\nnb_files = %1x soubor\r\nnb_folders = %1x složka\r\nloading = Načítám obsah\\u2026\r\nthis_operation_cannot_be_undone = Tato operace je nevratná.\r\nremove = Odstranit\r\ndetails = Podrobnosti\r\ntitle = Název\r\nwarning = Upozornění\r\nerror = Chyba\r\nfiles=soubor(y)\r\ngeneric_error = Při provádění požadované operace se vyskytla chyba.\r\nfolder_does_not_exist = Tato složka neexistuje nebo není dostupná.\r\nthis_folder_does_not_exist = Tato složka neexistuje nebo není dostupná: %1\r\nthis_file_does_not_exist = Tento soubor neexistuje nebo není dostupný: %1\r\nfailed_to_read_folder=Na\\u010Dten\\u00ED t\\u00E9to slo\\u017Eky selhalo.\r\ninvalid_path = Neplatná cesta: %1\r\ndirectory_already_exists = Složka %1 již existuje.\r\nfile_exists_in_destination = Soubor již v cíli existuje\r\nsource_parent_of_destination = Pokus o přesun složky do jedné z jejích podsložek\r\ncannot_read_file = Nelze přečíst soubor %1\r\ncannot_write_file = Nelze zapsat soubor %1\r\noverwrite_readonly_file = Soubor je pouze pro čtení: %1\r\ncannot_create_folder = Nelze vytvořit složku %1\r\ncannot_read_folder = Nelze přečíst obsah složky %1\r\ncannot_delete_file = Nelze smazat soubor %1\r\ncannot_delete_folder = Nelze smazat složku %1\r\nerror_while_transferring = Chyba při přenosu souboru %1\r\nerror_unsupported_operation=Operace nen\\u00ED podporov\\u00E1na\r\nsame_source_destination = Zdojová a cílová složka se shodují\r\nfile_already_exists = %1 již existuje, chcete jej přepsat?\r\nwrite_error = Chyba zápisu\r\nread_error = Chyba při čtení\r\nintegrity_check_error = Kontrola integrity se nezdařila: zdroj a cíl se neshodují\r\nstartup_error = Vyskytla se chyba bránící spuštění aplikace trolCommander.\r\nimage_size = Velikost obrázku\r\ncannot_write_symlink = Nelze vytvořit symlink %1\r\ncannot_write_symlink_already_exists = Symlink již existuje: %1\r\ncannot_write_symlink_access_denied = Symlink nelze uložit %1, přístup zamítnut\r\npermissions.read = Čtení\r\npermissions.write = Zápis\r\npermissions.executable = Spouštění\r\npermissions.user = Uživatel\r\npermissions.group = Skupina\r\npermissions.other = Ostatní\r\npermissions.octal_notation = Zápis v osmičkové soustavě\r\naction_categories.all = Vše\r\naction_categories.navigation = Navigace\r\naction_categories.selection = Výběr\r\naction_categories.view = Zobrazit\r\naction_categories.file_operations = Operace se soubory\r\naction_categories.windows = Okna\r\naction_categories.tabs = Panely \r\naction_categories.commands = Vlastní příkazy\r\naction_categories.misc = Ostatní\r\nTerminal.label = Terminál\r\nTerminal.tooltip = Spustit okno terminálu v aktuální složce\r\nTerminalPanel.label = Terminál\r\nTerminalPanel.tooltip = Zobrazit/skrýt panel terminálu\r\nFindFile.label = Hledat soubor\r\nFindFile.tooltip = Hledat soubor\r\nAddBookmark.label = Přidat záložku\r\nAddBookmark.tooltip = Přidat stávající složku na seznam záložek\r\nBatchRename.label = Dávkové přejmenování\r\nEditBookmarks.label = Upravit záložky\r\nExploreBookmarks.label = Procházet záložky\r\nEditCredentials.label = Upravit účty\r\nChangeLocation.label = Změnit stávající umístění\r\nChangeDate.label = Změnit datum\r\nChangeDate.tooltip = Změnit datum vybraných souborů\r\nChangePermissions.label = Změnit oprávnění\r\nChangePermissions.tooltip = Změnit oprávnění u vybraných souborů\r\nCheckForUpdates.label = Zkontrolovat aktualizace\r\nCompareFolders.label = Porovnat složky\r\nCompareFolderFiles.label=Porovnat soubory\r\nCompareFolderFiles.tooltip=Porovnat soubory a ozna\\u010Dit zm\\u011Bn\\u011Bn\\u00E9 v aktu\\u00E1ln\\u00ED slo\\u017Ece\r\nConnectToServer.label = Připojit se k serveru\r\nConnectToServer.tooltip = Připojit se k vzdálenému serveru\r\nTextEditorsList.label=Zobrazit seznam textov\\u00FDch editor\\u016F\r\nTextEditorsList.tooltip=Zobrazit seznam textov\\u00FDch editor\\u016F\r\nView.label = Zobrazit\r\nViewAs.label = Zobrazit jako\r\nInternalView.label = Zobrazit (interně)\r\nviewer_type.text = Textový soubor\r\nviewer_type.hex = Binární soubor\r\nviewer_type.image = Obrázek\r\nviewer_type.pdf = Dokument PDF\r\nviewer_type.audio = Audio soubor\r\nviewer_type.html = Dokument HTML\r\nView.tooltip = Zobrazit vybraný soubor\r\nEdit.label = Upravit\r\nInternalEdit.label = Upravit (interně)\r\nCalculator.label = Kalkulačka\r\nCreateSymlink.label = Vytvořit symlink\r\nLocateSymlink.label = Přejít na cíl symlinku\r\nShowFoldersSize.label = Zobrazit velikost složek\r\nEditCommands.label = Upravit příkazy\r\nEditCommands.group.view = Prohlížeče\r\nEditCommands.group.edit = Editory\r\nEditCommands.group.others = Ostatní\r\nCompareFiles.label=Porovnat soubory\r\nCompareFiles.tooltip=Porovnat textov\\u00E9 soubory (FileMerge)\r\nEditCommands.new = Nový\r\nEditCommands.alias = Přezdívka\r\nEditCommands.command = Příkaz\r\nEditCommands.display_name = Zobrazený název\r\nEditCommands.filemask = Souborová maska\r\nsymboliclinkeditor.edit = Upravit symlink\r\nsymboliclinkeditor.create = Symbolický link\r\nsymboliclinkeditor.target_file_create = Existující soubor (soubor, na který odkazuje symlink)\r\nsymboliclinkeditor.target_file_edit = Symlink „%s“ odkazuje na\r\nsymboliclinkeditor.link_name = Soubor symbolického linku\r\nEdit.tooltip = Upravit vybraný soubor\r\nCopy.label = Kopírovat\r\nCopy.tooltip = Kopírovat označené soubory\r\nLocalCopy.label = Lokální kopie\r\nLocalCopy.tooltip = Kopírovat vybraný soubor do stávající složky\r\nMove.label = Přesunout\r\nMove.tooltip = Přesunout označené soubory\r\nRename.label = Přejmenovat\r\nRename.tooltip = Přejmenovat vybraný soubor\r\nMkdir.label = Vytvořit složku\r\nMkdir.tooltip = Vytvořit složku ve stávající\r\nMkfile.label = Vytvořit nový soubor\r\nMkfile.tooltip = Vytvořit soubor ve stávající složce\r\nDelete.label = Smazat\r\nDelete.tooltip = Je-li možné, označené přesunout do Koše\r\nPermanentDelete.label = Smazat přímo\r\nPermanentDelete.tooltip = Smazat označené soubory bez použití systémového Koše\r\nRefresh.label = Načíst znovu\r\nRefresh.tooltip = Načíst znovu stávající složku\r\nCloseWindow.label = Zavřít okno\r\nCloseWindow.tooltip = Zavřít stávající okno\r\nCopyFileNames.label = Kopírovat jméno(a)\r\nCopyFileBaseNames.label = Kopírovat název(názvy)\r\nCopyFilePaths.label = Kopírovat cestu(y)\r\nCopyFilesToClipboard.label = Kopírovat soubor(y)\r\nPasteClipboardFiles.label = Vložit soubor(y)\r\nEmail.label = Odeslat e-mailem\r\nEmail.tooltip = Odeslat označené soubory jako přílohu\r\nGoBack.label = Zpět\r\nGoBack.tooltip = Předchozí složka\r\nGoForward.label = Vpřed\r\nGoForward.tooltip = Následující složka\r\nGoToHome.label = Domovská složka\r\nGoToParent.label = O úroveň výš\r\nGoToParent.tooltip = Nadřazená složka\r\nGoToParentInOtherPanel.label = O úroveň výš ve druhém panelu\r\nGoToParentInBothPanels.label = O úroveň výš v obou panelech\r\nGoToRoot.label = Kořenová složka\r\nSortByName.label = Řadit dle jména\r\nSortByDate.label = Řadit dle datumu\r\nSortBySize.label = Řadit dle velikosti\r\nSortByExtension.label = Řadit dle přípony\r\nSortByPermissions.label = Řadit dle oprávnění\r\nSortByOwner.label = Řadit dle vlastníka\r\nSortByGroup.label = Řadit dle skupiny\r\nMarkGroup.label = Označit soubory\r\nMarkGroup.tooltip = Označit skupinu souborů\r\nUnmarkGroup.label = Odznačit soubory\r\nUnmarkGroup.tooltip = Odznačit skupinu souborů\r\nMarkAll.label = Označit vše\r\nUnmarkAll.label = Odznačit vše\r\nMarkSelectedFile.label = Označit/odznačit\r\nMarkSelectedFile.tooltip = Označit/odznačit vybraný soubor\r\nMarkNextBlock.label = Označit blok následujících 5 objektů\r\nMarkPreviousBlock.label = Označit blok předchozích 5 objektů\r\nMarkNextRow.label = Označit následující řádek\r\nMarkPreviousRow.label = Označit předchozí řádek\r\nMarkNextPage.label = Označit po následující stránku\r\nMarkPreviousPage.label = Označit po předchozí stránku\r\nMarkToFirstRow.label = Označit soubory do začátku\r\nMarkToLastRow.label = Označit soubory do konce\r\nMarkExtension.label = Označit příponu\r\nMarkEmpty.label=Ozna\\u010D pr\\u00E1zdn\\u00E9\r\nInvertSelection.label = Obrátit výběr\r\nSwapFolders.label = Prohodit složky\r\nSwapFolders.tooltip = Prohodit složky vlevo a vpravo\r\nSetSameFolder.label = Nastavit stejnou složku\r\nSetSameFolder.tooltip = Nastavit stejnou složku vlevo a vpravo\r\nToggleTableViewModeFull.label=Pln\\u00FD re\\u017Eim\r\nToggleTableViewModeFull.tooltip=P\\u0159epnout zobrazen\\u00ED v pln\\u00E9m re\\u017Eimu\r\nToggleTableViewModeCompact.label=Kompaktn\\u00ED re\\u017Eim\r\nToggleTableViewModeCompact.tooltip=P\\u0159epnout zobrazen\\u00ED v kompaktn\\u00EDm re\\u017Eimu\r\nToggleTableViewModeShort.label=Kr\\u00E1tk\\u00FD re\\u017Eim\r\nToggleTableViewModeShort.tooltip=P\\u0159epnout zobrazen\\u00ED v kr\\u00E1tk\\u00E9m re\\u017Eimu\r\nTogglePanelPreviewMode.label=Rychl\\u00FD re\\u017Eim\r\nTogglePanelPreviewMode.tooltip=P\\u0159epnout zobrazen\\u00ED v rychl\\u00E9m re\\u017Eimu\r\nNewWindow.label = Nové okno\r\nNewWindow.tooltip = Otevřít nové okno\r\nOpen.label = Otevřít\r\nOpen.tooltip = Otevřít složku / Otevřít archív / Spustit\r\nOpenNatively.label = Otevřít výchozí aplikací\r\nOpenNatively.tooltip = Otevřít vybraný soubor asociovanou aplikací\r\nOpenInNewTab.label = Otevřít v novém panelu\r\nOpenInOtherPanel.label = Otevřít ve druhém panelu\r\nOpenInBothPanels.label = Otevřít v obou panelech\r\nOpenLeftInRightPanel.label = Otevřít levý v právém panelu\r\nOpenRightInLeftPanel.label = Otevřít pravý v levém panelu\r\nRevealInDesktop.label = Otevřít v aplikaci %1\r\nRunCommand.label = Spustit příkaz\r\nRunCommand.tooltip = Spustit příkaz ve stávající složce\r\nPack.label = Komprimovat\r\nPack.tooltip = Komprimovat označené soubory do archívu\r\nUnpack.label = Dekomprimovat\r\nUnpack.tooltip = Dekomprimovat označené archívy\r\nShowFileProperties.label = Vlastnosti\r\nShowFileProperties.tooltip = Zobrazit vlastnosti označených souborů\r\nShowFilePopupMenu.label=Kontextov\\u00E1 nab\\u00EDdka\r\nShowFilePopupMenu.tooltip=Zobrazit konktextovou nab\\u00EDdku pro zvolen\\u00FD soubor\r\nShowPreferences.label = Nastavení\r\nShowPreferences.tooltip = Přizpůsobit trolCommander\r\nShowServerConnections.label = Zobrazit otevřená připojení\r\nQuit.label = Ukončit\r\nReverseSortOrder.label = Obrátit pořadí\r\nToggleAutoSize.label = Automatická šířka sloupců\r\nStop.label = Zastavit změnu složky\r\nToggleColumn.show = Zobrazit sloupec %1\r\nToggleColumn.hide = Skrýt sloupec %1\r\nToggleCommandBar.show = Zobrazit lištu příkazů\r\nToggleCommandBar.hide = Skrýt lištu příkazů\r\nToggleHiddenFiles.label = Zobrazit skryté soubory\r\nToggleToolBar.show = Zobrazit tlačítkovou lištu\r\nToggleToolBar.hide = Skrýt tlačítkovou lištu\r\nCustomizeCommandBar.label = Přizpůsobit lištu příkazů\r\nToggleStatusBar.show = Zobrazit stavový řádek\r\nToggleStatusBar.hide = Skrýt stavový řádek\r\nToggleShowFoldersFirst.label = Zobrazit nejdříve složky\r\nToggleFoldersAlwaysAlphabetical.label=T\\u0159idit slo\\u017Eky v\\u017Edy abecedn\\u011B\r\nToggleTree.label = Zobrazit strom složek\r\nToggleSinglePanel.label=P\\u0159epnout jedin\\u00FD panel\r\nPopupLeftDriveButton.label = Změnit složku vlevo\r\nPopupRightDriveButton.label = Změnit složku vpravo\r\nRecallPreviousWindow.label = Přepnout do předchozího okna\r\nRecallNextWindow.label = Přepnout do následujícího okna\r\nRecallWindow.label = Přepnout do okna #%1\r\nRecallWindow1.label = Přepnout do okna #%1\r\nRecallWindow2.label = Přepnout do okna #%1\r\nRecallWindow3.label = Přepnout do okna #%1\r\nRecallWindow4.label = Přepnout do okna #%1\r\nRecallWindow5.label = Přepnout do okna #%1\r\nRecallWindow6.label = Přepnout do okna #%1\r\nRecallWindow7.label = Přepnout do okna #%1\r\nRecallWindow8.label = Přepnout do okna #%1\r\nRecallWindow9.label = Přepnout do okna #%1\r\nRecallWindow10.label = Přepnout do okna #%1\r\nBringAllToFront.label = Přenést vše do popředí\r\nSwitchActiveTable.label = Přepnout mezi levým a pravým panelem\r\nSelectNextBlock.label = Přejít o blok (5 objektů) níž\r\nSelectPreviousBlock.label = Přejít o blok (5 objektů) výš\r\nSelectNextPage.label = Přejít o stránku níž\r\nSelectPreviousPage.label = Přejít o stránku výš\r\nSelectNextRow.label = Přejít o řádek níž\r\nSelectPreviousRow.label = Přejít o řádek výš\r\nSelectFirstRow.label = Vybrat první soubor ve stávající složce\r\nSelectLastRow.label = Vybrat poslední soubor ve stávající složce\r\nLeftArrowAction.label=Navigovat vlevo\r\nRightArrowAction.label=Navigovat vpravo\r\nSplitEqually.label = Rozdělit rovnoměrně\r\nSplitVertically.label = Rozdělit vertikálně\r\nSplitHorizontally.label = Rozdělit horizontálně\r\nShowKeyboardShortcuts.label = Klávesové zkratky\r\nGoToWebsite.label = Webová stránka\r\nGoToForums.label = Diskuzní fórum\r\nReportBug.label = Nahlásit chybu\r\nDonate.label = Darovat příspěvek\r\nShowAbout.label = O aplikaci trolCommander\r\nOpenTrash.label = Otevřít Koš\r\nEmptyTrash.label = Vyprázdnit Koš\r\nCalculateChecksum.label = Kontrolní součet\r\nMaximizeWindow.label = Maximalizovat\r\nMaximizeWindow.label.mac_os_x = Měřítko\r\nMinimizeWindow.label = Minimalizovat\r\nGoToDocumentation.label = Dokumentace na webu\r\nShowParentFoldersQL.label = Nadřazené složky\r\nShowRecentLocationsQL.label = Dříve navštívená umístění\r\nShowRecentExecutedFilesQL.label = Dříve spuštěné soubory\r\nShowRootFoldersQL.label = Kořenové složky\r\nShowTabsQL.label = Otevřít panely\r\nShowRecentViewedFilesQL.label = Nedávno zobrazené soubory\r\nShowRecentEditedFilesQL.label = Nedávno upravené soubory\r\nSplitFile.label = Rozdělit\r\nSplitFile.tooltip = Rozdělit soubor na více částí\r\nCombineFiles.label = Sloučit\r\nCombineFiles.tooltip = Sloučit části rozděleného souboru do původního souboru\r\nShowDebugConsole.label = Ladící konzole\r\nFocusPrevious.label = Zaměřit fokus na předchozí komponentu\r\nFocusNext.label = Zaměřit fokus na následující komponentu\r\nShowBookmarksQL.label = Záložky\r\nShowEditorBookmarksQL.label=Z\\u00E1lo\\u017Eky soubor\\u016F\r\nEjectDrive.label=Vysunout za\\u0159\\u00EDzen\\u00ED\r\nEjectDrive.tooltip=Bezpe\\u010Dn\\u011B odebrat za\\u0159\\u00EDzen\\u00ED\r\nfile_menu = Soubor\r\nfile_menu.open_with = Otevřít pomocí\r\nfile_menu.open_as=Otev\\u0159\\u00EDt jako\r\nmark_menu = Označit\r\nview_menu = Zobrazit\r\nview_menu.show_hide_columns = Zobrazit/skrýt sloupce\r\nview_menu.table_mode=Re\\u017Eim\r\ngo_menu = Přejít\r\ntools_menu = Nástroje\r\neject_menu=Vysunout za\\u0159\\u00EDzen\\u00ED\r\nbookmarks_menu = Záložky\r\nbookmarks_menu.no_bookmark = Bez záložek\r\ndrive_popup.network_shares = Síťové sdílení\r\nquick_lists_menu = Rychlé přehledy\r\nwindow_menu = Okno\r\nhelp_menu = Nápověda\r\nstatus_bar.selected_files = Vybráno: %1 z %2\r\nstatus_bar.connecting_to_folder = Připojuji se, stiskem ESCAPE akci zrušíte.\r\nstatus_bar.volume_free = Volné místo: %1\r\nstatus_bar.volume_capacity = Kapacita: %1\r\nstatus_bar.quick_search.press_esc_to_stop_search=p\\u0159eru\\u0161it kl\\u00E1vesou Esc\r\nshortcuts_panel.title = Zkratky\r\nshortcuts_panel.restore_defaults = Obnovit výchozí\r\nshortcuts_panel.show = Zobrazit\r\nshortcuts_panel.search = Vyhledávat\r\nshortcuts_panel.default_message = Stiskněte Enter na zkratce nebo poklepejte na zkratku, kterou chcete upravit\r\nshortcuts_table.action_description = Popis akce\r\nshortcuts_table.shortcut = Zkratka\r\nshortcuts_table.alternate_shortcut = Alternativní zkratka\r\nshortcuts_table.type_in_a_shortcut = Zadejte zkratku\r\ncommand_bar_dialog.help = Přetáhněte vybraná tlačítka na náhled lišty\r\ntable.folder_access_error_title = Chyba při přístupu k složce\r\ntable.folder_access_error = Nelze číst obsah složky\r\ntable.download_or_browse = Chcete si soubor prohlédnout nebo jej stáhnout?\r\nAddTab.label = Přidat panel\r\nAddTab.tooltip = Přidat nový panel v aktivním panelu\r\nToggleLockTab.lock = Zamknout\r\nToggleLockTab.unlock = Odemknout\r\nToggleLockTab.label = Zamknout/odemknout\r\nToggleLockTab.tooltip = Zmenit stav zámku panelu\r\nDuplicateTab.label = Duplikovat\r\nDuplicateTab.tooltip = Duplikovat aktivní panel ve stejném panelu\r\nCloseDuplicateTabs.label = Zavřít duplikáty\r\nCloseDuplicateTabs.tooltip = Zavřít duplicitní panely\r\nCloseOtherTabs.label = Zavřít ostatní\r\nCloseOtherTabs.tooltip = Zavřít ostatní panely\r\nCloseTab.label = Zavřít\r\nCloseTab.tooltip = Zavřít panel\r\nMoveTabToOtherPanel.label = Přesunout na jiný panel\r\nMoveTabToOtherPanel.tooltip = Přesunout panel na jiný panel\r\nCloneTabToOtherPanel.label = Klonovat na jiný panel\r\nCloneTabToOtherPanel.tooltip = Přidat podobný panel na jiný panel\r\nNextTab.label = Další panel\r\nNextTab.tooltip = Přepnout na panel vpravo\r\nPreviousTab.label = Předchozí panel\r\nPreviousTab.tooltip = Přepnout na panel vlevo\r\nSetTabTitle.label = Nastavit název\r\nSetTabTitle.tooltip = Nastavit fixní název panelu\r\nversion_dialog.no_new_version_title = Není nová verze\r\nversion_dialog.no_new_version = Blahopřejeme, máte aktuální verzi.\r\nversion_dialog.new_version_title = K dispozici je novější verze.\r\nversion_dialog.new_version = K dispozici je nová verze aplikace trolCommander.\r\nversion_dialog.new_version_url = Novou verzi aplikace trolCommander naleznete na %1.\r\nversion_dialog.not_available_title = Server není dostupný\r\nversion_dialog.not_available = Nelze získat ze serveru informace o verzi.\r\nversion_dialog.install_and_restart = Nainstalovat a restartovat\r\nversion_dialog.preparing_for_update = Aktualizace se připravuje\\u2026\r\nquit_dialog.title = Ukončit trolCommander\r\nquit_dialog.desc = Chcete zavřít všechna otevřená okna (%1) a ukončit trolCommander ?\r\nquit_dialog.show_next_time = Zobrazit příště\r\ndestination_dialog.file_exists_action = Výchozí akce pro existující soubor\r\ndestination_dialog.verify_integrity = Ověřit integritu\r\ndestination_dialog.skip_errors = Ignorovat chyby\r\ndestination_dialog.background_mode = Na pozadí\r\nfile_collision_dialog.title = Kolize souboru\r\nrename_dialog.new_name = Nové jméno\r\ncopy_dialog.destination = Kopírovat vybrané soubory do\r\ncopy_dialog.error_title = Chyba při kopírování\r\ncopy_dialog.copying = Kopírování souborů\r\ncopy_dialog.copying_file = Kopírování %1\\u2026\r\npack_dialog.packing = Komprimace\r\npack_dialog.packing_file = Komprimace %1\\u2026\r\npack_dialog.error_title = Chyba komprimace\r\npack_dialog_description = Přidat vybrané soubory do\r\npack_dialog.archive_format = Formát archívu\r\nunpack_dialog.destination = Dekomprimovat do\r\nunpack_dialog.error_title = Chyba dekomprimace\r\nunpack_dialog.unpacking = Dekomprimace\r\nunpack_dialog.unpacking_file = Dekomprimace %1\\u2026\r\noptimizing_archive = Optimalizace archívu %1\\u2026\r\nerror_while_optimizing_archive = Chyba při optimalizaci archívu %1\r\nmove_dialog.move_description = Přesunout do\r\nmove_dialog.error_title = Chyba při přesouvání\r\nmove_dialog.moving = Přesouvání souborů\r\nmove_dialog.moving_file = Přesouvání %1\\u2026\r\ndownload_dialog.description = Stáhnout soubor do\r\ndownload_dialog.download = Stáhnout\r\ndownload_dialog.error_title = Chyba při stahování\r\ndownload_dialog.downloading = Stahování\r\ndownload_dialog.downloading_file = Stahování %1\\u2026\r\nmkfile_dialog.allocate_space = Alokovaný prostor\r\nmkfile_dialog.open_in_editor = Otevřít v textovém editoru\r\nmkfile_dialog.make_executable = Spustitelný soubor\r\nmkfile_dialog.convert_whitespace=P\\u0159ev\\u00E9st netisknuteln\\u00E9 znaky\r\ndelete_dialog.permanently_delete.confirmation = Trvale smazat vybrané soubory?\r\ndelete_dialog.permanently_delete.confirmation_1=Trvale smazat vybran\\u00FD soubor?\r\ndelete_dialog.move_to_trash.confirmation = Chcete smazat vybrané soubory?\r\ndelete_dialog.move_to_trash.confirmation_1=Chcete smazat vybran\\u00FD soubor?\r\ndelete_dialog.move_to_trash.confirmation_details = Soubory budou přesunuty do Koše.\r\ndelete_dialog.move_to_trash.confirmation_details_1=Soubor bude p\\u0159esunut do Ko\\u0161e.\r\ndelete_dialog.move_to_trash.option = Přesunout do Koše\r\ndelete_dialog.move_to_trash.failed = Některý soubor nebylo možné přesunout do Koše.\r\ndelete_dialog.deleting = Odstraňování\r\ndelete_dialog.error_title = Chyba při mazání\r\ndelete.deleting_file = Odstraňování %1\\2026\r\nemail_dialog.prefs_not_set_title = Chybějící nastavení elektronické pošty\r\nemail_dialog.prefs_not_set = Nejprve nastavte parametry pro odesílání elektronické pošty.\r\nemail_dialog.from = Od\r\nemail_dialog.to = Komu\r\nemail_dialog.subject = Předmět\r\nemail_dialog.send = Odeslat\r\nemail_dialog.error_title = Chyba při odesílání\r\nemail_dialog.read_error = Nelze číst soubory v podsložkách.\r\nemail_dialog.sending = Odesílání souborů\r\nemail.sending_file = Odesílání %1\\u2026\r\nemail.connecting_to_server = Připojování k %1\\u2026\r\nemail.server_unavailable = Nelze se připojit k poštovnímu serveru %1, zkontrolujte nastavení nebo opakujte akci později.\r\nemail.connection_closed = Server ukončil připojení, pošta nebyla odeslána.\r\nemail.goodbye_failed = Chyba při ukončování připojení, pošta pravděpodobně nebyla odeslána.\r\nemail.send_file_error = Nelze odeslat soubor %1, zpráva nebyla odeslána.\r\nsplit_file_dialog.error_title = Chyba při rozdělování souboru\r\nsplit_file_dialog.file_to_split = Soubor k rozdělení\r\nsplit_file_dialog.target_directory = Cílová složka\r\nsplit_file_dialog.part_size = Velikost každé části\r\nsplit_file_dialog.parts = Počet částí\r\nsplit_file_dialog.generate_CRC = Vytvořit kontrolní součet\r\nsplit_file_dialog.max_parts = Maximální počet částí je %1\r\nsplit_file_dialog.auto = Automaticky\r\nsplit_file_dialog.insert_new_media = Vložte nové médium\r\ncombine_files_dialog.error_title = Chyba při slučování\r\ncombine_files_job.no_crc_file = Sloučení proběhlo úspěšně (bez kontrolního součtu).\r\ncombine_files_job.crc_read_error = Chyba při čtení kontrolního součtu.\r\ncombine_files_job.crc_check_failed = Kontrolní součet nesouhlasí: očekáváno %2, zjištěno %1\r\ncombine_files_job.crc_ok = Sloučení proběhlo úspěšně. Kontrolní součet je v pořádku.\r\nfile_selection_dialog.mark = Označit\r\nfile_selection_dialog.unmark = Odznačit\r\nfile_selection_dialog.mark_description = Označit soubory, jejichž jméno\r\nfile_selection_dialog.unmark_description = Odznačit soubory, jejichž jméno\r\nfile_selection_dialog.case_sensitive = Rozlišovat VELKÁ/malá písmena\r\nfile_selection_dialog.include_folders = Zahrnout složky\r\nprogress_dialog.starting = Přenos zahájen\\u2026\r\nprogress_dialog.transferred = Přeneseno %1 při rychlosti %2\r\nprogress_dialog.elapsed_time = Uplynulý čas\r\nprogress_dialog.advanced = Pokročilé\r\nprogress_dialog.current_speed = Aktuální rychlost\r\nprogress_dialog.limit_speed = Rychlostní limit\r\nprogress_dialog.close_when_finished = Po dokončení zavřít okno\r\nprogress_dialog.processing_files = Zpracování souborů\r\nprogress_dialog.processing_file = Zpracování %1\\u2026\r\nprogress_dialog.verifying_file = Ověřování %1\\u2026\r\nprogress_dialog.job_finished = Úkol dokončen\r\nprogress_dialog.job_error = Chyba\r\nprogress_dialog.hide = Skrýt\r\nproperties_dialog.file_properties = Vlastnosti objektu „%1“\r\nproperties_dialog.contents = Obsah\r\nproperties_dialog.calculating = výpočet\\u2026\r\ncalculate_checksum_dialog.checksum_algorithm = Algoritmus výpočtu\r\ncalculate_checksum_dialog.temporary_file = Dočasný soubor\r\nchange_date_dialog.now = Aktuální\r\nchange_date_dialog.specific_date = Jiné datum\r\nrun_dialog.run_command_description = Spustit v aktuální složce\r\nrun_dialog.run_in_home_description = Spustit v domovské složce\r\nrun_dialog.command_output = Výstup příkazu\r\nrun_dialog.run = Spustit\r\nrun_dialog.stop = Zastavit\r\nrun_dialog.clear_history = Vymazat historii\r\nserver_connect_dialog.server_type = Typ připojení\r\nserver_connect_dialog.server = Server\r\nserver_connect_dialog.share = Sdílet\r\nserver_connect_dialog.domain = Doména\r\nserver_connect_dialog.username = Přihlašovací jméno\r\nserver_connect_dialog.initial_dir = Počáteční složka\r\nserver_connect_dialog.port = Port\r\nserver_connect_dialog.server_url = Adresa serveru\r\nserver_connect_dialog.http_url = Adresa webu\r\nserver_connect_dialog.connect = Připojit\r\nserver_connect_dialog.protocol = Protokol\r\nserver_connect_dialog.nfs_version = Verze NFS\r\nserver_connect_dialog.private_key = Soukromý klíč\r\nserver_connect_dialog.passphrase = Heslo\r\nftp_connect.passive_mode = Povolit pasivní mód\r\nftp_connect.anonymous_user = Anonymní uživatel\r\nftp_connect.nb_connection_retries = Počet opakování pokusů o připojení\r\nftp_connect.retry_delay = Prodleva mezi pokusy (v sekundách)\r\nhttp_connect.basic_authentication = Základní ověření HTTP (volitelné)\r\nserver_connections_dialog.disconnect = Odpojit\r\nserver_connections_dialog.connection_busy = Zaneprázdněný\r\nserver_connections_dialog.connection_idle = Neaktivní\r\nvsphere_connections_dialog.guest_server = Host server %1\r\nvsphere_connections_dialog.guest_user = Host jméno\r\nvsphere_connections_dialog.guest_password = Host heslo\r\nbonjour.bonjour_services = Služba Bonjour\r\nbonjour.no_service_discovered = Služba nenalezena\r\nbonjour.bonjour_disabled = Služba Bonjour nepovolena\r\nauth_dialog.title = Ověření\r\nauth_dialog.desc = Zadejte prosím přihlašovací jméno a heslo\r\nauth_dialog.server = Server\r\nauth_dialog.store_credentials = Uložit přihlašovací jméno a heslo (slabé šifrování)\r\nauth_dialog.connect_as = Připojit jako \r\nauth_dialog.authentication_failed = Ověření se nezdařilo\r\nsortable_list.move_up = Posun výš\r\nsortable_list.move_down = Posun níž\r\nadd_bookmark_dialog.add = Přidat\r\nedit_bookmarks_dialog.location = Umístění\r\nedit_bookmarks_dialog.new = Nová\r\nedit_bookmarks_dialog.is_separator=Specifikovan\\u00FD n\\u00E1zev definuje separ\\u00E1tor\r\nfile_viewer.view_error_title = Chyba zobrazení\r\nfile_viewer.view_error = Nelze zobrazit soubor.\r\nfile_viewer.file_menu = Soubor\r\nfile_viewer.close = Zavřít\r\nfile_viewer.large_file_warning = Vybraný soubor může být pro tuto operaci příliš velký.\r\nfile_viewer.open_anyway = Přesto otevřít\r\nfile_viewer.open_hex = Zobrazit binárně\r\ntext_viewer.edit = Upravit\r\ntext_viewer.copy = Kopírovat\r\ntext_viewer.select_all = Vybrat vše\r\ntext_viewer.find = Najít\r\ntext_viewer.find_button=Naj\\u00EDt\r\ntext_viewer.find_next = Najít následující\r\ntext_viewer.find_previous = Najít předchozí\r\ntext_viewer.find.case_sensitive=Rozli\\u0161ovat velikost\r\ntext_viewer.find.whole_word=Cel\\u00E9 slovo\r\ntext_viewer.find.regexp=Regexp\r\ntext_viewer.find.mark_all=Ozna\\u010D v\\u0161e\r\ntext_viewer.find.direction=Sm\\u011Br\r\ntext_viewer.find.up=Nahoru\r\ntext_viewer.find.down=Dol\\u016F\r\ntext_viewer.view = Zobrazit\r\ntext_viewer.line_wrap = Zalamovat řádky\r\ntext_viewer.line_numbers = Čísla řádků\r\ntext_viewer.binary_file_warning = Toto je zřejmě binární soubor\r\ntext_viewer.goto_line = Skočit na řádek\r\ntext_viewer.line = Řádek\r\nimage_viewer.controls_menu = Ovládání\r\nimage_viewer.zoom_in = Přiblížit\r\nimage_viewer.zoom_out = Oddálit\r\nfile_editor.edit_error_title = Chyba při provádění úprav\r\nfile_editor.edit_error = Soubor nelze upravovat.\r\nfile_editor.save = Uložit\r\nfile_editor.save_as = Uložit jako\\u2026\r\nfile_editor.save_warning = Chcete před zavřením uložit provedené změny?\r\nfile_editor.cannot_write = Nelze zapisovat do souboru.\r\nfile_editor.file_menu = Soubor\r\nfile_editor.close = Zavřít\r\nfile_editor.open_anyway = Přesto otevřít\r\nfile_editor.save_anyway = Přesto zavřít\r\nfile_editor.overwrite_readonly = Soubor je jenom pro čtení\r\nfile_editor.files=Soubory\r\nfile_editor.files.list=Vyberte soubor\r\nfile_editor.show_file_manager=Zobrazit souborov\\u00E9ho spr\\u00E1vce\r\nfile_editor.add_to_bookmark=P\\u0159idat do z\\u00E1lo\\u017Eek\r\nfile_editor.remove_from_bookmark=Odebrat ze z\\u00E1lo\\u017Eek\r\nfile_editor.goto_header_source=P\\u0159ejit na hlavi\\u010Dku/zdroj\r\ntext_editor.cut = Vyjmout\r\ntext_editor.paste = Vložit\r\ntext_editor.undo = Zpět\r\ntext_editor.redo = Znovu\r\ntext_editor.edit = Změnit\r\ntext_editor.copy = Kopírovat\r\ntext_editor.select_all = Vybrat vše\r\ntext_editor.view = Zobrazit\r\ntext_editor.find = Hledat\r\ntext_editor.find_next = Hledat další\r\ntext_editor.find_previous = Hledat předchozí\r\ntext_editor.search=Vyhled\\u00E1vat\r\ntext_editor.replace=Nahradit\r\ntext_editor.replace_menu=Nahradit\\u2026\r\ntext_editor.replace_button=Nahradit\r\ntext_editor.replace_with=Nahradit \\u010D\\u00EDm\r\ntext_editor.replace_all=Nahradit v\\u0161e\r\ntext_editor.replaced=Nahrazeno\r\ntext_editor.occurrences=v\\u00FDskyt\\u016F\r\ntext_editor.line_wrap = Zalamování řádků\r\ntext_editor.line_numbers = Čísla řádků\r\ntext_editor.syntax = Zvýrazňování\r\ntext_editor.format = Formát\r\ntext_editor.writing = Ukládání\\u2026\r\ntext_editor.modified = Změněno\r\ntext_editor.saved = Soubor uložen\r\ntext_editor.text_not_found = Text nenalezen\r\ntext_editor.found=Nalezeno\r\ntext_editor.matches=shody\r\ntext_editor.cant_save_file = Soubor nelze uložit\r\ntext_editor.press_alt_enter_to_open_file=Stiskem Alt+Enter otev\\u0159\\u00EDt soubor\r\ntext_editor.tools=N\\u00E1stroje\r\ntext_editor.build=Sestaven\\u00ED\r\ntext_editor.invisible_chars=Neviditeln\\u00E9 znaky\r\nshortcuts_dialog.quick_search = Rychlé vyhled\\u00E1ní\r\nshortcuts_dialog.quick_search.start_search = Vepsáním libovolného znaku zahájíte rychlé hledání\r\nshortcuts_dialog.quick_search.cancel_search = Ukončit rychlé hledání\r\nshortcuts_dialog.quick_search.remove_last_char = Odstranit z hledaného řetězce poslední znak\r\nshortcuts_dialog.quick_search.jump_to_previous = Přejít na předchozí výsledek hledání\r\nshortcuts_dialog.quick_search.jump_to_next = Přejít na následující výsledek hledání\r\nshortcuts_dialog.quick_search.mark_jump_next = Označit/odznačit stávající soubor a přejít na následující výsledek hledání\r\ntheme_editor.title = Editor motivů\r\ntheme_editor.folder_tab = Panely souborů\r\ntheme_editor.shell_tab = Shell\r\ntheme_editor.shell_history_tab = Historie shellu\r\ntheme_editor.terminal_tab = Terminál\r\ntheme_editor.statusbar_tab = Stavový řádek\r\ntheme_editor.free_space = Volné místo\r\ntheme_editor.free_space.ok = OK\r\ntheme_editor.free_space.warning = Upozornění\r\ntheme_editor.free_space.critical = Kritické\r\ntheme_editor.locationbar_tab = Adresní řádek\r\ntheme_editor.editor_tab = Editor souborů\r\ntheme_editor.font = Písmo\r\ntheme_editor.active_panel = Aktivní\r\ntheme_editor.inactive_panel = Neaktivní\r\ntheme_editor.general = Obecné\r\ntheme_editor.theme_warning_predefined=Vestav\\u011Bn\\u00E9 motivy nelze m\\u011Bnit. Chcete vytvo\\u0159it nov\\u00FD motiv?\r\ntheme_editor.could_not_save_theme = Motiv „%1“ nelze uložit motiv\r\ntheme_editor.border = Ohraničení\r\ntheme_editor.background = Pozadí\r\ntheme_editor.alternate_background = Alternativní pozadí\r\ntheme_editor.unfocused_background = Pozadí (bez fokusu)\r\ntheme_editor.quick_search = Rychlé vyhledávání\r\ntheme_editor.quick_search.unmatched_file = Nevyhovující soubor\r\ntheme_editor.text = Text\r\ntheme_editor.progress = Průběh\r\ntheme_editor.normal = Normální\r\ntheme_editor.normal_unfocused = Normální (bez fokusu)\r\ntheme_editor.selected = Výběr\r\ntheme_editor.selected_unfocused = Výběr (bez fokusu)\r\ntheme_editor.color = Barva\r\ntheme_editor.colors = Barvy\r\ntheme_editor.plain_file = Běžný soubor\r\ntheme_editor.marked_file = Označený soubor\r\ntheme_editor.hidden_file = Skrytý soubor\r\ntheme_editor.folder = Složka\r\ntheme_editor.archive_file = Archív\r\ntheme_editor.symbolic_link = Symbolický odkaz\r\ntheme_editor.executable_file = Spustitelný soubor\r\ntheme_editor.header = Záhlaví\r\ntheme_editor.current = Řádek\r\ntheme_editor.file_groups = Skupiny souborů\r\ntheme_editor.group_ = Skupina\r\ntheme_editor.normal_color = Normální barva\r\ntheme_editor.selected_color = Barva výběru\r\ntheme_editor.filemask = Souborové masky\r\ntheme_editor.group_file_ = Sobor skupiny \r\ntheme_editor.item = Položka\r\ntheme_editor.text_editor_tab=Prohl\\u00ED\\u017Ee\\u010D a editor textu\r\ntheme_editor.hex_viewer_tab=Prohl\\u00ED\\u017Ee\\u010D dat\r\ntheme_editor.normal_hex=Hex\r\ntheme_editor.normal_offset=Offset\r\ntheme_editor.normal_ascii=ASCII\r\ntheme_editor.alternate=Alternativn\\u00ED\r\ntheme_editor.selected_hex=Vybran\\u00FD bin\\u00E1rn\\u00ED\r\ntheme_editor.selected_ascii=Vybran\\u00FD ASCII\r\ncommand_bar_customize_dialog.available_actions = Dostupné akce\r\ncommand_bar_customize_dialog.modifier = Modifikátor\r\nprefs_dialog.title = Nastavení\r\nprefs_dialog.general_tab = Obecné\r\nprefs_dialog.day = Den\r\nprefs_dialog.month = Měsíc\r\nprefs_dialog.year = Rok\r\nprefs_dialog.language = Jazyk (vyžaduje restart)\r\nprefs_dialog.date_time = Formát datumu a času\r\nprefs_dialog.time = Čas\r\nprefs_dialog.date = Datum\r\nprefs_dialog.date_separator = Oddělovač\r\nprefs_dialog.time_12_hour = 12-hodinový formát\r\nprefs_dialog.time_24_hour = 24-hodinový formát\r\nprefs_dialog.show_seconds = Zobrazit sekundy\r\nprefs_dialog.show_century = Zobrazit století\r\nprefs_dialog.check_for_updates_on_startup = Při spuštění hledat aktualizace\r\nprefs_dialog.show_splash_screen = Zobrazit uvítací obrázek\r\nprefs_dialog.folders_tab = Složky\r\nprefs_dialog.startup_folders = Počáteční složky\r\nprefs_dialog.left_folder = Složka vlevo\r\nprefs_dialog.right_folder = Složka vpravo\r\nprefs_dialog.last_folder = Poslední navštívená složka\r\nprefs_dialog.custom_folder = Vlastní složka\r\nprefs_dialog.quick_search=Rychl\\u00E9 hled\\u00E1n\\u00ED\r\nprefs_dialog.quick_search_timeout=Zastavit rychl\\u00E9 hled\\u00E1n\\u00ED po neaktivit\\u011B\r\nprefs_dialog.quick_search_timeout_never=Nikdy\r\nprefs_dialog.quick_search_timeout_sec=Sek\r\nprefs_dialog.show_quick_search_matches_first=Zobrazit shodn\\u00E9 prvn\\u00ED\r\nprefs_dialog.show_hidden_files = Zobrazit skryté soubory\r\nprefs_dialog.show_ds_store_files = Zobrazit soubory .DS_Store\r\nprefs_dialog.show_system_folders = Zobrazit systémové složky\r\nprefs_dialog.compact_file_size = Zaokrouhlit zobrazenou velikost souborů\r\nprefs_dialog.follow_symlinks_when_cd = Následovat symbolické odkazy při změnách složky\r\nprefs_dialog.show_tab_header = Vždy zobrazovat hlavičku panelu\r\nprefs_dialog.appearance_tab = Vzhled\r\nprefs_dialog.look_and_feel = Vzhled a chování\r\nprefs_dialog.icons_size = Velikost ikon\r\nprefs_dialog.toolbar_icons = Tlačítková lišta\r\nprefs_dialog.command_bar_icons = Lišta příkazů\r\nprefs_dialog.file_icons = Typy souborů\r\nprefs_dialog.use_system_file_icons = Použít systémové ikony\r\nprefs_dialog.use_system_file_icons.always = Vždy\r\nprefs_dialog.use_system_file_icons.never = Nikdy\r\nprefs_dialog.use_system_file_icons.applications = Jen u aplikací\r\nprefs_dialog.edit_current_theme = Změnit stávající motiv\\u2026\r\nprefs_dialog.themes = Motivy\r\nprefs_dialog.syntax_themes = Editor motivu zvýraznění\r\nprefs_dialog.import_theme = Importovat motiv\r\nprefs_dialog.import_look_and_feel = Importovat Vzhled a chování\r\nprefs_dialog.no_look_and_feel = Žádný Vzhled a chování nebyl nalezen.\r\nprefs_dialog.error_in_import = Chyba při importu motivu „%1“.\r\nprefs_dialog.cannot_read_theme = Nelze načíst motiv ze souboru „%1“\r\nprefs_dialog.export_theme = Exportovat „%1“\r\nprefs_dialog.import = Importovat\r\nprefs_dialog.export = Exportovat\r\nprefs_dialog.theme_type = Typ: %1\r\nprefs_dialog.delete_theme = Chcete trvale smazat motiv „%1“?\r\nprefs_dialog.delete_look_and_feel = Chcete trvale smazat Vzhled a chování „%1“?\r\nprefs_dialog.rename_failed = Nepodařilo se přejmenovat motiv „%1“\r\nprefs_dialog.xml_file = Soubor XML\r\nprefs_dialog.jar_file = Soubor JAR\r\nprefs_dialog.mail_tab = E-mail\r\nprefs_dialog.mail_settings = Nastavení odchozí pošty\r\nprefs_dialog.mail_name = Vaše jméno\r\nprefs_dialog.mail_address = Váš e-mail\r\nprefs_dialog.mail_server = Server SMTP\r\nprefs_dialog.misc_tab = Různé\r\nprefs_dialog.use_brushed_metal = Použít vzhled „brushed metal“ (vyžaduje restart)\r\nprefs_dialog.confirm_on_quit = Při ukončení zobrazovat potvrzovací dialog\r\nprefs_dialog.shell = Spustit příkaz\r\nprefs_dialog.default_shell = Použít výchozí systémový shell\r\nprefs_dialog.custom_shell = Použít vlastní chell\r\nprefs_dialog.shell_encoding = Kódování textu shellu\r\nprefs_dialog.auto_detect_shell_encoding = Automatické rozpoznání\r\nprefs_dialog.external_terminal = Externí terminál\r\nprefs_dialog.default_terminal = Použít výchozí terminál\r\nprefs_dialog.custom_terminal = Použít vlastní příkaz\r\nprefs_dialog.builtin_terminal = Vestavěný terminál\r\nprefs_dialog.default_shell = Použít výchozí shell\r\nprefs_dialog.custom_shell = Použít vlastní příkaz\r\nprefs_dialog.enable_bonjour_discovery = Povolit služby Bonjour discovery\r\nprefs_dialog.enable_system_notifications = Zobrazovat oznámení systému\r\nprefs_dialog.calculate_folder_size_on_mark = Vypočítat velikost složky po označení\r\ndebug_console_dialog.level = Úroveň záznamu\r\ndebug_console_dialog.threads = Vlákna\r\ndebug_console_dialog.active_threads=Aktivn\\u00ED vl\\u00E1kna\r\nunit.byte = bajt\r\nunit.bytes = bajtů\r\nunit.bytes_short = b\r\nunit.kb = KB\r\nunit.mb = MB\r\nunit.gb = GB\r\nunit.tb = TB\r\nunit.speed = %1/s\r\nduration.seconds = %1 sek.\r\nduration.minutes = %1 min.\r\nduration.hours = %1 hod.\r\nduration.days = %1 d\r\nduration.months = %1 měs.\r\nduration.years = %1 let\r\nduration.infinite = ∞\r\ntheme.custom_theme = Vlastní motiv\r\ntheme.custom = Vlastní\r\ntheme.built_in = Vestavěné\r\ntheme.add_on = Doplněk\r\ntheme.current = stávající\r\ntheme_could_not_be_loaded = Chyba při zavádění motivu.\r\ncannot_open_cyclic_symlink = Nelze otevřít zvolený odkaz, neboť odkazuje sám na sebe\r\nsetup.title = Vítá Vás trolCommander\r\nsetup.intro = Prosím, zvolte vlastní nastavení aplikace trolCommander.\r\nsetup.look_and_feel = Vyberte si Vzhled a chování\r\nsetup.theme = Vyberte si motiv\r\nfont_chooser.font_size = Velikost\r\nfont_chooser.font_bold = Tučné\r\nfont_chooser.font_italic = Kurzíva\r\ncolor_chooser.red = Červená\r\ncolor_chooser.green = Zelená\r\ncolor_chooser.blue = Modrá\r\ncolor_chooser.hue = Odstín\r\ncolor_chooser.brightness = Jas\r\ncolor_chooser.swatches = Barevný mixér\r\ncolor_chooser.saturation = Sytost\r\ncolor_chooser.rgb = RGB\r\ncolor_chooser.hsb = HSB\r\ncolor_chooser.recent = Naposledy použité\r\ncolor_chooser.alpha = Průhlednost alfa\r\ncolor_chooser.title = Vyberte barvu\r\nbatch_rename_dialog.mask = Maska pro přejmenování\r\nbatch_rename_dialog.search_replace = Najít a nahradit\r\nbatch_rename_dialog.search_for = Hledaný výraz\r\nbatch_rename_dialog.replace_with = Bude nahrazen\r\nbatch_rename_dialog.counter = Počítadlo\r\nbatch_rename_dialog.start_at = Počítat od\r\nbatch_rename_dialog.step_by = Přírůstek\r\nbatch_rename_dialog.format = Formát\r\nbatch_rename_dialog.upper_lower_case = Velikost písmen\r\nbatch_rename_dialog.no_change = Beze změny\r\nbatch_rename_dialog.lower_case = malá písmena\r\nbatch_rename_dialog.upper_case = VELKÁ PÍSMENA\r\nbatch_rename_dialog.first_upper = První písmeno velké\r\nbatch_rename_dialog.word = První Písmena Velká\r\nbatch_rename_dialog.regexp = RegExp\r\nbatch_rename_dialog.regexp_error = Chyba syntaxe RegExp\r\nbatch_rename_dialog.old_name = Původní jméno\r\nbatch_rename_dialog.new_name = Nové jméno\r\nbatch_rename_dialog.block_name = Zachovat\r\nbatch_rename_dialog.range = Rozsah\r\nbatch_rename_dialog.proceed_renaming = %1 z %2 souborů bude přejmenováno. Chcete pokračovat?\r\nbatch_rename_dialog.duplicate_names = Duplicitní jméno!\r\nbatch_rename_dialog.names_conflict = Konflikt jmen! Stejná hodnota pro staré a nové jméno.\r\nparent_folders_quick_list.empty_message = Stávající umístění nemá nadřazenou složku\r\nrecent_locations_quick_list.empty_message = Dříve navštívená umístění nebyla nalezena\r\nrecent_executed_files_quick_list.empty_message = Dříve spuštěné soubory nebyly nalezeny\r\nrecent_edited_files_quick_list.empty_message = Žádné dříve editované soubory\r\nrecent_viewed_files_quick_list.empty_message = Žádné dříve zobrazované soubory\r\nroots_quick_list.empty_message = Žádné kořenové složky\r\ntabs_quick_list.empty_message = Je dostupný pouze jeden panel\r\neditor_bookmarks_quick_list.empty_message=\\u017D\\u00E1dn\\u00E9 soubory\r\n#server_connect_dialog.auth_error = Nesprávné uživatelské jméno nebo heslo.\r\n#move_dialog.cannot_move_to_itself = Nemůžu přesunout soubory do podsložky.\r\n#pack.error_on_file = Chyba při kompresi %1\r\n#pack_dialog.cannot_write = V cílové složce nelze vytvořit soubor.\r\n#unpack.unable_to_open_zip = Chyba při otevírání souboru %1.\r\n#image_viewer.previous_image = Předchozí obrázek\r\n#image_viewer.next_image = Následující obrázek\r\n#mkdir_dialog.title = Vytvořit složku\r\n#mkdir_dialog.error_title = Chyba při vytváření složky\r\n#edit_bookmarks_dialog.remove = Odstranit\r\n#mkdir_dialog.description = Vytvořit složku\r\n#mkfile_dialog.description = Vytvořit nový prázdný soubor\r\n#done = Hotovo\r\n#progress_dialog.hide = Skrýt\r\n#move_dialog.rename_description = Přejmenovat soubor na\r\n#theme_editor.shell_font = Písmo shellu\r\n#theme_editor.history_font = Písmo historie\r\n#theme_editor.shell_colors = Barvy shellu\r\n#theme_editor.history_colors = Barvy historie\r\n#ToggleHiddenFiles.hide = Skrýt skryté soubory\r\n#auth_dialog.error_was = Chyba: %1\r\n#table.hide_column = Skrýt sloupec\r\n#delete.symlink_warning_title = Nalezen symbolický odkaz\r\n#delete.symlink_warning = Tento soubor vypadá jako symbolický odkaz:\\n\\n Soubor: %1\\n odkazujuje na: %2\\n\\nChcete smazat jen odkaz nebo\\ni cíl odkazu (UPOZORNĚNÍ) ?\r\n#delete.delete_link_only = Smazat odkaz\r\n#delete.delete_linked_folder = Smazat cíl odkazu\r\n#Unpack.label = Dekomprimovat\r\n#Pack.label = Komprimovat\r\nfind_dialog.name = Jméno souboru\r\nfind_dialog.contains = Obsahuje\r\nfind_dialog.initial_directory = Začíná na\r\nfind_dialog.search_subdirectories = Prohledávat podsložky\r\nfind_dialog.search_archives = Prohledávat archívy\r\nfind_dialog.case_sensitive = Rozlišovat velikost písmen\r\nfind_dialog.ignore_hidden = Ignorovat skryté\r\nfind_dialog.search_results = Výsledky hledání\r\nfind_dialog.found = Nalezené soubory \r\nfind_dialog.encoding = Kódování textu\r\nfind_dialog.search_hex = Vyhledávat binárně\r\nimage_viewer.next_image = Další obrázek\r\nimage_viewer.previous_image = Předchozí obrázek\r\nhex_viewer.offset = Posun\r\nhex_viewer.ascii_dump = Výpis ASCII\r\nhex_viewer.view = Zobrazit\r\nhex_viewer.goto = Skočit na\r\nhex_viewer.goto.offset = Posun\r\nhex_viewer.search = Hledat\r\nhex_viewer.searchNext = Najit další\r\nhex_viewer.searchPrev = Najit předchozí\r\nhex_viewer.find = Najít\r\nhex_view.text = Hledat\r\nhex_viewer.hex = Hex\r\nhex_viewer.search_not_found = Vzor nenalezen\r\ncalculator.calculator = Kalkulačka\r\ncalculator.expression = Výraz\r\ncalculator.error = Chyba ve výrazu\r\nreplication=Faktor replikace\r\nblocksize=Velikost bloku\r\nChangeReplication.label=Zm\\u011Bnit replikaci\r\nreplication.number=Faktor replikace\r\nadb.android_devices=Android\r\nadb.no_devices=\\u017D\\u00E1dn\\u00E9 za\\u0159\\u00EDzen\\u00ED\r\neject.no_mounted_devices=\\u017D\\u00E1dn\\u00E9 p\\u0159ipojen\\u00E9 za\\u0159\\u00EDzen\\u00ED\r\n"
  },
  {
    "path": "src/main/resources/dictionary_da_DA.properties",
    "content": "ok = OK\nyes = Ja\nno = Nej\ncancel = Annuller\nedit = Rediger\nclose = Luk\nreset = Nulstil\nrename = Omdøb\napply = Anvend\nchange = Ændre\nsave = Gem\ndont_save = Gem ikke\nreplace = Erstat\ndont_replace = Erstat ikke\ndelete = Slet\nskip = Spring over\nskip_all = Spring over alle\nretry = Forsøg igen\nresume = Forsæt\noverwrite = Overskriv\noverwrite_if_older = Overskriv hvis ældre\nduplicate = Dupliker\napply_to_all = Anvend på alle\ncopy = Kopier\nmove = Flyt\npack = Komprimer\nunpack = Udpak\ndownload = Hent\nsplit = Del\ncombine = Sammenflet\nbrowse = Gennemse\nask = Spørg\nstop = Stop\npause = Pause\nquick_search = Hurtig søgning\nfile_manager = Filhåndterer\ncreate = Opret\ncreating_file = Opret %1\nchoose = Vælg\ncustomize = Tilpas\nchoose_folder = Vælg mappe\nlogin = Brugernavn\npassword = Kodeord\nuser = Ejer\nencoding = Tegnkodning\npreferred_encodings = Foretrukket indkodning\nlicense = Licens\nname = Navn\nsize = Størrelse\ndate = Dato\nextension = Filtype\npermissions = Rettigheder\nowner = Ejer\ngroup = Gruppe\nlocation = Placering\nuntitled = Unavngivet\nsource = Kilde\ndestination = Destination\nrecurse_directories = Behandel markerede mapper rekursivt\ngo_to = Gå til\nexample = Eksempel\npreview = Eksempel\ncomment = Kommentar\nsample_text = Eksempel på tekst\nnb_files = %1 fil(er)\nnb_folders = %1 mappe(r)\nloading = Indlæser...\nthis_operation_cannot_be_undone = Du kan ikke fortryde denne handling.\nremove = Fjern\ndetails = Detaljer\nwarning = Advarsel\nerror = Fejl\ngeneric_error = En fejl opstod under den ønskede handling\nfolder_does_not_exist = Mappen findes ikke eller er ikke tilgængelig\nthis_folder_does_not_exist = Mappen findes ikke eller er ikke tilgængelig: %1\nthis_file_does_not_exist = Filen findes ikke eller er ikke tilgængelig: %1\ninvalid_path = Ugyldig sti: %1\ndirectory_already_exists = Mappen %1 findes allerede.\nfile_exists_in_destination = Filen findes allerede på denne lokation.\nsource_parent_of_destination = Forsøg på at flytte mappen til en af dens undermapper\ncannot_read_file = Kan ikke læse filen %1\ncannot_write_file = Kan ikke skrive til filen %1\ncannot_create_folder = Kan ikke oprette mappen %1\ncannot_read_folder = Kan ikke læse indholdet af mappen %1\ncannot_delete_file = Kan ikke slette filen %1\ncannot_delete_folder = Kan ikke slette mappen %1\nerror_while_transferring = Fejl ved overførsel af filen %1\nsame_source_destination = Kilden og destinationen er den samme\nfile_already_exists = %1 findes allerede, vil du erstatte den?\nwrite_error = Skrivefejl\nread_error = Læsefejl\nintegrity_check_error = Integritetstjek fejlede: kilden og destinationen stemmer ikke overens\nstartup_error = En fejl forhindrede trolCommander fra at starte.\npermissions.read = Læsbar\npermissions.write = Skrivbar\npermissions.executable = Eksekverbar\npermissions.group = Gruppe\npermissions.other = Andre\npermissions.octal_notation = Oktal notation\naction_categories.all = Alle\naction_categories.navigation = Navigation\naction_categories.selection = Markering\naction_categories.view = Vis\naction_categories.file_operations = Fil handlinger\naction_categories.windows = Vinduer\nTerminal.label = Terminal\nTerminal.tooltip = Terminal\nFindFile.label = Find file\nFindFile.tooltip = Find file\nAddBookmark.label = Nyt bogmærke\nAddBookmark.tooltip = Tilføj den nuværende mappe til bogmærker\nBatchRename.label = Multi omdøbning\nEditBookmarks.label = Rediger bogmærker\nExploreBookmarks.label = Vis bogmærker\nEditCredentials.label = Rediger akkreditiver\nChangeLocation.label = Ændre den nuværende sti\nChangeDate.label = Ændre dato\nChangeDate.tooltip = Ændre dato på markered(e) fil(er)\nChangePermissions.label = Ændre rettigheder\nChangePermissions.tooltip = Ændre rettigheder på markered(e) fil(er)\nCheckForUpdates.label = Søg efter opdateringer\nCompareFolders.label = Sammenlign mapper\nConnectToServer.label = Forbind til server\nConnectToServer.tooltip = Forbind til en fjernserver\nView.label = Vis\nInternalView.label = Vis (indbygget)\nView.tooltip = Vis markerede fil\nInternalEdit.label = Rediger (indbygget)\nEdit.tooltip = Rediger markerede fil\nCopy.tooltip = Kopier markerede filer\nLocalCopy.label = Lokal kopi\nLocalCopy.tooltip = Kopier markeret fil til den aktuelle mappe\nMove.tooltip = Flyt markerede filer\nRename.tooltip = Omdøb markeret fil\nMkdir.label = Ny mappe\nMkdir.tooltip = Opret en ny mappe i den aktuelle mappe\nMkfile.label = Ny fil\nMkfile.tooltip = Opret en ny fil i den aktuelle mappe\nDelete.tooltip = Slet markerede filer via papirkurven når det er muligt\nPermanentDelete.label = Slet permanent\nPermanentDelete.tooltip = Slet markerede filer uden at bruge papirkurven\nRefresh.label = Opdater\nRefresh.tooltip = Opdater aktuelle mappe\nCloseWindow.label = Luk vindue\nCloseWindow.tooltip = Luk dette vindue\nCopyFileNames.label = Kopier navn(e)\nCopyFilePaths.label = Kopier sti(er)\nCopyFilesToClipboard.label = Kopier fil(er)\nPasteClipboardFiles.label = Indsæt fil(er)\nEmail.label = E-mail filer\nEmail.tooltip = Send de markerede filer i en mail\nGoBack.label = Tilbage\nGoBack.tooltip = Foregående mappe\nGoForward.label = Frem\nGoForward.tooltip = Næste mappe\nGoToHome.label = Hjemmemappe\nGoToParent.label = Forrige\nGoToParent.tooltip = Til forrige mappe\nGoToParentInOtherPanel.label = Gå til forrige mappe i modsatte panel\nGoToParentInBothPanels.label = Gå til forrige mappe i begge paneler\nGoToRoot.label = Gå til roden\nSortByName.label = Sorter efter navn\nSortByDate.label = Sorter efter dato\nSortBySize.label = Sorter efter størrelse\nSortByExtension.label = Sorter efter filendelse\nSortByPermissions.label = Sorter efter rettigheder\nSortByOwner.label = Sorter efter ejer\nSortByGroup.label = Sorter efter gruppe\nMarkGroup.label = Marker filer\nMarkGroup.tooltip = Marker en gruppe filer\nUnmarkGroup.label = Afmarker filer\nUnmarkGroup.tooltip = Afmarker en gruppe filer\nMarkAll.label = Marker alt\nUnmarkAll.label = Afmarker alt\nMarkSelectedFile.label = Marker/afmarker\nMarkSelectedFile.tooltip = Marker/afmarker valgte fil\nMarkNextBlock.label = Marker en blok ned\nMarkPreviousBlock.label = Marker en blok op\nMarkNextRow.label = Marker en række ned\nMarkPreviousRow.label = Marker en række op\nMarkNextPage.label = Marker side ned\nMarkPreviousPage.label = Marker side op\nMarkToFirstRow.label = Marker filer til begyndelsen\nMarkToLastRow.label = Marker filer til enden\nMarkExtension.label = Marker filendelse\nInvertSelection.label = Omvend markering\nSwapFolders.label = Ombyt mapper\nSwapFolders.tooltip = Ombyt venstre og højre panel\nSetSameFolder.label = Vis samme mappe\nSetSameFolder.tooltip = Vis samme mappe i venstre og højre panel\nNewWindow.label = Nyt vindue\nNewWindow.tooltip = Åben et nyt vindue\nOpen.label = Åben\nOpen.tooltip = Åben mappe / Åben arkiv / Kør\nOpenNatively.label = Åben normalt\nOpenNatively.tooltip = Åben markeret fil med systemets predefinerede filtilknytning\nOpenInOtherPanel.label = Åben i andet panel\nOpenInBothPanels.label = Åben i begge paneler\nRevealInDesktop.label = Vis i %1\nRunCommand.label = Kør kommando\nRunCommand.tooltip = Kør en kommando i aktuel mappe\nPack.tooltip = Komprimer markerede filer til et arkiv\nUnpack.tooltip = Udpak markerede filarkiver\nShowFileProperties.label = Egenskaber\nShowFileProperties.tooltip = Vis egenskaber for markerede filer\nShowPreferences.label = Indstillinger\nShowPreferences.tooltip = Konfigurer trolCommander\nShowServerConnections.label = Vis åbne forbindelser\nQuit.label = Afslut\nReverseSortOrder.label = Omvend sortering\nToggleAutoSize.label = Automatisk kolonne bredde\nStop.label = Stop ombytning af mapper\nToggleColumn.show = Vis %1 kolonne\nToggleColumn.hide = Skjul %1 kolonne\nToggleCommandBar.show = Vis kommandolinien\nToggleCommandBar.hide = Skjul kommandolinien\nToggleToolBar.show = Vis værktøjslinie\nToggleToolBar.hide = Skjul værktøjslinie\nCustomizeCommandBar.label = Tilpas kommandolinien\nToggleStatusBar.show = Vis statuslinie\nToggleStatusBar.hide = Skjul statuslinie\nToggleShowFoldersFirst.label = Vis mapper først\nToggleTree.label = Træ visning\nPopupLeftDriveButton.label = Ændre venstre mappe\nPopupRightDriveButton.label = Ændre højre mappe\nRecallPreviousWindow.label = Fremkald foregående vindue\nRecallNextWindow.label = Fremkald næste vindue\nRecallWindow.label = Fremkald vindue #%1\nBringAllToFront.label = Læg alle øverst\nSwitchActiveTable.label = Skift i mellem venstre og højre panel\nSelectNextBlock.label = Spring en blok ned\nSelectPreviousBlock.label = Spring en blok op\nSelectNextPage.label = Spring en side ned\nSelectPreviousPage.label = Spring en side op\nSelectNextRow.label = Spring en række ned\nSelectPreviousRow.label = Spring en række op\nSelectFirstRow.label = Vælg første fil i aktuel mappe\nSelectLastRow.label = Vælg sidste fil i aktuel mappe\nSplitEqually.label = Del ligeligt\nSplitVertically.label = Del lodret\nSplitHorizontally.label = Del vandret\nShowKeyboardShortcuts.label = Tastatur genveje\nGoToWebsite.label = Gå til hjemmeside\nGoToForums.label = Gå til forum\nReportBug.label = Rapporter en fejl\nDonate.label = Doner\nShowAbout.label = Om trolCommander\nOpenTrash.label = Åben papirkurv\nEmptyTrash.label = Tøm papirkurv\nCalculateChecksum.label = Udregn kontrolsum\nMaximizeWindow.label = Maksimer\nMinimizeWindow.label = Minimer\nGoToDocumentation.label = Online dokumentation\nShowParentFoldersQL.label = Ovenliggende mapper til den nuværende placering\nShowRecentLocationsQL.label = Nyeligt tilgåede placeringer\nShowRecentExecutedFilesQL.label = Nyeligt kørte filer\nSplitFile.tooltip = Del en fil op i flere dele\nCombineFiles.tooltip = Sammenflet delte filer for at genoprette de oprindelige filer\nShowDebugConsole.label = Fejlsøgningskonsol\nFocusPrevious.label = Fokuser på tidligere komponent\nFocusNext.label = Fokuser på næste komponent\nfile_menu = Filer\nfile_menu.open_with = Åben med\nmark_menu = Marker\nview_menu = Vis\nview_menu.show_hide_columns = Vis/skjul kolonner\ngo_menu = Gå\nbookmarks_menu = Bogmærker\nbookmarks_menu.no_bookmark = Ingen bogmærker\nquick_lists_menu = Hurtig lister\nwindow_menu = Vindue\nhelp_menu = Hjælp\nstatus_bar.selected_files = %1 af %2 markerede\nstatus_bar.connecting_to_folder = Forbinder til mappe, tryk på ESCAPE for at annullere.\nstatus_bar.volume_free = Ledigt: %1\nstatus_bar.volume_capacity = Kapacitet: %1\nshortcuts_panel.title = Genveje\nshortcuts_panel.restore_defaults = Standard indstillinger\nshortcuts_panel.show = Vis\nshortcuts_panel.default_message = Tryk Enter/dobbelt klik på den genvej du gerne vil redigere\nshortcuts_table.action_description = Handlingsbeskrivelse\nshortcuts_table.shortcut = Genvej\nshortcuts_table.alternate_shortcut = Alternativ genvej\nshortcuts_table.type_in_a_shortcut = Indtast en genvej\ncommand_bar_dialog.help = Træk knapper for at tilpasse kommandolinien\ntable.folder_access_error_title = Fejl ved mappe adgang\ntable.folder_access_error = Mappens indhold kunne ikke læses\ntable.download_or_browse = Vil du gennemse eller hente denne fil?\nversion_dialog.no_new_version_title = Der findes ingen ny version\nversion_dialog.no_new_version = Tillykke, du har allerede den nyeste version.\nversion_dialog.new_version_title = Ny version tilgængelig\nversion_dialog.new_version = En ny version af trolCommander er tilgængelig.\nversion_dialog.new_version_url = En ny version af trolCommander er tilgængelig på: %1.\nversion_dialog.not_available_title = Serveren er ikke tilgængelig\nversion_dialog.not_available = Det er ikke muligt at få versions information fra serveren.\nversion_dialog.install_and_restart = Installer og genstart\nversion_dialog.preparing_for_update = Forbereder opdatering...\nquit_dialog.title = Afslut trolCommander\nquit_dialog.desc = Du har %1 vinduer åbne. Er du sikker på du vil afslutte? \nquit_dialog.show_next_time = Vis næste gang\ndestination_dialog.file_exists_action = Standardhandling når filen allerede findes\ndestination_dialog.verify_integrity = Verificer data integritet\ndestination_dialog.skip_errors = Ignorer fejl\nfile_collision_dialog.title = Filen findes allerede\nrename_dialog.new_name = Nyt navn\ncopy_dialog.destination = Kopier markerede fil(er) til\ncopy_dialog.error_title = Kopieringsfejl\ncopy_dialog.copying = Kopierer filer\ncopy_dialog.copying_file = Kopierer %1\npack_dialog.packing = Komprimerer filer\npack_dialog.packing_file = Komprimerer %1\npack_dialog.error_title = Komprimeringsfejl\npack_dialog_description = Tilføj markerede filer til\npack_dialog.archive_format = Arkivformat\nunpack_dialog.destination = Udpak markerede fil(er) til\nunpack_dialog.error_title = Udpakningsfejl\nunpack_dialog.unpacking = Udpakker filer\nunpack_dialog.unpacking_file = Udpakker %1\noptimizing_archive = Optimerer arkiv %1\nerror_while_optimizing_archive = Fejl under optimering af arkiv %1\nmove_dialog.move_description = Flyt til\nmove_dialog.error_title = Fejl under flytning\nmove_dialog.moving = Flytter filer\nmove_dialog.moving_file = Flytter %1\ndownload_dialog.description = Hent fil til\ndownload_dialog.error_title = Fejl under hentning\ndownload_dialog.downloading = Henter\ndownload_dialog.downloading_file = Henter %1\nmkfile_dialog.allocate_space = Alloker plads\ndelete_dialog.permanently_delete.confirmation = Slet makerede fil(er) permanent?\ndelete_dialog.move_to_trash.confirmation = Slet markerede fil(er)?\ndelete_dialog.move_to_trash.confirmation_details = Filerne flyttes til papirkurven.\ndelete_dialog.move_to_trash.option = Flyt til papirkurven\ndelete_dialog.move_to_trash.failed = En eller flere filer kunne ikke flyttes til papirkurven.\ndelete_dialog.deleting = Sletter\ndelete_dialog.error_title = Fejl under sletning\ndelete.deleting_file = Sletter %1\nemail_dialog.prefs_not_set_title = E-mail er ikke konfigureret\nemail_dialog.prefs_not_set = Du skal indstille din e-mail konfiguration først.\nemail_dialog.from = Fra\nemail_dialog.to = Til\nemail_dialog.subject = Emne\nemail_dialog.send = Send\nemail_dialog.error_title = Fejl under afsendelse af filer\nemail_dialog.read_error = Filerne i undermapperne kunne ikke læses.\nemail_dialog.sending = Sender filer\nemail.sending_file = Sender %1\nemail.connecting_to_server = Forbinder til %1\nemail.server_unavailable = Kunne ikke kontakte mailserveren %1, kontroller dine e-mail indstillinger eller forsøg igen senere.\nemail.connection_closed = Forbindelsen blev afsluttet af serveren, mailen er ikke sendt.\nemail.goodbye_failed = Fejl under lukning af forbindelse, mailen blev måske ikke sendt.\nemail.send_file_error = Kunne ikke sende filen %1, mailen er ikke sendt.\nsplit_file_dialog.error_title = Fejl under deling af fil\nsplit_file_dialog.file_to_split = Fil som skal deles\nsplit_file_dialog.target_directory = Destinations mappe\nsplit_file_dialog.part_size = Størrelse per del\nsplit_file_dialog.parts = Antal dele\nsplit_file_dialog.generate_CRC = Generer CRC fil\nsplit_file_dialog.max_parts = Maksimal antal dele er %1\nsplit_file_dialog.auto = Automatisk\nsplit_file_dialog.insert_new_media = Indsæt nyt medie\ncombine_files_dialog.error_title = Fejl under sammenfletning af filer\ncombine_files_job.no_crc_file = Sammenfletning fuldført. Ingen CRC fil.\ncombine_files_job.crc_read_error = Fejl under løsning af CRC fil.\ncombine_files_job.crc_check_failed = CRC fejl: forventede %2 men fik %1\ncombine_files_job.crc_ok = Sammenfletning fuldført. CRC kontrolsum ok.\nfile_selection_dialog.mark = Marker\nfile_selection_dialog.unmark = Afmarker\nfile_selection_dialog.mark_description = Marker filer hvis navn\nfile_selection_dialog.unmark_description = Afmarker filer hvis navn\nfile_selection_dialog.case_sensitive = Forskel på store og små bogstaver\nfile_selection_dialog.include_folders = Inkluder mapper\nprogress_dialog.starting = Starter overførsel...\nprogress_dialog.transferred = Overført %1 med %2\nprogress_dialog.elapsed_time = Forløbet tid\nprogress_dialog.advanced = Avanceret\nprogress_dialog.current_speed = Aktuel hastighed\nprogress_dialog.limit_speed = Begræns hastighed\nprogress_dialog.close_when_finished = Luk vinduet når færdig\nprogress_dialog.processing_files = Bearbejder filer\nprogress_dialog.processing_file = Bearbejder %1\nprogress_dialog.verifying_file = Verificerer %1\nprogress_dialog.job_finished = Job færdigt\nprogress_dialog.job_error = Fejl under job\nproperties_dialog.file_properties = %1 Egenskaber\nproperties_dialog.contents = Indeholder\nproperties_dialog.calculating = Udregner...\ncalculate_checksum_dialog.checksum_algorithm = Kontrolsum algoritme\ncalculate_checksum_dialog.temporary_file = Midlertidig fil\nchange_date_dialog.now = Nu\nchange_date_dialog.specific_date = Angiv dato\nrun_dialog.run_command_description = Kør i aktuel mappe\nrun_dialog.run_in_home_description = Kør i hjemmemappen\nrun_dialog.command_output = Kommando uddata\nrun_dialog.run = Kør\nrun_dialog.clear_history = Ryd historik\nserver_connect_dialog.server_type = Tilslutningstype\nserver_connect_dialog.server = Server\nserver_connect_dialog.share = Del\nserver_connect_dialog.domain = Domæne\nserver_connect_dialog.username = Brugernavn\nserver_connect_dialog.initial_dir = Startmappe\nserver_connect_dialog.port = Port\nserver_connect_dialog.server_url = Server adresse\nserver_connect_dialog.http_url = Hjemmeside adresse\nserver_connect_dialog.connect = Forbind\nserver_connect_dialog.protocol = Protokol\nserver_connect_dialog.nfs_version = NFS-version\nserver_connect_dialog.private_key = Privat nøgle\nserver_connect_dialog.passphrase = Løsen\nftp_connect.passive_mode = Aktiver passiv tilstand\nftp_connect.anonymous_user = Anonym bruger\nftp_connect.nb_connection_retries = Antal gentagende forbindelses forsøg\nftp_connect.retry_delay = Forsinkelse i mellem forsøg (i sekunder)\nhttp_connect.basic_authentication = HTTP basal godkendelse (valgfrit)\nserver_connections_dialog.disconnect = Afbryd\nserver_connections_dialog.connection_busy = Optaget\nserver_connections_dialog.connection_idle = Inaktiv\nbonjour.bonjour_services = Bonjour tjenester\nbonjour.no_service_discovered = Ingen tjenester kunne findes\nbonjour.bonjour_disabled = Bonjour deaktiveret\nauth_dialog.title = Godkendelse\nauth_dialog.desc = Indtast venligst brugernavn og kodeord\nauth_dialog.server = Server\nauth_dialog.store_credentials = Husk brugernavn og kodeord (svag kryptering)\nauth_dialog.connect_as = Forbind som\nauth_dialog.authentication_failed = Godkendelse fejlet\nsortable_list.move_up = Flyt op\nsortable_list.move_down = Flyt ned\nadd_bookmark_dialog.add = Tilføj\nedit_bookmarks_dialog.new = Ny\nfile_viewer.view_error_title = Visningsfejl\nfile_viewer.view_error = Filen kunne ikke vises.\nfile_viewer.file_menu = Filer\nfile_viewer.close = Luk\nfile_viewer.large_file_warning = Filen kan være for stor til denne handling.\nfile_viewer.open_anyway = Åben alligevel\ntext_viewer.edit = Rediger\ntext_viewer.copy = Kopier\ntext_viewer.select_all = Marker alt\ntext_viewer.find = Søg\ntext_viewer.find_next = Find næste\ntext_viewer.find_previous = Find forrige\ntext_viewer.binary_file_warning = Dette lader til at være en binær fil\nimage_viewer.controls_menu = Funktioner\nimage_viewer.zoom_in = Zoom ind\nimage_viewer.zoom_out = Zoom ud\nfile_editor.edit_error_title = Redigeringsfejl\nfile_editor.edit_error = Kunne ikke redigere filen.\nfile_editor.save = Gem\nfile_editor.save_as = Gem som...\nfile_editor.save_warning = Gem ændringer til filen inden lukning?\nfile_editor.cannot_write = Kunne ikke læse filen.\ntext_editor.cut = Klip ud\ntext_editor.paste = Sæt ind\nshortcuts_dialog.quick_search.start_search = Indtast et tegn for at starte en hurtig søgning\nshortcuts_dialog.quick_search.cancel_search = Annuller hurtig søgning\nshortcuts_dialog.quick_search.remove_last_char = Fjern det sidste tegn fra hurtig-søg strengen\nshortcuts_dialog.quick_search.jump_to_previous = Gå til forrige hurtig-søg resultat\nshortcuts_dialog.quick_search.jump_to_next = Gå til næste hurtig-søg resultat\nshortcuts_dialog.quick_search.mark_jump_next = Marker/afmarker aktuel fil og gå til næste søgeresultat\ntheme_editor.title = Tema behandler\ntheme_editor.folder_tab = Mappe panel\ntheme_editor.shell_tab = Terminal\ntheme_editor.shell_history_tab = Terminal historik\ntheme_editor.statusbar_tab = Statuslinie\ntheme_editor.free_space = Ledigt plads\ntheme_editor.free_space.ok = OK\ntheme_editor.free_space.warning = Advarsel\ntheme_editor.free_space.critical = Kritisk\ntheme_editor.locationbar_tab = Lokationslinie\ntheme_editor.editor_tab = Fil behandler\ntheme_editor.font = Skrifttype\ntheme_editor.active_panel = Aktiv\ntheme_editor.inactive_panel = Inaktiv\ntheme_editor.general = Generelt\ntheme_editor.could_not_save_theme = Kunne ikke gemme temaet %1\ntheme_editor.border = Kant\ntheme_editor.background = Baggrund\ntheme_editor.alternate_background = Alternativ baggrund\ntheme_editor.unfocused_background = Baggrund (uden fokus)\ntheme_editor.quick_search.unmatched_file = Filer som ikke matcher\ntheme_editor.text = Tekst\ntheme_editor.progress = Fremskridt\ntheme_editor.normal = Normal\ntheme_editor.normal_unfocused = Normal (i fokus)\ntheme_editor.selected = Markeret\ntheme_editor.selected_unfocused = Markeret (uden fokus)\ntheme_editor.color = Farve\ntheme_editor.colors = Farver\ntheme_editor.plain_file = Simple fil\ntheme_editor.marked_file = Markeret fil\ntheme_editor.hidden_file = Skjult fil\ntheme_editor.folder = Mappe\ntheme_editor.archive_file = Arkiv\ntheme_editor.symbolic_link = Symbolsk henvisning\ntheme_editor.header = Overskrift\ntheme_editor.item = Element\ncommand_bar_customize_dialog.available_actions = Tilgængelige handlinger\ncommand_bar_customize_dialog.modifier = Ændrer\nprefs_dialog.title = Indstillinger\nprefs_dialog.general_tab = Generelt\nprefs_dialog.day = Dag\nprefs_dialog.month = Måned\nprefs_dialog.year = År\nprefs_dialog.language = Sprog (kræver genstart)\nprefs_dialog.date_time = Dato og tidsformat\nprefs_dialog.time = Tid\nprefs_dialog.date = Dato\nprefs_dialog.date_separator = Separator\nprefs_dialog.time_12_hour = 12-timers format\nprefs_dialog.time_24_hour = 24-timers format\nprefs_dialog.show_seconds = Vis sekunder\nprefs_dialog.show_century = Vis århundrede\nprefs_dialog.check_for_updates_on_startup = Tjek for opdateringer ved opstart\nprefs_dialog.show_splash_screen = Vis opstartsbillede\nprefs_dialog.folders_tab = Mapper\nprefs_dialog.startup_folders = Startmappe\nprefs_dialog.left_folder = Venstre mappe\nprefs_dialog.right_folder = Højre mappe\nprefs_dialog.last_folder = Sidst åbne mappe\nprefs_dialog.custom_folder = Fast mappe\nprefs_dialog.show_hidden_files = Vis skjulte filer\nprefs_dialog.show_ds_store_files = Vis .DS_Store filer\nprefs_dialog.show_system_folders = Vis system mapper\nprefs_dialog.compact_file_size = Afrund filstørrelser\nprefs_dialog.follow_symlinks_when_cd = Følg symbolske henvisninger ved ændring af aktuel mappe\nprefs_dialog.appearance_tab = Udseende\nprefs_dialog.look_and_feel = Udseende og følelse\nprefs_dialog.icons_size = Ikonstørrelse\nprefs_dialog.toolbar_icons = Værktøjslinie\nprefs_dialog.command_bar_icons = Kommandolinie\nprefs_dialog.file_icons = Filtyper\nprefs_dialog.use_system_file_icons = Brug systemets filikoner\nprefs_dialog.use_system_file_icons.always = Altid\nprefs_dialog.use_system_file_icons.never = Aldrig\nprefs_dialog.use_system_file_icons.applications = Kun til programfiler\nprefs_dialog.edit_current_theme = Rediger aktuelt tema...\nprefs_dialog.themes = Temaer\nprefs_dialog.import_theme = Importer tema\nprefs_dialog.import_look_and_feel = Importer udseende (udseende og følelse)\nprefs_dialog.no_look_and_feel = Intet udseende blev fundet.\nprefs_dialog.error_in_import = Fejl ved import af temaet %1.\nprefs_dialog.cannot_read_theme = Kunne ikke hente temaet fra filen %1\nprefs_dialog.export_theme = Eksporter %1\nprefs_dialog.import = Importer\nprefs_dialog.export = Eksporter\nprefs_dialog.theme_type = Type: %1\nprefs_dialog.delete_theme = Slet temaet %1 ?\nprefs_dialog.delete_look_and_feel = Vil du permanent slette udseenet %1 ?\nprefs_dialog.rename_failed = Kunne ikke omdøbe temaet %1\nprefs_dialog.xml_file = XML-fil\nprefs_dialog.jar_file = JAR-fil\nprefs_dialog.mail_tab = E-mail\nprefs_dialog.mail_settings = Udgående e-mail indstillinger\nprefs_dialog.mail_name = Dit navn\nprefs_dialog.mail_address = Din e-mail adresse\nprefs_dialog.mail_server = SMTP-server\nprefs_dialog.misc_tab = Diverse\nprefs_dialog.use_brushed_metal = Anvend udseenet 'børstet metal' (kræver genstart)\nprefs_dialog.confirm_on_quit = Spørg efter bekræftelse ved afslutning\nprefs_dialog.default_shell = Anvend systemets standard kommandofortolker\nprefs_dialog.custom_shell = Anvend anden kommandofortolker\nprefs_dialog.shell_encoding = Kommandofortolker indkodning\nprefs_dialog.auto_detect_shell_encoding = Vælg automatisk\nprefs_dialog.enable_bonjour_discovery = Aktiver opdagelse af Bonjour tjenester\nprefs_dialog.enable_system_notifications = Aktiver system meddelelser\ndebug_console_dialog.level = Niveau\nunit.byte = byte\nunit.bytes = bytes\nunit.bytes_short = B\nunit.kb = KB\nunit.mb = MB\nunit.gb = GB\nunit.tb = TB\nunit.speed = %1/sek\nduration.seconds = %1s\nduration.minutes = %1m\nduration.hours = %1t\nduration.days = %1d\nduration.months = %1mån\nduration.years = %1år\ntheme.custom_theme = Tilpasset tema\ntheme.custom = Tilpasset\ntheme.built_in = Indbygget\ntheme.add_on = Tilføjelse\ntheme.current = Aktuel\ntheme_could_not_be_loaded = En fejl opstod ved indlæsning af dette tema.\nsetup.title = Velkommen til trolCommander\nsetup.intro = Vælg venligst hvordan du vil have trolCommander til at optræde.\nsetup.look_and_feel = Vælg udseende og følelse\nsetup.theme = Vælg tema\nfont_chooser.font_size = Størrelse\nfont_chooser.font_bold = Fed\nfont_chooser.font_italic = Kursiv\ncolor_chooser.red = Rød\ncolor_chooser.green = Grøn\ncolor_chooser.blue = Blå\ncolor_chooser.hue = Tone\ncolor_chooser.brightness = Intensitet\ncolor_chooser.swatches = Farveprøver\ncolor_chooser.saturation = Mætning\ncolor_chooser.rgb = RGB\ncolor_chooser.hsb = HSB\ncolor_chooser.recent = Seneste\ncolor_chooser.alpha = Alpha-gennemsigtig\ncolor_chooser.title = Vælg en farve\nbatch_rename_dialog.mask = Maske\nbatch_rename_dialog.search_replace = Søg og erstat\nbatch_rename_dialog.search_for = Søg efter\nbatch_rename_dialog.replace_with = Erstat med\nbatch_rename_dialog.counter = Tæller\nbatch_rename_dialog.start_at = Start på\nbatch_rename_dialog.step_by = Trin\nbatch_rename_dialog.format = Format\nbatch_rename_dialog.upper_lower_case = Store/små bogstaver\nbatch_rename_dialog.no_change = Uændret\nbatch_rename_dialog.lower_case = små bogstaver\nbatch_rename_dialog.upper_case = STORE BOGSTAVER\nbatch_rename_dialog.first_upper = Første bogstaver stort\nbatch_rename_dialog.word = Første Af Hvert Ord\nbatch_rename_dialog.old_name = Gammelt navn\nbatch_rename_dialog.new_name = Nyt navn\nbatch_rename_dialog.block_name = Gruppe\nbatch_rename_dialog.range = Område\nbatch_rename_dialog.proceed_renaming = %1 filer ud af %2 vil blive omdøbt. Ønsker du at forsætte?\nbatch_rename_dialog.duplicate_names = Duplikerede navne!\nparent_folders_quick_list.empty_message = Der er ikke nogle ovenliggende mapper til denne placering\nrecent_locations_quick_list.empty_message = Der er ingen nyeligt tilgåede placeringer\nrecent_executed_files_quick_list.empty_message = Ingen nyeligt kørte filer\n#auth_dialog.error_was = Fejlen var: %1\n#table.hide_column = Skjul kolonne\n#delete.symlink_warning_title = Symbolsk henvisning fundet\n#delete.symlink_warning = Filen ligner en symbolsk henvisning:\\n\\n  Fil: %1\\n  Henviser til: %2\\n\\nSlet kun henvisningen eller\\nfølg henvisningen og slet mappen (ADVARSEL)?\n#delete.delete_link_only = Slet henvisning\n#delete.delete_linked_folder = Slet mappe\n#Unpack.label = Udpak filer\n#Pack.label = Komprimer filer\n"
  },
  {
    "path": "src/main/resources/dictionary_de_DE.properties",
    "content": "ok = OK\nyes = Ja\nno = Nein\ncancel = Abbrechen\nedit = Bearbeiten\nclose = Schließen\nreset = Zurücksetzen\nrename = Umbenennen\napply = Auf alle anwenden\nchange = Verändern\nsave = Speichern\ndont_save = Nicht speichern\nreplace = Ersetzen\ndont_replace = Nicht ersetzen\ndelete = Löschen\nskip = Überspringen\nskip_all = Alle Überspringen\noverwrite_all = Alles überschreiben\nretry = Erneut versuchen\nresume = Weiter\noverwrite = Überschreiben\noverwrite_if_older = Ältere überschreiben\nduplicate = Duplizieren\napply_to_all = Zuweisen an alle\ncopy = Kopieren\nmove = Verschieben\npack = Packen\nunpack = Entpacken\ndownload = Download\nsplit = Aufteilen\ncombine = Zusammenfügen\nbrowse = Browsen\nask = Fragen\nstop = Stopp\npause = Pause\nquick_search = Schnellsuche\nfile_manager = Dateimanager\ncreate = Erstellen\ncreating_file = Erzeugen %1\nchoose = Auswählen\ncustomize = Anpassen\nclean = Säubern\nsearch = Suchen\nchoose_folder = Wähle ein Verzeichnis\nlogin = Login\npassword = Passwort\nuser = Benutzer\nencoding = Kodierung\npreferred_encodings = Bevorzugte Kodierungen\nlicense = Lizenz\nname = Name\nsize = Größe\ndate = Datum\nextension = Dateierweiterung\npermissions = Zugriffsrechte\nowner = Eigentümer\ngroup = Gruppe\nlocation = Ort\nuntitled = Unbenannt\nsource = Quelle\ndestination = Ziel\nrecurse_directories = Auf Unterverzeichnisse anwenden\ngo_to = Gehe zu\nexample = Beispiel\npreview = Vorschau\ncomment = Kommentar\nsample_text = Beispieltext\nnb_files = %1 Datei(en)\nnb_folders = %1 Ordner\nloading = lade...\nthis_operation_cannot_be_undone = Diese Operation kann nicht rückgängig gemacht werden.\nremove = Entfernen\ndetails = Details\ntitle = Titel\nwarning = Warnung\nerror = Fehler\ngeneric_error = Während der angeforderten Operation ist ein Fehler aufgetreten\nfolder_does_not_exist = Dieser Ordner existiert nicht oder ist nicht verfügbar.\nthis_folder_does_not_exist = Dieser Ordner existiert nicht oder ist nicht verfügbar: %1\nthis_file_does_not_exist = Die Datei existiert nicht oder ist nicht verfügbar: %1\ninvalid_path = Ungültiger Pfad: %1\ndirectory_already_exists = Ordner %1 existiert bereits.\nfile_exists_in_destination = Datei existiert schon am Ziel\nsource_parent_of_destination = Versuch ein Verzeichnis in ein Unterverzeichnis zu übertragen\ncannot_read_file = Kann Datei %1 nicht lesen\ncannot_write_file = Kann Datei %1 nicht schreiben\noverwrite_readonly_file = Die Datei ist schreibgeschützt: %1\ncannot_create_folder = Kann Ordner %1 nicht erstellen\ncannot_read_folder = Kann Inhalt des Ordners %1 nicht lesen\ncannot_delete_file = Kann Datei %1 nicht löschen\ncannot_delete_folder = Kann Ordner %1 nicht löschen\nerror_while_transferring = Fehler beim Transfer von Datei %1\nsame_source_destination = Quell-und Zielordner sind gleich\nfile_already_exists = %1 existiert bereits, soll sie überschrieben werden ?\nwrite_error = Schreibfehler\nread_error = Lesefehler\nintegrity_check_error = Integritätstest fehlgeschlagen: Quelle und Ziel stimmen nicht überein\nstartup_error = Ein Fehler hat trolCommander am Start gehindert.\nimage_size = Bildgröße\ncannot_write_symlink = Kann Symlink nicht schreiben: %1\ncannot_write_symlink_already_exists = Symlink ist schon vorhanden: %1\ncannot_write_symlink_access_denied = Kann Symlink %1 nicht schreiben, Zugriff verweigert\npermissions.read = Lesen\npermissions.write = Schreiben\npermissions.executable = Ausführen\npermissions.user = Benutzer\npermissions.group = Gruppe\npermissions.other = Andere\npermissions.octal_notation = Oktale Schreibweise\naction_categories.all = Alle\naction_categories.navigation = Navigation\naction_categories.selection = Auswahl\naction_categories.view = Ansicht\naction_categories.file_operations = Dateioperationen\naction_categories.windows = Fenster\naction_categories.tabs = Tabs\naction_categories.commands = Benuzterbefehle\naction_categories.misc = Sonstiges\nTerminal.label = Terminal\nTerminal.tooltip = Terminal\nTerminalPanel.label = Terminal\nTerminalPanel.tooltip = Terminalfenster anzeigen/verbergen\nFindFile.label = Datei suchen\nFindFile.tooltip = Datei suchen\nAddBookmark.label = Lesezeichen hinzufügen\nAddBookmark.tooltip = Füge den aktuellen Ordner zur Liste der Lesezeichen hinzu\nBatchRename.label = Mehrfach Umbenennung\nEditBookmarks.label = Lesezeichen bearbeiten\nExploreBookmarks.label = Untersuche Lesezeichen\nEditCredentials.label = Zugriffsrechte bearbeiten\nChangeLocation.label = Ändere aktuelles Verzeichnis\nChangeDate.label = Datum ändern\nChangeDate.tooltip = Datum der ausgewählten Datei(en) bearbeiten\nChangePermissions.label = Zugriffsrechte bearbeiten\nChangePermissions.tooltip = Zugriffsrechte der ausgewählten Datei(en) bearbeiten\nCheckForUpdates.label = Nach Updates suchen\nCompareFolders.label = Ordner vergleichen\nCompareFolderFiles.label = Dateien vergleichen\nCompareFolderFiles.tooltip = Vergleiche Dateien und markiere geänderte im aktuellen Ordner\nConnectToServer.label = Verbinde mit Server\nConnectToServer.tooltip = Mit einem Remote Server verbinden\nView.label = Anzeigen\nViewAs.label = Anzeigen als\nInternalView.label = Anzeigen (eingebaut)\nviewer_type.text = Textdatei\nviewer_type.hex = Binärdatei\nviewer_type.image = Bild\nviewer_type.pdf = PDF Dokument\nviewer_type.audio = Audiodatei\nviewer_type.html = HTML Dokument\nView.tooltip = Markierte Datei betrachten\nEdit.label = Bearbeiten\nInternalEdit.label = Editor (eingebaut)\nCalculator.label = Rechner\nCreateSymlink.label = Symlink erstellen\nLocateSymlink.label = Gehe zum Ziel des Symlinks\nShowFoldersSize.label = Zeige Ordnergröße\nEditCommands.label = Bearbeite Befehle\nEditCommands.group.view = Betrachter\nEditCommands.group.edit = Editoren\nEditCommands.group.others = Andere\nCompareFiles.label = Vergleiche Dateien\nCompareFiles.tooltip = Vergleiche Textdateien (FileMerge)\nEditCommands.new = Neu\nEditCommands.alias = Alias\nEditCommands.command = Befehl\nEditCommands.display_name = Anzeigename\nEditCommands.filemask = Dateifilter\nsymboliclinkeditor.edit = Bearbiete Symlink\nsymboliclinkeditor.create = Symbolische Verknüpfung\nsymboliclinkeditor.target_file_create = Bestehender Dateiname (Dateiname auf den der Symlink zeigen wird)\nsymboliclinkeditor.target_file_edit = Symlink '%s' zeigt auf\nsymboliclinkeditor.link_name = Dateiname für symbolische Verknüpfung\nEdit.tooltip = Bearbeite ausgewählte Datei\nCopy.label = Kopieren\nCopy.tooltip = Markierte Dateien kopieren\nLocalCopy.tooltip = Kopiert markierte Datei in den aktuellen Ordner\nLocalCopy.label = Lokale Kopie\nMove.label = Verschieben\nMove.tooltip = Markierte Dateien verschieben\nRename.label = Umbenennen\nRename.tooltip = Markierte Datei umbenennen\nMkdir.label = Ordner erstellen\nMkdir.tooltip = Erstellt einen Ordner im aktuellen Ordner\nMkfile.label = Neue Datei anlegen\nMkfile.tooltip = Neue Datei im aktuellen Verzeichnis anlegen\nDelete.label = Löschen\nDelete.tooltip = Markierte Dateien in den Papierkorb verschieben (falls möglich) \nPermanentDelete.label = unwiderruflich löschen\nPermanentDelete.tooltip = Markierte Dateien unwiderruflich löschen\nRefresh.label = Aktualisieren\nRefresh.tooltip = Aktualisiere aktuellen Ordner\nCloseWindow.label = Fenster schließen\nCloseWindow.tooltip = Schließt aktuelles Fenster\nCopyFileNames.label = Kopiere Name(n)\nCopyFileBaseNames.label = Kopiere Basisname(n)\nCopyFilePaths.label = Kopiere Pfad(e)\nCopyFilesToClipboard.label = Kopiere Datei(en)\nPasteClipboardFiles.label = Einfügen Datei(en)\nEmail.label = Dateien mailen\nEmail.tooltip = Markierte Dateien als eMail Anhang versenden\nGoBack.label = Zurueck gehen\nGoBack.tooltip = Zum vorigen Ordner gehen\nGoForward.label = Vorwärts gehen\nGoForward.tooltip = Zum nächsten Ordner gehen\nGoToHome.label = Gehe zum Heimat-Verzeichnis\nGoToParent.label = Gehe Ebene höher\nGoToParent.tooltip = Zum übergeordneten Ordner gehen\nGoToParentInOtherPanel.label = Im anderen Panel zum übergeordneten Ordner gehen\nGoToParentInBothPanels.label = Im beiden Paneln zum übergeordneten Ordner gehen\nGoToRoot.label = Gehe zum Wurzel-Verzeichnis\nSortByName.label = Nach Namen sortieren\nSortByDate.label = Nach Datum sortieren\nSortBySize.label = Nach Größe sortieren\nSortByExtension.label = Nach Erweiterung sortieren\nSortByPermissions.label = Nach Zugriffsrechten sortieren\nSortByOwner.label = Nach Eigentümer sortieren\nSortByGroup.label = Nach Gruppe sortieren\nMarkGroup.label = Dateien markieren\nMarkGroup.tooltip = Eine Gruppe von Dateien markieren\nUnmarkGroup.label = Dateien demarkieren\nUnmarkGroup.tooltip = Eine Gruppe von Dateien demarkieren\nMarkAll.label = Alle markieren\nUnmarkAll.label = Alle demarkieren\nMarkSelectedFile.label = Markieren/Demarkieren\nMarkSelectedFile.tooltip = Datei markieren/demarkieren\nMarkNextBlock.label = Nächsten Block markieren\nMarkPreviousBlock.label = Vorigen Block markieren\nMarkNextRow.label = Nächste Zeile markieren\nMarkPreviousRow.label = Vorige Zeile markieren\nMarkNextPage.label = Dateien bis zur nächsten Seite Markieren\nMarkPreviousPage.label = Dateien bis zur vorigen Seite Markieren\nMarkToFirstRow.label = Dateien bis zum Anfang Markieren\nMarkToLastRow.label = Dateien bis zum Ende Markieren\nMarkExtension.label = Erweiterung markieren\nMarkEmpty.label = Leere markieren\nInvertSelection.label = Auswahl umkehren\nSwapFolders.label = Fenster vertauschen\nSwapFolders.tooltip = Linkes und rechtes Fenster vertauschen\nSetSameFolder.label = Auf selben Ordner setzen\nSetSameFolder.tooltip = Gleicher Ordner für linkes und rechtes Fenster\nToggleTableViewModeFull.label = Vollansichtsmodus\nToggleTableViewModeFull.tooltip = Auf Vollansicht umschalten\nToggleTableViewModeCompact.label = Kompakter Modus\nToggleTableViewModeCompact.tooltip = Auf Kompaktansicht umschalten\nToggleTableViewModeShort.label = Kurzansichtmodus\nToggleTableViewModeShort.tooltip = Auf Kurzansichtmodus umschalten\nNewWindow.label = Neues Fenster\nNewWindow.tooltip = Ein neues Fenster öffnen\nOpen.label = Öffnen\nOpen.tooltip = Ordner öffnen / Archiv öffnen / Ausführen\nOpenNatively.label = Natürlich Öffnen\nOpenNatively.tooltip = Markierte Datei mit assoziiertem Programm öffnen\nOpenInNewTab.label = In neuem Panel öffnen\nOpenInOtherPanel.label = In anderem Panel öffnen\nOpenInBothPanels.label = In beiden Paneln öffnen\nOpenLeftInRightPanel.label = Linkes Panel in rechtem Panel öffnen\nOpenRightInLeftPanel.label = Rechtes Panel in linkem Panel öffnen\nRevealInDesktop.label = Öffne in %1\nRunCommand.label = Befehl ausführen\nRunCommand.tooltip = Einen Befehl im aktuellen Ordner ausführen\nPack.label = Dateien packen\nPack.tooltip = Markierte Dateien in ein Archiv packen\nUnpack.label = Dateien entpacken\nUnpack.tooltip = Markierte Dateien aus dem Archiv extrahieren\nShowFileProperties.label = Eigenschaften\nShowFileProperties.tooltip = Eigenschaften der markierten Dateien anzeigen\nShowPreferences.label = Einstellungen\nShowPreferences.tooltip = trolCommander konfigurieren\nShowServerConnections.label = Zeige offene Verbindungen\nQuit.label = Beenden\nReverseSortOrder.label = Sortierung umdrehen\nToggleAutoSize.label = Spaltengröße automatisch bestimmen\nStop.label = Ordnerwechsel abbrechen\nToggleColumn.show = Zeige Spalte %1\nToggleColumn.hide = Verberge Spalte %1\nToggleCommandBar.show = Befehlsleiste anzeigen\nToggleCommandBar.hide = Befehlsleiste verbergen\nToggleHiddenFiles.label = Zeige versteckte Dateien\nToggleToolBar.show = Symbolleiste anzeigen\nToggleToolBar.hide = Symbolleiste verbergen\nCustomizeCommandBar.label = Kommandozeile anpassen\nToggleStatusBar.show = Statusleiste anzeigen\nToggleStatusBar.hide = Statusleiste verbergen\nToggleShowFoldersFirst.label = Ordner zuerst anzeigen\nToggleFoldersAlwaysAlphabetical.label = Ordner immer alphabetisch ordnen\nToggleTree.label = Zeige Baumansicht\nToggleSinglePanel.label = Einzelnes Panel\nPopupLeftDriveButton.label = Linken Ordner ändern\nPopupRightDriveButton.label = Rechten Ordner ändern\nRecallPreviousWindow.label = Zum vorherigen Fenster wechseln\nRecallNextWindow.label = Zum nächsten Fenster wechseln\nRecallWindow.label = Fenster #%1 aufrufen\nRecallWindow1.label = Rufe Fenster #%1 auf\nRecallWindow2.label = Rufe Fenster #%1 auf\nRecallWindow3.label = Rufe Fenster #%1 auf\nRecallWindow4.label = Rufe Fenster #%1 auf\nRecallWindow5.label = Rufe Fenster #%1 auf\nRecallWindow6.label = Rufe Fenster #%1 auf\nRecallWindow7.label = Rufe Fenster #%1 auf\nRecallWindow8.label = Rufe Fenster #%1 auf\nRecallWindow9.label = Rufe Fenster #%1 auf\nRecallWindow10.label = Rufe Fenster #%1 auf\nBringAllToFront.label = Alle Fenster nach vorne bringen\nSwitchActiveTable.label = Zwischen linkem und rechtem Fenster wechseln\nSelectNextBlock.label = Zum nächsten Block springen\nSelectPreviousBlock.label = Zum vorigen Block springen\nSelectNextPage.label = Nächste Seite\nSelectPreviousPage.label = Vorige Seite\nSelectNextRow.label = Nächste Zeile\nSelectPreviousRow.label = Vorige Zeile\nSelectFirstRow.label = Erste Datei im aktuellen Ordner markieren\nSelectLastRow.label = Letzte Datei im aktuellen Ordner markieren\nSplitEqually.label = Gleichmäßig aufteilen\nSplitVertically.label = Vertikal aufteilen\nSplitHorizontally.label = Horizontal aufteilen\nShowKeyboardShortcuts.label = Tastaturkürzel\nGoToWebsite.label = Zur Internetseite\nGoToForums.label = Zum Forum\nReportBug.label = Einen Fehler melden\nDonate.label = Geld spenden\nShowAbout.label = Über trolCommander\nOpenTrash.label = Papierkorb öffnen\nEmptyTrash.label = Papierkorb leeren\nCalculateChecksum.label = Prüfsumme berechnen\nMaximizeWindow.label = Maximieren\nMaximizeWindow.label.mac_os_x = Zoomen\nMinimizeWindow.label = Minimieren\nGoToDocumentation.label = Online Dokumentation\nShowParentFoldersQL.label = Übergeordnetes Verzeichnis\nShowRecentLocationsQL.label = Kürzlich besuchte Ordner\nShowRecentExecutedFilesQL.label = Kürzlich ausgeführte Programme\nShowRootFoldersQL.label = Wurzelverzeichnisse\nShowTabsQL.label = Öffne Tabs\nShowRecentViewedFilesQL.label = Kürzlich betrachtete Dateien\nShowRecentEditedFilesQL.label = Kürzlich bearbeitete Dateien\nSplitFile.label = Teilen\nSplitFile.tooltip = Datei in mehrere Teile aufspalten\nCombineFiles.label = Zusammenfügen\nCombineFiles.tooltip = Dateiteile zum Original zusammenfügen\nShowDebugConsole.label = Debug-Konsole\nFocusPrevious.label = Fokus in vorherige Komponente\nFocusNext.label = Fokus in nächste Komponente\nShowBookmarksQL.label = Lesezeichen\nEjectDrive.label = Laufwerk auswerfen\nEjectDrive.tooltip = Laufwerk sicher entfernen\nfile_menu = Datei\nfile_menu.open_with = Öffne mit\nfile_menu.open_as = Öffnen als\nmark_menu = Markieren\nview_menu = Ansicht\nview_menu.show_hide_columns = Spalten ein-/ausblenden\nview_menu.table_mode = Modus\ngo_menu = Gehe\ntools_menu = Werkzeuge\neject_menu = Laufwerk auswerfen\nbookmarks_menu = Lesezeichen\nbookmarks_menu.no_bookmark = Keine Lesezeichen\ndrive_popup.network_shares = Netzwerkfreigaben\nquick_lists_menu = Quick Listen\nwindow_menu = Fenster\nhelp_menu = Hilfe\nstatus_bar.selected_files = %1 von %2 markiert\nstatus_bar.connecting_to_folder = Verbindungsaufbau zu Ordner, drücke ESC um Abzubrechen.\nstatus_bar.volume_free = Frei: %1\nstatus_bar.volume_capacity = Speicherkapazität: %1\nshortcuts_panel.title = Tastenkürzel\nshortcuts_panel.restore_defaults = Standard wiederherstellen\nshortcuts_panel.show = Zeigen\nshortcuts_panel.search = Suchen\nshortcuts_panel.default_message = Drücken Sie Enter oder führen Sie einen Doppelklick auf dem Tastenkürzel aus, dass sie bearbeiten wollen\nshortcuts_table.action_description = Beschreibung der Aktion\nshortcuts_table.shortcut = Tastenkürzel\nshortcuts_table.alternate_shortcut = Alternatives Tastenkürzel\nshortcuts_table.type_in_a_shortcut = Tastenkürzel eingeben\ncommand_bar_dialog.help = Verschieben Sie die Schaltflächen um die Kommandozeile anzupassen\ntable.folder_access_error_title = Fehler beim Ordnerzugriff\ntable.folder_access_error = Kann Ordnerinhalt nicht lesen\ntable.download_or_browse = Wollen sie zur Datei browsen oder sie downloaden?\nAddTab.label = Tab hinzufügen\nAddTab.tooltip = Neuen Tab im aktiven Fenster hinzufügen\nToggleLockTab.lock = Fixieren\nToggleLockTab.unlock = Freigeben\nToggleLockTab.label = Fixieren/Freigeben\nToggleLockTab.tooltip = Ändere den Fixierungs-Status des Tabs\nDuplicateTab.label = Duplizieren\nDuplicateTab.tooltip = Dupliziert den aktiven Tab im gleichen Fenster\nCloseDuplicateTabs.label = Duplikate schließem\nCloseDuplicateTabs.tooltip = Schließe doppelte Tabs\nCloseOtherTabs.label = Schließe Andere\nCloseOtherTabs.tooltip = Schließe die anderen Tabs\nCloseTab.label = Schließen\nCloseTab.tooltip = Schließe diesen Tab\nMoveTabToOtherPanel.label = Zum anderen Fenster bewegen\nMoveTabToOtherPanel.tooltip = Verschiebe den Tab zum anderen Fenster\nCloneTabToOtherPanel.label = Zum anderen Fenster duplizieren\nCloneTabToOtherPanel.tooltip = Füge im anderen Fenster einen gleichen Tab hinzu\nNextTab.label = Nächster Tab\nNextTab.tooltip = Schalte zum Tab nach rechts weiter\nPreviousTab.label = Vorheriger Tab\nPreviousTab.tooltip = Schalte zum Tab nach links weiter\nSetTabTitle.label = Titel setzen\nSetTabTitle.tooltip = Setze einen festen Titel für den Tab\nversion_dialog.no_new_version_title = Keine neue Version\nversion_dialog.no_new_version = Gratulation, sie haben schon die aktuellste Version.\nversion_dialog.new_version_title = Neu Version verfügbar\nversion_dialog.new_version = Eine neue Version des trolCommander ist verfügbar.\nversion_dialog.new_version_url = Eine neue Version des trolCommander ist bei %1 erhältlich.\nversion_dialog.not_available_title = Server nicht erreichbar\nversion_dialog.not_available = Kann die Versionsinformation nicht vom Server laden.\nversion_dialog.install_and_restart = Installieren und Neustart\nversion_dialog.preparing_for_update = Vorbereitungen für den Update...\nquit_dialog.title = trolCommander beenden\nquit_dialog.desc = Sie haben %1 offene(s) Fenster. Alle Fenster schließen und trolCommander beenden?\nquit_dialog.show_next_time = Nächstes Mal anzeigen\ndestination_dialog.file_exists_action = Standard Aktion wenn Datei existiert\ndestination_dialog.verify_integrity = Daten Integrität überprüfen\ndestination_dialog.skip_errors = Fehler ignorieren\ndestination_dialog.background_mode = Im Hintergrund\nfile_collision_dialog.title = Dateikonflikt\nrename_dialog.new_name = Neuer Name\ncopy_dialog.destination = Kopiere markierte Datei(en) nach\ncopy_dialog.error_title = Kopierfehler\ncopy_dialog.copying = Kopiere Dateien\ncopy_dialog.copying_file = Kopiere %1\npack_dialog.packing = Packe Dateien\npack_dialog.packing_file = Packe Datei %1\npack_dialog.error_title = Fehler beim Packen\npack_dialog_description = Markierte Dateien hinzufügen zu\npack_dialog.archive_format = Archiv Format\nunpack_dialog.destination = Entpacke markierte Datei(en) nach\nunpack_dialog.error_title = Fehler beim Entpacken\nunpack_dialog.unpacking = Entpacke Dateien\nunpack_dialog.unpacking_file = Entpacke %1\noptimizing_archive = Archiv optimieren  %1\nerror_while_optimizing_archive = Fehler bei der Optimierung des Archivs %1\nmove_dialog.move_description = Verschieben nach\nmove_dialog.error_title = Fehler beim Verschieben\nmove_dialog.moving = Verschiebe Dateien\nmove_dialog.moving_file = Verschiebe %1\ndownload_dialog.description = Downloaden nach\ndownload_dialog.error_title = Fehler beim Download\ndownload_dialog.downloading = Downloaden\ndownload_dialog.downloading_file = Download %1\ndownload_dialog.downloading_file = Download von %1\nmkfile_dialog.allocate_space = Speicherplatz bereitstellen\nmkfile_dialog.open_in_editor = Im Texteditor öffnen\nmkfile_dialog.make_executable = Ausführbare Datei\ndelete_dialog.permanently_delete.confirmation = Markierte Datei(en) permanent löschen ?\ndelete_dialog.move_to_trash.confirmation = Ausgewählte Datei(en) löschen ?\ndelete_dialog.move_to_trash.confirmation_details = Dateien werden in den Papierkorb bewegt.\ndelete_dialog.move_to_trash.option = In den Papierkorb verschieben\ndelete_dialog.move_to_trash.failed = Eine oder mehrere Dateien konnten nicht in den Papierkorb verschoben werden.\ndelete_dialog.deleting = Löschen\ndelete_dialog.error_title = Fehler beim Löschen\ndelete.deleting_file = Lösche %1\nemail_dialog.prefs_not_set_title = eMail nicht konfiguriert\nemail_dialog.prefs_not_set = Nehmen sie erst die eMail Einstellungen vor.\nemail_dialog.from = Von\nemail_dialog.to = An\nemail_dialog.subject = Betreff\nemail_dialog.send = Senden\nemail_dialog.error_title = Fehler beim Senden der Dateien\nemail_dialog.read_error = Kann Dateien im Unterordner nicht lesen.\nemail_dialog.sending = Sende Dateien\nemail.sending_file = Sende %1\nemail.connecting_to_server = Verbinden mit %1\nemail.server_unavailable = Kann Verbindung zu Server %1 nicht herstellen, überprüfen sie die eMail Einstellungen oder versuchen sie es später.\nemail.connection_closed = Verbindung vom Server unterbrochen, eMail nicht gesendet.\nemail.goodbye_failed = Fehler beim Schließen der Verbindung, etvl. wurde die eMail nicht gesendet.\nemail.send_file_error = Kann Datei %1 nicht senden, eMail nicht gesendet.\nsplit_file_dialog.error_title = Fehler beim Aufspalten einer Datei \nsplit_file_dialog.file_to_split = Datei zum Aufspalten\nsplit_file_dialog.target_directory = Zielverzeichnis\nsplit_file_dialog.part_size = Größe eines Teils\nsplit_file_dialog.parts = Anzahl der Teile\nsplit_file_dialog.generate_CRC = Erzeuge CRC-Datei\nsplit_file_dialog.max_parts = Maximale Anzehl der Teile ist %1\nsplit_file_dialog.auto = Automatisch\nsplit_file_dialog.insert_new_media = Neues Medium einlegen\ncombine_files_dialog.error_title = Fehler beim Zusammenfügen der Datei\ncombine_files_job.no_crc_file = Zusammenfügen der Datei erfolgreich. Keine CRC-Datei.\ncombine_files_job.crc_read_error = Fehler beim Lesen der CRC-Datei.\ncombine_files_job.crc_check_failed = CRC-Fehler: erwartet %2, gefunden %1\ncombine_files_job.crc_ok = Zusammenfügen der Datei erfolgreich. CRC-Prüfsumme o.k.\nfile_selection_dialog.mark = Markieren\nfile_selection_dialog.unmark = Demarkieren\nfile_selection_dialog.mark_description = Markiere Dateien deren Name\nfile_selection_dialog.unmark_description = Demarkiere Dateien deren Name\nfile_selection_dialog.case_sensitive = Groß-/Kleinschreibung beachten\nfile_selection_dialog.include_folders = Ordner einschliessen\nprogress_dialog.starting = Transfer startet...\nprogress_dialog.transferred = Transferiert %1 mit %2\nprogress_dialog.elapsed_time = Zeitdauer\nprogress_dialog.advanced = Erweitert\nprogress_dialog.current_speed = Aktuelle Geschwindigkeit\nprogress_dialog.limit_speed = Maximal Geschwindigkeit\nprogress_dialog.close_when_finished = Nach Ende Fenster schließen\nprogress_dialog.processing_files = Bearbeite Dateien\nprogress_dialog.processing_file = Bearbeite %1\nprogress_dialog.verifying_file = Überprüfung %1\nprogress_dialog.job_finished = Job beendet\nprogress_dialog.job_error = Jobfehler\nprogress_dialog.hide = Verbergen\nproperties_dialog.file_properties = Eigenschaften von %1\nproperties_dialog.contents = Inhalt\nproperties_dialog.calculating = Kalkuliere...\ncalculate_checksum_dialog.checksum_algorithm = Prüfsummen Algorithmus\ncalculate_checksum_dialog.temporary_file = Temporäre Datei\nchange_date_dialog.now = Jetzt\nchange_date_dialog.specific_date = Spezielles Datum\nrun_dialog.run_command_description = Ausführen im aktuellen Ordner\nrun_dialog.run_in_home_description = In Heimatverzeichnis ausführen\nrun_dialog.command_output = Befehlsausgabe\nrun_dialog.run = Ausführen\nrun_dialog.stop = Anhalten\nrun_dialog.clear_history = Verlauf löschen\nserver_connect_dialog.server_type = Verbindungstyp\nserver_connect_dialog.server = Server\nserver_connect_dialog.share = Freigabe\nserver_connect_dialog.domain = Domäne\nserver_connect_dialog.username = Benutzername\nserver_connect_dialog.initial_dir = Startordner\nserver_connect_dialog.port = Port\nserver_connect_dialog.server_url = Server URL\nserver_connect_dialog.http_url = URL Webseite\nserver_connect_dialog.connect = Verbinden\nserver_connect_dialog.protocol = Protokoll\nserver_connect_dialog.nfs_version = NFS-Version\nserver_connect_dialog.private_key = Privater Schlüssel\nserver_connect_dialog.passphrase = Passwort\nftp_connect.passive_mode = Passiven Modus aktivieren\nftp_connect.anonymous_user = Anonymer Benutzer\nftp_connect.nb_connection_retries = Maximale Anzahl Verbindungsversuche\nftp_connect.retry_delay = Wartezeit zwischen Verbindungsversuchen (in Sekunden)\nhttp_connect.basic_authentication = HTTP Basic Authentication (optional)\nserver_connections_dialog.disconnect = Verbindung Abbrechen\nserver_connections_dialog.connection_busy = Besetzt\nserver_connections_dialog.connection_idle = Inaktiv\nvsphere_connections_dialog.guest_server = Gast Server %1\nvsphere_connections_dialog.guest_user = Gast Username\nvsphere_connections_dialog.guest_password = Gast Passwort\nbonjour.bonjour_services = Bonjour Dienste\nbonjour.no_service_discovered = Keine Dienste gefunden\nbonjour.bonjour_disabled = Bonjour nicht aktiv\nauth_dialog.title = Authentifizierung\nauth_dialog.desc = Bitte Login und Passwort eingeben\nauth_dialog.server = Server\nauth_dialog.store_credentials = Speichere Login und Passwort (schwach verschlüsselt)\nauth_dialog.connect_as = Verbinde als\nauth_dialog.authentication_failed = Authentifizierung fehlgeschlagen\nsortable_list.move_up = Nach oben\nsortable_list.move_down = Nach unten\nadd_bookmark_dialog.add = Hinzufügen\nedit_bookmarks_dialog.location = Ort\nedit_bookmarks_dialog.new = Neu\nfile_viewer.view_error_title = Fehler beim Betrachten\nfile_viewer.view_error = Datei kann nicht betrachtet werden.\nfile_viewer.file_menu = Datei\nfile_viewer.close = Schließen\nfile_viewer.large_file_warning = Diese Datei könnte für diese Operation zu groß sein.\nfile_viewer.open_anyway = Trotzdem öffnen\nfile_viewer.open_hex = Hex Ansicht\ntext_viewer.edit = Bearbeiten\ntext_viewer.copy = Kopieren\ntext_viewer.select_all = Alles markieren\ntext_viewer.find = Suchen\ntext_viewer.find_next = Weitersuchen\ntext_viewer.find_previous = Vorherigen suchen\ntext_viewer.view = Ansicht\ntext_viewer.line_wrap = Zeilenumbruch\ntext_viewer.line_numbers = Zeilennummern\ntext_viewer.binary_file_warning = Dies scheint eine Binärdatei zu sein\ntext_viewer.goto_line = Gehe zu Zeile\ntext_viewer.line = Zeile\nimage_viewer.controls_menu = Steuerung\nimage_viewer.zoom_in = Hinein zoomen\nimage_viewer.zoom_out = Heraus zoomen\nfile_editor.edit_error_title = Fehler beim Bearbeiten\nfile_editor.edit_error = Kann Datei nicht bearbeiten.\nfile_editor.save = Speichern\nfile_editor.save_as = Speichern als...\nfile_editor.add_to_bookmark = Lesezeichen für Datei\nfile_editor.save_warning = Änderungen an der Datei vor dem Schließen speichern ?\nfile_editor.cannot_write = Kann Datei nicht schreiben.\nfile_editor.files = Dateien\nfile_editor.file_menu = Datei\nfile_editor.show_file_manager = Zeige Datei-Manager\nfile_editor.close = Schließen\nfile_editor.open_anyway = Trotzdem öffnen\nfile_editor.save_anyway = Trotzdem speichern\nfile_editor.overwrite_readonly = Die Datei ist schreibgeschützt\ntext_editor.cut = Ausschneiden\ntext_editor.paste = Einfügen\ntext_editor.undo = Rückgänging\ntext_editor.redo = Wiederholen\ntext_editor.edit = Bearbeiten\ntext_editor.copy = Kopieren\ntext_editor.select_all = Alles auswählen\ntext_editor.view = Ansicht\ntext_editor.find = Suchen\ntext_editor.find_next = Suche nächste\ntext_editor.find_previous = Suche vorherige\ntext_editor.search = Suchen\ntext_editor.replace_menu = Ersetzen...\ntext_editor.line_wrap = Zeilenumbruch\ntext_editor.line_numbers = Zeilennummern\ntext_editor.syntax = Syntax\ntext_editor.format = Format\ntext_editor.writing = Schreibe…\ntext_editor.modified = Geändert\ntext_editor.saved = Datei gespeichert\ntext_editor.invisible_chars = Unsichtbare Zeichen\ntext_editor.text_not_found = Text nicht gefunden\ntext_editor.cant_save_file = Kann Datei nicht speichern\nshortcuts_dialog.quick_search = Schnellsuche\nshortcuts_dialog.quick_search.start_search = Geben Sie ein beliebiges Zeichen ein um die Schnellsuche zu starten\nshortcuts_dialog.quick_search.cancel_search = Schnellsuche abbrechen\nshortcuts_dialog.quick_search.remove_last_char = Letztes Zeichen aus der Schnellsuche entfernen\nshortcuts_dialog.quick_search.jump_to_previous = Springe zum vorigen Ergebnis der Schnellsuche\nshortcuts_dialog.quick_search.jump_to_next = Springe zum nächsten Ergebnis der Schnellsuche\nshortcuts_dialog.quick_search.mark_jump_next = Markiere/Demarkiere aktuelle Datei und springe zum nächsten Suchergebnis\ntheme_editor.title = Themen-Editor\ntheme_editor.folder_tab = Ordnerbereich\ntheme_editor.shell_tab = Kommandozeile\ntheme_editor.shell_history_tab = Shell Historie\ntheme_editor.terminal_tab = Terminal\ntheme_editor.statusbar_tab = Statuszeile\ntheme_editor.free_space = Free Speicher\ntheme_editor.free_space.ok = OK\ntheme_editor.free_space.warning = Warnung\ntheme_editor.free_space.critical = Kritisch\ntheme_editor.locationbar_tab = Bereich aktueller Ordner\ntheme_editor.editor_tab = Dateieditor\ntheme_editor.font = Schriftart\ntheme_editor.active_panel = Aktiv\ntheme_editor.inactive_panel = Inaktiv\ntheme_editor.general = Allgemein\ntheme_editor.theme_warning_predefined = Eingebaute Themen können nicht geändert werden. Möchten Sie ein neues Thema erstellen?\ntheme_editor.could_not_save_theme = Kann Thema %1 nicht schreiben\ntheme_editor.border = Rand\ntheme_editor.background = Hintergrund\ntheme_editor.alternate_background = Alternativer Hintergrund\ntheme_editor.unfocused_background = Hintergrund (ohne Fokus)\ntheme_editor.copy_colors = Kopiere %1\ntheme_editor.quick_search = Schnellsuche\ntheme_editor.quick_search.unmatched_file = Nichtpassende Datei\ntheme_editor.text = Text\ntheme_editor.progress = Fortschritt\ntheme_editor.normal = Normal\ntheme_editor.normal_unfocused = Normal (ohne Fokus)\ntheme_editor.selected = Ausgewählt\ntheme_editor.selected_unfocused = Ausgewählt (ohne Fokus)\ntheme_editor.color = Farbe\ntheme_editor.colors = Farben\ntheme_editor.plain_file = Normale Datei\ntheme_editor.marked_file = Markierte Datei\ntheme_editor.hidden_file = Versteckte Datei\ntheme_editor.folder = Ordner\ntheme_editor.archive_file = Archivdatei\ntheme_editor.symbolic_link = Symbolische Verknüpfung\ntheme_editor.executable_file = Executable file\ntheme_editor.header = Überschrift\ntheme_editor.current = Aktuelle Zeile\ntheme_editor.file_groups = Dateigruppen\ntheme_editor.group_ = Gruppen\ntheme_editor.normal_color = Normale Farbe\ntheme_editor.selected_color = Auswahlfarbe\ntheme_editor.filemask = Dateifilter\ntheme_editor.group_file_ = Datei der Gruppe\ntheme_editor.item = Element\ncommand_bar_customize_dialog.available_actions = Verfügbare Aktionen\ncommand_bar_customize_dialog.modifier = Modifizierer\nprefs_dialog.title = Einstellungen\nprefs_dialog.general_tab = Allgemein\nprefs_dialog.day = Tag\nprefs_dialog.month = Monat\nprefs_dialog.year = Jahr\nprefs_dialog.language = Sprache (Neustart nötig)\nprefs_dialog.date_time = Datum- und Zeitformat\nprefs_dialog.time = Zeit\nprefs_dialog.date = Datum\nprefs_dialog.date_separator = Trennzeichen\nprefs_dialog.time_12_hour = 12-Stunden Format\nprefs_dialog.time_24_hour = 24-Stunden Format\nprefs_dialog.show_seconds = Sekunden anzeigen\nprefs_dialog.show_century = Jahrhundert anzeigen\nprefs_dialog.check_for_updates_on_startup = Beim Start nach Updates suchen\nprefs_dialog.show_splash_screen = Zeige Startbild\nprefs_dialog.folders_tab = Ordner\nprefs_dialog.startup_folders = Startordner\nprefs_dialog.left_folder = Linker Ordner\nprefs_dialog.right_folder = Rechter Ordner\nprefs_dialog.last_folder = Zuletzt besuchter Ordner\nprefs_dialog.custom_folder = Benutzerdefinierter Ordner\nprefs_dialog.show_hidden_files = Versteckte Dateien anzeigen\nprefs_dialog.show_ds_store_files = Zeige .DS_Store-Dateien\nprefs_dialog.show_system_folders = Zeige Systemverzeichnisse\nprefs_dialog.compact_file_size = Dateigrößen gerundet anzeigen\nprefs_dialog.follow_symlinks_when_cd = Symbolischen Links beim Wechsel des aktuellen Verzeichnis folgen\nprefs_dialog.show_tab_header = Tab-Überschrift immer anzeigen\nprefs_dialog.appearance_tab = Erscheinungsbild\nprefs_dialog.look_and_feel = Look & Feel\nprefs_dialog.icons_size = Icon-Größe\nprefs_dialog.toolbar_icons = Toolbar\nprefs_dialog.command_bar_icons = Kommandozeile\nprefs_dialog.file_icons = Dateiarten\nprefs_dialog.use_system_file_icons = Dateisystem-Icons nutzen\nprefs_dialog.use_system_file_icons.always = Immer\nprefs_dialog.use_system_file_icons.never = Nie\nprefs_dialog.use_system_file_icons.applications = Nur für Programme\nprefs_dialog.edit_current_theme = Aktuelles Thema bearbeiten...\nprefs_dialog.themes = Themen\nprefs_dialog.syntax_themes = Editor-Syntax Farbthema\nprefs_dialog.import_theme = Thema importieren\nprefs_dialog.import_look_and_feel = Look & Feel importieren\nprefs_dialog.no_look_and_feel = Kein Look & Feel gefunden.\nprefs_dialog.error_in_import = Fehler beim Import von Thema %1.\nprefs_dialog.cannot_read_theme = Kann Thema nicht aus Datei %1 laden\nprefs_dialog.export_theme = Exportiere %1\nprefs_dialog.import = Import\nprefs_dialog.export = Export\nprefs_dialog.theme_type = Typ: %1\nprefs_dialog.delete_theme = Thema %1 unwiderruflich löschen ?\nprefs_dialog.delete_look_and_feel = Look & Feel %1 dauerhaft löschen?\nprefs_dialog.rename_failed = Fehler beim Umbenennen von Thema %1\nprefs_dialog.xml_file = XML-Datei\nprefs_dialog.jar_file = JAR Datei\nprefs_dialog.mail_tab = eMail\nprefs_dialog.mail_settings = Einstellungen für ausgehende eMails\nprefs_dialog.mail_name = Ihr Name\nprefs_dialog.mail_address = Ihre eMail Adresse\nprefs_dialog.mail_server = SMTP Server\nprefs_dialog.misc_tab = Verschiedenes\nprefs_dialog.use_brushed_metal = 'Brushed Metal' Look benutzen (Neustart nötig)\nprefs_dialog.confirm_on_quit = Bestätigung beim Beenden\nprefs_dialog.shell = Befehl ausführen\nprefs_dialog.default_shell = Benutze Standard-Eingabeaufforderung\nprefs_dialog.custom_shell = Benutze angepasste Eingabeaufforderung\nprefs_dialog.shell_encoding = Shell Kodierung\nprefs_dialog.auto_detect_shell_encoding = Automatisch feststellen\nprefs_dialog.external_terminal = Externes Terminal\nprefs_dialog.default_terminal = Standard-Terminalprogramm verwenden\nprefs_dialog.custom_terminal = Benutze Benutzerbefehl\nprefs_dialog.builtin_terminal = Eingebautes Terminal verwenden\nprefs_dialog.default_shell = Standard-Shell verwenden\nprefs_dialog.custom_shell = Benutze Benutzerbefehl\nprefs_dialog.enable_bonjour_discovery = Erlaube Bonjour Servicesuche\nprefs_dialog.enable_system_notifications = System-Nachrichten einschalten\ndebug_console_dialog.level = Level\ndebug_console_dialog.threads = Threads\nunit.byte = Byte\nunit.bytes = Bytes\nunit.bytes_short = B\nunit.kb = KiB\nunit.mb = MiB\nunit.gb = GiB\nunit.tb = TiB\nunit.speed = %1/s\nduration.seconds = %1Sek\nduration.minutes = %1M\nduration.hours = %1S\nduration.days = %1T\nduration.months = %1M\nduration.years = %1J\nduration.infinite = ∞\ntheme.custom_theme = Angepasstes Thema\ntheme.custom = Angepasst\ntheme.built_in = Eingebaut\ntheme.add_on = Zusatz\ntheme.current = aktuell\ntheme_could_not_be_loaded = Beim Laden dieses Themas trat ein Fehler auf.\ncannot_open_cyclic_symlink = Kann den auswählten Link nicht öffnen, da er zyklisch ist\nsetup.title = Willkommen beim trolCommander\nsetup.intro = Bitte wählen Sie, wie sich der trolCommander verhalten soll.\nsetup.look_and_feel = Wählen Sie Aussehen und Verhalten\nsetup.theme = Wählen Sie Ihr Thema\nfont_chooser.font_size = Größe\nfont_chooser.font_bold = Fett\nfont_chooser.font_italic = Kursiv\ncolor_chooser.red = Rot\ncolor_chooser.green = Grün\ncolor_chooser.blue = Blau\ncolor_chooser.hue = Farbton\ncolor_chooser.brightness = Helligkeit\ncolor_chooser.swatches = Muster\ncolor_chooser.saturation = Sättigung\ncolor_chooser.rgb = RGB\ncolor_chooser.hsb = HSB\ncolor_chooser.recent = Bisherige\ncolor_chooser.alpha = Alpha-Transparenz\ncolor_chooser.title = Farbe wählen\nbatch_rename_dialog.mask = Umbenennungs Muster\nbatch_rename_dialog.search_replace = Suchen & Ersetzen\nbatch_rename_dialog.search_for = Suche nach\nbatch_rename_dialog.replace_with = Ersetze durch\nbatch_rename_dialog.counter = Zähler\nbatch_rename_dialog.start_at = Starte mit\nbatch_rename_dialog.step_by = Schrittweite\nbatch_rename_dialog.format = Format\nbatch_rename_dialog.upper_lower_case = Groß/Kleinschreibung\nbatch_rename_dialog.no_change = Unverändert\nbatch_rename_dialog.lower_case = kleinschreibung\nbatch_rename_dialog.upper_case = GROSSSCHREIBUNG\nbatch_rename_dialog.first_upper = Erster buchstabe gross\nbatch_rename_dialog.word = Erster Buchstabe Pro Wort Gross\nbatch_rename_dialog.regexp = RegExp\nbatch_rename_dialog.regexp_error = Syntaxfeler im regulären Ausdruck\nbatch_rename_dialog.old_name = Alter Name\nbatch_rename_dialog.new_name = Neuer Name\nbatch_rename_dialog.block_name = Beibehalten\nbatch_rename_dialog.range = Bereich\nbatch_rename_dialog.proceed_renaming = %1 Dateien von %2 werden umbenannt. Wollen Sie fortfahren?\nbatch_rename_dialog.duplicate_names = Doppelte Namen!\nbatch_rename_dialog.names_conflict = Namens Konflikt! Gleiche Werte im alten und neuen Namen.\nparent_folders_quick_list.empty_message = Aktuelles Verzeichnis hat kein übergeordnetes Verzeichnis\nrecent_locations_quick_list.empty_message = Kein jüngstes Verzeichnis\nrecent_executed_files_quick_list.empty_message = Keine jüngst ausgeführten Programme\nrecent_edited_files_quick_list.empty_message = Keine kürzlich bearbeitete Datei\nrecent_viewed_files_quick_list.empty_message = Keine kürzlich betrachtete Datei\nroots_quick_list.empty_message = Keine Wurzelverzeichnisse verfügbar\ntabs_quick_list.empty_message = Nur ein Tab vorhanden\n#server_connect_dialog.auth_error = Ungültiger Login oder Paßwort.\n#move_dialog.cannot_move_to_itself = Kann Dateien nicht in Unterordner verschieben.\n#pack.error_on_file = Fehler beim Packen von %1\n#pack_dialog.cannot_write = Kann Datei nicht im Zielordner erstellen.\n#unpack.unable_to_open_zip = Kann ZIP Datei %1 nicht öffnen.\n#image_viewer.previous_image = Vorheriges Bild\n#image_viewer.next_image = Nächstes Bild\n#mkdir_dialog.title = Ordner erstellen\n#mkdir_dialog.error_title = Fehler beim Erstellen des Ordners\n#edit_bookmarks_dialog.remove = Entfernen\n#mkdir_dialog.description = Ordner erstellen\n#mkfile_dialog.description = Erstelle neue leere Datei\n#done = Erledigt\n#move_dialog.rename_description = Umbenennen nach\n#theme_editor.shell_font = Kommandozeilen-Zeichensatz\n#theme_editor.history_font = Historien-Zeichensatz\n#theme_editor.shell_colors = Farbe Eingabeauforderung\n#theme_editor.history_colors = Historienfarbe\n#ToggleHiddenFiles.hide = Versteckte Dateien nicht anzeigen\n#auth_dialog.error_was = Fehler war: %1\n#table.hide_column = Spalte ausblenden\n#delete.symlink_warning_title = Verknüpfung gefunden\n#delete.symlink_warning = Diese Datei scheint ein symbolischer Link zu sein:\\n\\n  Datei: %1\\n  Verlinktt nach: %2\\n\\nLink löschen oder dem \\nLink folgen und Ordner löschen (VORSICHT) ?\n#delete.delete_link_only = Link löschen\n#delete.delete_linked_folder = Ordner löschen\n#Unpack.label = Dateien entpacken\n#Pack.label = Dateien packen\nfind_dialog.name = Dateiname\nfind_dialog.contains = Enthält\nfind_dialog.initial_directory = Starte bei\nfind_dialog.search_subdirectories = Durchsuche Unterverzeichnisse\nfind_dialog.search_archives = Durchsuche Archive\nfind_dialog.case_sensitive = Groß-/Kleinschreibung beachten\nfind_dialog.ignore_hidden = Ignoriere versteckte\nfind_dialog.search_results = Suchergebnis\nfind_dialog.found = Gefundene Dateien\nfind_dialog.encoding = Textkodierung\nfind_dialog.search_hex = Suche Hexadezimal\nimage_viewer.next_image = Nächstes Bild\nimage_viewer.previous_image = Vorheriges Bild\nhex_viewer.offset = Offset\nhex_viewer.ascii_dump = ASCII Ausdruck\nhex_viewer.view = Ansicht\nhex_viewer.goto = Gehe zu\nhex_viewer.goto.offset = Offset\nhex_viewer.search = Suche\nhex_viewer.searchNext = Suche nächstes\nhex_viewer.searchPrev = Suche vorheriges\nhex_viewer.find = Suche\nhex_view.text = Suche nach\nhex_viewer.hex = Hex\nhex_viewer.search_not_found = Muster nicht gefunden\ncalculator.calculator = Rechner\ncalculator.expression = Ausdruck\ncalculator.error = Fehler im Ausdruck\nreplication = Wiederholungsfaktor\nblocksize = Blockgröße\nChangeReplication.label = Ändere Wiederholung\nreplication.number = Wiederholungsfaktor\n\nadb.android_devices = Android\nadb.no_devices = Keine Geräte\neject.no_mounted_devices = Keine angeschlossenen Geräte\n#MinimizeWindow.label.mac_os_x = Im Dock ablegen\n"
  },
  {
    "path": "src/main/resources/dictionary_en_GB.properties",
    "content": "license = Licence\nMaximizeWindow.label = Maximise\nMinimizeWindow.label = Minimise\noptimizing_archive = Optimising archive %1\nerror_while_optimizing_archive = Error while optimising archive %1\ntheme_editor.color = Colour\ntheme_editor.colors = Colours\nprefs_dialog.confirm_on_quit = Show confirmation dialogue on quit\ntheme.custom = Customised\ncolor_chooser.title = Pick a colour\n#theme_editor.shell_colors = Shell colours\n#theme_editor.history_colors = History colours\n"
  },
  {
    "path": "src/main/resources/dictionary_en_US.properties",
    "content": ""
  },
  {
    "path": "src/main/resources/dictionary_es_ES.properties",
    "content": "ok = Aceptar\nyes = Sí\nno = No\ncancel = Cancelar\nedit = Editar\nclose = Cerrar\nreset = Reiniciar\nrename = Renombrar\napply = Aplicar\nchange = Cambiar\nsave = Guardar\ndont_save = No guardar\nreplace = Reemplazar\ndont_replace = No reemplazar\ndelete = Eliminar\nskip = Saltar\nskip_all = Saltar todos\nretry = Reintentar\nresume = Continuar\noverwrite = Reemplazar\noverwrite_if_older = Reemplazar si es más antiguo\nduplicate = Duplicar\napply_to_all = Aplicar a todo\ncopy = Copiar\nmove = Mover\npack = Comprimir\nunpack = Descomprimir\ndownload = Descargar\nsplit = Dividir\ncombine = Unir\nbrowse = Navegar\nask = Preguntar\nstop = Parar\npause = Pausa\nquick_search = Búsqueda rápida\nfile_manager = Gestor de ficheros\ncreate = Crear\ncreating_file = Creando %1\nchoose = Seleccionar\ncustomize = Personalizar\nchoose_folder = Seleccionar un directorio\nlogin = Identificación\npassword = Contraseña\nuser = Usuario\nencoding = Codificación\npreferred_encodings = Codificaciones preferidas\nlicense = Licencia\nname = Nombre\nsize = Tamaño\ndate = Fecha\nextension = Extensión\npermissions = Permisos\nowner = Propietario\ngroup = Grupo\nlocation = Ubicación\nuntitled = Sin nombre\nsource = Origen\ndestination = Destino\nrecurse_directories = Procesar directorios seleccionados recursivamente\ngo_to = Ir a\nexample = Ejemplo\npreview = Previsualizar\ncomment = Commentaire\nsample_text = Texto de ejemplo\nnb_files = %1 fichero(s)\nnb_folders = %1 directorio(s)\nloading = Cargando...\nthis_operation_cannot_be_undone = Esta operación no puede ser deshecha.\nremove = Eliminar\ndetails = Detalles\nwarning = Aviso\nerror = Error\ngeneric_error = Ha ocurrido un error mientras se realizaba la operación\nfolder_does_not_exist = El directoio no existe o no está disponible.\nthis_folder_does_not_exist = El directoio no existe o no está disponible: %1\nthis_file_does_not_exist = Este fichero no existe o no está disponible: %1\ninvalid_path = Dirección inválida: %1\ndirectory_already_exists = El directorio %1 ya existe.\nfile_exists_in_destination = El fichero ya existe en el destino\nsource_parent_of_destination = Intentar transferir un directorio a uno de sus subdirectorios\ncannot_read_file = No se puede leer el archivo %1\ncannot_write_file = No se puede escribir el archivo %1\ncannot_create_folder = No se puede crear el directorio %1\ncannot_read_folder = No se puede leer el directorio %1\ncannot_delete_file = No se puede borrar el fichero %1\ncannot_delete_folder = No se puede borrar el directorio %1\nerror_while_transferring = Error al transferir %1\nsame_source_destination = El directorio de origen y destino es el mismo\nfile_already_exists = %1 ya existe , ¿desea reemplazarlo?\nwrite_error = Error de escritura\nread_error = Error de lectura\nintegrity_check_error = Comprobación de integridad fallida: fuente y destino no coinciden\nstartup_error = Un error ha impedido iniciar trolCommander\npermissions.read = Lectura\npermissions.write = Escritura\npermissions.executable = Ejecución\npermissions.group = Grupo\npermissions.other = Otro\npermissions.octal_notation = Notación octal\naction_categories.all = Todos\naction_categories.navigation = Navigation\naction_categories.selection = Selección\naction_categories.view = Ver\naction_categories.file_operations = Operaciones con ficheros\naction_categories.windows = Ventanas\nTerminal.label = Terminal\nTerminal.tooltip = Terminal\nFindFile.label = Find file\nFindFile.tooltip = Find file\nAddBookmark.label = Añadir favorito\nAddBookmark.tooltip = Añadir a favoritos el directorio actual\nBatchRename.label = Renombrado masivo\nEditBookmarks.label = Editar favoritos\nExploreBookmarks.label = Explorar favoritos\nEditCredentials.label = Editar credenciales\nChangeLocation.label = Cambiar ubicación actual\nChangeDate.label = Cambiar fecha\nChangeDate.tooltip = Cambiar fecha de fichero(s) seleccionado(s)\nChangePermissions.label = Cambiar permisos\nChangePermissions.tooltip = Cambiar permisos de fichero(s) seleccionado(s)\nCheckForUpdates.label = Comprobar actualizaciones\nCompareFolders.label = Comparar directorios\nConnectToServer.label = Conectar a un servidor\nConnectToServer.tooltip = Conectar a un servidor remoto\nView.label = Ver\nInternalView.label = Visualizar (interno)\nView.tooltip = Ver el fichero seleccionado\nInternalEdit.label = Editar (interno)\nEdit.tooltip = Editar el fichero seleccionado\nCopy.tooltip = Copiar los ficheros seleccionados\nLocalCopy.label = Copia local\nLocalCopy.tooltip = Copiar el fichero seleccionado en el directorio actual\nMove.tooltip = Mover los ficheros seleccionados\nRename.tooltip = Renombrar el fichero seccionado\nMkdir.label = Crear directorio\nMkdir.tooltip = Crear subdirectorio en el directorio actual\nMkfile.label = Crear fichero\nMkfile.tooltip = Crear un fichero en el directorio actual\nDelete.tooltip = Eliminar los ficheros marcados\nPermanentDelete.label = Borrar permanentemente\nPermanentDelete.tooltip = Borrar ficheros marcados sin usar la papelera de reciclaje\nRefresh.label = Actualizar\nRefresh.tooltip = Actualizar directorio actual\nCloseWindow.label = Cerrar ventana\nCloseWindow.tooltip = Cerrar esta ventana\nCopyFileNames.label = Copiar nombre(s)\nCopyFilePaths.label = Copiar ruta(s)\nCopyFilesToClipboard.label = Copiar fichero(s)\nPasteClipboardFiles.label = Pegar fichero(s)\nEmail.label = Enviar por email\nEmail.tooltip = Enviar los ficheros seleccionados como adjuntos en un correo electrónico\nGoBack.label = Volver\nGoBack.tooltip = Ir al directorio anterior\nGoForward.label = Siguiente\nGoForward.tooltip = Ir al directorio siguiente\nGoToHome.label = Ir al directorio de inicio\nGoToParent.label = Ir al padre\nGoToParent.tooltip = Ir al directorio padre\nGoToParentInOtherPanel.label = Ir al directorio padre en el otro panel\nGoToParentInBothPanels.label = Ir al directorio padre en ambos paneles\nGoToRoot.label = Ir al raiz\nSortByName.label = Ordenar por Nombre\nSortByDate.label = Ordenar por Fecha\nSortBySize.label = Ordenar por Tamaño\nSortByExtension.label = Ordenar por Extensión\nSortByPermissions.label = Ordenar por Permisos\nSortByOwner.label = Ordenar por Propietario\nSortByGroup.label = Ordenar por Grupo\nMarkGroup.label = Marcar ficheros\nMarkGroup.tooltip = Marcar un grupo de ficheros\nUnmarkGroup.label = Desmarcar ficheros\nUnmarkGroup.tooltip = Desmarcar un grupo de ficheros\nMarkAll.label = Marcar todos\nUnmarkAll.label = Desmarcar todos\nMarkSelectedFile.label = Marcar/Desmarcar\nMarkSelectedFile.tooltip = Marcar/Desmarcar los ficheros seleccionados\nMarkNextBlock.label = Seleccionar el siguiente bloque\nMarkPreviousBlock.label = Seleccionar el bloque anterior\nMarkNextRow.label = Seleccionar una fila hacia abajo\nMarkPreviousRow.label = Seleccionar una fila hacia arriba\nMarkNextPage.label = Marcar página abajo\nMarkPreviousPage.label = Marcar página arriba\nMarkToFirstRow.label = Marcar ficheros hasta el comienzo\nMarkToLastRow.label = Marcar ficheros hasta el final\nMarkExtension.label = Marcar extensión\nInvertSelection.label = Invertir selección\nSwapFolders.label = Intercambiar directorios\nSwapFolders.tooltip = Intercambiar directorios\nSetSameFolder.label = Marcar el mismo directorio\nSetSameFolder.tooltip = Ajustar el mismo directorio en ambos lados\nNewWindow.label = Nueva ventana\nNewWindow.tooltip = Abrir una nueva ventana\nOpen.label = Abrir\nOpen.tooltip = Entrar en el directorio / Entrar en el fichero / Ejecutar\nOpenNatively.label = Abrir nativamente\nOpenNatively.tooltip = Ejecutar el fichero seleccionado con la asosiación del sistema\nOpenInNewTab.label = Abrir en pestaña nueva\nOpenInOtherPanel.label = Abrir en otro panel\nOpenInBothPanels.label = Abrir en ambos paneles\nRevealInDesktop.label = Mostrar en %1\nRunCommand.label = Ejecutar un comando\nRunCommand.tooltip = Ejecutar un comando en el directorio actual\nPack.tooltip = Empaquetar ficheros marcados\nUnpack.tooltip = Desempaquetar ficheros marcados\nShowFileProperties.label = Propiedades\nShowFileProperties.tooltip = Mostrar propiedades de los ficheros seleccionados\nShowPreferences.label = Preferencias\nShowPreferences.tooltip = Configurar trolCommander\nShowServerConnections.label = Mostrar conexiones abiertas\nQuit.label = Salir\nReverseSortOrder.label = Invertir el orden\nToggleAutoSize.label = Autodimensionar las columnas\nStop.label = Detener cambio de directorio\nToggleColumn.show = Mostrar la columna %1\nToggleColumn.hide = Ocultar la columna %1\nToggleCommandBar.show = Mostar la barra de comandos\nToggleCommandBar.hide = Ocultar la barra de comandos\nToggleToolBar.show = Mostrar la barra de herramientas\nToggleToolBar.hide = Ocultar la barra de herramientas\nCustomizeCommandBar.label = Personalizar la barra de comandos\nToggleStatusBar.show = Mostrar barra de estado\nToggleStatusBar.hide = Ocultar la barra de estado\nToggleShowFoldersFirst.label = Mostrar directorios primero\nToggleTree.label = Mostrar vista en árbol\nPopupLeftDriveButton.label = Cambiar el directorio izquierdo\nPopupRightDriveButton.label = Cambiar el directorio derecho\nRecallPreviousWindow.label = Cambiar a la ventana anterior\nRecallNextWindow.label = Cambiar a la siguiente ventana\nRecallWindow.label = Rellamar ventana #%1\nBringAllToFront.label = Traer todo al frente\nSwitchActiveTable.label = Cambiar entre los paneles izquierdo y derecho\nSelectNextBlock.label = Saltar un bloque hacia abajo\nSelectPreviousBlock.label = Saltar un bloque hacia arriba\nSelectNextPage.label = Saltar una página hacia abajo\nSelectPreviousPage.label = Saltar una página hacia arriba\nSelectNextRow.label = Saltar una fila hacia abajo\nSelectPreviousRow.label = Saltar una fila hacia arriba\nSelectFirstRow.label = Seleccionar el primer fichero en el directorio actual\nSelectLastRow.label = Seleccionar el último fichero en el directorio actual\nSplitEqually.label = Dividir equitativamente\nSplitVertically.label = Dividir verticalmente\nSplitHorizontally.label = Dividir horizontalmente\nShowKeyboardShortcuts.label = Atajos de teclado\nGoToWebsite.label = Ir a sitio web\nGoToForums.label = Foros\nReportBug.label = Comunicar fallo\nDonate.label = Hacer una donación\nShowAbout.label = Acerca de trolCommander\nOpenTrash.label = Abrir papelera\nEmptyTrash.label = Vaciar papelera\nCalculateChecksum.label = Calcular checksum\nMaximizeWindow.label = Maximizar\nGoToDocumentation.label = Documentación en línea\nShowParentFoldersQL.label = Directorios padre\nShowRecentLocationsQL.label = Ubicaciones recientes\nShowRecentExecutedFilesQL.label = Ficheros ejecutados recientemente\nShowRootFoldersQL.label = Directorios raíz\nSplitFile.tooltip = Dividir un fichero en múltiples partes\nCombineFiles.tooltip = Reconstruir un fichero a partir de sus partes\nShowDebugConsole.label = Consola de depuración\nFocusPrevious.label = Posiciona el foco en el componente anterior\nFocusNext.label = Posiciona el foco en el siguiente componente\nfile_menu = Archivo\nfile_menu.open_with = Abrir con\nmark_menu = Marcar\nview_menu = Ver\nview_menu.show_hide_columns = Mostrar/Ocultar columnas\ngo_menu = Ir\nbookmarks_menu = Favoritos\nbookmarks_menu.no_bookmark = No existe favorito\ndrive_popup.network_shares = Ubicaciones de red compartidas\nquick_lists_menu = Listas rápidas\nwindow_menu = Ventana\nhelp_menu = Ayuda\nstatus_bar.selected_files = %1 de %2 seleccionados\nstatus_bar.connecting_to_folder = Conectando al directorio, presiona ESCAPE para cancelar\nstatus_bar.volume_free = Libre: %1\nstatus_bar.volume_capacity = Capacidad: %1\nshortcuts_panel.title = Atajos de teclado\nshortcuts_panel.restore_defaults = Restaurar valores por defecto\nshortcuts_panel.show = Mostrar\nshortcuts_panel.default_message = Pulsar Intro o hacer doble click sobre el atajo de teclado a editar\nshortcuts_table.action_description = Descripción de la acción\nshortcuts_table.shortcut = Atajo de teclado\nshortcuts_table.alternate_shortcut = Atajo de teclado alternativo\nshortcuts_table.type_in_a_shortcut = Escribir un atajo de teclado\ncommand_bar_dialog.help = Arrastrar botones para personalizar la barra de comandos\ntable.folder_access_error_title = Error al acceder al directorio\ntable.folder_access_error = Imposible leer el contenido del directorio\ntable.download_or_browse = ¿Desea explorar o descargar este fichero?\nCloseDuplicateTabs.tooltip = Cerrar pestañas duplicadas\nCloseOtherTabs.tooltip = Cerrar las demás pestañas\nCloseTab.tooltip = Cerrar pestaña\nMoveTabToOtherPanel.tooltip = Mover pestaña al otro panel\nNextTab.label = Siguiente pestaña\nPreviousTab.label = Pestaña anterior\nversion_dialog.no_new_version_title = No hay una versión nueva\nversion_dialog.no_new_version = Felicitaciones, tienes la última versión.\nversion_dialog.new_version_title = Nueva versión disponible\nversion_dialog.new_version = Una nueva versión de trolCommander está disponible.\nversion_dialog.new_version_url = Una nueva versión de trolCommander está disponible en %1.\nversion_dialog.not_available_title = El servidor no está disponible\nversion_dialog.not_available = Imposible obtener la versión desde el servidor.\nversion_dialog.install_and_restart = Instalar y reiniciar\nversion_dialog.preparing_for_update = Preparando actualización...\nquit_dialog.title = Salir de trolCommander\nquit_dialog.desc = Tienes %1 ventana(s) abierta(s). ¿Estás seguro de que deseas salir?\nquit_dialog.show_next_time = Mostrar la próxima vez\ndestination_dialog.file_exists_action = Acción por defecto cuando el fichero existe\ndestination_dialog.verify_integrity = Verificando integridad de datos\ndestination_dialog.skip_errors = Ignorar los errores\nfile_collision_dialog.title = Colisión de fichero\nrename_dialog.new_name = Nuevo nombre\ncopy_dialog.destination = Copiar fichero(s) a\ncopy_dialog.error_title = Error al copiar\ncopy_dialog.copying = Copiando\ncopy_dialog.copying_file = Copiando %1\npack_dialog.packing = Comprimiendo\npack_dialog.packing_file = Comprimiendo %1\npack_dialog.error_title = Error de compresión\npack_dialog_description = Añadir los ficheros seleccionados a\npack_dialog.archive_format = Formato de fichero\nunpack_dialog.destination = Descomprimir fichero(s) seleccionado(s) en\nunpack_dialog.error_title = Error al descomprimir\nunpack_dialog.unpacking = Descomprimiendo\nunpack_dialog.unpacking_file = Descomprimiendo %1\noptimizing_archive = Optimizando fichero %1\nerror_while_optimizing_archive = Error al comprimir el fichero %1\nmove_dialog.move_description = Mover a\nmove_dialog.error_title = Error de movimiento\nmove_dialog.moving = Moviendo ficheros\nmove_dialog.moving_file = Moviendo %1\ndownload_dialog.description = Descargar fichero a\ndownload_dialog.error_title = Error de descarga\ndownload_dialog.downloading = Descargando\ndownload_dialog.downloading_file = Descargando %1\nmkfile_dialog.allocate_space = Tamaño\ndelete_dialog.permanently_delete.confirmation = ¿Eliminar permanentemene el/los archivo(s) seleccionado(s)?\ndelete_dialog.move_to_trash.confirmation = ¿Eliminar fichero(s) seleccionados?\ndelete_dialog.move_to_trash.confirmation_details = Los ficheros serán movidos a la papelera.\ndelete_dialog.move_to_trash.option = Mover a la papelera\ndelete_dialog.move_to_trash.failed = Uno o más ficheros no pudieron moverse a la papelera de reciclaje\ndelete_dialog.deleting = Eliminando\ndelete_dialog.error_title = Error al eliminar\ndelete.deleting_file = Eliminando %1\nemail_dialog.prefs_not_set_title = Correo no configurado\nemail_dialog.prefs_not_set = Debes ajustar los parámatros de correo primero.\nemail_dialog.from = De\nemail_dialog.to = A\nemail_dialog.subject = Asunto\nemail_dialog.send = Enviar\nemail_dialog.error_title = Error de envío\nemail_dialog.read_error = Imposible leer ficheros en subdirectorios.\nemail_dialog.sending = Enviando ficheros\nemail.sending_file = Enviando %1\nemail.connecting_to_server = Conectando a %1\nemail.server_unavailable = Imposible conectar al servidor %1, comprueba tus preferencias de correo o inténtalo más tarde.\nemail.connection_closed = Conexión cerrada por el servidor, el mensaje no ha sido enviado.\nemail.goodbye_failed = Error al cerrar la conexión, el mensaje no ha sido enviado.\nemail.send_file_error = Imposible enviar el fichero %1, el mensaje no ha sido enviado.\nsplit_file_dialog.error_title = Error dividiendo fichero\nsplit_file_dialog.file_to_split = Fichero a dividir \nsplit_file_dialog.target_directory = Directorio destino\nsplit_file_dialog.part_size = Tamaño de cada parte\nsplit_file_dialog.parts = Número de partes\nsplit_file_dialog.generate_CRC = Generar fichero CRC\nsplit_file_dialog.max_parts = El máximo número de partes permitido es %1\nsplit_file_dialog.auto = Automático\nsplit_file_dialog.insert_new_media = Insertar un nuevo disco\ncombine_files_dialog.error_title = Error uniendo ficheros\ncombine_files_job.no_crc_file = Unión finalizada. Sin fichero CRC a verificar.\ncombine_files_job.crc_read_error = Error leyendo fichero CRC.\ncombine_files_job.crc_check_failed = Error de verificación de CRC: esperado %2, encontrado %1\ncombine_files_job.crc_ok = Unión finalizada. Verificación de CRC correcta.\nfile_selection_dialog.mark = Marcar\nfile_selection_dialog.unmark = Desmarcar\nfile_selection_dialog.mark_description = Marcar ficheros cuyo nombre\nfile_selection_dialog.unmark_description = Desmarcar ficheros cuyo nombre\nfile_selection_dialog.case_sensitive = Sensible a las mayúsculas\nfile_selection_dialog.include_folders = Incluir directorios\nprogress_dialog.starting = Comenzando transferencia...\nprogress_dialog.transferred = %1 transferidos a %2\nprogress_dialog.elapsed_time = Tiempo transcurrido\nprogress_dialog.advanced = Avanzadas\nprogress_dialog.current_speed = Velocidad actual\nprogress_dialog.limit_speed = Limitar la velocidad\nprogress_dialog.close_when_finished = Cerrar ventana al finalizar\nprogress_dialog.processing_files = Procesando ficheros\nprogress_dialog.processing_file = Procesando %1\nprogress_dialog.verifying_file = Comprobando %1\nprogress_dialog.job_finished = Trabajo finalizado\nprogress_dialog.job_error = Error\nproperties_dialog.file_properties = %1 Propiedades\nproperties_dialog.contents = Contenido\nproperties_dialog.calculating = Calculando..\ncalculate_checksum_dialog.checksum_algorithm = Algoritmo de checksum\ncalculate_checksum_dialog.temporary_file = Fichero temporal\nchange_date_dialog.now = Ahora\nchange_date_dialog.specific_date = Fecha específica\nrun_dialog.run_command_description = Ejecutar en el directorio actual\nrun_dialog.run_in_home_description = Ejecutar en el directorio de inicio\nrun_dialog.command_output = Salida del comando\nrun_dialog.run = Ejecutar\nrun_dialog.clear_history = Limpiar historial\nserver_connect_dialog.server_type = Tipo de conexión\nserver_connect_dialog.server = Servidor\nserver_connect_dialog.share = Compartir\nserver_connect_dialog.domain = Dominio\nserver_connect_dialog.username = Nombre de usuario\nserver_connect_dialog.initial_dir = Directorio inicial\nserver_connect_dialog.port = Puerto\nserver_connect_dialog.server_url = Dirección del servidor\nserver_connect_dialog.http_url = Dirección del sitio web\nserver_connect_dialog.connect = Conectar\nserver_connect_dialog.protocol = Protocolo\nserver_connect_dialog.nfs_version = Versión de NFS\nserver_connect_dialog.private_key = Clave privada\nserver_connect_dialog.passphrase = Contraseña\nftp_connect.passive_mode = Activar modo pasivo\nftp_connect.anonymous_user = Usuario anónimo\nftp_connect.nb_connection_retries = Número de reintentos de conexión\nftp_connect.retry_delay = Tiempo de espera entre reintentos (en segundos)\nhttp_connect.basic_authentication = HTTP Basic Authentication (opcional)\nserver_connections_dialog.disconnect = Desconectar\nserver_connections_dialog.connection_busy = Ocupado\nserver_connections_dialog.connection_idle = Inactivo\nbonjour.bonjour_services = Servicios Bonjour\nbonjour.no_service_discovered = No se han descubierto servicios\nbonjour.bonjour_disabled = Bonjour deshabilitado\nauth_dialog.title = Autentificación\nauth_dialog.desc = Por favor, introduzca un nombre y una contraseña\nauth_dialog.server = Servidor\nauth_dialog.store_credentials = Almacenar usuario y contraseña (encriptación débil)\nauth_dialog.connect_as = Conectar como\nauth_dialog.authentication_failed = Error de autenticación\nsortable_list.move_up = Subir\nsortable_list.move_down = Bajar\nadd_bookmark_dialog.add = Añadir\nedit_bookmarks_dialog.new = Nuevo\nfile_viewer.view_error_title = Error de vista\nfile_viewer.view_error = Imposible ver el fichero.\nfile_viewer.file_menu = Archivo\nfile_viewer.close = Cerrar\nfile_viewer.large_file_warning = Este fichero puede que sea demasiado largo para llevar a cabo la operación.\nfile_viewer.open_anyway = Ignorar y abrir\ntext_viewer.edit = Editar\ntext_viewer.copy = Copiar\ntext_viewer.select_all = Seleccionar todo\ntext_viewer.find = Buscar\ntext_viewer.find_next = Buscar siguiente\ntext_viewer.find_previous = Buscar anterior\ntext_viewer.view = Vista\ntext_viewer.line_wrap = Ajuste de línea\ntext_viewer.line_numbers = Números de línea\ntext_viewer.binary_file_warning = Parece ser un fichero binario\nimage_viewer.controls_menu = Controles\nimage_viewer.zoom_in = Acercar\nimage_viewer.zoom_out = Alejar\nfile_editor.edit_error_title = Error al editar\nfile_editor.edit_error = Imposible editar el fichero.\nfile_editor.save = Salvar\nfile_editor.save_as = Salvar como...\nfile_editor.save_warning = ¿Desea guardar los cambios realizados a este fichero antes de salir?\nfile_editor.cannot_write = Imposible escribir el fichero.\ntext_editor.cut = Cortar\ntext_editor.paste = Pegar\nshortcuts_dialog.quick_search.start_search = Teclee cualquier carácter para comenzar una búsqueda rápida\nshortcuts_dialog.quick_search.cancel_search = Cancelar búsqueda rápida\nshortcuts_dialog.quick_search.remove_last_char = Eliminar último carácter de la cadena de búsqueda rápida\nshortcuts_dialog.quick_search.jump_to_previous = Ir al resultado previo de búsqueda rápida\nshortcuts_dialog.quick_search.jump_to_next = Ir al resultado siguiente de búsqueda rápida\nshortcuts_dialog.quick_search.mark_jump_next = Marcar/Desmarcar fichero actual e ir al resultado siguiente de búsqueda rápida\ntheme_editor.title = Editor de tema\ntheme_editor.folder_tab = Panel de directorio\ntheme_editor.shell_tab = Consola\ntheme_editor.shell_history_tab = Historial de terminal\ntheme_editor.statusbar_tab = Barra de estado\ntheme_editor.free_space = Espacio libre\ntheme_editor.free_space.ok = OK\ntheme_editor.free_space.warning = Aviso\ntheme_editor.free_space.critical = Crítico\ntheme_editor.locationbar_tab = Barra de ubicación\ntheme_editor.editor_tab = Editor de ficheros\ntheme_editor.font = Fuente\ntheme_editor.active_panel = Activo\ntheme_editor.inactive_panel = Inactivo\ntheme_editor.general = General\ntheme_editor.could_not_save_theme = Imposible escribir el tema %1\ntheme_editor.border = Borde\ntheme_editor.background = Fondo\ntheme_editor.alternate_background = Alternar fondo\ntheme_editor.unfocused_background = Fondo (sin foco)\ntheme_editor.quick_search.unmatched_file = Fichero sin encajar\ntheme_editor.text = Texto\ntheme_editor.progress = Progreso\ntheme_editor.normal = Normal\ntheme_editor.normal_unfocused = Normal (sin foco)\ntheme_editor.selected = Seleccionado\ntheme_editor.selected_unfocused = Seleccionado (sin foco)\ntheme_editor.color = Color\ntheme_editor.colors = Colores\ntheme_editor.plain_file = Fichero plano\ntheme_editor.marked_file = Fichero marcado\ntheme_editor.hidden_file = Fichero oculto\ntheme_editor.folder = Directorio\ntheme_editor.archive_file = Archivar fichero\ntheme_editor.symbolic_link = Enlace simbólico\ntheme_editor.header = Cabecera\ntheme_editor.item = Elemento\ncommand_bar_customize_dialog.available_actions = Acciones disponibles\ncommand_bar_customize_dialog.modifier = Modificador\nprefs_dialog.title = Preferencias\nprefs_dialog.general_tab = General\nprefs_dialog.day = Día\nprefs_dialog.month = Mes\nprefs_dialog.year = Año\nprefs_dialog.language = Idioma (requiere reiniciar)\nprefs_dialog.date_time = Formato de fecha y hora\nprefs_dialog.time = Hora\nprefs_dialog.date = Fecha\nprefs_dialog.date_separator = Separador\nprefs_dialog.time_12_hour = Formato 12 horas\nprefs_dialog.time_24_hour = Formato 24 horas\nprefs_dialog.show_seconds = Mostrar segundos\nprefs_dialog.show_century = Mostrar siglo\nprefs_dialog.check_for_updates_on_startup = Comprobar actualizaciones al inicio\nprefs_dialog.show_splash_screen = Mostrar pantalla de bienvenida\nprefs_dialog.folders_tab = Directorios\nprefs_dialog.startup_folders = Directorios de inicio\nprefs_dialog.left_folder = Directorio de la izquierda\nprefs_dialog.right_folder = Directorio de la derecha\nprefs_dialog.last_folder = Último directorio visitado\nprefs_dialog.custom_folder = Directorio personalizado\nprefs_dialog.show_hidden_files = Mostrar ficheros ocultos\nprefs_dialog.show_ds_store_files = Mostrar ficheros .DS_Store\nprefs_dialog.show_system_folders = Mostrar directorios de sistema\nprefs_dialog.compact_file_size = Redondear el tamaño de los ficheros seleccionados\nprefs_dialog.follow_symlinks_when_cd = Seguir enlaces simbólicos al cambiar el directorio actual\nprefs_dialog.appearance_tab = Apariencia\nprefs_dialog.look_and_feel = Look & Feel\nprefs_dialog.icons_size = Tamaño de icono\nprefs_dialog.toolbar_icons = Barra de herramientas\nprefs_dialog.command_bar_icons = Barra de comandos\nprefs_dialog.file_icons = Tipos de ficheros\nprefs_dialog.use_system_file_icons = Usar iconos del sistema de ficheros\nprefs_dialog.use_system_file_icons.always = Siempre\nprefs_dialog.use_system_file_icons.never = Nunca\nprefs_dialog.use_system_file_icons.applications = Sólo para las aplicaciones\nprefs_dialog.edit_current_theme = Editar tema actual...\nprefs_dialog.themes = Temas\nprefs_dialog.import_theme = Importar tema\nprefs_dialog.import_look_and_feel = Importar look & feel\nprefs_dialog.no_look_and_feel = No se encontró look & feel alguno.\nprefs_dialog.error_in_import = Error al importar el tema %1.\nprefs_dialog.cannot_read_theme = No se pudo leer el tema del fichero %1\nprefs_dialog.export_theme = Exportar %1\nprefs_dialog.import = Importar\nprefs_dialog.export = Exportar\nprefs_dialog.theme_type = Tipo: %1\nprefs_dialog.delete_theme = ¿Eliminar permanentemente el tema %1?\nprefs_dialog.delete_look_and_feel = Eliminar permanentemente el look & feel %1 ?\nprefs_dialog.rename_failed = Error al renombrar el tema %1\nprefs_dialog.xml_file = Fichero XML\nprefs_dialog.jar_file = Fichero JAR\nprefs_dialog.mail_tab = Correo electrónico\nprefs_dialog.mail_settings = Parámetros de correo saliente\nprefs_dialog.mail_name = Su nombre\nprefs_dialog.mail_address = Su correo electrónico\nprefs_dialog.mail_server = Servidor SMTP\nprefs_dialog.misc_tab = Miscelánea\nprefs_dialog.use_brushed_metal = Usar la apariencia 'brocha de metal' (requiere volver a arrancar)\nprefs_dialog.confirm_on_quit = Mostrar diálogo de confirmación al salir\nprefs_dialog.default_shell = Utilizar shell predeterminada del sistema\nprefs_dialog.custom_shell = Utilizar shell específica\nprefs_dialog.shell_encoding = Codificación de terminal\nprefs_dialog.auto_detect_shell_encoding = Detección automática\nprefs_dialog.enable_bonjour_discovery = Activar servicios de descubrimiento de Bonjour\nprefs_dialog.enable_system_notifications = Activar las notificaciones del sistema\ndebug_console_dialog.level = Nivel\nunit.byte = byte\nunit.bytes = bytes\nunit.bytes_short = b\nunit.kb = KB\nunit.mb = MB\nunit.gb = GB\nunit.tb = TB\nunit.speed = %1/s\nduration.seconds = %1s\nduration.minutes = %1m\nduration.hours = %1h\nduration.days = %1d\nduration.months = %1me\nduration.years = %1a\ntheme.custom_theme = Tema personalizado\ntheme.custom = Personalizado\ntheme.built_in = Preinstalado\ntheme.add_on = Añadido\ntheme.current = actual\ntheme_could_not_be_loaded = Ocurrió un error mientras se cargaba éste tema.\nsetup.title = Bienvenida/o a trolCommander\nsetup.intro = Por favor, seleccione la forma en la que desea que trolCommander se comporte.\nsetup.look_and_feel = Seleccione su Look & feel\nsetup.theme = Seleccione su tema\nfont_chooser.font_size = Tamaño\nfont_chooser.font_bold = Negrita\nfont_chooser.font_italic = Itálica\ncolor_chooser.red = Rojo\ncolor_chooser.green = Verde\ncolor_chooser.blue = Azul\ncolor_chooser.hue = Tonalidad\ncolor_chooser.brightness = Brillo\ncolor_chooser.swatches = Muestras\ncolor_chooser.saturation = Saturación\ncolor_chooser.rgb = RGB\ncolor_chooser.hsb = HSB\ncolor_chooser.recent = Reciente\ncolor_chooser.alpha = Transparencia alpha\ncolor_chooser.title = Seleccionar un color\nbatch_rename_dialog.mask = Patrón de renombrado\nbatch_rename_dialog.search_replace = Buscar y reemplazar \nbatch_rename_dialog.search_for = Buscar\nbatch_rename_dialog.replace_with = Reemplazar con\nbatch_rename_dialog.counter = Contador\nbatch_rename_dialog.start_at = Comenzar en\nbatch_rename_dialog.step_by = Incrementar en\nbatch_rename_dialog.format = Formato\nbatch_rename_dialog.upper_lower_case = Mayúsculas/Minúsculas\nbatch_rename_dialog.no_change = Sin cambios\nbatch_rename_dialog.lower_case = minúsculas\nbatch_rename_dialog.upper_case = MAYÚSCULAS\nbatch_rename_dialog.first_upper = Primera letra en mayúscula\nbatch_rename_dialog.word = Primera Letra De Cada Palabra\nbatch_rename_dialog.old_name = Nombre antiguo\nbatch_rename_dialog.new_name = Nombre nuevo\nbatch_rename_dialog.block_name = Preservar\nbatch_rename_dialog.range = Intervalo\nbatch_rename_dialog.proceed_renaming = Se renombrarán %1 ficheros de %2. ¿ Desea continuar ?\nbatch_rename_dialog.duplicate_names = ¡Nombres duplicados!\nbatch_rename_dialog.names_conflict = ¡Conflicto de nombres! Coinciden nombres antiguos y nuevos.\nparent_folders_quick_list.empty_message = La ubicación actual no tiene padre\nrecent_locations_quick_list.empty_message = No hay ubicaciones recientes\nrecent_executed_files_quick_list.empty_message = No hay ficheros ejecutados recientmente\n#server_connect_dialog.auth_error = Nombre o contraseña inválido\n#move_dialog.cannot_move_to_itself = Imposible mover los ficheros al subdirectorio.\n#pack.error_on_file = Error al comprimir %1\n#pack_dialog.cannot_write = Imposible crear el fichero zip en el direcotorio de destino.\n#unpack.unable_to_open_zip = Error al abrir el fichero zip %1.\n#image_viewer.previous_image = Anterior\n#image_viewer.next_image = Siguiente\n#mkdir_dialog.title = Crear directorio\n#mkdir_dialog.error_title = Error al crear el directorio\n#edit_bookmarks_dialog.remove = Eliminar\n#mkdir_dialog.description = Crear directorio\n#done = Terminado\n#progress_dialog.hide = Ocultar\n#move_dialog.rename_description = Renombrar fichero a\n#theme_editor.shell_font = Fuente de consola\n#theme_editor.history_font = Fuente de historial\n#theme_editor.shell_colors = Colores de consola\n#theme_editor.history_colors = Colores del historial\n#ToggleHiddenFiles.hide = Ocultar ficheros ocultos\n#auth_dialog.error_was = El error era: %1\n#table.hide_column = Ocultar columna\n#delete.symlink_warning_title = Enlace simbólico encontrado\n#delete.symlink_warning = El fichero parece un enlace simbólico:\\n\\n Fichero: %1\\n Enlace a: %2\\n\\n¿Eliminar enlace simbólico sólo o\\nseguir el enlace y elimiar el directorio (PRECAUCIÓN)?\n#delete.delete_link_only = Eliminar enlace\n#delete.delete_linked_folder = Eliminar directorio\n#Unpack.label = Descomprimir ficheros\n#Pack.label = Comprimir ficheros\n"
  },
  {
    "path": "src/main/resources/dictionary_fr_FR.properties",
    "content": "ok = OK\nyes = Oui\nno = Non\ncancel = Annuler\nedit = Editer\nclose = Fermer\nreset = Réinitialiser\nrename = Renommer\napply = Appliquer\nchange = Changer\nsave = Enregistrer\ndont_save = Ne pas enregistrer\nreplace = Remplacer\ndont_replace = Ne pas remplacer\ndelete = Supprimer\nskip = Ignorer\nskip_all = Ignorer tous\nretry = Réessayer\nresume = Reprendre\noverwrite = Remplacer\noverwrite_if_older = Remplacer si ancien\nduplicate = Dupliquer\napply_to_all = Appliquer à tous\ncopy = Copier\nmove = Déplacer\npack = Compresser\nunpack = Décompresser\ndownload = Télécharger\nsplit = Scinder\ncombine = Aggréger\nbrowse = Explorer\nask = Demander\nstop = Arrêter\npause = Pause\nquick_search = Recherche rapide\nfile_manager = Gestionnaire de fichiers\ncreate = Créer\ncreating_file = Création de %1 en cours\nchoose = Choisir\ncustomize = Personnaliser\nchoose_folder = Choisir un répertoire\nlogin = Identifiant\npassword = Mot de passe\nuser = User\nencoding = Encodage\npreferred_encodings = Encodages préférés\nlicense = License\nname = Nom\nsize = Taille\ndate = Date\nextension = Extension\npermissions = Permissions\nowner = Propriétaire\ngroup = Groupe\nlocation = Emplacement\nuntitled = Sans titre\nsource = Source\ndestination = Destination\nrecurse_directories = Appliquer récursivement aux répertoires sélectionnés\ngo_to = Aller à\nexample = Exemple\npreview = Apperçu\ncomment = Commentaire\nsample_text = Texte d'exemple\nnb_files = %1 fichier(s)\nnb_folders = %1 dossier(s)\nloading = Chargement en cours...\nthis_operation_cannot_be_undone = Cette opération ne peut pas être annulée.\nremove = Retirer\ndetails = Détails\nwarning = Avertissement\nerror = Erreur\ngeneric_error = Une erreur est survenue pendant l'exécution de la tache demandée.\nfolder_does_not_exist = Ce dossier n'existe pas ou n'est pas disponible.\nthis_folder_does_not_exist = Ce dossier n'existe pas ou n'est pas disponible: %1\nthis_file_does_not_exist = Ce fichier n'existe pas ou n'est pas disponible: %1\ninvalid_path = Chemin invalide: %1\ndirectory_already_exists = Le répertoire %1 existe déjà.\nfile_exists_in_destination = Le fichier existe dans la destination\nsource_parent_of_destination = Tentative de déplacement d'un répertoire vers un de ses sous-répertoires\ncannot_read_file = Erreur lors de la lecture de %1\ncannot_write_file = Erreur lors de l'écriture de %1\ncannot_create_folder = Erreur lors de la création du répertoire %1\ncannot_read_folder = Erreur lors de la lecture du dossier %1\ncannot_delete_file = Erreur lors de la suppression du fichier %1\ncannot_delete_folder = Erreur lors de la suppression du répertoire %1\nerror_while_transferring = Erreur lors du transfert de %1\nsame_source_destination = Les répertoires source et destination sont les mêmes\nfile_already_exists = %1 existe déjà, voulez-vous le remplacer ?\nwrite_error = Erreur d'écriture\nread_error = Erreur de lecture\nintegrity_check_error = Echec du contrôle d'intégrité: les fichiers source et destination sont différents\nstartup_error = Une erreur a empêché trolCommander de démarrer.\npermissions.read = Lecture\npermissions.write = Ecriture\npermissions.executable = Execution\npermissions.group = Groupe\npermissions.other = Autre\npermissions.octal_notation = Notation octale\naction_categories.all = Tous\naction_categories.navigation = Navigation\naction_categories.selection = Selection\naction_categories.view = Vue\naction_categories.file_operations = Opérations sur fichiers\naction_categories.windows = Fenêtres\nTerminal.label = Terminal\nTerminal.tooltip = Terminal\nFindFile.label = Find file\nFindFile.tooltip = Find file\nAddBookmark.label = Ajouter aux favoris\nAddBookmark.tooltip = Ajouter le dossier courant à la liste de favoris\nBatchRename.label = Renommage multiple\nEditBookmarks.label = Éditer les favoris\nExploreBookmarks.label = Explorer les favoris\nEditCredentials.label = Éditer les indentifiants\nChangeLocation.label = Changer le chemin courant\nChangeDate.label = Changer la date\nChangeDate.tooltip = Change la date des fichiers selectionnés\nChangePermissions.label = Changer les permissions\nChangePermissions.tooltip = Change les permissions des fichiers selectionnés\nCheckForUpdates.label = Mises à jour\nCompareFolders.label = Comparer dossiers\nConnectToServer.label = Connexion à un serveur\nConnectToServer.tooltip = Se connecter à un serveur distant\nView.label = Voir\nInternalView.label = Voir (interne)\nView.tooltip = Voir le fichier sélectionné\nInternalEdit.label = Editer (interne)\nEdit.tooltip = Editer le fichier selectionné\nCopy.tooltip = Copier les fichiers marqués\nLocalCopy.label = Copier local\nLocalCopy.tooltip = Copier le fichier selectionné dans le dossier courant\nMove.tooltip = Déplacer les fichiers marqués\nRename.tooltip = Renommer le fichier selectionné\nMkdir.label = Créer rep\nMkdir.tooltip = Créer un répertoire dans le dossier courant\nMkfile.label = Créer fichier\nMkfile.tooltip = Créer un fichier dans le dossier courant\nDelete.tooltip = Supprimer les fichiers marqués en utilisant la corbeille système si possible\nPermanentDelete.label = Supprimer définitivement\nPermanentDelete.tooltip = Supprimer les fichiers marqués sans utiliser la corbeille système\nRefresh.label = Actualiser\nRefresh.tooltip = Actualiser le répertoire courant\nCloseWindow.label = Fermer fenêtre\nCloseWindow.tooltip = Fermer cette fenêtre\nCopyFileNames.label = Copier nom(s)\nCopyFilePaths.label = Copier chemin(s)\nCopyFilesToClipboard.label = Copier fichier(s)\nPasteClipboardFiles.label = Coller fichier(s)\nEmail.label = Envoyer par email\nEmail.tooltip = Envoyer les fichiers marqués en pièces jointes à un message\nGoBack.label = Dossier précédent\nGoBack.tooltip = Aller au dossier précédent\nGoForward.label = Dossier suivant\nGoForward.tooltip = Aller au dossier suivant\nGoToHome.label = Aller au dossier utilisateur\nGoToParent.label = Dossier parent\nGoToParent.tooltip = Remonter au dossier parent\nGoToParentInOtherPanel.label = Remonter dans l'autre panneau\nGoToParentInBothPanels.label = Remonter dans les deux panneaux\nGoToRoot.label = Aller à la racine\nSortByName.label = Trier par Nom\nSortByDate.label = Trier par Date\nSortBySize.label = Trier par Taille\nSortByExtension.label = Trier par Extension\nSortByPermissions.label = Trier par Permissions\nSortByOwner.label = Trier par Propriétaire\nSortByGroup.label = Trier par Groupe\nMarkGroup.label = Marquer fichiers\nMarkGroup.tooltip = Marquer un groupe de fichiers\nUnmarkGroup.label = Dé-marquer fichiers\nUnmarkGroup.tooltip = Dé-marquer un groupe de fichiers\nMarkAll.label = Marquer tous\nUnmarkAll.label = Dé-marquer tous\nMarkSelectedFile.label = Marquer/dé-marquer\nMarkSelectedFile.tooltip = Marquer/Dé-marquer le fichier sélectionné\nMarkNextBlock.label = Marquer un bloc vers le bas\nMarkPreviousBlock.label = Marquer un bloc vers le haut\nMarkNextRow.label = Marquer une ligne vers le bas\nMarkPreviousRow.label = Marquer une ligne vers le haut\nMarkNextPage.label = Marquer une page vers le bas\nMarkPreviousPage.label = Marquer une page vers le haut\nMarkToFirstRow.label = Marquer les fichiers jusqu'au début\nMarkToLastRow.label = Marquer les fichiers jusqu'à la fin\nMarkExtension.label = Marquer l'extension\nInvertSelection.label = Inverser sélection\nSwapFolders.label = Echanger les dossiers\nSwapFolders.tooltip = Echanger les dossiers de gauche et de droite\nSetSameFolder.label = Affecter le même dossier\nSetSameFolder.tooltip = Affecter le même repertoire aux panneaux de gauche et de droite\nNewWindow.label = Nouvelle fenêtre\nNewWindow.tooltip = Ouvrir une nouvelle fenêtre\nOpen.label = Ouvrir\nOpen.tooltip = Entrer dans un dossier / Entrer dans une archive / Exécuter\nOpenNatively.label = Ouvrir nativement\nOpenNatively.tooltip = Exécuter le fichier sélectionné avec les associations de fichiers du système\nOpenInOtherPanel.label = Ouvrir dans l'autre panneau\nOpenInBothPanels.label = Ouvrir dans les deux panneaux\nRevealInDesktop.label = Afficher dans %1\nRunCommand.label = Exécuter une commande\nRunCommand.tooltip = Exécuter une commande dans le dossier courant\nPack.tooltip = Compresser les fichiers marqués dans une archive\nUnpack.tooltip = Décompresser les archives marquées\nShowFileProperties.label = Propriétés\nShowFileProperties.tooltip = Afficher les propriétés des fichiers marqués\nShowPreferences.label = Préférences\nShowPreferences.tooltip = Configurer trolCommander\nShowServerConnections.label = Afficher les connexions ouvertes\nQuit.label = Quitter\nReverseSortOrder.label = Inverser l'ordre\nToggleAutoSize.label = Auto-dimensionner les colonnes\nStop.label = Arrêter le changement de dossier\nToggleColumn.show = Afficher la colonne %1\nToggleColumn.hide = Cacher la colonne %1\nToggleCommandBar.show = Afficher la barre de commande\nToggleCommandBar.hide = Cacher la barre de commande\nToggleToolBar.show = Afficher la barre d'outils\nToggleToolBar.hide = Cacher la barre d'outils\nCustomizeCommandBar.label = Personnaliser la barre de commande\nToggleStatusBar.show = Afficher la barre de status\nToggleStatusBar.hide = Cacher la barre de status\nToggleShowFoldersFirst.label = Afficher d'abord les répertoires\nToggleTree.label = Vue arborescente\nPopupLeftDriveButton.label = Changer le dossier de gauche\nPopupRightDriveButton.label = Changer le dossier de droite\nRecallPreviousWindow.label = Rappeler la fenêtre précédente\nRecallNextWindow.label = Rappeler la fenêtre suivante\nRecallWindow.label = Rappeler la fenêtre #%1\nBringAllToFront.label = Tout ramener au premier plan\nSwitchActiveTable.label = Basculer entre les dossiers de droite et de gauche\nSelectNextBlock.label = Descendre d'un bloc\nSelectPreviousBlock.label = Remonter d'un bloc\nSelectNextPage.label = Descendre d'une page\nSelectPreviousPage.label = Remonter d'une page\nSelectNextRow.label = Descendre d'une ligne\nSelectPreviousRow.label = Remonter d'une ligne\nSelectFirstRow.label = Sélectionner le premier fichier du dossier\nSelectLastRow.label = Sélectionner le dernier fichier du dossier\nSplitEqually.label = Scinder au milieu\nSplitVertically.label = Scincer verticalement\nSplitHorizontally.label = Scincer horizontalement\nShowKeyboardShortcuts.label = Raccourcis clavier\nGoToWebsite.label = Site web \nGoToForums.label = Forums\nReportBug.label = Reporter a bug\nDonate.label = Faire une donation\nShowAbout.label = A propos de trolCommander\nOpenTrash.label = Ouvrir la Corbeille\nEmptyTrash.label = Vider la Corbeille\nCalculateChecksum.label = Calculer la checksum\nMaximizeWindow.label = Agrandir\nMinimizeWindow.label = Réduire\nGoToDocumentation.label = Documentation en ligne\nShowParentFoldersQL.label = Dossiers parents\nShowRecentLocationsQL.label = Dossiers récents\nShowRecentExecutedFilesQL.label = Fichiers récemment exécutés\nSplitFile.tooltip = Scinder un fichier en plusieurs parties\nCombineFiles.tooltip = Aggréger des fichiers scindés pour recréer le fichier original\nShowDebugConsole.label = Console de débug\nFocusPrevious.label = Sélectionner le précédent composant\nFocusNext.label = Sélectionner le prochain composant\nfile_menu = Fichier\nfile_menu.open_with = Ouvrir avec\nmark_menu = Marquer\nview_menu = Vue\nview_menu.show_hide_columns = Afficher/Cacher colonnes\ngo_menu = Go\nbookmarks_menu = Favoris\nbookmarks_menu.no_bookmark = Pas de favori\ndrive_popup.network_shares = Partages réseau\nquick_lists_menu = Listes éclair \nwindow_menu = Fenêtre\nhelp_menu = Aide\nstatus_bar.selected_files = %1 de %2 sélectionnés\nstatus_bar.connecting_to_folder = Connexion en cours, appuyer sur ESCAPE pour annuler \nstatus_bar.volume_free = Libre: %1\nstatus_bar.volume_capacity = Capacité: %1\nshortcuts_panel.title = Raccourcis\nshortcuts_panel.restore_defaults = Réinitialiser\nshortcuts_panel.show = Afficher\nshortcuts_panel.default_message = Appuyez sur Entrée ou double-cliquez sur le raccourci à éditer \nshortcuts_table.action_description = Description de l'action\nshortcuts_table.shortcut = Raccourci\nshortcuts_table.alternate_shortcut = Raccourci secondaire\nshortcuts_table.type_in_a_shortcut = Saisissez le raccourci\ncommand_bar_dialog.help = Déplacez les boutons pour personnaliser la barre de commande\ntable.folder_access_error_title = Erreur d'accès au répertoire\ntable.folder_access_error = Le contenu du dossier ne peut être lu\ntable.download_or_browse = Souhaitez-vous explorer ou télécharger ce fichier ?\nversion_dialog.no_new_version_title = Pas de nouvelle version\nversion_dialog.no_new_version = Félicitations, vous avez déjà la dernière version.\nversion_dialog.new_version_title = Nouvelle version disponible\nversion_dialog.new_version = Une nouvelle version de trolCommander est disponible.\nversion_dialog.new_version_url = Une nouvelle version de trolCommander est disponible à %1.\nversion_dialog.not_available_title = Serveur indisponible\nversion_dialog.not_available = L'information de version n'a pas pu être récupérée.\nversion_dialog.install_and_restart = Installer et redémarrer\nversion_dialog.preparing_for_update = Préparation de la mise à jour...\nquit_dialog.title = Quitter trolCommander\nquit_dialog.desc = Il y a %1 fenêtre(s) ouverte(s). Etes-vous sûr de vouloir quitter ?\nquit_dialog.show_next_time = Afficher la prochaine fois\ndestination_dialog.file_exists_action = Action par défaut lorsqu'un fichier existe\ndestination_dialog.verify_integrity = Vérifier l'intégrité des données\ndestination_dialog.skip_errors = Ignorer les erreurs\nfile_collision_dialog.title = Collision de fichiers\nrename_dialog.new_name = Nouveau nom\ncopy_dialog.destination = Copier les fichiers sélectionnés dans\ncopy_dialog.error_title = Erreur de copie\ncopy_dialog.copying = Copie en cours\ncopy_dialog.copying_file = Copie de %1 en cours\npack_dialog.packing = Compression en cours\npack_dialog.packing_file = Compression de %1 en cours\npack_dialog.error_title = Erreur de compression\npack_dialog_description = Ajouter les fichiers sélectionnés dans\npack_dialog.archive_format = Format de l'archive\nunpack_dialog.destination = Décompresser les fichiers selectionnés dans\nunpack_dialog.error_title = Erreur de décompression\nunpack_dialog.unpacking = Décompression en cours\nunpack_dialog.unpacking_file = Décompression de %1 en cours\noptimizing_archive = Optimisation de l'archive %1\nerror_while_optimizing_archive = Erreur pendant l'optimisation de %1\nmove_dialog.move_description = Déplacer vers\nmove_dialog.error_title = Erreur de déplacement\nmove_dialog.moving = Déplacement en cours\nmove_dialog.moving_file = Déplacement de %1 en cours\ndownload_dialog.description = Télécharger le fichier dans\ndownload_dialog.error_title = Erreur de téléchargement\ndownload_dialog.downloading = Téléchargement en cours\ndownload_dialog.downloading_file = Téléchargement de %1 en cours\nmkfile_dialog.allocate_space = Allouer de l'espace\ndelete_dialog.permanently_delete.confirmation = Supprimer définitivement les fichiers sélectionnés ?\ndelete_dialog.move_to_trash.confirmation = Supprimer les fichiers sélectionnés ?\ndelete_dialog.move_to_trash.confirmation_details = Les fichiers seront déplacés dans la corbeille.\ndelete_dialog.move_to_trash.option = Déplacer dans la corbeille\ndelete_dialog.move_to_trash.failed = Un ou plusieurs fichiers n'ont pu être déplacés dans la corbeille.\ndelete_dialog.deleting = Suppression en cours\ndelete_dialog.error_title = Erreur de suppression\ndelete.deleting_file = Suppression de %1 en cours\nemail_dialog.prefs_not_set_title = Paramètres absents\nemail_dialog.prefs_not_set = Vous devez d'abord configurer vos paramètres email.\nemail_dialog.from = De\nemail_dialog.to = A\nemail_dialog.subject = Sujet\nemail_dialog.send = Envoyer\nemail_dialog.error_title = Erreur d'envoi\nemail_dialog.read_error = Les fichiers ne peuvent être lus.\nemail_dialog.sending = Envoi en cours\nemail.sending_file = Envoi de %1\nemail.connecting_to_server = Connexion à %1\nemail.server_unavailable = Echec de la connexion au serveur %1, vérifiez vos préférences de mail ou réessayez plus tard.\nemail.connection_closed = Connexion fermée par le serveur, le message n'a pas été envoyé.\nemail.goodbye_failed = Erreur lors de la fermeture de connexion, le message n'a peut-être pas été envoyé.\nemail.send_file_error = Erreur lors de l'envoi du fichier %1, le message n'a pas été envoyé.\nsplit_file_dialog.error_title = Erreur de scindage\nsplit_file_dialog.file_to_split = Fichier à scinder\nsplit_file_dialog.target_directory = Répertoire cible\nsplit_file_dialog.part_size = Taille d'une partie\nsplit_file_dialog.parts = Nombre de parties\nsplit_file_dialog.generate_CRC = Générer un fichier CRC\nsplit_file_dialog.max_parts = Le nombre maximum de parties authorisé est de %1\nsplit_file_dialog.auto = Automatique\nsplit_file_dialog.insert_new_media = Insérer un nouveau disque\ncombine_files_dialog.error_title = Erreur d'agrégation\ncombine_files_job.no_crc_file = Agrégation terminée avec succès. Pas de fichier CRC.\ncombine_files_job.crc_read_error = Erreur lors de la lecture du fichier CRC.\ncombine_files_job.crc_check_failed = Échec vérification CRC (%1 au lieu de %2).\ncombine_files_job.crc_ok = Agrégation terminée avec succès. Vérification CRC OK.\nfile_selection_dialog.mark = Marquer\nfile_selection_dialog.unmark = Dé-marquer\nfile_selection_dialog.mark_description = Marquer les fichiers dont le nom\nfile_selection_dialog.unmark_description = Dé-marquer les fichiers dont le nom\nfile_selection_dialog.case_sensitive = Respecter la casse\nfile_selection_dialog.include_folders = Inclure les dossiers\nprogress_dialog.starting = Démarrage du transfert...\nprogress_dialog.transferred = %1 transférés à %2\nprogress_dialog.elapsed_time = Temps écoulé\nprogress_dialog.advanced = Avancé\nprogress_dialog.current_speed = Vitesse actuelle\nprogress_dialog.limit_speed = Limiter la vitesse\nprogress_dialog.close_when_finished = Fermer la fenêtre lorsque terminé\nprogress_dialog.processing_files = Traitement des fichiers en cours\nprogress_dialog.processing_file = Traitement de %1 en cours\nprogress_dialog.verifying_file = Vérification de %1\nprogress_dialog.job_finished = Tâche terminée\nprogress_dialog.job_error = Erreur pendant tâche\nproperties_dialog.file_properties = Propriétés de %1\nproperties_dialog.contents = Contenu\nproperties_dialog.calculating = Calcul en cours...\ncalculate_checksum_dialog.checksum_algorithm = Algorithme de checksum\ncalculate_checksum_dialog.temporary_file = Fichier temporaire\nchange_date_dialog.now = Maintenant\nchange_date_dialog.specific_date = Date spécifique\nrun_dialog.run_command_description = Exécuter dans le répertoire courant\nrun_dialog.run_in_home_description = Exécuter dans le répertoire utilisateur\nrun_dialog.command_output = Sortie de la commande\nrun_dialog.run = Exécuter\nrun_dialog.clear_history = Effacer l'historique\nserver_connect_dialog.server_type = Type de connexion\nserver_connect_dialog.server = Serveur\nserver_connect_dialog.share = Partage\nserver_connect_dialog.domain = Domaine\nserver_connect_dialog.username = Nom d'utilisateur\nserver_connect_dialog.initial_dir = Répertoire initial\nserver_connect_dialog.port = Port\nserver_connect_dialog.server_url = URL serveur\nserver_connect_dialog.http_url = URL site web\nserver_connect_dialog.connect = Se connecter\nserver_connect_dialog.protocol = Protocole\nserver_connect_dialog.nfs_version = Version de NFS\nserver_connect_dialog.private_key = Clé privée\nserver_connect_dialog.passphrase = Phrase secrète\nftp_connect.passive_mode = Activer le mode passif\nftp_connect.anonymous_user = Utilisateur anonyme\nftp_connect.nb_connection_retries = Nombre de re-tentatives de connexion\nftp_connect.retry_delay = Délai entre tentatives de connexion (en secondes)\nhttp_connect.basic_authentication = HTTP Basic Authentication (optionnel)\nserver_connections_dialog.disconnect = Déconnecter\nserver_connections_dialog.connection_busy = Occupée\nserver_connections_dialog.connection_idle = Inactive\nbonjour.bonjour_services = Services Bonjour\nbonjour.no_service_discovered = Aucun service découvert\nbonjour.bonjour_disabled = Bonjour disabled\nauth_dialog.title = Authentification\nauth_dialog.desc = Veuillez entrer un identifiant et un mot de passe\nauth_dialog.server = Serveur\nauth_dialog.store_credentials = Sauver les identifiants (encryption faible)\nauth_dialog.connect_as = Se connecter en tant que\nauth_dialog.authentication_failed = L'authentification a échoué\nsortable_list.move_up = Monter\nsortable_list.move_down = Descendre\nadd_bookmark_dialog.add = Ajouter\nedit_bookmarks_dialog.new = Nouveau\nfile_viewer.view_error_title = Erreur d'affichage\nfile_viewer.view_error = Impossible de voir le fichier.\nfile_viewer.file_menu = Fichier\nfile_viewer.close = Fermer\nfile_viewer.large_file_warning = Ce fichier est peut-être trop large pour cette operation.\nfile_viewer.open_anyway = Ignorer et ouvrir\ntext_viewer.edit = Edition\ntext_viewer.copy = Copier\ntext_viewer.select_all = Tout sélectionner\ntext_viewer.find = Rechercher\ntext_viewer.find_next = Rechercher suivant\ntext_viewer.find_previous = Rechercher précédent\ntext_viewer.binary_file_warning = Ce fichier a l'air d'être un fichier binaire\nimage_viewer.controls_menu = Contrôles\nimage_viewer.zoom_in = Zoomer\nimage_viewer.zoom_out = Dézoomer\nfile_editor.edit_error_title = Erreur d'édition\nfile_editor.edit_error = Impossible d'éditer le fichier.\nfile_editor.save = Enregistrer\nfile_editor.save_as = Enregistrer sous...\nfile_editor.save_warning = Enregistrer les modifications avant de fermer ce fichier ?\nfile_editor.cannot_write = Impossible d'écrire le fichier. \ntext_editor.cut = Couper\ntext_editor.paste = Coller\nshortcuts_dialog.quick_search.start_search = Entrez un caractère pour commencer une recherche rapide\nshortcuts_dialog.quick_search.cancel_search = Annuler la recherche rapide\nshortcuts_dialog.quick_search.remove_last_char = Supprimer le dernier caractère de recherche\nshortcuts_dialog.quick_search.jump_to_previous = Aller au résultat précédent\nshortcuts_dialog.quick_search.jump_to_next = Aller au résultat suivant\nshortcuts_dialog.quick_search.mark_jump_next = Marquer/Dé-marquer le fichier courant et aller au résultat suivant\ntheme_editor.title = Editeur de thèmes\ntheme_editor.folder_tab = Panneau de dossier\ntheme_editor.shell_tab = Shell\ntheme_editor.shell_history_tab = Historique du shell\ntheme_editor.statusbar_tab = Barre de status\ntheme_editor.free_space = Espace libre\ntheme_editor.free_space.ok = OK\ntheme_editor.free_space.warning = Avertissement\ntheme_editor.free_space.critical = Critique\ntheme_editor.locationbar_tab = Barre d'emplacement\ntheme_editor.editor_tab = Editeur de fichiers\ntheme_editor.font = Fonte\ntheme_editor.active_panel = Actif\ntheme_editor.inactive_panel = Inactif\ntheme_editor.general = General\ntheme_editor.could_not_save_theme = Erreur lors de l'écriture du thème %1\ntheme_editor.border = Bordure\ntheme_editor.background = Fond\ntheme_editor.alternate_background = Fond alterné\ntheme_editor.unfocused_background = Fond (sans focus)\ntheme_editor.quick_search.unmatched_file = Fichier ne correspondant pas\ntheme_editor.text = Texte\ntheme_editor.progress = Progression\ntheme_editor.normal = Normal\ntheme_editor.normal_unfocused = Normal (sans focus)\ntheme_editor.selected = Sélectionné\ntheme_editor.selected_unfocused = Sélectionné (without focus)\ntheme_editor.color = Couleur\ntheme_editor.colors = Couleurs\ntheme_editor.plain_file = Fichier normal\ntheme_editor.marked_file = Fichier marqué\ntheme_editor.hidden_file = Fichier caché\ntheme_editor.folder = Dossier\ntheme_editor.archive_file = Archive\ntheme_editor.symbolic_link = Lien symbolique\ntheme_editor.header = En-tête\ntheme_editor.item = Élément\ncommand_bar_customize_dialog.available_actions = Actions disponibles\ncommand_bar_customize_dialog.modifier = Modificateur\nprefs_dialog.title = Préférences\nprefs_dialog.general_tab = Général\nprefs_dialog.day = Jour\nprefs_dialog.month = Mois\nprefs_dialog.year = Année\nprefs_dialog.language = Langage (après redémarrage)\nprefs_dialog.date_time = Format de date et d'heure\nprefs_dialog.time = Heure\nprefs_dialog.date = Date\nprefs_dialog.date_separator = Séparateur\nprefs_dialog.time_12_hour = Format 12 heures\nprefs_dialog.time_24_hour = Format 24 heures\nprefs_dialog.show_seconds = Afficher les secondes\nprefs_dialog.show_century = Afficher le siècle\nprefs_dialog.check_for_updates_on_startup = Chercher les mises à jour au démarrage\nprefs_dialog.show_splash_screen = Afficher l'écran de chargement\nprefs_dialog.folders_tab = Dossiers\nprefs_dialog.startup_folders = Dossiers de démarrage\nprefs_dialog.left_folder = Dossier de gauche\nprefs_dialog.right_folder = Dossier de droite\nprefs_dialog.last_folder = Dernier dossier visité\nprefs_dialog.custom_folder = Dossier spécifique\nprefs_dialog.show_hidden_files = Afficher les fichiers cachés\nprefs_dialog.show_ds_store_files = Afficher les fichiers .DS_Store\nprefs_dialog.show_system_folders = Afficher les dossiers système\nprefs_dialog.compact_file_size = Arrondir les tailles de fichiers affichées\nprefs_dialog.follow_symlinks_when_cd = Suivre les liens symboliques en changeant de répertoire courant\nprefs_dialog.appearance_tab = Apparence\nprefs_dialog.look_and_feel = Look & Feel\nprefs_dialog.icons_size = Taille des icônes\nprefs_dialog.toolbar_icons = Barre d'outils\nprefs_dialog.command_bar_icons = Barre de commande\nprefs_dialog.file_icons = Type de fichiers\nprefs_dialog.use_system_file_icons = Utiliser les icônes de fichier système\nprefs_dialog.use_system_file_icons.always = Toujours\nprefs_dialog.use_system_file_icons.never = Jamais\nprefs_dialog.use_system_file_icons.applications = Seulement pour les applications\nprefs_dialog.edit_current_theme = Editer le thème courrant...\nprefs_dialog.themes = Thèmes\nprefs_dialog.import_theme = Importer un thème\nprefs_dialog.import_look_and_feel = Importer un look & feel\nprefs_dialog.no_look_and_feel = Aucun look & feel n'a été trouvé.\nprefs_dialog.error_in_import = Une erreur est survenue lors de l'import du thème %1.\nprefs_dialog.cannot_read_theme = Erreur lors de la lecture du fichier %1\nprefs_dialog.export_theme = Exporter %1\nprefs_dialog.import = Importer\nprefs_dialog.export = Exporter\nprefs_dialog.theme_type = Type: %1\nprefs_dialog.delete_theme = Supprimer définitivement le thème %1 ?\nprefs_dialog.delete_look_and_feel = Supprimer définitivement le look & feel %1 ?\nprefs_dialog.rename_failed = Impossible de renommer le thème %1\nprefs_dialog.xml_file = Fichier XML\nprefs_dialog.jar_file = Fichier JAR\nprefs_dialog.mail_tab = Email\nprefs_dialog.mail_settings = Paramètres d'email sortant\nprefs_dialog.mail_name = Votre nom\nprefs_dialog.mail_address = Votre adresse email\nprefs_dialog.mail_server = Serveur SMTP\nprefs_dialog.misc_tab = Autres\nprefs_dialog.use_brushed_metal = Utiliser le look 'brushed metal' (après redémarrage)\nprefs_dialog.confirm_on_quit = Afficher un dialogue de confirmation en quittant\nprefs_dialog.default_shell = Utiliser le shell par défaut du système\nprefs_dialog.custom_shell = Utiliser un shell spécifique\nprefs_dialog.shell_encoding = Encodage du shell\nprefs_dialog.auto_detect_shell_encoding = Détection automatique\nprefs_dialog.enable_bonjour_discovery = Activer la découverte des services Bonjour\nprefs_dialog.enable_system_notifications = Activer les notifications système\ndebug_console_dialog.level = Niveau\nunit.byte = octet\nunit.bytes = octets\nunit.bytes_short = o\nunit.kb = Ko\nunit.mb = Mo\nunit.gb = Go\nunit.tb = To\nunit.speed = %1/s\nduration.seconds = %1s\nduration.minutes = %1m\nduration.hours = %1h\nduration.days = %1j\nduration.months = %1mo\nduration.years = %1a\ntheme.custom_theme = Thème personnalisé\ntheme.custom = Personnalisé\ntheme.built_in = Pré-installé\ntheme.add_on = Ajouté\ntheme.current = courant\ntheme_could_not_be_loaded = Une erreur est survenue lors du chargement de ce thème.\nsetup.title = Bienvenue dans trolCommander\nsetup.intro = Veuillez choisir le comportement souhaité de trolCommander.\nsetup.look_and_feel = Selectionnez votre Look & Feel\nsetup.theme = Selectionnez votre thème\nfont_chooser.font_size = Taille\nfont_chooser.font_bold = Gras\nfont_chooser.font_italic = Italique\ncolor_chooser.red = Rouge\ncolor_chooser.green = Vert\ncolor_chooser.blue = Bleu\ncolor_chooser.hue = Tonalité\ncolor_chooser.brightness = Luminosité\ncolor_chooser.swatches = Echantillons\ncolor_chooser.saturation = Saturation\ncolor_chooser.rgb = RVB\ncolor_chooser.hsb = TSL\ncolor_chooser.recent = Récentes\ncolor_chooser.alpha = Transparence alpha\ncolor_chooser.title = Choisir une couleur\nbatch_rename_dialog.mask = Modèle de renommage\nbatch_rename_dialog.search_replace = Chercher & Remplacer\nbatch_rename_dialog.search_for = Chercher\nbatch_rename_dialog.replace_with = Remplacer par\nbatch_rename_dialog.counter = Compteur\nbatch_rename_dialog.start_at = Commencer à\nbatch_rename_dialog.step_by = Incrément\nbatch_rename_dialog.format = Format\nbatch_rename_dialog.upper_lower_case = Casse\nbatch_rename_dialog.no_change = Inchangée\nbatch_rename_dialog.lower_case = minuscule\nbatch_rename_dialog.upper_case = MAJUSCULE\nbatch_rename_dialog.first_upper = Première lettre majuscule\nbatch_rename_dialog.word = Première De Chaque Mot\nbatch_rename_dialog.old_name = Ancien nom\nbatch_rename_dialog.new_name = Nouveau nom\nbatch_rename_dialog.block_name = Conserver\nbatch_rename_dialog.range = Intervalle\nbatch_rename_dialog.proceed_renaming = %1 fichiers sur %2 vont être renommés. Etes-vous sûr?\nbatch_rename_dialog.duplicate_names = Doublons de noms!\nparent_folders_quick_list.empty_message = Le dossier courant n'a pas de parent\nrecent_locations_quick_list.empty_message = Pas de dossier récent\nrecent_executed_files_quick_list.empty_message = Pas de fichier récemment exécuté\n#server_connect_dialog.auth_error = Identifiant ou mot de passe incorrect.\n#move_dialog.cannot_move_to_itself = Impossible de déplacer les fichiers vers un sous-répertoire.\n#pack.error_on_file = Erreur lors de la compression de %1\n#pack_dialog.cannot_write = Impossible de créer le fichier dans le dossier de destination.\n#unpack.unable_to_open_zip = Erreur lors de l'ouverture du fichier zip %1.\n#image_viewer.previous_image = Image précédente\n#image_viewer.next_image = Image suivante\n#mkdir_dialog.title = Créer répertoire\n#mkdir_dialog.error_title = Erreur de création\n#edit_bookmarks_dialog.remove = Supprimer\n#mkdir_dialog.description = Créer le répertoire\n#mkfile_dialog.description = Créer le fichier vide\n#done = Terminé\n#progress_dialog.hide = Cacher\n#move_dialog.rename_description = Renommer en\n#theme_editor.shell_font = Fonte du shell\n#theme_editor.history_font = Fonte de l'historique\n#theme_editor.shell_colors = Couleurs du shell\n#theme_editor.history_colors = Couleurs de l'historique\n#ToggleHiddenFiles.hide = Ne pas afficher les fichiers cachés\n#auth_dialog.error_was = L'erreur était: %1\n#table.hide_column = Cacher la colonne\n#delete.symlink_warning_title = Lien symbolique\n#delete.symlink_warning = Ce fichier a l'air d'être un lien symbolique:\\n\\n  Fichier: %1\\n  Pointe vers: %2\\n\\nSupprimer le lien seulement ou\\nSuivre le lien symbolique et supprimer le répertoire (ATTENTION) ?\n#delete.delete_link_only = Supprimer le lien\n#delete.delete_linked_folder = Supprimer le répertoire\n#Unpack.label = Décompresser fichiers\n#Pack.label = Compresser fichiers\n"
  },
  {
    "path": "src/main/resources/dictionary_hu_HU.properties",
    "content": "ok = OK\nyes = Igen\nno = Nem\ncancel = Mégse\nedit = Szerkesztés\nclose = Bezár\nreset = Alaphelyzet\nrename = Átnevez\napply = Alkalmaz\nchange = Csere\nsave = Mentés\ndont_save = Mentés nélkül\nreplace = Csere\ndont_replace = Csere nélkül\ndelete = Törlés\nskip = Kihagy\nskip_all = Mindet kihagy\nretry = Újra\nresume = Folytat\noverwrite = Felülír\noverwrite_if_older = Felülír, ha régebbi\nduplicate = Megkettőz\napply_to_all = Mindhez alkalmaz\ncopy = Másol\nmove = Mozgat\npack = Tömörítés\nunpack = Kicsomagolás\ndownload = Letölt\nsplit = Fájldarabolás\ncombine = Fájlok összefűzése\nbrowse = Tallóz\nask = Kérdez\nstop = Megállít\npause = Szünet\nquick_search = Gyorskeresés\nfile_manager = Fájlkezelő\ncreate = Létrehoz\ncreating_file = %1 Létrehozása\nchoose = Kiválaszt\ncustomize = Beállítás\nchoose_folder = Könyvtár választás\nlogin = Login\npassword = Jelszó\nuser = Felhasználó\nencoding = Kódolás\npreferred_encodings = Ajánlott kódolás\nlicense = Licenc\nname = Név\nsize = Méret\ndate = Dátum\nextension = Típus\npermissions = Engedély\nowner = Tulajdonos\ngroup = Csoport\nlocation = Hely\nuntitled = Névtelen\nsource = Forrás\ndestination = Cél\nrecurse_directories = A kiválasztott könyvtárakra rekurzívan alkalmaz\ngo_to = Ugrás\nexample = Példa\npreview = Előnézet\ncomment = Megjegyzés\nsample_text = Példa szöveg\nnb_files = %1 fájl\nnb_folders = %1 könyvtár\nloading = Betöltés...\nthis_operation_cannot_be_undone = A művelet nem vonható vissza.\nremove = Mozgat\ndetails = Részletek\nwarning = Figyelem\nerror = Hiba\ngeneric_error = Hiba történt a művelet elvégzése alatt.\nfolder_does_not_exist = A könyvtár nem létezik vagy nem található.\nthis_folder_does_not_exist = A könyvtár nem létezik vagy nem elérhető: %1\nthis_file_does_not_exist = A fájl nem létezik vagy nem elérhető: %1\ninvalid_path = Érvénytelen útvonal: %1\ndirectory_already_exists = %1 könyvtár már létezik.\nfile_exists_in_destination = A fájl már létezik a megadott helyen\nsource_parent_of_destination = A könyvtárat saját alkönyvtárába próbálta másolni\ncannot_read_file = Nem olvasható fájl %1\ncannot_write_file = Nem írható fájl %1\ncannot_create_folder = Nem lehet a könyvtárat létrehozni %1\ncannot_read_folder = Nem lehet a könyvtár tartalmát olvasni %1\ncannot_delete_file = Nem lehet a fájlt törölni %1\ncannot_delete_folder = Nem lehet a könyvtárat törölni %1\nerror_while_transferring = Hiba fájlátvitel közben %1\nsame_source_destination = A forrás és a célkönyvtár azonos\nfile_already_exists = %1 már létezik, lecseréli?\nwrite_error = Írási hiba\nread_error = Olvasási hiba\nintegrity_check_error = Hiba az ellenőrzéskor: A forrás és a cél nem azonos\nstartup_error = Váratlan hiba fordult elő a trolCommander indulásakor.\npermissions.read = Olvasás\npermissions.write = Írás\npermissions.executable = Végrehajtás\npermissions.group = Csoport\npermissions.other = Egyéb\npermissions.octal_notation = Oktális jelölés\naction_categories.all = Összes\naction_categories.navigation = Navigáció\naction_categories.selection = Kiválasztás\naction_categories.view = Megjelenítés\naction_categories.file_operations = Fájl műveletek\naction_categories.windows = Ablak\nTerminal.label = Terminal\nTerminal.tooltip = Terminal\nFindFile.label = Find file\nFindFile.tooltip = Find file\nAddBookmark.label = Hozzáadás a Könyvjelzőkhöz\nAddBookmark.tooltip = Az aktuális könyvtár hozzáadása a Könyvjelzőkhöz\nBatchRename.label = Csoportos átnevezés\nEditBookmarks.label = Könyvjelzők rendezése\nExploreBookmarks.label = Könyvjelzők átvizsgálása\nEditCredentials.label = Megbízólevél javítása\nChangeLocation.label = Az aktuális hely módosítása\nChangeDate.label = Dátum módosítása\nChangeDate.tooltip = A kijelölt fájl(ok) módosítása\nChangePermissions.label = Jogosultságok módosítása\nChangePermissions.tooltip = A kijelölt fájl(ok) jogosultságainak módosítása\nCheckForUpdates.label = Frissítések keresése\nCompareFolders.label = Könyvtárak összehasonlítása\nConnectToServer.label = Csatlakozás a szerverhez\nConnectToServer.tooltip = Kapcsolodás a távoli szerverhez\nView.label = Megnéz\nInternalView.label = Megnéző (beépített)\nView.tooltip = A kijelölt fájl megtekintése\nInternalEdit.label = Szerkesztő (beépített)\nEdit.tooltip = A kijelölt fájl módosítása\nCopy.tooltip = A kijelölt fájlok másolása\nLocalCopy.label = Lokális másolás\nLocalCopy.tooltip = A kijelölt fájlok másolása az aktuális könyvtárba\nMove.tooltip = A kijelölt fájlok mozgatása\nRename.tooltip = A kijelölt fájl átnevezése\nMkdir.label = Új könyvtár\nMkdir.tooltip = Új könyvtár létrehozása az aktuális könyvtárban\nMkfile.label = Fájl létrehozása\nMkfile.tooltip =  Fájl létrehozása az aktuális könyvtárban\nDelete.tooltip = A kijelölt fájlok lomtárba helyezése, ha lehetséges\nPermanentDelete.label = Végleges törlés\nPermanentDelete.tooltip = A kijelölt fájlok végleges törlése a Lomtár használata nélkül\nRefresh.label = Frissít\nRefresh.tooltip = Az aktuális könyvtár frissítése\nCloseWindow.label = Ablak bezárása\nCloseWindow.tooltip = Az ablak bezárása\nCopyFileNames.label = Fájlnevek másolása\nCopyFilePaths.label = Útvonalak másolása\nCopyFilesToClipboard.label = Fájl(ok) másolása\nPasteClipboardFiles.label = Fájl(ok) beillesztése\nEmail.label = Küldés e-mailben\nEmail.tooltip = A kijelölt fájl(ok) küldése mellékletként\nGoBack.label = Ugrás vissza\nGoBack.tooltip = Ugrás az előző könyvtárhoz\nGoForward.label = Ugrás előre\nGoForward.tooltip = Ugrás a következő könyvtárhoz\nGoToHome.label = Ugrás a kezdő mappához\nGoToParent.label = Ugrás a kezdethez\nGoToParent.tooltip = Ugrás a kezdeti könyvtárhoz\nGoToParentInOtherPanel.label = Ugrás a másik panel elejére\nGoToParentInBothPanels.label = Ugrás mindkét panel elejére\nGoToRoot.label = Ugrás a főkönyvtárhoz\nSortByName.label = Megnevezés szerint rendez\nSortByDate.label = Dátum szerint rendez\nSortBySize.label = Méret szerint rendez\nSortByExtension.label = Típus szerint rendez\nSortByPermissions.label = Jogosultság szerint rendez\nSortByOwner.label = Rendezés tulajdonos szerint\nSortByGroup.label = Rendezés csoportok szerint\nMarkGroup.label = Fájl(ok) kijelölése\nMarkGroup.tooltip = Csoportos fájlkijelölés\nUnmarkGroup.label = Kijelölés megszüntetése\nUnmarkGroup.tooltip = Csoportos fájlkijelölés megszüntetése\nMarkAll.label = Mindet kijelöl\nUnmarkAll.label = Összes kijelölés megszüntetése\nMarkSelectedFile.label = Kijelöl/Kihagy\nMarkSelectedFile.tooltip = Kijelöli/Kihagyja a kiválasztott fájlt\nMarkNextBlock.label = Egy blokk kijelölése lefelé\nMarkPreviousBlock.label = Egy blokk kijelölése felfelé\nMarkNextRow.label = Egy sor kijelölése lefelé\nMarkPreviousRow.label = Egy sor kijelölése felfelé\nMarkNextPage.label = Előző lap kijelölése\nMarkPreviousPage.label = Következő lap kijelölése\nMarkToFirstRow.label = Fájlok kijelölése a kezdethez\nMarkToLastRow.label = Fájlok kijelölése a végéhez\nMarkExtension.label = Kiterjesztés megjelölése\nInvertSelection.label = A kijelölés megfordítása\nSwapFolders.label = Oldalak felcserélése\nSwapFolders.tooltip = A bal és jobb panel váltás\nSetSameFolder.label = Ugyanaz mindkét oldalon\nSetSameFolder.tooltip = A bal és jobb oldalon ugyanaz a panel legyen\nNewWindow.label = Új ablak\nNewWindow.tooltip = Megnyitás új ablakban\nOpen.label = Megnyitás\nOpen.tooltip = Belép a könyvtárba / Belép az archív fájlba / Végrehajt\nOpenNatively.label = Megnyitás közvetlenül\nOpenNatively.tooltip = A kijelölt fájl megnyitása a rendszer által hozzárendelt programmal\nOpenInOtherPanel.label = Megnyitás a másik panelen\nOpenInBothPanels.label = Megnyitás mindkét panelen\nRevealInDesktop.label = Megnyitás a következővel: %1\nRunCommand.label = Parancsfuttatás\nRunCommand.tooltip = Parancs futtatása az aktuális könyvtárból\nPack.tooltip = Kijelölt fájlok csomagolása az archívumba\nUnpack.tooltip = Kijelölt archívumok kicsomagolása\nShowFileProperties.label = Tulajdonságok\nShowFileProperties.tooltip = A kijelölt fájl(ok) tulajdonságainak megjelenítése\nShowPreferences.label = Beállítások\nShowPreferences.tooltip = trolCommander beállítása\nShowServerConnections.label = A megnyitott kapcsolatok megjelenítése\nQuit.label = Kilépés\nReverseSortOrder.label = Fordított sorrend\nToggleAutoSize.label = Oszlopok automatikus méretezése\nStop.label = Könyvtár váltás megállítása\nToggleColumn.show = %1 oszlop megjelenítése\nToggleColumn.hide = %1 oszlop elrejtése\nToggleCommandBar.show = Parancsszalag megjelenítése\nToggleCommandBar.hide = Parancsszalag elrejtése\nToggleToolBar.show = Eszköztár megjelenítése\nToggleToolBar.hide = Eszköztár elrejtése\nCustomizeCommandBar.label = Parancsszalag beállítása\nToggleStatusBar.show = Állapotsor megjelenítése\nToggleStatusBar.hide = Állapotsor elrejtése\nToggleShowFoldersFirst.label = Mappákat a listában előre\nToggleTree.label = Könyvtárfa\nPopupLeftDriveButton.label = A bal panel megváltoztatása\nPopupRightDriveButton.label = A jobb panel megváltoztatása\nRecallPreviousWindow.label = Váltás az előző ablakra\nRecallNextWindow.label = Váltás a következő ablakra\nRecallWindow.label = Kiválasztandó ablak #%1\nBringAllToFront.label = Minden ablakot előre\nSwitchActiveTable.label = A bal és jobb panel váltás\nSelectNextBlock.label = Ugrás egy blokkal lefelé\nSelectPreviousBlock.label = Ugrás egy blokkal felfelé\nSelectNextPage.label = Ugrás a következő lapra\nSelectPreviousPage.label = Ugrás az előző lapra\nSelectNextRow.label = Ugrás a következő sorra\nSelectPreviousRow.label = Ugrás az előző sorra\nSelectFirstRow.label = Az első fájl kiválasztása az aktuális mappában\nSelectLastRow.label = Az utolsó fájl kiválasztása az aktuális mappában\nSplitEqually.label = Felosztás egyenlő részekre\nSplitVertically.label = Felosztás függőlegesen\nSplitHorizontally.label = Felosztás vízszintesen\nShowKeyboardShortcuts.label = Gyorsbillentyűk\nGoToWebsite.label = trolCommander honlap\nGoToForums.label = trolCommander fórum\nReportBug.label = Hibajelentés\nDonate.label = Adomány a fejlesztőknek\nShowAbout.label = trolCommander névjegy\nOpenTrash.label = Lomtár megnyitása\nEmptyTrash.label = Lomtár ürítése\nCalculateChecksum.label = Ellenőrző összeg számolása\nMaximizeWindow.label = Nagyítás\nMaximizeWindow.label.mac_os_x = Nagyítás\nMinimizeWindow.label = Kicsinyítés\nMinimizeWindow.label.mac_os_x = Kicsinyítés\nGoToDocumentation.label = Online dokumentáció\nShowParentFoldersQL.label = Felsőbb alkönyvtárak\nShowRecentLocationsQL.label = Legutóbb felkeresett helyek\nShowRecentExecutedFilesQL.label = Legutóbb futtatott fájlok\nSplitFile.tooltip = A fájl darabolása kisebb részekre\nCombineFiles.tooltip = Az eredeti fájl összeállítása a kisebb részekből\nShowDebugConsole.label = A konzol nyomkövetése\nFocusPrevious.label = Az előző összetevő kijelölése\nFocusNext.label = A következő összetevő kijelölése\nfile_menu = Fájl\nfile_menu.open_with = Megnyitás másként\nmark_menu = Kijelölés\nview_menu = Nézet\nview_menu.show_hide_columns = Oszlopok Mutatása/Elrejtése\ngo_menu = Ugrás\nbookmarks_menu = Könyvjelzők\nbookmarks_menu.no_bookmark = Nincsenek Könyvjelzők\nquick_lists_menu = Gyorslista\nwindow_menu = Ablak\nhelp_menu = Súgó\nstatus_bar.selected_files = %1 / %2 kijelölt\nstatus_bar.connecting_to_folder = Kapcsolódás a mappához... Mégse: [ESCAPE]\nstatus_bar.volume_free = Szabad: %1\nstatus_bar.volume_capacity = Teljes méret: %1\nshortcuts_panel.title = Gyorsbillentyűk\nshortcuts_panel.restore_defaults = Alaphelyzet visszaállítása\nshortcuts_panel.show = Megjelenítés\nshortcuts_panel.default_message = Nyomja meg az Enter billentyűt vagy kattintson duplán a Gyorsbillentyűk módosításához.\nshortcuts_table.action_description = A művelet leírása\nshortcuts_table.shortcut = Gyorsbillentyű\nshortcuts_table.alternate_shortcut = Másodlagos Gyorsbillentyű\nshortcuts_table.type_in_a_shortcut = Gyorsbillentyű típusa\ncommand_bar_dialog.help = A nyomógombok húzásával módosíthatja a Parancsszalagot.\ntable.folder_access_error_title = Könyvtár hozzáférési hiba\ntable.folder_access_error = A könyvtár tartalmát nem lehet olvasni\ntable.download_or_browse = Szeretné tallózni vagy letölteni ezt a fájlt?\nversion_dialog.no_new_version_title = Nincs új verzió\nversion_dialog.no_new_version = Gratulálunk, Önnek már a legújabb verziója van.\nversion_dialog.new_version_title = Az új verzió elérhető\nversion_dialog.new_version = Egy új trolCommander verzió elérhető.\nversion_dialog.new_version_url = Egy új trolCommander verzió elérhető itt %1.\nversion_dialog.not_available_title = A szerver nem elérhető\nversion_dialog.not_available = A verzióinformáció elérése nem lehetséges a szerverről.\nversion_dialog.install_and_restart = Telepítés és újraindítás\nversion_dialog.preparing_for_update = A frissítés előkészítése...\nquit_dialog.title = Kilépés a trolCommanderből\nquit_dialog.desc = Biztos ki akar lépni? (%1 trolCommander van megnyitva.)\nquit_dialog.show_next_time = Üzenet megjelenítése\ndestination_dialog.file_exists_action = Alapértelmezett művelet, ha a fájl létezik\ndestination_dialog.verify_integrity = Adatintegritás ellenőrzése\ndestination_dialog.skip_errors = Hibák kihagyása\nfile_collision_dialog.title = Fájlütközés\nrename_dialog.new_name = Új név\ncopy_dialog.destination = A kiválasztott fájl(ok) másolása\ncopy_dialog.error_title = Másolási hiba\ncopy_dialog.copying = Fájl(ok) másolása\ncopy_dialog.copying_file = Másolás %1\npack_dialog.packing = Fájl(ok) tömörítése\npack_dialog.packing_file = Tömörítés %1\npack_dialog.error_title = Tömörítési hiba\npack_dialog_description = A kiválasztott fájlok hozzáadása\npack_dialog.archive_format = Archív formátum\nunpack_dialog.destination = A kijelölt fájl(ok) kicsomagolása\nunpack_dialog.error_title = Kicsomagolási hiba\nunpack_dialog.unpacking = Fájl(ok) kicsomagolása\nunpack_dialog.unpacking_file = Kicsomagolás %1\noptimizing_archive = Archívum optimalizálás %1\nerror_while_optimizing_archive = Hiba az archívum optimalizálásakor %1\nmove_dialog.move_description = Áthelyezés ide\nmove_dialog.error_title = Áthelyezési hiba\nmove_dialog.moving = Fálj(ok) áthelyezése\nmove_dialog.moving_file = Áthelyezés %1\ndownload_dialog.description = Fájl(ok) letöltése ide\ndownload_dialog.error_title = Letöltési hiba\ndownload_dialog.downloading = Letöltés\ndownload_dialog.downloading_file = Letöltés %1\nmkfile_dialog.allocate_space = Hely lefoglalása\ndelete_dialog.permanently_delete.confirmation = Folyamatosan törli a kijelölt fájl(oka)t?\ndelete_dialog.move_to_trash.confirmation = Törli a kijelölt fájl(oka)t?\ndelete_dialog.move_to_trash.confirmation_details = A fájlok a lomtárba lesznek áthelyezve.\ndelete_dialog.move_to_trash.option = Áthelyezés a lomtárba\ndelete_dialog.move_to_trash.failed = A fájl(oka)t nem lehet a Lomtárba dobni.\ndelete_dialog.deleting = Törlés\ndelete_dialog.error_title = Törlési hiba\ndelete.deleting_file = Törlés %1\nemail_dialog.prefs_not_set_title = A levelező nincs konfigurálva\nemail_dialog.prefs_not_set = Önnek be kell állítania a levelező paramétereit.\nemail_dialog.from = Feladó\nemail_dialog.to = Címzett\nemail_dialog.subject = Tárgy\nemail_dialog.send = Küldés\nemail_dialog.error_title = E-mail fájl hiba\nemail_dialog.read_error = Az alkönyvtár(ak)ban lévő fájl(ok) olvasása nem lehetséges\nemail_dialog.sending = Fájlok küldése\nemail.sending_file = Küldés %1\nemail.connecting_to_server = Kapcsolódás %1\nemail.server_unavailable = Nem lehet kapcsolódni a levelezőszerverhez %1, kérjük, ellenőrizze a levelező beállításait és próbálja újra.\nemail.connection_closed = A szerver megszakította a kapcsolatot, a levél nincs elküldve.\nemail.goodbye_failed = Kapcsolatbontás közben hiba történt, így a levél nem biztos, hogy elment.\nemail.send_file_error = A fájl küldése nem lehetséges %1, a levél nincs elküldve.\nsplit_file_dialog.error_title = Fájldarabolási hiba\nsplit_file_dialog.file_to_split = Fájldarabolás\nsplit_file_dialog.target_directory = Célkönyvtár\nsplit_file_dialog.part_size = Egy rész mérete\nsplit_file_dialog.parts = Darabok száma\nsplit_file_dialog.generate_CRC = CRC fájl készítése\nsplit_file_dialog.max_parts = A részek maximális darabszáma: %1\nsplit_file_dialog.auto = Automatikus\nsplit_file_dialog.insert_new_media = Helyezzen be új adathordozót\ncombine_files_dialog.error_title = Fájl összefűzési hiba\ncombine_files_job.no_crc_file = A fájlok összefűzése sikerült. Nincs CRC fájl.\ncombine_files_job.crc_read_error = CRC fájl olvasási hiba.\ncombine_files_job.crc_check_failed = CRC hiba: hiányzik: %2, megtalált: %1\ncombine_files_job.crc_ok = A fájlok összefűzése sikerült. CRC ellenőrzőszám rendben.\nfile_selection_dialog.mark = Kijelölés\nfile_selection_dialog.unmark = Kijelölés visszavonása\nfile_selection_dialog.mark_description = Kijelöli a fájlokat, amelyeknek a neve\nfile_selection_dialog.unmark_description = Kijelölés visszavonása azon fájloknál, amelyeknek a neve\nfile_selection_dialog.case_sensitive = Kis- és NAGYBETŰ érzékeny\nfile_selection_dialog.include_folders = Könyvtárakkal együtt\nprogress_dialog.starting = Átvitel indítása...\nprogress_dialog.transferred = %1 átvitel %2 alatt megtörtént\nprogress_dialog.elapsed_time = Eltelt idő\nprogress_dialog.advanced = Speciális\nprogress_dialog.current_speed = Jelenlegi sebesség\nprogress_dialog.limit_speed = Sebesség korlát\nprogress_dialog.close_when_finished = Ablak bezárása, ha elkészült\nprogress_dialog.processing_files = Fájl(ok) feldolgozása\nprogress_dialog.processing_file = Feldolgozás %1\nprogress_dialog.verifying_file = %1 Ellenőrzése\nprogress_dialog.job_finished = Feladat befejezve\nprogress_dialog.job_error = Hiba a feladatban\nproperties_dialog.file_properties = %1 tulajdonságok\nproperties_dialog.contents = Tartalom\nproperties_dialog.calculating = számol...\ncalculate_checksum_dialog.checksum_algorithm = Ellenőrző algoritmus\ncalculate_checksum_dialog.temporary_file = Ideiglenes fájl\nchange_date_dialog.now = Most\nchange_date_dialog.specific_date = Speciális dátum\nrun_dialog.run_command_description = Futtatás az aktuális könyvtárban\nrun_dialog.run_in_home_description = Futtatás a főkönyvtárban\nrun_dialog.command_output = Parancs kimenet\nrun_dialog.run = Futtat\nrun_dialog.clear_history = Előzmények törlése\nserver_connect_dialog.server_type = Kapcsolat típusa\nserver_connect_dialog.server = Szerver\nserver_connect_dialog.share = Megoszt\nserver_connect_dialog.domain = Tartomány\nserver_connect_dialog.username = Felhasználónév\nserver_connect_dialog.initial_dir = Kezdő könyvtár\nserver_connect_dialog.port = Port\nserver_connect_dialog.server_url = Szerver URL\nserver_connect_dialog.http_url = Honlap URL\nserver_connect_dialog.connect = Kapcsolat\nserver_connect_dialog.protocol = Protokoll\nserver_connect_dialog.nfs_version = NFS verzió\nserver_connect_dialog.private_key = Privát kulcs\nserver_connect_dialog.passphrase = Jelmondat\nftp_connect.passive_mode = A passzív mód engedélyezése\nftp_connect.anonymous_user = Névtelen felhasználó\nftp_connect.nb_connection_retries = Próbálkozások száma\nftp_connect.retry_delay = Várakozás a próbálkozások között (másodpercben)\nhttp_connect.basic_authentication = HTTP Alapszintű Hitelesítés (opcionális)\nserver_connections_dialog.disconnect = Szétkapcsol\nserver_connections_dialog.connection_busy = Foglalt\nserver_connections_dialog.connection_idle = Inaktív\nbonjour.bonjour_services = Bonjour szolgáltatások\nbonjour.no_service_discovered = Nincs felderített szolgáltatás\nbonjour.bonjour_disabled = Bonjour letiltva\nauth_dialog.title = Hitelesítés\nauth_dialog.desc = Írja be a felhasználónevet és a jelszót\nauth_dialog.server = Szerver\nauth_dialog.store_credentials = A felhasználónév és a jelszó mentése (gyenge titkosítás)\nauth_dialog.connect_as = Csatlakozás mint\nauth_dialog.authentication_failed = A hitelesítés sikertelen\nsortable_list.move_up = Mozgatás fel\nsortable_list.move_down = Mozgatás le\nadd_bookmark_dialog.add = Hozzáadás\nedit_bookmarks_dialog.new = Új\nfile_viewer.view_error_title = Megtekintési hiba\nfile_viewer.view_error = A fájl megtekintése nem lehetséges.\nfile_viewer.file_menu = Fájl\nfile_viewer.close = Bezárás\nfile_viewer.large_file_warning = Ez a fájl túl nagy lehet ehhez a művelethez.\nfile_viewer.open_anyway = Mindenképpen megnyit\ntext_viewer.edit = Módosítás\ntext_viewer.copy = Másolás\ntext_viewer.select_all = Összes kijelölése\ntext_viewer.find = Keresés\ntext_viewer.find_next = Következő keresése\ntext_viewer.find_previous = Előző keresése\ntext_viewer.binary_file_warning = Ez a fájl valószínűleg bináris fájl\nimage_viewer.controls_menu = Beállítás\nimage_viewer.zoom_in = Nagyít\nimage_viewer.zoom_out = Kicsinyít\nfile_editor.edit_error_title = Módosítási hiba\nfile_editor.edit_error = A fájl módosítása nem lehetséges.\nfile_editor.save = Mentés\nfile_editor.save_as = Mentés másként...\nfile_editor.save_warning = Menti a változtatásokat ebben a fájlban a bezárás előtt?\nfile_editor.cannot_write = A fájl írása nem lehetséges.\ntext_editor.cut = Kivágás\ntext_editor.paste = Beillesztés\nshortcuts_dialog.quick_search.start_search = Gyorskereséshez írjon be néhány karaktert\nshortcuts_dialog.quick_search.cancel_search = Gyorskeresés befejezése\nshortcuts_dialog.quick_search.remove_last_char = A gyorkeresési karakterlánc utolsó betűjének törlése\nshortcuts_dialog.quick_search.jump_to_previous = Ugrás az előző gyorskeresés eredményéhez\nshortcuts_dialog.quick_search.jump_to_next = Ugrás a következő gyorskeresés eredményéhez\nshortcuts_dialog.quick_search.mark_jump_next = Kilelöli/Visszavonja az aktuális fájlt és a következő keresési eredményhez ugrik\ntheme_editor.title = Témaszerkesztő\ntheme_editor.folder_tab = Könyvtár tábla\ntheme_editor.shell_tab = Végrehajtás\ntheme_editor.shell_history_tab = Előzmények\ntheme_editor.statusbar_tab = Állapotsor\ntheme_editor.free_space = Szabad hely\ntheme_editor.free_space.ok = OK\ntheme_editor.free_space.warning = Figyelem\ntheme_editor.free_space.critical = Kritikus\ntheme_editor.locationbar_tab = Helyzetsor\ntheme_editor.editor_tab = Fájlszerkesztő\ntheme_editor.font = Betű\ntheme_editor.active_panel = Aktív\ntheme_editor.inactive_panel = Inaktív\ntheme_editor.general = Általános\ntheme_editor.could_not_save_theme = Nem lehet írni a témát %1\ntheme_editor.border = Keret\ntheme_editor.background = Háttér\ntheme_editor.alternate_background = Alternatív háttér\ntheme_editor.unfocused_background = Háttér (kijelölve)\ntheme_editor.quick_search.unmatched_file = Kívülálló fájlok\ntheme_editor.text = Szöveg\ntheme_editor.progress = Folyamat\ntheme_editor.normal = Normál\ntheme_editor.normal_unfocused = Normál (kijelöletlen)\ntheme_editor.selected = Kiválasztott\ntheme_editor.selected_unfocused = Kiválasztott (kijelöletlen)\ntheme_editor.color = Szín\ntheme_editor.colors = Színek\ntheme_editor.plain_file = Sima fájl\ntheme_editor.marked_file = Kijelölt fájl\ntheme_editor.hidden_file = Rejtett fájl\ntheme_editor.folder = Könyvtár\ntheme_editor.archive_file = Archívum\ntheme_editor.symbolic_link = Jelképes hivatkozás\ntheme_editor.header = Fejléc\ntheme_editor.item = Elem\ncommand_bar_customize_dialog.available_actions = Lehetséges műveletek\ncommand_bar_customize_dialog.modifier = Módosítók\nprefs_dialog.title = Tulajdonságok\nprefs_dialog.general_tab = Általános\nprefs_dialog.day = Nap\nprefs_dialog.month = Hónap\nprefs_dialog.year = Év\nprefs_dialog.language = Nyelv (újraindítást igényel)\nprefs_dialog.date_time = Dátum és idő formátum\nprefs_dialog.time = Idő\nprefs_dialog.date = Dátum\nprefs_dialog.date_separator = Elválasztó\nprefs_dialog.time_12_hour = 12 órás formátum\nprefs_dialog.time_24_hour = 24 órás formátum\nprefs_dialog.show_seconds = Másodpercek megjelenítése\nprefs_dialog.show_century = Évszázad megjelenítése\nprefs_dialog.check_for_updates_on_startup = Ellenőrizze a frissítéseket induláskor\nprefs_dialog.show_splash_screen = Indítóképernyő megjelenítése\nprefs_dialog.folders_tab = Könyvtárak\nprefs_dialog.startup_folders = Indítási panelek\nprefs_dialog.left_folder = Bal panel\nprefs_dialog.right_folder = Jobb panel\nprefs_dialog.last_folder = Utoljára megtekintett könyvtár\nprefs_dialog.custom_folder = Egyedi könyvtár\nprefs_dialog.show_hidden_files = Rejtett fájlok megjelenítése\nprefs_dialog.show_ds_store_files = A .DS_Store fájlok megjelenítése\nprefs_dialog.show_system_folders = Rendszer könyvtárak megjelenítése\nprefs_dialog.compact_file_size = Fájlméretek kijelzése kerekítve\nprefs_dialog.follow_symlinks_when_cd = Könyvtár váltáskor ugrás a hivatkozásához\nprefs_dialog.appearance_tab = Megjelenés\nprefs_dialog.look_and_feel = Kinézet & Hatások\nprefs_dialog.icons_size = Ikon méret\nprefs_dialog.toolbar_icons = Eszköztár\nprefs_dialog.command_bar_icons = Parancssor\nprefs_dialog.file_icons = Fájl típusok\nprefs_dialog.use_system_file_icons = Rendszerikonok használata\nprefs_dialog.use_system_file_icons.always = Mindig\nprefs_dialog.use_system_file_icons.never = Soha\nprefs_dialog.use_system_file_icons.applications = Csak alkalmazásokhoz\nprefs_dialog.edit_current_theme = Jelenlegi téma javítása...\nprefs_dialog.themes = Témák\nprefs_dialog.import_theme = Téma importálás\nprefs_dialog.import_look_and_feel = Kinézet importálása\nprefs_dialog.no_look_and_feel = Nem található Kinézet.\nprefs_dialog.error_in_import = Hiba a téma importálásakor %1\nprefs_dialog.cannot_read_theme = Nem lehet betölteni a témát a fájlból %1\nprefs_dialog.export_theme = Exportálás %1\nprefs_dialog.import = Importálás\nprefs_dialog.export = Exportálás\nprefs_dialog.theme_type = Típus: %1\nprefs_dialog.delete_theme = Folyamatosan törli a(z) %1 témát?\nprefs_dialog.delete_look_and_feel = Folyamatosan töröljem a(z) %1 Kinézetet?\nprefs_dialog.rename_failed = Hiba a téma átnevezéskor %1\nprefs_dialog.xml_file = XML fájl\nprefs_dialog.jar_file = JAR fájl\nprefs_dialog.mail_tab = E-mail\nprefs_dialog.mail_settings = Kimenő levelezési beállítások\nprefs_dialog.mail_name = Az Ön neve\nprefs_dialog.mail_address = Az Ön e-mail címe\nprefs_dialog.mail_server = Kimenő (SMTP) szerver\nprefs_dialog.misc_tab = Egyebek\nprefs_dialog.use_brushed_metal = A 'brushed metal' felületet használata (újraindítást igényel)\nprefs_dialog.confirm_on_quit = Kérjen megerősítést kilépéskor\nprefs_dialog.default_shell = Az alapértelmezett végrehajtás használata\nprefs_dialog.custom_shell = Egyedi végrehajtás használata\nprefs_dialog.shell_encoding = Karakter kódolás\nprefs_dialog.auto_detect_shell_encoding = Automatikus felismerés\nprefs_dialog.enable_bonjour_discovery = A Bonjour szolgáltatások felderítésének engedélyezése\nprefs_dialog.enable_system_notifications = Rendszerértesítések engedélyezése\ndebug_console_dialog.level = Szint\nunit.byte = bájt\nunit.bytes = bájt\nunit.bytes_short = b\nunit.kb = KB\nunit.mb = MB\nunit.gb = GB\nunit.tb = TB\nunit.speed = %1/s\nduration.seconds = %1m\nduration.minutes = %1p\nduration.hours = %1ó\nduration.days = %1n\nduration.months = %1h\nduration.years = %1é\ntheme.custom_theme = Egyedi téma\ntheme.custom = Módosított\ntheme.built_in = Beépített\ntheme.add_on = Bővítmény\ntheme.current = jelenlegi\ntheme_could_not_be_loaded = Hiba a téma betöltésekor.\nsetup.title = Üdvözöljük a trolCommanderben\nsetup.intro = Válassza ki a trolCommander működési módját\nsetup.look_and_feel = Válassza ki a Megjelenést\nsetup.theme = Válassza ki a témát\nfont_chooser.font_size = Méret\nfont_chooser.font_bold = Félkövér\nfont_chooser.font_italic = Dőlt\ncolor_chooser.red = Piros\ncolor_chooser.green = Zöld\ncolor_chooser.blue = Kék\ncolor_chooser.hue = Színárnyalat\ncolor_chooser.brightness = Világosság\ncolor_chooser.swatches = Minta\ncolor_chooser.saturation = Telítettség\ncolor_chooser.rgb = RGB\ncolor_chooser.hsb = HSB\ncolor_chooser.recent = Legutóbbi\ncolor_chooser.alpha = Alfa átlátszóság\ncolor_chooser.title = Válasszon színt\nbatch_rename_dialog.mask = Átnevezési minta\nbatch_rename_dialog.search_replace = Keresés és Csere\nbatch_rename_dialog.search_for = Keresett szöveg\nbatch_rename_dialog.replace_with = Csere erre\nbatch_rename_dialog.counter = Számláló\nbatch_rename_dialog.start_at = Kezdés ennél\nbatch_rename_dialog.step_by = Növekmény\nbatch_rename_dialog.format = Formátum\nbatch_rename_dialog.upper_lower_case = Kisbetű - Nagybetű\nbatch_rename_dialog.no_change = Változatlan\nbatch_rename_dialog.lower_case = kisbetű\nbatch_rename_dialog.upper_case = NAGYBETŰ\nbatch_rename_dialog.first_upper = Mondatkezdő nagybetű\nbatch_rename_dialog.word = Szókezdő nagybetű\nbatch_rename_dialog.old_name = Régi név\nbatch_rename_dialog.new_name = Új név\nbatch_rename_dialog.block_name = Megtart\nbatch_rename_dialog.range = Tartomány\nbatch_rename_dialog.proceed_renaming = %1 fájl átnevezve (összesen: %2). Folytassam?\nbatch_rename_dialog.duplicate_names = Azonos megnevezések!\nparent_folders_quick_list.empty_message = A jelenlegi helynek nincs felsőbb alkönyvtára\nrecent_locations_quick_list.empty_message = Nincsenek legutóbbi helyek\nrecent_executed_files_quick_list.empty_message = Nincsenek legutóbb futtatott fájlok\n#pack.error_on_file = Hiba a tömörítés közben %1\n#pack_dialog.cannot_write = A célkönyvtárba nem lehetséges tömörített fájlt készíteni.\n#unpack.unable_to_open_zip = A Zip fájl megnyitása nem lehetséges %1.\n#mkdir_dialog.title = új könyvtár\n#mkdir_dialog.error_title = könyvtár létrehozási hiba\n#mkdir_dialog.description = új könyvtár létrehozása\n#progress_dialog.hide = rejt\n#move_dialog.rename_description = átnevezés ide\n#theme_editor.shell_font = Végrehajtás betűtípus\n#theme_editor.history_font = Előzmények betűtípus\n#theme_editor.shell_colors = Végrehajtás színek\n#theme_editor.history_colors = Előzmények színei\n#ToggleHiddenFiles.hide = Rejtett fájlok elrejtése\n#auth_dialog.error_was = Hiba történt: %1\n#table.hide_column = Oszlop elrejtése\n#delete.symlink_warning_title = Parancsikont találtam\n#delete.symlink_warning = Ez a fájl egy parancsikonnak néz ki:\\n\\n  Fájl: %1\\n  Link: %2\\n\\nCsak a parancsikon törlése vagy\\nkövesse a parancsikont és törölje a könyvtárat (VIGYÁZAT)?\n#delete.delete_link_only = Hivatkozás törlése\n#delete.delete_linked_folder = Könyvtár törlése\n#Unpack.label = Kicsomagolás\n#Pack.label = Tömörítés\n"
  },
  {
    "path": "src/main/resources/dictionary_it_IT.properties",
    "content": "ok = OK\nyes = Si\nno = No\ncancel = Annulla\nedit = Modifica\nclose = Chiudi\nreset = Reset\nrename = Rinomina\napply = Applica\nchange = Cambia\nsave = Salva\ndont_save = Non salvare\nreplace = Sostituisci\ndont_replace = Non sostituire\ndelete = Elimina\nskip = Salta\nretry = Riprova\nresume = Riprendi\noverwrite = Sovrascrivi\noverwrite_if_older = Sovrascrivi vecchio\nduplicate = Duplica\napply_to_all = Applica a tutti\ncopy = Copia\nmove = Sposta\npack = Compressione\nunpack = Decomprimi\ndownload = Scarica\nbrowse = Naviga\nask = Chiedi\nstop = Ferma\npause = Pausa\nquick_search = Ricerca rapida\nfile_manager = Gestione File\ncreate = Crea\ncreating_file = Creazione di %1 in corso\nchoose = Seleziona\nchoose_folder = Seleziona una cartella\nlogin = Login\npassword = Password\nuser = Utente\nencoding = Codifica\nlicense = Licenza\nname = Nome\nsize = Dimensione\ndate = Data\nextension = Estensione\npermissions = Permessi\nowner = Proprietario\ngroup = Gruppo\nlocation = Location\nuntitled = SenzaTitolo\nsource = Sorgente\ndestination = Destinazione\nrecurse_directories = Elabora anche le sotto-directory\ngo_to = Vai a\nexample = Esempio\npreview = Anteprima\ncomment = Commento\nsample_text = Testo di esempio\nnb_files = %1 file\nnb_folders = %1 cartella(e)\nloading = Caricamento in corso...\nthis_operation_cannot_be_undone = Questa operazione non puo' essere annullata.\nremove = Elimina\nwarning = Attenzione\nerror = Errore\ngeneric_error = Errore durante l'esecuzione dell'operazione richiesta.\nfolder_does_not_exist = Directory inesistente o non disponibile.\nthis_folder_does_not_exist = Directory inesistente o non disponibile: %1\nthis_file_does_not_exist = Questo file non esiste o non e' disponibile: %1\ninvalid_path = Percorso non valido: %1\ndirectory_already_exists = Directory %1 gia' esistente.\nfile_exists_in_destination = File gia' esistente nella destinazione\nsource_parent_of_destination = Tentativo di trasferire una cartella in una sua sottocartella\ncannot_read_file = Impossibile leggere il file %1\ncannot_write_file = Impossibile scrivere file %1\ncannot_create_folder = Impossibile creare directory %1\ncannot_read_folder = Impossibile leggere il contenuto della directory %1\ncannot_delete_file = Impossibile eliminare il file %1\ncannot_delete_folder = Impossibile eliminare directory %1\nerror_while_transferring = Errore trasferimento file %1\nsame_source_destination = Directory sorgente e destinazione uguali.\nfile_already_exists = %1 esiste gia', vuoi sostituirlo ?\nwrite_error = Errore in scrittura\nread_error = Errore in lettura\nintegrity_check_error = Verifica integrità fallita: sorgente e destinazione non corrispondono\npermissions.read = Lettura\npermissions.write = Scrittura\npermissions.executable = Esegui\npermissions.group = Gruppo\npermissions.other = Altro\npermissions.octal_notation = Notazione ottale\naction_categories.navigation = Navigare\naction_categories.selection = Selezionare\naction_categories.view = Visualizzare\naction_categories.file_operations = Operazioni su file\naction_categories.windows = Finestre\nTerminal.label = Terminal\nTerminal.tooltip = Terminal\nFindFile.label = Find file\nFindFile.tooltip = Find file\nAddBookmark.label = Aggiungi a favoriti\nAddBookmark.tooltip = Aggiungi directory a favoriti\nBatchRename.label = Multi rinomina\nEditBookmarks.label = Modifica favoriti\nExploreBookmarks.label = Esplora i bookmark\nEditCredentials.label = Modifica credenziali\nChangeLocation.label = Cambia percorso corrente\nChangeDate.label = Cambia data\nChangeDate.tooltip = Cambia data dei file selezionati\nChangePermissions.label = Cambia permessi\nChangePermissions.tooltip = Cambia permessi dei file selezionati\nCheckForUpdates.label = Controlla aggiornamenti\nCompareFolders.label = Confronta directory\nConnectToServer.label = Connetti al server\nConnectToServer.tooltip = Connetti ad un server remoto\nView.label = Vedi\nView.tooltip = Visualizza file selezionati\nEdit.tooltip = Modifica file selezionato\nCopy.tooltip = Copia file selezionati\nLocalCopy.label = Copia locale\nLocalCopy.tooltip = Copia i file selezionati nella corrente directory\nMove.tooltip = Sposta i file selezionati\nRename.tooltip = Rinomina i file selezionati\nMkdir.label = Crea\nMkdir.tooltip = Crea una nuova directory nella cartella corrente\nMkfile.label = Crea nuovo file\nMkfile.tooltip = Crea un file nella cartella corrente\nDelete.tooltip = Elimina file selezionati usando il cestino quando possibile\nPermanentDelete.label = Elimina definitivamente\nPermanentDelete.tooltip = Elimina file selezionati senza usare il cestino\nRefresh.label = Rileggi\nRefresh.tooltip = Rigenera cartella corrente\nCloseWindow.label = Chiudi\nCloseWindow.tooltip = Chiudi questa finestra\nCopyFileNames.label = Copia nome(i)\nCopyFilePaths.label = Copia percorso(i)\nCopyFilesToClipboard.label = Copia il(i) file\nPasteClipboardFiles.label = Incolla il(i) file\nEmail.label = Invia i file via eMail\nEmail.tooltip = Invia i file selezionati come allegati ad una eMail\nGoBack.label = Indietro\nGoBack.tooltip = Torna alla cartella precedente\nGoForward.label = Vai avanti\nGoForward.tooltip = Vai alla cartella seguente\nGoToHome.label = Torna nella cartella Home\nGoToParent.label = Vai alla cartella superiore\nGoToParent.tooltip = Vai alla cartella superiore \nGoToParentInOtherPanel.label = Vai su superiore nell'altro pannello\nGoToParentInBothPanels.label = Vai su superiore in entrambi i pannelli\nGoToRoot.label = Vai sulla directory Root\nSortByName.label = Ordina per Nome\nSortByDate.label = Ordina per Data\nSortBySize.label = Ordina per Dimensione\nSortByExtension.label = Ordina per Estensione\nSortByPermissions.label = Ordina per Permessi\nSortByOwner.label = Ordina per Propietario\nSortByGroup.label = Ordina per Gruppo\nMarkGroup.label = Seleziona file\nMarkGroup.tooltip = Seleziona un gruppo di file\nUnmarkGroup.label = Deseleziona file\nUnmarkGroup.tooltip = Deseleziona un gruppo di file\nMarkAll.label = Seleziona tutto\nUnmarkAll.label = Deseleziona tutto\nMarkSelectedFile.label = Seleziona/Deseleziona\nMarkSelectedFile.tooltip = Seleziona/Deseleziona file\nMarkNextPage.label = Seleziona pagina giu'\nMarkPreviousPage.label = Seleziona pagina su\nMarkToFirstRow.label = Seleziona file dall'inizio\nMarkToLastRow.label = Seleziona file fino alla fine\nMarkExtension.label = Marca estensione\nInvertSelection.label = Inverti selezione\nSwapFolders.label = Scambia cartelle\nSwapFolders.tooltip = Scambia finestre\nSetSameFolder.label = Allinea destinazione e sorgente\nSetSameFolder.tooltip = Setta la stessa directory su cartella di destra e sinistra\nNewWindow.label = Nuova finestra\nNewWindow.tooltip = Apri una nuova finestra\nOpen.label = Apri\nOpen.tooltip = Inserisci directory / Inserisci archivio / Esegui\nOpenNatively.label = Apri nativamente\nOpenNatively.tooltip = Esegui i file selezionati con le associazioni di sistema \nOpenInOtherPanel.label = Apri nell'altro pannello\nOpenInBothPanels.label = Apri in entrambi i pannelli\nRevealInDesktop.label = Mostra in %1\nRunCommand.label = Esegui comando\nRunCommand.tooltip = Esegui un comando nella cartella corrente\nPack.tooltip = Comprimi file selezionati\nUnpack.tooltip = Decomprimi gli archivi selezionati\nShowFileProperties.label = Proprieta'\nShowFileProperties.tooltip = Mostra proprieta' dei file selezionati\nShowPreferences.label = Preferenze\nShowPreferences.tooltip = Configura trolCommander\nShowServerConnections.label = Mostra le connessioni aperte\nQuit.label = Esci\nReverseSortOrder.label = Inverti ordine\nToggleAutoSize.label = Auto-dimensiona colonne\nStop.label = Ferma cambio cartella\nToggleCommandBar.show = Mostra barra di comando\nToggleCommandBar.hide = Nascondi barra di comando\nToggleToolBar.show = Mostra toolbar\nToggleToolBar.hide = Nascondi toolbar\nToggleStatusBar.show = Mostra barra di stato\nToggleStatusBar.hide = Nascondi barra di stato\nToggleShowFoldersFirst.label = Mostra le cartelle per prime\nToggleTree.label = Visualizza struttura\nPopupLeftDriveButton.label = Cambia cartella sinistra\nPopupRightDriveButton.label = Cambia cartella destra\nRecallPreviousWindow.label = Richiama finestra precedente\nRecallNextWindow.label = Richiama prossima finestra\nRecallWindow.label = Richiama finestra #%1\nBringAllToFront.label = Porta tutte le finestre in primo piano\nSwitchActiveTable.label = Scambia tra pannello sinistro e destro\nSelectFirstRow.label = Seleziona primo file nella cartella corrente\nSelectLastRow.label = Seleziona ultimo file nella cartella corrente\nSplitEqually.label = Dividi egualmente\nSplitVertically.label = Dividi verticalmente\nSplitHorizontally.label = Dividi orizzontalmente\nShowKeyboardShortcuts.label = Scorciatoie da tastiera\nGoToWebsite.label = Visualizza sito Web\nGoToForums.label = Visualizza forum\nReportBug.label = Segnala un bug\nDonate.label = Fai una donazione\nShowAbout.label = Info su trolCommander\nOpenTrash.label = Apri il cestino\nEmptyTrash.label = Vuota il cestino\nCalculateChecksum.label = Calcola checksum\nMaximizeWindow.label = Massimizza\nMaximizeWindow.label.mac_os_x = Zoom\nMinimizeWindow.label = Minimizza\nGoToDocumentation.label = Documentazione in linea\nShowParentFoldersQL.label = Cartelle superiori\nShowRecentLocationsQL.label = Percorsi recenti\nShowRecentExecutedFilesQL.label = File eseguibili recenti\nfile_menu = File\nfile_menu.open_with = Apri con\nmark_menu = Selezione\nview_menu = Visualizza\nview_menu.show_hide_columns = Mostra/Nascondi colonne\ngo_menu = Vai\nbookmarks_menu = Favoriti\nbookmarks_menu.no_bookmark = Nessun favorito \nquick_lists_menu = Liste rapide\nwindow_menu = Finestra\nhelp_menu = Aiuto\nstatus_bar.selected_files = %1 di %2 selezionati\nstatus_bar.connecting_to_folder = Connessione in corso, premere ESC per annullare\nstatus_bar.volume_free = Libero: %1\nstatus_bar.volume_capacity = Capacita': %1\ntable.folder_access_error_title = Errore nell'accesso alla cartella\ntable.folder_access_error = Impossibile leggere il contenuto della cartella\ntable.download_or_browse = Vuoi navigare o scaricare questo file ?\nversion_dialog.no_new_version_title = Nessuna nuova versione\nversion_dialog.no_new_version = Congratulazioni, hai gia' l'ultima versione.\nversion_dialog.new_version_title = Nessuna versione disponibile\nversion_dialog.new_version = E' disponibile una nuova versione di trolCommander.\nversion_dialog.new_version_url = E' disponibile una nuova versione di trolCommander: %1.\nversion_dialog.not_available_title = Server non disponibile\nversion_dialog.not_available = Impossibile prendere le informazioni sulla versione dal server.\nversion_dialog.install_and_restart = Installa e riparti\nversion_dialog.preparing_for_update = Preparazione per aggiornamento...\nquit_dialog.title = Esci da trolCommander\nquit_dialog.desc = Hai %1 finestra(e) aperta(e). Sei sicuro di voler uscire ?\nquit_dialog.show_next_time = Mostra la prossima volta\ndestination_dialog.file_exists_action = Azione di default quando il file esiste\ndestination_dialog.verify_integrity = Verifica integrità dei dati\nfile_collision_dialog.title = Collisione di file\nrename_dialog.new_name = Nuovo nome\ncopy_dialog.destination = Copia file selezionato(i) su \ncopy_dialog.error_title = Errore durante la copia\ncopy_dialog.copying = Copiando file\ncopy_dialog.copying_file = Copiando %1\npack_dialog.packing = Comprimendo file\npack_dialog.packing_file = Comprimendo %1\npack_dialog.error_title = Errore nella compressione\npack_dialog_description = Aggiungi i file selezionati a\npack_dialog.archive_format = Formato archivio\nunpack_dialog.destination = Decomprimi i file selezionati in\nunpack_dialog.error_title = Errore decompressione\nunpack_dialog.unpacking = Decomprimendo file\nunpack_dialog.unpacking_file = Decomprimendo %1\noptimizing_archive = Ottimizzazione archivio %1\nerror_while_optimizing_archive = Errore ottimizzando l'archivio %1\nmove_dialog.move_description = Sposta verso\nmove_dialog.error_title = Errore spostando\nmove_dialog.moving = Spostando file\nmove_dialog.moving_file = Spostando %1\ndownload_dialog.description = Scaricando file in\ndownload_dialog.error_title = Errore in download\ndownload_dialog.downloading = In corso di scaricamento\ndownload_dialog.downloading_file = Scaricando %1\nmkfile_dialog.allocate_space = Alloca spazio\ndelete_dialog.permanently_delete.confirmation = Elimino definitivamente i file selezionati ?\ndelete_dialog.move_to_trash.confirmation = Cancellare file selezionato(i) ?\ndelete_dialog.move_to_trash.confirmation_details = I file saranno spostati nel cestino.\ndelete_dialog.move_to_trash.option = Sposta nel cestino\ndelete_dialog.deleting = Eliminazione in corso\ndelete_dialog.error_title = Errore durante la cancellazione\ndelete.deleting_file = In corso di cancellazione %1\nemail_dialog.prefs_not_set_title = Mail non configurato\nemail_dialog.prefs_not_set = Devi prima impostare i tuoi parametri di Mail.  \nemail_dialog.from = Da\nemail_dialog.to = A\nemail_dialog.subject = Soggetto\nemail_dialog.send = Invia\nemail_dialog.error_title = Errore Email file\nemail_dialog.read_error = Impossibile leggere i file nelle sottodirectory.\nemail_dialog.sending = Inviando file\nemail.sending_file = Inviando %1\nemail.connecting_to_server = Contattando %1\nemail.server_unavailable = Impossibile contattare mail server %1, verifica le tue preferenze di mail o prova piu' tardi.\nemail.connection_closed = Connessione chiusa dal server, mail non inviata.\nemail.goodbye_failed = Errore chiudendo la connessione, la meil puo' non essere stata inviata.\nemail.send_file_error = Impossibile inviare il file %1, mail non inviata.\nfile_selection_dialog.mark = Selezione\nfile_selection_dialog.unmark = Deselezione\nfile_selection_dialog.mark_description = Selezione file il cui nome\nfile_selection_dialog.unmark_description = Deseleziona i file il cui nome\nfile_selection_dialog.case_sensitive = MAIUSCOLE/minuscole\nfile_selection_dialog.include_folders = Includi cartelle\nprogress_dialog.starting = Trasferimento iniziato...\nprogress_dialog.transferred = Trasferendo %1 in %2\nprogress_dialog.elapsed_time = Durata\nprogress_dialog.advanced = Avanzate\nprogress_dialog.current_speed = Velocita' attuale\nprogress_dialog.limit_speed = Limite di velocita'\nprogress_dialog.close_when_finished = Chiudi finestra quando finito\nprogress_dialog.processing_files = Elaborazione file\nprogress_dialog.processing_file = Elaborando %1\nprogress_dialog.verifying_file = Verificando %1\nprogress_dialog.job_finished = Lavoro terminato\nprogress_dialog.job_error = Errore durante il lavoro\nproperties_dialog.file_properties = %1 Proprieta'\nproperties_dialog.contents = Contenuto\nproperties_dialog.calculating = calcolando...\ncalculate_checksum_dialog.checksum_algorithm = Algoritmo di Checksum\ncalculate_checksum_dialog.temporary_file = File Temporaneo\nchange_date_dialog.now = Adesso\nchange_date_dialog.specific_date = Specifica data\nrun_dialog.run_command_description = Esegui nella cartella corrente\nrun_dialog.run_in_home_description = Esegui nella cartella Home\nrun_dialog.command_output = Command output\nrun_dialog.run = Esegui\nrun_dialog.clear_history = Ripulisci l'history\nserver_connect_dialog.server_type = Tipo connessione\nserver_connect_dialog.server = Server\nserver_connect_dialog.share = Condividi\nserver_connect_dialog.username = Username\nserver_connect_dialog.initial_dir = Directory iniziale\nserver_connect_dialog.port = Porta\nserver_connect_dialog.server_url = Server URL\nserver_connect_dialog.http_url = Web site URL\nserver_connect_dialog.connect = Connetti\nserver_connect_dialog.protocol = Protocollo\nserver_connect_dialog.nfs_version = Versione-NFS\nserver_connect_dialog.private_key = Chiave privata\nserver_connect_dialog.passphrase = Password\nftp_connect.passive_mode = Abilita modalita' passiva\nftp_connect.anonymous_user = Utente anonimo\nftp_connect.nb_connection_retries = Numero di tentativi di connessione\nftp_connect.retry_delay = Ritardo tra tentativi (in secondi)\nhttp_connect.basic_authentication = HTTP Autentica Base (opzionale)\nserver_connections_dialog.disconnect = Disconnetti\nserver_connections_dialog.connection_busy = Occupato\nserver_connections_dialog.connection_idle = Inattivo\nbonjour.bonjour_services = Servizi Bonjour\nbonjour.no_service_discovered = Nessun servizio scoperto\nbonjour.bonjour_disabled = Bonjour disabilitato\nauth_dialog.title = Autenticazione\nauth_dialog.desc = Prego inserire login e password\nauth_dialog.server = Server\nauth_dialog.store_credentials = Memorizza login e password (weak encryption)\nauth_dialog.connect_as = Connetti come\nauth_dialog.authentication_failed = Autenticazione fallita\nsortable_list.move_up = Muovi sopra\nsortable_list.move_down = Muovi sotto\nadd_bookmark_dialog.add = Aggiungi\nedit_bookmarks_dialog.new = Nuovo\nfile_viewer.view_error_title = Errore vista\nfile_viewer.view_error = Impossibile visualizzare il file.\nfile_viewer.file_menu = File\nfile_viewer.close = Chiudi\nfile_viewer.large_file_warning = Questo file e' troppo grande per questa operazione.\nfile_viewer.open_anyway = Apri comunque\ntext_viewer.edit = Modifica\ntext_viewer.copy = Copia\ntext_viewer.select_all = Seleziona tutto\ntext_viewer.find = Cerca\ntext_viewer.find_next = Trova il successivo\ntext_viewer.find_previous = Trova il precedente\ntext_viewer.binary_file_warning = Questo sembra essere un file binario\nimage_viewer.controls_menu = Controlli\nimage_viewer.zoom_in = Ingrandisci\nimage_viewer.zoom_out = Rimpiccolisci\nfile_editor.edit_error_title = Errore in modifica\nfile_editor.edit_error = Impossibile modificare il file.\nfile_editor.save = Salva\nfile_editor.save_as = Salva come...\nfile_editor.save_warning = Salvare le modifiche a questo file prima di chiudere ?\nfile_editor.cannot_write = Impossibile salvare il file.\ntext_editor.cut = Taglia\ntext_editor.paste = Incolla\nshortcuts_dialog.quick_search.start_search = Batti un tasto per iniziare una ricerca rapida\nshortcuts_dialog.quick_search.cancel_search = Annullare ricerca rapida\nshortcuts_dialog.quick_search.remove_last_char = Togli ultimo carattere dalla stringa di ricerca\nshortcuts_dialog.quick_search.jump_to_previous = Salta sulla ricerca rapida precedente\nshortcuts_dialog.quick_search.jump_to_next = Salta sulla ricerca rapida seguente\nshortcuts_dialog.quick_search.mark_jump_next = Seleziona/deseleziona il file corrente e salta sulla ricerca rapida seguente\ntheme_editor.title = Modifica tema\ntheme_editor.folder_tab = Pannello Cartella\ntheme_editor.shell_tab = Shell\ntheme_editor.shell_history_tab = Shell history\ntheme_editor.statusbar_tab = Barra di stato\ntheme_editor.free_space = Spazio libero\ntheme_editor.free_space.ok = OK\ntheme_editor.free_space.warning = Attenzione\ntheme_editor.free_space.critical = Critico\ntheme_editor.locationbar_tab = Barra delle Localita'\ntheme_editor.editor_tab = Editor dei file\ntheme_editor.font = Font\ntheme_editor.active_panel = Attivo\ntheme_editor.inactive_panel = Disattivo\ntheme_editor.general = Generale\ntheme_editor.could_not_save_theme = Impossibile scrivere il tema %1\ntheme_editor.border = Bordo\ntheme_editor.background = Sfondo\ntheme_editor.alternate_background = Sfondo alternativo\ntheme_editor.unfocused_background = Sfondo (non selezionato)\ntheme_editor.quick_search.unmatched_file = File non corrispondenti\ntheme_editor.text = Testo\ntheme_editor.progress = Progresso\ntheme_editor.normal = Normale\ntheme_editor.normal_unfocused = Normale (senza selezione)\ntheme_editor.selected = Seleziona\ntheme_editor.selected_unfocused = Selezionato (senza focus)\ntheme_editor.color = Colore\ntheme_editor.colors = Colori\ntheme_editor.plain_file = File normale\ntheme_editor.marked_file = File selezionato\ntheme_editor.hidden_file = File nascosto\ntheme_editor.folder = Cartella\ntheme_editor.archive_file = Archivio\ntheme_editor.symbolic_link = Link simbolico\nprefs_dialog.title = Preferenze\nprefs_dialog.general_tab = Generale\nprefs_dialog.day = Giorno\nprefs_dialog.month = Mese\nprefs_dialog.year = Anno\nprefs_dialog.language = Italiano (E' necessario il restart)\nprefs_dialog.date_time = Formato data e ora\nprefs_dialog.time = Ora\nprefs_dialog.date = Data\nprefs_dialog.date_separator = Separatore\nprefs_dialog.time_12_hour = Formato 12 ore\nprefs_dialog.time_24_hour = Formato 24 ore\nprefs_dialog.show_seconds = Mostra secondi\nprefs_dialog.show_century = Mostra secolo\nprefs_dialog.check_for_updates_on_startup = Controlla aggiornamenti alla partenza\nprefs_dialog.show_splash_screen = Mostra pannello di apertura\nprefs_dialog.folders_tab = Cartelle\nprefs_dialog.startup_folders = Cartelle iniziali\nprefs_dialog.left_folder = Cartella di sinistra\nprefs_dialog.right_folder = Cartella di destra\nprefs_dialog.last_folder = Ultima cartella visitata\nprefs_dialog.custom_folder = Cartella definita dall'utilizzatore\nprefs_dialog.show_hidden_files = Mostra file nascosti\nprefs_dialog.show_ds_store_files = Mostra file .DS_Store\nprefs_dialog.show_system_folders = Mostra cartelle di sistema\nprefs_dialog.compact_file_size = Arrotonda le grandezze dei file mostrate\nprefs_dialog.follow_symlinks_when_cd = Segui link simbolici quando si cambia la directory corrente\nprefs_dialog.appearance_tab = Aspetto\nprefs_dialog.look_and_feel = Look & Feel\nprefs_dialog.icons_size = Grandezza icone\nprefs_dialog.toolbar_icons = Toolbar\nprefs_dialog.command_bar_icons = Barra comandi\nprefs_dialog.file_icons = Tipi file\nprefs_dialog.use_system_file_icons = Usa per i file le icone di sistema\nprefs_dialog.use_system_file_icons.always = Sempre\nprefs_dialog.use_system_file_icons.never = Mai\nprefs_dialog.use_system_file_icons.applications = Solo per le applicazioni\nprefs_dialog.edit_current_theme = Modifica il tema corrente...\nprefs_dialog.themes = Temi\nprefs_dialog.import_theme = Importa il tema\nprefs_dialog.import_look_and_feel = Importa look & feel\nprefs_dialog.no_look_and_feel = Nessun look & feel trovato.\nprefs_dialog.error_in_import = Errore durante import del tema %1.\nprefs_dialog.cannot_read_theme = Impossibile caricare il tema dal file %1\nprefs_dialog.export_theme = Esportare %1\nprefs_dialog.import = Import\nprefs_dialog.export = Esportare\nprefs_dialog.theme_type = Tipo: %1\nprefs_dialog.delete_theme = Cancellazione definitiva del tema %1 ?\nprefs_dialog.delete_look_and_feel = Cancello definitivamente il look & feel %1 ?\nprefs_dialog.rename_failed = Impossibile rinominare il tema %1\nprefs_dialog.xml_file = File XML\nprefs_dialog.jar_file = File JAR\nprefs_dialog.mail_tab = Email\nprefs_dialog.mail_settings = Parametri mail in uscita\nprefs_dialog.mail_name = Il Vostro Nome\nprefs_dialog.mail_address = Il Vostro indirizzo email\nprefs_dialog.mail_server = SMTP server \nprefs_dialog.misc_tab = Altro\nprefs_dialog.use_brushed_metal = Usa 'brushed metal' look (richiede restart)\nprefs_dialog.confirm_on_quit = Mostra pannello di conferma in uscita\nprefs_dialog.default_shell = Usa il terminale di default del sistema\nprefs_dialog.custom_shell = Usa il terminale utente\nprefs_dialog.shell_encoding = Codifica Shell\nprefs_dialog.auto_detect_shell_encoding = Auto-detect\nprefs_dialog.enable_bonjour_discovery = Abilita Bonjour services discovery\nprefs_dialog.enable_system_notifications = Abilita notifiche di sistema\nunit.byte = byte\nunit.bytes = bytes\nunit.bytes_short = b\nunit.kb = KB\nunit.mb = MB\nunit.gb = GB\nunit.tb = TB\nunit.speed = %1/s\nduration.seconds = %1s\nduration.minutes = %1m\nduration.hours = %1h\nduration.days = %1g\nduration.months = %1me\nduration.years = %1a\ntheme.custom_theme = Tema utente\ntheme.custom = Personalizzato\ntheme.built_in = Pre-istallato\ntheme.add_on = Aggiuntivo\ntheme.current = corrente\ntheme_could_not_be_loaded = Errore durante il caricamento del tema.\nsetup.title = Benvenuti in trolCommander\nsetup.intro = Prego, scegliere il modo di comportarsi di trolCommander.\nsetup.look_and_feel = Scegliete il Vostro Look & Feel\nsetup.theme = Selezionate il Vostro tema\nfont_chooser.font_size = Dimensione\nfont_chooser.font_bold = Grassetto\nfont_chooser.font_italic = Corsivo\ncolor_chooser.red = Rosso\ncolor_chooser.green = Verde\ncolor_chooser.blue = Blu\ncolor_chooser.hue = Tonalita'\ncolor_chooser.brightness = Luminosita'\ncolor_chooser.swatches = Palette dei colori\ncolor_chooser.saturation = Saturazione\ncolor_chooser.rgb = RGB\ncolor_chooser.hsb = HSB\ncolor_chooser.recent = Recente\ncolor_chooser.alpha = Alpha transparenza\ncolor_chooser.title = Scegli un colore\nbatch_rename_dialog.mask = Maschera di rinomina\nbatch_rename_dialog.search_replace = Cerca & Sostituisci\nbatch_rename_dialog.search_for = Cerca\nbatch_rename_dialog.replace_with = Sostituisci con\nbatch_rename_dialog.counter = Contatore\nbatch_rename_dialog.start_at = Inizia da\nbatch_rename_dialog.step_by = Incremento\nbatch_rename_dialog.format = Formato\nbatch_rename_dialog.upper_lower_case = Maiuscolo/Minuscolo\nbatch_rename_dialog.no_change = Invariato\nbatch_rename_dialog.lower_case = minuscolo\nbatch_rename_dialog.upper_case = MAIUSCOLO\nbatch_rename_dialog.first_upper = Prima lettera maiuscola\nbatch_rename_dialog.word = Prima Lettera Di Ogni Parola\nbatch_rename_dialog.old_name = Vecchio nome\nbatch_rename_dialog.new_name = Nuovo nome\nbatch_rename_dialog.block_name = Conserva\nbatch_rename_dialog.range = Intervallo\nbatch_rename_dialog.proceed_renaming = %1 file di %2 verranno rinominati. Vuoi continuare?\nbatch_rename_dialog.duplicate_names = Nomi duplicati!\nparent_folders_quick_list.empty_message = Il percorso corrente non ha un livello superiore\nrecent_locations_quick_list.empty_message = Nessun percorso recente\nrecent_executed_files_quick_list.empty_message = Nessun file eseguibile recente\n#mkdir_dialog.description = Crea directory\n#mkfile_dialog.description = Crea un nuovo file vuoto\n#done = Fatto\n#move_dialog.rename_description = Rinomina file in\n#theme_editor.shell_colors = Colori del terminale\n#ToggleHiddenFiles.hide = Non mostrare i file nascosti\n#auth_dialog.error_was = Errore: %1\n#table.hide_column = Nascondi colonna\n#delete.symlink_warning_title = Trovato link simbolico\n#delete.symlink_warning = Questo file sembra un link simbolico:\\n\\n  File: %1\\n  Link a: %2\\n\\nElimina solo link oppure\\nSegio link e cancella la cartella (ATTENZIONE) ?\n#delete.delete_link_only = Elimina link\n#delete.delete_linked_folder = Elimina directory\n#Unpack.label = Decomprimi file\n#Pack.label = Comprimi file\n"
  },
  {
    "path": "src/main/resources/dictionary_ja_JP.properties",
    "content": "ok = OK\nyes = はい\nno = いいえ\ncancel = キャンセル\nedit = 編集\nclose = 閉じる\nreset = リセット\nrename = 名前の変更\napply = 適用\nchange = 変更\nsave = 保存\ndont_save = 保存しない\nreplace = 置換\ndont_replace = 置換しない\ndelete = 削除\nskip = スキップ\nretry = 再試行\nresume = 再開\noverwrite = 上書き\noverwrite_if_older = より古ければ上書き\nduplicate = 複製\napply_to_all = すべて適用\ncopy = コピー\nmove = 移動\npack = パック\nunpack = パック解除\ndownload = ダウンロード\nbrowse = 参照\nask = 質問\nstop = 停止\npause = 一時停止\nquick_search = クイック検索\nfile_manager = ファイル マネージャー\ncreate = 作成\ncreating_file = %1 を作成しています\nchoose = 選択\nchoose_folder = フォルダーを選択します\nlogin = ログイン\npassword = パスワード\nuser = ユーザー\nencoding = エンコード\nlicense = ライセンス\nname = 名前\nsize = サイズ\ndate = 日付\nextension = 拡張子\npermissions = 権限\nowner = 所有者\ngroup = グループ\nlocation = 場所\nuntitled = 無題\nsource = ソース\ndestination = 先\nrecurse_directories = 再帰的に選択されたディレクトリを処理する\ngo_to = 移動\nexample = 見本\npreview = プレビュー\ncomment = コメント\nsample_text = サンプル テキスト\nnb_files = %1 個のファイル\nnb_folders = %1 個のフォルダー\nloading = 読み込んでいます...\nthis_operation_cannot_be_undone = この操作は元に戻せません。\nremove = 削除\nwarning = 警告\nerror = エラー\ngeneric_error = 要求された操作を行っている間にエラーが発生しています。\nfolder_does_not_exist = このフォルダーは存在しないか利用できません。\nthis_folder_does_not_exist = このフォルダーは存在しないか利用できません: %1\nthis_file_does_not_exist = このファイルは存在しないか利用できません: %1\ninvalid_path = 不正なパスです: %1\ndirectory_already_exists = ディレクトリ %1 はすでに存在します。\nfile_exists_in_destination = ファイルはすでに先にあります\nsource_parent_of_destination = フォルダーのそのサブフォルダーの 1 つへの転送を試みています\ncannot_read_file = ファイル %1 を読み取れません\ncannot_write_file = ファイル %1 を書き込めません\ncannot_create_folder = ディレクトリ %1 を作成することができません\ncannot_read_folder = フォルダー %1 の内容を読み取ることができません\ncannot_delete_file = ファイル %1 を削除することができません\ncannot_delete_folder = フォルダー %1 を削除することができません\nerror_while_transferring = ファイル %1 の転送中のエラーです\nsame_source_destination = ソースと先のフォルダーは同じです\nfile_already_exists = %1 はすでに存在します、置換しますか ?\nwrite_error = 書き込みエラー\nread_error = 読み取りエラー\nintegrity_check_error = 整合性チェックが失敗しました: ソースと先が一致しません\npermissions.read = 読み取り\npermissions.write = 書き込み\npermissions.executable = 実行ファイル\npermissions.group = グループ\npermissions.other = その他\npermissions.octal_notation = 8 進法\naction_categories.navigation = 操作\naction_categories.selection = 選択範囲\naction_categories.view = 表示\naction_categories.file_operations = ファイルの操作\naction_categories.windows = ウィンドウ\nTerminal.label = Terminal\nTerminal.tooltip = Terminal\nFindFile.label = Find file\nFindFile.tooltip = Find file\nAddBookmark.label = ブックマークの追加\nAddBookmark.tooltip = 現在のフォルダーをブックマークの一覧に追加します\nBatchRename.label = 名前の一括変更\nEditBookmarks.label = ブックマークの編集\nExploreBookmarks.label = ブックマークの検索\nEditCredentials.label = 信任状の編集\nChangeLocation.label = 現在の位置を変更\nChangeDate.label = 日付の変更\nChangeDate.tooltip = 選択されたファイルの日付を変更します\nChangePermissions.label = 権限の変更\nChangePermissions.tooltip = 選択されたファイルの権限を変更します\nCheckForUpdates.label = 更新のチェック\nCompareFolders.label = フォルダーの比較\nConnectToServer.label = サーバーへ接続\nConnectToServer.tooltip = リモート サーバーへ接続します\nView.label = 表示\nView.tooltip = 選択されたファイルを表示します\nEdit.tooltip = 選択されたファイルを編集します\nCopy.tooltip = マークされたファイルをコピーします\nLocalCopy.label = ローカル コピー\nLocalCopy.tooltip = 現在のフォルダーに選択されたファイルをコピーします\nMove.tooltip = マークされたファイルを移動します\nRename.tooltip = 選択されたファイルの名前を変更します\nMkdir.label = ディレクトリの作成\nMkdir.tooltip = 現在のフォルダーにディレクトリを作成します\nMkfile.label = ファイルの作成\nMkfile.tooltip = 現在のフォルダーにファイルを作成します\nDelete.tooltip = マークされたファイルを削除します\nPermanentDelete.label = 永久に削除\nPermanentDelete.tooltip = システムのごみ箱を使用せずにマークされたファイルを削除します\nRefresh.label = 更新\nRefresh.tooltip = 現在のフォルダーを更新します\nCloseWindow.label = ウィンドウを閉じる\nCloseWindow.tooltip = このウィンドウを閉じます\nCopyFileNames.label = 名前のコピー\nCopyFilePaths.label = パスのコピー\nCopyFilesToClipboard.label = ファイルのコピー\nPasteClipboardFiles.label = ファイルの貼り付け\nEmail.label = ファイルの電子メール\nEmail.tooltip = 電子メールの添付としてマークされたファイルを送信します\nGoBack.label = 戻る\nGoBack.tooltip = 前のフォルダーへ移動します\nGoForward.label = 進む\nGoForward.tooltip = 次のフォルダーへ移動します\nGoToHome.label = ホーム フォルダーへ移動\nGoToParent.label = 親へ移動\nGoToParent.tooltip = 親フォルダーへ移動します\nGoToParentInOtherPanel.label = 他のパネルで親へ移動\nGoToParentInBothPanels.label = 両方のパネルで親へ移動\nGoToRoot.label = ルートへ移動\nSortByName.label = 名前で整列\nSortByDate.label = 日付で整列\nSortBySize.label = サイズで整列\nSortByExtension.label = 拡張子で整列\nSortByPermissions.label = 権限で整列\nSortByOwner.label = 所有者で整列\nSortByGroup.label = グループで整列\nMarkGroup.label = ファイルのマーク\nMarkGroup.tooltip = ファイルのグループをマークします\nUnmarkGroup.label = ファイルのマーク解除\nUnmarkGroup.tooltip = ファイルのグループをマーク解除します\nMarkAll.label = すべてマーク\nUnmarkAll.label = すべてマーク解除\nMarkSelectedFile.label = マーク/解除\nMarkSelectedFile.tooltip = 選択されたフォルダーをマーク/解除します\nMarkNextPage.label = 下へページをマーク\nMarkPreviousPage.label = 上へページをマーク\nMarkToFirstRow.label = 始めまでのファイルをマーク\nMarkToLastRow.label = 終わりまでのファイルをマーク\nMarkExtension.label = 拡張子のマーク\nInvertSelection.label = 選択範囲の反転\nSwapFolders.label = フォルダーの交換\nSwapFolders.tooltip = 左右のフォルダーを交換します\nSetSameFolder.label = 同じフォルダーに設定\nSetSameFolder.tooltip = 左右のフォルダーを同じフォルダーに設定します\nNewWindow.label = 新しいウィンドウ\nNewWindow.tooltip = 新しいウィンドウを開きます\nOpen.label = 開く\nOpen.tooltip = フォルダーの入力 / アーカイブの入力 / 実行\nOpenNatively.label = 自然に開く\nOpenNatively.tooltip = システムのファイルの関連付けで選択されたファイルを実行します\nOpenInOtherPanel.label = 他のパネルで開く\nOpenInBothPanels.label = 両方のパネルで開く\nRevealInDesktop.label = %1 で実行\nRunCommand.label = コマンドの実行\nRunCommand.tooltip = 現在のフォルダーでコマンドを実行します\nPack.tooltip = アーカイブにマークされたファイルをパックします\nUnpack.tooltip = マークされたアーカイブ ファイルをパック解除します\nShowFileProperties.label = プロパティ\nShowFileProperties.tooltip = マークされたファイルのプロパティを表示します\nShowPreferences.label = 環境設定\nShowPreferences.tooltip = trolCommander を構成します\nShowServerConnections.label = 開かれている接続の表示\nQuit.label = 終了\nReverseSortOrder.label = 順序の反転\nToggleAutoSize.label = 列のサイズの自動変更\nStop.label = フォルダーの変更を中止\nToggleCommandBar.show = コマンド バーの表示\nToggleCommandBar.hide = コマンド バーを非表示にする\nToggleToolBar.show = ツール バーの表示\nToggleToolBar.hide = ツール バーを非表示にする\nToggleStatusBar.show = ステータス バーの表示\nToggleStatusBar.hide = ステータス バーを非表示にする\nToggleShowFoldersFirst.label = 最初にフォルダーを表示\nToggleTree.label = ツリー ビューの表示\nPopupLeftDriveButton.label = 左のフォルダーを変更\nPopupRightDriveButton.label = 右のフォルダーを変更\nRecallPreviousWindow.label = 前のウィンドウを呼び戻す\nRecallNextWindow.label = 次のウィンドウを呼び戻す\nRecallWindow.label = ウィンドウ #%1 の呼び戻し\nBringAllToFront.label = すべて最前面へ移動\nSwitchActiveTable.label = 左右のパネル間を切り替え\nSelectFirstRow.label = 現在のフォルダーの最初のファイルを選択\nSelectLastRow.label = 現在のフォルダーの最後のファイルを選択\nSplitEqually.label = 均等に分割\nSplitVertically.label = 垂直に分割\nSplitHorizontally.label = 水平に分割\nShowKeyboardShortcuts.label = キーボード ショートカット\nGoToWebsite.label = ウェブサイトへ移動\nGoToForums.label = フォーラムへ移動\nReportBug.label = バグの報告\nDonate.label = 寄付\nShowAbout.label = trolCommander のバージョン情報\nOpenTrash.label = ごみ箱を開く\nEmptyTrash.label = ごみ箱を空にする\nCalculateChecksum.label = チェックサムの計算\nMaximizeWindow.label = 最大化\nMaximizeWindow.label.mac_os_x = ズーム\nMinimizeWindow.label = 最小化\nGoToDocumentation.label = オンライン ドキュメント\nShowParentFoldersQL.label = 親フォルダー\nShowRecentLocationsQL.label = 最近の場所\nShowRecentExecutedFilesQL.label = 最近実行したファイル\nfile_menu = ファイル\nfile_menu.open_with = 開く\nmark_menu = マーク\nview_menu = 表示\nview_menu.show_hide_columns = 列の表示/非表示を切り替え\ngo_menu = 移動\nbookmarks_menu = ブックマーク\nbookmarks_menu.no_bookmark = ブックマークがありません\nquick_lists_menu = クイック リスト\nwindow_menu = ウィンドウ\nhelp_menu = ヘルプ\nstatus_bar.selected_files = %2 個中 %1 個選択されています\nstatus_bar.connecting_to_folder = フォルダーへ接続しています、取り消すには ESCAPE を押します。\nstatus_bar.volume_free = 空き: %1\nstatus_bar.volume_capacity = 容量: %1\ntable.folder_access_error_title = フォルダー アクセス エラー\ntable.folder_access_error = フォルダーの内容を読み取ることができません\ntable.download_or_browse = このファイルを参照またはダウンロードしますか ?\nversion_dialog.no_new_version_title = 新しいバージョンがありません\nversion_dialog.no_new_version = おめでとうございます、すでに最新のバージョンです。\nversion_dialog.new_version_title = 新しいバージョンが利用可能です\nversion_dialog.new_version = trolCommander の新しいバージョンは利用可能です。\nversion_dialog.new_version_url = trolCommander の新しいバージョンは %1 で 利用可能です。\nversion_dialog.not_available_title = サーバーが到達できません\nversion_dialog.not_available = サーバーからバージョン情報を取得することができません。\nversion_dialog.install_and_restart = インストールと再起動\nversion_dialog.preparing_for_update = 更新を準備しています...\nquit_dialog.title = trolCommander の終了\nquit_dialog.desc = %1 個のウィンドウを開いています。終了してもよろしいですか ? \nquit_dialog.show_next_time = 次回に表示する\ndestination_dialog.file_exists_action = ファイルが存在するときの既定の動作\ndestination_dialog.verify_integrity = データの統合性を検証する\nfile_collision_dialog.title = ファイルの衝突\nrename_dialog.new_name = 新しい名前\ncopy_dialog.destination = 選択されたファイルのコピー先\ncopy_dialog.error_title = コピー エラー\ncopy_dialog.copying = ファイルのコピー中\ncopy_dialog.copying_file = %1 のコピー中\npack_dialog.packing = ファイルのパック中\npack_dialog.packing_file = %1 の圧縮中\npack_dialog.error_title = パック エラー\npack_dialog_description = 選択されたファイルの追加先\npack_dialog.archive_format = アーカイブ フォーマット\nunpack_dialog.destination = 選択されたファイルのパック解除先\nunpack_dialog.error_title = パック解除 エラー\nunpack_dialog.unpacking = ファイルのパック解除中\nunpack_dialog.unpacking_file = %1 のパック解除中\noptimizing_archive = アーカイブ %1 の最適化中\nerror_while_optimizing_archive = アーカイブ %1 の最適化中のエラーです\nmove_dialog.move_description = 移動先\nmove_dialog.error_title = 移動エラー\nmove_dialog.moving = ファイルの移動中\nmove_dialog.moving_file = %1 の移動中\ndownload_dialog.description = ファイルのダウンロード先\ndownload_dialog.error_title = ダウンロード エラー\ndownload_dialog.downloading = ダウンロード中\ndownload_dialog.downloading_file = %1 のダウンロード中\nmkfile_dialog.allocate_space = 割り当てる領域\ndelete_dialog.permanently_delete.confirmation = 選択されたファイルを永久に削除しますか ?\ndelete_dialog.move_to_trash.confirmation = 選択されたファイルを削除しますか ?\ndelete_dialog.move_to_trash.confirmation_details = ファイルはごみ箱へ移動されます。\ndelete_dialog.move_to_trash.option = ごみ箱へ移動\ndelete_dialog.deleting = 削除中\ndelete_dialog.error_title = 削除エラー\ndelete.deleting_file = %1 の削除中\nemail_dialog.prefs_not_set_title = メールが構成されていません\nemail_dialog.prefs_not_set = まずメール パラメーターを設定する必要があります。\nemail_dialog.from = 送信者\nemail_dialog.to = 宛先\nemail_dialog.subject = 件名\nemail_dialog.send = 送信\nemail_dialog.error_title = ファイルの電子メール エラー\nemail_dialog.read_error = サブフォルダーのファイルを読み取ることができません。\nemail_dialog.sending = ファイルの送信中\nemail.sending_file = %1 の送信中\nemail.connecting_to_server = %1 へ接続中\nemail.server_unavailable = メール サーバー %1 に連絡することができません、メールの環境設定をチェックするか後で再び試行します。\nemail.connection_closed = 接続がサーバーによって閉じられました、メールが送信されませんでした。\nemail.goodbye_failed = 接続を閉じている間のエラーです、メールは送信されていない可能性があります。\nemail.send_file_error = ファイル %1 を送信することができません、メールが送信されませんでした。\nfile_selection_dialog.mark = マーク\nfile_selection_dialog.unmark = マーク解除\nfile_selection_dialog.mark_description = マークするファイルのファイル名\nfile_selection_dialog.unmark_description = マーク解除するファイルのファイル名\nfile_selection_dialog.case_sensitive = 大文字と小文字を区別する\nfile_selection_dialog.include_folders = フォルダーを含める\nprogress_dialog.starting = 転送が開始されています...\nprogress_dialog.transferred = %2 で %1 を転送しました\nprogress_dialog.elapsed_time = 経過時間\nprogress_dialog.advanced = 詳細\nprogress_dialog.current_speed = 現在の速度\nprogress_dialog.limit_speed = 速度の制限\nprogress_dialog.close_when_finished = 完了時にウィンドウを閉じる\nprogress_dialog.processing_files = ファイルの処理中\nprogress_dialog.processing_file = %1 の処理中\nprogress_dialog.verifying_file = %1 の検証中\nprogress_dialog.job_finished = ジョブが完了しました\nprogress_dialog.job_error = ジョブ エラー\nproperties_dialog.file_properties = %1 のプロパティ\nproperties_dialog.contents = 内容\nproperties_dialog.calculating = 計算しています...\ncalculate_checksum_dialog.checksum_algorithm = チェックサムのアルゴリズム\ncalculate_checksum_dialog.temporary_file = 一時ファイル\nchange_date_dialog.now = 現在\nchange_date_dialog.specific_date = 特定の日付\nrun_dialog.run_command_description = 現在のフォルダーで実行します\nrun_dialog.run_in_home_description = ホーム フォルダーで実行します\nrun_dialog.command_output = コマンド出力\nrun_dialog.run = 実行\nrun_dialog.clear_history = 履歴のクリア\nserver_connect_dialog.server_type = 接続の種類\nserver_connect_dialog.server = サーバー\nserver_connect_dialog.share = 共有\nserver_connect_dialog.username = ユーザー名\nserver_connect_dialog.initial_dir = 初期ディレクトリ\nserver_connect_dialog.port = ポート\nserver_connect_dialog.server_url = サーバーの URL\nserver_connect_dialog.http_url = Web サイトの URL\nserver_connect_dialog.connect = 接続\nserver_connect_dialog.protocol = プロトコル\nserver_connect_dialog.nfs_version = NFS のバージョン\nserver_connect_dialog.private_key = プライベート キー\nserver_connect_dialog.passphrase = パスフレーズ\nftp_connect.passive_mode = パッシブ モードを有効にする\nftp_connect.anonymous_user = 匿名ユーザー\nftp_connect.nb_connection_retries = 接続の再試行の数\nftp_connect.retry_delay = 再試行間の遅延 (秒)\nhttp_connect.basic_authentication = HTTP ベーシック認証 (オプション)\nserver_connections_dialog.disconnect = 切断\nserver_connections_dialog.connection_busy = ビジー\nserver_connections_dialog.connection_idle = アイドル\nbonjour.bonjour_services = Bonjour サービス\nbonjour.no_service_discovered = サービスが発見されませんでした\u0004\nbonjour.bonjour_disabled = Bonjour が無効\nauth_dialog.title = 認証\nauth_dialog.desc = ログインとパスワードを入力してください\nauth_dialog.server = サーバー\nauth_dialog.store_credentials = ログインとパスワードを格納する (弱い暗号化)\nauth_dialog.connect_as = 名義\nauth_dialog.authentication_failed = 認証が失敗しました\nsortable_list.move_up = 上へ移動\nsortable_list.move_down = 下へ移動\nadd_bookmark_dialog.add = 追加\nedit_bookmarks_dialog.new = 新規\nfile_viewer.view_error_title = 表示エラー\nfile_viewer.view_error = ファイルを表示することができません。\nfile_viewer.file_menu = ファイル\nfile_viewer.close = 閉じる\nfile_viewer.large_file_warning = このファイルはこの操作には大きすぎる可能性があります。\nfile_viewer.open_anyway = そのまま開く\ntext_viewer.edit = 編集\ntext_viewer.copy = コピー\ntext_viewer.select_all = すべて選択\ntext_viewer.find = 検索\ntext_viewer.find_next = 次を検索\ntext_viewer.find_previous = 前を検索\ntext_viewer.binary_file_warning = これはバイナリ ファイルにみえます\nimage_viewer.controls_menu = 制御\nimage_viewer.zoom_in = 拡大\nimage_viewer.zoom_out = 縮小\nfile_editor.edit_error_title = 編集エラー\nfile_editor.edit_error = ファイルを編集することができません。\nfile_editor.save = 上書き保存\nfile_editor.save_as = 名前を付けて保存...\nfile_editor.save_warning = 閉じる前にこのファイルへの変更を保存しますか ?\nfile_editor.cannot_write = ファイルを書き込むことができません。\ntext_editor.cut = 切り取り\ntext_editor.paste = 貼り付け\nshortcuts_dialog.quick_search.start_search = クイック検索を開始するには何か文字を入力します\nshortcuts_dialog.quick_search.cancel_search = クイック検索の取り消し\nshortcuts_dialog.quick_search.remove_last_char = クイック検索の文字列から最後の文字を削除\nshortcuts_dialog.quick_search.jump_to_previous = 前のクイック検索の結果へジャンプ\nshortcuts_dialog.quick_search.jump_to_next = 次のクイック検索の結果へジャンプ\nshortcuts_dialog.quick_search.mark_jump_next = 現在のファイルをマーク/解除して次の検索結果へジャンプ\ntheme_editor.title = テーマ エディター\ntheme_editor.folder_tab = フォルダー ペイン\ntheme_editor.shell_tab = シェル\ntheme_editor.shell_history_tab = シェルの履歴\ntheme_editor.statusbar_tab = ステータス バー\ntheme_editor.free_space = 空き領域\ntheme_editor.free_space.ok = OK\ntheme_editor.free_space.warning = 警告\ntheme_editor.free_space.critical = 致命的\ntheme_editor.locationbar_tab = ロケーション バー\ntheme_editor.editor_tab = ファイル エディター\ntheme_editor.font = フォント\ntheme_editor.active_panel = アクティブ\ntheme_editor.inactive_panel = 非アクティブ\ntheme_editor.general = 全般\ntheme_editor.could_not_save_theme = テーマ %1 へ書き込むことができません\ntheme_editor.border = 枠\ntheme_editor.background = 背景\ntheme_editor.alternate_background = 代替の背景\ntheme_editor.unfocused_background = 背景 (フォーカスなし)\ntheme_editor.quick_search.unmatched_file = 不一致のファイル\ntheme_editor.text = テキスト\ntheme_editor.progress = 進行状況\ntheme_editor.normal = 通常\ntheme_editor.normal_unfocused = 通常 (フォーカスなし)\ntheme_editor.selected = 選択済み\ntheme_editor.selected_unfocused = 選択済み (フォーカスなし)\ntheme_editor.color = 色\ntheme_editor.colors = 色\ntheme_editor.plain_file = プレーン ファイル\ntheme_editor.marked_file = マーク済みファイル\ntheme_editor.hidden_file = 隠しファイル\ntheme_editor.folder = フォルダー\ntheme_editor.archive_file = アーカイブ\ntheme_editor.symbolic_link = シンボリック リンク\nprefs_dialog.title = 環境設定\nprefs_dialog.general_tab = 全般\nprefs_dialog.day = 日\nprefs_dialog.month = 月\nprefs_dialog.year = 年\nprefs_dialog.language = 言語 (再起動を必要とします)\nprefs_dialog.date_time = 日時のフォーマット\nprefs_dialog.time = 時間\nprefs_dialog.date = 日付\nprefs_dialog.date_separator = 区切り\nprefs_dialog.time_12_hour = 12 時間制\nprefs_dialog.time_24_hour = 24 時間制\nprefs_dialog.show_seconds = 秒を表示する\nprefs_dialog.show_century = 世紀を表示する\nprefs_dialog.check_for_updates_on_startup = 起動時に更新をチェックする\nprefs_dialog.show_splash_screen = スプラッシュ画面を表示する\nprefs_dialog.folders_tab = フォルダー\nprefs_dialog.startup_folders = スタートアップ フォルダー\nprefs_dialog.left_folder = 左のフォルダー\nprefs_dialog.right_folder = 右のフォルダー\nprefs_dialog.last_folder = 最後に訪問したフォルダー\nprefs_dialog.custom_folder = カスタム フォルダー\nprefs_dialog.show_hidden_files = 隠しファイルを表示する\nprefs_dialog.show_ds_store_files = .DS_Store ファイルを表示する\nprefs_dialog.show_system_folders = システム フォルダーを表示する\nprefs_dialog.compact_file_size = 表示されるファイル サイズを四捨五入する\nprefs_dialog.follow_symlinks_when_cd = 現在のフォルダーの変更時にシンボリック リンクに追従する\nprefs_dialog.appearance_tab = 外観\nprefs_dialog.look_and_feel = 外観\nprefs_dialog.icons_size = アイコンのサイズ\nprefs_dialog.toolbar_icons = ツール バー\nprefs_dialog.command_bar_icons = コマンド バー\nprefs_dialog.file_icons = ファイルの種類\nprefs_dialog.use_system_file_icons = システムのファイル アイコンを使用する\nprefs_dialog.use_system_file_icons.always = 常に\nprefs_dialog.use_system_file_icons.never = しない\nprefs_dialog.use_system_file_icons.applications = アプリケーションのみ\nprefs_dialog.edit_current_theme = 現在のテーマを編集...\nprefs_dialog.themes = テーマ\nprefs_dialog.import_theme = テーマのインポート\nprefs_dialog.import_look_and_feel = 外観のインポート\nprefs_dialog.no_look_and_feel = 外観は見つかりませんでした。\nprefs_dialog.error_in_import = テーマ %1 のインポート中のエラーです。\nprefs_dialog.cannot_read_theme = ファイル %1 からテーマを読み取れません\nprefs_dialog.export_theme = %1 のエクスポート\nprefs_dialog.import = インポート\nprefs_dialog.export = エクスポート\nprefs_dialog.theme_type = 種類: %1\nprefs_dialog.delete_theme = テーマ %1 を永久に削除しますか ?\nprefs_dialog.delete_look_and_feel = 外観 %1 を永久に削除しますか?\nprefs_dialog.rename_failed = テーマ %1 の名前の変更に失敗しました\nprefs_dialog.xml_file = XML ファイル\nprefs_dialog.jar_file = JAR ファイル\nprefs_dialog.mail_tab = メール\nprefs_dialog.mail_settings = 発信メールの設定\nprefs_dialog.mail_name = 名前\nprefs_dialog.mail_address = 電子メール アドレス\nprefs_dialog.mail_server = SMTP サーバー\nprefs_dialog.misc_tab = さまざま\nprefs_dialog.use_brushed_metal = 'ブラシ メタル' ルックを使用する (再起動を必要とします)\nprefs_dialog.confirm_on_quit = 終了時に確認ダイアログを表示する\nprefs_dialog.default_shell = 既定のシステム シェルを使用する\nprefs_dialog.custom_shell = カスタム シェルを使用する\nprefs_dialog.shell_encoding = シェルのエンコード\nprefs_dialog.auto_detect_shell_encoding = 自動検出\nprefs_dialog.enable_bonjour_discovery = Bonjour サービス ディスカバリを有効にする\nprefs_dialog.enable_system_notifications = システム通知を有効にする\nunit.byte = バイト\nunit.bytes = バイト\nunit.bytes_short = b\nunit.kb = KB\nunit.mb = MB\nunit.gb = GB\nunit.tb = TB\nunit.speed = %1/s\nduration.seconds = %1s\nduration.minutes = %1m\nduration.hours = %1h\nduration.days = %1d\nduration.months = %1mo\nduration.years = %1y\ntheme.custom_theme = カスタム テーマ\ntheme.custom = カスタマイズ済み\ntheme.built_in = ビルトイン\ntheme.add_on = アドオン\ntheme.current = 現在\ntheme_could_not_be_loaded = このテーマの読み込み中にエラーが発生しました。\nsetup.title = trolCommander へようこそ\nsetup.intro = trolCommander の挙動を選択してください。\nsetup.look_and_feel = 外観を選択します\nsetup.theme = テーマを選択します\nfont_chooser.font_size = サイズ\nfont_chooser.font_bold = 太字\nfont_chooser.font_italic = 斜体\ncolor_chooser.red = 赤\ncolor_chooser.green = 緑\ncolor_chooser.blue = 青\ncolor_chooser.hue = 色合い\ncolor_chooser.brightness = 明るさ\ncolor_chooser.swatches = 色見本\ncolor_chooser.saturation = 鮮やかさ\ncolor_chooser.rgb = RGB\ncolor_chooser.hsb = HSB\ncolor_chooser.recent = 最近\ncolor_chooser.alpha = Alpha 透明度\ncolor_chooser.title = 色の抽出\nbatch_rename_dialog.mask = 名前の変更パターン\nbatch_rename_dialog.search_replace = 検索と置換\nbatch_rename_dialog.search_for = 検索する文字列\nbatch_rename_dialog.replace_with = 置換する文字列\nbatch_rename_dialog.counter = カウンター\nbatch_rename_dialog.start_at = 開始\nbatch_rename_dialog.step_by = 増加\nbatch_rename_dialog.format = フォーマット\nbatch_rename_dialog.upper_lower_case = 大文字と小文字\nbatch_rename_dialog.no_change = 変更しない\nbatch_rename_dialog.lower_case = 小文字にする\nbatch_rename_dialog.upper_case = 大文字にする\nbatch_rename_dialog.first_upper = 最初の文字を大文字にする\nbatch_rename_dialog.word = 単語の最初の文字を大文字にする\nbatch_rename_dialog.old_name = 古い名前\nbatch_rename_dialog.new_name = 新しい名前\nbatch_rename_dialog.block_name = 除外\nbatch_rename_dialog.range = 範囲\nbatch_rename_dialog.proceed_renaming = %2 中 %1 個のファイルの名前が変更されます。続行しますか?\nbatch_rename_dialog.duplicate_names = 重複する名前です!\nparent_folders_quick_list.empty_message = 現在の場所には親がありません\nrecent_locations_quick_list.empty_message = 最近の場所がありません\nrecent_executed_files_quick_list.empty_message = 最近実行したファイルがありません\n#auth_dialog.error_was = エラー: %1\n#table.hide_column = 列を非表示にする\n#delete.symlink_warning_title = シンボリック リンクが見つかりました\n#delete.symlink_warning = このファイルはシンボリック リンクのようにみえます:\\n\\n  ファイル: %1\\n  リンク先: %2\\n\\nシンボリック リンクを削除するか\\nシンボリック リンクに追従してフォルダーを削除しますか (注意) ?\n#delete.delete_link_only = リンクの削除\n#delete.delete_linked_folder = フォルダーの削除\n#Unpack.label = ファイルのパック解除\n#Pack.label = ファイルのパック\n"
  },
  {
    "path": "src/main/resources/dictionary_ko_KR.properties",
    "content": "ok = 승인\nyes = 네\nno = 아니오\ncancel = 취소\nedit = 편집\nclose = 닫기\nrename = 이름변경\napply = 적용\nsave = 저장\ndont_save = 저장하지 않음\nreplace = 바꾸기\ndont_replace = 바꾸지않음\ndelete = 삭제\nskip = 지나가기\nretry = 재시도\nresume = 이어쓰기\noverwrite = 겹쳐쓰기\noverwrite_if_older = 오래된 경우 겹쳐쓰기\nduplicate = 중복\napply_to_all = 모두적용\ncopy = 복사\nmove = 이동\npack = 압축\nunpack = 압축해제\ndownload = 다운로드\nbrowse = 탐색\nask = 질문\nstop = 중지\npause = 일시중지\ncreate = 작성\nlogin = 로그인\npassword = 비밀번호\nuser = 사용자\nname = 이름\nsize = 크기\ndate = 날짜\nextension = 확장자\npermissions = 권한\nlocation = 위치\nuntitled = 이름없음\nsource = 원본\ndestination = 목적지\nrecurse_directories = 선택된 하위 디렉토리까지 작업\ngo_to = 이동\nexample = 예제\ncomment = 설명추가\nnb_files = %1 파일\nnb_folders = %1 폴더\nwarning = 주의\nerror = 오류\nfolder_does_not_exist = 폴더가 존재하지 않거나 사용할수 없습니다\nthis_folder_does_not_exist = 폴더가 존재하지 않거나 사용할수 없습니다: %1\ninvalid_path = 알수없는 경로: %1\ndirectory_already_exists = 디렉토리 %1은 이미 존재합니다.\nfile_exists_in_destination = 이미 파일이 존재 합니다\nsource_parent_of_destination = 자신의 하위 폴더로 이동하려고 합니다\ncannot_read_file = 파일을 읽을수 없음 %1\ncannot_write_file = 파일을 쓸수 없음 %1\ncannot_create_folder = 디렉토리를 생성할수 없음 %1\ncannot_read_folder = 폴더의 내용을 읽을수 없음 %1\ncannot_delete_file = 파일을 삭제할수 없음 %1\ncannot_delete_folder = 폴더를 삭제할수 없음 %1\nerror_while_transferring = 파일이동중에 오류가 발생하였습니다 %1\nsame_source_destination = 원본과 복사하려는 위치가 같습니다\nfile_already_exists = %1가 이미 존재합니다. 덮어쓰시겠습니까?\nwrite_error = 쓰기 오류\npermissions.read = 읽기\npermissions.write = 쓰기\npermissions.executable = 실행가능\npermissions.group = 그룹\npermissions.other = 기타\npermissions.octal_notation = 8진법 표시\naction_categories.navigation = 탐색\naction_categories.selection = 셀렉션\naction_categories.view = 보기\naction_categories.file_operations = 파일 작업\naction_categories.windows = 창\nTerminal.label = Terminal\nTerminal.tooltip = Terminal\nFindFile.label = Find file\nFindFile.tooltip = Find file\nAddBookmark.label = 북마크 추가\nAddBookmark.tooltip = 현재 폴더를 북마크 목록에 추가\nEditBookmarks.label = 북마크 편집\nEditCredentials.label = 암호목록 편집\nChangeLocation.label = 현재위치 변경\nChangeDate.label = 날짜 변경\nChangeDate.tooltip = 선택된 파일의 날짜를 변경\nChangePermissions.label = 권한 정보 변경\nChangePermissions.tooltip = 선택된 파일에 대한 권한 정보 변경\nCheckForUpdates.label = 최신버전 검사\nCompareFolders.label = 폴더 비교\nConnectToServer.label = 서버에 접속\nConnectToServer.tooltip = 원격 서버에 접속\nView.label = 보기\nView.tooltip = 선택된 파일 보기\nEdit.tooltip = 선택된 파일 편집\nCopy.tooltip = 선택된 파일 복사\nLocalCopy.label = 로컬 복사\nLocalCopy.tooltip = 선택된 파일을 현재 폴더로 복사\nMove.tooltip = 선택된 파일을 이동\nRename.tooltip = 선택된 파일을 이름변경\nMkdir.label = 디렉토리작성\nMkdir.tooltip = 현재 폴더에 디렉토리 작성\nMkfile.label = 파일작성\nMkfile.tooltip = 현재 폴더에 파일 작성\nDelete.tooltip = 선택된 파일 삭제\nRefresh.label = 새로고침\nRefresh.tooltip = 현재 폴더를 새로고침\nCloseWindow.label = 창닫기\nCloseWindow.tooltip = 현재 창을 닫기\nCopyFileNames.label = 파일 이름 복사\nCopyFilePaths.label = 경로 복사\nCopyFilesToClipboard.label = 파일 복사\nPasteClipboardFiles.label = 파일 붙이기\nEmail.label = 메일로 보내기\nEmail.tooltip = 선택된 파일을 이메일 첨부로 보냄\nGoBack.label = 이전으로\nGoBack.tooltip = 이전 폴더로 이동\nGoForward.label = 다음으로\nGoForward.tooltip = 다음 폴더로 이동\nGoToHome.label = 홈폴더로 이동\nGoToParent.label = 상위폴더로 이동\nGoToParent.tooltip = 상위폴더로 이동\nGoToRoot.label = 최상위 폴더로\nSortByName.label = 이름으로 정렬\nSortByDate.label = 날짜로 정렬\nSortBySize.label = 크기로 정렬\nSortByExtension.label = 확장자로 정렬\nSortByPermissions.label = 권한별로 정렬\nMarkGroup.label = 파일 선택\nMarkGroup.tooltip = 여러 파일 선택\nUnmarkGroup.label = 파일 선택 해제\nUnmarkGroup.tooltip = 여러 파일 선택 해제\nMarkAll.label = 모두 선택\nUnmarkAll.label = 모두 선택 해제\nMarkSelectedFile.label = 선택/해제\nMarkSelectedFile.tooltip = 선택된 파일에 대해 선택/해제\nMarkNextPage.label = 페이지 아래로 선택\nMarkPreviousPage.label = 페이지 위로 선택\nMarkToFirstRow.label = 시작부터 선택\nMarkToLastRow.label = 끝까지 선택\nInvertSelection.label = 선택 반전\nSwapFolders.label = 폴더를 서로 바꾸기\nSwapFolders.tooltip = 좌우 폴더를 서로 바꾸기\nSetSameFolder.label = 같은 폴더로 설정\nSetSameFolder.tooltip = 좌우를 같은 폴더로 설정\nNewWindow.label = 새창\nNewWindow.tooltip = 새로운 창을 열기\nOpen.label = 열기\nOpen.tooltip = 폴더 열기 / 압축파일 열기 / 실행\nOpenNatively.label = 시스템에서 실행\nOpenNatively.tooltip = 선택된 파일을 시스템에 연결된 프로그램으로 실행\nRevealInDesktop.label = %1에서 보기\nRunCommand.label = 명령어 실행\nRunCommand.tooltip = 현재 폴더에서 명령어 실행\nPack.tooltip = 선택된 파일을 압축\nUnpack.tooltip = 선택된 압축파일 해제\nShowFileProperties.label = 환경설정\nShowFileProperties.tooltip = 선택된 파일의 환경설정 표시\nShowPreferences.label = 환경설정\nShowPreferences.tooltip = trolCommander 설정\nShowServerConnections.label = 열린 접속 보이기\nQuit.label = 종료\nReverseSortOrder.label = 순서 변경\nToggleAutoSize.label = 컬럼 자동 크기 조정\nStop.label = 폴더 변경 중단\nToggleCommandBar.show = 커맨드바 보이기\nToggleCommandBar.hide = 커맨드바 숨기기\nToggleToolBar.show = 툴바 보이기\nToggleToolBar.hide = 툴바 숨기기\nToggleStatusBar.show = 상태바 보이기\nToggleStatusBar.hide = 상태바 숨기기\nToggleShowFoldersFirst.label = 폴더를 먼저 보이기\nPopupLeftDriveButton.label = 왼쪽 폴더 변경\nPopupRightDriveButton.label = 오른쪽 폴더 변경\nRecallPreviousWindow.label = 이전창 불러오기\nRecallNextWindow.label = 다음창 불러오기\nRecallWindow.label = 창 불러오기 #%1\nSwitchActiveTable.label = 좌우 판넬을 바꿈\nSelectFirstRow.label = 현재 폴더에 처음 파일 셀렉트\nSelectLastRow.label = 현재 폴더에 마지막 파일 셀렉트\nSplitEqually.label = 똑같은 크기로 분할\nSplitVertically.label = 수직으로 분할\nSplitHorizontally.label = 수평으로 분할\nShowKeyboardShortcuts.label = 키보드 단축키\nGoToWebsite.label = 웹사이트 이동\nGoToForums.label = 포럼으로 이동\nReportBug.label = 버그 보고\nDonate.label = 기부 하기\nShowAbout.label = trolCommander에 대해서\nfile_menu = 파일\nfile_menu.open_with = 연결프로그램\nmark_menu = 선택\nview_menu = 보기\nview_menu.show_hide_columns = 컬럼 표시/숨김\ngo_menu = 이동\nbookmarks_menu = 북마크\nbookmarks_menu.no_bookmark = 북마크 없음\nwindow_menu = 윈도우\nhelp_menu = 도움말\nstatus_bar.selected_files = %1 / %2 선택됨\nstatus_bar.connecting_to_folder = 폴더 접속중, 취소하려면 ESCAPE를 눌러주세요.\nstatus_bar.volume_free = 빈공간: %1\nstatus_bar.volume_capacity = 용량: %1\ntable.folder_access_error_title = 폴더 접근 오류\ntable.folder_access_error = 폴더의 내용을 읽을수 없음\ntable.download_or_browse = 탐색을 하거나 이 파일을 다운로드 받겠습니까?\nversion_dialog.no_new_version_title = 새로운 버전이 없습니다\nversion_dialog.no_new_version = 축하합니다. 이미 최신버전 입니다\nversion_dialog.new_version_title = 최신버전 알림\nversion_dialog.new_version = 새로운 버전의 trolCommander가 사용 가능합니다\nversion_dialog.new_version_url = 새로운 버전의 trolCommander를 다음 주소에서 받아 주십시오 %1.\nversion_dialog.not_available_title = 서버 접근 오류\nversion_dialog.not_available = 서버에서 버전정보를 얻을수 없습니다\nquit_dialog.title = trolCommander 종료\nquit_dialog.desc = 모든 창들과 trolCommander를 종료 하시겠습니까? \nquit_dialog.show_next_time = 다음에도 보입니다\ndestination_dialog.file_exists_action = 파일이 존재할때 기본설정으로\nfile_collision_dialog.title = 파일 충돌\ncopy_dialog.destination = 선택된 파일을 복사 \ncopy_dialog.error_title = 복사 오류\ncopy_dialog.copying = 파일 복사중\ncopy_dialog.copying_file = 복사중 %1\npack_dialog.packing = 파일 압축\npack_dialog.packing_file = 압축중 %1\npack_dialog.error_title = 압축오류\npack_dialog_description = 선택된 파일을 추가\npack_dialog.archive_format = 압축파일 형식\nunpack_dialog.destination = 압축파일을 선택한 곳으로 해제합니다\nunpack_dialog.error_title = 압축해제 오류\nunpack_dialog.unpacking = 압축해제중\nunpack_dialog.unpacking_file = 압축해제중 %1\nmove_dialog.move_description = 이동 위치\nmove_dialog.error_title = 이동 오류\nmove_dialog.moving = 파일 이동중\nmove_dialog.moving_file = 이동중 %1\ndownload_dialog.description = 다운로드 파일 위치\ndownload_dialog.error_title = 다운로드 오류\ndownload_dialog.downloading = 다운로드중\ndownload_dialog.downloading_file = 다운로드중 %1\ndelete_dialog.permanently_delete.confirmation = 선택한 파일을 영구히 삭제 하겠습니까?\ndelete_dialog.deleting = 삭제중\ndelete_dialog.error_title = 삭제 오류\ndelete.deleting_file = 삭제중 %1\nemail_dialog.prefs_not_set_title = 메일이 설정되지 않음\nemail_dialog.prefs_not_set = 메일 파라미터 설정을 해야 합니다 \nemail_dialog.from = 보내는 사람\nemail_dialog.to = 받는사람\nemail_dialog.subject = 제목\nemail_dialog.send = 보내기\nemail_dialog.error_title = 이메일 보내기 오류\nemail_dialog.read_error = 하위 폴더에서 파일을 읽을수 없습니다\nemail.sending_file = 파일 보내는중 %1\nemail.connecting_to_server = 연결중 %1\nemail.server_unavailable = 메일 서버에 연결할수 없습니다 %1, 메일 설정을 확인하거나 나중에 재시도 해주세요\nemail.connection_closed = 서버에서 접속이 종료되었습니다. 메일이 발송되지 않았습니다\nemail.goodbye_failed = 접속을 닫는중에 오류가 발생하였습니다. 메일이 발송되지 않을수 있습니다.\nemail.send_file_error = 파일 %1을 보내지 못했습니다, 메일을 보낼수 없습니다.\nfile_selection_dialog.mark = 선택\nfile_selection_dialog.unmark = 선택 해제\nfile_selection_dialog.mark_description = 파일 이름으로 선택\nfile_selection_dialog.unmark_description = 파일 이름으로 선택 해제\nfile_selection_dialog.case_sensitive = 대소문자 구분\nfile_selection_dialog.include_folders = 폴더포함\nprogress_dialog.starting = 이동시작...\nprogress_dialog.transferred = 이동됨 %1 속도 %2\nprogress_dialog.elapsed_time = 경과시간\nprogress_dialog.advanced = 고급설정\nprogress_dialog.current_speed = 현재속도\nprogress_dialog.limit_speed = 속도제한\nprogress_dialog.close_when_finished = 종료후 윈도우 닫기\nprogress_dialog.processing_files = 파일 처리중\nprogress_dialog.processing_file = 처리중 %1\nprogress_dialog.job_finished = 작업 종료\nproperties_dialog.file_properties = %1 속성\nproperties_dialog.contents = 내용\nproperties_dialog.calculating = 계산중...\nchange_date_dialog.now = 현재\nchange_date_dialog.specific_date = 지정날짜\nrun_dialog.run_command_description = 현재 폴더에서 실행\nrun_dialog.run_in_home_description = 홈 폴더에서 실행\nrun_dialog.command_output = 커맨드 출력\nrun_dialog.run = 실행\nrun_dialog.clear_history = 기록지우기\nserver_connect_dialog.server_type = 접속 방식\nserver_connect_dialog.server = 서버\nserver_connect_dialog.username = 유저이름\nserver_connect_dialog.initial_dir = 시작 디렉토리\nserver_connect_dialog.port = 포트\nserver_connect_dialog.server_url = 서버 URL\nserver_connect_dialog.http_url = 웹사이트 URL\nserver_connect_dialog.connect = 연결\nftp_connect.passive_mode = passive 모드 설정\nftp_connect.anonymous_user = 익명연결\nhttp_connect.basic_authentication = HTTP 기본 인증 (선택사항)\nserver_connections_dialog.disconnect = 접속종료\nserver_connections_dialog.connection_busy = 바쁨\nserver_connections_dialog.connection_idle = 쉬는중\nbonjour.bonjour_services = Bonjour 서비스\nbonjour.no_service_discovered = 서비스가 발견되지 않음\nbonjour.bonjour_disabled = Bonjour 사용불가\nauth_dialog.title = 인증\nauth_dialog.desc = login, password를 입력해주세요\nauth_dialog.server = 서버\nauth_dialog.store_credentials = login, password 저장 (약한 보안)\nsortable_list.move_up = 위로\nsortable_list.move_down = 아래로\nadd_bookmark_dialog.add = 추가\nedit_bookmarks_dialog.new = 신규\nfile_viewer.view_error_title = 보기 오류\nfile_viewer.view_error = 파일을 볼수 없음\nfile_viewer.file_menu = 파일\nfile_viewer.close = 닫기\nfile_viewer.large_file_warning = 이 작업을 하기에 파일이 너무 큽니다.\nfile_viewer.open_anyway = 계속열기\ntext_viewer.edit = 편집\ntext_viewer.copy = 복사\ntext_viewer.select_all = 모두선택\nimage_viewer.controls_menu = 조정\nimage_viewer.zoom_in = 확대\nimage_viewer.zoom_out = 축소\nfile_editor.edit_error_title = 편집 오류\nfile_editor.edit_error = 파일을 편집할수 없음\nfile_editor.save = 저장\nfile_editor.save_as = 새이름으로 저장...\nfile_editor.save_warning = 파일을 닫기전 저장하시겠습니까?\nfile_editor.cannot_write = 파일을 기록할수 없음\ntext_editor.cut = 잘라내기\ntext_editor.paste = 붙여넣기\nshortcuts_dialog.quick_search.start_search = 빠른 검색을 위하여, 적절한 단어를 입력해보세요.\nshortcuts_dialog.quick_search.cancel_search = 빠른 검색 취소\nshortcuts_dialog.quick_search.remove_last_char = 빠른 검색 단어에서 마지막 글자를 지웁니다\nshortcuts_dialog.quick_search.jump_to_previous = 이전 빠른 검색 결과로 이동\nshortcuts_dialog.quick_search.jump_to_next = 다음 빠른 검색 결과로 이동\nshortcuts_dialog.quick_search.mark_jump_next = 현재 파일을 선택/선택해제하고 다음 검색결과로 이동\ntheme_editor.font = 글꼴\ntheme_editor.background = 바탕\ntheme_editor.plain_file = 일반 파일\ntheme_editor.marked_file = 선택된 파일\ntheme_editor.hidden_file = 숨김파일\ntheme_editor.folder = 폴더\ntheme_editor.archive_file = 아카이브 파일\ntheme_editor.symbolic_link = 심볼릭 링크\nprefs_dialog.title = 환경설정\nprefs_dialog.general_tab = 일반\nprefs_dialog.day = 일\nprefs_dialog.month = 월\nprefs_dialog.year = 년\nprefs_dialog.language = 언어 (재시작 필요)\nprefs_dialog.date_time = 날짜 시간 형식\nprefs_dialog.time = 시간\nprefs_dialog.date = 날짜\nprefs_dialog.date_separator = 분리기호\nprefs_dialog.time_12_hour = 12시간제\nprefs_dialog.time_24_hour = 24시간제\nprefs_dialog.show_seconds = 초 보임\nprefs_dialog.show_century = 세기(100년)보임\nprefs_dialog.check_for_updates_on_startup = 시작시에 최신버전 확인\nprefs_dialog.folders_tab = 폴더\nprefs_dialog.startup_folders = 시작 폴더\nprefs_dialog.left_folder = 왼쪽 폴더\nprefs_dialog.right_folder = 오른쪽 폴더\nprefs_dialog.last_folder = 최종 방문 폴더\nprefs_dialog.custom_folder = 사용자 정의 폴더\nprefs_dialog.show_hidden_files = 숨김 파일 표시\nprefs_dialog.show_ds_store_files = .DS_Store 파일 표시\nprefs_dialog.show_system_folders = 시스템 폴더 표시\nprefs_dialog.compact_file_size = 파일 크기(용량) 올림\nprefs_dialog.appearance_tab = 모양새\nprefs_dialog.look_and_feel = 룩엔필\nprefs_dialog.icons_size = 아이콘 크기\nprefs_dialog.toolbar_icons = 도구 바\nprefs_dialog.command_bar_icons = 명령 바\nprefs_dialog.file_icons = 파일 타입\nprefs_dialog.use_system_file_icons = 시스템파일 아이콘 사용\nprefs_dialog.use_system_file_icons.always = 항상사용\nprefs_dialog.use_system_file_icons.never = 사용안함\nprefs_dialog.use_system_file_icons.applications = 어플리케이션만\nprefs_dialog.themes = 테마\nprefs_dialog.mail_tab = 메일\nprefs_dialog.mail_settings = 보내는 메일 설정\nprefs_dialog.mail_name = 이름\nprefs_dialog.mail_address = 메일 주소\nprefs_dialog.mail_server = SMTP 서버\nprefs_dialog.misc_tab = 기타\nprefs_dialog.use_brushed_metal = '브러쉬드 메탈' 룩을 사용합니다 (재시작 필요)\nprefs_dialog.confirm_on_quit = 종료시에 확인창을 띄웁니다\nprefs_dialog.default_shell = 기본 시스템 쉘을 사용합니다\nprefs_dialog.custom_shell = 사용자 정의 쉘을 사용합니다\nprefs_dialog.enable_bonjour_discovery = Bonjour서비스 탐색을 켭니다\nunit.byte = byte\nunit.bytes = bytes\nunit.bytes_short = b\nunit.kb = KB\nunit.mb = MB\nunit.gb = GB\nunit.tb = TB\nunit.speed = %1/s\nduration.seconds = %1s\nduration.minutes = %1m\nduration.hours = %1h\nduration.days = %1d\nduration.months = %1mo\nduration.years = %1y\ntheme.custom_theme = 사용자 테마\ntheme_could_not_be_loaded = 테마를 읽는중에 오류가 발생하였습니다\nsetup.title = trolCommander에 환영합니다\nsetup.intro = trolCommander가 작동하는 방식을 선택하세요\nsetup.look_and_feel = 룩엔필을 선택하세요\nsetup.theme = 테마선택\nfont_chooser.font_size = 크기\nfont_chooser.font_bold = 볼드\nfont_chooser.font_italic = 이탤릭\ncolor_chooser.title = 색상 선택\n#move_dialog.rename_description = 이름변경 위치\n#theme_editor.shell_colors = 쉘 색상\n#auth_dialog.error_was = 오류: %1\n#table.hide_column = 컬럼 숨김\n#delete.symlink_warning_title = 심볼릭링크 발견됨\n#delete.symlink_warning = 이 파일은 심볼릭 링크로 보입니다:\\n\\n  파일: %1\\n  연결됨: %2\\n\\n심볼릭 링크만 삭제하겠습니까\\n심볼릭 링크를 따라가서 폴더를 지우겠습니까 (주의) ?\n#delete.delete_link_only = 링크삭제\n#delete.delete_linked_folder = 폴더삭제\n#Unpack.label = 파일 압축해제\n#Pack.label = 파일압축\n"
  },
  {
    "path": "src/main/resources/dictionary_nl_NL.properties",
    "content": "ok = OK\nyes = Ja\nno = Nee\ncancel = Annuleren\nedit = Bewerken\nclose = Sluiten\nreset = Resetten\nrename = Hernoemen\napply = Toepassen\nchange = Veranderen\nsave = Opslaan\ndont_save = Verwerpen\nreplace = Vervangen\ndont_replace = Niet vervangen\ndelete = Verwijderen\nskip = Overslaan\nskip_all = Alles overslaan\nretry = Opnieuw proberen\nresume = Doorgaan\noverwrite = Overschrijven\noverwrite_if_older = Oudere overschrijven\nduplicate = Dupliceren\napply_to_all = Toepassen op alle\ncopy = Kopiëren\nmove = Verplaatsen\npack = Inpakken\nunpack = Uitpakken\ndownload = Downloaden\nsplit = Splitsen\ncombine = Samenvoegen\nbrowse = Bladeren\nask = Vragen\nstop = Afbreken\npause = Onderbreken\nquick_search = Snelzoeken\nfile_manager = Bestandsbeheerder\ncreate = Maken\ncreating_file = %1 wordt gemaakt\nchoose = Uitkiezen\ncustomize = Aanpassen\nchoose_folder = Kies map\nlogin = Login\npassword = Wachtwoord\nuser = Gebruiker\nencoding = Tekenset\npreferred_encodings = Voorkeurs tekenset\nlicense = Licentie\nname = Naam\nsize = Grootte\ndate = Datum en tijd\nextension = Extensie\npermissions = Rechten\nowner = Eigenaar\ngroup = Groep\nlocation = Plaats\nuntitled = Zonder naam\nsource = Bron\ndestination = Doellocatie\nrecurse_directories = Toepassen op onderliggende mappen\ngo_to = Ga naar\nexample = Voorbeeld\npreview = Voorbeeld\ncomment = Commentaar\nsample_text = Voorbeeldtekst\nnb_files = %1 Bestand(en)\nnb_folders = %1 map(pen)\nloading = Laden...\nthis_operation_cannot_be_undone = Deze bewerking kan niet ongedaan gemaakt worden.\nremove = Verwijder\ndetails = Details\nwarning = Waarschuwing\nerror = Fout\ngeneric_error = Er heeft zich een fout voorgedaan bij het uitvoeren van de gevraagde taak.\nfolder_does_not_exist = Map bestaat niet of is niet beschikbaar\nthis_folder_does_not_exist = Deze map bestaat niet of is niet beschikbaar: %1\nthis_file_does_not_exist = Dit bestand bestaat niet of is niet beschikbaar: %1\ninvalid_path = Ongeldige padaanduiding: %1\ndirectory_already_exists = Er is al een map %1\nfile_exists_in_destination = Bestand bestaat al in het doel\nsource_parent_of_destination = Poging om een map in een onderliggende map te verplaatsen\ncannot_read_file = Kan bestand niet lezen %1\ncannot_write_file = Kan bestand niet schrijven %1\ncannot_create_folder = Kan map niet maken %1\ncannot_read_folder = Kan inhoud van de map niet lezen %1\ncannot_delete_file = Kan bestand niet wissen %1\ncannot_delete_folder = Kan map niet wissen %1\nerror_while_transferring = Fout bij het verplaatsen van bestand %1\nsame_source_destination = Oorspronkelijk bestand is hetzelfde als doel\nfile_already_exists = %1 bestaat al, wilt u het vervangen ?\nwrite_error = Fout bij het schrijven\nread_error = Leesfout\nintegrity_check_error = Integriteitscheck negatief: bron en doel komen niet overeen\nstartup_error = Een fout verhinderde het opstarten van trolCommander.\npermissions.read = Lezen\npermissions.write = Schrijven\npermissions.executable = Uitvoerbaar\npermissions.group = Groep\npermissions.other = Anderen\npermissions.octal_notation = Octale schrijfwijze\naction_categories.all = Alle\naction_categories.navigation = Navigatie\naction_categories.selection = Selectie\naction_categories.view = Beeld\naction_categories.file_operations = Bestandsbewerkingen\naction_categories.windows = Vensters\nTerminal.label = Terminal\nTerminal.tooltip = Terminal\nFindFile.label = Find file\nFindFile.tooltip = Find file\nAddBookmark.label = Toevoegen aan bladwijzers\nAddBookmark.tooltip = Voeg huidige map aan bladwijzers toe\nBatchRename.label = Multi hernoemen\nEditBookmarks.label = Bladwijzers bewerken\nExploreBookmarks.label = Bladwijzers verkennen\nEditCredentials.label = Toegangsgegevens bewerken\nChangeLocation.label = Huidige locatie veranderen\nChangeDate.label = Verander datum\nChangeDate.tooltip = Verander datum van gemarkeerde bestanden\nChangePermissions.label = Rechten veranderen\nChangePermissions.tooltip = Rechten van gemarkeerde bestanden wijzigen\nCheckForUpdates.label = Zoeken naar updates\nCompareFolders.label = Vergelijk mappen\nConnectToServer.label = Met server verbinden\nConnectToServer.tooltip = Met server op afstand verbinden\nView.label = Bekijken\nInternalView.label = Bekijken (intern)\nView.tooltip = Geselecteerd bestand bekijken\nInternalEdit.label = Bewerken (intern)\nEdit.tooltip = Gemarkeerd bestand bewerken\nCopy.tooltip = Kopieer gemarkeerde bestanden\nLocalCopy.label = Locale kopie\nLocalCopy.tooltip = Kopieer gemarkeerde bestanden naar huidige directory\nMove.tooltip = Gemarkeerde bestanden verplaatsen\nRename.tooltip = Geselecteerd bestand hernoemen\nMkdir.label = Maak map\nMkdir.tooltip = Maak map in huidige map\nMkfile.label = Maak bestand\nMkfile.tooltip = Maak bestand in huidig directory\nDelete.tooltip = Gemarkeerde bestanden verwijderen met gebruik van de prullenbak indien mogelijk\nPermanentDelete.label = Definitief verwijderen\nPermanentDelete.tooltip = Verwijder gemarkeerde bestanden zonder de prullenbak te gebruiken\nRefresh.label = Vernieuwen\nRefresh.tooltip = Vernieuw huidige map\nCloseWindow.label = Venster sluiten\nCloseWindow.tooltip = Dit venster sluiten\nCopyFileNames.label = Naam kopiëren\nCopyFilePaths.label = Kopieer pad\nCopyFilesToClipboard.label = Bestand(en) kopiëren\nPasteClipboardFiles.label = Bestand(en) plakken\nEmail.label = Bestanden emailen\nEmail.tooltip = Verstuur gemarkeerde bestanden als bijlagen in een email\nGoBack.label = Ga terug\nGoBack.tooltip = Teruggaan naar vorige map\nGoForward.label = Naar volgende\nGoForward.tooltip = Naar volgende map\nGoToHome.label = Ga naar home-map\nGoToParent.label = Naar bovenliggende\nGoToParent.tooltip = Naar bovenliggende map\nGoToParentInOtherPanel.label = Ga naar bovenliggende map in het andere paneel\nGoToParentInBothPanels.label = Ga naar bovenliggende map in beide panelen\nGoToRoot.label = Ga naar root\nSortByName.label = Sorteren op naam\nSortByDate.label = Sorteren op datum\nSortBySize.label = Sorteren op grootte\nSortByExtension.label = Sorteren op extensie\nSortByPermissions.label = Sorteren op rechten\nSortByOwner.label = Sorteren op eigenaar\nSortByGroup.label = Sorteren op groep\nMarkGroup.label = Bestanden markeren\nMarkGroup.tooltip = Een groep bestanden markeren\nUnmarkGroup.label = Bestanden demarkeren\nUnmarkGroup.tooltip = Demarkeer groep bestanden\nMarkAll.label = Alles markeren\nUnmarkAll.label = Demarkeer alles\nMarkSelectedFile.label = Markeer/Demarkeer\nMarkSelectedFile.tooltip = Markeer/demarkeer geselecteerd bestand\nMarkNextBlock.label = Markeer een blok naar beneden\nMarkPreviousBlock.label = Markeer een blok omhoog\nMarkNextRow.label = Markeer een regel naar beneden\nMarkPreviousRow.label = Markeer een regel omhoog\nMarkNextPage.label = Markeer bestanden tot volgende bladzijde \nMarkPreviousPage.label = Markeer bestanden tot vorige pagina\nMarkToFirstRow.label = Markeer bestanden tot begin\nMarkToLastRow.label = Markeer bestanden tot eind\nMarkExtension.label = Markeer extensie\nInvertSelection.label = Selectie omkeren\nSwapFolders.label = Verwissel vensters\nSwapFolders.tooltip = Linker en rechter mappen wisselen\nSetSameFolder.label = Op zelfde map zetten\nSetSameFolder.tooltip = Dezelfde map in linker en rechter venster\nNewWindow.label = Nieuw venster\nNewWindow.tooltip = Nieuw venster openen\nOpen.label = Openen\nOpen.tooltip = Open map/ Open archief / Uitvoeren\nOpenNatively.label = Bestand met eigen toepassing openen\nOpenNatively.tooltip = Uitvoeren met systeem-geassocieerd programma\nOpenInOtherPanel.label = Openen in nieuw scherm\nOpenInBothPanels.label = Openen in beide schermen\nRevealInDesktop.label = Toon in %1\nRunCommand.label = Commando uitvoeren\nRunCommand.tooltip = Commando uitvoeren in huidige map\nPack.tooltip = Gemarkeerde bestanden inpakken in een archief\nUnpack.tooltip = Gemarkeerde archefbestanden uitpakken\nShowFileProperties.label = Eigenschappen\nShowFileProperties.tooltip = Toon eigenschappen van gemarkeerde bestanden\nShowPreferences.label = Voorkeuren\nShowPreferences.tooltip = trolCommander configureren\nShowServerConnections.label = Toon open verbindingen\nQuit.label = Beëindigen\nReverseSortOrder.label = Volgorde omkeren\nToggleAutoSize.label = Kolombreedte automatisch bepalen\nStop.label = Verandering aan mappen afbreken\nToggleColumn.show = Toon de kolom %1\nToggleColumn.hide = Verberg de kolom %1\nToggleCommandBar.show = Commandobalk tonen\nToggleCommandBar.hide = Commandobalk verbergen\nToggleToolBar.show = Toon gereedschappenbalk\nToggleToolBar.hide = Gereedschappenbalk verbergen\nCustomizeCommandBar.label = Commandobalk aanpassen\nToggleStatusBar.show = Toon statusbalk\nToggleStatusBar.hide = Statusbalk verbergen\nToggleShowFoldersFirst.label = Toon mappen eerst\nToggleTree.label = Toon boomweergave\nPopupLeftDriveButton.label = Linker map veranderen\nPopupRightDriveButton.label = Rechter map veranderen\nRecallPreviousWindow.label = Terug naar vorige venster\nRecallNextWindow.label = Terug naar volgende venster\nRecallWindow.label = Terug naar venster #%1\nBringAllToFront.label = Alles naar voren halen\nSwitchActiveTable.label = Wissel tussen linker en rechter venster\nSelectNextBlock.label = Een blok naar beneden\nSelectPreviousBlock.label = Een blok omhoog\nSelectNextPage.label = Een bladzijde omlaag\nSelectPreviousPage.label = Een bladzijde omhoog\nSelectNextRow.label = Een regel omlaag\nSelectPreviousRow.label = Een regel omhoog\nSelectFirstRow.label = Selecteer eerste bestand in huidige map\nSelectLastRow.label = Selecteer laatste bestand in huidig directory\nSplitEqually.label = Splits in gelijke delen\nSplitVertically.label = Splits verticaal\nSplitHorizontally.label = Splits horizontaal\nShowKeyboardShortcuts.label = Sneltoetsen\nGoToWebsite.label = Naar website\nGoToForums.label = Naar forum\nReportBug.label = Een bug melden\nDonate.label = Doe een donatie\nShowAbout.label = Over trolCommander\nOpenTrash.label = Open prullenbak\nEmptyTrash.label = Prullenbak leeg maken\nCalculateChecksum.label = Checksum berekenen\nMaximizeWindow.label = Maximaliseren\nMaximizeWindow.label.mac_os_x = Zoom\nMinimizeWindow.label = Minimaliseren\nGoToDocumentation.label = Online dokumentatie\nShowParentFoldersQL.label = Bovenliggende mappen\nShowRecentLocationsQL.label = Recente locaties\nShowRecentExecutedFilesQL.label = Recent uitgevoerde bestanden\nSplitFile.tooltip = Bestand opsplitsen in verschillende gedeeltes\nCombineFiles.tooltip = Gesplitse bestandsdelen samenvoegen om het orrigineel terug te krijgen\nShowDebugConsole.label = Debug console\nFocusPrevious.label = Focus op vorige component\nFocusNext.label = Focus op vorige component\nfile_menu = Bestand\nfile_menu.open_with = Openen met\nmark_menu = Markeren\nview_menu = Beeld\nview_menu.show_hide_columns = Kolommen tonen/verbergen\ngo_menu = Toepassen\nbookmarks_menu = Bladwijzers\nbookmarks_menu.no_bookmark = Geen bladwijzer\nquick_lists_menu = Snellijsten\nwindow_menu = Venster\nhelp_menu = Help\nstatus_bar.selected_files = %1 van %2 geselecteerd\nstatus_bar.connecting_to_folder = Verbinden met map, druk ESC om af te breken\nstatus_bar.volume_free = Vrij: %1\nstatus_bar.volume_capacity = Capaciteit: %1\nshortcuts_panel.title = Sneltoetsen\nshortcuts_panel.restore_defaults = Standaard instellingen\nshortcuts_panel.show = Toon\nshortcuts_panel.default_message = Druk op Enter of dubbelklik op de sneltoets die u wilt bewerken\nshortcuts_table.action_description = Beschrijving van de actie\nshortcuts_table.shortcut = Sneltoets\nshortcuts_table.alternate_shortcut = Alternatieve sneltoets\nshortcuts_table.type_in_a_shortcut = Voer een sneltoets in\ncommand_bar_dialog.help = Sleep knoppen om de commandobalk aan te passen\ntable.folder_access_error_title = Fout bij toegang tot map\ntable.folder_access_error = Kan mapinhoud niet lezen\ntable.download_or_browse = Wilt u dit bestand verkennen of wilt u het downloaden?\nversion_dialog.no_new_version_title = Geen nieuwe versie\nversion_dialog.no_new_version = Gefeliciteerd, u hebt al de nieuwste versie.\nversion_dialog.new_version_title = Nieuwe versie beschikbaar\nversion_dialog.new_version = Een nieuwe versie van trolCommander is beschikbaar.\nversion_dialog.new_version_url = Een nieuwe versie van trolCommander is beschikbaar op %1.\nversion_dialog.not_available_title = Server niet berijkbaar\nversion_dialog.not_available = Kan geen versie-informatie van server krijgen\nversion_dialog.install_and_restart = Installeren en opnieuw opstarten\nversion_dialog.preparing_for_update = Update voorbereiden...\nquit_dialog.title = trolCommander afsluiten\nquit_dialog.desc = Er zijn %1 venster(s) geopend. Alle vensters sluiten en trolCommander afsluiten?\nquit_dialog.show_next_time = Toon volgende keer\ndestination_dialog.file_exists_action = Voorkeursactie wanneer bestand er is\ndestination_dialog.verify_integrity = Verifieer data-integriteit\ndestination_dialog.skip_errors = Negeer fouten\nfile_collision_dialog.title = Dataconflict\nrename_dialog.new_name = Nieuwe naam\ncopy_dialog.destination = Kopieer gemarkeerde bestand(en) naar\ncopy_dialog.error_title = Kopieerfout\ncopy_dialog.copying = Kopieer bestanden\ncopy_dialog.copying_file = Kopiëren %1\npack_dialog.packing = Bezig bestanden in te pakken\npack_dialog.packing_file = Inpakken %1\npack_dialog.error_title = Fout bij inpakken\npack_dialog_description = Voeg gemarkeerde bestanden toe aan\npack_dialog.archive_format = Archiefformaat\nunpack_dialog.destination = Pak gemarkeerde bestand(en) uit naar\nunpack_dialog.error_title = Fout bij uitpakken\nunpack_dialog.unpacking = Bezig bestanden uit te pakken\nunpack_dialog.unpacking_file = Uitpakken %1\noptimizing_archive = Optimaliseren van archief %1\nerror_while_optimizing_archive = Fout bij het optimaliseren van archief %1\nmove_dialog.move_description = Verplaats naar\nmove_dialog.error_title = Fout bij verplaatsen\nmove_dialog.moving = Bezig te verplaatsen\nmove_dialog.moving_file = Verplaatsen %1\ndownload_dialog.description = Download bestand naar\ndownload_dialog.error_title = Fout bij downloaden\ndownload_dialog.downloading = Downloaden\ndownload_dialog.downloading_file = Downloaden %1\nmkfile_dialog.allocate_space = Bestandsruimte toewijzen\ndelete_dialog.permanently_delete.confirmation = Gemarkeerde bestand(en) voorgoed verwijderen ?\ndelete_dialog.move_to_trash.confirmation = Wis gemarkeerd(e) bestand(en) ?\ndelete_dialog.move_to_trash.confirmation_details = Bestanden worden naar de prullenbak verplaatst.\ndelete_dialog.move_to_trash.option = Verplaats naar prullenbak\ndelete_dialog.move_to_trash.failed = Een of meer bestanden konden niet naar de prullenbak verplaatst worden.\ndelete_dialog.deleting = Verwijderen\ndelete_dialog.error_title = Fout bij verwijderen\ndelete.deleting_file = Verwijderen %1\nemail_dialog.prefs_not_set_title = Email niet geconfigureerd\nemail_dialog.prefs_not_set = U moet eerst uw emailvoorkeuren instellen\nemail_dialog.from = Van\nemail_dialog.to = Aan\nemail_dialog.subject = Onderwerp\nemail_dialog.send = Verzenden\nemail_dialog.error_title = Fout bij versturen van email\nemail_dialog.read_error = Kan bestanen in onderliggende mappen niet lezen\nemail_dialog.sending = Verzenden bestanden\nemail.sending_file = Versturen %1\nemail.connecting_to_server = Verbinden met %1\nemail.server_unavailable = Kan niet verbinden met server %1, kontroleer uw emailvoorkeuren of probeer het later nog eens.\nemail.connection_closed = Verbinding door server verbroken, email niet verzonden\nemail.goodbye_failed = Fout bij het beeinigen van de verbinding, email eventueel niet verzonden\nemail.send_file_error = Kan bestand %1 niet verzenden, emial niet verstuurd.\nsplit_file_dialog.error_title = Fout bij bestanden opsplitsen\nsplit_file_dialog.file_to_split = Te splitsen bestand\nsplit_file_dialog.target_directory = Doelmap\nsplit_file_dialog.part_size = Grootte van de stukken\nsplit_file_dialog.parts = Hoeveelheid stukken\nsplit_file_dialog.generate_CRC = Creëer CRC bestand\nsplit_file_dialog.max_parts = Maximum hoeveelheid delen is %1\nsplit_file_dialog.auto = Automatisch\nsplit_file_dialog.insert_new_media = Nieuw medium invoeren\ncombine_files_dialog.error_title = Fout bij bestand samenvoegen\ncombine_files_job.no_crc_file = Samenvoegen geslaagd. Geen CRC bestand.\ncombine_files_job.crc_read_error = Fout bij lezen CRC bestand.\ncombine_files_job.crc_check_failed = CRC past niet: verwacht %2, gevonden %1\ncombine_files_job.crc_ok = Samenvoegen geslaagd. CRC controlsom ok.\nfile_selection_dialog.mark = Markeren\nfile_selection_dialog.unmark = Demarkeren\nfile_selection_dialog.mark_description = Markeer bestanden met de naam\nfile_selection_dialog.unmark_description = Demarkeer bestanden met de naam\nfile_selection_dialog.case_sensitive = Hoofdlettergevoelig\nfile_selection_dialog.include_folders = Mappen erbij nemen\nprogress_dialog.starting = Start overdracht...\nprogress_dialog.transferred = Overgedragen %1 met %2\nprogress_dialog.elapsed_time = Tijdsduur\nprogress_dialog.advanced = Geavanceerd\nprogress_dialog.current_speed = Huidige snelheid\nprogress_dialog.limit_speed = Maximale snelheid\nprogress_dialog.close_when_finished = Na beëinigen venster sluiten\nprogress_dialog.processing_files = Bestanden bewerken\nprogress_dialog.processing_file = Bewerken %1\nprogress_dialog.verifying_file = %1 verifiëren\nprogress_dialog.job_finished = Taak beeindigd\nprogress_dialog.job_error = Fout bij uitvoeren taak\nproperties_dialog.file_properties = Eigenschappen van %1\nproperties_dialog.contents = Inhoud\nproperties_dialog.calculating = Berekenen...\ncalculate_checksum_dialog.checksum_algorithm = Checksum algoritme\ncalculate_checksum_dialog.temporary_file = Tijdelijk bestand\nchange_date_dialog.now = Nu\nchange_date_dialog.specific_date = Bepaalde datum\nrun_dialog.run_command_description = Voer uit in huidige map\nrun_dialog.run_in_home_description = Voer uit in home-map\nrun_dialog.command_output = Commando output\nrun_dialog.run = Uitvoeren\nrun_dialog.clear_history = Geschiedenis wissen\nserver_connect_dialog.server_type = Verbindingstype\nserver_connect_dialog.server = Server\nserver_connect_dialog.share = Delen\nserver_connect_dialog.domain = Domein\nserver_connect_dialog.username = Gebruikersnaam\nserver_connect_dialog.initial_dir = Begindirectory\nserver_connect_dialog.port = Poort\nserver_connect_dialog.server_url = Server URL\nserver_connect_dialog.http_url = Website URL\nserver_connect_dialog.connect = Verbinden\nserver_connect_dialog.protocol = Protocol\nserver_connect_dialog.nfs_version = NFS versie\nserver_connect_dialog.private_key = Persoonlijke sleutel\nserver_connect_dialog.passphrase = Wachtwoord\nftp_connect.passive_mode = Passive modus inschakelen\nftp_connect.anonymous_user = Annonieme gebruiker\nftp_connect.nb_connection_retries = Hoeveelheid pogingen tot verbinden\nftp_connect.retry_delay = Tijd tussen pogingen (in seconden)\nhttp_connect.basic_authentication = HTTP Basic Authentication (optioneel)\nserver_connections_dialog.disconnect = Verbreken\nserver_connections_dialog.connection_busy = Bezet\nserver_connections_dialog.connection_idle = Niet actief\nbonjour.bonjour_services = Bonjour services\nbonjour.no_service_discovered = Geen service gevonden\nbonjour.bonjour_disabled = Bonjour uitgeschakeld\nauth_dialog.title = Autentificatie\nauth_dialog.desc = Geef a.u.b. naam en wachtwoord\nauth_dialog.server = Server\nauth_dialog.store_credentials = Sla naam en wachtwoord op (matige encryptie)\nauth_dialog.connect_as = Verbinden als\nauth_dialog.authentication_failed = Autentificatie mislukt\nsortable_list.move_up = Ga omhoog\nsortable_list.move_down = Ga omlaag\nadd_bookmark_dialog.add = Toevoegen\nedit_bookmarks_dialog.new = Nieuw\nfile_viewer.view_error_title = Fout bij het bekijken\nfile_viewer.view_error = Kan bestand niet bekijken\nfile_viewer.file_menu = Bestand\nfile_viewer.close = Sluiten\nfile_viewer.large_file_warning = Dit bestand is misschien te groot voor deze bewerking.\nfile_viewer.open_anyway = Toch openen\ntext_viewer.edit = Bewerken\ntext_viewer.copy = Kopiëren\ntext_viewer.select_all = Selecteer alles\ntext_viewer.find = Zoeken\ntext_viewer.find_next = Zoek volgende\ntext_viewer.find_previous = Zoek vorige\ntext_viewer.binary_file_warning = Dit bestand lijkt een binair bestand te zijn\nimage_viewer.controls_menu = Bediening\nimage_viewer.zoom_in = Zoom in\nimage_viewer.zoom_out = Zoom uit\nfile_editor.edit_error_title = Fout bij het bewerken\nfile_editor.edit_error = Kan bestand niet bewerken\nfile_editor.save = Opslaan\nfile_editor.save_as = Opslaan als...\nfile_editor.save_warning = Voor het afsluiten veranderingen opslaan?\nfile_editor.cannot_write = Kan het bestand niet schrijven\ntext_editor.cut = Knippen\ntext_editor.paste = Plakken\nshortcuts_dialog.quick_search.start_search = Voer een teken in om het snelzoeken te starten\nshortcuts_dialog.quick_search.cancel_search = Snelzoeken afbreken\nshortcuts_dialog.quick_search.remove_last_char = Verwijder laatste teken uit snelzoek-opdracht\nshortcuts_dialog.quick_search.jump_to_previous = Ga naar vorige snelzoekresultaat\nshortcuts_dialog.quick_search.jump_to_next = Ga naar volgende snelzoekresultaat\nshortcuts_dialog.quick_search.mark_jump_next = Markeer/Demarkeer huidige bestand en ga naar volgend zoekresultaat\ntheme_editor.title = Themabewerker\ntheme_editor.folder_tab = Bestandsvenster\ntheme_editor.shell_tab = Shell\ntheme_editor.shell_history_tab = Shell geschiedenis\ntheme_editor.statusbar_tab = Statusbalk\ntheme_editor.free_space = Ruimte vrij\ntheme_editor.free_space.ok = OK\ntheme_editor.free_space.warning = Waarschuwing\ntheme_editor.free_space.critical = Kritiek\ntheme_editor.locationbar_tab = Locatiebalk\ntheme_editor.editor_tab = Bestandsbewerker\ntheme_editor.font = Font\ntheme_editor.active_panel = Actief\ntheme_editor.inactive_panel = Niet actief\ntheme_editor.general = Algemeen\ntheme_editor.could_not_save_theme = Problemen bij opslaan van thema %1\ntheme_editor.border = Rand\ntheme_editor.background = Achtergrond\ntheme_editor.alternate_background = Afwisselende achtergrond\ntheme_editor.unfocused_background = Achtergrond (zonder focus)\ntheme_editor.quick_search.unmatched_file = Bestanden zijn niet hetzelfde\ntheme_editor.text = Tekst\ntheme_editor.progress = Voortgang\ntheme_editor.normal = Normaal\ntheme_editor.normal_unfocused = Normaal (zonder focus)\ntheme_editor.selected = Gemarkeerd\ntheme_editor.selected_unfocused = Geselecteerd (zonder focus)\ntheme_editor.color = Kleur\ntheme_editor.colors = Kleuren\ntheme_editor.plain_file = Normaal bestand\ntheme_editor.marked_file = Gemarkeerd bestand\ntheme_editor.hidden_file = Verborgen bestand\ntheme_editor.folder = Map\ntheme_editor.archive_file = Ingepakt bestand\ntheme_editor.symbolic_link = Symbolische link\ntheme_editor.header = Kop\ntheme_editor.item = Inhoud\ncommand_bar_customize_dialog.available_actions = Beschikbare acties\ncommand_bar_customize_dialog.modifier = Veranderen\nprefs_dialog.title = Voorkeuren\nprefs_dialog.general_tab = Algemeen\nprefs_dialog.day = Dag\nprefs_dialog.month = Maand\nprefs_dialog.year = Jaar\nprefs_dialog.language = Taal (herstarten nodig)\nprefs_dialog.date_time = Formaat van datum en tijd\nprefs_dialog.time = Tijd\nprefs_dialog.date = Datum\nprefs_dialog.date_separator = Scheidingsteken\nprefs_dialog.time_12_hour = 12-uurs formaat\nprefs_dialog.time_24_hour = 24-uurs formaat\nprefs_dialog.show_seconds = Toon seconden\nprefs_dialog.show_century = Toon eeuw\nprefs_dialog.check_for_updates_on_startup = Controleer op updates bij het opstarten\nprefs_dialog.show_splash_screen = Toon opstartscherm\nprefs_dialog.folders_tab = Mappen\nprefs_dialog.startup_folders = Opstartmappen\nprefs_dialog.left_folder = Linker map\nprefs_dialog.right_folder = Rechter map\nprefs_dialog.last_folder = Laatst bezochte map\nprefs_dialog.custom_folder = Zelfgekozen map\nprefs_dialog.show_hidden_files = Toon verborgen bestanden\nprefs_dialog.show_ds_store_files = Toon .DS_Store bestanden\nprefs_dialog.show_system_folders = Toon systeem-mappen\nprefs_dialog.compact_file_size = Getoonde bestandsgroottes afronden\nprefs_dialog.follow_symlinks_when_cd = Volg symbolische koppelingen bij wijzigen huidige directory\nprefs_dialog.appearance_tab = Weergave\nprefs_dialog.look_and_feel = Look & Feel\nprefs_dialog.icons_size = Grootte van iconen\nprefs_dialog.toolbar_icons = Gereedschappenbalk\nprefs_dialog.command_bar_icons = Commandobalk\nprefs_dialog.file_icons = Bestandstypen\nprefs_dialog.use_system_file_icons = Gebruik systeemeigen bestandsiconen\nprefs_dialog.use_system_file_icons.always = Altijd\nprefs_dialog.use_system_file_icons.never = Nooit\nprefs_dialog.use_system_file_icons.applications = Alleen voor toepassingen\nprefs_dialog.edit_current_theme = Huidige thema bewerken...\nprefs_dialog.themes = Thema's\nprefs_dialog.import_theme = Thema importeren\nprefs_dialog.import_look_and_feel = Importeer look and feel\nprefs_dialog.no_look_and_feel = Geen look en feel gevonden.\nprefs_dialog.error_in_import = Fout bij importeren van thema\nprefs_dialog.cannot_read_theme = Kan thema niet importeren uit bestand %1\nprefs_dialog.export_theme = Exporteer %1\nprefs_dialog.import = Importeren\nprefs_dialog.export = Exporteren\nprefs_dialog.theme_type = Type: %1\nprefs_dialog.delete_theme = Voorgoed verwijderen thema %1 ?\nprefs_dialog.delete_look_and_feel = Voorgoed verwijderen van look and feel %1 ?\nprefs_dialog.rename_failed = Fout bij hernoemen thema %1\nprefs_dialog.xml_file = XML bestand\nprefs_dialog.jar_file = JAR bestand\nprefs_dialog.mail_tab = Email\nprefs_dialog.mail_settings = Instellingen voor uitgaande mail\nprefs_dialog.mail_name = Uw naam\nprefs_dialog.mail_address = Uw email adres\nprefs_dialog.mail_server = SMTP server\nprefs_dialog.misc_tab = Diversen\nprefs_dialog.use_brushed_metal = Gebruik 'brushet metal' uiterlijk (herstarten vereist)\nprefs_dialog.confirm_on_quit = Toon bevestigingsdialoog bij afsluiten\nprefs_dialog.default_shell = Gebruik voorkeursshell van het systeem\nprefs_dialog.custom_shell = Gebruik zelfgekozen shell\nprefs_dialog.shell_encoding = Tekenset shell\nprefs_dialog.auto_detect_shell_encoding = Auto-detect\nprefs_dialog.enable_bonjour_discovery = Schakel Bonjour services discovery in\nprefs_dialog.enable_system_notifications = Systeemnotificaties inschakelen\ndebug_console_dialog.level = Niveau\nunit.byte = byte\nunit.bytes = bytes\nunit.bytes_short = b\nunit.kb = KB\nunit.mb = MB\nunit.gb = GB\nunit.tb = TB\nunit.speed = %1/s\nduration.seconds = %1s\nduration.minutes = %1m\nduration.hours = %1h\nduration.days = %1d\nduration.months = %1mnd\nduration.years = %1jr\ntheme.custom_theme = Zelfgemaakt thema\ntheme.custom = Aangepast\ntheme.built_in = Ingebouwd\ntheme.add_on = Toegevoegd\ntheme.current = huidig\ntheme_could_not_be_loaded = Er deed zich een fout voor bij het laden van dit thema.\nsetup.title = Welkom bij trolCommander\nsetup.intro = Kiest u a.u.b. de manier waarop trolCommander zich moet gedragen.\nsetup.look_and_feel = Kies uw Look & feel\nsetup.theme = Kies uw thema\nfont_chooser.font_size = Grootte\nfont_chooser.font_bold = Vet\nfont_chooser.font_italic = Schuin\ncolor_chooser.red = Rood\ncolor_chooser.green = Groen\ncolor_chooser.blue = Blauw\ncolor_chooser.hue = Tint\ncolor_chooser.brightness = Helderheid\ncolor_chooser.swatches = Actieve kleuren\ncolor_chooser.saturation = Verzadiging\ncolor_chooser.rgb = RGB\ncolor_chooser.hsb = HSB\ncolor_chooser.recent = Recent\ncolor_chooser.alpha = Alpha transparantie\ncolor_chooser.title = Kies een kleur\nbatch_rename_dialog.mask = Patroon nieuwe naam\nbatch_rename_dialog.search_replace = Zoek en vervang\nbatch_rename_dialog.search_for = Zoeken\nbatch_rename_dialog.replace_with = Vervang door\nbatch_rename_dialog.counter = Teller\nbatch_rename_dialog.start_at = Start bij\nbatch_rename_dialog.step_by = Verhoog met\nbatch_rename_dialog.format = Voorloopnullen\nbatch_rename_dialog.upper_lower_case = Hoofdletters/kleine letters\nbatch_rename_dialog.no_change = onveranderd\nbatch_rename_dialog.lower_case = kleine letters\nbatch_rename_dialog.upper_case = HOOFDLETTERS\nbatch_rename_dialog.first_upper = Eerste letter hoofdletter\nbatch_rename_dialog.word = Eerste Van Ieder Woord\nbatch_rename_dialog.old_name = Oude naam\nbatch_rename_dialog.new_name = Nieuwe naam\nbatch_rename_dialog.block_name = Groep\nbatch_rename_dialog.range = Gebied\nbatch_rename_dialog.proceed_renaming = %1 bestanden van %2 zullen hernoemd worden. Doorgaan?\nbatch_rename_dialog.duplicate_names = Dubbele Naam!\nparent_folders_quick_list.empty_message = Er is geen bovenliggende map bij huidige locatie\nrecent_locations_quick_list.empty_message = Geen recente locaties\nrecent_executed_files_quick_list.empty_message = Geen recent uitgevoerde bestanden\n#move_dialog.rename_description = Hernoem bestand naar\n#theme_editor.shell_font = Shell font\n#theme_editor.history_font = Font van Geschiedenis\n#theme_editor.shell_colors = Kleuren van de shell\n#theme_editor.history_colors = Kleuren van geschiedenis\n#ToggleHiddenFiles.hide = verborgen bestanden niet tonen\n#auth_dialog.error_was = Fout was: %1\n#table.hide_column = Kolom verbergen\n#delete.symlink_warning_title = Symbolische koppeling\n#delete.symlink_warning = Dit bestand lijkt op een symbolische koppeling:\\n\\n Bestand: %1\\n Verwijst naar: %2\\n\\nAlleen koppeling verwijderen of \\nKoppeling volgen en map verwijderen (VOORZICHTIG)?\n#delete.delete_link_only = Verwijder koppeling\n#delete.delete_linked_folder = Map verwijderen\n#Unpack.label = Bestanden uitpakken\n#Pack.label = Bestanden inpakken\n"
  },
  {
    "path": "src/main/resources/dictionary_no_NO.properties",
    "content": "ok = OK\nyes = Ja\nno = Nei\ncancel = Avbryt\nedit = Rediger\nclose = Lukk\nreset = Nullstill\nrename = Gi nytt navn\napply = Bruk\nchange = Endre\nsave = Lagre\ndont_save = Ikke lagre\nreplace = Erstatt\ndont_replace = Ikke erstatt\ndelete = Slett\nskip = Hopp over\nskip_all = Hopp over alle\nretry = Prøv igjen\nresume = Fortsett\noverwrite = Overskriv\noverwrite_if_older = Overskriv hvis eldre\nduplicate = Lag kopi\napply_to_all = Bruk på alle\ncopy = Kopier\nmove = Flytt\npack = Komprimer\nunpack = Pakk ut\ndownload = Last ned\nsplit = Del opp\ncombine = Sammenflett\nbrowse = Utforsk\nask = Spør\nstop = Stopp\npause = Pause\nquick_search = Hurtigsøk\nfile_manager = Filhåndterer\ncreate = Opprett\nchoose = Velg\ncustomize = Tilpass\nchoose_folder = Velg mappe\nlogin = Brukernavn\npassword = Passord\nuser = Bruker\nencoding = Tegnsett\npreferred_encodings = Foretrukket tegnsett\nlicense = Lisens\nname = Navn\nsize = Størrelse\ndate = Dato\nextension = Filtype\npermissions = Rettigheter\nowner = Eier\ngroup = Gruppe\nlocation = Plassering\nuntitled = Uten navn\nsource = Kilde\ndestination = Mål\nrecurse_directories = Behandle valgte mapper rekursivt\ngo_to = Gå til\nexample = Eksempel\npreview = Forhåndsvisning\ncomment = Kommentar\nsample_text = Eksempel på tekst\nnb_files = %1 fil(er)\nnb_folders = %1 mappe(r)\nloading = Laster...\nthis_operation_cannot_be_undone = Denne handlingen kan ikke angres.\nremove = Fjern\ndetails = Detaljer\nwarning = Advarsel\nerror = Feilmelding\ngeneric_error = En feil oppstod under utførelsen av valgte handling.\nfolder_does_not_exist = Mappen finnes ikke eller er utilgjengelig.\nthis_folder_does_not_exist = Mappen finnes ikke eller er utilgjengelig: %1\nthis_file_does_not_exist = Filen finnes ikke eller er utilgjengelig: %1\ninvalid_path = Ugyldig sti: %1\ndirectory_already_exists = Mappen %1 finnes allerede.\nfile_exists_in_destination = Filen finnes allerede i målmappen.\nsource_parent_of_destination = Forsøker å flytte mappen til en av dens undermapper\ncannot_read_file = Kan ikke lese filen %1\ncannot_write_file = Kan ikke skrive til filen %1\ncannot_create_folder = Kan ikke opprette mappen %1\ncannot_read_folder = Kan ikke lese innholdet i mappen %1\ncannot_delete_file = Kan ikke slette filen %1\ncannot_delete_folder = Kan ikke slette mappen %1\nerror_while_transferring = Feil oppstod ved flyttingen av filen %1\nsame_source_destination = Kilde- og målmappen er den samme\nfile_already_exists = %1 finnes allerede. Vil du erstatte den?\nwrite_error = Feil ved skriving\nread_error = Feil ved lesing\nintegrity_check_error = Integritetssjekk feilet: kilde og målet stemmer ikke med hverandre\nstartup_error = En feil forhindret trolCommander fra å starte.\npermissions.read = Lese\npermissions.write = Skrive\npermissions.executable = Kjøre\npermissions.group = Gruppe\npermissions.other = Andre\npermissions.octal_notation = Oktal notasjon\naction_categories.all = Alle\naction_categories.navigation = Navigasjon\naction_categories.selection = Markering\naction_categories.view = Vis\naction_categories.file_operations = Filhandlinger\naction_categories.windows = Vinduer\nTerminal.label = Terminal\nTerminal.tooltip = Terminal\nFindFile.label = Find file\nFindFile.tooltip = Find file\nAddBookmark.label = Nytt bokmerke\nAddBookmark.tooltip = Legg gjeldende mappe til listen over bokmerker\nBatchRename.label = Gi nytt navn til filer\nEditBookmarks.label = Rediger bokmerker\nExploreBookmarks.label = Vis bokmerker\nEditCredentials.label = Endre fullmakt\nChangeLocation.label = Endre gjeldene sti\nChangeDate.label = Endre dato\nChangeDate.tooltip = Endre dato for gjeldene fil(er)\nChangePermissions.label = Endre rettigheter\nChangePermissions.tooltip = Endre rettigheter for markert(e) fil(er)\nCheckForUpdates.label = Søk etter oppdateringer\nCompareFolders.label = Sammenlign mapper\nConnectToServer.label = Koble til tjener\nConnectToServer.tooltip = Koble til fjerntjener\nView.label = Vis\nInternalView.label = Vis (intern)\nView.tooltip = Vis markert fil\nInternalEdit.label = Rediger (intern)\nEdit.tooltip = Rediger markert fil\nCopy.tooltip = Kopier markert fil\nLocalCopy.label = Lokal kopi\nLocalCopy.tooltip = Kopier markert fil til gjeldene mappe\nMove.tooltip = Flytt markert fil\nRename.tooltip = Gi nytt navn til markert fil\nMkdir.label = Ny mappe\nMkdir.tooltip = Opprett en ny mappe i gjeldende mappe\nMkfile.label = Ny fil\nMkfile.tooltip = Opprett en ny fil i gjeldene mappe\nDelete.tooltip = Slett markerte filer via papirkurven hvis mulig\nPermanentDelete.label = Slett permanent\nPermanentDelete.tooltip = Slett markerte filer uten å bruke papirkurven\nRefresh.label = Oppdater\nRefresh.tooltip = Oppdater gjeldene mappe\nCloseWindow.label = Lukk vindu\nCloseWindow.tooltip = Lukk dette vinduet\nCopyFileNames.label = Kopier navn(ene)\nCopyFilePaths.label = Kopier sti(ene)\nCopyFilesToClipboard.label = Kopier fil(er)\nPasteClipboardFiles.label = Lim inn fil(er)\nEmail.label = Send via epost\nEmail.tooltip = Send markerte filer som epost vedlegg\nGoBack.label = Tilbake\nGoBack.tooltip = Gå tilbake til forrige mappe\nGoForward.label = Frem\nGoForward.tooltip = Gå til neste mappe\nGoToHome.label = Hjemmappe\nGoToParent.label = Gå opp\nGoToParent.tooltip = Gå opp en mappe\nGoToParentInOtherPanel.label = Opp en mappe, andre panel\nGoToParentInBothPanels.label = Opp en mappe, begge paneler\nGoToRoot.label = Gå til rot\nSortByName.label = Sorter etter navn\nSortByDate.label = Sorter etter dato\nSortBySize.label = Sorter etter størrelse\nSortByExtension.label = Sorter etter filtype\nSortByPermissions.label = Sorter etter rettigheter\nSortByOwner.label = Sorter etter eier\nSortByGroup.label = Sorter etter gruppe\nMarkGroup.label = Marker filer\nMarkGroup.tooltip = Marker en gruppe filer\nUnmarkGroup.label = Fjern markering av filer\nUnmarkGroup.tooltip = Fjern markeringen av en gruppe filer\nMarkAll.label = Marker alt\nUnmarkAll.label = Fjern markering av alt\nMarkSelectedFile.label = Marker/fjern markering\nMarkSelectedFile.tooltip = Marker/fjern markering av valgt fil\nMarkNextBlock.label = Marker en blokk nedover\nMarkPreviousBlock.label = Marker en blokk oppover\nMarkNextRow.label = Marker en rad nedover\nMarkPreviousRow.label = Marker en rad oppover\nMarkNextPage.label = Marker en side nedover\nMarkPreviousPage.label = Marker en side oppover\nMarkToFirstRow.label = Marker filer til begynnelsen\nMarkToLastRow.label = Marker filer til slutten\nMarkExtension.label = Marker filendelse\nInvertSelection.label = Omvend markering\nSwapFolders.label = Bytt mapper\nSwapFolders.tooltip = Bytt om høyre og venstre panel\nSetSameFolder.label = Vis samme mappe\nSetSameFolder.tooltip = Vis samme mappe i høyre og venstre panel\nNewWindow.label = Nytt vindu\nNewWindow.tooltip = Åpne et nytt vindu\nOpen.label = Åpne\nOpen.tooltip = Åpne mappe / Åpne arkiv / Kjør\nOpenNatively.label = Åpne normalt\nOpenNatively.tooltip = Åpne markert fil med systemets filassosiasjoner\nOpenInOtherPanel.label = Åpne i det andre panelet\nOpenInBothPanels.label = Åpne i begge panelene\nRevealInDesktop.label = Vis i %1\nRunCommand.label = Kjør kommando\nRunCommand.tooltip = Kjør kommando i gjeldene mappe\nPack.tooltip = Pakk markerte filer til et arkiv\nUnpack.tooltip = Pakk ut markerte arkivfiler\nShowFileProperties.label = Egenskaper\nShowFileProperties.tooltip = Vis egenskaper til markerte filer\nShowPreferences.label = Innstillinger\nShowPreferences.tooltip = Konfigurer trolCommander\nShowServerConnections.label = Vis åpne tilkoblinger\nQuit.label = Avslutt\nReverseSortOrder.label = Snu sortering\nToggleAutoSize.label = Automatisk kolonnebredde\nStop.label = Avbryt mappebytte\nToggleColumn.show = Vis %1 kolonne\nToggleColumn.hide = Skjul %1 kolonne\nToggleCommandBar.show = Vis kommandolinjen\nToggleCommandBar.hide = Skjul kommandolinjen\nToggleToolBar.show = Vis verktøylinje\nToggleToolBar.hide = Skjul verktøylinje\nCustomizeCommandBar.label = Tilpass kommandolinjen\nToggleStatusBar.show = Vis statuslinjen\nToggleStatusBar.hide = Skjul statuslinjen\nToggleShowFoldersFirst.label = Vis mapper først\nToggleTree.label = Trevisning\nPopupLeftDriveButton.label = Endre venstre mappe\nPopupRightDriveButton.label = Endre høyre mappe\nRecallPreviousWindow.label = Gjennkall forrige vindu\nRecallNextWindow.label = Gjennkall neste vindu\nRecallWindow.label = Gjennkall vindu #%1\nBringAllToFront.label = Legg alle øverst\nSwitchActiveTable.label = Bytt høyre og venstre panel\nSelectNextBlock.label = Hopp en blokk ned\nSelectPreviousBlock.label = Hopp en blokk opp\nSelectNextPage.label = Hopp ned en side\nSelectPreviousPage.label = Hopp opp en side\nSelectNextRow.label = Hopp ned en rad\nSelectPreviousRow.label = Hopp opp en rad\nSelectFirstRow.label = Merk første fil (her,) i gjeldende mappe\nSelectLastRow.label = Merk siste fil (her,) i gjeldende mappe\nSplitEqually.label = Del likt\nSplitVertically.label = Del vertikalt\nSplitHorizontally.label = Del horisontalt\nShowKeyboardShortcuts.label = Tastatursnarveier\nGoToWebsite.label = Gå til nettside\nGoToForums.label = Gå til forum\nReportBug.label = Rapporter en feil\nDonate.label = Doner\nShowAbout.label = Om trolCommander\nOpenTrash.label = Åpne papirkurv\nEmptyTrash.label = Tøm papirkurv\nCalculateChecksum.label = Regn ut kontrollsum\nMaximizeWindow.label = Maksimer\nMaximizeWindow.label.mac_os_x = Zoom\nMinimizeWindow.label = Minimer\nMinimizeWindow.label.mac_os_x = Minimer til Dock\nGoToDocumentation.label = Online dokumentasjon\nShowParentFoldersQL.label = Overordnede mapper\nShowRecentLocationsQL.label = Nylig brukte plasser\nShowRecentExecutedFilesQL.label = Nylig åpnede filer\nSplitFile.tooltip = Del en fil opp i flere deler\nCombineFiles.tooltip = Kombiner oppdelte filer for å gjenskape orginalfilen\nShowDebugConsole.label = Feilsøkingskonsoll\nFocusPrevious.label = Fokuser på forrige komponent\nFocusNext.label = Fokuser på neste komponent\nfile_menu = Fil\nfile_menu.open_with = Åpne med\nmark_menu = Marker\nview_menu = Vis\nview_menu.show_hide_columns = Vis/skjul kolonner\ngo_menu = Gå\nbookmarks_menu = Bokmerker\nbookmarks_menu.no_bookmark = Ingen bokmerker\ndrive_popup.network_shares = Nettverksmapper\nquick_lists_menu = Hurtiglister\nwindow_menu = Vindu\nhelp_menu = Hjelp\nstatus_bar.selected_files = %1 av %2 markerte\nstatus_bar.connecting_to_folder = Kobler til mappe. Trykk ESC for å avbryte.\nstatus_bar.volume_free = Ledig: %1\nstatus_bar.volume_capacity = Kapasitet: %1\nshortcuts_panel.title = Snarveier\nshortcuts_panel.restore_defaults = Gjenopprett innstillinger\nshortcuts_panel.show = Vis\nshortcuts_panel.default_message = Trykk Enter eller dobbeltklikk på snarveien du vil redigere\nshortcuts_table.action_description = Handlingsbeskrivelse\nshortcuts_table.shortcut = Snarvei\nshortcuts_table.alternate_shortcut = Alternativ snarvei\nshortcuts_table.type_in_a_shortcut = Skriv inn snarvei\ncommand_bar_dialog.help = Dra i knappene for å tilpasse kommandolinjen\ntable.folder_access_error_title = Feil ved mappetilgang\ntable.folder_access_error = Kan ikke lese mappeinnhold\ntable.download_or_browse = Vil du utforske eller laste ned filen?\nversion_dialog.no_new_version_title = Ingen ny versjon\nversion_dialog.no_new_version = Gratulerer, du har allerede den nyeste versjonen.\nversion_dialog.new_version_title = Ny versjon tilgjengelig\nversion_dialog.new_version = En ny versjon av trolCommander er tilgjengelig.\nversion_dialog.new_version_url = En ny versjon av trolCommander er tilgjengelig på %1.\nversion_dialog.not_available_title = Tjeneren er utilgjengelig\nversion_dialog.not_available = Kan ikke hente informasjon om nyeste versjon fra tjeneren.\nversion_dialog.install_and_restart = Installer og restart\nversion_dialog.preparing_for_update = Forbereder oppdatering...\nquit_dialog.title = Avslutt trolCommander\nquit_dialog.desc = Du har %1 vindu(er) åpne. Er du sikker på at du vil avslutte? \nquit_dialog.show_next_time = Vis neste gang\ndestination_dialog.file_exists_action = Standardhandling når fil allerede finnes\ndestination_dialog.verify_integrity = Bekreft dataintegritet\ndestination_dialog.skip_errors = Ignorer feil\nfile_collision_dialog.title = Filen finnes allerede\nrename_dialog.new_name = Nytt navn\ncopy_dialog.destination = Kopierer valgt(e) fil(er) til\ncopy_dialog.error_title = Feil ved kopiering\ncopy_dialog.copying = Kopierer filer\ncopy_dialog.copying_file = Kopierer %1\npack_dialog.packing = Pakker filer\npack_dialog.packing_file = Komprimerer %1\npack_dialog.error_title = Feil ved pakking\npack_dialog_description = Legg markerte filer til i\npack_dialog.archive_format = Arkivformat\nunpack_dialog.destination = Pakk ut markert(e) fil(er) i\nunpack_dialog.error_title = Feil ved utpakking\nunpack_dialog.unpacking = Pakker ut filer\nunpack_dialog.unpacking_file = Pakker ut %1\noptimizing_archive = Optimaliserer arkiv %1\nerror_while_optimizing_archive = Feil ved optimalisering av arkiv %1\nmove_dialog.move_description = Flytt til\nmove_dialog.error_title = Feil ved flytting\nmove_dialog.moving = Flytter filer\nmove_dialog.moving_file = Flytter %1\ndownload_dialog.description = Last ned fil til\ndownload_dialog.error_title = Feil ved nedlasting\ndownload_dialog.downloading = Laster ned\ndownload_dialog.downloading_file = Laster ned %1\nmkfile_dialog.allocate_space = Tildel plass\ndelete_dialog.permanently_delete.confirmation = Slett markert(e) fil(er) permanent?\ndelete_dialog.move_to_trash.confirmation = Slett markert(e) fil(er)?\ndelete_dialog.move_to_trash.confirmation_details = Filene vil bli flyttet til papirkurven.\ndelete_dialog.move_to_trash.option = Flytt til papirkurven\ndelete_dialog.move_to_trash.failed = En eller flere filer kunne ikke flyttes til papirkurven.\ndelete_dialog.deleting = Sletter\ndelete_dialog.error_title = Feil ved sletting\ndelete.deleting_file = Sletter %1\nemail_dialog.prefs_not_set_title = Epost er ikke konfigurert\nemail_dialog.prefs_not_set = Du må stille inn epost-konfigurasjonen din først.\nemail_dialog.from = Fra\nemail_dialog.to = Til\nemail_dialog.subject = Emne\nemail_dialog.send = Send\nemail_dialog.error_title = Feil ved sending av filene\nemail_dialog.read_error = Kunne ikke lese filene i undermappene.\nemail_dialog.sending = Sender filer\nemail.sending_file = Sender %1\nemail.connecting_to_server = Kobler til %1\nemail.server_unavailable = Kunne ikke kontakte epost-tjeneren %1. Sjekk epost-instillingene dine eller prøv igjen senere.\nemail.connection_closed = Forbindelsen ble stengt av tjeneren, eposten ble ikke sendt.\nemail.goodbye_failed = Feil ved lukking av forbindelse, eposten ble kanskje ikke sendt.\nemail.send_file_error = Kunne ikke sende filen %1, eposten ble ikke sendt.\nsplit_file_dialog.error_title = Feil ved oppdeling av fil\nsplit_file_dialog.file_to_split = Fil som skal deles\nsplit_file_dialog.target_directory = Målmappe\nsplit_file_dialog.part_size = Størrelse per del\nsplit_file_dialog.parts = Antall deler\nsplit_file_dialog.generate_CRC = Generer CRC-fil\nsplit_file_dialog.max_parts = Maksimum tillatte deler er %1\nsplit_file_dialog.auto = Automatisk\nsplit_file_dialog.insert_new_media = Sett inn et nytt medium\ncombine_files_dialog.error_title = Feil ved kombinering av fil\ncombine_files_job.no_crc_file = Sammenslåelse fullført. Ingen CRC-fil.\ncombine_files_job.crc_read_error = Feil ved lesing av CRC-fil.\ncombine_files_job.crc_check_failed = CRC feil: forventet %2, fant %1\ncombine_files_job.crc_ok = Sammenslåelse lyktes. CRC kontrollsum ok.\nfile_selection_dialog.mark = Marker\nfile_selection_dialog.unmark = Fjern markering\nfile_selection_dialog.mark_description = Marker filer hvis navn\nfile_selection_dialog.unmark_description = Fjern markering av filer hvis navn\nfile_selection_dialog.case_sensitive = Forskjell på små og store bokstaver\nfile_selection_dialog.include_folders = Inkluder mapper\nprogress_dialog.starting = Starter overføring...\nprogress_dialog.transferred = Overført %1 med %2\nprogress_dialog.elapsed_time = Tid brukt\nprogress_dialog.advanced = Avansert\nprogress_dialog.current_speed = Nåværende hastighet\nprogress_dialog.limit_speed = Begrens hastighet\nprogress_dialog.close_when_finished = Lukk vinduet når ferdig\nprogress_dialog.processing_files = Bearbeider filer\nprogress_dialog.processing_file = Bearbeider %1\nprogress_dialog.verifying_file = Verifiserer %1\nprogress_dialog.job_finished = Jobb ferdig\nprogress_dialog.job_error = Feil ved utførelse\nproperties_dialog.file_properties = %1 Egenskaper\nproperties_dialog.contents = Innhold\nproperties_dialog.calculating = Beregner...\ncalculate_checksum_dialog.checksum_algorithm = Algoritme for kontrollsum\ncalculate_checksum_dialog.temporary_file = Midlertidig fil\nchange_date_dialog.now = Nå\nchange_date_dialog.specific_date = Angi dato\nrun_dialog.run_command_description = Kjør i gjeldene mappe\nrun_dialog.run_in_home_description = Kjør i hjemmappe\nrun_dialog.command_output = Kommandoutskrift\nrun_dialog.run = Kjør\nrun_dialog.clear_history = Slett historikk\nserver_connect_dialog.server_type = Tilkoblingstype\nserver_connect_dialog.server = Tjener\nserver_connect_dialog.share = Del\nserver_connect_dialog.domain = Domene\nserver_connect_dialog.username = Brukernavn\nserver_connect_dialog.initial_dir = Startmappe\nserver_connect_dialog.port = Port\nserver_connect_dialog.server_url = Tjenerens URL\nserver_connect_dialog.http_url = Nettsidens URL\nserver_connect_dialog.connect = Koble til\nserver_connect_dialog.protocol = Protokoll\nserver_connect_dialog.nfs_version = NFS-versjon\nserver_connect_dialog.private_key = Privat nøkkel\nserver_connect_dialog.passphrase = Passord\nftp_connect.passive_mode = Aktiver passiv modus\nftp_connect.anonymous_user = Anonym bruker\nftp_connect.nb_connection_retries = Antall forsøk på å tilkoble\nftp_connect.retry_delay = Mellomrom mellom tilkoblinger (i sekunder)\nhttp_connect.basic_authentication = Grunnleggende HTTP autorisering (frivillig)\nserver_connections_dialog.disconnect = Koble fra\nserver_connections_dialog.connection_busy = Opptatt\nserver_connections_dialog.connection_idle = Inaktiv\nbonjour.bonjour_services = Bonjour-tjenester\nbonjour.no_service_discovered = Fant ingen tjeneste\nbonjour.bonjour_disabled = Bonjour deaktivert\nauth_dialog.title = Godkjenning\nauth_dialog.desc = Vennligst angi brukernavn og passord\nauth_dialog.server = Tjener\nauth_dialog.store_credentials = Lagre brukernavn og passord (svak kryptering)\nauth_dialog.connect_as = Koble til som\nauth_dialog.authentication_failed = Godkjenning feilet\nsortable_list.move_up = Flytt oppover\nsortable_list.move_down = Flytt nedover\nadd_bookmark_dialog.add = Legg til\nedit_bookmarks_dialog.new = Ny\nfile_viewer.view_error_title = Feil ved filvisning\nfile_viewer.view_error = Filen kunne ikke vises.\nfile_viewer.file_menu = Fil\nfile_viewer.close = Lukk\nfile_viewer.large_file_warning = Filen kan være for stor for denne handlingen.\nfile_viewer.open_anyway = Åpne allikevel\ntext_viewer.edit = Rediger\ntext_viewer.copy = Kopier\ntext_viewer.select_all = Velg alt\ntext_viewer.find = Finn\ntext_viewer.find_next = Finn neste\ntext_viewer.find_previous = Finn forrige\ntext_viewer.view = Vis\ntext_viewer.line_wrap = Tekstbrytning\ntext_viewer.line_numbers = Linjenummere\ntext_viewer.binary_file_warning = Dette ser ut til å være en binærfil\nimage_viewer.controls_menu = Funksjoner\nimage_viewer.zoom_in = Forstørr\nimage_viewer.zoom_out = Forminsk\nfile_editor.edit_error_title = Feil under redigering\nfile_editor.edit_error = Kan ikke redigere filen.\nfile_editor.save = Lagre\nfile_editor.save_as = Lagre som...\nfile_editor.save_warning = Lagre endringer til filen før lukking?\nfile_editor.cannot_write = Kan ikke skrive til filen.\ntext_editor.cut = Klipp ut\ntext_editor.paste = Lim inn\nshortcuts_dialog.quick_search.start_search = Skiv inn et tegn for å starte hurtigsøk\nshortcuts_dialog.quick_search.cancel_search = Avbryt hurtigsøk\nshortcuts_dialog.quick_search.remove_last_char = Fjern det siste tegnet fra strengen i hurtigssøket\nshortcuts_dialog.quick_search.jump_to_previous = Hopp to resultatene fra forrige hurtigsøk\nshortcuts_dialog.quick_search.jump_to_next = Hopp to resultatene fra neste hurtigsøk\nshortcuts_dialog.quick_search.mark_jump_next = Merk/fjern markering av gjeldende fil og hopp til neste søkeresultat\ntheme_editor.title = Temabehandler\ntheme_editor.folder_tab = Mappepanel\ntheme_editor.shell_tab = Skall\ntheme_editor.shell_history_tab = Skall-historikk\ntheme_editor.statusbar_tab = Statuslinje\ntheme_editor.free_space = Ledig plass\ntheme_editor.free_space.ok = OK\ntheme_editor.free_space.warning = Advarsel\ntheme_editor.free_space.critical = Kritisk\ntheme_editor.locationbar_tab = Adresselinje\ntheme_editor.editor_tab = Filbehandler\ntheme_editor.font = Skrifttype\ntheme_editor.active_panel = Aktivt\ntheme_editor.inactive_panel = Inaktivt\ntheme_editor.general = Generelt\ntheme_editor.could_not_save_theme = Kunne ikke lagre temaet %1\ntheme_editor.border = Kanter\ntheme_editor.background = Bakgrunn\ntheme_editor.alternate_background = Sekundær bakgrunn\ntheme_editor.unfocused_background = Bakgrunn (ute av fokus)\ntheme_editor.quick_search.unmatched_file = Filer som ikke matcher\ntheme_editor.text = Tekst\ntheme_editor.progress = Fremdrift\ntheme_editor.normal = Normal\ntheme_editor.normal_unfocused = Normal (ute av fokus)\ntheme_editor.selected = Markert\ntheme_editor.selected_unfocused = Markert (ute av fokus)\ntheme_editor.color = Farge\ntheme_editor.colors = Farger\ntheme_editor.plain_file = Vanlig fil\ntheme_editor.marked_file = Markert fil\ntheme_editor.hidden_file = Skjult fil\ntheme_editor.folder = Mappe\ntheme_editor.archive_file = Arkiv\ntheme_editor.symbolic_link = Symbolsk lenke\ntheme_editor.header = Overskrift\ntheme_editor.item = Element\ncommand_bar_customize_dialog.available_actions = Tilgjengelige handlinger\ncommand_bar_customize_dialog.modifier = Endrer\nprefs_dialog.title = Innstillinger\nprefs_dialog.general_tab = Generelt\nprefs_dialog.day = Dag\nprefs_dialog.month = Måned\nprefs_dialog.year = År\nprefs_dialog.language = Språk (krever omstart)\nprefs_dialog.date_time = Tids- og datoformat\nprefs_dialog.time = Tid\nprefs_dialog.date = Dato\nprefs_dialog.date_separator = Separator\nprefs_dialog.time_12_hour = 12-timersklokke\nprefs_dialog.time_24_hour = 24-timersklokke\nprefs_dialog.show_seconds = Vis sekunder\nprefs_dialog.show_century = Vis århundre\nprefs_dialog.check_for_updates_on_startup = Søk etter oppdateringer ved oppstart\nprefs_dialog.show_splash_screen = Vis oppstartsbilde\nprefs_dialog.folders_tab = Mapper\nprefs_dialog.startup_folders = Oppstartsmapper\nprefs_dialog.left_folder = Venstre mappe\nprefs_dialog.right_folder = Høyre mappe\nprefs_dialog.last_folder = Sist besøkte mappe\nprefs_dialog.custom_folder = Velg mappe\nprefs_dialog.show_hidden_files = Vis skjulte filer\nprefs_dialog.show_ds_store_files = Vis .DS_Store filer\nprefs_dialog.show_system_folders = Vis systemmapper\nprefs_dialog.compact_file_size = Rund av filstørrelser\nprefs_dialog.follow_symlinks_when_cd = Følg symbolske lenker ved endring av gjeldende mappe\nprefs_dialog.appearance_tab = Utseende\nprefs_dialog.look_and_feel = Stil\nprefs_dialog.icons_size = Ikonstørrelse\nprefs_dialog.toolbar_icons = Verktøylinje\nprefs_dialog.command_bar_icons = Kommandolinje\nprefs_dialog.file_icons = Filtyper\nprefs_dialog.use_system_file_icons = Bruk systemets filikoner\nprefs_dialog.use_system_file_icons.always = Alltid\nprefs_dialog.use_system_file_icons.never = Aldri\nprefs_dialog.use_system_file_icons.applications = Kun for programmer\nprefs_dialog.edit_current_theme = Rediger gjeldende tema...\nprefs_dialog.themes = Temaer\nprefs_dialog.import_theme = Importer tema\nprefs_dialog.import_look_and_feel = Importer stil\nprefs_dialog.no_look_and_feel = Fant ingen stil\nprefs_dialog.error_in_import = Feil ved importering av temaet %1.\nprefs_dialog.cannot_read_theme = Kan ikke laste inn temaet fre filen %1\nprefs_dialog.export_theme = Eksporter %1\nprefs_dialog.import = Importer\nprefs_dialog.export = Eksporterer\nprefs_dialog.theme_type = Type: %1\nprefs_dialog.delete_theme = Permanent sletting av temaet %1 ?\nprefs_dialog.delete_look_and_feel = Permanent sletting av stilen %1 ?\nprefs_dialog.rename_failed = Kunne ikke gi nytt navn til temaet %1\nprefs_dialog.xml_file = XML-fil\nprefs_dialog.jar_file = JAR-fil\nprefs_dialog.mail_tab = Epost\nprefs_dialog.mail_settings = Innstillinger for utgående epost\nprefs_dialog.mail_name = Ditt navn\nprefs_dialog.mail_address = Din epost-adresse\nprefs_dialog.mail_server = SMTP-tjener\nprefs_dialog.misc_tab = Diverse\nprefs_dialog.use_brushed_metal = Bruk stilen 'børstet metall' (krever omstart)\nprefs_dialog.confirm_on_quit = Spør etter bekreftelse før lukking\nprefs_dialog.default_shell = Bruk systemets standardskall\nprefs_dialog.custom_shell = Bruk annet skall\nprefs_dialog.shell_encoding = Skallets tegnsett\nprefs_dialog.auto_detect_shell_encoding = Velg automatisk\nprefs_dialog.enable_bonjour_discovery = Aktiver oppdagelse av Bonjour-tjenester\nprefs_dialog.enable_system_notifications = Aktiver systemmeldinger\ndebug_console_dialog.level = Nivå\nunit.byte = byte\nunit.bytes = bytes\nunit.bytes_short = b\nunit.kb = KB\nunit.mb = MB\nunit.gb = GB\nunit.tb = TB\nunit.speed = %1/sek\nduration.seconds = %1sek\nduration.minutes = %1min\nduration.hours = %1t\nduration.days = %1dag(er)\nduration.months = %1mnd\nduration.years = %1år\ntheme.custom_theme = Tilpasset tema\ntheme.custom = Tilpasset\ntheme.built_in = Innebygd\ntheme.add_on = Tillegg\ntheme.current = aktivt\ntheme_could_not_be_loaded = En feil oppstod ved lastingen av dette temaet.\nsetup.title = Velkommen til trolCommander\nsetup.intro = Vennligst velg hvordan du ønsker at trolCommander skal oppføre seg.\nsetup.look_and_feel = Velg stil\nsetup.theme = Velg tema\nfont_chooser.font_size = Størrelse\nfont_chooser.font_bold = Fet\nfont_chooser.font_italic = Kursiv\ncolor_chooser.red = Rød\ncolor_chooser.green = Grønn\ncolor_chooser.blue = Blå\ncolor_chooser.hue = Fargetone\ncolor_chooser.brightness = Lysstyrke\ncolor_chooser.swatches = Fargekart\ncolor_chooser.saturation = Metning\ncolor_chooser.rgb = RGB\ncolor_chooser.hsb = HSB\ncolor_chooser.recent = Nylig\ncolor_chooser.alpha = Gjennomsiktighet/Alfa-verdi\ncolor_chooser.title = Velg en farge\nbatch_rename_dialog.mask = Navngi mønster\nbatch_rename_dialog.search_replace = Søk og erstatt\nbatch_rename_dialog.search_for = Søk etter\nbatch_rename_dialog.replace_with = Erstatt med\nbatch_rename_dialog.counter = Teller\nbatch_rename_dialog.start_at = Begynn ved\nbatch_rename_dialog.step_by = Øk med\nbatch_rename_dialog.format = Format\nbatch_rename_dialog.upper_lower_case = Store/små bokstaver\nbatch_rename_dialog.no_change = Uendret\nbatch_rename_dialog.lower_case = små bokstaver\nbatch_rename_dialog.upper_case = STORE BOKSTAVER\nbatch_rename_dialog.first_upper = Stor bokstav først\nbatch_rename_dialog.word = På Begynnelsen Av Hvert Ord\nbatch_rename_dialog.old_name = Gammelt navn\nbatch_rename_dialog.new_name = Nytt navn\nbatch_rename_dialog.block_name = Bevar\nbatch_rename_dialog.range = Område\nbatch_rename_dialog.proceed_renaming = %1 filer av %2 vil få nytt navn. Ønsker du å fortsette?\nbatch_rename_dialog.duplicate_names = Dupliser navn!\nbatch_rename_dialog.names_conflict = Navnkonflikt! Samme verdi i gammelt og nytt navn.\nparent_folders_quick_list.empty_message = Gjeldende mappe har ingen overmappe\nrecent_locations_quick_list.empty_message = Ingen nylig besøkte steder\nrecent_executed_files_quick_list.empty_message = Ingen nylig åpnede filer\n#server_connect_dialog.auth_error = Ugyldig brukernavn eller passord.\n#move_dialog.cannot_move_to_itself = Kan ikke flytte fil til undermappe.\n#pack.error_on_file = Feil ved komprimering av %1\n#pack_dialog.cannot_write = Kunne ikke opprette fil i målmappe.\n#unpack.unable_to_open_zip = Kunne ikke åpne zip-fil %1.\n#image_viewer.previous_image = Forrige bilde\n#image_viewer.next_image = Neste bilde\n#mkdir_dialog.title = Neste mappe\n#mkdir_dialog.error_title = Feil ved opprettelse\n#edit_bookmarks_dialog.remove = Fjern\n#mkdir_dialog.description = Opprett mappe\n#mkfile_dialog.description = Opprett ny tom fil\n#done = Utført\n#progress_dialog.hide = Skjul\n#move_dialog.rename_description = Gi nytt navn til\n#theme_editor.shell_font = Skrifttype for skall\n#theme_editor.history_font = Skrifttype for historie\n#theme_editor.shell_colors = Farger for skall\n#theme_editor.history_colors = Farger for historikk\n#ToggleHiddenFiles.hide = Ikke vis skulte filer\n#auth_dialog.error_was = Feilen var: %1\n#table.hide_column = Skjul kolonne\n#delete.symlink_warning_title = Symbolsk lenke funnet\n#delete.symlink_warning = Denne filen ser ut til å være en symbolsk lenke:\\n\\n  Fil: %1\\n  Lenker til: %2\\n\\nDelete symlink only or\\nFølg symbolsk lenke og slett mappen(FORSIKTIG) ?\n#delete.delete_link_only = Slett lenke\n#delete.delete_linked_folder = Slett mappe\n#Unpack.label = Pakk ut filer\n#Pack.label = Pakk filer\n"
  },
  {
    "path": "src/main/resources/dictionary_pl_PL.properties",
    "content": "ok = OK\nyes = TAK\nno = Nie\ncancel = Anuluj\nedit = Edycja\nclose = Zamknij\nreset = Resetuj\nrename = Zmień nazwę\napply = Zastosuj\nchange = Zmień\nsave = Zapisz\ndont_save = Nie zapisuj\nreplace = Zastąp\ndont_replace = Nie zastępuj\ndelete = Usuń\nskip = Pomiń\nskip_all = Pomiń wszystkie\nretry = Ponów\nresume = Wznów\noverwrite = Nadpisz\noverwrite_if_older = Nadpisz jeśli starszy\nduplicate = Powiel\napply_to_all = Zastosuj do wszystkich\ncopy = Kopiuj\nmove = Przenieś\npack = Spakuj\nunpack = Rozpakuj\ndownload = Download\nsplit = Podziel\ncombine = Scal\nbrowse = Przeglądaj\nask = Zapytaj\nstop = Zatrzymaj\npause = Pauza\nquick_search = Szybkie wyszukiwanie\nfile_manager = Menedżer plików\ncreate = Utwórz\ncreating_file = Tworzenie %1\nchoose = Wybierz\ncustomize = Dostosuj\nchoose_folder = Wybierz katalog\nlogin = Login\npassword = Hasło\nuser = Właciciel\nencoding = Kodowanie\npreferred_encodings = Preferowane kodowanie\nlicense = Licencja\nname = Nazwa\nsize = Rozmiar\ndate = Data\nextension = Rozszerzenie\npermissions = Uprawnienia\nowner = Właściciel\ngroup = Grupa\nlocation = Lokalizacja\nuntitled = Bez nazwy\nsource = Źródło\ndestination = Miejsce docelowe\nrecurse_directories = Przetwórz zaznaczone katalogi rekurencyjnie\ngo_to = Przejdź do\nexample = Przykład\npreview = Podgląd\ncomment = Komentarz\nsample_text = Przykładowy tekst\nnb_files = %1 plik(i)\nnb_folders = %1 katalog(i)\nloading = Ładowanie...\nthis_operation_cannot_be_undone = Operacja nie może być cofnięta.\nremove = Usuń\ndetails = Szczegóły\nwarning = Uwaga\nerror = Błąd\ngeneric_error = Wystąpił błąd podczas wykonywania operacji.\nfolder_does_not_exist = Ten katalog nie istnieje lub jest nieosiągalny.\nthis_folder_does_not_exist = Ten katalog nie istnieje lub jest nieosiągalny: %1\nthis_file_does_not_exist = Plik nie istnieje lub jest niedostępny: %1\ninvalid_path = Błędna ścieżka: %1\ndirectory_already_exists = Katalog %1 już istnieje.\nfile_exists_in_destination = Plik już istnieje w miejscu docelowym\nsource_parent_of_destination = Próba przeniesienia katalogu do zawartego w nim podkatalogu \ncannot_read_file = Nie mogę czytać pliku %1\ncannot_write_file = Nie mogę zapisać pliku %1\ncannot_create_folder = Nie mogę utworzyć katalogu %1\ncannot_read_folder = Nie mogę odczytać zawartości katalogu %1\ncannot_delete_file = Nie mogę skasować pliku %1\ncannot_delete_folder = Nie mogę skasować katalogu %1\nerror_while_transferring = Błąd przenoszenia pliku %1\nsame_source_destination = Taki sam katalog źródłowy i docelowy\nfile_already_exists = %1 już istnieje, chcesz zastąpić ?\nwrite_error = Błąd zapisu\nread_error = Błąd odczytu\nintegrity_check_error = Błąd integralności danych: plik źródlowy i docelowy nie zgadzają się\nstartup_error = Błąd uniemożliwił uruchomienie trolCommander'a.\npermissions.read = Odczytu\npermissions.write = Zapis\npermissions.executable = Wykonywanie\npermissions.group = Grupa\npermissions.other = Inni\npermissions.octal_notation = Ósemkowo\naction_categories.all = Wszystkie\naction_categories.navigation = Nawigacja\naction_categories.selection = Wybór\naction_categories.view = Podgląd\naction_categories.file_operations = Operacje na plikach\naction_categories.windows = Okna\nTerminal.label = Terminal\nTerminal.tooltip = Terminal\nFindFile.label = Find file\nFindFile.tooltip = Find file\nAddBookmark.label = Dodaj zakładkę\nAddBookmark.tooltip = Dodaj do zakładek aktualny katalog\nBatchRename.label = Zmiana nazw plików\nEditBookmarks.label = Edytuj Zakładki\nExploreBookmarks.label = Przeglądaj zakładki\nEditCredentials.label = Zmień uprawnienia\nChangeLocation.label = Zmień aktualne położenie\nChangeDate.label = Zmień datę\nChangeDate.tooltip = Zmień datę zaznaczonym plikom\nChangePermissions.label = Zmień uprawnienia\nChangePermissions.tooltip = Zmień uprawnienia zaznaczonym plikom\nCheckForUpdates.label = Sprawdź aktualizacje\nCompareFolders.label = Porównaj katalogi\nConnectToServer.label = Połączenie z serwerem\nConnectToServer.tooltip = Podłącz do zdalnego serwera\nView.label = Podgląd\nInternalView.label = Podgląd (wewnętrzny)\nView.tooltip = Zobacz wybrany plik\nInternalEdit.label = Edycja (wewnętrzny)\nEdit.tooltip = Edytuj wybrany plik\nCopy.tooltip = Kopiuj zaznaczone pliki\nLocalCopy.label = Kopiuj lokalnie\nLocalCopy.tooltip = Kopiuj zaznaczone pliki do aktywnego katalogu\nMove.tooltip = Przenieś zaznaczone pliki\nRename.tooltip = Przemianuj zaznaczone pliki\nMkdir.label = Utwórz katalog\nMkdir.tooltip = Utwórz katalog w aktualnym katalogu\nMkfile.label = Utwórz plik\nMkfile.tooltip = Utwórz plik w aktualnym katalogu\nDelete.tooltip = Usuwa zaznaczone pliki (przenosi do kosza, jeżeli możliwe)\nPermanentDelete.label = Usuń trwale\nPermanentDelete.tooltip = Trwale usuwa zaznaczone pliki (nie używa systemowego kosza)\nRefresh.label = Odśwież\nRefresh.tooltip = Odśwież aktualny katalog\nCloseWindow.label = Zamknij okno\nCloseWindow.tooltip = Zamknij aktualne okno\nCopyFileNames.label = Kopiuj nazwę(y)\nCopyFilePaths.label = Kopiuj ścieżkę(ki)\nCopyFilesToClipboard.label = Kopiuj plik(i)\nPasteClipboardFiles.label = Wklej plik(i)\nEmail.label = Wyślij pliki pocztą\nEmail.tooltip = Wyślij zaznaczone pliki jako załącznik\nGoBack.label = Wstecz\nGoBack.tooltip = Idź do poprzedniego katalogu\nGoForward.label = Dalej\nGoForward.tooltip = Idź do następnego katalogu\nGoToHome.label = Idź do katalogu domowego\nGoToParent.label = Idź do katalogu nadrzędnego\nGoToParent.tooltip = Idź do katalogu nadrzędnego\nGoToParentInOtherPanel.label = Idź do katalogu nadrzędnego w drugim panelu\nGoToParentInBothPanels.label = Idź do katalogu nadrzędnego w obu panelach\nGoToRoot.label = Idź do katalogu głównego (Root)\nSortByName.label = Sortuj po nazwie\nSortByDate.label = Sortuj po dacie\nSortBySize.label = Sortuj po rozmiarze\nSortByExtension.label = Sortuj po rozszerzeniu\nSortByPermissions.label = Sortuj po uprawnieniach\nSortByOwner.label = Sortuj po właścicielu\nSortByGroup.label = Sortuj po grupie\nMarkGroup.label = Zaznacz pliki\nMarkGroup.tooltip = Zaznacz grupę plików\nUnmarkGroup.label = Odznacz pliki\nUnmarkGroup.tooltip = Odznacz grupę plików\nMarkAll.label = Zaznacz wszystko\nUnmarkAll.label = Odznacz wszystko\nMarkSelectedFile.label = Zaznacz/Odznacz\nMarkSelectedFile.tooltip = Zaznacz/Odznacz wybrany plik\nMarkNextBlock.label = Zaznacz blok w dół\nMarkPreviousBlock.label = Zaznacz blok w górę\nMarkNextRow.label = Zaznacz wiersz poniżej\nMarkPreviousRow.label = Zaznacz wiersz powyżej\nMarkNextPage.label = Zaznacz strone niżej (page down)\nMarkPreviousPage.label = Zaznacz strone wyżej (page up)\nMarkToFirstRow.label = Zaznacz pliki do początku\nMarkToLastRow.label = Zaznacz pliki do końca\nMarkExtension.label = Zaznacz wg rozszerzenia\nInvertSelection.label = Odwróć zaznaczenie\nSwapFolders.label = Zamień katalogi\nSwapFolders.tooltip = Zmień katalogi  z lewej i prawej strony\nSetSameFolder.label = Źródłowy=Docelowy\nSetSameFolder.tooltip = Ustawe te same katalogi w obu oknach\nNewWindow.label = Nowe okno\nNewWindow.tooltip = Otwórz nowe okno\nOpen.label = Otwórz\nOpen.tooltip = Otwórz katalog / Otwórz archiwum / Wykonaj\nOpenNatively.label = Otwórz skojarzoną aplikacją\nOpenNatively.tooltip = Uruchom wybrany plik za pomocą skojarzonego programu\nOpenInOtherPanel.label = Otwórz w drugim panelu\nOpenInBothPanels.label = Otwórz w dwóch panelach\nRevealInDesktop.label = Otwórz w %1\nRunCommand.label = Uruchom\nRunCommand.tooltip = Wykonaj polecenie w aktualnym katalogu\nPack.tooltip = Dodaj zaznaczone pliku do archiwu\nUnpack.tooltip = Rozpakuj zaznaczone archiwum(a)\nShowFileProperties.label = Właściwości\nShowFileProperties.tooltip = Pokaż właściwości zaznaczonych plików\nShowPreferences.label = Preferencje\nShowPreferences.tooltip = Konfiguruj trolCommandera\nShowServerConnections.label = Pokaż otwarte połączenia\nQuit.label = Koniec\nReverseSortOrder.label = Odwrotna kolejność\nToggleAutoSize.label = Automatycznie rozszerz kolumny\nStop.label = Zaniechaj zmiany katalogu\nToggleColumn.show = Pokaż kolumnę %1\nToggleColumn.hide = Ukryj kolumnę %1\nToggleCommandBar.show = Pokaż pasek komend\nToggleCommandBar.hide = Ukryj pasek komend\nToggleToolBar.show = Pokaż pasek narzędzi\nToggleToolBar.hide = Ukryj pasek narzędzi\nCustomizeCommandBar.label = Dostosuj pasek komend\nToggleStatusBar.show = Pokaż pasek statusu\nToggleStatusBar.hide = Ukryj pasek statusu\nToggleShowFoldersFirst.label = Pokazuj foldery jako pierwsze\nToggleTree.label = Pokaż drzewo katalogów\nPopupLeftDriveButton.label = Zmień lewy katalog\nPopupRightDriveButton.label = Zmień prawy katalog\nRecallPreviousWindow.label = Przełącz do poprzedniego okna\nRecallNextWindow.label = Przełącz do następnego okna\nRecallWindow.label = Wywołaj okno #%1\nBringAllToFront.label = Pokaż wszystkie okna\nSwitchActiveTable.label = Zamień aktualne panel\nSelectNextBlock.label = Skocz blok w dół\nSelectPreviousBlock.label = Skocz blok w dół\nSelectNextPage.label = Skocz stronę w dół\nSelectPreviousPage.label = Skocz stronę w górę\nSelectNextRow.label = Skocz wiersz niżej\nSelectPreviousRow.label = Skocz wiersz wyżej\nSelectFirstRow.label = Zaznacz pierwszy plik w aktualnym katalogu\nSelectLastRow.label = Zaznacz ostatnij plik w aktualnym katalogu\nSplitEqually.label = Podziel okno\nSplitVertically.label = Podziel pionowo\nSplitHorizontally.label = Podziel poziomo\nShowKeyboardShortcuts.label = Skróty klawiaturowe\nGoToWebsite.label = Idź do strony internetowej\nGoToForums.label = Zobacz forum\nReportBug.label = Zgłoś błąd\nDonate.label = Wesprzyj\nShowAbout.label = O programie trolCommander\nOpenTrash.label = Otwórz kosz\nEmptyTrash.label = Opróżnij kosz\nCalculateChecksum.label = Oblicz sumę kontrolną\nMaximizeWindow.label = Maksymalizuj\nMinimizeWindow.label = Minimalizuj\nGoToDocumentation.label = Dokumentacja online\nShowParentFoldersQL.label = Katalogi nadrzędne\nShowRecentLocationsQL.label = Ostatnie lokalizacje\nShowRecentExecutedFilesQL.label = Ostatnio wykonywane pliki\nSplitFile.tooltip = Podziel plik na wiele części\nCombineFiles.tooltip = Scal części podzielonego pliku aby uzyskać oryginalny plik\nShowDebugConsole.label = Konsola debugowania\nFocusPrevious.label = Przenieś fokus do poprzedniego komponentu\nFocusNext.label = Przenieś fokus do następnego komponentu\nfile_menu = Plik\nfile_menu.open_with = Otwórz za pomocą\nmark_menu = Zaznacz\nview_menu = Podgląd\nview_menu.show_hide_columns = Pokaż/Ukryj kolumny\ngo_menu = Idź\nbookmarks_menu = Zakładki\nbookmarks_menu.no_bookmark = Brak zakładek\ndrive_popup.network_shares = Sieć\nquick_lists_menu = Szybkie listy\nwindow_menu = Okno\nhelp_menu = Pomoc\nstatus_bar.selected_files = %1 z %2 zaznaczonych\nstatus_bar.connecting_to_folder = Podłączam do katalogu, naciśnij ESCAPE  aby anulować.\nstatus_bar.volume_free = Wolne: %1\nstatus_bar.volume_capacity = Pojemność: %1\nshortcuts_panel.title = Skróty\nshortcuts_panel.restore_defaults = Przywróć domyślne\nshortcuts_panel.show = Pokaż\nshortcuts_panel.default_message = Wciśnij Enter lub kliknij dwukrotnie na skrócie, który chcesz edytować\nshortcuts_table.action_description = Opis akcji\nshortcuts_table.shortcut = Skrót\nshortcuts_table.alternate_shortcut = Alternatywny skrót\nshortcuts_table.type_in_a_shortcut = Wprowadź skrót\ncommand_bar_dialog.help = Przenieś przyciski aby dostosować pasek komend\ntable.folder_access_error_title = Błąd dostępu do katalogu\ntable.folder_access_error = Nie mogę odczytać zawartości katalogu\ntable.download_or_browse = Chcesz przeglądać lub ściągnąć ten plik?\nversion_dialog.no_new_version_title = Brak nowych wersji\nversion_dialog.no_new_version = Gratulacje, posiadasz najnowszą wersję.\nversion_dialog.new_version_title = Dostępna jest nowsza wersja\nversion_dialog.new_version = Dostępna jest nowsza wersja trolCommandera.\nversion_dialog.new_version_url = Dostępna jest nowsza wersja trolCommandera pod adresem %1.\nversion_dialog.not_available_title = Serwer nie odpowiada\nversion_dialog.not_available = Nie mogę uzyskać informacji o wersji z serwera.\nversion_dialog.install_and_restart = Instaluj i uruchom\nversion_dialog.preparing_for_update = Przygotowanie do uaktualnienia...\nquit_dialog.title = Zakończ trolCommandera\nquit_dialog.desc = Masz otwartych %1 okien. Zamknąć wszystkie okna i zakończyć pracę trolCommandera ?\nquit_dialog.show_next_time = Pokaż następnym razem\ndestination_dialog.file_exists_action = Domyślna akcja kiedy plik istnieje\ndestination_dialog.verify_integrity = Weryfikuj zgodność danych\ndestination_dialog.skip_errors = Pomiń błędy\nfile_collision_dialog.title = Kolizja plików\nrename_dialog.new_name = Nowa nazwa\ncopy_dialog.destination = Kopiuj zaznaczone pliki do\ncopy_dialog.error_title = Błąd kopiowania\ncopy_dialog.copying = Kopiuje pliki\ncopy_dialog.copying_file = Kopiuję %1\npack_dialog.packing = Pakuję\npack_dialog.packing_file = Spakuj %1\npack_dialog.error_title = Błąd kompresji\npack_dialog_description = Dodaj wybrane pliki do\npack_dialog.archive_format = Typ archiwum\nunpack_dialog.destination = Rozpakuj zaznaczone pliki do\nunpack_dialog.error_title = Błąd rozpakowania\nunpack_dialog.unpacking = Rozpakuj pliki\nunpack_dialog.unpacking_file = Rozpakuj %1\noptimizing_archive = Optymalizacja archiwum %1\nerror_while_optimizing_archive = Błąd podczas optymalizacji archiwum %1\nmove_dialog.move_description = Przenieś do\nmove_dialog.error_title = Błąd przenoszenia\nmove_dialog.moving = Przenoszę pliki\nmove_dialog.moving_file = Przenoszę %1\ndownload_dialog.description = Pobierz plik do\ndownload_dialog.error_title = Błąd pobierania\ndownload_dialog.downloading = Pobierz\ndownload_dialog.downloading_file = Pobieram %1\nmkfile_dialog.allocate_space = Ustaw wielkość\ndelete_dialog.permanently_delete.confirmation = Trwale usunąć zaznaczone pliki ?\ndelete_dialog.move_to_trash.confirmation_details = Pliki zostaną przeniesione do kosza.\ndelete_dialog.move_to_trash.option = Przenieś do kosza\ndelete_dialog.move_to_trash.failed = Jeden lub więcej plików nie może być przeniesionych do kosza.\ndelete_dialog.deleting = Usuwam\ndelete_dialog.error_title = Błąd usuwania\ndelete.deleting_file = Usuwam %1\nemail_dialog.prefs_not_set_title = Poczta nie skonfigurowana\nemail_dialog.prefs_not_set = Musisz ustwaić najpierw parametry poczty.\nemail_dialog.from = Od\nemail_dialog.to = Do\nemail_dialog.subject = Temat\nemail_dialog.send = Wyślij\nemail_dialog.error_title = Błąd wysyłania plików\nemail_dialog.read_error = Nie mogę czytać plików w podkatalogu.\nemail_dialog.sending = Wysyłam pliki\nemail.sending_file = Wysyłam %1\nemail.connecting_to_server = Podłączam się do %1\nemail.server_unavailable = Nie mogę się z kontaktować z serwerem poczyowym %1, sprawdź ustawienia lub spróbuj ponownie.\nemail.connection_closed = Połączenie przerwane przez serwer, poczta nie wysłana.\nemail.goodbye_failed = Błąd podczas zamykania połączenia, poczta może zostać niewysłana.\nemail.send_file_error = Nie mogę wysłać pliku %1, poczta nie wysłana.\nsplit_file_dialog.error_title = Błąd podziału pliku\nsplit_file_dialog.file_to_split = Plik do podziału\nsplit_file_dialog.target_directory = Katalog docelowy\nsplit_file_dialog.part_size = Wielkość jednej części\nsplit_file_dialog.parts = Liczba części\nsplit_file_dialog.generate_CRC = Wygeneruj plik CRC\nsplit_file_dialog.max_parts = Maksymalna liczba części wynosi %1\nsplit_file_dialog.auto = Automatycznie\nsplit_file_dialog.insert_new_media = Włóż kolejny dysk\ncombine_files_dialog.error_title = Błąd łączenia plików\ncombine_files_job.no_crc_file = Scalanie zakończone pomyślnie. Brak pliku CRC.\ncombine_files_job.crc_read_error = Błąd podczas odczytywania pliku CRC.\ncombine_files_job.crc_check_failed = Pliki CRC nie zgadzają się (oczekiwano %2, jest %1)\ncombine_files_job.crc_ok = Scalanie zakończone pomyślnie. Plik CRC zweryfikowany.\nfile_selection_dialog.mark = Zaznacz\nfile_selection_dialog.unmark = Odznacz\nfile_selection_dialog.mark_description = Zaznacz pliki nazywające się\nfile_selection_dialog.unmark_description = Odznacz pliki nazywające się\nfile_selection_dialog.case_sensitive = Rozróżniaj wielkość liter\nfile_selection_dialog.include_folders = Uwzględnij podfoldery\nprogress_dialog.starting = Transfer rozpoczęty...\nprogress_dialog.transferred = Przesłano %1 z %2\nprogress_dialog.elapsed_time = Czas\nprogress_dialog.advanced = Zaawansowane\nprogress_dialog.current_speed = Aktualna prędkość\nprogress_dialog.limit_speed = Ograniczenie prędkości\nprogress_dialog.close_when_finished = Zamknij okno po zakończeniu\nprogress_dialog.processing_files = Przetwarzam pliki\nprogress_dialog.processing_file = Przetworzono %1\nprogress_dialog.verifying_file = Weryfikacja %1\nprogress_dialog.job_finished = Zakończono\nprogress_dialog.job_error = Błąd\nproperties_dialog.file_properties = %1 Właściwości\nproperties_dialog.contents = Zawartość\nproperties_dialog.calculating = Obliczam...\ncalculate_checksum_dialog.checksum_algorithm = Algorytm sumy kontrolnej\ncalculate_checksum_dialog.temporary_file = Plik tymczasowy\nchange_date_dialog.now = Teraz\nchange_date_dialog.specific_date = Określona data\nrun_dialog.run_command_description = Uruchom w aktywnym katalogu\nrun_dialog.run_in_home_description = Uruchom w katalogu domowym\nrun_dialog.command_output = Wynik polecenia\nrun_dialog.run = Uruchom\nrun_dialog.clear_history = Wyczyć historię\nserver_connect_dialog.server_type = Typ połączenia\nserver_connect_dialog.server = Serwer\nserver_connect_dialog.share = Udział\nserver_connect_dialog.domain = Domena\nserver_connect_dialog.username = Nazwa użytkownika\nserver_connect_dialog.initial_dir = Katalog początkowy\nserver_connect_dialog.port = Port\nserver_connect_dialog.server_url = Adres serwera\nserver_connect_dialog.http_url = Adres strony internetowej\nserver_connect_dialog.connect = Połącz\nserver_connect_dialog.protocol = Protokół\nserver_connect_dialog.nfs_version = Wersja NFS\nserver_connect_dialog.private_key = Klucz prywatny\nserver_connect_dialog.passphrase = Hasło\nftp_connect.passive_mode = Użyj trybu pasywnego\nftp_connect.anonymous_user = Użytkownik anonimowy\nftp_connect.nb_connection_retries = Liczba ponowień połączenia\nftp_connect.retry_delay = Opóźnienie między ponowieniem połączenia (w sekundach)\nhttp_connect.basic_authentication = HTTP Prosta Autoryzacja/Basic Authentication (opcja)\nserver_connections_dialog.disconnect = Rozłącz\nserver_connections_dialog.connection_busy = Zajęty\nserver_connections_dialog.connection_idle = Nieaktywny\nbonjour.bonjour_services = Powitanie\nbonjour.no_service_discovered = Nie rozpoznano serwisu\nbonjour.bonjour_disabled = Powitanie nieaaktywne\nauth_dialog.title = Autoryzacja\nauth_dialog.desc = Proszę wprowadzić nazwę użytkownika i hasło\nauth_dialog.server = Serwer\nauth_dialog.store_credentials = Zapisz login i hasło (słabe szyfrowanie)\nauth_dialog.connect_as = Połącz jako\nauth_dialog.authentication_failed = Autentykacja nieudana\nsortable_list.move_up = Przenieś do góry\nsortable_list.move_down = Przenieś na dół\nadd_bookmark_dialog.add = Dodaj\nedit_bookmarks_dialog.new = Nowa\nfile_viewer.view_error_title = Błąd podglądu pliku\nfile_viewer.view_error = Nie mogę wyświetlić pliku\nfile_viewer.file_menu = Plik\nfile_viewer.close = Zamknij\nfile_viewer.large_file_warning = Wybrany plik może być zbyt duży dla tej operacji.\nfile_viewer.open_anyway = Otwórz mimo wszystko\ntext_viewer.edit = Edytuj\ntext_viewer.copy = Kopiuj\ntext_viewer.select_all = Zaznacz wszystko\ntext_viewer.find = Znajdź\ntext_viewer.find_next = Znajdź następne\ntext_viewer.find_previous = Znajdź poprzednie\ntext_viewer.binary_file_warning = Wygląda że jest to plik binarny nie tekstowy\nimage_viewer.controls_menu = Sterowanie\nimage_viewer.zoom_in = Powiększ\nimage_viewer.zoom_out = Pomniejsz\nfile_editor.edit_error_title = Błąd edycji\nfile_editor.edit_error = Błąd edycji pliku.\nfile_editor.save = Zapisz\nfile_editor.save_as = Zapisz jako...\nfile_editor.save_warning = Zapisać zmiany przed zamknięciem ?\nfile_editor.cannot_write = Nie mogę zapisać pliku.\ntext_editor.cut = Wytnij\ntext_editor.paste = Wklej\nshortcuts_dialog.quick_search.start_search = Wpisz dowolne znaki aby rozpocząć wyszukiwanie\nshortcuts_dialog.quick_search.cancel_search = Anuluj szybkie wyszukiwanie\nshortcuts_dialog.quick_search.remove_last_char = Usuń ostatni znak z szybkiego wyszukiwania\nshortcuts_dialog.quick_search.jump_to_previous = Przejdź do poprzednich wyników wyszukiwania\nshortcuts_dialog.quick_search.jump_to_next = Przejdź do natępnych wyników wyszukiwania\nshortcuts_dialog.quick_search.mark_jump_next = Zaznacz/Odznacz aktualny plik i skocz do następnych wyników wyszukiwania\ntheme_editor.title = Edytor tematu\ntheme_editor.folder_tab = Panel plików\ntheme_editor.shell_tab = Shell\ntheme_editor.shell_history_tab = Historia shell'a\ntheme_editor.statusbar_tab = Pasek stanu\ntheme_editor.free_space = Wolne miejsce\ntheme_editor.free_space.ok = OK\ntheme_editor.free_space.warning = Ostrzeżenie\ntheme_editor.free_space.critical = Krytyczne\ntheme_editor.locationbar_tab = Pasek lokalizacji\ntheme_editor.editor_tab = Edytor plików\ntheme_editor.font = Czcionka\ntheme_editor.active_panel = Aktywny\ntheme_editor.inactive_panel = Nieaktywny\ntheme_editor.general = Ogólne\ntheme_editor.could_not_save_theme = Nie mogę zapisać tematu %1\ntheme_editor.border = Ramka\ntheme_editor.background = Tło\ntheme_editor.alternate_background = Tło (alternatywne)\ntheme_editor.unfocused_background = Background (bez fokusa)\ntheme_editor.quick_search.unmatched_file = Pliki niepasujące\ntheme_editor.text = Tekst\ntheme_editor.progress = Postęp\ntheme_editor.normal = Zwykły\ntheme_editor.normal_unfocused = Normalne (bez fokusa)\ntheme_editor.selected = Zaznaczone\ntheme_editor.selected_unfocused = Zaznaczone (bez fokusa)\ntheme_editor.color = Kolor\ntheme_editor.colors = Kolory\ntheme_editor.plain_file = Zwykły plik\ntheme_editor.marked_file = Zaznaczony plik\ntheme_editor.hidden_file = Ukryty plik\ntheme_editor.folder = Katalog\ntheme_editor.archive_file = Plik archiwum\ntheme_editor.symbolic_link = Dowiązanie symboliczne\ntheme_editor.header = Nagłówek\ntheme_editor.item = Pozycja\ncommand_bar_customize_dialog.available_actions = Dostępne akcje\ncommand_bar_customize_dialog.modifier = Modyfikator\nprefs_dialog.title = Ustawienia\nprefs_dialog.general_tab = Główne\nprefs_dialog.day = Dzień\nprefs_dialog.month = Miesiąc\nprefs_dialog.year = Rok\nprefs_dialog.language = Język (Wymaga restartu)\nprefs_dialog.date_time = Format daty i czasu\nprefs_dialog.time = Czas\nprefs_dialog.date = Data\nprefs_dialog.date_separator = Separator/Odstęp\nprefs_dialog.time_12_hour = format 12 godzinny\nprefs_dialog.time_24_hour = format 24 godzinny\nprefs_dialog.show_seconds = Pokaż sekundy\nprefs_dialog.show_century = Pokaż wiek\nprefs_dialog.check_for_updates_on_startup = Sprawdź czy są aktulizacje przy starcie\nprefs_dialog.show_splash_screen = Pokaż ekran powitalny\nprefs_dialog.folders_tab = Katalogi\nprefs_dialog.startup_folders = Katalogi startowe\nprefs_dialog.left_folder = Lewy panel\nprefs_dialog.right_folder = Prawy panel\nprefs_dialog.last_folder = Ostatnio przeglądany katalog\nprefs_dialog.custom_folder = Katalog użytkownika\nprefs_dialog.show_hidden_files = Pokaż ukryte pliki\nprefs_dialog.show_ds_store_files = Pokaż pliki .DS_Store\nprefs_dialog.show_system_folders = Pokaż katalogi systemowe\nprefs_dialog.compact_file_size = Zaokrągl rozmiar wyświetlanych plików\nprefs_dialog.follow_symlinks_when_cd = Zmień katalog przy przechodzeniu do dowiązania symbolicznego\nprefs_dialog.appearance_tab = Wygląd\nprefs_dialog.look_and_feel = Look & Feel\nprefs_dialog.icons_size = Rozmiar ikon\nprefs_dialog.toolbar_icons = Pasek narzędzi\nprefs_dialog.command_bar_icons = Linia komend\nprefs_dialog.file_icons = Typy plików\nprefs_dialog.use_system_file_icons = Używaj ikon systemowych\nprefs_dialog.use_system_file_icons.always = Zawsze\nprefs_dialog.use_system_file_icons.never = Nigdy\nprefs_dialog.use_system_file_icons.applications = Tylko dla aplikacji\nprefs_dialog.edit_current_theme = Edytuj bieżący temat...\nprefs_dialog.themes = Tematy wizualne (skórki)\nprefs_dialog.import_theme = Importuj temat\nprefs_dialog.import_look_and_feel = Zaimportuj wygląd i zachowanie  (look & feel)\nprefs_dialog.no_look_and_feel = Nie znaleziono pliku ustawień wyglądu i zachowania (look & feel).\nprefs_dialog.error_in_import = Błąd podczas importu tematu %1.\nprefs_dialog.cannot_read_theme = Nie można załadować tematu z pliku %1\nprefs_dialog.export_theme = Eksport %1\nprefs_dialog.import = Import\nprefs_dialog.export = Eksport\nprefs_dialog.theme_type = Typ: %1\nprefs_dialog.delete_theme = Czy na pewno usunąć temat %1 ?\nprefs_dialog.delete_look_and_feel = Definitywnie skasować ustawienia wyglądu i zachowania (look & feel) %1 ?\nprefs_dialog.rename_failed = Nie można zmienić nazwy tematu %1\nprefs_dialog.xml_file = plik XML\nprefs_dialog.jar_file = Plik JAR\nprefs_dialog.mail_tab = Poczta elektroniczna\nprefs_dialog.mail_settings = Ustawienia poczty wychodzącej\nprefs_dialog.mail_name = Twoie imię\nprefs_dialog.mail_address = Twój adres email\nprefs_dialog.mail_server = Serwer poczty wychodzącej SMTP\nprefs_dialog.misc_tab = Różne\nprefs_dialog.use_brushed_metal = Użyj wyglądu 'brushed metal' (Wymaga restartu)\nprefs_dialog.confirm_on_quit = Pokaż okno potwierdzające przy wyjściu\nprefs_dialog.default_shell = Użyj domylnego systemowego shella\nprefs_dialog.custom_shell = Użyj shella użytkownika\nprefs_dialog.shell_encoding = Kodowanie shella\nprefs_dialog.auto_detect_shell_encoding = Auto-wybór\nprefs_dialog.enable_bonjour_discovery = Uaktywnij Powitanie\nprefs_dialog.enable_system_notifications = Włącz powiadomienia\ndebug_console_dialog.level = Poziom\nunit.byte = bajt\nunit.bytes = bajty\nunit.bytes_short = b\nunit.kb = KB\nunit.mb = MB\nunit.gb = GB\nunit.tb = TB\nunit.speed = %1/s\nduration.seconds = %1s\nduration.minutes = %1m\nduration.hours = %1g\nduration.days = %1d\nduration.months = %1M\nduration.years = %1r\ntheme.custom_theme = Temat użytkownika\ntheme.custom = Użytkownika\ntheme.built_in = Wbudowany\ntheme.add_on = Dodatek\ntheme.current = bieżący\ntheme_could_not_be_loaded = Wystąpł błąd podczas ładowania tematu wizualnego\nsetup.title = Witamy w programie trolCommander\nsetup.intro = Proszę wybrać jak ma się zachowywać trolCommander.\nsetup.look_and_feel = Wygląd i zachowanie\nsetup.theme = Wybierz temat wizualny\nfont_chooser.font_size = Rozmiar\nfont_chooser.font_bold = Pogrubiony\nfont_chooser.font_italic = Kursywa\ncolor_chooser.red = Czerwony\ncolor_chooser.green = Zielony\ncolor_chooser.blue = Niebieski\ncolor_chooser.hue = Odcień\ncolor_chooser.brightness = Jasność\ncolor_chooser.swatches = Próbki\ncolor_chooser.saturation = Nasycenie\ncolor_chooser.rgb = RGB\ncolor_chooser.hsb = HSB\ncolor_chooser.recent = Ostatnie\ncolor_chooser.alpha = Przeźroczystość\ncolor_chooser.title = Wybierz kolor\nbatch_rename_dialog.mask = Matryca\nbatch_rename_dialog.search_replace = Znajdź i zamień\nbatch_rename_dialog.search_for = Szukaj\nbatch_rename_dialog.replace_with = Zamień na\nbatch_rename_dialog.counter = Licznik\nbatch_rename_dialog.start_at = Start\nbatch_rename_dialog.step_by = Krok\nbatch_rename_dialog.format = Format\nbatch_rename_dialog.upper_lower_case = Wielkość znaków\nbatch_rename_dialog.no_change = Bez zmian\nbatch_rename_dialog.lower_case = małe litery\nbatch_rename_dialog.upper_case = DUŻE LITERY\nbatch_rename_dialog.first_upper = Pierwsza litera duża\nbatch_rename_dialog.word = Pierwsza Każdego Słowa\nbatch_rename_dialog.old_name = Stara nazwa\nbatch_rename_dialog.new_name = Nowa nazwa\nbatch_rename_dialog.block_name = Zablokuj\nbatch_rename_dialog.range = Zakres\nbatch_rename_dialog.proceed_renaming = %1 nazw plików z %2 będzie zmienionych. Czy kontynuować?\nbatch_rename_dialog.duplicate_names = Wykryto zduplikowane nazwy!\nbatch_rename_dialog.names_conflict = Wykryto konflikt nazw! Wartości w kolumnie stara i nowa nazwa są takie same.\nparent_folders_quick_list.empty_message = Bieżąca lokalizacja nie posiada katalogu nadrzędnego\nrecent_locations_quick_list.empty_message = Brak ostanich lokalizacji\nrecent_executed_files_quick_list.empty_message = Brak ostatnio uruchamianych plików\n#pack.error_on_file = Błąd podczas kompresji %1\n#pack_dialog.cannot_write = Nie mogę utworzyć archiwum w katalogu docelowym\n#unpack.unable_to_open_zip = Nie mogę otworzyć archiwum %1.\n#mkdir_dialog.title = Utwórz katalog\n#mkdir_dialog.error_title = Błąd tworzenia katalogu\n#edit_bookmarks_dialog.remove = Usuń\n#mkdir_dialog.description = Utwórz katalog\n#done = Zrobione\n#progress_dialog.hide = Ukryj\n#move_dialog.rename_description = Zmień nazwę na\n#theme_editor.shell_colors = Kolor okna terminala (shell)\n#auth_dialog.error_was = Wystąpił błąd: %1\n#table.hide_column = Ukryj kolumny\n#delete.symlink_warning_title = Znaleziono łącze symboliczne\n#delete.symlink_warning = Ten plik wygląda jak łącze symboliczne:\\n\\n Plik: %1\\n wskazuje na: %2\\n\\nSkasuj łącze symboliczne tylko lub\\npodąż za dowiązaniem i skasuj katalog (UWAGA) ?\n#delete.delete_link_only = Usuń dowiązanie (link)\n#delete.delete_linked_folder = Usuń katalog\n#Unpack.label = Rozpakuj pliki\n#Pack.label = Spakuj pliki\n"
  },
  {
    "path": "src/main/resources/dictionary_pt_BR.properties",
    "content": "ok = Aceitar\nyes = Sim\nno = Não\ncancel = Cancelar\nedit = Editar\nclose = Fechar\nreset = Reiniciar\nrename = Renomear\napply = Aplicar\nchange = Alterar\nsave = Salvar\ndont_save = Não salvar\nreplace = Substituir\ndont_replace = Não substituir\ndelete = Remover\nskip = Ignorar\nskip_all = Ignorar tudo\nretry = Tentar novamente\nresume = Resumir\noverwrite = Sobrescrever\noverwrite_if_older = Sobrescrever se mais antigo\nduplicate = Duplicar\napply_to_all = Aplicar a todos\ncopy = Copiar\nmove = Mover\npack = Compactar\nunpack = Descompactar\ndownload = Baixar\nsplit = Dividir\ncombine = Combinar\nbrowse = Navegar\nask = Perguntar\nstop = Parar\npause = Pausa\nquick_search = Busca rápida\nfile_manager = Gerenciador de arquivos\ncreate = Criar\ncreating_file = Criando %1\nchoose = Escolha\ncustomize = Customize\nchoose_folder = Escolha um diretório\nlogin = Usuário\npassword = Senha\nuser = Usuário\nencoding = Codificação\npreferred_encodings = Encode preferido\nlicense = Licença\nname = Nome\nsize = Tamanho\ndate = Data\nextension = Extensão\npermissions = Permissões\nowner = Proprietário\ngroup = Grupo\nlocation = Localização\nuntitled = Sem título\nsource = Fonte\ndestination = Destino\nrecurse_directories = Processar recursivamente aos diretórios selecionados\ngo_to = Ir para\nexample = Exemplo\npreview = Visualizar\ncomment = Comentário\nsample_text = Texto de exemplo\nnb_files = %1 arquivo(s)\nnb_folders = %1 diretório(s)\nloading = Carregando...\nthis_operation_cannot_be_undone = Esta operação não poderá ser desfeita\nremove = Remover\ndetails = Detalhes\nwarning = Alerta\nerror = Erro\ngeneric_error = Ocorreu um erro durante a operação\nfolder_does_not_exist = Diretório inexistente ou não disponível\nthis_folder_does_not_exist = Este diretório não existe ou não está disponível: %1\nthis_file_does_not_exist = Este arquivo não existe ou não está disponível: %1\ninvalid_path = Caminho inválido: %1\ndirectory_already_exists = Este diretório já existe\nfile_exists_in_destination = Existe um arquivo com mesmo nome neste caminho\nsource_parent_of_destination = Tentativa de transferir um diretório para um de seus subdiretórios\ncannot_read_file = Não foi possível ler o arquivo %1\ncannot_write_file = Não foi possível escrever o arquivo %1\ncannot_create_folder = Não foi possível criar o diretório %1\ncannot_read_folder = Não foi possível ler o diretório %1\ncannot_delete_file = Não foi possível apagar o arquivo %1\ncannot_delete_folder = Não foi possível apagar o diretório %1\nerror_while_transferring = Erro ao transfer o arquivo %1\nsame_source_destination = Diretório fonte e de destino são os mesmos\nfile_already_exists = %1 já existe, deseja sobrescrevê-lo ?\nwrite_error = Erro de escrita\nread_error = Erro de leitura\nintegrity_check_error = Falha na checagem de integridade: fonte e destino estão diferentes\nstartup_error = Um erro impediu trolCommander de iniciar.\npermissions.read = Leitura\npermissions.write = Gravação\npermissions.executable = Execução\npermissions.group = Grupo\npermissions.other = Outros\npermissions.octal_notation = Notação Octal\naction_categories.all = Todos\naction_categories.navigation = Navegação\naction_categories.selection = Seleção\naction_categories.view = Visualizar\naction_categories.file_operations = Operações com arquivos\naction_categories.windows = Janelas\nTerminal.label = Terminal\nTerminal.tooltip = Terminal\nFindFile.label = Find file\nFindFile.tooltip = Find file\nAddBookmark.label = Adicionar aos favoritos\nAddBookmark.tooltip = Adicionar o diretório atual à lista de favoritos\nBatchRename.label = Multipla renomeação\nEditBookmarks.label = Editar favoritos\nExploreBookmarks.label = Explorar favoritos\nEditCredentials.label = Editar credenciais\nChangeLocation.label = Alterar localização corrente\nChangeDate.label = Alterar data\nChangeDate.tooltip = Alterar data do(s) arquivo(s) selecionado(s)\nChangePermissions.label = Alterar permissões\nChangePermissions.tooltip = Alterar permissões do(s) seguinte(s) arquivo(s)\nCheckForUpdates.label = Checar atualizações\nCompareFolders.label = Comparar diretórios\nConnectToServer.label = Conectar ao servidor\nConnectToServer.tooltip = Conectar ao servidor remoto\nView.label = Ver\nInternalView.label = Ver (interno)\nView.tooltip = Ver arquivo selecionado\nInternalEdit.label = Editar (interno)\nEdit.tooltip = Editar arquivo selecionado\nCopy.tooltip = Copiar arquivos selecionados\nLocalCopy.label = Cópia local\nLocalCopy.tooltip = Copiar arquivo selecionado para o diretório atual\nMove.tooltip = Mover arquivos selecionados\nRename.tooltip = Renomear os arquivos selecionados\nMkdir.label = Criar diretório\nMkdir.tooltip = Criar um diretório no diretório atual\nMkfile.label = Criar arquivo\nMkfile.tooltip = Criar um arquivo no diretório atual\nDelete.tooltip = Apagar arquivos selecionados\nPermanentDelete.label = Remover definitivamente\nPermanentDelete.tooltip = Remover arquivos marcados definitivamente\nRefresh.label = Atualizar\nRefresh.tooltip = Atualizar diretório atual\nCloseWindow.label = Fechar janela\nCloseWindow.tooltip = Fechar esta janela\nCopyFileNames.label = Copiar nome(s)\nCopyFilePaths.label = Copiar caminho(s)\nCopyFilesToClipboard.label = Copiar arquivo(s)\nPasteClipboardFiles.label = Colar arquivo(s)\nEmail.label = Enviar por email\nEmail.tooltip = Enviar arquivos selecionados como anexos de email\nGoBack.label = Voltar\nGoBack.tooltip = Ir para o diretório anterior\nGoForward.label = Seguir\nGoForward.tooltip = Ir para o próximo diretório\nGoToHome.label = Ir para o diretório inicial\nGoToParent.label = Ir para o diretório superior\nGoToParent.tooltip = Ir para o diretório superior\nGoToParentInOtherPanel.label = Exibir o nível superior em outro painel\nGoToParentInBothPanels.label = Exibir o nível superior em ambos os paineis\nGoToRoot.label = Ir à raiz\nSortByName.label = Order por nome\nSortByDate.label = Ordenar por data\nSortBySize.label = Ordenar pelo tamanho\nSortByExtension.label = Ordenar pelo extensão\nSortByPermissions.label = Ordenar pela permissão\nSortByOwner.label = Ordenar pelo proprietário\nSortByGroup.label = Ordenar pelo grupo\nMarkGroup.label = Marcar arquivos\nMarkGroup.tooltip = Marcar um grupo de arquivos\nUnmarkGroup.label = Desmarcar arquivos\nUnmarkGroup.tooltip = Desmarcar um grupo de arquivos\nMarkAll.label = Marcar tudo\nUnmarkAll.label = Desmarcar tudo\nMarkSelectedFile.label = Marcar/Desmarcar\nMarkSelectedFile.tooltip = Marcar/Desmarcar arquivos selecionados\nMarkNextBlock.label = Marque um próximo bloco\nMarkPreviousBlock.label = Marque um bloco anterior\nMarkNextRow.label = Marque uma próxima linha\nMarkPreviousRow.label = Marque uma linha anterior\nMarkNextPage.label = Marcar página abaixo\nMarkPreviousPage.label = Marcar página acima\nMarkToFirstRow.label = Marcar arquivos até o início\nMarkToLastRow.label = Marcar arquivos até o final\nMarkExtension.label = Marcar extensão\nInvertSelection.label = Inverter seleção\nSwapFolders.label = Trocar diretórios\nSwapFolders.tooltip = Trocar diretórios\nSetSameFolder.label = Marcar mesmo diretório\nSetSameFolder.tooltip = Abilitar mesmo diretório em ambos os lados\nNewWindow.label = Nova janela\nNewWindow.tooltip = Abrir uma nova janela\nOpen.label = Abrir\nOpen.tooltip = Abrir diretório / Abrir arquivo / Executar\nOpenNatively.label = Abrir com programa padrão\nOpenNatively.tooltip = Executar o arquivo com o programa padrão\nOpenInOtherPanel.label = Abrir em um novo painel\nOpenInBothPanels.label = Abrir em ambos os paineis\nRevealInDesktop.label = Mostrar em %1\nRunCommand.label = Executar comando\nRunCommand.tooltip = Executar comando no diretório atual\nPack.tooltip = Compactar arquivos selecionados\nUnpack.tooltip = Descompactar arquivos selecionados\nShowFileProperties.label = Propriedades\nShowFileProperties.tooltip = Exibir propriedades dos arquivos selecionados\nShowPreferences.label = Preferências\nShowPreferences.tooltip = Configurar trolCommander\nShowServerConnections.label = Exibir conexões abertas\nQuit.label = Sair\nReverseSortOrder.label = Inverter ordem\nToggleAutoSize.label = Dimensionar colunas automaticamente\nStop.label = Parar mudança de diretórios\nToggleColumn.show = Exibir coluna %1\nToggleColumn.hide = Esconder coluna %1\nToggleCommandBar.show = Exibir barra de comandos\nToggleCommandBar.hide = Esconder barra de comandos\nToggleToolBar.show = Exibir barra de ferramentas\nToggleToolBar.hide = Esconder barra de ferramentas\nCustomizeCommandBar.label = Customizar barra de comando\nToggleStatusBar.show = Exibir barra de status\nToggleStatusBar.hide = Esconder barra de status\nToggleShowFoldersFirst.label = Exibir diretórios primeiro\nToggleTree.label = Visualização em árvore\nPopupLeftDriveButton.label = Mudar diretório esquerdo\nPopupRightDriveButton.label = Mudar diretório direito\nRecallPreviousWindow.label = Recarregar janela anterior\nRecallNextWindow.label = Recarregar próxima janela\nRecallWindow.label = Recarregar a janela #%1\nBringAllToFront.label = Trazer tudo para frete\nSwitchActiveTable.label = Alternar entre os paneis esquerdo e direito\nSelectNextBlock.label = Pular para próximo bloco\nSelectPreviousBlock.label = Pular para bloco anterior\nSelectNextPage.label = Pular para próxima página\nSelectPreviousPage.label = Pular para página anterior\nSelectNextRow.label = Pular para próxima linha\nSelectPreviousRow.label = Pular para linha anterior\nSelectFirstRow.label = Selecionar primeiro arquivo do diretório corrente\nSelectLastRow.label = Selecionar último arquivo do diretório corrente\nSplitEqually.label = Dividir igualmente\nSplitVertically.label = Dividir verticalmente\nSplitHorizontally.label = Dividir horizontalmente\nShowKeyboardShortcuts.label = Atalhos de teclado\nGoToWebsite.label = Ir para website\nGoToForums.label = Ir para fóruns\nReportBug.label = Reportar uma falha\nDonate.label = Fazer uma doação\nShowAbout.label = Sobre trolCommander\nOpenTrash.label = Abrir lixeira\nEmptyTrash.label = Esvaziar lixeira\nCalculateChecksum.label = Calcular checksum\nMaximizeWindow.label = Maximizar\nMaximizeWindow.label.mac_os_x = Zoom\nMinimizeWindow.label = Minimizar\nGoToDocumentation.label = Documentação online\nShowParentFoldersQL.label = Diretório superior\nShowRecentLocationsQL.label = Locais recentes\nShowRecentExecutedFilesQL.label = Arquivos executados recentemente\nSplitFile.tooltip = Dividir um arquivo em várias partes\nCombineFiles.tooltip = Recriar arquivo dividido em várias partes\nShowDebugConsole.label = Console de debug\nFocusPrevious.label = Focar componente anterior\nFocusNext.label = Focar próximo componente\nfile_menu = Arquivo\nfile_menu.open_with = Abrir com\nmark_menu = Marcar\nview_menu = Ver\nview_menu.show_hide_columns = Exibir/Ocultar colunas\ngo_menu = Ir\nbookmarks_menu = Favoritos\nbookmarks_menu.no_bookmark = Nenhum favorito definido\nquick_lists_menu = Quick lists\nwindow_menu = Janela\nhelp_menu = Ajuda\nstatus_bar.selected_files = %1 de %2 selecionados \nstatus_bar.connecting_to_folder = Conectando ao diretório, tecle ESC para cancelar.\nstatus_bar.volume_free = Livre: %1\nstatus_bar.volume_capacity = Capacidade: %1\nshortcuts_panel.title = Atalhos\nshortcuts_panel.restore_defaults = Restaurar\nshortcuts_panel.show = Exibir\nshortcuts_panel.default_message = Aperte Enter ou dê dois cliques sobre o atalho que você deseja editar\nshortcuts_table.action_description = Descrição da ação\nshortcuts_table.shortcut = Atalho\nshortcuts_table.alternate_shortcut = Atalho secundário\nshortcuts_table.type_in_a_shortcut = Digite um atalho\ncommand_bar_dialog.help = Arraste os butões para customizar a barra de comandos\ntable.folder_access_error_title = Erro ao acessar diretório\ntable.folder_access_error = Não foi possível ler conteúdo do diretório\ntable.download_or_browse = Você gostaria de explorar ou baixar este arquivo ?\nversion_dialog.no_new_version_title = Nenhuma nova versão disponível\nversion_dialog.no_new_version = Parabéns, você já possue a última versão.\nversion_dialog.new_version_title = Nova versão disponível\nversion_dialog.new_version = Uma nova versão do trolCommander está disponível.\nversion_dialog.new_version_url = Uma nova versão do trolCommander está disponível em %1.\nversion_dialog.not_available_title = Servidor não disponível\nversion_dialog.not_available = Não foi possível obter informação de versão no servidor.\nversion_dialog.install_and_restart = Instalar e reiniciar\nversion_dialog.preparing_for_update = Preparando atualização...\nquit_dialog.title = Sair do trolCommander\nquit_dialog.desc = Você tem %1 janela(s) aberta(s). Deseja realmente sair?\nquit_dialog.show_next_time = Exibir na próxima vez\ndestination_dialog.file_exists_action = Ação padrão quando o arquivo existir\ndestination_dialog.verify_integrity = Verificando integridade dos dados\ndestination_dialog.skip_errors = Ignore erros\nfile_collision_dialog.title = Colisão de arquivo\nrename_dialog.new_name = Novo nome\ncopy_dialog.destination = Copiar arquivo(s) selecionado(s) para\ncopy_dialog.error_title = Erro ao copiar\ncopy_dialog.copying = Copiando arquivos\ncopy_dialog.copying_file = Copiando %1\npack_dialog.packing = Compactando arquivos\npack_dialog.packing_file = Compactando %1\npack_dialog.error_title = Erro na compactação\npack_dialog_description = Adicionar arquivos selecionados ao\npack_dialog.archive_format = Formato de arquivo\nunpack_dialog.destination = Descompactar arquivo(s) selecionado(s) para\nunpack_dialog.error_title = Erro ao descompactar\nunpack_dialog.unpacking = Descompactando arquivos\nunpack_dialog.unpacking_file = Descompactando %1\noptimizing_archive = Otimizando arquivo %1\nerror_while_optimizing_archive = Erro ao otimizar arquivo %1\nmove_dialog.move_description = Mover para\nmove_dialog.error_title = Erro ao mover\nmove_dialog.moving = Movendo arquivos\nmove_dialog.moving_file = Movendo %1\ndownload_dialog.description = Baixar arquivo para\ndownload_dialog.error_title = Erro ao baixar\ndownload_dialog.downloading = Baixando\ndownload_dialog.downloading_file = Baixando %1\nmkfile_dialog.allocate_space = Tamanho\ndelete_dialog.permanently_delete.confirmation = Remover permanentemente os arquivo(s) selecionado(s) ?\ndelete_dialog.move_to_trash.confirmation = Remover o(s) arquivo(s) selecionado(s) ?\ndelete_dialog.move_to_trash.confirmation_details = Arquivos serão movidos para a lixeira.\ndelete_dialog.move_to_trash.option = Mover para lixeira\ndelete_dialog.move_to_trash.failed = Um ou mais arquivos não puderam ser movidos para a lixeira.\ndelete_dialog.deleting = Removendo\ndelete_dialog.error_title = Erro ao remover\ndelete.deleting_file = Removendo %1\nemail_dialog.prefs_not_set_title = Conta de email não configurada\nemail_dialog.prefs_not_set = Você deve primeiramente configurar sua conta de email.\nemail_dialog.from = De\nemail_dialog.to = Para\nemail_dialog.subject = Assunto\nemail_dialog.send = Enviar\nemail_dialog.error_title = Erro ao enviar arquivos\nemail_dialog.read_error = Não foi possível ler os arquivos no subdiretório\nemail_dialog.sending = Enviando arquivos\nemail.sending_file = Enviando %1\nemail.connecting_to_server = Conectando à %1\nemail.server_unavailable = Não foi possível contactar o servidor de email %1, verifique as configurações de email ou tente novamente mais tarde.\nemail.connection_closed = Conexão encerrada pelo servidor, email não enviado.\nemail.goodbye_failed = Erro ao encerrar a conexão, o email talvez não tenha sido enviado.\nemail.send_file_error = Não foi possível enviar o arquivo %1, email não enviado.\nsplit_file_dialog.error_title = Erro ao dividir arquivo\nsplit_file_dialog.file_to_split = Arquivo a ser dividido\nsplit_file_dialog.target_directory = Diretório de destino\nsplit_file_dialog.part_size = Tamanho de uma parte\nsplit_file_dialog.parts = Número de partes\nsplit_file_dialog.generate_CRC = Gerar arquivo CRC\nsplit_file_dialog.max_parts = Número máximo de partes permitidas é %1\nsplit_file_dialog.auto = Automático\nsplit_file_dialog.insert_new_media = Insira nova mídia\ncombine_files_dialog.error_title = Erro ao combinar arquivo\ncombine_files_job.no_crc_file = Arquivo combinado com sucesso. Sem arquivo CRC.\ncombine_files_job.crc_read_error = Erro ao ler arquivo CRC.\ncombine_files_job.crc_check_failed = CRC erro de checagem: esperado %2, encontrado %1\ncombine_files_job.crc_ok = Combinação realizada com sucesso. CRC verificado.\nfile_selection_dialog.mark = Marcar\nfile_selection_dialog.unmark = Desmarcar\nfile_selection_dialog.mark_description = Marcar arquivos com o nome\nfile_selection_dialog.unmark_description = Desmarcar arquivos com o nome\nfile_selection_dialog.case_sensitive = Diferenciar maiúsculas de minúsculas\nfile_selection_dialog.include_folders = Incluir diretórios\nprogress_dialog.starting = Iniciando transferência...\nprogress_dialog.transferred = Transferido %1 a %2\nprogress_dialog.elapsed_time = Tempo decorrido\nprogress_dialog.advanced = Avançado\nprogress_dialog.current_speed = Velocidade atual\nprogress_dialog.limit_speed = Limitar a velocidade\nprogress_dialog.close_when_finished = Fechar a janela quando finalizado\nprogress_dialog.processing_files = Processando arquivos\nprogress_dialog.processing_file = Processando %1\nprogress_dialog.verifying_file = Verificando %1\nprogress_dialog.job_finished = Trabalho finalizado\nprogress_dialog.job_error = Erro\nproperties_dialog.file_properties = Propriedades de %1\nproperties_dialog.contents = Conteúdo\nproperties_dialog.calculating = Calculando...\ncalculate_checksum_dialog.checksum_algorithm = Algorítimo de checksum\ncalculate_checksum_dialog.temporary_file = Arquivo temporário\nchange_date_dialog.now = Agora\nchange_date_dialog.specific_date = Data específica\nrun_dialog.run_command_description = Executar no diretório corrente\nrun_dialog.run_in_home_description = Executar no diretório inicial\nrun_dialog.command_output = Saída do camando\nrun_dialog.run = Executar\nrun_dialog.clear_history = Limpar histórico\nserver_connect_dialog.server_type = Tipo de conexão\nserver_connect_dialog.server = Servidor\nserver_connect_dialog.share = Compartilhar\nserver_connect_dialog.domain = Domínio\nserver_connect_dialog.username = Usuário\nserver_connect_dialog.initial_dir = Diretório inicial\nserver_connect_dialog.port = Porta\nserver_connect_dialog.server_url = Endereço do servidor\nserver_connect_dialog.http_url = Endereço do website\nserver_connect_dialog.connect = Conectar\nserver_connect_dialog.protocol = Protocolo\nserver_connect_dialog.nfs_version = Versão NFS\nserver_connect_dialog.private_key = Chave privada\nserver_connect_dialog.passphrase = Frase secreta\nftp_connect.passive_mode = Abilitando modo passivo\nftp_connect.anonymous_user = Usuário anônimo\nftp_connect.nb_connection_retries = Numero de tentativas de conexão\nftp_connect.retry_delay = Tempo de espera entre as tentativas (segundos)\nhttp_connect.basic_authentication = HTTP Autenticação básica (opcional)\nserver_connections_dialog.disconnect = Desconectar\nserver_connections_dialog.connection_busy = Ocupado\nserver_connections_dialog.connection_idle = Inativo\nbonjour.bonjour_services = Serviço Bonjour\nbonjour.no_service_discovered = Nenhum serviço encontrado\nbonjour.bonjour_disabled = Bonjour desabilitado\nauth_dialog.title = Autenticação\nauth_dialog.desc = Por favor, insira o usuário e a senha\nauth_dialog.server = Servidor\nauth_dialog.store_credentials = Gravar usuário e senha (criptografia baixa)\nauth_dialog.connect_as = Conectar como\nauth_dialog.authentication_failed = Falha na autenticação\nsortable_list.move_up = Subir\nsortable_list.move_down = Descer\nadd_bookmark_dialog.add = Adicionar\nedit_bookmarks_dialog.new = Novo\nfile_viewer.view_error_title = Erro de visualização\nfile_viewer.view_error = Não possível visualizar arquivo.\nfile_viewer.file_menu = Arquivo\nfile_viewer.close = Fechar\nfile_viewer.large_file_warning = Este arquivo parece ser muito grande para esta operação.\nfile_viewer.open_anyway = Ignorar e abrir\ntext_viewer.edit = Editar\ntext_viewer.copy = Copiar\ntext_viewer.select_all = Selecionar tudo\ntext_viewer.find = Procurar\ntext_viewer.find_next = Procurar próxima ocorrência\ntext_viewer.find_previous = Procurar ocorrência anterior\ntext_viewer.binary_file_warning = Parece ser um arquivo binário\nimage_viewer.controls_menu = Controles\nimage_viewer.zoom_in = Mais zoom\nimage_viewer.zoom_out = Menos zoom\nfile_editor.edit_error_title = Erro ao editar\nfile_editor.edit_error = Não foi possível editar o arquivo.\nfile_editor.save = Salvar\nfile_editor.save_as = Salvar como...\nfile_editor.save_warning = Salvar mudanças feitas ao arquivo antes de fechar ?\nfile_editor.cannot_write = Não foi possível escrever o arquivo.\ntext_editor.cut = Cortar\ntext_editor.paste = Colar\nshortcuts_dialog.quick_search.start_search = Tecle qualquer caractere para iniciar uma busca rápida\nshortcuts_dialog.quick_search.cancel_search = Cancelar busca rápida\nshortcuts_dialog.quick_search.remove_last_char = Remover o último caractere da cadeia utilizada na busca rápida\nshortcuts_dialog.quick_search.jump_to_previous = Pular para o resultado anterior da busca rápida\nshortcuts_dialog.quick_search.jump_to_next = Pular para o próximo resultado da busca rápida\nshortcuts_dialog.quick_search.mark_jump_next = Marcar/Desmarcar arquivo corrente e pular para o próximo resultado da busca\ntheme_editor.title = Editor de tema\ntheme_editor.folder_tab = Painel de diretório\ntheme_editor.shell_tab = Shell\ntheme_editor.shell_history_tab = Histórico do Shell\ntheme_editor.statusbar_tab = Barra de status\ntheme_editor.free_space = Espaço livre\ntheme_editor.free_space.ok = OK\ntheme_editor.free_space.warning = Alerta\ntheme_editor.free_space.critical = Crítico\ntheme_editor.locationbar_tab = Barra de endereço\ntheme_editor.editor_tab = Editor de arquivos\ntheme_editor.font = Fonte\ntheme_editor.active_panel = Ativo\ntheme_editor.inactive_panel = Inativo\ntheme_editor.general = Geral\ntheme_editor.could_not_save_theme = Não foi possível salvar o tema %1\ntheme_editor.border = Borda\ntheme_editor.background = Fundo\ntheme_editor.alternate_background = Cor de fundo alternativa\ntheme_editor.unfocused_background = Fundo (sem o foco)\ntheme_editor.quick_search.unmatched_file = Arquivo não encontrado\ntheme_editor.text = Texto\ntheme_editor.progress = Progresso\ntheme_editor.normal = Normal\ntheme_editor.normal_unfocused = Normal (sem o foco)\ntheme_editor.selected = Selecionado\ntheme_editor.selected_unfocused = Selecionado (sem o foco)\ntheme_editor.color = Cor\ntheme_editor.colors = Cores\ntheme_editor.plain_file = Arquivo normal\ntheme_editor.marked_file = Arquivo marcado\ntheme_editor.hidden_file = Arquivo oculto\ntheme_editor.folder = Diretório\ntheme_editor.archive_file = Arquivar\ntheme_editor.symbolic_link = Link simbólico\ntheme_editor.header = Cabeçalho\ntheme_editor.item = Item\ncommand_bar_customize_dialog.available_actions = Ações disponíveis\ncommand_bar_customize_dialog.modifier = Modificador\nprefs_dialog.title = Preferências\nprefs_dialog.general_tab = Geral\nprefs_dialog.day = Dia\nprefs_dialog.month = Mês\nprefs_dialog.year = Ano\nprefs_dialog.language = Idioma (requer reiniciar programa)\nprefs_dialog.date_time = Formato da data e hora\nprefs_dialog.time = Hora\nprefs_dialog.date = Data\nprefs_dialog.date_separator = Separador\nprefs_dialog.time_12_hour = Formato 12 horas\nprefs_dialog.time_24_hour = Formato 24 horas\nprefs_dialog.show_seconds = Exibir segundos\nprefs_dialog.show_century = Exibir centésimos\nprefs_dialog.check_for_updates_on_startup = Verificar por atualizações quando iniciar\nprefs_dialog.show_splash_screen = Show splash screen\nprefs_dialog.folders_tab = Diretórios\nprefs_dialog.startup_folders = Diretórios de início\nprefs_dialog.left_folder = Diretório esquerdo\nprefs_dialog.right_folder = Diretório direito\nprefs_dialog.last_folder = Último diretório visitado\nprefs_dialog.custom_folder = Diretório personalizado\nprefs_dialog.show_hidden_files = Exibir arquivos ocultos\nprefs_dialog.show_ds_store_files = Exibir arquivos .DS_Store\nprefs_dialog.show_system_folders = Exibir diretórios de sistema\nprefs_dialog.compact_file_size = Arendondar o tamanho dos arquivos exibidos\nprefs_dialog.follow_symlinks_when_cd = Follow symlinks when changing current directory\nprefs_dialog.appearance_tab = Aparência\nprefs_dialog.look_and_feel = Look & Feel\nprefs_dialog.icons_size = Tamanho dos ícones\nprefs_dialog.toolbar_icons = Barra de ferramentas\nprefs_dialog.command_bar_icons = Barra de comandos\nprefs_dialog.file_icons = Tipos de arquivos\nprefs_dialog.use_system_file_icons = Utilisar arquivos de ícones do sistema\nprefs_dialog.use_system_file_icons.always = Sempre\nprefs_dialog.use_system_file_icons.never = Nunca\nprefs_dialog.use_system_file_icons.applications = Somente para aplicações\nprefs_dialog.edit_current_theme = Editar tema atual...\nprefs_dialog.themes = Temas\nprefs_dialog.import_theme = Importar tema\nprefs_dialog.import_look_and_feel = Importar aparência (look & feel)\nprefs_dialog.no_look_and_feel = Nenhuma aparência (look & feel) foi encontrada.\nprefs_dialog.error_in_import = Erro ao importar o tema %1\nprefs_dialog.cannot_read_theme = Não foi possível carregar o tema a partir do arquivo %1\nprefs_dialog.export_theme = Exportar %1\nprefs_dialog.import = Importar\nprefs_dialog.export = Exportar\nprefs_dialog.theme_type = Tipo: %1\nprefs_dialog.delete_theme = Remover permanentemente o tema %1 ?\nprefs_dialog.delete_look_and_feel = Remover completamente a aparência (look & feel) %1 ?\nprefs_dialog.rename_failed = Falha ao renomear tema %1\nprefs_dialog.xml_file = Arquivo XML\nprefs_dialog.jar_file = Arquivo JAR\nprefs_dialog.mail_tab = Email\nprefs_dialog.mail_settings = Parâmetros de saída de email\nprefs_dialog.mail_name = Seu nome\nprefs_dialog.mail_address = Seu endereço de email\nprefs_dialog.mail_server = Servidor SMTP\nprefs_dialog.misc_tab = Miscelânia\nprefs_dialog.use_brushed_metal = Usar aparência de 'brushed metal' (requer reiniciar)\nprefs_dialog.confirm_on_quit = Pedir confirmação ao sair\nprefs_dialog.default_shell = Usar prompt de comando padrão do sistema\nprefs_dialog.custom_shell = Usar prompt de comando personalizado\nprefs_dialog.shell_encoding = Codificação do Shell\nprefs_dialog.auto_detect_shell_encoding = Auto-detectar\nprefs_dialog.enable_bonjour_discovery = Abilitar Bonjour service discovery\nprefs_dialog.enable_system_notifications = Ativar notificações do sistema\ndebug_console_dialog.level = Nível\nunit.byte = byte\nunit.bytes = bytes\nunit.bytes_short = b\nunit.kb = KB\nunit.mb = MB\nunit.gb = GB\nunit.tb = TB\nunit.speed = %1/s\nduration.seconds = %1s\nduration.minutes = %1m\nduration.hours = %1h\nduration.days = %1d\nduration.months = %1m\nduration.years = %1a\ntheme.custom_theme = Tema personalizado\ntheme.custom = Customizado\ntheme.built_in = Pré-instalado\ntheme.add_on = Complementos\ntheme.current = Atual\ntheme_could_not_be_loaded = Ocorreu um erro ao tentar carregar este tema.\nsetup.title = Bem-vindo ao trolCommander\nsetup.intro = Por favor, selecione como o trolCommander deve se comportar.\nsetup.look_and_feel = Selecione seu Look & feel\nsetup.theme = Selecione seu tema\nfont_chooser.font_size = Tamanho\nfont_chooser.font_bold = Negrito\nfont_chooser.font_italic = Itálico\ncolor_chooser.red = Vermelho\ncolor_chooser.green = Verde\ncolor_chooser.blue = Azul\ncolor_chooser.hue = Tonalidade\ncolor_chooser.brightness = Brilho\ncolor_chooser.swatches = Swatches\ncolor_chooser.saturation = Saturação\ncolor_chooser.rgb = RGB\ncolor_chooser.hsb = HSB\ncolor_chooser.recent = Recente\ncolor_chooser.alpha = Transparência alpha\ncolor_chooser.title = Escolha uma cor\nbatch_rename_dialog.mask = Modelo de renomeação\nbatch_rename_dialog.search_replace = Pesquisar e Substituir\nbatch_rename_dialog.search_for = Pesquisar por\nbatch_rename_dialog.replace_with = Substituir por\nbatch_rename_dialog.counter = Contador\nbatch_rename_dialog.start_at = Começar\nbatch_rename_dialog.step_by = Incrementar\nbatch_rename_dialog.format = Formatar\nbatch_rename_dialog.upper_lower_case = Maiúsculo/Minúsculo\nbatch_rename_dialog.no_change = Inalterado\nbatch_rename_dialog.lower_case = minúsculo\nbatch_rename_dialog.upper_case = MAIÚSCULO\nbatch_rename_dialog.first_upper = Primeira letra em maiúsculo\nbatch_rename_dialog.word = Primeira de cada palavra\nbatch_rename_dialog.old_name = Nome antigo\nbatch_rename_dialog.new_name = Novo nome\nbatch_rename_dialog.block_name = Preservar\nbatch_rename_dialog.range = Intervalo\nbatch_rename_dialog.proceed_renaming = %1 arquivos de %2 serão renomeados. Continuar?\nbatch_rename_dialog.duplicate_names = Nomes duplicados!\nparent_folders_quick_list.empty_message = Localização atual não possue superior\nrecent_locations_quick_list.empty_message = Nenhum local recente\nrecent_executed_files_quick_list.empty_message = Nenhum arquivo executado recentemente\n#move_dialog.rename_description = Renomear arquivo para\n#theme_editor.shell_font = Fonte do shell\n#theme_editor.history_font = Fonte do histórico\n#theme_editor.shell_colors = Cor do prompt de comandos (terminal)\n#theme_editor.history_colors = Cores do histórico\n#ToggleHiddenFiles.hide = Esconder arquivos ocultos\n#auth_dialog.error_was = O erro foi: %1\n#table.hide_column = Ocultar colunas\n#delete.symlink_warning_title = Link simbólico encontrado\n#delete.symlink_warning = Este arquivo parece ser um link simbólico:\\n\\n Arquivo: %1\\n Linkado ao: %2\\n\\nRemover somente o link simbólico or\\nSeguir link simbólico e remover diretório (cuidado) ?\n#delete.delete_link_only = Remover link\n#delete.delete_linked_folder = Remover diretório\n#Unpack.label = Descompactar arquivos\n#Pack.label = Compactar arquivos\n"
  },
  {
    "path": "src/main/resources/dictionary_ro_RO.properties",
    "content": "ok = OK\nyes = Da\nno = Nu\ncancel = Renunță\nedit = Editează\nclose = Închide\nreset = Reinițializează\nrename = Redenumește\napply = Aplică\nchange = Schimbă\nsave = Salvează\ndont_save = Nu salva\nreplace = Înlocuiește\ndont_replace = Nu înlocui\ndelete = Șterge\nskip = Treci mai departe\nskip_all = Sari peste toate\nretry = Încearca din nou\nresume = Continuă\noverwrite = Suprascrie\noverwrite_if_older = Suprascrie dacă e mai vechi\nduplicate = Duplică\napply_to_all = Aplică la toate\ncopy = Copiază\nmove = Mută\npack = Arhivează\nunpack = Dearhivează\ndownload = Descarcă\nsplit = Împarte\ncombine = Combină\nbrowse = Navighează\nask = Întreabă\nstop = Stop\npause = Pauză\nquick_search = Căutare rapidă\nfile_manager = Manager de fișiere\ncreate = Crează\ncreating_file = Creez %1\nchoose = Alege\ncustomize = Personalizează\nchoose_folder = Alege un director\nlogin = Login\npassword = Parolă\nuser = Utilizator\nencoding = Codificare\npreferred_encodings = Codificări preferate\nlicense = Licență\nname = Nume\nsize = Dimensiune\ndate = Dată\nextension = Extensie\npermissions = Permisiuni\nowner = Proprietar\ngroup = Grup\nlocation = Locație\nuntitled = Fara nume\nsource = Sursă\ndestination = Destinație\nrecurse_directories = Prelucrează directoarele recursiv\ngo_to = Mergi la\nexample = Exemplu\npreview = Previzualizare\ncomment = Comentarii\nsample_text = Text exemplu\nnb_files = %1 fișier(e)\nnb_folders = %1 directo(a)r(e)\nloading = Încărcare în curs...\nthis_operation_cannot_be_undone = Această operațiune este definitivă și irevocabilă.\nremove = Elimină\ndetails = Detalii\nwarning = Avertisment\nerror = Eroare\ngeneric_error = O eroare a apărut in timp ce executam operațiunea cerută\nfolder_does_not_exist = Acest director nu există sau nu este disponibil.\nthis_folder_does_not_exist = Acest director nu exista sau nu este disponibil: %1\nthis_file_does_not_exist = Acest fișier nu există sau nu este disponibil: %1\ninvalid_path = Cale greșită: %1\ndirectory_already_exists = Directorul %1 exista deja.\nfile_exists_in_destination = Fișierul există deja la destinație\nsource_parent_of_destination = Încercare de a transfera un director într-un subdirector al său\ncannot_read_file = Nu pot citi fișierul %1\ncannot_write_file = Nu pot scrie fișierul %1\ncannot_create_folder = Nu pot crea directorul %1\ncannot_read_folder = Nu pot citi conținutul directorului %1\ncannot_delete_file = Nu pot șterge fișierul %1\ncannot_delete_folder = Nu pot șterge directorul %1\nerror_while_transferring = Eroare la transferul fișierului %1\nsame_source_destination = Directoarele sursă și destinație coincid.\nfile_already_exists = %1 există deja, vreți să îl înlocuiți?\nwrite_error = Eroare la scriere\nread_error = Eroare la citire\nintegrity_check_error = Controlul integrității a eșuat: sursa și destinația sunt diferite\nstartup_error = O eroare a prevenit trolCommander să pornească.\npermissions.read = Citire\npermissions.write = Scriere\npermissions.executable = Executare\npermissions.group = Grup\npermissions.other = Altele\npermissions.octal_notation = Notație în baza opt\naction_categories.all = Toate\naction_categories.navigation = Navigare\naction_categories.selection = Selectare\naction_categories.view = Vizualizare\naction_categories.file_operations = Operații cu fișiere\naction_categories.windows = Ferestre\nTerminal.label = Terminal\nTerminal.tooltip = Terminal\nFindFile.label = Find file\nFindFile.tooltip = Find file\nAddBookmark.label = Adaugă la favorite\nAddBookmark.tooltip = Adauga directorul curent la favorite\nBatchRename.label = Redenumire multiplă\nEditBookmarks.label = Modifică favoritele\nExploreBookmarks.label = Explorează favoritele\nEditCredentials.label = Schimbă identitatea\nChangeLocation.label = Schimbă calea actuala\nChangeDate.label = Schimbă data\nChangeDate.tooltip = Schimbă data fișierelor selectate\nChangePermissions.label = Schimbă permisiuni\nChangePermissions.tooltip = Schimbă permisiunile fișierelor selectate\nCheckForUpdates.label = Actualizează programul\nCompareFolders.label = Compară directoarele\nConnectToServer.label = Conectare la un server\nConnectToServer.tooltip = Conectare la un server la distanță\nView.label = Vezi\nInternalView.label = Vizualizează (intern)\nView.tooltip = Vezi fișierul selectat\nInternalEdit.label = Editează (internal)\nEdit.tooltip = Editează fișierul selectat\nCopy.tooltip = Copiază fișierele selectate\nLocalCopy.label = Copiază local\nLocalCopy.tooltip = Copiază fișierele selectate în directorul curent\nMove.tooltip = Mută fișierele selectate\nRename.tooltip = Redenumește fișierul selectat\nMkdir.label = Director nou\nMkdir.tooltip = Crează un director nou în directorul curent\nMkfile.label = Fișier nou\nMkfile.tooltip = Crează un fișier nou în directorul curent\nDelete.tooltip = Șterge fișierele selectate\nPermanentDelete.label = Șterge definitiv\nPermanentDelete.tooltip = Șterge definitiv fișierele selectate fără a folosi coșul de gunoi\nRefresh.label = Actualizează\nRefresh.tooltip = Actualizează directorul curent\nCloseWindow.label = Închide fereastră\nCloseWindow.tooltip = Închide această fereastră\nCopyFileNames.label = Copiază nume\nCopyFilePaths.label = Copiază cale/căi\nCopyFilesToClipboard.label = Copiază fișier(e)\nPasteClipboardFiles.label = Lipește fișier(e)\nEmail.label = Trimite prin email\nEmail.tooltip = Trimite fișierele selectate prin email\nGoBack.label = Înapoi\nGoBack.tooltip = Mergi înapoi în directorul anterior\nGoForward.label = Înainte\nGoForward.tooltip = Mergi în directorul următor\nGoToHome.label = Mergi în directorul utilizatorului\nGoToParent.label = Părinte\nGoToParent.tooltip = Mergi în directorul părinte\nGoToParentInOtherPanel.label = Mergi în directorul părinte în cealaltă fereastră\nGoToParentInBothPanels.label = Mergi în directorul părinte în ambele ferestre\nGoToRoot.label = Mergi în directorul rădăcină\nSortByName.label = Sortează după nume\nSortByDate.label = Sortează după dată\nSortBySize.label = Sortează după dimensiune\nSortByExtension.label = Sortează după extensie\nSortByPermissions.label = Sortează după permisiuni\nSortByOwner.label = Sortează după proprietar\nSortByGroup.label = Sortează după grup\nMarkGroup.label = Selectează fișiere\nMarkGroup.tooltip = Selectează un grup de fișiere\nUnmarkGroup.label = Deselectează fișiere\nUnmarkGroup.tooltip = Deselectează un grup de fișiere\nMarkAll.label = Selecteză tot\nUnmarkAll.label = Deselectează tot\nMarkSelectedFile.label = Selectează/Deselectează\nMarkSelectedFile.tooltip = Selectează/Deselectează fișier\nMarkNextBlock.label = Selectează un bloc în jos\nMarkPreviousBlock.label = Selectează un bloc în sus\nMarkNextRow.label = Selectează un rând în jos\nMarkPreviousRow.label = Selectează un rând în sus\nMarkNextPage.label = Selectează fișierele din pagina următoare\nMarkPreviousPage.label = Selectează fișierele din pagina anterioară\nMarkToFirstRow.label = Selectează fișierele până la început\nMarkToLastRow.label = Selectează fișierele până la sfârșit\nMarkExtension.label = Selectează aceeași extensie\nInvertSelection.label = Inversează selectarea\nSwapFolders.label = Interschimbă ferestrele\nSwapFolders.tooltip = Interschimbă fereastra stângă și cea dreaptă\nSetSameFolder.label = Alege același director\nSetSameFolder.tooltip = Alege același director pentru fereastra stângă și dreaptă\nNewWindow.label = Fereastră nouă\nNewWindow.tooltip = Deschide o fereastră nouă\nOpen.label = Deschide\nOpen.tooltip = Deschide director / Deschide arhivă / Execută\nOpenNatively.label = Deschide cu aplicația asociată\nOpenNatively.tooltip = Deschide fișierul selectat folosind aplicația asociată\nOpenInOtherPanel.label = Deschide în cealaltă fereastră\nOpenInBothPanels.label = Deschide în ambele ferestre\nRevealInDesktop.label = Arată în %1\nRunCommand.label = Execută comandă\nRunCommand.tooltip = Execută o comandă în directorul curent\nPack.tooltip = Arhivează fișierele selectate\nUnpack.tooltip = Dearhivează fișierele arhivate selectate\nShowFileProperties.label = Proprietăți\nShowFileProperties.tooltip = Afișează proprietățile fișierelor selectate\nShowPreferences.label = Preferințe\nShowPreferences.tooltip = Configurează trolCommander\nShowServerConnections.label = Afișează conexiunile deschise\nQuit.label = Închide\nReverseSortOrder.label = Inversează ordinea\nToggleAutoSize.label = Dimensiune automată la coloane\nStop.label = Oprește schimbarea de director\nToggleColumn.show = Afișează coloana cu %1\nToggleColumn.hide = Ascunde coloana cu %1\nToggleCommandBar.show = Arată bara de comenzi\nToggleCommandBar.hide = Ascunde bara de comenzi\nToggleToolBar.show = Arată bara de unelte\nToggleToolBar.hide = Ascunde bara de unelte\nCustomizeCommandBar.label = Personalizează bara de comande\nToggleStatusBar.show = Arată bara de stare\nToggleStatusBar.hide = Ascunde bara de stare\nToggleShowFoldersFirst.label = Arată directoarele la început\nToggleTree.label = Arată arbore directoare\nPopupLeftDriveButton.label = Schimbă directorul din stânga\nPopupRightDriveButton.label = Schimbă directorul din dreapta\nRecallPreviousWindow.label = Schimbă la fereastra anterioară\nRecallNextWindow.label = Schimbă la fereastra următoare\nRecallWindow.label = Schimbă la fereastra #%1\nBringAllToFront.label = Toate ferestrele în prim plan\nSwitchActiveTable.label = Interschimbă fereastra stângă și cea dreaptă\nSelectNextBlock.label = Coboară un bloc\nSelectPreviousBlock.label = Urcă un bloc\nSelectNextPage.label = Coboară o pagină\nSelectPreviousPage.label = Urcă o pagină\nSelectNextRow.label = Coboară un rând\nSelectPreviousRow.label = Urcă un rând\nSelectFirstRow.label = Selectează primul fișier din directorul curent\nSelectLastRow.label = Selectează ultimul fișier din directorul curent\nSplitEqually.label = Împarte în mod egal\nSplitVertically.label = Împarte vertical\nSplitHorizontally.label = Împarte orizontal\nShowKeyboardShortcuts.label = Combinații de taste\nGoToWebsite.label = Vizitează situl Web\nGoToForums.label = Vizitează forumul\nReportBug.label = Raportează un bug\nDonate.label = Fă o donație\nShowAbout.label = Despre trolCommander\nOpenTrash.label = Deschide coșul de gunoi\nEmptyTrash.label = Golește coșul de gunoi\nCalculateChecksum.label = Calculează suma de control\nMaximizeWindow.label = Maximizează\nMinimizeWindow.label = Minimizează\nGoToDocumentation.label = Documentație online\nShowParentFoldersQL.label = Directoarele părinte\nShowRecentLocationsQL.label = Locații recente\nShowRecentExecutedFilesQL.label = Fișiere executate recent\nSplitFile.tooltip = Împarte un fișier în mai multe bucăți\nCombineFiles.tooltip = Combină un fișier împărțit în bucăți pentru a recrea originalul\nShowDebugConsole.label = Consolă pentru debug\nFocusPrevious.label = Selectează componenta precedentă\nFocusNext.label = Selectează component următoare\nfile_menu = Fișiere\nfile_menu.open_with = Deschide cu\nmark_menu = Selectează\nview_menu = Vizualizare\nview_menu.show_hide_columns = Ascunde/Arată coloane\ngo_menu = Mergi\nbookmarks_menu = Favorite\nbookmarks_menu.no_bookmark = Nici un director favorit\nquick_lists_menu = Liste rapide\nwindow_menu = Fereastră\nhelp_menu = Ajutor\nstatus_bar.selected_files = %1 din %2 selectate\nstatus_bar.connecting_to_folder = În curs de conectare la un director, apasă ESCAPE pentru a anula.\nstatus_bar.volume_free = Liber: %1\nstatus_bar.volume_capacity = Capacitate: %1\nshortcuts_panel.title = Scurtături\nshortcuts_panel.restore_defaults = Revine la configurația inițială\nshortcuts_panel.show = Arată\nshortcuts_panel.default_message = Apăsați Enter sau dați dublu-clic pe scurtătura pe care doriți să o editați\nshortcuts_table.action_description = Descrierea acțiunii\nshortcuts_table.shortcut = Scurtătură\nshortcuts_table.alternate_shortcut = Scurtătură alternativă\nshortcuts_table.type_in_a_shortcut = Tipul de scurtătură\ncommand_bar_dialog.help = Trageți butoane pentru a personaliza bara de comenzi\ntable.folder_access_error_title = Eroare la accesul la director\ntable.folder_access_error = Nu pot citi conținutul directorului\ntable.download_or_browse = Vreți să navigați sau să descărcați acest fișier?\nversion_dialog.no_new_version_title = Nici o versiune nouă\nversion_dialog.no_new_version = Felicitări, aveți deja versiunea cea mai actuală.\nversion_dialog.new_version_title = O versiune nouă este disponibilă\nversion_dialog.new_version = O versiune nouă de trolCommander este disponibilă.\nversion_dialog.new_version_url = O versiune nouă de trolCommander este disponibilă la: %1.\nversion_dialog.not_available_title = Server indisponibil\nversion_dialog.not_available = Informația despre versiuni nu a putut fi accesată.\nversion_dialog.install_and_restart = Instalează și repornește\nversion_dialog.preparing_for_update = Pregătire pentru actualizare...\nquit_dialog.title = Închide trolCommander\nquit_dialog.desc = Aveți %1 ferestre deschise. Sunteți sigur ca doriti sa închideți trolCommander?\nquit_dialog.show_next_time = Arată și data viitoare\ndestination_dialog.file_exists_action = Acțiune implicită când fișierul există deja\ndestination_dialog.verify_integrity = Verifică integritatea datelor\ndestination_dialog.skip_errors = Ignoră erorile\nfile_collision_dialog.title = Coliziune între fișiere\nrename_dialog.new_name = Nume nou\ncopy_dialog.destination = Copiază fisierul(ele) la\ncopy_dialog.error_title = Eroare la copiere\ncopy_dialog.copying = În curs de copiere\ncopy_dialog.copying_file = În curs de copiere %1\npack_dialog.packing = În curs de arhivare\npack_dialog.packing_file = În curs de arhivare %1\npack_dialog.error_title = Eroare la arhivare\npack_dialog_description = Adaugă fișierele selectate la\npack_dialog.archive_format = Formatul arhivei\nunpack_dialog.destination = Dearhivează fisierul(ele) selectat(e) la\nunpack_dialog.error_title = Eroare la dearhivare\nunpack_dialog.unpacking = În curs de dearhivare\nunpack_dialog.unpacking_file = În curs de dearhivare %1\noptimizing_archive = Optimizez arhiva %1\nerror_while_optimizing_archive = Eroare la optimizarea arhivei %1\nmove_dialog.move_description = Mută în\nmove_dialog.error_title = Eroare la mutare\nmove_dialog.moving = În curs de mutare\nmove_dialog.moving_file = În curs de mutare %1\ndownload_dialog.description = Descarcă fișierul în\ndownload_dialog.error_title = Eroare la descărcare\ndownload_dialog.downloading = În curs de descărcare\ndownload_dialog.downloading_file = În curs de descărcare %1\nmkfile_dialog.allocate_space = Alocă spațiu\ndelete_dialog.permanently_delete.confirmation = Șterg definitiv fișierul(ele) selectat(e) ?\ndelete_dialog.move_to_trash.confirmation = Șterge fișierele selectate ?\ndelete_dialog.move_to_trash.confirmation_details = Fișierele selectate vor fi aruncate la gunoi.\ndelete_dialog.move_to_trash.option = Aruncă la gunoi\ndelete_dialog.move_to_trash.failed = Unul sau mai multe fișiere nu au putut fi mutate la gunoi.\ndelete_dialog.deleting = În curs de ștergere\ndelete_dialog.error_title = Eroare la ștergere\ndelete.deleting_file = În curs de ștergere %1\nemail_dialog.prefs_not_set_title = Email-ul nu e configurat\nemail_dialog.prefs_not_set = Mai întâi trebuie să configurați email-ul \nemail_dialog.from = De la\nemail_dialog.to = Pentru\nemail_dialog.subject = Subiect\nemail_dialog.send = Trimite\nemail_dialog.error_title = Eroare la trimiterea fișierelor prin email\nemail_dialog.read_error = Nu pot citi fișierele din subdirectoare.\nemail_dialog.sending = Fișiere în curs de trimitere\nemail.sending_file = În curs de trimitere %1\nemail.connecting_to_server = În curs de conectare la %1\nemail.server_unavailable = Nu pot contacta serverul de mail %1, verificați configurația pentru email și încercați din nou mai târziu.\nemail.connection_closed = Conexiunea întreruptă de server, email-ul nu a fost trimis.\nemail.goodbye_failed = Eroare la închiderea conexiunii, se poate ca email-ul sa nu fi fost trimis.\nemail.send_file_error = Nu pot trimite fișierul %1, email-ul nu a fost trimis.\nsplit_file_dialog.error_title = Eroare la împărțirea fișierul\nsplit_file_dialog.file_to_split = Fișierul care trebuie împărțit\nsplit_file_dialog.target_directory = Directorul destinație\nsplit_file_dialog.part_size = Dimensiunea fiecărei părți\nsplit_file_dialog.parts = Numărul de părți\nsplit_file_dialog.generate_CRC = Generează sumă de control\nsplit_file_dialog.max_parts = Numărul maxim de părți este %1\nsplit_file_dialog.auto = Automatic\nsplit_file_dialog.insert_new_media = Introduceți următorul disc\ncombine_files_dialog.error_title = Eroare la combinare\ncombine_files_job.no_crc_file = Combinarea s-a terminat cu succes. Fără sumă de control.\ncombine_files_job.crc_read_error = Eroare la citirea sumei de control.\ncombine_files_job.crc_check_failed = Suma de control nu se potrivește: așteptată %2, găsită %1\ncombine_files_job.crc_ok = Combinarea a reușit. Suma de control este OK.\nfile_selection_dialog.mark = Selectează\nfile_selection_dialog.unmark = Deselectează\nfile_selection_dialog.mark_description = Selectează fișierele ale căror nume\nfile_selection_dialog.unmark_description = Deselectează fișierele ale căror nume\nfile_selection_dialog.case_sensitive = Respectă capitalizarea\nfile_selection_dialog.include_folders = Inclusiv directoare\nprogress_dialog.starting = Încep să transfer...\nprogress_dialog.transferred = Am transferat %1 la %2\nprogress_dialog.elapsed_time = Durata\nprogress_dialog.advanced = Avansat\nprogress_dialog.current_speed = Viteza actuală\nprogress_dialog.limit_speed = Limitare de viteză\nprogress_dialog.close_when_finished = Închide când e gata\nprogress_dialog.processing_files = Fișierele sunt procesate\nprogress_dialog.processing_file = Procesez %1\nprogress_dialog.verifying_file = Verificare %1\nprogress_dialog.job_finished = Sarcină îndeplinită\nprogress_dialog.job_error = Eroare de proces\nproperties_dialog.file_properties = %1 Proprietăți\nproperties_dialog.contents = Conținut\nproperties_dialog.calculating = calculez...\ncalculate_checksum_dialog.checksum_algorithm = Algoritm pentru suma de control\ncalculate_checksum_dialog.temporary_file = Fișier temporar\nchange_date_dialog.now = Acum\nchange_date_dialog.specific_date = Dară specifică\nrun_dialog.run_command_description = Execută în directorul curent\nrun_dialog.run_in_home_description = Execută în directorul utilizatorului\nrun_dialog.command_output = Rezultatul comenzii\nrun_dialog.run = Execută\nrun_dialog.clear_history = Șterge istoria\nserver_connect_dialog.server_type = Tipul conexiunii\nserver_connect_dialog.server = Server\nserver_connect_dialog.share = Partajează\nserver_connect_dialog.domain = Domeniu\nserver_connect_dialog.username = Nume utilizator\nserver_connect_dialog.initial_dir = Directorul inițial\nserver_connect_dialog.port = Port\nserver_connect_dialog.server_url = URL-ul serverului\nserver_connect_dialog.http_url = URL-ul sitului Web\nserver_connect_dialog.connect = Conectare\nserver_connect_dialog.protocol = Protocol\nserver_connect_dialog.nfs_version = Versiunea de NFS\nserver_connect_dialog.private_key = Cheia privată\nserver_connect_dialog.passphrase = Frază secretă\nftp_connect.passive_mode = Folosește modul pasiv\nftp_connect.anonymous_user = Utilizator anonim\nftp_connect.nb_connection_retries = Numărul de încercări de conectare\nftp_connect.retry_delay = Durata între încercări (în secunde)\nhttp_connect.basic_authentication = HTTP Basic Authentication (optional)\nserver_connections_dialog.disconnect = Deconectează\nserver_connections_dialog.connection_busy = Ocupată\nserver_connections_dialog.connection_idle = Inactivă\nbonjour.bonjour_services = Servicii Bonjour\nbonjour.no_service_discovered = Nici un serviciu descoperit\nbonjour.bonjour_disabled = Bonjour dezactivat\nauth_dialog.title = Autentificare\nauth_dialog.desc = Vă rog să întroduceți parola\nauth_dialog.server = Server\nauth_dialog.store_credentials = Salvează numele de utilizator și parola (protecție redusă)\nauth_dialog.connect_as = Connectează ca\nauth_dialog.authentication_failed = Autentificare nereușită\nsortable_list.move_up = Mută în sus\nsortable_list.move_down = Mută în jos\nadd_bookmark_dialog.add = Adaugă\nedit_bookmarks_dialog.new = Nou\nfile_viewer.view_error_title = Eroare la vizualizare\nfile_viewer.view_error = Nu pot vizualiza fișierul.\nfile_viewer.file_menu = Fișier\nfile_viewer.close = Închide\nfile_viewer.large_file_warning = Acest fișier ar putea fi prea mare pentru această operațiune.\nfile_viewer.open_anyway = Deschide oricum\ntext_viewer.edit = Editează\ntext_viewer.copy = Copiază\ntext_viewer.select_all = Selectează tot\ntext_viewer.find = Caută\ntext_viewer.find_next = Caută urmatoarea apariție\ntext_viewer.find_previous = Caută apariția precedentă\ntext_viewer.binary_file_warning = Se pare că acest fișier este binar\nimage_viewer.controls_menu = Control\nimage_viewer.zoom_in = Mărește\nimage_viewer.zoom_out = Micșorează\nfile_editor.edit_error_title = Eroare la editare\nfile_editor.edit_error = Nu pot edita fișierul.\nfile_editor.save = Salvează\nfile_editor.save_as = Salvează ca ...\nfile_editor.save_warning = Salvez modificările efectuate înainte de a închide editorul?\nfile_editor.cannot_write = Nu pot scrie fișierul.\ntext_editor.cut = Taie\ntext_editor.paste = Lipește\nshortcuts_dialog.quick_search.start_search = Introduceți orice caracter pentru a începe o căutare rapidă\nshortcuts_dialog.quick_search.cancel_search = Anulează căutarea rapidă\nshortcuts_dialog.quick_search.remove_last_char = Șterge ultimul caracter introdus la o cautare rapidă\nshortcuts_dialog.quick_search.jump_to_previous = Mergi la rezultatul anterior al căutării rapide\nshortcuts_dialog.quick_search.jump_to_next = Mergi la rezultatul următor al căutării rapide\nshortcuts_dialog.quick_search.mark_jump_next = Selectează/Deselectează fișierul curent si mergi la următorul rezultat al căutării\ntheme_editor.title = Editor de teme\ntheme_editor.folder_tab = Panoul cu directoare\ntheme_editor.shell_tab = Linie de comandă\ntheme_editor.shell_history_tab = Istoria terminalului\ntheme_editor.statusbar_tab = Bara de stare\ntheme_editor.free_space = Spațiu liber\ntheme_editor.free_space.ok = OK\ntheme_editor.free_space.warning = Atenție\ntheme_editor.free_space.critical = Situație critică\ntheme_editor.locationbar_tab = Bara de locație\ntheme_editor.editor_tab = Editorul de fișiere\ntheme_editor.font = Font\ntheme_editor.active_panel = Activ\ntheme_editor.inactive_panel = Inactiv\ntheme_editor.general = General\ntheme_editor.could_not_save_theme = Nu am reușit să salvez tema %1\ntheme_editor.border = Margine\ntheme_editor.background = Fond\ntheme_editor.alternate_background = Fond alternativ\ntheme_editor.unfocused_background = În fundal (fără focus)\ntheme_editor.quick_search.unmatched_file = Fișiere care nu corespund\ntheme_editor.text = Text\ntheme_editor.progress = Progres\ntheme_editor.normal = Normal\ntheme_editor.normal_unfocused = Normal (fără focus)\ntheme_editor.selected = Selectat\ntheme_editor.selected_unfocused = Selectat (fără focus)\ntheme_editor.color = Culoare\ntheme_editor.colors = Culori\ntheme_editor.plain_file = Fișier normal\ntheme_editor.marked_file = Fișier selectat\ntheme_editor.hidden_file = Fișier ascuns\ntheme_editor.folder = Director\ntheme_editor.archive_file = Arhivă\ntheme_editor.symbolic_link = Legătură simbolică\ntheme_editor.header = Antet\ntheme_editor.item = Element\ncommand_bar_customize_dialog.available_actions = Acțiuni disponibile\ncommand_bar_customize_dialog.modifier = Modifică\nprefs_dialog.title = Preferințe\nprefs_dialog.general_tab = Generale\nprefs_dialog.day = Zi\nprefs_dialog.month = Lună\nprefs_dialog.year = An\nprefs_dialog.language = Limbă (necesită repornirea programului)\nprefs_dialog.date_time = Formatul datei și orei\nprefs_dialog.time = Ora\nprefs_dialog.date = Dată\nprefs_dialog.date_separator = Separator\nprefs_dialog.time_12_hour = Format 12 ore\nprefs_dialog.time_24_hour = Format 24 ore\nprefs_dialog.show_seconds = Afișează secunde\nprefs_dialog.show_century = Afișează secol\nprefs_dialog.check_for_updates_on_startup = Caută versiuni mai recente la pornire\nprefs_dialog.show_splash_screen = Arată ecran de pornire\nprefs_dialog.folders_tab = Directoare\nprefs_dialog.startup_folders = Director inițial\nprefs_dialog.left_folder = Directorul din stânga\nprefs_dialog.right_folder = Directorul din dreapta\nprefs_dialog.last_folder = Ultimul director vizitat\nprefs_dialog.custom_folder = Director definit de utilizator\nprefs_dialog.show_hidden_files = Afișează fișierele ascunse\nprefs_dialog.show_ds_store_files = Afișează fișierele .DS_Store\nprefs_dialog.show_system_folders = Afișează directoarele system\nprefs_dialog.compact_file_size = Rotunjește dimensiunile afișate ale fișierelor\nprefs_dialog.follow_symlinks_when_cd = Urmează legăturile simbolice la schimbarea directorului curent\nprefs_dialog.appearance_tab = Înfățișare\nprefs_dialog.look_and_feel = Look & Feel\nprefs_dialog.icons_size = Dimensiunea iconițelor\nprefs_dialog.toolbar_icons = Bara de unelte\nprefs_dialog.command_bar_icons = Bara de comenzi\nprefs_dialog.file_icons = Tipuri de fișiere\nprefs_dialog.use_system_file_icons = Folosește iconițele sistemului\nprefs_dialog.use_system_file_icons.always = Întotdeauna\nprefs_dialog.use_system_file_icons.never = Niciodata\nprefs_dialog.use_system_file_icons.applications = Numai pentru aplicații\nprefs_dialog.edit_current_theme = Modifică tema curentă...\nprefs_dialog.themes = Teme\nprefs_dialog.import_theme = Importă temă\nprefs_dialog.import_look_and_feel = Importă look & feel\nprefs_dialog.no_look_and_feel = Acest look & feel nu a fost gasit.\nprefs_dialog.error_in_import = Eroare la importarea temei %1.\nprefs_dialog.cannot_read_theme = Nu am putut încărca tema din fișierul %1\nprefs_dialog.export_theme = Exportare %1\nprefs_dialog.import = Importă\nprefs_dialog.export = Export\nprefs_dialog.theme_type = Tipul: %1\nprefs_dialog.delete_theme = Sterg definitiv tema %1 ?\nprefs_dialog.delete_look_and_feel = Șterge look & feel-ul %1 definitiv?\nprefs_dialog.rename_failed = Nu am reușit să redenumesc tema %1\nprefs_dialog.xml_file = Fișier XML\nprefs_dialog.jar_file = Fișier JAR\nprefs_dialog.mail_tab = Email\nprefs_dialog.mail_settings = Parametrii email-urilor trimise\nprefs_dialog.mail_name = Numele dumneavoastră\nprefs_dialog.mail_address = Adresa dumneavoastră de email\nprefs_dialog.mail_server = Serverul SMTP \nprefs_dialog.misc_tab = Altele\nprefs_dialog.use_brushed_metal = Folosește \"look\"-ul \"brushed metal\" (necesită repornirea programului)\nprefs_dialog.confirm_on_quit = Afișează dialogul de confirmare la ieșire\nprefs_dialog.default_shell = Folosește terminalul implicit pe sistem\nprefs_dialog.custom_shell = Folosește un terminal anume\nprefs_dialog.shell_encoding = Codificarea terminalului\nprefs_dialog.auto_detect_shell_encoding = Detectează automat\nprefs_dialog.enable_bonjour_discovery = Activează descoperirea serviciilor Bonjour\nprefs_dialog.enable_system_notifications = Activează sistemul de notificare\ndebug_console_dialog.level = Nivel\nunit.byte = octet\nunit.bytes = octeți\nunit.bytes_short = o\nunit.kb = Ko\nunit.mb = Mo\nunit.gb = Go\nunit.tb = To\nunit.speed = %1/s\nduration.seconds = %1s\nduration.minutes = %1m\nduration.hours = %1h\nduration.days = %1z\nduration.months = %1l\nduration.years = %1a\ntheme.custom_theme = Temă personalizată\ntheme.custom = Personalizată\ntheme.built_in = Predefinită\ntheme.add_on = Adițională\ntheme.current = curentă\ntheme_could_not_be_loaded = O eroare a apărut la încărcarea acestei teme.\nsetup.title = Bine ați venit în trolCommander\nsetup.intro = Vă rugăm să alegeți modul în care trolCommander se va comporta.\nsetup.look_and_feel = Alegeți aspectul\nsetup.theme = Alegeți o temă.\nfont_chooser.font_size = Dimensiune\nfont_chooser.font_bold = Îngroșat\nfont_chooser.font_italic = Cursiv\ncolor_chooser.red = Roșu\ncolor_chooser.green = Verde\ncolor_chooser.blue = Albastru\ncolor_chooser.hue = Tonalitate\ncolor_chooser.brightness = Luminozitate\ncolor_chooser.swatches = Mostre\ncolor_chooser.saturation = Saturație\ncolor_chooser.rgb = RGB\ncolor_chooser.hsb = HSB\ncolor_chooser.recent = Recent\ncolor_chooser.alpha = Transparență alpha\ncolor_chooser.title = Alegeți o culoare\nbatch_rename_dialog.mask = Șablon redenumire\nbatch_rename_dialog.search_replace = Caută & Înlocuiește\nbatch_rename_dialog.search_for = Caută\nbatch_rename_dialog.replace_with = Înlocuiește cu\nbatch_rename_dialog.counter = Numără\nbatch_rename_dialog.start_at = Începe la\nbatch_rename_dialog.step_by = Increment\nbatch_rename_dialog.format = Format\nbatch_rename_dialog.upper_lower_case = Capitalizare\nbatch_rename_dialog.no_change = Neschimbat\nbatch_rename_dialog.lower_case = minuscule\nbatch_rename_dialog.upper_case = MAJUSCULE\nbatch_rename_dialog.first_upper = Prima literă majusculă\nbatch_rename_dialog.word = Prima Literă Din Fiecare Cuvânt\nbatch_rename_dialog.old_name = Numele vechi\nbatch_rename_dialog.new_name = Numele nou\nbatch_rename_dialog.block_name = Păstrează\nbatch_rename_dialog.range = Interval\nbatch_rename_dialog.proceed_renaming = %1 din %2 fișiere vor fi redenumite. Vreți să continuați?\nbatch_rename_dialog.duplicate_names = Nume care se repetă!\nparent_folders_quick_list.empty_message = Locația curentă nu are părinte\nrecent_locations_quick_list.empty_message = Nici o locație recentă\nrecent_executed_files_quick_list.empty_message = Nici un fișier executat recent\n#mkdir_dialog.description = Crează director\n#mkfile_dialog.description = Crează un nou fișier gol\n#done = Închide\n#move_dialog.rename_description = Redenumește fișierul la\n#theme_editor.shell_font = Font linie de comandă\n#theme_editor.history_font = Font pentru istorie\n#theme_editor.shell_colors = Culori pentru shell\n#theme_editor.history_colors = Culori pentru istorie\n#ToggleHiddenFiles.hide = Nu arăta fișierele ascunse\n#auth_dialog.error_was = Eroare: %1\n#table.hide_column = Ascunde coloană\n#delete.symlink_warning_title = Am găsit o legătură\n#delete.symlink_warning = Acest fișier pare a fi o legătură simbolică (symbolic link):\\n\\n  Fișierul: %1\\n  Arată spre: %2\\n\\nȘterg doar legătura sau \\nUrmez legătura și șterg directorul destinație (ATENȚIE) ?\n#delete.delete_link_only = Șterge legătura\n#delete.delete_linked_folder = Șterge directorul\n#Unpack.label = Dearhivează fișiere\n#Pack.label = Arhivează fișiere\n"
  },
  {
    "path": "src/main/resources/dictionary_ru_RU.properties",
    "content": "ok = OK\nyes = Да\nno = Нет\ncancel = Отмена\nedit = Редактировать\nclose = Закрыть\nreset = сброс\nrename = Переименование\napply = Применить\nchange = Изменить\nsave = Сохранить\ndont_save = Не сохранять\nreplace = Заменить\ndont_replace = Не заменять\ndelete = Удаление\nskip = Пропустить\nskip_all = Пропустить все\noverwrite_all = Перезаписать все\nretry = Повторить\nresume = Продолжить\noverwrite = Заменить\noverwrite_if_older = Только старые\nduplicate = Скопировать\napply_to_all = Применить ко всему\ncopy = Копировать\nmove = Перенести\npack = Архивация\nunpack = Разархивация\ndownload = Скачать\nsplit = Разъединить\ncombine = Соединить\nbrowse = Обзор\nask = Задать вопрос\nstop = Прервать\npause = Приостановить\nquick_search = Быстрый поиск\nfile_manager = Файловый менеджер\ncreate = Создать\ncreating_file = Создается %1\nchoose = Выберите\ncustomize = Персонализация\nclean = Очистить\nsearch = Поиск\nchoose_folder = Выберите каталог\nlogin = Логин\npassword = Пароль\nuser = Пользователь\nencoding = Кодировка\npreferred_encodings = Используемые кодировки\nlicense = Лицензия\nname = Имя\nsize = Размер\ndate = Дата\nextension = Расширение\npermissions = Права доступа\nowner = Владелец\ngroup = Группа\nlocation = Местоположение\nuntitled = Без имени\nsource = Источник\ndestination = Получатель\nrecurse_directories = Обрабатывать подкаталоги\ngo_to = Перейти к\nexample = Пример\npreview = Предпросмотр\ncomment = Комментарий\nsample_text = Пример текста\nnb_files = %1 файл(ов)\nnb_folders = %1 каталог(ов)\nloading = Загрузка...\ntitle = Заголовок\nthis_operation_cannot_be_undone = Эта операция не может быть отменена\nremove = Удалить\ndetails = Подробнее\nwarning = Предупреждение\nerror = Ошибка\nfiles = файлов\nparent = Предок\ngeneric_error = При выполнении запрошенной операции возникла ошибка\nfolder_does_not_exist = Каталог с таким именем не существует или недоступен\nthis_folder_does_not_exist = Каталог %1 не существует или недоступен\nthis_file_does_not_exist = Файл %1 не существует или недоступен\nfailed_to_read_folder = Не могу прочитать текущую эту директорию.\ninvalid_path = Неправильный путь: %1\ndirectory_already_exists = Каталог %1 уже существует.\nfile_exists_in_destination = Файл уже существует\nsource_parent_of_destination = Попытка скопировать каталог в его собственный подкаталог\ncannot_read_file = Не удается прочитать файл %1\ncannot_write_file = Не удается записать файл %1\noverwrite_readonly_file = Файл только для чтения: %1\ncannot_create_folder = Не удается создать каталог %1.\ncannot_read_folder = Не удается прочесть содержимое каталога %1\ncannot_delete_file = Не удается удалить файл %1\ncannot_delete_folder = Не удается создать каталог %1\nerror_while_transferring = Ошибка при передаче файла %1\nsame_source_destination = Исходный и конечный каталог - один и тот же\nfile_already_exists = Файл %1 уже существует. Вы хотите его заменить?\nwrite_error = Ошибка записи\nread_error = Ошибка считывания\nintegrity_check_error = Проверка целостности не прошла: файл-источник и файл-приемник не совпадают\nstartup_error = Предстартовая ошибка trolCommander'а.\nimage_size = Размеры изображения\ncannot_write_symlink=Не могу создать символическую ссылку %1\ncannot_write_symlink_already_exists=Ссылка уже существует: %1\ncannot_write_symlink_access_denied=Ошибка доступа при создании символической ссылки %1\npermissions.read = Чтение\npermissions.write = Запись\npermissions.executable = Выполнение\npermissions.group = Группа\npermissions.other = Остальные\npermissions.octal_notation = Цифровое обозначение\npermissions.user = Пользователь\naction_categories.all = Все\naction_categories.navigation = Перемещение\naction_categories.selection = Выбор\naction_categories.view = Вид\naction_categories.file_operations = Файловые операции\naction_categories.windows = Окна\naction_categories.tabs = Вкладки\naction_categories.misc = Прочее\naction_categories.commands = Пользовательские команды\nTerminal.label = Терминал\nTerminal.tooltip = Открыть консоль в текущей директории\nTerminalPanel.label = Терминал\nTerminalPanel.tooltip = Открыть/закрыть панель терминала\nUserMenu.label = Пользовательское меню\nUserMenu.tooltip = Открыть пользовательское меню\nUserMenu.press_f4_to_edit_menu = <html>Нажмите <b>F4</b> для редактирования меню\nUserMenu.command_not_defined = Команда не задана\nFindFile.label = Поиск файлов\nFindFile.tooltip = Поиск файлов\nAddBookmark.label = Добавить закладку\nAddBookmark.tooltip = Добавить текущий каталог в закладки\nBatchRename.label = Групповое переименование\nEditBookmarks.label = Редактировать закладки\nExploreBookmarks.label = Просмотреть закладки\nEditCredentials.label = Редактировать реквизиты\nChangeLocation.label = Перемещение\nChangeDate.label = Изменение даты\nChangeDate.tooltip = Изменить дату выбранных файлов\nChangePermissions.label = Изменение прав доступа\nChangePermissions.tooltip = Изменить права доступа выбранных файлов\nCheckForUpdates.label = Проверить обновления\nCompareFolders.label = Сравнить каталоги\nCompareFolderFiles.label = Сравнить файлы\nCompareFolderFiles.tooltip = Сравнить файлы и отметить изменённые в текущей директории\nConnectToServer.label = Соединение с сервером\nConnectToServer.tooltip = Соединиться с удаленным сервером\nTextEditorsList.label = Редактируемые файлы\nTextEditorsList.tooltip = Показать все редактируемые файлы\nView.label = Просмотреть\nViewAs.label = Просмотреть как\nInternalView.label = Просмотр (встроенным)\nCompareFiles.label = Сравнить текстовые файлы\nCompareFiles.tooltip = Сравнить текстовые файлы (FileMerge)\nviewer_type.text = Текстовый файл\nviewer_type.hex = Двоичный файл\nviewer_type.image = Рисунок\nviewer_type.pdf = Документ PDF\nviewer_type.audio = Аудио файл\nviewer_type.html = Документ HTML\nView.tooltip = Просмотр выбранного файла\nInternalEdit.label = Редактировать (встроенным)\nCalculator.label = Калькулятор\nCreateSymlink.label = Создать ссылку\nLocateSymlink.label = Перейти к файлу ссылки\nShowFoldersSize.label = Показать размеры директорий\nEditCommands.label = Редактирование команд\nEditCommands.group.view = Программы просмотра\nEditCommands.group.edit = Редакторы\nEditCommands.group.others = Прочие\nEditCommands.new = Новая\nEditCommands.alias = Имя\nEditCommands.command = Команда\nEditCommands.display_name = Отображаемое имя\nEditCommands.filemask = Маски файлов\nsymboliclinkeditor.edit = Редактировать ссылку\nsymboliclinkeditor.create = Символическая ссылка\nsymboliclinkeditor.target_file_create = Существующий файл (ссылка будет указывать на него)\nsymboliclinkeditor.target_file_edit = Ссылка '%s' указывает на\nsymboliclinkeditor.link_name = Имя ссылки\nEdit.label = Редактировать\nEdit.tooltip = Редактирование выбранного файла\nEditAs.label = Редактировать как\nCopy.tooltip = Копирование выделенных файлов\nLocalCopy.label = Копировать локально\nCopy.label = Копировать\nLocalCopy.tooltip = Копирование выбранного файла в текущий каталог\nMove.label = Перенести\nMove.tooltip = Перенос выделенных файлов\nRename.label = Переименовать\nRename.tooltip = Переименование выбранного файла\nMkdir.label = Создать каталог\nMkdir.tooltip = Создание подкаталог в текущем каталоге\nMkfile.label = Создать файл\nMkfile.tooltip = Создать файл в текущем каталоге\nDelete.label = Удалить\nDelete.tooltip = Удаление выделенных файлов в корзину, если это возможно\nPermanentDelete.label = Удалить сразу\nPermanentDelete.tooltip = Удалить выделенные файлы минуя корзину\nRefresh.label = Обновить\nRefresh.tooltip = Обновление списка файлов в текущем каталоге\nCloseWindow.label = Закрыть окно\nCloseWindow.tooltip = Закрытие окна\nCopyFileNames.label = Копировать полные имена\nCopyFileBaseNames.label = Копировать только имена\nCopyFilePaths.label = Копировать пути\nCopyFilesToClipboard.label = Копировать файл(ы)\nPasteClipboardFiles.label = Вставить файл(ы)\nEmail.label = Отправить файлы по Email\nEmail.tooltip = Отправить выделенные файлы как приложения по Email\nGoBack.label = Назад\nGoBack.tooltip = Перейти к предыдущему каталогу\nGoForward.label = Вперед\nGoForward.tooltip = Перейти к следующему каталогу\nGoToHome.label = Перейти в домашний каталог\nGoToParent.label = Перейти в родительский каталог\nGoToParent.tooltip = Перейти к родительскому каталогу\nGoToParentInOtherPanel.label = Перейти наверх в другой панели\nGoToParentInBothPanels.label = Перейти наверх в обеих панелях\nGoToRoot.label = Перейти в корень\nSortByName.label = Сортировать по имени\nSortByDate.label = Сортировать по дате\nSortBySize.label = Сортировать по размеру\nSortByExtension.label = Сортировать по расширению\nSortByPermissions.label = Сортировать по правам доступа\nSortByOwner.label = Сортировать по владельцу\nSortByGroup.label = Сортировать по группе\nMarkGroup.label = Выделить файлы\nMarkGroup.tooltip = Выделить группу файлов\nUnmarkGroup.label = Снять выделение с файлов\nUnmarkGroup.tooltip = Отменить выделение группы файлов\nMarkAll.label = Выделить все\nUnmarkAll.label = Снять выделение со всего\nMarkSelectedFile.label = Выделить/снять\nMarkSelectedFile.tooltip = Выделить/снять выделение выбранного файла\nMarkNextBlock.label = Выделить блок строк вниз\nMarkPreviousBlock.label = Выделить блок строк вверх\nMarkNextRow.label = Построчное выделение вниз\nMarkPreviousRow.label = Построчное выделение вверх\nMarkNextPage.label = Выделить файлы на следующей странице\nMarkPreviousPage.label = Выделить файлы на предыдущей странице\nMarkToFirstRow.label = Выделить файлы до начала\nMarkToLastRow.label = Выделить файлы до конца\nMarkExtension.label = Выделить по расширению\nMarkEmpty.label = Выделить пустые\nInvertSelection.label = Инвертировать выделение\nSwapFolders.label = Поменять местами каталоги\nSwapFolders.tooltip = Поменять местами панели\nSetSameFolder.label = Сделать каталоги одинаковыми\nSetSameFolder.tooltip = Одинаковые каталоги в обеих панелях\nToggleTableViewModeFull.label = Полный режим\nToggleTableViewModeFull.tooltip = Переключиться в полный режим\nToggleTableViewModeCompact.label = Компактный режим\nToggleTableViewModeCompact.tooltip = Переключиться в компактный режим\nToggleTableViewModeShort.label = Короткий режим\nToggleTableViewModeShort.tooltip = Переключиться в короткий режим\nTogglePanelPreviewMode.label = Быстрый просмотр\nTogglePanelPreviewMode.tooltip = Переключиться в режим быстрого просмотра\nNewWindow.label = Новое окно\nNewWindow.tooltip = Открыть новое окно\nOpen.label = Открыть\nOpen.tooltip = Войти в каталог / Войти в архив / Выполнить\nOpenNatively.label = Открыть в связанной программе\nOpenNatively.tooltip = Выполнить выделенный файл с помощью программы, связанной с ним в настройках системы\nOpenInOtherPanel.label = Открыть в другой панели\nOpenInBothPanels.label = Открыть в обеих панелях\nOpenLeftInRightPanel.label = Открыть левый объект в правой панели\nOpenRightInLeftPanel.label = Открыть правый объект в левой панели\nOpenInNewTab.label = Открыть в новой вкладке\nCloseTab.label = Закрыть\nCloseTab.tooltip = Закрыть вкладку\nPreviousTab.label = Предыдущая вкладка\nPreviousTab.tooltip = Переключиться на вкладку левее\nNextTab.label = Следующая вкладка\nNextTab.tooltip = Переключиться на вкладку правее\nMoveTabToOtherPanel.label = Перенести на другую панель\nMoveTabToOtherPanel.tooltip = Перенести вкладку на другую панель\nCloseOtherTabs.label = Закрыть остальные\nCloseOtherTabs.tooltip = Закрыть остальные вкладки\nCloseDuplicateTabs.label = Закрыть дубликаты\nCloseDuplicateTabs.tooltip = Закрыть вкладки дубликатов\nCloneTabToOtherPanel.label = Клонировать в другую панель\nCloneTabToOtherPanel.tooltip = Добавить такую же вкладку в другой панели\nSetTabTitle.label = Задать заголовок\nSetTabTitle.tooltip = Задать заголовок вкладки\nRevealInDesktop.label = Отобразить в %1\nRunCommand.label = Выполнение команды\nRunCommand.tooltip = Выполнить команду из текущего каталога\nPack.label = Архивировать файлы\nPack.tooltip = Архивировать выбранные файлы\nUnpack.label = Разархивировать файлы\nUnpack.tooltip = Разархивировать выделенные файлы\nShowFileProperties.label = Свойства\nShowFileProperties.tooltip = Показать свойства выбранных файлов\nShowFilePopupMenu.label = Контекстное меню\nShowFilePopupMenu.tooltip = Показать контекстное меню для выбранного файла\nShowPreferences.label = Настройки\nShowPreferences.tooltip = Настройка trolCommander\nShowServerConnections.label = Показать установленные соединения\nQuit.label = Выход\nShowBookmarksQL.label = Закладки\nShowEditorBookmarksQL.label = Закладки на файлы\nEjectDrive.label = Извлечь диск\nEjectDrive.tooltip = Безопасно извлечь диск\nReverseSortOrder.label = В обратном порядке\nToggleAutoSize.label = Автоподбор размера колонок\nStop.label = Прекратить изменение каталога\nToggleColumn.show = Показать колонку %1\nToggleColumn.hide = Убрать колонку %1\nToggleCommandBar.show = Показать панель команд\nToggleCommandBar.hide = Скрыть панель команд\nToggleToolBar.show = Показать панель инструментов\nToggleToolBar.hide = Скрыть панель инструментов\nCustomizeCommandBar.label = Настройка панели команд\nToggleHiddenFiles.label = Показывать скрытые файлы\nToggleStatusBar.show = Показать панель статуса\nToggleStatusBar.hide = Скрыть панель статуса\nToggleShowFoldersFirst.label = Вынести каталоги в начало списка\nToggleFoldersAlwaysAlphabetical.label = Сортировать каталоги всегда по алфавиту\nToggleTree.label = Показать древовидный вид\nToggleSinglePanel.label = Режим одной панели\nPopupLeftDriveButton.label = Изменить левый каталог\nPopupRightDriveButton.label = Изменить правый каталог\nRecallPreviousWindow.label = Переключиться в предыдущее окно\nRecallNextWindow.label = Переключиться в следующее окно\nRecallWindow.label = Восстановить окно #%1\nRecallWindow1.label = Восстановить окно #%1\nRecallWindow2.label = Восстановить окно #%1\nRecallWindow3.label = Восстановить окно #%1\nRecallWindow4.label = Восстановить окно #%1\nRecallWindow5.label = Восстановить окно #%1\nRecallWindow6.label = Восстановить окно #%1\nRecallWindow7.label = Восстановить окно #%1\nRecallWindow8.label = Восстановить окно #%1\nRecallWindow9.label = Восстановить окно #%1\nRecallWindow10.label = Восстановить окно #%1\nBringAllToFront.label = Показать все окна\nShowTabsQL.label = Открыть вкладки\nShowRootFoldersQL.label = Корневые директории\nSwitchActiveTable.label = Переключение между левой и правой панелью\nSelectNextBlock.label = Спуститься на блок строк вниз\nSelectPreviousBlock.label = Подняться на блок строк вверх\nSelectNextPage.label = Перейти в конец страницы\nSelectPreviousPage.label = Перейти в начало страницы\nSelectNextRow.label = Спуститься на одну строку вниз\nSelectPreviousRow.label = Подняться на одну строку вверх\nSelectFirstRow.label = Перейти к первому файлу в текущем каталоге\nSelectLastRow.label = Перейти к последнему файлу в текущем каталоге\nLeftArrowAction.label = Перейти левее\nRightArrowAction.label = Перейти правее\nSplitEqually.label = Разделить поровну\nSplitVertically.label = Разделить вертикально\nSplitHorizontally.label = Разделить горизонтально\nShowKeyboardShortcuts.label = Быстрые клавиши\nGoToWebsite.label = Перейти на домашнюю страницу программы\nGoToForums.label = Перейти на форум\nReportBug.label = Сообщить об ошибке\nDonate.label = Сделать пожертвование на развитие программы\nShowAbout.label = О программе trolCommander\nOpenTrash.label = Открыть Корзину\nEmptyTrash.label = Очистить корзину\nCalculateChecksum.label = Расчет контрольной суммы\nMinimizeWindow.label = Свернуть\nMaximizeWindow.label = Развернуть\nMaximizeWindow.label.mac_os_x = Развернуть\nGoToDocumentation.label = Документация онлайн\nShowParentFoldersQL.label = Родительские каталоги\nShowRecentLocationsQL.label = Недавно посещенные каталоги\nShowRecentExecutedFilesQL.label = Недавно используемые файлы\nShowRecentViewedFilesQL.label = Недавно просматриваемые файлы\nShowRecentEditedFilesQL.label = Недавно редактируемые файлы\nSplitFile.label = Разъединить\nSplitFile.tooltip = Разъединить файл на несколько частей\nCombineFiles.label = Собрать\nCombineFiles.tooltip = Собрать разъединенный на части файл в исходный файл\nShowDebugConsole.label = Консоль Debug\nFocusPrevious.label = Перейти к предыдущему компоненту\nFocusNext.label = Перейти к следующему компоненту\nfile_menu = Файл\nfile_menu.open_with = Открыть с помощью\nfile_menu.open_as = Open как\nmark_menu = Выделение\nview_menu = Вид\nview_menu.show_hide_columns = Показать/скрыть столбцы\nview_menu.table_mode = Режим\ngo_menu = Перейти\ntools_menu = Инструменты\neject_menu = Извлечь диск\nbookmarks_menu = Закладки\nbookmarks_menu.no_bookmark = Закладок нет\ndrive_popup.network_shares = Сетевой общий ресурс\nquick_lists_menu = Быстрый список\nwindow_menu = Окна\nhelp_menu = Справка\nAddTab.label = Добавить вкладку\nAddTab.tooltip = Добавить новую вкладку в активной панели\nDuplicateTab.label = Дублировать вкладку\nDuplicateTab.tooltip = Дублировать текущую вкладку на текущей панели\nToggleLockTab.label = Блокировать/Разблокировать\nToggleLockTab.tooltip = Изменить состояние блокировки вкладки\nToggleLockTab.lock = Блокировать\nToggleLockTab.unlock = Разблокировать\nstatus_bar.selected_files = %1 из %2 файлов выбрано\nstatus_bar.connecting_to_folder = Подключаемся к каталогу\nstatus_bar.volume_free = Свободно: %1\nstatus_bar.volume_capacity = Емкость диска: %1\nstatus_bar.quick_search.press_esc_to_stop_search = нажмите Esc для отмены поиска\nshortcuts_panel.title = Комбинации клавиш\nshortcuts_panel.restore_defaults = Вернуть стандартные\nshortcuts_panel.show = Показать\nshortcuts_panel.search = Поиск\nshortcuts_panel.default_message = Нажатие Enter или двойной-щелчек обеспечит редактирование комбинаций клавиш\nshortcuts_table.action_description = Описание действия\nshortcuts_table.shortcut = Комбинации клавиш\nshortcuts_table.alternate_shortcut = Альтернативные комбинации\nshortcuts_table.type_in_a_shortcut = Нажмите комбинацию клавиш\ncommand_bar_dialog.help = Перетаскивайте кнопки для настройки панели команд\ntable.folder_access_error_title = Ошибка: нет доступа в каталог\ntable.folder_access_error = Не удается прочесть содержимое каталога\ntable.download_or_browse = Вы хотите просмотреть или скачать данный файл?\nversion_dialog.no_new_version_title = Новых версий не обнаружено\nversion_dialog.no_new_version = У вас уже установлена самая последняя версия\nversion_dialog.new_version_title = Доступна новая версия\nversion_dialog.new_version = Доступна новая версия trolCommander.\nversion_dialog.new_version_url = Новая версия trolCommander доступна по адресу %1.\nversion_dialog.not_available_title = Сервер недоступен\nversion_dialog.not_available = Не удается получить информацию о новых версиях с сервера.\nversion_dialog.install_and_restart = Установить и перезапустить\nversion_dialog.preparing_for_update = Идет подготовка к обновлению...\nquit_dialog.title = Выход из  trolCommander\nquit_dialog.desc = Закрыть %1 окон и выйти из trolCommander ?\nquit_dialog.show_next_time = Показывать следующий раз\ndestination_dialog.file_exists_action = Действие по умолчанию если файл уже существует\ndestination_dialog.verify_integrity = Проверить целостность данных\ndestination_dialog.skip_errors = Пропустить ошибки\ndestination_dialog.background_mode = Фоновый режим\nfile_collision_dialog.title = Совпадение файлов\nrename_dialog.new_name = Новое имя\ncopy_dialog.destination = Копировать выбранные файлы в\ncopy_dialog.error_title = Ошибка при копировании\ncopy_dialog.copying = Копирование файлов\ncopy_dialog.copying_file = Копирую %1\npack_dialog.packing = Архивируются файлы\npack_dialog.packing_file = Архивируется файл %1\npack_dialog.error_title = Ошибка архивации\npack_dialog_description = Добавить выбранные файлы в\npack_dialog.archive_format = Формат архива\nunpack_dialog.destination = Разархивировать выбранные файлы в\nunpack_dialog.error_title = Ошибка разархивации\nunpack_dialog.unpacking = Разархивация файлов\nunpack_dialog.unpacking_file = Разархивируется файл %1\noptimizing_archive = Оптимизация архива %1\nerror_while_optimizing_archive = Ошибка при оптимизации архива %1\nmove_dialog.move_description = Перенести в\nmove_dialog.error_title = Ошибка при переносе\nmove_dialog.moving = Перемещение файлов\nmove_dialog.moving_file = Перемещается файл %1\ndownload_dialog.description = Скачать файл в\ndownload_dialog.error_title = Ошибка при скачивании\ndownload_dialog.downloading = Скачивание\ndownload_dialog.downloading_file = Скачивается файл %1\ndownload_dialog.download = Скачать\nmkfile_dialog.allocate_space = Выделить место\nmkfile_dialog.open_in_editor = Открыть в редакторе\nmkfile_dialog.make_executable = Исполняемый файл\nmkfile_dialog.convert_whitespace = Преобразовать пробелы\ndelete_dialog.permanently_delete.confirmation = Удалить выбранные файлы без возможности восстановления?\ndelete_dialog.permanently_delete.confirmation_1 = Удалить выбранный файл без возможности восстановления?\ndelete_dialog.permanently_delete.symlink_confirmation_1 = Удалить выбранную символическую ссылку без возможности восстановления?\ndelete_dialog.move_to_trash.confirmation = Удалить выбранные файлы?\ndelete_dialog.move_to_trash.confirmation_1 = Удалить выбранный файл?\ndelete_dialog.move_to_trash.confirmation_details = Файлы будут помещены в Корзину\ndelete_dialog.move_to_trash.confirmation_details_1 = Файл будет помещён в Корзину\ndelete_dialog.move_to_trash.option = Поместить в Корзину\ndelete_dialog.move_to_trash.failed = Один или несколько файлов не могут быть перемещены в корзину.\ndelete_dialog.deleting = Удаление файлов\ndelete_dialog.error_title = Ошибка при удалении файла\ndelete.deleting_file = Удаляется файл %1\nemail_dialog.prefs_not_set_title = Почта не настроена\nemail_dialog.prefs_not_set = Вам необходимо настроить параметры почты перед тем, как пытаться что-то отправить.\nemail_dialog.from = От кого\nemail_dialog.to = Кому\nemail_dialog.subject = Тема\nemail_dialog.send = Отправить\nemail_dialog.error_title = Ошибка при отправке файлов\nemail_dialog.read_error = Невозможно прочитать файлы в подкаталогах.\nemail_dialog.sending = Отправка файлов\nemail.sending_file = Отправляется %1\nemail.connecting_to_server = Установка соединения с %1\nemail.server_unavailable = Не удалось соединиться с почтовым сервером %1, проверьте настройки почты и попробуйте еще раз позже.\nemail.connection_closed = Соединение закрыто сервером, почта не была отправлена.\nemail.goodbye_failed = Ошибка при завершении соединения, возможно, почта не была отправлена.\nemail.send_file_error = Невозможно отправить файл %1, почта не была отправлена.\nsplit_file_dialog.error_title = Ошибка в разъединении файла\nsplit_file_dialog.file_to_split = Файл для разъединения\nsplit_file_dialog.target_directory = Результирующий каталог\nsplit_file_dialog.part_size = Размер каждой части\nsplit_file_dialog.parts = Число частей\nsplit_file_dialog.generate_CRC = Сформировать CRC файл\nsplit_file_dialog.max_parts = Максимально допустимое число частей %1\nsplit_file_dialog.auto = Авто\nsplit_file_dialog.insert_new_media = Вставьте новый носитель\ncombine_files_dialog.error_title = Ошибка соединения файла\ncombine_files_job.no_crc_file = Соеденино успешно. Нет CRC файла.\ncombine_files_job.crc_read_error = Ошибка чтения CRC файла.\ncombine_files_job.crc_check_failed = CRC расхождения: ожидается %2, получено %1\ncombine_files_job.crc_ok = Соеденино успешно. CRC контрольная сумма ok.\nfile_selection_dialog.mark = Выделение\nfile_selection_dialog.unmark = Снятие выделения\nfile_selection_dialog.mark_description = Выделить файлы по маске\nfile_selection_dialog.unmark_description = Снять выделение с файлов по маске\nfile_selection_dialog.case_sensitive = С учетом регистра\nfile_selection_dialog.include_folders = Включая каталоги\nprogress_dialog.starting = Передача файлов начата...\nprogress_dialog.transferred = Передано %1, в среднем %2\nprogress_dialog.elapsed_time = Потрачено времени\nprogress_dialog.advanced = Ещё\nprogress_dialog.current_speed = Cкорость\nprogress_dialog.limit_speed = Предел скорости\nprogress_dialog.close_when_finished = Закрыть окно после завершения\nprogress_dialog.processing_files = Обработка файлов\nprogress_dialog.processing_file = Обрабатывается %1\nprogress_dialog.verifying_file = Идет проверка %1\nprogress_dialog.job_finished = Обработка завершена\nprogress_dialog.job_error = Ошибка обработки\nprogress_dialog.hide = Свернуть в фон\nproperties_dialog.file_properties = Свойства %1\nproperties_dialog.contents = Содержимое\nproperties_dialog.calculating = идет подсчет...\ncalculate_checksum_dialog.checksum_algorithm = Алгоритм расчета контр. суммы\ncalculate_checksum_dialog.temporary_file = Временный файл\nchange_date_dialog.now = Текущая дата\nchange_date_dialog.specific_date = Указанная дата\nrun_dialog.run_command_description = Запустить из текущего каталога\nrun_dialog.run_in_home_description = Выполнить в домашнем каталоге\nrun_dialog.command_output = Вывод программы\nrun_dialog.run = Запустить\nrun_dialog.clear_history = Очистить буфер команд\nrun_dialog.stop = Остановить\nserver_connect_dialog.server_type = Тип соединения\nserver_connect_dialog.server = Сервер\nserver_connect_dialog.share = Общий ресурс\nserver_connect_dialog.domain = Домен\nserver_connect_dialog.username = Имя пользователя\nserver_connect_dialog.initial_dir = Начальный каталог\nserver_connect_dialog.port = Порт\nserver_connect_dialog.server_url = Адрес сервера\nserver_connect_dialog.http_url = URL сайта\nserver_connect_dialog.connect = Соединиться\nserver_connect_dialog.protocol = Протокол\nserver_connect_dialog.nfs_version = Версия NFS\nserver_connect_dialog.private_key = Закрытый ключ\nserver_connect_dialog.passphrase = Ключевая фраза\nftp_connect.passive_mode = Использовать пассивный режим\nftp_connect.anonymous_user = Анонимное соединение\nftp_connect.nb_connection_retries = Количество попыток установления соединения\nftp_connect.retry_delay = Задержка между повторами (в секундах)\nhttp_connect.basic_authentication = Базовая аутентификация HTTP (не обязательно)\nserver_connections_dialog.disconnect = Отключиться\nserver_connections_dialog.connection_busy = Занято\nserver_connections_dialog.connection_idle = Свободно\nbonjour.bonjour_services = Служба Bonjour\nbonjour.no_service_discovered = Служб не обнаружено\nbonjour.bonjour_disabled = Служба Bonjour отключена\nauth_dialog.title = Аутентификация\nauth_dialog.desc = Введите логин и пароль\nauth_dialog.server = Сервер\nauth_dialog.store_credentials = Сохранить логин и пароль (со слабым шифрованием)\nauth_dialog.connect_as = Просоединиться как\nauth_dialog.authentication_failed = Аутентификации не пройдена\nsortable_list.move_up = Выше\nsortable_list.move_down = Ниже\nadd_bookmark_dialog.add = Добавить\nedit_bookmarks_dialog.new = Новый\nedit_bookmarks_dialog.location = Местоположение\nedit_bookmarks_dialog.is_separator = Указанное имя задает разделитель\nfile_viewer.view_error_title = Ошибка при просмотре\nfile_viewer.view_error = Невозможно просмотреть файл.\nfile_viewer.file_menu = Файл\nfile_viewer.close = Закрыть\nfile_viewer.large_file_warning = Этот файл может оказаться слишком большим для просмотра.\nfile_viewer.open_anyway = Открыть все равно\nfile_viewer.open_hex = Двоичный просмотр\ntext_viewer.edit = Редактировать\ntext_viewer.copy = Копировать\ntext_viewer.select_all = Выбрать все\ntext_viewer.find = Поиск\ntext_viewer.find_button = Найти\ntext_viewer.find_next = Искать далее\ntext_viewer.find_previous = Искать предыдущий\ntext_viewer.find.case_sensitive = Различать регистр\ntext_viewer.find.whole_word = Целые слова\ntext_viewer.find.regexp = Regexp\ntext_viewer.find.mark_all = Пометить всё\ntext_viewer.find.direction = Направление\ntext_viewer.find.up = Вверх\ntext_viewer.find.down = Вниз\ntext_viewer.view = Просмотр\ntext_viewer.line_wrap = Перенос строк\ntext_viewer.line_numbers = Нумерация строк\ntext_viewer.binary_file_warning = Этот файл, скорее всего, двоичный\ntext_viewer.goto_line = Перейти к строке\ntext_viewer.line = Строка\ntext_viewer.open_file_error = Не могу открыть файл\nimage_viewer.controls_menu = Элементы управления\nimage_viewer.zoom_in = Увеличить\nimage_viewer.zoom_out = Уменьшить\nfile_editor.file_menu = Файл\nfile_editor.close = Закрыть\nfile_editor.edit_error_title = Ошибка при редактировании\nfile_editor.edit_error = Невозможно отредактировать файл.\nfile_editor.save = Сохранить\nfile_editor.save_as = Сохранить как...\nfile_editor.save_warning = Сохранить сделанные изменения в файл перед выходом ?\nfile_editor.cannot_write = Не удается записать файл.\nfile_editor.open_anyway = Все равно открыть\nfile_editor.save_anyway = Все равно сохранить\nfile_editor.overwrite_readonly = Файл только для чтения\nfile_editor.files = Файлы\nfile_editor.files.list = Выбор файла\nfile_editor.show_file_manager = Менеджер файлов\nfile_editor.add_to_bookmark = Добавить в закладки\nfile_editor.remove_from_bookmark = Удалить из закладок\nfile_editor.goto_header_source = Перейти к заголовку/исходнику\ntext_editor.cut = Вырезать\ntext_editor.paste = Вставить\ntext_editor.undo = Отменить изменение\ntext_editor.redo = Вернуть изменение\ntext_editor.edit = Правка\ntext_editor.copy = Копировать\ntext_editor.select_all = Выделить все\ntext_editor.view = Просмотр\ntext_editor.find = Искать...\ntext_editor.find_next = Искать следующее\ntext_editor.find_previous = Искать предыдущее\ntext_editor.search = Поиск\ntext_editor.replace_menu = Заменить..\ntext_editor.replace_button = Заменить\ntext_editor.replace = Замена\ntext_editor.replace_with = Заменить на\ntext_editor.replace_all = Заменить всё\ntext_editor.replaced = Заменено\ntext_editor.occurrences = вхождений\ntext_editor.line_wrap = Пенерос строк\ntext_editor.line_numbers = Нумерация строк\ntext_editor.syntax = Синтаксис\ntext_editor.format = Форматировать\ntext_editor.writing = Сохраняю...\ntext_editor.modified = Изменен\ntext_editor.saved = Файл сохранен\ntext_editor.text_not_found = Текст не найден\ntext_editor.found = Найдено\ntext_editor.matches = вхождений\ntext_editor.cant_save_file = Не удалось сохранить файл\ntext_editor.press_alt_enter_to_open_file = Нажмите Alt+Enter чтобы открыть файл\ntext_editor.tools=Инструменты\ntext_editor.build=Собрать\ntext_editor.invisible_chars = Невидимые символы\nshortcuts_dialog.quick_search = Быстрый поиск\nshortcuts_dialog.quick_search.start_search = Введите любой символ для начала быстрого поиска\nshortcuts_dialog.quick_search.cancel_search = Отмена быстрого поиска\nshortcuts_dialog.quick_search.remove_last_char = Удалить последний символ из строки быстрого поиска\nshortcuts_dialog.quick_search.jump_to_previous = Перейти к предыдущему результату быстрого поиска\nshortcuts_dialog.quick_search.jump_to_next = Перейти к следующему результату быстрого поиска\nshortcuts_dialog.quick_search.mark_jump_next = Выделить/снять выделение с файла и перейти к следующему результату поиска\ntheme_editor.title = Редактор темы\ntheme_editor.folder_tab = Панель каталога\ntheme_editor.shell_tab = Оболочка\ntheme_editor.shell_history_tab = История команд\ntheme_editor.terminal_tab = Терминал\ntheme_editor.statusbar_tab = Строка состояния\ntheme_editor.free_space = Свободное место\ntheme_editor.free_space.ok = OK\ntheme_editor.free_space.warning = Заканчивается\ntheme_editor.free_space.critical = Очень мало\ntheme_editor.locationbar_tab = Указатель местонахождения\ntheme_editor.editor_tab = Редактор файлов\ntheme_editor.font = Шрифт\ntheme_editor.active_panel = Активная\ntheme_editor.inactive_panel = Неактивная\ntheme_editor.general = Общий\ntheme_editor.could_not_save_theme = Не удается сохранить тему %1\ntheme_editor.border = Рамка\ntheme_editor.background = Фон\ntheme_editor.alternate_background = Другой фон\ntheme_editor.unfocused_background = Фон (без фокуса)\ntheme_editor.quick_search.unmatched_file = Несовпадающие файлы\ntheme_editor.text = Текст\ntheme_editor.progress = Индикатор выполнения\ntheme_editor.normal = Обычный\ntheme_editor.normal_unfocused = Обычный (без фокуса)\ntheme_editor.selected = Выбранный\ntheme_editor.selected_unfocused = Выбранный (без фокуса)\ntheme_editor.color = Цвет\ntheme_editor.colors = Цвета\ntheme_editor.plain_file = Обычный файл\ntheme_editor.marked_file = Выделенный файл\ntheme_editor.hidden_folder = Скрытый каталог\ntheme_editor.hidden_file = Скрытый файл\ntheme_editor.folder = Каталог\ntheme_editor.archive_file = Архив\ntheme_editor.symbolic_link = Символьная ссылка\ntheme_editor.executable_file = Исполняемый файл\ntheme_editor.header = Заголовок\ntheme_editor.current = Подсветка строки\ntheme_editor.file_groups = Группы файлов\ntheme_editor.group_ = Группа\ntheme_editor.normal_color = Нормальный цвет\ntheme_editor.selected_color = Выделенный цвет\ntheme_editor.filemask = Маски файлов\ntheme_editor.group_file_ = Файл группы \ntheme_editor.item = Элемент\ntheme_editor.quick_search = Быстрый поиск\ntheme_editor.text_editor_tab = Текстовый редактор и вьювер\ntheme_editor.hex_viewer_tab = Шестнадцатиричный вьювер\ntheme_editor.normal_hex = Дамп\ntheme_editor.normal_offset = Адрес\ntheme_editor.normal_ascii = ASCII\ntheme_editor.alternate = Альтернативный\ntheme_editor.selected_hex = Выбранный дамп\ntheme_editor.selected_ascii = Выбранный ASCII\ncommand_bar_customize_dialog.available_actions = Доступные действия\ncommand_bar_customize_dialog.modifier = Переключатель\nprefs_dialog.title = Настройки\nprefs_dialog.general_tab = Общие\nprefs_dialog.day = День\nprefs_dialog.month = Месяц\nprefs_dialog.year = Год\nprefs_dialog.language = Язык (потребуется перезапуск)\nprefs_dialog.date_time = Формат даты и времени\nprefs_dialog.time = Время\nprefs_dialog.date = Дата\nprefs_dialog.date_separator = Разделитель\nprefs_dialog.time_12_hour = 12-часовой формат\nprefs_dialog.time_24_hour = 24-часовой формат\nprefs_dialog.show_seconds = Показывать секунды\nprefs_dialog.show_century = Показывать 4 цифры года\nprefs_dialog.check_for_updates_on_startup = Проверять наличие новых версий при запуске\nprefs_dialog.show_splash_screen = Показывать заставку\nprefs_dialog.folders_tab = Каталоги\nprefs_dialog.startup_folders = Каталоги при запуске\nprefs_dialog.left_folder = Каталог в левой панели\nprefs_dialog.right_folder = Каталог в правой панели\nprefs_dialog.last_folder = Последний посещенный каталог\nprefs_dialog.custom_folder = Заданный каталог\nprefs_dialog.quick_search = Быстрый поиск\nprefs_dialog.quick_search_timeout = Таймаут\nprefs_dialog.show_quick_search_matches_first = Показывать найденныые файлы первыми\nprefs_dialog.quick_search_timeout_never = Без таймаута\nprefs_dialog.quick_search_timeout_sec = Сек\nprefs_dialog.show_hidden_files = Показывать скрытые файлы\nprefs_dialog.show_ds_store_files = Показывать файлы .DS_Store\nprefs_dialog.show_system_folders = Показывать системные каталоги\nprefs_dialog.compact_file_size = Округлять показываемые размеры файлов\nprefs_dialog.follow_symlinks_when_cd = Переходить по ссылкам при смене каталога\nprefs_dialog.show_tab_header = Всегда показывать вкладки\nprefs_dialog.appearance_tab = Внешний вид\nprefs_dialog.look_and_feel = Настройки отображения\nprefs_dialog.icons_size = Размер значков\nprefs_dialog.toolbar_icons = Панель инструментов\nprefs_dialog.command_bar_icons = Панель команд\nprefs_dialog.file_icons = Типы файлов\nprefs_dialog.use_system_file_icons = Использовать системные значки файлов\nprefs_dialog.use_system_file_icons.always = Всегда\nprefs_dialog.use_system_file_icons.never = Никогда\nprefs_dialog.use_system_file_icons.applications = Только для приложений\nprefs_dialog.edit_current_theme = Редактировать тему оформления...\nprefs_dialog.themes = Темы\nprefs_dialog.syntax_themes = Тема подсветки синтаксиса в редакторе\nprefs_dialog.import_theme = Импортировать тему\nprefs_dialog.import_look_and_feel = Импорт настроек внешнего вида\nprefs_dialog.no_look_and_feel = Настроек внешнего вида не найдено.\nprefs_dialog.error_in_import = Ошибка при импорте темы %1.\nprefs_dialog.cannot_read_theme = Не удается загрузить тему из файла %1\nprefs_dialog.export_theme = Экспорт %1\nprefs_dialog.import = Импорт\nprefs_dialog.export = Экспорт\nprefs_dialog.theme_type = Тип: %1\nprefs_dialog.delete_theme = Удалить тему %1 полностью?\nprefs_dialog.delete_look_and_feel = Удалить настройки %1 навсегда?\nprefs_dialog.rename_failed = Не удалось переименовать тему %1\nprefs_dialog.xml_file = XML-файл\nprefs_dialog.jar_file = JAR-файл\nprefs_dialog.mail_tab = Email\nprefs_dialog.mail_settings = Настройки исходящей почты\nprefs_dialog.mail_name = Ваше имя\nprefs_dialog.mail_address = Ваш адрес Email\nprefs_dialog.mail_server = SMTP-сервер\nprefs_dialog.misc_tab = Еще\nprefs_dialog.use_brushed_metal = Использовать тему 'brushed metal' (потребуется перезапуск)\nprefs_dialog.confirm_on_quit = Запрашивать подтверждение при выходе\nprefs_dialog.default_shell = Использовать системную оболочку по умолчанию\nprefs_dialog.custom_shell = Использовать указанную оболочку\nprefs_dialog.shell_encoding = Кодировка командной строки\nprefs_dialog.auto_detect_shell_encoding = Автоопределение\nprefs_dialog.external_terminal = Внешний терминал\nprefs_dialog.default_terminal = Использовать команду по умолчанию\nprefs_dialog.custom_terminal = Использовать указанную команду\nprefs_dialog.iterm_terminal = Использовать iTerm\nprefs_dialog.builtin_terminal = Встроенный терминал\nprefs_dialog.enable_bonjour_discovery = Включить службу обнаружения Bonjour\nprefs_dialog.enable_system_notifications = Разрешить системные уведомления\nprefs_dialog.calculate_folder_size_on_mark = Вычислять размер директорий при выделении\nprefs_dialog.shell = Команда запуска\ndebug_console_dialog.level = Уровень\ndebug_console_dialog.threads = Потоки\ndebug_console_dialog.active_threads = Активные потоки\nunit.byte = байт\nunit.bytes = байтов\nunit.bytes_short = б\nunit.kb = Кб\nunit.mb = Мб\nunit.gb = Гб\nunit.tb = ТБ\nunit.speed = %1/сек\nduration.seconds = %1с\nduration.minutes = %1м\nduration.hours = %1ч\nduration.days = %1д\nduration.months = %1мес\nduration.years = %1г\ntheme.custom_theme = Собственная тема\ntheme.custom = Пользовательская тема\ntheme.built_in = Встроенная\ntheme.add_on = Дополнение\ntheme.current = текущая\ntheme_could_not_be_loaded = В процессе загрузки темы произошла ошибка.\nsetup.title = Добро пожаловать в trolCommander\nsetup.intro = Выберите основные настройки trolCommander.\nsetup.look_and_feel = Выберите пользовательский интерфейс\nsetup.theme = Выберите тему оформления\nfont_chooser.font_size = Размер\nfont_chooser.font_bold = Полужирный\nfont_chooser.font_italic = Курсив\ncolor_chooser.red = Красный\ncolor_chooser.green = Зеленый\ncolor_chooser.blue = Синий\ncolor_chooser.hue = Оттенок\ncolor_chooser.brightness = Яркость\ncolor_chooser.swatches = Образцы\ncolor_chooser.saturation = Насыщенность\ncolor_chooser.rgb = RGB\ncolor_chooser.hsb = HSB\ncolor_chooser.recent = Предыдущий\ncolor_chooser.alpha = Прозрачность\ncolor_chooser.title = Выбор цвета\nbatch_rename_dialog.mask = Шаблон переименования\nbatch_rename_dialog.search_replace = Поиск и замена\nbatch_rename_dialog.search_for = Искать\nbatch_rename_dialog.replace_with = Заменить на\nbatch_rename_dialog.counter = Счетчик\nbatch_rename_dialog.start_at = Начать с\nbatch_rename_dialog.step_by = Шаг\nbatch_rename_dialog.format = Формат\nbatch_rename_dialog.upper_lower_case = Верхний/нижний регистр\nbatch_rename_dialog.no_change = Без изменений\nbatch_rename_dialog.lower_case = нижний регистр\nbatch_rename_dialog.upper_case = ВЕРХНИЙ РЕГИСТР\nbatch_rename_dialog.first_upper = Первая буква заглавная\nbatch_rename_dialog.word = Первый от каждого слова\nbatch_rename_dialog.regexp = RegExp\nbatch_rename_dialog.regexp_error = Синтаксическая ошибка в RegExp-е\nbatch_rename_dialog.old_name = Старое имя\nbatch_rename_dialog.new_name = Новое имя\nbatch_rename_dialog.block_name = Без изменений\nbatch_rename_dialog.range = Интервал\nbatch_rename_dialog.proceed_renaming = Количество файлов %1 из %2 будут переименованны. Начать процесс?\nbatch_rename_dialog.duplicate_names = Совпадение имен!\nbatch_rename_dialog.names_conflict = Конфликт имен! Некоторые значения совпадают в станых и новых именах.\nparent_folders_quick_list.empty_message = Текущее местонахождение не имеет родительского\nrecent_locations_quick_list.empty_message = Не найдено недавних местонахождений\nrecent_executed_files_quick_list.empty_message = Не найдено недавно используемых файлов\nrecent_edited_files_quick_list.empty_message = Не найдено недавно редактируемых файлов\nrecent_viewed_files_quick_list.empty_message = Не найдено недавно просматриваемых файлов\neditor_bookmarks_quick_list.empty_message = Нет закладок\neditor_bookmarks_quick_list.file_not_found = Файл не найден\neditor_bookmarks_quick_list.press_f4_to_edit_list = <html>Нажмите <b>F4</b> для редактирования списка\n#pack.error_on_file = Ошибка при архивации файла %1\n#pack_dialog.cannot_write = Невозможно создать архив в каталоге-получателе\n#unpack.unable_to_open_zip = Не удается открыть архив %1.\n#mkdir_dialog.title = Создание каталога\n#mkdir_dialog.error_title = Ошибка при создании каталога\n#edit_bookmarks_dialog.remove = Удалить\n#mkdir_dialog.description = Создать каталог\n#done = Готово\n#progress_dialog.hide = Скрыть\n#move_dialog.rename_description = Переименовать файл в\n#theme_editor.shell_font = Шрифт в оболочке\n#theme_editor.history_font = Шрифт буфера команд\n#theme_editor.shell_colors = Системные цвета\n#theme_editor.history_colors = Цвета буфера команд\n#auth_dialog.error_was = Ошибка: %1\n#table.hide_column = Скрыть столбец\n#delete.symlink_warning_title = Обнаружена символьная ссылка\n#delete.symlink_warning = Этот файл представляет собой символьную ссылку:\\n\\n  Файл: %1\\n  Ссылается на: %2\\n\\nУдалить только символьную ссылку или\\nПерейти по ссылке и удалить весь каталог (ВНИМАНИЕ - ОПАСНОЕ ДЕЙСТВИЕ)?\n#delete.delete_link_only = Удалить ссылку\n#delete.delete_linked_folder = Удалить каталог\nfind_dialog.name = Имя файла\nfind_dialog.contains = Содержит текст\nfind_dialog.initial_directory = Стартовая директория\nfind_dialog.search_subdirectories = Поиск в поддиректориях\nfind_dialog.search_archives = Поиск в архивах\nfind_dialog.case_sensitive = Различать регистр\nfind_dialog.ignore_hidden = Игнорировать скрытые файлы\nfind_dialog.search_results = Результаты поиска\nfind_dialog.found = Найдено файлов\nfind_dialog.encoding = Кодировка текста\nfind_dialog.search_hex = HEX-байты\nimage_viewer.next_image = Следующий рисунок\nimage_viewer.previous_image = Предыдущий рисунок\nhex_viewer.offset = Адрес\nhex_viewer.ascii_dump = Дамп ASCII\nhex_viewer.view = Просмотр\nhex_viewer.goto = Переход\nhex_viewer.goto.offset = Смещение\nhex_viewer.search = Поиск\nhex_viewer.searchNext = Искать далее\nhex_viewer.searchPrev = Искать предыдущий\nhex_viewer.find = Найти\nhex_view.text = Искать\nhex_viewer.hex = Hex\nhex_viewer.search_not_found = Шаблон не найден\ncalculator.calculator = Калькулятор\ncalculator.expression = Выражение\ncalculator.error = Ошибка в выражении\nvsphere_connections_dialog.guest_password = Гостевой пароль\nvsphere_connections_dialog.guest_user = Гостевой логин\nvsphere_connections_dialog.guest_server = Гостевой сервер %1\ntabs_quick_list.empty_message = Только одна вкладка\ncannot_open_cyclic_symlink = Не могу открыть выбранную ссылку поскольку она циклическая\nroots_quick_list.empty_message = Не найдено корневых директорий\n\nadb.no_devices = Нет устройств\neject.no_mounted_devices = Нет примонтировнных устройств\n\nretry_as_root = Повторить от администратора"
  },
  {
    "path": "src/main/resources/dictionary_sk_SK.properties",
    "content": "ok = OK\nyes = Áno\nno = Nie\ncancel = Zrušiť\nedit = Upraviť\nclose = Zatvoriť\nreset = Obnoviť\nrename = Premenovať\napply = Použiť\nchange = Zmeniť\nsave = Uložiť\ndont_save = Neukladať\nreplace = Nahradiť\ndont_replace = Nenahrádzať\ndelete = Zmazať\nskip = Preskočiť\nskip_all = Preskočiť všetko\nretry = Opakovať\nresume = Pokračovať\noverwrite = Prepísať\noverwrite_if_older = Prepísať staršie\nduplicate = Duplikovať\napply_to_all = Použiť na všetky\ncopy = Kopírovať\nmove = Presunúť\npack = Komprimovať\nunpack = Dekomprimovať\ndownload = Stiahnuť\nsplit = Rozdeliť\ncombine = Spojiť\nbrowse = Prechádzať\nask = Spýtať sa\nstop = Zastaviť\npause = Pauza\nquick_search = Rýchle hľadanie\nfile_manager = Správca súborov\ncreate = Vytvoriť\ncreating_file = Vytvára sa %1\nchoose = Zvoliť\ncustomize = Upraviť\nchoose_folder = Zvoliť priečinok\nlogin = Prihlasovacie meno\npassword = Heslo\nuser = Užívateľ\nencoding = Kódovanie\npreferred_encodings = Predvolené kódovanie\nlicense = Licencia\nname = Meno\nsize = Veľkosť\ndate = Dátum\nextension = Prípona\npermissions = Oprávnenia\nowner = Vlastník\ngroup = Skupina\nlocation = Umiestnenie\nuntitled = Bez mena\nsource = Zdroj\ndestination = Cieľ\nrecurse_directories = Použiť aj pre vnorené zložky\ngo_to = Ísť na\nexample = Príklad\npreview = Ukážka\ncomment = Komentár\nsample_text = Ukážkový text\nnb_files = %1 súbor(y)\nnb_folders = %1 priečinok(priečinky)\nloading = Načítavam obsah...\nthis_operation_cannot_be_undone = Túto operáciu nie je možné vrátiť späť.\nremove = Odstrániť\ndetails = Detaily\nwarning = Upozornenie\nerror = Chyba\ngeneric_error = Pri požadovanej operácii sa vyskytla chyba.\nfolder_does_not_exist = Tento priečinok neexistuje, alebo nie je k dispozícii.\nthis_folder_does_not_exist = Tento priečinok neexistuje, alebo nie je k dispozícii: %1\nthis_file_does_not_exist = Tento súbor neexistuje, alebo nie je k dispozícii: %1\ninvalid_path = Neplatná cesta\ndirectory_already_exists = Priečinok %1 už existuje.\nfile_exists_in_destination = Tento súbor už v cieli existuje\nsource_parent_of_destination = Pokus o presun priečinku do jeho vlastného podpriečinku\ncannot_read_file = Súbor sa nedá čítať %1\ncannot_write_file = Súbor sa nedá zapísať %1\ncannot_create_folder = Priečinok sa nedá vytvoriť %1\ncannot_read_folder = Obsah priečinku sa nedá čítať %1\ncannot_delete_file = Súbor sa nedá zmazať %1\ncannot_delete_folder = Priečinok sa nedá zmazať %1\nerror_while_transferring = Chyba pri prenose súboru %1\nsame_source_destination = Zdrojový a cieľový priečinok sa zhodujú\nfile_already_exists = %1 už existuje, chcete ho prepísať ?\nwrite_error = Chyba zápisu\nread_error = Chyba pri čítaní\nintegrity_check_error = Chyba pri kontrole integrity: zdroj a cieľ sa nezhodujú\nstartup_error = Chyba bráni spusteniu trolCommander-a.\npermissions.read = Čítanie\npermissions.write = Zápis\npermissions.executable = Spúšťanie\npermissions.group = Skupina\npermissions.other = Ostatní\npermissions.octal_notation = Zápis v osmičkovej sústave\naction_categories.all = Všetky\naction_categories.navigation = Navigácia\naction_categories.selection = Výber\naction_categories.view = Zobraziť\naction_categories.file_operations = Operácie so súbormi\naction_categories.windows = Okná\nTerminal.label = Terminal\nTerminal.tooltip = Terminal\nFindFile.label = Find file\nFindFile.tooltip = Find file\nAddBookmark.label = Pridať záložku\nAddBookmark.tooltip = Pridať aktuálny priečinok do zoznamu záložiek\nBatchRename.label = Dávkové premenovanie\nEditBookmarks.label = Editovať záložky\nExploreBookmarks.label = Prechádzať záložky\nEditCredentials.label = Editovať účty\nChangeLocation.label = Zmeniť aktuálne umiestnenie\nChangeDate.label = Zmeniť dátum\nChangeDate.tooltip = Zmeniť dátum vybraných súborov\nChangePermissions.label = Zmeniť oprávnenia\nChangePermissions.tooltip = Zmeniť oprávnenia pre vybrané súbory\nCheckForUpdates.label = Kontrola aktualizácii\nCompareFolders.label = Porovnať priečinky\nConnectToServer.label = Pripojiť na server\nConnectToServer.tooltip = Pripojiť na vzdialený server\nView.label = Zobraziť\nInternalView.label = Zobraziť (interne)\nView.tooltip = Zobraziť označené súbory\nInternalEdit.label = Upraviť (interne)\nEdit.tooltip = Upraviť označené súbory\nCopy.tooltip = Kopírovať označené súbory\nLocalCopy.label = Lokálna kópia\nLocalCopy.tooltip = Kopírovať vybraný súbor do aktuálneho priečinku\nMove.tooltip = Presunúť označené súbory\nRename.tooltip = Premenovať vybrané súbory\nMkdir.label = Vytvoriť priečinok\nMkdir.tooltip = Vytvoriť priečinok v aktuálnom priečinku\nMkfile.label = Vytvoriť súbor\nMkfile.tooltip = Vytvoriť súbor v aktuálnom priečinku\nDelete.tooltip = Zmazať označené súbory (do koša, ak je k dispozícii)\nPermanentDelete.label = Úplne zmazať\nPermanentDelete.tooltip = Úplne zmazať označené súbory\nRefresh.label = Obnoviť\nRefresh.tooltip = Obnoviť aktuálny priečinok\nCloseWindow.label = Zatvoriť okno\nCloseWindow.tooltip = Zatvoriť toto okno\nCopyFileNames.label = Kopírovať názov(názvy)\nCopyFilePaths.label = Kopírovať cestu(y)\nCopyFilesToClipboard.label = Kopírovať súbor(y)\nPasteClipboardFiles.label = Prilepiť súbor(y)\nEmail.label = Poslať súbory e-mailom\nEmail.tooltip = Poslať označené súbory ako prílohu e-mailu\nGoBack.label = Späť\nGoBack.tooltip = Predchádzajúci priečinok\nGoForward.label = Dopredu\nGoForward.tooltip = Nasledujúci priečinok\nGoToHome.label = Domovský priečinok\nGoToParent.label = O úroveň vyššie\nGoToParent.tooltip = Nadradený priečinok\nGoToParentInOtherPanel.label = O úroveň vyššie v druhom panele\nGoToParentInBothPanels.label = O úroveň vyššie v obidvoch paneloch\nGoToRoot.label = Koreňový priečinok\nSortByName.label = Zoradiť podľa názvu\nSortByDate.label = Zoradiť podľa dátumu\nSortBySize.label = Zoradiť podľa veľkosti\nSortByExtension.label = Zoradiť podľa prípony\nSortByPermissions.label = Zoradiť podľa oprávnení\nSortByOwner.label = Zoradiť podľa vlastníka\nSortByGroup.label = Zoradiť podľa skupiny\nMarkGroup.label = Označiť súbory\nMarkGroup.tooltip = Označiť skupinu súborov\nUnmarkGroup.label = Odznačiť súbory\nUnmarkGroup.tooltip = Odznačiť skupinu súborov\nMarkAll.label = Označiť všetko\nUnmarkAll.label = Odznačiť všetko\nMarkSelectedFile.label = Označiť/odznačiť\nMarkSelectedFile.tooltip = Označiť/odznačiť vybraný súbor\nMarkNextBlock.label = Označiť blok dole\nMarkPreviousBlock.label = Označiť blok hore\nMarkNextRow.label = Označiť riadok dole\nMarkPreviousRow.label = Označiť riadok hore\nMarkNextPage.label = Označiť po nasledujúcu stránku\nMarkPreviousPage.label = Označiť po predchádzajúcu stránku\nMarkToFirstRow.label = Označiť súbory po začiatok\nMarkToLastRow.label = Označiť súbory do konca\nMarkExtension.label = Označit príponu\nInvertSelection.label = Obrátiť výber\nSwapFolders.label = Vymeniť priečinky\nSwapFolders.tooltip = Vymeniť ľavý a pravý priečinok\nSetSameFolder.label = Nastaviť ten istý priečinok\nSetSameFolder.tooltip = Nastaviť ten istý priečinok vľavo aj vpravo\nNewWindow.label = Nové okno\nNewWindow.tooltip = Otvoriť nové okno\nOpen.label = Otvoriť\nOpen.tooltip = Otvoriť priečinok / Otvoriť archív / Vykonať\nOpenNatively.label = Otvoriť predvolenou aplikáciou\nOpenNatively.tooltip = Otvoriť vybraný súbor s asociovanou aplikáciou\nOpenInOtherPanel.label = Otvoriť v druhom panely\nOpenInBothPanels.label = Otvoriť v obidvoch paneloch\nRevealInDesktop.label = Otvoriť v aplikácii %1\nRunCommand.label = Spustiť príkaz\nRunCommand.tooltip = Spustiť príkaz v aktuálnom priečinku\nPack.tooltip = Skomprimovať označené súbory do archívu\nUnpack.tooltip = Dekomprimovať súbory z označených archívov\nShowFileProperties.label = Vlastnosti\nShowFileProperties.tooltip = Zobraziť vlastnosti označených súborov\nShowPreferences.label = Nastavenia\nShowPreferences.tooltip = Konfigurovať trolCommander\nShowServerConnections.label = Zobratiť otvorené pripojenia\nQuit.label = Koniec\nReverseSortOrder.label = Obrátiť poradie\nToggleAutoSize.label = Automatická šírka stĺpcov\nStop.label = Zastaviť zmenu priečinka\nToggleColumn.show = Zobraziť stĺpec %1\nToggleColumn.hide = Zobraziť stĺpec %1\nToggleCommandBar.show = Zobraziť panel s príkazmi\nToggleCommandBar.hide = Skryť panel s príkazmi\nToggleToolBar.show = Zobraziť panel s nástrojmi\nToggleToolBar.hide = Skryť panel s nástrojmi\nCustomizeCommandBar.label = Vlastné nastavenie panela príkazov\nToggleStatusBar.show = Zobraziť stavový riadok\nToggleStatusBar.hide = Skryť stavový riadok\nToggleShowFoldersFirst.label = Zobraziť priečinky ako prvé\nToggleTree.label = Zobraziť strom\nPopupLeftDriveButton.label = Zmeniť ľavý priečinok\nPopupRightDriveButton.label = Zmeniť pravý priečinok\nRecallPreviousWindow.label = Prepnúť do predchádzajúceho okna\nRecallNextWindow.label = Prepnúť do nasledujúceho okna\nRecallWindow.label = Prepnúť do okna\nBringAllToFront.label = Okná dopredu\nSwitchActiveTable.label = Prepnúť medzi pravým a ľavým panelom\nSelectNextBlock.label = Skok o jeden blok dole\nSelectPreviousBlock.label = Skok o jeden blok hore\nSelectNextPage.label = Skok o stranu dole\nSelectPreviousPage.label = Skok o stranu hore\nSelectNextRow.label = Skok o riadok dole\nSelectPreviousRow.label = Skok o riadok hore\nSelectFirstRow.label = Vybrať prvý súbor v aktuálnom priečinku\nSelectLastRow.label = Vybrať posledný súbor v označenom priečinku\nSplitEqually.label = Rozdeliť rovnomerne\nSplitVertically.label = Rozdeliť vertikálne\nSplitHorizontally.label = Rozdeliť horizontálne\nShowKeyboardShortcuts.label = Klávesové skratky\nGoToWebsite.label = Webová stránka\nGoToForums.label = Diskusné fórum\nReportBug.label = Ohlásiť chybu\nDonate.label = Darovať príspevok\nShowAbout.label = O aplikácii trolCommander\nOpenTrash.label = Otvoriť kôš\nEmptyTrash.label = Kôš je prázdny\nCalculateChecksum.label = Kontrolný súčet\nMaximizeWindow.label = Maximalizovať\nGoToDocumentation.label = Dokumentácia online\nShowParentFoldersQL.label = Nadradený priečinok\nShowRecentLocationsQL.label = Predchádzajúce umiestnenia\nShowRecentExecutedFilesQL.label = Skôr spustené súbory\nSplitFile.tooltip = Rozdeliť súbor na viacero častí\nCombineFiles.tooltip = Spojiť časti súboru do originálneho súboru\nShowDebugConsole.label = Konzola pre ladenie\nFocusPrevious.label = Označiť predchádzajúcu komponentu\nFocusNext.label = Označiť nasledujúcu komponentu\nfile_menu = Súbor\nfile_menu.open_with = Otvoriť s\nmark_menu = Označiť\nview_menu = Zobraziť\nview_menu.show_hide_columns = Zobraziť/skryť stĺpce\ngo_menu = Prejsť\nbookmarks_menu = Záložky\nbookmarks_menu.no_bookmark = Bez záložiek\nquick_lists_menu = Rýchle prehľady\nwindow_menu = Okno\nhelp_menu = Pomocník\nstatus_bar.selected_files = %1 z %2 vybraných\nstatus_bar.connecting_to_folder = Pripája sa na priečinok, stlač ESC pre zrušenie\nstatus_bar.volume_free = Voľné miesto: %1\nstatus_bar.volume_capacity = Kapacita: %1\nshortcuts_panel.title = Skratky\nshortcuts_panel.restore_defaults = Obnoviť pôvodné\nshortcuts_panel.show = Zobraziť\nshortcuts_panel.default_message = Stlačte Enter alebo dvojklik myšou na skratku, ktorú chcete upraviť\nshortcuts_table.action_description = Popis akcie\nshortcuts_table.shortcut = Skratka\nshortcuts_table.alternate_shortcut = Alternatívna skratka\nshortcuts_table.type_in_a_shortcut = Zadajte skratku\ncommand_bar_dialog.help = Pre prispôsobenie príkazového panelu presuňte tlačidlá\ntable.folder_access_error_title = Chyba pri prístupe k priečinku\ntable.folder_access_error = Obsah priečinka sa nedá čítať\ntable.download_or_browse = Chcete súbor prezerať, alebo stiahnuť?\nversion_dialog.no_new_version_title = Žiadna nová verzia\nversion_dialog.no_new_version = Blahoželáme, máte najnovšiu verziu.\nversion_dialog.new_version_title = Nová verzia k dispozícii\nversion_dialog.new_version = Nová verzia trolCommander-a je k dispozícii.\nversion_dialog.new_version_url = Nová verzia trolCommander-a je k dispozícii na %1.\nversion_dialog.not_available_title = Server je nedostupný\nversion_dialog.not_available = Nie je možné získať informácie o verzii zo serveru.\nversion_dialog.install_and_restart = Nainštalovať a reštartovať\nversion_dialog.preparing_for_update = Pripravujem aktualizáciu...\nquit_dialog.title = Ukončiť trolCommander\nquit_dialog.desc = Chcete zatvoriť všetkých %1 okien a ukončiť trolCommander?\nquit_dialog.show_next_time = Zobraziť nabudúce\ndestination_dialog.file_exists_action = Predvolená akcia pre existujúci súbor\ndestination_dialog.verify_integrity = Overiť integritu\ndestination_dialog.skip_errors = Ignorovať chyby\nfile_collision_dialog.title = Kolízia súboru\nrename_dialog.new_name = Nový názov\ncopy_dialog.destination = Kopírovať vybrané súbory do\ncopy_dialog.error_title = Chyba pri kopírovaní\ncopy_dialog.copying = Kopírujem súbory\ncopy_dialog.copying_file = Kopírujem %1\npack_dialog.packing = Komprimovať súbory\npack_dialog.packing_file = Komprimujem %1\npack_dialog.error_title = Chyba pri kompresii\npack_dialog_description = Pridať vybrané súbory do\npack_dialog.archive_format = Formát archívu\nunpack_dialog.destination = Dekomprimovať vybrané súbory do\nunpack_dialog.error_title = Chyba pre dekompresii\nunpack_dialog.unpacking = Dekomprimujem súbory\nunpack_dialog.unpacking_file = Dekomprimujem %1\noptimizing_archive = Archív sa optimalizuje %1\nerror_while_optimizing_archive = Chyba pri optimalizácii archívu %1\nmove_dialog.move_description = Presunúť do\nmove_dialog.error_title = Chyba pri presúvaní\nmove_dialog.moving = Presúvam súbory\nmove_dialog.moving_file = Presúvam %1\ndownload_dialog.description = Stiahnuť súbor do\ndownload_dialog.error_title = Chyba pri sťahovaní\ndownload_dialog.downloading = Sťahuje sa\ndownload_dialog.downloading_file = Sťahuje sa %1\nmkfile_dialog.allocate_space = Alokovaný priestor\ndelete_dialog.permanently_delete.confirmation = Natrvalo odstrániť vybraný súbor(y) ?\ndelete_dialog.move_to_trash.confirmation = Odstrániť vybraný súbor(y) ?\ndelete_dialog.move_to_trash.confirmation_details = Súbory budú premiestnené do koša.\ndelete_dialog.move_to_trash.option = Premiestniť do koša\ndelete_dialog.move_to_trash.failed = Jeden alebo viacero súborov nemohlo byť presunutých do koša.\ndelete_dialog.deleting = Odstraňujem\ndelete_dialog.error_title = Chyba pri odstraňovaní\ndelete.deleting_file = Odstraňujem %1\nemail_dialog.prefs_not_set_title = Elektronická pošta nie je nastavená\nemail_dialog.prefs_not_set = Najprv je potrebné nastaviť parametre elektronickej pošty\nemail_dialog.from = Od\nemail_dialog.to = Komu\nemail_dialog.subject = Predmet\nemail_dialog.send = Odoslať\nemail_dialog.error_title = Chyba pri odosielaní\nemail_dialog.read_error = Súbory vo vnorených priečinkoch sa nedajú čítať.\nemail_dialog.sending = Odosielam súbory\nemail.sending_file = Odosielam %1\nemail.connecting_to_server = Pripájam sa k %1\nemail.server_unavailable = Nedá sa pripojiť na poštový server %1, skontrolujte nastavenie pošty, alebo akciu opakujte neskôr.\nemail.connection_closed = Server zatvoril pripojenie, pošta nebola odoslaná.\nemail.goodbye_failed = Chyba pri zatváraní pripojenia, pošta nemusela byť odoslaná.\nemail.send_file_error = Súbor %1 sa nedá odoslať, pošta nebola odoslaná.\nsplit_file_dialog.error_title = Chyba pri delení súboru\nsplit_file_dialog.file_to_split = Súbor na rozdelenie\nsplit_file_dialog.target_directory = Cieľový priečinok\nsplit_file_dialog.part_size = Veľkosť jednej časti\nsplit_file_dialog.parts = Počet častí\nsplit_file_dialog.generate_CRC = Generovať CRC súboru\nsplit_file_dialog.max_parts = Maximálny počet častí je %1\nsplit_file_dialog.auto = Automaticky\nsplit_file_dialog.insert_new_media = Vložte nové médium\ncombine_files_dialog.error_title = Chyba pri spájaní súboru\ncombine_files_job.no_crc_file = Spojenie úspešné. Žiadny CRC súbor.\ncombine_files_job.crc_read_error = Chyba pri čítaní CRC súboru.\ncombine_files_job.crc_check_failed = Chybné CRC: očakávané %2, nájdené %1\ncombine_files_job.crc_ok = Spojenie úspešné. CRC ok.\nfile_selection_dialog.mark = Označiť\nfile_selection_dialog.unmark = Odznačiť\nfile_selection_dialog.mark_description = Označiť súbory s názvom\nfile_selection_dialog.unmark_description = Odznačiť súbory s názvom\nfile_selection_dialog.case_sensitive = Rozlišovať veľké/malé písmená\nfile_selection_dialog.include_folders = Zahrnúť priečinky\nprogress_dialog.starting = Prenos spustený...\nprogress_dialog.transferred = Prenesených %1 pri %2\nprogress_dialog.elapsed_time = Uplynulý čas\nprogress_dialog.advanced = Rozšírené\nprogress_dialog.current_speed = Aktuálna rýchlosť\nprogress_dialog.limit_speed = Rýchlostný limit\nprogress_dialog.close_when_finished = Zatvoriť okno po dokončení\nprogress_dialog.processing_files = Spracovávajú sa súbory\nprogress_dialog.processing_file = Spracováva sa %1\nprogress_dialog.verifying_file = Overujem %1\nprogress_dialog.job_finished = Úloha dokončená\nprogress_dialog.job_error = Chyba\nproperties_dialog.file_properties = Vlastnosti\nproperties_dialog.contents = Obsah\nproperties_dialog.calculating = počítam...\ncalculate_checksum_dialog.checksum_algorithm = Algoritmus výpočtu\ncalculate_checksum_dialog.temporary_file = Dočasný súbor\nchange_date_dialog.now = Aktuálny\nchange_date_dialog.specific_date = Špecifický dátum\nrun_dialog.run_command_description = Spustiť v aktuálnom priečinku\nrun_dialog.run_in_home_description = Spustiť v domovskom priečinku\nrun_dialog.command_output = Výstup príkazu\nrun_dialog.run = Spustiť\nrun_dialog.clear_history = Zmazať históriu\nserver_connect_dialog.server_type = Typ pripojenia\nserver_connect_dialog.server = Server\nserver_connect_dialog.share = Zdieľať\nserver_connect_dialog.domain = Doména\nserver_connect_dialog.username = Užívateľské meno\nserver_connect_dialog.initial_dir = Počiatočný priečinok\nserver_connect_dialog.port = Port\nserver_connect_dialog.server_url = Adresa serveru\nserver_connect_dialog.http_url = Adresa web stránky\nserver_connect_dialog.connect = Pripojiť\nserver_connect_dialog.protocol = Protokol\nserver_connect_dialog.nfs_version = Verzia NFS\nserver_connect_dialog.private_key = Súkromý kľúč\nserver_connect_dialog.passphrase = Heslo\nftp_connect.passive_mode = Povoliť pasívny mód\nftp_connect.anonymous_user = Anonymný užívateľ\nftp_connect.nb_connection_retries = Počet pokusov o pripojenie\nftp_connect.retry_delay = Pauza medzi pokusmi (v sekundách)\nhttp_connect.basic_authentication = Základné overenie HTTP (voliteľné)\nserver_connections_dialog.disconnect = Odpojiť\nserver_connections_dialog.connection_busy = Zaneprázdnený\nserver_connections_dialog.connection_idle = Neaktívny\nbonjour.bonjour_services = Služba Bonjour\nbonjour.no_service_discovered = Služba nenájdená\nbonjour.bonjour_disabled = Služba Bonjour nepovolená\nauth_dialog.title = Overenie\nauth_dialog.desc = Zadajte prosím užívateľské meno a heslo\nauth_dialog.server = Server\nauth_dialog.store_credentials = Uložiť užívateľské meno a heslo (slabé šifrovanie)\nauth_dialog.connect_as = Pripojiť ako\nauth_dialog.authentication_failed = Overenie zlyhalo\nsortable_list.move_up = Presunúť vyššie\nsortable_list.move_down = Presunúť nižšie\nadd_bookmark_dialog.add = Pridať\nedit_bookmarks_dialog.new = Nová\nfile_viewer.view_error_title = Chyba pri zobrazovaní\nfile_viewer.view_error = Súbor sa nedá zobraziť.\nfile_viewer.file_menu = Súbor\nfile_viewer.close = Zatvoriť\nfile_viewer.large_file_warning = Tento súbor je pre túto operáciu priveľký.\nfile_viewer.open_anyway = Otvoriť napriek tomu\ntext_viewer.edit = Upraviť\ntext_viewer.copy = Kopírovať\ntext_viewer.select_all = Označiť všetko\ntext_viewer.find = Nájsť\ntext_viewer.find_next = Nájsť nasledujúce\ntext_viewer.find_previous = Nájsť predchádzajúce\ntext_viewer.binary_file_warning = Toto je pravdepodobne binárny súbor\nimage_viewer.controls_menu = Ovládanie\nimage_viewer.zoom_in = Zväčšiť\nimage_viewer.zoom_out = Zmenšiť\nfile_editor.edit_error_title = Chyba pri úprave\nfile_editor.edit_error = Súbor sa nedá upraviť.\nfile_editor.save = Uložiť\nfile_editor.save_as = Uložiť ako...\nfile_editor.save_warning = Uložiť zmeny v tomto súbore pred zatvorením ?\nfile_editor.cannot_write = Do súboru sa nedá zapisovať.\ntext_editor.cut = Vystrihnúť\ntext_editor.paste = Vložiť\nshortcuts_dialog.quick_search.start_search = Vložením ľubovoľného znaku spustíte rýchle hľadanie\nshortcuts_dialog.quick_search.cancel_search = Zrušiť rýchle hľadanie\nshortcuts_dialog.quick_search.remove_last_char = Odstrániť z hľadaného reťazca posledný znak\nshortcuts_dialog.quick_search.jump_to_previous = Prejsť na predchádzajúci výsledok rýchleho hľadania\nshortcuts_dialog.quick_search.jump_to_next = Prejsť na nasledujúci výsledok rýchleho hľadania\nshortcuts_dialog.quick_search.mark_jump_next = Označiť/odznačiť aktuálny súbor a prejsť na nasledujúci výsledok hľadania\ntheme_editor.title = Editor témy\ntheme_editor.folder_tab = Sekcia s priečinkom\ntheme_editor.shell_tab = Príkazový riadok\ntheme_editor.shell_history_tab = História shellu\ntheme_editor.statusbar_tab = Stavový riadok\ntheme_editor.free_space = Voľné miesto\ntheme_editor.free_space.ok = OK\ntheme_editor.free_space.warning = Varovné\ntheme_editor.free_space.critical = Kritické\ntheme_editor.locationbar_tab = Panel s umiestnením\ntheme_editor.editor_tab = Editor súboru\ntheme_editor.font = Písmo\ntheme_editor.active_panel = Aktívne\ntheme_editor.inactive_panel = Neaktívne\ntheme_editor.general = Všeobecné\ntheme_editor.could_not_save_theme = Nie je možné zapísať tému %1\ntheme_editor.border = Rám\ntheme_editor.background = Pozadie\ntheme_editor.alternate_background = Alternatívne pozadie\ntheme_editor.unfocused_background = Pozadie (neaktívne)\ntheme_editor.quick_search.unmatched_file = Jedinečný súbor\ntheme_editor.text = Text\ntheme_editor.progress = Priebeh\ntheme_editor.normal = Bežný\ntheme_editor.normal_unfocused = Bežný (neaktívny)\ntheme_editor.selected = Vybraný\ntheme_editor.selected_unfocused = Vybraný (neaktívny)\ntheme_editor.color = Farba\ntheme_editor.colors = Farby\ntheme_editor.plain_file = Bežný súbor\ntheme_editor.marked_file = Označený súbor\ntheme_editor.hidden_file = Skrutý súbor\ntheme_editor.folder = Priečinok\ntheme_editor.archive_file = Archív\ntheme_editor.symbolic_link = Symbolický odkaz\ntheme_editor.header = Hlavička\ntheme_editor.item = Položka\ncommand_bar_customize_dialog.available_actions = Dostupné akcie\ncommand_bar_customize_dialog.modifier = Modifikátor\nprefs_dialog.title = Nastavenia\nprefs_dialog.general_tab = Všeobecné\nprefs_dialog.day = Deň\nprefs_dialog.month = Mesiac\nprefs_dialog.year = Rok\nprefs_dialog.language = Jazyk (vyžaduje reštart)\nprefs_dialog.date_time = Formát dátumu a času\nprefs_dialog.time = Čas\nprefs_dialog.date = Dátum\nprefs_dialog.date_separator = Oddeľovač\nprefs_dialog.time_12_hour = 12 hodinový formát\nprefs_dialog.time_24_hour = 24 hodinový formát\nprefs_dialog.show_seconds = Zobraziť sekundy\nprefs_dialog.show_century = Zobraziť storočie\nprefs_dialog.check_for_updates_on_startup = Hľadať aktualizácie pri spustení\nprefs_dialog.show_splash_screen = Zobraziť uvítací obrázok\nprefs_dialog.folders_tab = Priečinok\nprefs_dialog.startup_folders = Priečinky pri štarte\nprefs_dialog.left_folder = Ľavý priečinok\nprefs_dialog.right_folder = Pravý priečinok\nprefs_dialog.last_folder = Naposledy navštívený priečinok\nprefs_dialog.custom_folder = Vlastný priečinok\nprefs_dialog.show_hidden_files = Zobraziť skryté súbory\nprefs_dialog.show_ds_store_files = Zobraziť súbory .DS_Store\nprefs_dialog.show_system_folders = Zobraziť systémové priečinky\nprefs_dialog.compact_file_size = Zaokrúhliť zobrazenú veľkosť súborov\nprefs_dialog.follow_symlinks_when_cd = Nasladovať symbolické odkazy pri zmenách aktuálneho priečinku\nprefs_dialog.appearance_tab = Vzhľad\nprefs_dialog.look_and_feel = Vzhľad & chovanie\nprefs_dialog.icons_size = Veľkosť ikon\nprefs_dialog.toolbar_icons = Panel s nástrojmi\nprefs_dialog.command_bar_icons = Panel príkazov\nprefs_dialog.file_icons = Typy súborov\nprefs_dialog.use_system_file_icons = Použiť systémové ikony\nprefs_dialog.use_system_file_icons.always = Vždy\nprefs_dialog.use_system_file_icons.never = Nikdy\nprefs_dialog.use_system_file_icons.applications = Iba pre aplikácie\nprefs_dialog.edit_current_theme = Upraviť aktuálnu tému...\nprefs_dialog.themes = Témy\nprefs_dialog.import_theme = Import témy\nprefs_dialog.import_look_and_feel = Importovať vzhľad & chovanie\nprefs_dialog.no_look_and_feel = Žiadny vzhľad & chovanie nebol nájdený.\nprefs_dialog.error_in_import = Chyba pri importovaní témy %1.\nprefs_dialog.cannot_read_theme = Téma %1 sa nedá načítať\nprefs_dialog.export_theme = Export %1\nprefs_dialog.import = Import\nprefs_dialog.export = Export\nprefs_dialog.theme_type = Typ: %1\nprefs_dialog.delete_theme = Natrvalo odstrániť tému %1 ?\nprefs_dialog.delete_look_and_feel = Navždy odstrániť vzhľad & chovanie %1 ?\nprefs_dialog.rename_failed = Chyba pri premenovaní témy %1\nprefs_dialog.xml_file = XML súbor\nprefs_dialog.jar_file = JAR súbor\nprefs_dialog.mail_tab = E-mail\nprefs_dialog.mail_settings = Nastavenie odchádzajúcej pošty\nprefs_dialog.mail_name = Vaše meno\nprefs_dialog.mail_address = Váš e-mail\nprefs_dialog.mail_server = Server SMTP\nprefs_dialog.misc_tab = Rôzne\nprefs_dialog.use_brushed_metal = Použiť vzhľad 'brushed metal' (vyžaduje reštart)\nprefs_dialog.confirm_on_quit = Pri ukončovaní zobraziť potvrdzujúci dialóg\nprefs_dialog.default_shell = Použiť preddefinovaný systémový shell\nprefs_dialog.custom_shell = Použiť vlastný shell\nprefs_dialog.shell_encoding = Kódovanie textu shellu\nprefs_dialog.auto_detect_shell_encoding = Automatické rozpoznávanie\nprefs_dialog.enable_bonjour_discovery = Povoliť služby Bonjour discoveny\nprefs_dialog.enable_system_notifications = Povoliť systémové oznámenia (v system tray)\ndebug_console_dialog.level = Úroveň\nunit.byte = bajt\nunit.bytes = bajty\nunit.bytes_short = b\nunit.kb = KB\nunit.mb = MB\nunit.gb = GB\nunit.tb = TB\nunit.speed = %1/s\nduration.seconds = %1s\nduration.minutes = %1m\nduration.hours = %1h\nduration.days = %1d\nduration.months = %1mesiacov\nduration.years = %1rokov\ntheme.custom_theme = Užívateľská téma\ntheme.custom = Prispôsobená\ntheme.built_in = Vstavaná\ntheme.add_on = Prídavná\ntheme.current = aktuálna\ntheme_could_not_be_loaded = Chyba pri načítavaní témy.\nsetup.title = Víta Vás trolCommander\nsetup.intro = Prosím, zvoľte vlastné nastavenie aplikácie trolCommander.\nsetup.look_and_feel = Vyberte Vzhľad & chovanie\nsetup.theme = Vyberte tému\nfont_chooser.font_size = Veľkosť\nfont_chooser.font_bold = Tučné\nfont_chooser.font_italic = Šikmé\ncolor_chooser.red = Červená\ncolor_chooser.green = Zelená\ncolor_chooser.blue = Modrá\ncolor_chooser.hue = Hue\ncolor_chooser.brightness = Jas\ncolor_chooser.swatches = Vzorka\ncolor_chooser.saturation = Sýtosť\ncolor_chooser.rgb = RGB\ncolor_chooser.hsb = HSB\ncolor_chooser.recent = Naposledy použité\ncolor_chooser.alpha = Alpha priehľadnosť\ncolor_chooser.title = Vyberte farbu\nbatch_rename_dialog.mask = Maska pre premenovanie\nbatch_rename_dialog.search_replace = Nájsť & nahradiť\nbatch_rename_dialog.search_for = Hľadaný výraz\nbatch_rename_dialog.replace_with = Nahradiť s\nbatch_rename_dialog.counter = Počítadlo\nbatch_rename_dialog.start_at = Počítať od\nbatch_rename_dialog.step_by = Prírastok\nbatch_rename_dialog.format = Formát\nbatch_rename_dialog.upper_lower_case = Veľkosť písmen\nbatch_rename_dialog.no_change = Bez zmeny\nbatch_rename_dialog.lower_case = malé písmená\nbatch_rename_dialog.upper_case = VELKÉ PÍSMENÁ\nbatch_rename_dialog.first_upper = Prvé písmeno veľké\nbatch_rename_dialog.word = Prvé Písmená Veľké\nbatch_rename_dialog.old_name = Pôvodný názov\nbatch_rename_dialog.new_name = Nový názov\nbatch_rename_dialog.block_name = Zachovať\nbatch_rename_dialog.range = Rozsah\nbatch_rename_dialog.proceed_renaming = %1 z %2 souborov bude premenovaných. Chcete pokračovat?\nbatch_rename_dialog.duplicate_names = Rovnaký názov sa už vyskytuje!\nparent_folders_quick_list.empty_message = Umiestnenie nemá nadradený priečinok\nrecent_locations_quick_list.empty_message = Žiadne skôr navštívené umiestnenia neboli nájdené\nrecent_executed_files_quick_list.empty_message = Žiadne skôr spustené súbory neboli nájdené\n#move_dialog.rename_description = Premenovať súbor na\n#theme_editor.shell_font = Písmo príkazového riadku\n#theme_editor.history_font = Písmo histórie\n#theme_editor.shell_colors = Farba terminálu (shell)\n#theme_editor.history_colors = Farba histórie\n#auth_dialog.error_was = Chyba: %1\n#table.hide_column = Skryť stĺpec\n#delete.symlink_warning_title = Nájdený symbolický odkaz\n#delete.symlink_warning = Tento súbor vyzerá ako symbolický odkaz:\\n\\n Súbor:%1\\n Odkazuje na: %2\\n\\nOdstrániť iba symbolický odkaz, alebo\\naj cieľ odkazu (UPOZORNENIE) ?\n#delete.delete_link_only = Odstrániť odkaz\n#delete.delete_linked_folder = Odstrániť priečinok\n#Unpack.label = Dekomprimovať súbory\n#Pack.label = Skomprimovať súbory\n"
  },
  {
    "path": "src/main/resources/dictionary_sl_SL.properties",
    "content": "ok = V redu\nyes = Da\nno = Ne\ncancel = Prekliči\nedit = Uredi\nclose = Zapri\nreset = Resetiraj\nrename = Preimenuj\napply = Potrdi\nchange = Spremeni\nsave = Shrani\ndont_save = Ne shrani\nreplace = Zamenjaj\ndont_replace = Ne zamenjaj\ndelete = Izbriši\nskip = Preskoči\nretry = Poskusi ponovno\nresume = Nadaljuj\noverwrite = Prepiši\noverwrite_if_older = Prepiši starejše\nduplicate = Podvoji\napply_to_all = Uporabi za vse\ncopy = Kopiraj\nmove = Premakni\npack = Stiskanje\nunpack = Razširjanje\ndownload = Shrani na disk\nbrowse = Prebrskaj\nask = Vprašaj\nstop = Ustavi\npause = Pavza\nquick_search = Hitro iskanje\nfile_manager = Urejevalnik datotek\ncreate = Ustvari\ncreating_file = Kreiram %1\nchoose = Izberi\nchoose_folder = Izberi mapo\nlogin = Uporabniško ime\npassword = Geslo\nuser = Uporabnik\nencoding = Kodiranje\nlicense = Licenca\nname = Ime\nsize = Velikost\ndate = Datum\nextension = Končnica\npermissions = Pravice\nowner = Lastnik\ngroup = Skupina\nlocation = Lokacija\nuntitled = Brez naslova\nsource = Izvor\ndestination = Cilj\nrecurse_directories = Recurse directories\ngo_to = Pojdi v\nexample = Primer\npreview = Predogled\ncomment = Komentar\nsample_text = Vzorec besedila\nnb_files = %1 datoteka(e)\nnb_folders = %1 mapa(e)\nloading = Nalagam...\nthis_operation_cannot_be_undone = Ta operacija ne more biti razveljavljena.\nremove = Odstrani\nwarning = Opozorilo\nerror = Napaka\ngeneric_error = Med izvajanjem zahtevane operacije se je zgodila napaka.\nfolder_does_not_exist = Ta mapa ne obstaja oz. ni dostopna.\nthis_folder_does_not_exist = Mapa ne obstaja oz. ni dostopna: %1\nthis_file_does_not_exist = Datoteka ne obstaja oz. ni dostopna: %1\ninvalid_path = Napačna pot: %1\ndirectory_already_exists = Mapa %1 že obstaja.\nfile_exists_in_destination = Datoteka s tem imenom že obstaja\nsource_parent_of_destination = Poskus prenosa mape v eno od njenih podmap\ncannot_read_file = Ne morem prebrati datoteke %1\ncannot_write_file = Ne morem zapisati datoteke %1\ncannot_create_folder = Ne morem ustvariti mape %1\ncannot_read_folder = Ne morem prebrati vsebine mape %1\ncannot_delete_file = Ne morem izbrisati datoteke %1\ncannot_delete_folder = Ne morem izbrisati mape %1\nerror_while_transferring = Napaka pri prenosu datoteke %1\nsame_source_destination = Izvorna in ciljna mapa sta identični\nfile_already_exists = %1 že obstaja, ali jo želi zamenjati?\nwrite_error = Napaka pri zapisovanju\nread_error = Napaka pri branju\nintegrity_check_error = Napaka pri preverjanju integritete: izvor in cilj se ne ujemata\npermissions.read = Branje\npermissions.write = Pisanje\npermissions.executable = Izvršitev\npermissions.group = Skupina\npermissions.other = Ostalo\npermissions.octal_notation = Oktanski sistem\naction_categories.navigation = Navigacija\naction_categories.selection = Izbira\naction_categories.view = Pogled\naction_categories.file_operations = Operacije z datotekami\naction_categories.windows = Okna\nTerminal.label = Terminal\nTerminal.tooltip = Terminal\nFindFile.label = Find file\nFindFile.tooltip = Find file\nAddBookmark.label = Dodaj med priljubljene\nAddBookmark.tooltip = Dodaj trenutno mapo med priljubljene\nBatchRename.label = Preimenuj sveženj\nEditBookmarks.label = Uredi priljubljene\nExploreBookmarks.label = Razišči Priljubljene\nEditCredentials.label = Uredi akreditive\nChangeLocation.label = Spremeni trenutno lokacijo\nChangeDate.label = Spremeni datum\nChangeDate.tooltip = Spremeni datum izbranim datotekam\nChangePermissions.label = Spremeni pravice\nChangePermissions.tooltip = Spremeni pravice izbranih datotek\nCheckForUpdates.label = Nadgradnja programa\nCompareFolders.label = Primerjaj mapi\nConnectToServer.label = Povezava s strežnikom\nConnectToServer.tooltip = Poveži z oddaljenim strežnikom\nView.label = Pogled\nView.tooltip = Poglej izbrano datoteko\nEdit.tooltip = Uredi izbrano datoteko\nCopy.tooltip = Kopiraj označene datoteke\nLocalCopy.label = Lokalno kopiranje\nLocalCopy.tooltip = Kopiraj označeno datoteko v trenutno mapo\nMove.tooltip = Premakni označene datoteke\nRename.tooltip = Preimenuj označeno datoteko\nMkdir.label = Nova mapa\nMkdir.tooltip = Ustvari mapo v trenutni mapi\nMkfile.label = Ustvari datoteko\nMkfile.tooltip = Ustvari datoteko v trenutni mapi\nDelete.tooltip = Izbriši označene datoteke z uporabo koša\nPermanentDelete.label = Trajno brisanje\nPermanentDelete.tooltip = Izbriši označene datoteke brez uporabe koša\nRefresh.label = Osveži\nRefresh.tooltip = Osveži trenutno mapo\nCloseWindow.label = Zapri okno\nCloseWindow.tooltip = Zapri okno\nCopyFileNames.label = Kopiraj ime/-na\nCopyFilePaths.label = Kopiraj pot/-i\nCopyFilesToClipboard.label = Kopiraj datoteko/-e\nPasteClipboardFiles.label = Prilepi datoteko/-e\nEmail.label = Pošiljanje prek e-pošte\nEmail.tooltip = Pošlji označene datoteke kot priponko e-pošte\nGoBack.label = Nazaj\nGoBack.tooltip = Prejšnja mapa\nGoForward.label = Naprej\nGoForward.tooltip = Naslednja mapa\nGoToHome.label = Pojdi v domačo mapo\nGoToParent.label = Izvor\nGoToParent.tooltip = Izvorna mapa\nGoToParentInOtherPanel.label = Pojdi v izvorno mapo v drugem oknu\nGoToParentInBothPanels.label = Pojdi v izvorno mapo v obeh oknih\nGoToRoot.label = Pojdi v koren\nSortByName.label = Razvrsti po imenu\nSortByDate.label = Razvrsti po datumu\nSortBySize.label = Razvrsti po velikosti\nSortByExtension.label = Razvrsti po podaljšku\nSortByPermissions.label = Razvrsti po pravicah\nSortByOwner.label = Razvrsti po lastniku\nSortByGroup.label = Razvrsti po skupini\nMarkGroup.label = Označi datoteke\nMarkGroup.tooltip = Označi skupino datotek\nUnmarkGroup.label = Odznači datoteke\nUnmarkGroup.tooltip = Odznači skupino datotek\nMarkAll.label = Označi vse\nUnmarkAll.label = Odznači vse\nMarkSelectedFile.label = Označi/Odznači\nMarkSelectedFile.tooltip = Označi/Odznači izbrano datoteko\nMarkNextPage.label = Označi datoteke do naslednje strani\nMarkPreviousPage.label = Označi datoteke od prejšnje strani\nMarkToFirstRow.label = Označi datoteke od začetka\nMarkToLastRow.label = Označi datoteke do konca\nMarkExtension.label = Označi končnico\nInvertSelection.label = Obrni izbor\nSwapFolders.label = Zamenjaj mapi\nSwapFolders.tooltip = Zamenjaj levo in desno okno\nSetSameFolder.label = Nastavi isto mapo\nSetSameFolder.tooltip = Nastavi enako mapo v levem in desnem oknu\nNewWindow.label = Novo okno\nNewWindow.tooltip = Odpri v novem oknu\nOpen.label = Odpri\nOpen.tooltip = Odpri mapo / Odpri arhiv / Zaženi\nOpenNatively.label = Odpri izvorno\nOpenNatively.tooltip = Zaženi izbrano datoteko\nOpenInOtherPanel.label = Odpri v drugem oknu\nOpenInBothPanels.label = Odpri v obeh oknih\nRevealInDesktop.label = Razkrij v %1\nRunCommand.label = Ukazna vrstica\nRunCommand.tooltip = Zaženi ukaz s trenutne mape\nPack.tooltip = Dodaj označene datoteke v arhiv\nUnpack.tooltip = Razširi označene arhivske datoteke\nShowFileProperties.label = Lastnosti\nShowFileProperties.tooltip = Prikaži podrobnosti označenih datotek\nShowPreferences.label = Nastavitve\nShowPreferences.tooltip = Konfiguriraj trolCommander\nShowServerConnections.label = Prikaži aktivne povezave\nQuit.label = Izhod\nReverseSortOrder.label = Obrni vrstni red\nToggleAutoSize.label = Samodejna širina stolpcev\nStop.label = Prekini spreminjanje mape\nToggleCommandBar.show = Prikaži ukazno vrstico\nToggleCommandBar.hide = Skrij ukazno vrstico\nToggleToolBar.show = Prikaži orodno vrstico\nToggleToolBar.hide = Skrij orodno vrstico\nToggleStatusBar.show = Prikaži statusno vrstico\nToggleStatusBar.hide = Skrij statusno vrstico\nToggleShowFoldersFirst.label = Najprej prikaži mape\nToggleTree.label = Prikaži drevesno strukturo\nPopupLeftDriveButton.label = Zamenjaj mapo na levi strani\nPopupRightDriveButton.label = Zamenjaj mapo na desni strani\nRecallPreviousWindow.label = Preklopi na prejšnje okno\nRecallNextWindow.label = Preklopi na naslednje okno\nRecallWindow.label = Prikliči okno #%1\nBringAllToFront.label = Postavi vse v ospredje\nSwitchActiveTable.label = Preklopi med levo in desno stranjo\nSelectFirstRow.label = Izberi prvo datoteko v trenutni mapi\nSelectLastRow.label = Izberi zadnjo datoteko v trenutni mapi\nSplitEqually.label = Razdeli enako na obeh straneh\nSplitVertically.label = Razdeli navpično\nSplitHorizontally.label = Razdeli vodoravno\nShowKeyboardShortcuts.label = Funkcije na tipkovnici\nGoToWebsite.label = Domača stran trolCommander\nGoToForums.label = Forum\nReportBug.label = Poročanje o napakah\nDonate.label = Donacije\nShowAbout.label = O programu trolCommander\nOpenTrash.label = Odpri koš\nEmptyTrash.label = Izprazni koš\nCalculateChecksum.label = Izračunaj vsoto\nMaximizeWindow.label = Povečaj\nMaximizeWindow.label.mac_os_x = Povečaj\nMinimizeWindow.label = Minimiziraj\nGoToDocumentation.label = Dokumentacija na spletu\nShowParentFoldersQL.label = Izvorne mape\nShowRecentLocationsQL.label = Nedavne lokacije\nShowRecentExecutedFilesQL.label = Nedavno izvršene datoteke\nfile_menu = Datoteka\nfile_menu.open_with = Odpri z\nmark_menu = Označevanje\nview_menu = Pogled\nview_menu.show_hide_columns = Prikaži/Skrij stolpce\ngo_menu = Pojdi\nbookmarks_menu = Priljubljene\nbookmarks_menu.no_bookmark = Ni priljubljenih\nquick_lists_menu = Hitri seznam\nwindow_menu = Okno\nhelp_menu = Pomoč\nstatus_bar.selected_files = %1 od %2 datotek\nstatus_bar.connecting_to_folder = Povezovanje z mapo, pritisni ESCAPE za preklic.\nstatus_bar.volume_free = Nezasedeno: %1\nstatus_bar.volume_capacity = Kapaciteta: %1\ntable.folder_access_error_title = Napaka pri dostopu do mape\ntable.folder_access_error = Ne morem prebrati vsebine mape\ntable.download_or_browse = BI rad prebrskal ali prenesel na disk to datoteko?\nversion_dialog.no_new_version_title = Ni novejše različice\nversion_dialog.no_new_version = Sedaj imate zadnjo različico.\nversion_dialog.new_version_title = Nova različica je na voljo\nversion_dialog.new_version = Nova različica trolCommander-ja je na razpolago.\nversion_dialog.new_version_url = Nova različica trolCommander-ja se nahaja na %1.\nversion_dialog.not_available_title = Strežnik ni dosegljiv\nversion_dialog.not_available = Ne morem dobiti informacij o različici s strežnika.\nversion_dialog.install_and_restart = Namesti in ponovno zaženi\nversion_dialog.preparing_for_update = Pripravljam za nadgradnjo...\nquit_dialog.title = Izhod iz trolCommander-ja\nquit_dialog.desc = Odprto/-ih je %1 okno/-en. Želite zapreti vsa okna? \nquit_dialog.show_next_time = Prikaži naslednjič\ndestination_dialog.file_exists_action = Privzeto dejanje, ko datoteka obstaja\ndestination_dialog.verify_integrity = Preverjanje integritete podatkov\nfile_collision_dialog.title = Datotečni spor\nrename_dialog.new_name = Novo ime\ncopy_dialog.destination = Kopiraj izbrano v\ncopy_dialog.error_title = Napaka pri kopiranju\ncopy_dialog.copying = Kopiram datoteke\ncopy_dialog.copying_file = Kopiram %1\npack_dialog.packing = Stiskanje datotek\npack_dialog.packing_file = Stiskanje %1\npack_dialog.error_title = Napaka pri stiskanju\npack_dialog_description = Dodaj izbrane datoteke v\npack_dialog.archive_format = Način stiskanja\nunpack_dialog.destination = Razširi izbrano v\nunpack_dialog.error_title = Napaka pri razširjanju\nunpack_dialog.unpacking = Razširjam datoteke\nunpack_dialog.unpacking_file = Razširjam %1\noptimizing_archive = Optimiziranje arhiva %1\nerror_while_optimizing_archive = Napaka pri optimizaciji arhiva %1\nmove_dialog.move_description = Premakni v\nmove_dialog.error_title = Napaka pri premikanju\nmove_dialog.moving = Premikam datoteke\nmove_dialog.moving_file = Premikanje %1\ndownload_dialog.description = Prenesi datoteko v\ndownload_dialog.error_title = Napaka pri prenosu\ndownload_dialog.downloading = Prenašam na disk\ndownload_dialog.downloading_file = Prenašam %1\ndelete_dialog.permanently_delete.confirmation = Trajno izbrišem datoteko(-e)?\ndelete_dialog.move_to_trash.confirmation = Izbrišem izbrane datoteke?\ndelete_dialog.move_to_trash.confirmation_details = Datoteke bodo premaknjene v koš.\ndelete_dialog.move_to_trash.option = Premakni v koš\ndelete_dialog.deleting = Brisanje\ndelete_dialog.error_title = Napaka pri brisanju\ndelete.deleting_file = Brisanje %1\nemail_dialog.prefs_not_set_title = E-pošta ni nastavljena\nemail_dialog.prefs_not_set = Vnesite parametre za e-pošto.\nemail_dialog.from = Pošiljatelj\nemail_dialog.to = Prejemnik\nemail_dialog.subject = Zadeva\nemail_dialog.send = Pošlji\nemail_dialog.error_title = Napaka pri pošiljanju\nemail_dialog.read_error = Ne morem prebrati dateteke v podmapi.\nemail_dialog.sending = Pošiljam datoteke\nemail.sending_file = Pošiljam %1\nemail.connecting_to_server = Povezovanje z %1\nemail.server_unavailable = Ne morem se povezati z e-poštnim strežnikom %1, preverite nastavitve ali poskusite kasneje.\nemail.connection_closed = Povezava s strežnikom zaključena, sporočilo ni poslano.\nemail.goodbye_failed = Napaka pri zaključevanju povezave, sporočilo morda ni poslano.\nemail.send_file_error = Ne morem poslati datoteke %1, sporočilo ni poslano.\nfile_selection_dialog.mark = Označi\nfile_selection_dialog.unmark = Odznači\nfile_selection_dialog.mark_description = Označi datoteke z imenom\nfile_selection_dialog.unmark_description = Odznači datoteke z imenom\nfile_selection_dialog.case_sensitive = Občutljiv na velike in male črke\nfile_selection_dialog.include_folders = Vključi mape\nprogress_dialog.starting = Prenos se izvaja...\nprogress_dialog.transferred = Prenešeno %1 od %2\nprogress_dialog.elapsed_time = Pretečen čas\nprogress_dialog.advanced = Napredno\nprogress_dialog.current_speed = Trenutna hitrost\nprogress_dialog.limit_speed = Omejitev hitrosti\nprogress_dialog.close_when_finished = Po prenosu zapri okno\nprogress_dialog.processing_files = Obdelujem datoteke\nprogress_dialog.processing_file = Obdelujem %1\nprogress_dialog.verifying_file = Preverjam %1\nprogress_dialog.job_finished = Naloga zaključena\nprogress_dialog.job_error = Napaka pri izvršitvi naloge\nproperties_dialog.file_properties = %1 Lastnosti\nproperties_dialog.contents = Vsebina\nproperties_dialog.calculating = izračunavam...\ncalculate_checksum_dialog.checksum_algorithm = Algoritem\ncalculate_checksum_dialog.temporary_file = Začasna datoteka\nchange_date_dialog.now = Trenutni\nchange_date_dialog.specific_date = Določi datum\nrun_dialog.run_command_description = Zaženi v trenutni mapi\nrun_dialog.run_in_home_description = Zaženi v domači mapi\nrun_dialog.command_output = Rezultat ukaza\nrun_dialog.run = Zaženi\nrun_dialog.clear_history = Počisti zgodovino\nserver_connect_dialog.server_type = Tip povezave\nserver_connect_dialog.server = Strežnik\nserver_connect_dialog.share = Share\nserver_connect_dialog.username = Uporabniško ime\nserver_connect_dialog.initial_dir = Začetna mapa\nserver_connect_dialog.port = Vrata\nserver_connect_dialog.server_url = URL strežnika\nserver_connect_dialog.http_url = Naslov spletne strani\nserver_connect_dialog.connect = Poveži\nserver_connect_dialog.protocol = Protokol\nserver_connect_dialog.nfs_version = NFS različica\nserver_connect_dialog.private_key = Zasebni ključ\nserver_connect_dialog.passphrase = Geslo\nftp_connect.passive_mode = Omogoči pasivni način\nftp_connect.anonymous_user = Anonimni uporabnik\nftp_connect.nb_connection_retries = Število poskusov povezave\nftp_connect.retry_delay = Zamik med poskusi (v sekundah)\nhttp_connect.basic_authentication = HTTP osnovno overjanje (izbirno)\nserver_connections_dialog.disconnect = Prekini povezavo\nserver_connections_dialog.connection_busy = Zasedeno\nserver_connections_dialog.connection_idle = Neaktivno\nbonjour.bonjour_services = \"Bonjour\" storitve\nbonjour.no_service_discovered = Ni aktivne storitve\nbonjour.bonjour_disabled = \"Bonjour\" onemogočen\nauth_dialog.title = Avtentikacija\nauth_dialog.desc = Vnesite uporabniško ime in geslo\nauth_dialog.server = Strežnik\nauth_dialog.store_credentials = Shrani uporabniško ime in geslo (nizka enkripcija)\nauth_dialog.connect_as = Poveži kot\nauth_dialog.authentication_failed = Avtentikacija neuspešna\nsortable_list.move_up = Premakni gor\nsortable_list.move_down = Premakni dol\nadd_bookmark_dialog.add = Dodaj\nedit_bookmarks_dialog.new = Nova\nfile_viewer.view_error_title = Napaka pri pregledu\nfile_viewer.view_error = Ne morem pregledati datoteke.\nfile_viewer.file_menu = Datoteka\nfile_viewer.close = Zapri\nfile_viewer.large_file_warning = Datoteka je morda prevelika za to opreacijo.\nfile_viewer.open_anyway = Odpri vseeno\ntext_viewer.edit = Uredi\ntext_viewer.copy = Kopiraj\ntext_viewer.select_all = Izberi vse\ntext_viewer.find = Najdi\ntext_viewer.find_next = Najdi naslednje\ntext_viewer.find_previous = Najdi prejšnje\ntext_viewer.binary_file_warning = Kaže se kot binarna datoteka\nimage_viewer.controls_menu = Kontrolne tipke\nimage_viewer.zoom_in = Približaj\nimage_viewer.zoom_out = Oddalji\nfile_editor.edit_error_title = Napaka pri urejanju\nfile_editor.edit_error = Ne morem urejati datoteke.\nfile_editor.save = Shrani\nfile_editor.save_as = Shrani kot ...\nfile_editor.save_warning = Shranim spremembe pred zapiranjem?\nfile_editor.cannot_write = Ne morem zapisati datoteke.\ntext_editor.cut = Izreži\ntext_editor.paste = Prilepi\nshortcuts_dialog.quick_search.start_search = Vnesi kateri koli znak za začetek hitrega iskanja\nshortcuts_dialog.quick_search.cancel_search = Prekliči hitro iskanje\nshortcuts_dialog.quick_search.remove_last_char = Odstrani zadnji znak iz vrstice hitrega iskanja\nshortcuts_dialog.quick_search.jump_to_previous = Preskoči na prejšnji rezultat hitrega iskanja\nshortcuts_dialog.quick_search.jump_to_next = Preskoči na naslednji rezultat hitrega iskanja\nshortcuts_dialog.quick_search.mark_jump_next = Označi/Odznači trenutno datoteko in preskoči na naslednji rezultat iskanja\ntheme_editor.title = Urejevalnik tem\ntheme_editor.folder_tab = Vrstica z mapami\ntheme_editor.shell_tab = Ogrodje\ntheme_editor.shell_history_tab = Ogrodje zgodovine\ntheme_editor.statusbar_tab = Statusna vrstica\ntheme_editor.free_space = Nezaseden prostor\ntheme_editor.free_space.ok = OK\ntheme_editor.free_space.warning = Opozorilo\ntheme_editor.free_space.critical = Kritično\ntheme_editor.locationbar_tab = Lokacijska vrstica\ntheme_editor.editor_tab = Urejevalnik datotek\ntheme_editor.font = Pisava\ntheme_editor.active_panel = Aktivno\ntheme_editor.inactive_panel = Neaktivno\ntheme_editor.general = Splošno\ntheme_editor.could_not_save_theme = Ne morem zapisati teme %1\ntheme_editor.border = Okvir\ntheme_editor.background = Ozadje\ntheme_editor.alternate_background = Ozadje (izmenično)\ntheme_editor.unfocused_background = Ozadje \ntheme_editor.quick_search.unmatched_file = Neustrezna datoteka\ntheme_editor.text = Pisava\ntheme_editor.progress = Napredek\ntheme_editor.normal = Normalno\ntheme_editor.normal_unfocused = Normalno (without focus)\ntheme_editor.selected = Izbrano\ntheme_editor.selected_unfocused = Izbrano (without focus)\ntheme_editor.color = Barva\ntheme_editor.colors = Barve\ntheme_editor.plain_file = Navadna datoteka\ntheme_editor.marked_file = Označena datoteka\ntheme_editor.hidden_file = Skrita datoteka\ntheme_editor.folder = Mapa\ntheme_editor.archive_file = Arhivska datoteka\ntheme_editor.symbolic_link = Simbolna povezava\nprefs_dialog.title = Lastnosti\nprefs_dialog.general_tab = Splošno\nprefs_dialog.day = Dan\nprefs_dialog.month = Mesec\nprefs_dialog.year = Leto\nprefs_dialog.language = Jezik (zahteva ponovni zagon programa)\nprefs_dialog.date_time = Format datuma in časa\nprefs_dialog.time = Čas\nprefs_dialog.date = Datum\nprefs_dialog.date_separator = Ločilo\nprefs_dialog.time_12_hour = 12-urni format\nprefs_dialog.time_24_hour = 24-urni format\nprefs_dialog.show_seconds = Prikaži sekunde\nprefs_dialog.show_century = Prikaži stoletje\nprefs_dialog.check_for_updates_on_startup = Preglej za posodobitve ob zagonu\nprefs_dialog.show_splash_screen = Prikaži brizgana okna\nprefs_dialog.folders_tab = Mape\nprefs_dialog.startup_folders = Mape ob zagonu\nprefs_dialog.left_folder = Levo okno\nprefs_dialog.right_folder = Desno okno\nprefs_dialog.last_folder = Zadnja odprta mapa\nprefs_dialog.custom_folder = Mapa po meri\nprefs_dialog.show_hidden_files = Prikaži skrite datoteke\nprefs_dialog.show_ds_store_files = Prikaži .DS arhivske datoteke\nprefs_dialog.show_system_folders = Prikaži sistemske mape\nprefs_dialog.compact_file_size = Zaokroži prikazane velikosti datotek\nprefs_dialog.follow_symlinks_when_cd = Sledi simbolnim povezavam, ko spremeniš trenutno mapo\nprefs_dialog.appearance_tab = Videz\nprefs_dialog.look_and_feel = Shema\nprefs_dialog.icons_size = Velikost ikone\nprefs_dialog.toolbar_icons = Orodna vrstica\nprefs_dialog.command_bar_icons = Ukazna vrstica\nprefs_dialog.file_icons = Vrste datotek\nprefs_dialog.use_system_file_icons = Uporabi sistemske ikone\nprefs_dialog.use_system_file_icons.always = Vedno\nprefs_dialog.use_system_file_icons.never = Nikoli\nprefs_dialog.use_system_file_icons.applications = Samo za aplikacije\nprefs_dialog.edit_current_theme = Uredi trenutno temo...\nprefs_dialog.themes = Teme\nprefs_dialog.import_theme = Uvoz teme\nprefs_dialog.import_look_and_feel = Uvozi \"Look & feel\"\nprefs_dialog.no_look_and_feel = Ne najdem \"Look & feel\"\nprefs_dialog.error_in_import = Napaka pri uvozu teme %1.\nprefs_dialog.cannot_read_theme = Ne morem naložiti teme iz datoteke %1\nprefs_dialog.export_theme = Izvoz %1\nprefs_dialog.import = Uvoz\nprefs_dialog.export = Izvoz\nprefs_dialog.theme_type = Vrsta: %1\nprefs_dialog.delete_theme = Trajno izbrišem temo %1?\nprefs_dialog.delete_look_and_feel = Trajno izbrišem \"look & feel\" %1 ?\nprefs_dialog.rename_failed = Ne morem preimenovati teme %1\nprefs_dialog.xml_file = XML datoteka\nprefs_dialog.jar_file = JAR datoteka\nprefs_dialog.mail_tab = E-pošta\nprefs_dialog.mail_settings = Nastavitve odhodne pošte\nprefs_dialog.mail_name = Ime\nprefs_dialog.mail_address = Naslov vaše e-pošte\nprefs_dialog.mail_server = SMTP strežnik\nprefs_dialog.misc_tab = Razno\nprefs_dialog.use_brushed_metal = Uporabi shemo \"brušena kovina\" (zahteva ponovni zagon)\nprefs_dialog.confirm_on_quit = Pokaži pogovorno okno za potrditev izhoda\nprefs_dialog.default_shell = Uporabi privzeto izvajanje ukazov\nprefs_dialog.custom_shell = Uporabi izvajanje ukazov po meri\nprefs_dialog.shell_encoding = Kodiranje ogrodja\nprefs_dialog.auto_detect_shell_encoding = Samodejno zaznaj\nprefs_dialog.enable_bonjour_discovery = Omogoči odkrivanje \"Bonjour\" storitev\nprefs_dialog.enable_system_notifications = Omogoči sistemska opozorila\nunit.byte = bajt\nunit.bytes = bajtov\nunit.bytes_short = b\nunit.kb = KB\nunit.mb = MB\nunit.gb = GB\nunit.tb = TB\nunit.speed = %1/s\nduration.seconds = %1s\nduration.minutes = %1min\nduration.hours = %1h\nduration.days = %1d\nduration.months = %1m\nduration.years = %1l\ntheme.custom_theme = Običajna tema\ntheme.custom = Prirejeno\ntheme.built_in = Vgrajeno\ntheme.add_on = Dodatek\ntheme.current = Trenutno\ntheme_could_not_be_loaded = Napaka med nalaganjem teme.\nsetup.title = Dobrodošli v trolCommanderju\nsetup.intro = Izberite prosim lastnosti trolCommanderja.\nsetup.look_and_feel = Izberite temo in videz\nsetup.theme = Izberite temo\nfont_chooser.font_size = Velikost\nfont_chooser.font_bold = Krepko\nfont_chooser.font_italic = Poševno\ncolor_chooser.red = Rdeča\ncolor_chooser.green = Zelena\ncolor_chooser.blue = Modra\ncolor_chooser.hue = Odtenek\ncolor_chooser.brightness = Svetlost\ncolor_chooser.swatches = Swatches\ncolor_chooser.saturation = Zasičenost\ncolor_chooser.rgb = RGB\ncolor_chooser.hsb = HSB\ncolor_chooser.recent = Nedaven\ncolor_chooser.alpha = Alfa prepustnost\ncolor_chooser.title = Izberi barvo\nbatch_rename_dialog.mask = Preimenuj vzorec\nbatch_rename_dialog.search_replace = Išči in zamenjaj\nbatch_rename_dialog.search_for = Iskanje za\nbatch_rename_dialog.replace_with = Zamenjaj z\nbatch_rename_dialog.counter = Števec\nbatch_rename_dialog.start_at = Začni v\nbatch_rename_dialog.step_by = Korak po\nbatch_rename_dialog.format = Formatiraj\nbatch_rename_dialog.upper_lower_case = Velike/Male črke\nbatch_rename_dialog.no_change = Nespremenjeno\nbatch_rename_dialog.lower_case = male črke\nbatch_rename_dialog.upper_case = VELIKE ČRKE\nbatch_rename_dialog.first_upper = Velika začetnica\nbatch_rename_dialog.word = Velika Začetnica Vsake Besede\nbatch_rename_dialog.old_name = Staro ime\nbatch_rename_dialog.new_name = Novo ime\nbatch_rename_dialog.block_name = Ohrani\nbatch_rename_dialog.range = Razpon\nbatch_rename_dialog.proceed_renaming = %1 datotek od %2 bo preimenovanih. Nadaljujem?\nbatch_rename_dialog.duplicate_names = Podvojena imena!\nparent_folders_quick_list.empty_message = Trenutna lokacija nima izvora\nrecent_locations_quick_list.empty_message = Ni nedavne lokacije\nrecent_executed_files_quick_list.empty_message = Ni nedavno zagnanih datotek\n#server_connect_dialog.auth_error = Napačno uporabniško ime ali geslo\n#move_dialog.cannot_move_to_itself = Ne morem premakniti datotek v podmapo\n#pack.error_on_file = Napaka med stiskanjem %1\n#pack_dialog.cannot_write = Ne morem ustvariti stisnjene datoteke v ciljni mapi.\n#unpack.unable_to_open_zip = Ne morem odpreti stisnjene datoteke %1.\n#image_viewer.previous_image = Prejšnja slika\n#image_viewer.next_image = Naslednja slika\n#mkdir_dialog.title = Nova mapa\n#mkdir_dialog.error_title = Napaka pri ustvarjanju mape\n#edit_bookmarks_dialog.remove = Odstrani\n#mkdir_dialog.description = Ustvari novo mapo\n#mkfile_dialog.description = Ustvari novo prazno datoteko\n#done = Narejeno\n#progress_dialog.hide = Skrij\n#move_dialog.rename_description = Preimenuj datoteko v\n#theme_editor.shell_font = Pisava ogrodja\n#theme_editor.history_font = Pisava zgodovine\n#theme_editor.shell_colors = Barve v ukazni vrstici\n#theme_editor.history_colors = Barve v menuju Zgodovina\n#ToggleHiddenFiles.hide = Skrij skrite datoteke\n#auth_dialog.error_was = Napaka: %1\n#table.hide_column = Skrij stolpec\n#delete.symlink_warning_title = Najdena simbolična povezava\n#delete.symlink_warning = Datoteka jo podobna simbolični povezavi:\\n\\n  File: %1\\n  Povezava z: %2\\n\\nIzbriši simbolično povezavo ali\\nSledi simbolični povezavi in izbriši mapo?\n#delete.delete_link_only = Izbriši povezavo\n#delete.delete_linked_folder = Izbriši mapo\n#Unpack.label = Razširjanje datotek\n#Pack.label = Stiskanje datotek\n"
  },
  {
    "path": "src/main/resources/dictionary_sv_SV.properties",
    "content": "ok = OK\nyes = Ja\nno = Nej\ncancel = Avbryt\nedit = Redigera\nclose = Stäng\nreset = Rensa\nrename = Döp om\napply = Utför\nchange = Ändra\nsave = Spara\ndont_save = Spara inte\nreplace = Ersätt\ndont_replace = Ersätt inte\ndelete = Radera\nskip = Hoppa över\nskip_all = Hoppa över alla\nretry = Försök igen\nresume = Återuppta\noverwrite = Skriv över\noverwrite_if_older = Skriv över äldre\nduplicate = Duplicera\napply_to_all = Utför allt\ncopy = Kopiera\nmove = Flytta\npack = Komprimera\nunpack = Extrahera\ndownload = Ladda ner\nsplit = Dela\ncombine = Sammanfläta\nbrowse = Bläddra\nask = Fråga\nstop = Stoppa\npause = Pausa\nquick_search = Snabbsök\nfile_manager = Filhanterare\ncreate = Skapa\ncreating_file = Skapar %1\nchoose = Välj\ncustomize = Anpassa\nchoose_folder = Välj mapp\nlogin = Användarnamn\npassword = Lösenord\nuser = Användare\nencoding = Textkodning\npreferred_encodings = Förvald textkodning\nlicense = Licens\nname = Namn\nsize = Storlek\ndate = Datum\nextension = Filtyp\npermissions = Rättigheter\nowner = Ägare\ngroup = Grupp\nlocation = Plats\nuntitled = Namnlös\nsource = Källa\ndestination = Mål\nrecurse_directories = Behandla markerade mappar rekursivt\ngo_to = Gå till\nexample = Exempel\npreview = Förhandsgranska\ncomment = Kommentar\nsample_text = Exempel på text\nnb_files = %1 fil(er)\nnb_folders = %1 mapp(ar)\nloading = Laddar...\nthis_operation_cannot_be_undone = Du kan inte ångra denna handling.\nremove = Ta bort\ndetails = Detaljer\nwarning = Varning\nerror = Fel\ngeneric_error = Ett fel uppstod när handlingen utfördes.\nfolder_does_not_exist = Mappen finns inte eller är inte tillgänglig.\nthis_folder_does_not_exist = Mappen finns inte eller är inte tillgänglig: %1\nthis_file_does_not_exist = Filen finns inte eller är inte tillgänglig: %1\ninvalid_path = Ogiltig sökväg: %1\ndirectory_already_exists = Mappen %1 finns redan.\nfile_exists_in_destination = Filen finns redan på denna plats.\nsource_parent_of_destination = Försöker flytta mappen till en av dess undermappar\ncannot_read_file = Kan inte läsa filen %1\ncannot_write_file = Kan inte skriva till filen %1\ncannot_create_folder = Kan inte skapa mappen %1\ncannot_read_folder = Kan inte läsa innehållet i mappen %1\ncannot_delete_file = Kan inte ta bort filen %1\ncannot_delete_folder = Kan inte ta bort mappen %1\nerror_while_transferring = Fel vid överföring av filen %1\nsame_source_destination = Källan och målet är densamma\nfile_already_exists = %1 finns redan, vill du ersätta den?\nwrite_error = Skrivfel\nread_error = Läsfel\nintegrity_check_error = Integritetscheck fel: källan och målet överensstämmer inte\nstartup_error = Ett fel förhindrade trolCommander från att starta.\npermissions.read = Läsbar\npermissions.write = Skrivbar\npermissions.executable = Exekverbar\npermissions.group = Grupp\npermissions.other = Annat\npermissions.octal_notation = Oktal notation\naction_categories.all = Alla\naction_categories.navigation = Navigation\naction_categories.selection = Markering\naction_categories.view = Visa\naction_categories.file_operations = Filåtgärder\naction_categories.windows = Fönster\nTerminal.label = Terminal\nTerminal.tooltip = Terminal\nFindFile.label = Find file\nFindFile.tooltip = Find file\nAddBookmark.label = Nytt bokmärke\nAddBookmark.tooltip = Lägg till aktuell mapp i bokmärken\nBatchRename.label = Döp om filer\nEditBookmarks.label = Redigera bokmärken\nExploreBookmarks.label = Visa bokmärken\nEditCredentials.label = Redigera referenser\nChangeLocation.label = Ändra aktuell sökväg\nChangeDate.label = Ändra datum\nChangeDate.tooltip = Ändra datum på markerad(e) fil(er)\nChangePermissions.label = Ändra rättigheter\nChangePermissions.tooltip = Ändra rättigheter på markerad(e) fil(er)\nCheckForUpdates.label = Sök uppdateringar\nCompareFolders.label = Jämför mappar\nConnectToServer.label = Anslut till server\nConnectToServer.tooltip = Anslut till fjärrserver\nView.label = Visa\nInternalView.label = Visa (intern)\nView.tooltip = Visa markerad fil\nInternalEdit.label = Redigera (intern)\nEdit.tooltip = Ändra markerad fil\nCopy.tooltip = Kopiera markerade filer\nLocalCopy.label = Lokal kopia\nLocalCopy.tooltip = Kopiera markerad fil till aktuell mapp\nMove.tooltip = Flytta markerade filer\nRename.tooltip = Döp om markerad fil\nMkdir.label = Ny mapp\nMkdir.tooltip = Skapa ny mapp i aktuell mapp\nMkfile.label = Ny fil\nMkfile.tooltip = Skapa ny fil i aktuell mapp\nDelete.tooltip = Radera markerade filer till papperskorgen när det är möjligt\nPermanentDelete.label = Radera permanent\nPermanentDelete.tooltip = Radera markerade filer utan att använda papperskorgen\nRefresh.label = Uppdatera\nRefresh.tooltip = Uppdatera aktuell mapp\nCloseWindow.label = Stäng fönster\nCloseWindow.tooltip = Stäng detta fönster\nCopyFileNames.label = Kopiera namn(en)\nCopyFilePaths.label = Kopiera sökväg(en)\nCopyFilesToClipboard.label = Kopiera fil(er)\nPasteClipboardFiles.label = Klistra in fil(er)\nEmail.label = Mejla filer\nEmail.tooltip = Mejla markerade filer\nGoBack.label = Tillbaka\nGoBack.tooltip = Föregående mapp\nGoForward.label = Framåt\nGoForward.tooltip = Nästa mapp\nGoToHome.label = Hemmamappen\nGoToParent.label = Överordnad\nGoToParent.tooltip = Till överordnad mapp\nGoToParentInOtherPanel.label = Gå till överordnad mapp i andra vyn\nGoToParentInBothPanels.label = Gå till överordnad mapp i båda vyerna\nGoToRoot.label = Gå till root\nSortByName.label = Sortera efter Namn\nSortByDate.label = Sortera efter Datum\nSortBySize.label = Sortera efter Storlek\nSortByExtension.label = Sortera efter Filtyp\nSortByPermissions.label = Sortera efter Rättigheter\nSortByOwner.label = Sortera efter Ägare\nSortByGroup.label = Sortera efter Grupp\nMarkGroup.label = Markera filer\nMarkGroup.tooltip = Markera en grupp filer\nUnmarkGroup.label = Avmarkera filer\nUnmarkGroup.tooltip = Avmarkera en grupp filer\nMarkAll.label = Markera allt\nUnmarkAll.label = Avmarkera allt\nMarkSelectedFile.label = Markera/Avmarkera\nMarkSelectedFile.tooltip = Markera/Avmarkera valda filer\nMarkNextBlock.label = Markera ett block neråt\nMarkPreviousBlock.label = Markera ett block uppåt\nMarkNextRow.label = Markera en rad neråt\nMarkPreviousRow.label = Markera en rad uppåt\nMarkNextPage.label = Markera sidan neråt\nMarkPreviousPage.label = Markera sidan uppåt\nMarkToFirstRow.label = Markera filer till början\nMarkToLastRow.label = Markera filer till slutet\nMarkExtension.label = Markera filtyp\nInvertSelection.label = Omvänd markering\nSwapFolders.label = Växla vyer\nSwapFolders.tooltip = Växla vänster och höger vy\nSetSameFolder.label = Visa samma vy\nSetSameFolder.tooltip = Visa samma vy till vänster och höger\nNewWindow.label = Nytt fönster\nNewWindow.tooltip = Öppna ett nytt fönster\nOpen.label = Öppna\nOpen.tooltip = Öppna mapp / Öppna arkiv / Kör\nOpenNatively.label = Öppna med förvalt\nOpenNatively.tooltip = Öppna markerad filen med systemets förvalda filanknytning\nOpenInOtherPanel.label = Öppna i andra vyn\nOpenInBothPanels.label = Öppna i båda vyerna\nRevealInDesktop.label = Visa i %1\nRunCommand.label = Kör kommando\nRunCommand.tooltip = Kör ett kommando i aktuell mapp\nPack.tooltip = Komprimera markerade filer till ett arkiv\nUnpack.tooltip = Extrahera markerade filarkiv\nShowFileProperties.label = Egenskaper\nShowFileProperties.tooltip = Visa egenskaper för markerade filer\nShowPreferences.label = Inställningar\nShowPreferences.tooltip = Konfigurera trolCommander\nShowServerConnections.label = Visa öppna anslutningar\nQuit.label = Avsluta\nReverseSortOrder.label = Omvänd sortering\nToggleAutoSize.label = Automatisk kolumnbredd\nStop.label = Stoppa mappbyte\nToggleColumn.show = Visa %1 kolumn\nToggleColumn.hide = Göm %1 kolumn\nToggleCommandBar.show = Visa kommandoraden\nToggleCommandBar.hide = Dölj kommandoraden\nToggleToolBar.show = Visa verktygsfältet\nToggleToolBar.hide = Dölj vertygsfältet\nCustomizeCommandBar.label = Anpassa kommandoraden\nToggleStatusBar.show = Visa statuslisten\nToggleStatusBar.hide = Dölj statuslisten\nToggleShowFoldersFirst.label = Visa mappar först\nToggleTree.label = Visa trädvy\nPopupLeftDriveButton.label = Ändra vänster vy\nPopupRightDriveButton.label = Ändra höger vy\nRecallPreviousWindow.label = Återkalla föregående fönster\nRecallNextWindow.label = Återkalla nästa fönster\nRecallWindow.label = Återkalla fönster #%1\nBringAllToFront.label = Lägga alla överst\nSwitchActiveTable.label = Växla mellan vänster och höger vy\nSelectNextBlock.label = Hoppa ett block neråt\nSelectPreviousBlock.label = Hoppa ett block uppåt\nSelectNextPage.label = Hoppa en sida neråt\nSelectPreviousPage.label = Hoppa en sida uppåt\nSelectNextRow.label = Hoppa en rad neråt\nSelectPreviousRow.label = Hoppa en rad uppåt\nSelectFirstRow.label = Välj första filen i aktuell vy\nSelectLastRow.label = Välj sista filen i aktuell vy\nSplitEqually.label = Dela lika\nSplitVertically.label = Dela vertikalt\nSplitHorizontally.label = Dela horisontellt\nShowKeyboardShortcuts.label = Kortkommandon\nGoToWebsite.label = Besök hemsidan\nGoToForums.label = Besök forum\nReportBug.label = Rapportera en bugg\nDonate.label = Donera\nShowAbout.label = Om trolCommander\nOpenTrash.label = Öppna papperskorg\nEmptyTrash.label = Töm papperskorg\nCalculateChecksum.label = Beräkna kontrollsumma\nMaximizeWindow.label = Maximera\nMaximizeWindow.label.mac_os_x = Zooma\nMinimizeWindow.label = Minimera\nGoToDocumentation.label = Online dokumentation\nShowParentFoldersQL.label = Överordnade mappar\nShowRecentLocationsQL.label = Nyligen besökta platser\nShowRecentExecutedFilesQL.label = Nyligen öppnade filer\nSplitFile.tooltip = Dela upp en fil i flera delar\nCombineFiles.tooltip = Slå samman uppdelade filer för att återskapa originalfilen\nShowDebugConsole.label = Felsökningskonsol\nFocusPrevious.label = Fokusera på föregående komponent\nFocusNext.label = Fokusera på nästa komponent\nfile_menu = Arkiv\nfile_menu.open_with = Öppna med\nmark_menu = Markera\nview_menu = Visa\nview_menu.show_hide_columns = Visa/Dölj kolumner\ngo_menu = Gå\nbookmarks_menu = Bokmärken\nbookmarks_menu.no_bookmark = Det finns inga bokmärken\nquick_lists_menu = Snabblista\nwindow_menu = Fönster\nhelp_menu = Hjälp\nstatus_bar.selected_files = %1 av %2 markerade\nstatus_bar.connecting_to_folder = Ansluter till mapp, tryck ESC för att avbryta.\nstatus_bar.volume_free = Ledigt: %1\nstatus_bar.volume_capacity = Kapacitet: %1\nshortcuts_panel.title = Kortkommandon\nshortcuts_panel.restore_defaults = Återställ förval\nshortcuts_panel.show = Visa\nshortcuts_panel.default_message = Tryck Enter eller dubbelklicka på kortkommandot som du vill redigera\nshortcuts_table.action_description = Handlingsbeskrivning\nshortcuts_table.shortcut = Kortkommando\nshortcuts_table.alternate_shortcut = Alternativt kortkommando\nshortcuts_table.type_in_a_shortcut = Ange kortkommando\ncommand_bar_dialog.help = Dra knapparna för att anpassa kommandoraden\ntable.folder_access_error_title = Fel vid mappåtkomst\ntable.folder_access_error = Mappens innehåll kan inte läsas\ntable.download_or_browse = Vill du bläddra eller laddar ner filen?\nversion_dialog.no_new_version_title = Ingen ny version finns\nversion_dialog.no_new_version = Grattis, du har redan den senaste versionen.\nversion_dialog.new_version_title = Ny version tillgänglig\nversion_dialog.new_version = En ny version av trolCommander finns tillgänglig.\nversion_dialog.new_version_url = En ny version av trolCommander finns här: %1.\nversion_dialog.not_available_title = Servern är inte tillgänglig\nversion_dialog.not_available = Kan inte få information om version från servern.\nversion_dialog.install_and_restart = Installera och starta om\nversion_dialog.preparing_for_update = Förbereder uppdatering...\nquit_dialog.title = Avsluta trolCommander\nquit_dialog.desc = Du har %1 fönster öppna. Vill du avsluta ändå?\nquit_dialog.show_next_time = Visa nästa gång\ndestination_dialog.file_exists_action = Standardåtgärd när filen redan finns\ndestination_dialog.verify_integrity = Verifierar data integritet\ndestination_dialog.skip_errors = Ignorera fel\nfile_collision_dialog.title = Filen finns redan\nrename_dialog.new_name = Nytt namn\ncopy_dialog.destination = Kopiera markerad(e) fil(er) till\ncopy_dialog.error_title = Kopieringsfel\ncopy_dialog.copying = Kopierar filer\ncopy_dialog.copying_file = Kopierar %1\npack_dialog.packing = Komprimerar filer\npack_dialog.packing_file = Komprimerar %1\npack_dialog.error_title = Komprimeringsfel\npack_dialog_description = Lägg till markerade filer i\npack_dialog.archive_format = Arkivformat\nunpack_dialog.destination = Extrahera markerade fil(er) till\nunpack_dialog.error_title = Extraheringsfel\nunpack_dialog.unpacking = Extraherar filer\nunpack_dialog.unpacking_file = Extraherar %1\noptimizing_archive = Optimerar arkiv %1\nerror_while_optimizing_archive = Fel vid optimering av arkiv %1\nmove_dialog.move_description = Flytta till\nmove_dialog.error_title = Flyttfel\nmove_dialog.moving = Flyttar filer\nmove_dialog.moving_file = Flyttar %1\ndownload_dialog.description = Ladda ner fil till\ndownload_dialog.error_title = Nerladdningsfel\ndownload_dialog.downloading = Laddar ner\ndownload_dialog.downloading_file = Laddar ner %1\nmkfile_dialog.allocate_space = Tilldela utrymme\ndelete_dialog.permanently_delete.confirmation = Radera markerade fil(er) permanent?\ndelete_dialog.move_to_trash.confirmation = Radera markerade fil(er)?\ndelete_dialog.move_to_trash.confirmation_details = Filerna kommer flyttas till papperskorgen.\ndelete_dialog.move_to_trash.option = Flytta till papperskorgen\ndelete_dialog.move_to_trash.failed = En eller flera filer kunde inte flyttas till papperskorgen\ndelete_dialog.deleting = Raderar\ndelete_dialog.error_title = Raderingsfel\ndelete.deleting_file = Raderar %1\nemail_dialog.prefs_not_set_title = Mejlen är inte inställd\nemail_dialog.prefs_not_set = Du måste ställa in din mejl först\nemail_dialog.from = Från\nemail_dialog.to = Till\nemail_dialog.subject = Ämne\nemail_dialog.send = Skicka\nemail_dialog.error_title = Mejlfel av filerna\nemail_dialog.read_error = Kunde inte läsa filer i undermappen\nemail_dialog.sending = Skickar filer\nemail.sending_file = Skickar %1\nemail.connecting_to_server = Ansluter till %1\nemail.server_unavailable = Kunde inte kontakta mejlservern %1, kolla dina mejlinställningar eller försök igen senare.\nemail.connection_closed = Anslutningen stängd av servern, mejlet skickades inte.\nemail.goodbye_failed = Fel när anslutning stängdes, mejlet kanske inte skickades.\nemail.send_file_error = Kunde inte skicka filen %1, mejlet skickades inte.\nsplit_file_dialog.error_title = Fel vid uppdelning av fil\nsplit_file_dialog.file_to_split = Dela upp fil\nsplit_file_dialog.target_directory = Målmapp\nsplit_file_dialog.part_size = Delstorlek\nsplit_file_dialog.parts = Antal delar\nsplit_file_dialog.generate_CRC = Skapa CRC fil\nsplit_file_dialog.max_parts = Maximalt antal tillåtna delar är %1\nsplit_file_dialog.auto = Automatiskt\nsplit_file_dialog.insert_new_media = Sätt in ny media\ncombine_files_dialog.error_title = Fel vid sammanslagning av filer\ncombine_files_job.no_crc_file = Sammanslagning klart. Ingen CRC fil.\ncombine_files_job.crc_read_error = Fel vid läsning av CRC fil.\ncombine_files_job.crc_check_failed = CRC matchar inte: förväntade %2, hittade %1\ncombine_files_job.crc_ok = Sammanslagning klart. CRC checksum ok.\nfile_selection_dialog.mark = Markera\nfile_selection_dialog.unmark = Avmarkera\nfile_selection_dialog.mark_description = Markera filer vars namn\nfile_selection_dialog.unmark_description = Avmarkera filer vars namn\nfile_selection_dialog.case_sensitive = Skiftlägeskänslig\nfile_selection_dialog.include_folders = Inkludera mappar\nprogress_dialog.starting = Startar överföringen...\nprogress_dialog.transferred = Överfört %1 med %2\nprogress_dialog.elapsed_time = Förfluten tid\nprogress_dialog.advanced = Avancerat\nprogress_dialog.current_speed = Aktuell hastighet\nprogress_dialog.limit_speed = Begränsad hastighet\nprogress_dialog.close_when_finished = Stäng fönstret när det är klart\nprogress_dialog.processing_files = Bearbetar filer\nprogress_dialog.processing_file = Bearbetar %1\nprogress_dialog.verifying_file = Verifierar %1\nprogress_dialog.job_finished = Arbetet klart\nprogress_dialog.job_error = Bearbetningsfel\nproperties_dialog.file_properties = %1 Egenskaper\nproperties_dialog.contents = Innehåller\nproperties_dialog.calculating = Beräknar...\ncalculate_checksum_dialog.checksum_algorithm = Kontrollsumma algoritm\ncalculate_checksum_dialog.temporary_file = Temporär fil\nchange_date_dialog.now = Nu\nchange_date_dialog.specific_date = Ange datum\nrun_dialog.run_command_description = Kör i aktuell mapp\nrun_dialog.run_in_home_description = Kör i hemmamappen\nrun_dialog.command_output = Kommandoutskrift\nrun_dialog.run = Kör\nrun_dialog.clear_history = Rensa historik\nserver_connect_dialog.server_type = Anslutningstyp\nserver_connect_dialog.server = Server\nserver_connect_dialog.share = Dela\nserver_connect_dialog.domain = Domän\nserver_connect_dialog.username = Användarnamn\nserver_connect_dialog.initial_dir = Startmapp\nserver_connect_dialog.port = Port\nserver_connect_dialog.server_url = Serveradress\nserver_connect_dialog.http_url = Hemsideadress\nserver_connect_dialog.connect = Anslut\nserver_connect_dialog.protocol = Protokoll\nserver_connect_dialog.nfs_version = NFS-version\nserver_connect_dialog.private_key = Privat nyckel\nserver_connect_dialog.passphrase = Lösenfras\nftp_connect.passive_mode = Aktivera passivt läge \nftp_connect.anonymous_user = Anonym användare\nftp_connect.nb_connection_retries = Antal försök att återansluta\nftp_connect.retry_delay = Fördröjning mellan försöken (i sekunder)\nhttp_connect.basic_authentication = HTTP grundläggande autentisering (frivilligt)\nserver_connections_dialog.disconnect = Koppla ner\nserver_connections_dialog.connection_busy = Upptaget\nserver_connections_dialog.connection_idle = Overksam\nbonjour.bonjour_services = Bonjour-tjänster\nbonjour.no_service_discovered = Ingen tjänst hittades\nbonjour.bonjour_disabled = Bonjour frånkopplat\nauth_dialog.title = Autentisering\nauth_dialog.desc = Vänligen ange ett användarnam och lösenord\nauth_dialog.server = Server\nauth_dialog.store_credentials = Spara användarnamn och lösenord (lätt kryptering)\nauth_dialog.connect_as = Anslut som\nauth_dialog.authentication_failed = Autentisering misslyckades\nsortable_list.move_up = Flytta upp\nsortable_list.move_down = Flytta ner\nadd_bookmark_dialog.add = Lägg till\nedit_bookmarks_dialog.new = Nytt\nfile_viewer.view_error_title = Visningfel\nfile_viewer.view_error = Kunde inte visa filen\nfile_viewer.file_menu = Arkiv\nfile_viewer.close = Stäng\nfile_viewer.large_file_warning = Filen kan vara för stort för denna begäran.\nfile_viewer.open_anyway = Öppna ändå\ntext_viewer.edit = Redigera\ntext_viewer.copy = Kopiera\ntext_viewer.select_all = Markera allt\ntext_viewer.find = Sök\ntext_viewer.find_next = Sök nästa\ntext_viewer.find_previous = Sök föregående\ntext_viewer.binary_file_warning = Detta verkar vara en binär fil\nimage_viewer.controls_menu = Funktioner\nimage_viewer.zoom_in = Zomma in\nimage_viewer.zoom_out = Zomma ut\nfile_editor.edit_error_title = Redigeringsfel\nfile_editor.edit_error = Kunde inte redigera filen.\nfile_editor.save = Spara\nfile_editor.save_as = Spara som...\nfile_editor.save_warning = Spara ändringar till filen innan avslut?\nfile_editor.cannot_write = Kunde inte läsa filen.\ntext_editor.cut = Klipp ut\ntext_editor.paste = Klistra in\nshortcuts_dialog.quick_search.start_search = Skriv något för att starta ett snabbsök\nshortcuts_dialog.quick_search.cancel_search = Avbryt snabbsök\nshortcuts_dialog.quick_search.remove_last_char = Ta bort sista tecknet från snabbsökstråden\nshortcuts_dialog.quick_search.jump_to_previous = Gå till föregående snabbsöksresultat\nshortcuts_dialog.quick_search.jump_to_next = Gå till nästa snabbsöksresultat\nshortcuts_dialog.quick_search.mark_jump_next = Markera/Avmarkera aktuell fil och gå till nästa sökresultat\ntheme_editor.title = Temaredigeraren\ntheme_editor.folder_tab = Mappvyer\ntheme_editor.shell_tab = Terminal\ntheme_editor.shell_history_tab = Terminalhistorik\ntheme_editor.statusbar_tab = Statuslisten\ntheme_editor.free_space = Ledigt utrymme\ntheme_editor.free_space.ok = OK\ntheme_editor.free_space.warning = Varning\ntheme_editor.free_space.critical = Kritiskt\ntheme_editor.locationbar_tab = Sökvägsfältet\ntheme_editor.editor_tab = Filredigeraren\ntheme_editor.font = Typsnitt\ntheme_editor.active_panel = Aktiv\ntheme_editor.inactive_panel = Inaktiv\ntheme_editor.general = Allmänt\ntheme_editor.could_not_save_theme = Kunde inte spara temat %1\ntheme_editor.border = Kanter\ntheme_editor.background = Bakgrund\ntheme_editor.alternate_background = Alternativ bakgrund\ntheme_editor.unfocused_background = Bakgrund (ej i fokus)\ntheme_editor.quick_search.unmatched_file = Filer som ej matchar\ntheme_editor.text = Text\ntheme_editor.progress = Förlopp\ntheme_editor.normal = Normal\ntheme_editor.normal_unfocused = Normal (i fokus)\ntheme_editor.selected = Markerad\ntheme_editor.selected_unfocused = Markerad (ej i fokus)\ntheme_editor.color = Färg\ntheme_editor.colors = Färger\ntheme_editor.plain_file = Enkel fil\ntheme_editor.marked_file = Markerad fil\ntheme_editor.hidden_file = Dold fil\ntheme_editor.folder = Mapp\ntheme_editor.archive_file = Arkiv\ntheme_editor.symbolic_link = Symbolisk länk\ntheme_editor.header = Sidhuvud\ntheme_editor.item = Lista\ncommand_bar_customize_dialog.available_actions = Tillgängliga handlingar\ncommand_bar_customize_dialog.modifier = Specialtangent\nprefs_dialog.title = Inställningar\nprefs_dialog.general_tab = Allmänt\nprefs_dialog.day = Dag\nprefs_dialog.month = Månad\nprefs_dialog.year = År\nprefs_dialog.language = Språk (kräver omstart)\nprefs_dialog.date_time = Datum- och tidsformat\nprefs_dialog.time = Tid\nprefs_dialog.date = Datum\nprefs_dialog.date_separator = Avskiljare\nprefs_dialog.time_12_hour = 12-timmarsklocka\nprefs_dialog.time_24_hour = 24-timmarsklocka\nprefs_dialog.show_seconds = Visa sekunder\nprefs_dialog.show_century = Visa århundrade\nprefs_dialog.check_for_updates_on_startup = Sök uppdateringar vid uppstart\nprefs_dialog.show_splash_screen = Visa startbild\nprefs_dialog.folders_tab = Mappvyer\nprefs_dialog.startup_folders = Startvyer\nprefs_dialog.left_folder = Vänster vy\nprefs_dialog.right_folder = Höger vy\nprefs_dialog.last_folder = Senast öppnade mappen\nprefs_dialog.custom_folder = Välj mapp\nprefs_dialog.show_hidden_files = Visa dolda filer\nprefs_dialog.show_ds_store_files = Visa .DS_Store filer\nprefs_dialog.show_system_folders = Visa systemkataloger\nprefs_dialog.compact_file_size = Avrunda filstorlek\nprefs_dialog.follow_symlinks_when_cd = Följ symboliska länkar när aktuell vy ändras\nprefs_dialog.appearance_tab = Utseende\nprefs_dialog.look_and_feel = Utseende och känsla\nprefs_dialog.icons_size = Ikonstorlek\nprefs_dialog.toolbar_icons = Verktygsfältet\nprefs_dialog.command_bar_icons = Kommandoraden\nprefs_dialog.file_icons = Filtyper\nprefs_dialog.use_system_file_icons = Använd systemets filikoner\nprefs_dialog.use_system_file_icons.always = Alltid\nprefs_dialog.use_system_file_icons.never = Aldrig\nprefs_dialog.use_system_file_icons.applications = Bara för program\nprefs_dialog.edit_current_theme = Redigera aktuellt tema...\nprefs_dialog.themes = Teman\nprefs_dialog.import_theme = Importera tema\nprefs_dialog.import_look_and_feel = Importera utseende och känsla\nprefs_dialog.no_look_and_feel = Inget utseende och känsla hittades.\nprefs_dialog.error_in_import = Fel vid import av temat %1.\nprefs_dialog.cannot_read_theme = Kunde inte ladda tema från filen %1\nprefs_dialog.export_theme = Exportera %1\nprefs_dialog.import = Importera\nprefs_dialog.export = Exportera\nprefs_dialog.theme_type = Typ: %1\nprefs_dialog.delete_theme = Radera temat %1 ?\nprefs_dialog.delete_look_and_feel = Radera utseende och känsla %1 ?\nprefs_dialog.rename_failed = Kunde inte döpa om temat %1\nprefs_dialog.xml_file = XML-fil\nprefs_dialog.jar_file = JAR-fil\nprefs_dialog.mail_tab = Mejl\nprefs_dialog.mail_settings = Utgående mejlinställningar\nprefs_dialog.mail_name = Mitt namn\nprefs_dialog.mail_address = Min mejladress\nprefs_dialog.mail_server = SMTP-server\nprefs_dialog.misc_tab = Blandat\nprefs_dialog.use_brushed_metal = Använd \"borstad metall\" utseendet (kräver omstart)\nprefs_dialog.confirm_on_quit = Visa bekräftelseruta vid avslut\nprefs_dialog.default_shell = Använd systemets standard kommandotolk\nprefs_dialog.custom_shell = Använd skräddarsytt shell\nprefs_dialog.shell_encoding = Terminal textkodning\nprefs_dialog.auto_detect_shell_encoding = Välj automatiskt\nprefs_dialog.enable_bonjour_discovery = Aktivera upptäckt av Bonjour-tjänster\nprefs_dialog.enable_system_notifications = Aktivera systemmeddelanden\ndebug_console_dialog.level = Nivå\nunit.byte = byte\nunit.bytes = bytes\nunit.bytes_short = b\nunit.kb = kB\nunit.mb = MB\nunit.gb = GB\nunit.tb = TB\nunit.speed = %1/sek\nduration.seconds = %1sek\nduration.minutes = %1min\nduration.hours = %1tim\nduration.days = %1dag\nduration.months = %1mån\nduration.years = %1år\ntheme.custom_theme = Skräddarsy tema\ntheme.custom = Skräddarsytt\ntheme.built_in = Inbyggt\ntheme.add_on = Tillägg\ntheme.current = aktuell\ntheme_could_not_be_loaded = Ett fel uppstod när temat öppnades.\nsetup.title = Välkommen till trolCommander\nsetup.intro = Vänligen välj hur du vill att trolCommander ska agera.\nsetup.look_and_feel = Välj utseende och känsla\nsetup.theme = Välj tema\nfont_chooser.font_size = Storlek\nfont_chooser.font_bold = Fet\nfont_chooser.font_italic = Kursiv\ncolor_chooser.red = Röd\ncolor_chooser.green = Grön\ncolor_chooser.blue = Blå\ncolor_chooser.hue = Färgton\ncolor_chooser.brightness = Intensitet\ncolor_chooser.swatches = Färgkarta\ncolor_chooser.saturation = Mättnad\ncolor_chooser.rgb = RGB\ncolor_chooser.hsb = HSB\ncolor_chooser.recent = Senaste\ncolor_chooser.alpha = Alpha-transparent\ncolor_chooser.title = Välj en färg\nbatch_rename_dialog.mask = Mönster\nbatch_rename_dialog.search_replace = Sök och Ersätt\nbatch_rename_dialog.search_for = Sök efter\nbatch_rename_dialog.replace_with = Ersätt med\nbatch_rename_dialog.counter = Räknare\nbatch_rename_dialog.start_at = Börja vid\nbatch_rename_dialog.step_by = Öka med\nbatch_rename_dialog.format = Format\nbatch_rename_dialog.upper_lower_case = Skiftläge\nbatch_rename_dialog.no_change = Oförändrad\nbatch_rename_dialog.lower_case = små bokstäver\nbatch_rename_dialog.upper_case = STORA BOKSTÄVER\nbatch_rename_dialog.first_upper = Första bokstaven stor\nbatch_rename_dialog.word = Fösta Bokstaven I Varje Ord\nbatch_rename_dialog.old_name = Gammalt namn\nbatch_rename_dialog.new_name = Nytt namn\nbatch_rename_dialog.block_name = Bevara\nbatch_rename_dialog.range = Område\nbatch_rename_dialog.proceed_renaming = %1 av %2 filer kommer döpas om. Vill du fortsätta?\nbatch_rename_dialog.duplicate_names = Duplicera namn!\nparent_folders_quick_list.empty_message = Det finns ingen överodnad mapp till denna plats\nrecent_locations_quick_list.empty_message = Inga nyligen besökta platser\nrecent_executed_files_quick_list.empty_message = Inga nyligen öppnade filer\n#auth_dialog.error_was = Felet var %1\n#table.hide_column = Dölj kolumn\n#delete.symlink_warning_title = Symbolisk länk hittad\n#delete.symlink_warning = Filen ser ut som en symbolisk länk:\\n\\n  Fil: %1\\n  Länkar till: %2\\n\\nRadera bara länken eller\\nFölj länken och radera mappen (VARNING) ?\n#delete.delete_link_only = Radera länk\n#delete.delete_linked_folder = Radera mapp\n#Unpack.label = Extrahera filer\n#Pack.label = Komprimera filer\n"
  },
  {
    "path": "src/main/resources/dictionary_uk_UA.properties",
    "content": "ok = Гаразд\nyes = Так\nno = Ні\ncancel = Скасувати\nedit = Редагувати\nclose = Закрити\nreset = Скинути\nrename = Перейменувати\napply = Застосувати\nchange = Змінити\nsave = Зберегти\ndont_save = Не зберігати\nreplace = Замінити\ndont_replace = Не заміняти\ndelete = Видалити\nskip = Пропустити\nskip_all = Пропустити все\nretry = Повторити\nresume = Продовжити\noverwrite = Перезаписати\noverwrite_if_older = Перезаписати старіший\nduplicate = Зкопіювати\napply_to_all = Застосувати до всіх\ncopy = Копіювати\nmove = Перенести\npack = Стиснути\nunpack = Розпакувати\ndownload = Завантажити\nsplit = Розділити\ncombine = Об'єднати\nbrowse = Переглянути\nask = Запитати\nstop = Зупинити\npause = Призупинити\nquick_search = Швидкий пошук\nfile_manager = Менеджер файлів\ncreate = Створити\ncreating_file = Створюється %1\nchoose = Вибрати\ncustomize = Налаштувати\nchoose_folder = Вибрати папку\nlogin = Логін\npassword = Пароль\nuser = Користувач\nencoding = Кодування\npreferred_encodings = Доступні кодування\nlicense = Ліцензія\nname = Ім'я\nsize = Розмір\ndate = Дата\nextension = Розширення\npermissions = Привілеї доступу\nowner = Власник\ngroup = Група\nlocation = Розташування\nuntitled = Без імені\nsource = Відправник\ndestination = Записати у\nrecurse_directories = Рекурсивно обробляти вкладені папки\ngo_to = Перейти\nexample = Приклад\npreview = Попередній перегляд\ncomment = Коментар\nsample_text = Приклад тексту\nnb_files = %1 файл(ів)\nnb_folders = %1 папка(и)\nloading = Завантаження...\nthis_operation_cannot_be_undone = Неможливо скасувати дану операцію\nremove = Видалити\ndetails = Детальніше\nwarning = Попередження\nerror = Помилка\ngeneric_error = При виконанні операції сталася помилка\nfolder_does_not_exist = Папка не існує або є недоступною\nthis_folder_does_not_exist = Папка %1 не існує або є недоступною\nthis_file_does_not_exist = Файл %1 не існує або є недоступним\ninvalid_path = Неправильний шлях\ndirectory_already_exists = Папка %1 вже існує\nfile_exists_in_destination = Файл вже існує\nsource_parent_of_destination = Спроба перенести папку в одну з її вкладених папок\ncannot_read_file = Не вдається прочитати файл %1\ncannot_write_file = Не вдається записати файл %1\ncannot_create_folder = Не вдається створити папку %1\ncannot_read_folder = Не вдається прочитати вміст папки %1\ncannot_delete_file = Не вдається видалити файл %1\ncannot_delete_folder = Не вдається видалити папку %1\nerror_while_transferring = Помилка при передачі файлу %1\nsame_source_destination = Вихідна та кінцева папки однакові\nfile_already_exists = %1 вже існує, хочете перезаписати ?\nwrite_error = Помилка під час запису\nread_error = Помилка під час читання\nintegrity_check_error = Не пройдено перевірки цілісності: початковий та кінцевий файли відрізняються\nstartup_error = Під час запуску trolCommander'а виникла помилка.\npermissions.read = Читати\npermissions.write = Писати\npermissions.executable = Виконувати\npermissions.group = Група\npermissions.other = Інші\npermissions.octal_notation = Числове значення\naction_categories.all = Всі\naction_categories.navigation = Навігація\naction_categories.selection = Вибір\naction_categories.view = Перегляд\naction_categories.file_operations = Файлові операції\naction_categories.windows = Вікна\nTerminal.label = Terminal\nTerminal.tooltip = Terminal\nFindFile.label = Find file\nFindFile.tooltip = Find file\nAddBookmark.label = Додати закладку\nAddBookmark.tooltip = Додати поточну папку до закладок\nBatchRename.label = Масове перейменування\nEditBookmarks.label = Редагувати закладки\nExploreBookmarks.label = Переглянути закладки\nEditCredentials.label = Переглянути реквізити\nChangeLocation.label = Зміти розташування\nChangeDate.label = Змінити дату\nChangeDate.tooltip = Змінити дату вибраних файлів\nChangePermissions.label = Змінити привілеї доступу\nChangePermissions.tooltip = Змінити привілеї доступу вибраного файла(файлів)\nCheckForUpdates.label = Перевірити наявність оновлення\nCompareFolders.label = Порівняти папки\nConnectToServer.label = З'єднатися з сервером\nConnectToServer.tooltip = З'єднатися з віддаленим сервером\nView.label = Переглянути\nInternalView.label = Перегляд (вбудованим)\nView.tooltip = Переглянути вибраний файл\nInternalEdit.label = Редагувати (вбудованим)\nEdit.tooltip = Редагувати вибраний файл\nCopy.tooltip = Копіювати відмічені файл\nLocalCopy.label = Копіювати локально\nLocalCopy.tooltip = Копіювати вибраний файл в поточну папку\nMove.tooltip = Перенести відмічені файли\nRename.tooltip = Перейменувати відмічені файли\nMkdir.label = Нова папка\nMkdir.tooltip = Створити в поточній папці\nMkfile.label = Створити файл\nMkfile.tooltip = Створити файл в поточній папці\nDelete.tooltip = Видаляти відмічені файли у системний кошик, коли доступно\nPermanentDelete.label = Видаляти зразу\nPermanentDelete.tooltip = Видаляти відмічені файли не використовуючи системний кошик\nRefresh.label = Оновити\nRefresh.tooltip = Оновити поточну папку\nCloseWindow.label = Закрити вікно\nCloseWindow.tooltip = Закрити дане вікно\nCopyFileNames.label = Копіювати імена\nCopyFilePaths.label = Копіювати шлях(и)\nCopyFilesToClipboard.label = Копіювати файл(и)\nPasteClipboardFiles.label = Вставити файл(и)\nEmail.label = Надіслати файл(и) по email\nEmail.tooltip = Надіслати відмічені файли додатками до email\nGoBack.label = Назад\nGoBack.tooltip = Перейти до попередньої папки\nGoForward.label = Вперед\nGoForward.tooltip = Перейти до наступної папки\nGoToHome.label = Перейти в домашню папку\nGoToParent.label = Перейти наверх\nGoToParent.tooltip = Перейти в батьківську папку\nGoToParentInOtherPanel.label = Перейти наверх в іншій панелі\nGoToParentInBothPanels.label = Перейти наверх в обох панелях\nGoToRoot.label = Перейти в кореневу папку\nSortByName.label = Сортувати за ім'ям\nSortByDate.label = Сортувати за датою\nSortBySize.label = Сортувати за розміром\nSortByExtension.label = Сортувати за розширенням\nSortByPermissions.label = Сортувати за привілеями доступу\nSortByOwner.label = Сортувати за власником\nSortByGroup.label = Сортувати за групою\nMarkGroup.label = Відмітити файли\nMarkGroup.tooltip = Відмітити групу файлів\nUnmarkGroup.label = Зняти відмітку з файлів\nUnmarkGroup.tooltip = Зняти відмітку з групи файлів\nMarkAll.label = Відмітити все\nUnmarkAll.label = Зняти відмітку з всього\nMarkSelectedFile.label = Відмітити/зняти\nMarkSelectedFile.tooltip = Відмітити/зняти відмітку для вибраного файла\nMarkNextBlock.label = Виділити блок рядків вниз\nMarkPreviousBlock.label = Виділити блок рядків вверх\nMarkNextRow.label = Виділити рядок вниз\nMarkPreviousRow.label = Виділити рядок вверх\nMarkNextPage.label = Відмітити файли на сторінку вниз\nMarkPreviousPage.label = Відмітити файли на сторінку вверх\nMarkToFirstRow.label = Відмітити файли до початку\nMarkToLastRow.label = Відмітити файли до кінця\nMarkExtension.label = Відмітити розширення\nInvertSelection.label = Інвертувати виділення\nSwapFolders.label = Поміняти папки місцями\nSwapFolders.tooltip = Поміняти праву і ліву папки місцями\nSetSameFolder.label = Встановити однакові папки\nSetSameFolder.tooltip = Однакові папки в обох панелях\nNewWindow.label = Нове вікно\nNewWindow.tooltip = Відкрити нове вікно\nOpen.label = Відкрити\nOpen.tooltip = Відкрити папку / Відкрити архів / Виконати\nOpenNatively.label = Відкрити з допомогою ОС\nOpenNatively.tooltip = Виконати вибраний файл програмою, асоційованою з ним в налаштуваннях ситеми\nOpenInNewTab.label = Відкрити у новій вкладці\nOpenInOtherPanel.label = Відкрити в іншій панелі\nOpenInBothPanels.label = Відкрити в обох панелях\nRevealInDesktop.label = Відкрити в %1\nRunCommand.label = Виконати команду\nRunCommand.tooltip = Виконати в поточній папці\nPack.tooltip = Стиснути вибрані файли у архів\nUnpack.tooltip = Розпакувати відмічені файли-архіви\nShowFileProperties.label = Властивості\nShowFileProperties.tooltip = Переглянути властивості відмічених файлів\nShowPreferences.label = Налаштування\nShowPreferences.tooltip = Налаштувати trolCommander\nShowServerConnections.label = Переглянути відкриті з'єднання\nQuit.label = Вихід\nReverseSortOrder.label = В зворотньому порядку\nToggleAutoSize.label = Колонки автоматично змінюють розмір\nStop.label = Зупинити зміну папки\nToggleColumn.show = Показати стовпець %1\nToggleColumn.hide = Сховати стовпець %1\nToggleCommandBar.show = Показати панель команд\nToggleCommandBar.hide = Сховати панель команд\nToggleToolBar.show = Показати панель інструментів\nToggleToolBar.hide = Сховати панель інструментів\nCustomizeCommandBar.label = Налаштування панелі команд\nToggleStatusBar.show = Показати статус-панель\nToggleStatusBar.hide = Сховати статус-панель\nToggleShowFoldersFirst.label = Показувати папки першими в списку\nToggleTree.label = Показати дерево\nPopupLeftDriveButton.label = Змінити ліву папку\nPopupRightDriveButton.label = Змінити праву папку\nRecallPreviousWindow.label = Перейти в попереднє вікно\nRecallNextWindow.label = Перейти в наступне вікно\nRecallWindow.label = Відновити вікно %1\nBringAllToFront.label = Показати всі вікна\nSwitchActiveTable.label = Перемкнути праву і ліву панелі\nSelectNextBlock.label = Опуститись на блок рядків вниз\nSelectPreviousBlock.label = Піднятись на блок рядків вверх\nSelectNextPage.label = Перейти в кінець сторінки\nSelectPreviousPage.label = Перейти на початок сторінки\nSelectNextRow.label = Опуститись на один рядок вниз\nSelectPreviousRow.label = Піднятись на один рядок вверх\nSelectFirstRow.label = Перейти до першого файлу в каталозі\nSelectLastRow.label = Перейти до останнього файлу в каталозі\nSplitEqually.label = Поділити навпіл\nSplitVertically.label = Вертикальний поділ\nSplitHorizontally.label = Горизонтальний поділ\nShowKeyboardShortcuts.label = Гарячі клавіші\nGoToWebsite.label = Перейти на сторінку trolCommander\nGoToForums.label = Перейти на форум\nReportBug.label = Повідомити про помилку\nDonate.label = Пожертвувати на розвиток програми\nShowAbout.label = Про програму trolCommander\nOpenTrash.label = Відкрити Смітник\nEmptyTrash.label = Очистити Смітник\nCalculateChecksum.label = Порахувати контрольну суму\nMaximizeWindow.label = Розгорнути\nMaximizeWindow.label.mac_os_x = Збільшити\nMinimizeWindow.label = Згорнути\nMinimizeWindow.label.mac_os_x = В Док\nGoToDocumentation.label = Документація в мережі\nShowParentFoldersQL.label = Наверх\nShowRecentLocationsQL.label = Останні відвідані місця\nShowRecentExecutedFilesQL.label = Останні виконані файли\nShowRootFoldersQL.label = Кореневі папки\nSplitFile.tooltip = Розділити файл на декілька частин\nCombineFiles.tooltip = Об'єднати розділений на частини файл в початковий файл\nShowDebugConsole.label = Консоль Debug\nFocusPrevious.label = Перейти до попереднього компоненту\nFocusNext.label = Перейти до наступного компоненту\nfile_menu = Файл\nfile_menu.open_with = Відкрити з\nmark_menu = Відмітити\nview_menu = Перегляд\nview_menu.show_hide_columns = Показати/сховати колонки\ngo_menu = Перейти\nbookmarks_menu = Закладки\nbookmarks_menu.no_bookmark = Закладок немає\ndrive_popup.network_shares = Спільний мережевий ресурс\nquick_lists_menu = Швидкий список\nwindow_menu = Вікно\nhelp_menu = Допомога\nstatus_bar.selected_files = %1 з %2 вибрано\nstatus_bar.connecting_to_folder = З'єднання з папкою, натисни ESCAPE щоб скасувати\nstatus_bar.volume_free = Вільно %1\nstatus_bar.volume_capacity = Ємність %1\nshortcuts_panel.title = Комбінації клавіш\nshortcuts_panel.restore_defaults = Відновити стандартні\nshortcuts_panel.show = Показати\nshortcuts_panel.default_message = Натиснення Enter або подвійний клік надасть змогу редагувати комбінації клавіш\nshortcuts_table.action_description = Опис дії\nshortcuts_table.shortcut = Комбінації клавіш\nshortcuts_table.alternate_shortcut = Альтернативні комбінації\nshortcuts_table.type_in_a_shortcut = Натисніть комбінацію клавіш\ncommand_bar_dialog.help = Перетягуйте кнопки для налаштування панелі команд\ntable.folder_access_error_title = Помилка під час доступу до папки\ntable.folder_access_error = Не вдалося прочитати вміст папки\ntable.download_or_browse = Хочете переглянути чи завантажити даний файл ?\nCloseDuplicateTabs.tooltip = Закрити вкладки, що повторюються\nCloseOtherTabs.tooltip = Закрити інші вкладки\nCloseTab.tooltip = Закрити вкладку\nMoveTabToOtherPanel.tooltip = Перемістити вкладку на іншу панель\nNextTab.label = Наступна вкладка\nPreviousTab.label = Попередня вкладка\nversion_dialog.no_new_version_title = Немає нової версії\nversion_dialog.no_new_version = Ви вже користуєтесь найостаннішою версією\nversion_dialog.new_version_title = Є нова версія\nversion_dialog.new_version = Є нова версія trolCommander.\nversion_dialog.new_version_url = Нова версія trolCommander доступна за адресою %1.\nversion_dialog.not_available_title = Сервер не відповідає\nversion_dialog.not_available = Не вдалося отримати інформацію про версії з сервера\nversion_dialog.install_and_restart = Встановити та перезапустити\nversion_dialog.preparing_for_update = Готуюсь до оновлення...\nquit_dialog.title = Вийти з trolCommander\nquit_dialog.desc = Вийти з trolCommander (відкрито %1 вікон) ?\nquit_dialog.show_next_time = Показувати наступного разу\ndestination_dialog.file_exists_action = Дія за замовчуванням, якщо файл вже існує\ndestination_dialog.verify_integrity = Перевірити цілісність даних\ndestination_dialog.skip_errors = Пропустити помилки\nfile_collision_dialog.title = Співпадіння файлів\nrename_dialog.new_name = Нове ім'я\ncopy_dialog.destination = Копіювати вибрані файли до\ncopy_dialog.error_title = Помилка при копіюванні\ncopy_dialog.copying = Копіювання файлів\ncopy_dialog.copying_file = Копіюю %1\npack_dialog.packing = Стиснення файлів\npack_dialog.packing_file = Стискаю %1\npack_dialog.error_title = Помилка стиснення\npack_dialog_description = Додати вибрані файли до\npack_dialog.archive_format = Формат архіву\nunpack_dialog.destination = Розпакувати вибрані файли до\nunpack_dialog.error_title = Помилка при розпакуванні\nunpack_dialog.unpacking = Розпакування файлів\nunpack_dialog.unpacking_file = Розпакування %1\noptimizing_archive = Оптимізація архіву %1\nerror_while_optimizing_archive = Помилка при оптимізації архіву %1\nmove_dialog.move_description = Перемістити до\nmove_dialog.error_title = Помилки при переміщенні\nmove_dialog.moving = Переміщення файлів\nmove_dialog.moving_file = Переміщення %1\ndownload_dialog.description = Завантажити файл до \ndownload_dialog.error_title = Помилка при завантаженні\ndownload_dialog.downloading = Завантаження\ndownload_dialog.downloading_file = Завантаження %1\nmkfile_dialog.allocate_space = Виділити місце\ndelete_dialog.permanently_delete.confirmation = Видалити вибрані файли безвідновно ?\ndelete_dialog.move_to_trash.confirmation = Видалити вибрані файли ?\ndelete_dialog.move_to_trash.confirmation_details = Файли буде перенесено в Смітник\ndelete_dialog.move_to_trash.option = Перенести в Смітник\ndelete_dialog.move_to_trash.failed = Один або декілька файлів не можуть бути перенесені у смітник.\ndelete_dialog.deleting = Видалення\ndelete_dialog.error_title = Помилка при видаленні\ndelete.deleting_file = Видалення %1\nemail_dialog.prefs_not_set_title = Пошта не налаштована\nemail_dialog.prefs_not_set = Спершу налаштуйте пошту.\nemail_dialog.from = Від\nemail_dialog.to = Кому\nemail_dialog.subject = Тема\nemail_dialog.send = Надіслати\nemail_dialog.error_title = Помилка при відсиланні файлів\nemail_dialog.read_error = Не вдалося прочитати файли з вкладених папок\nemail_dialog.sending = Відсилання файлів\nemail.sending_file = Відсилання %1\nemail.connecting_to_server = З'єднання з %1\nemail.server_unavailable = Не вдалось з'єднатися з поштовим сервером %1, перевір налаштування пошти або спробуй пізніше\nemail.connection_closed = Сервер перервав з'єднання, пошта не відіслана\nemail.goodbye_failed = Помилка при закритті з'єднання, можливо пошта не відіслана\nemail.send_file_error = Не вдалося надіслати файл %1, пошта не відіслана\nsplit_file_dialog.error_title = Помилка під час розподілу файлу\nsplit_file_dialog.file_to_split = Файл для розподілу\nsplit_file_dialog.target_directory = Каталог призначення\nsplit_file_dialog.part_size = Розмір кожної частини\nsplit_file_dialog.parts = Кількість частин\nsplit_file_dialog.generate_CRC = Сформувати CRC файл\nsplit_file_dialog.max_parts = Максимально допустима кількість частин %1\nsplit_file_dialog.auto = Автоматично\nsplit_file_dialog.insert_new_media = Вставте новий носій\ncombine_files_dialog.error_title = Помилка об'єднання файлу\ncombine_files_job.no_crc_file = Об'єднано успішно. Немає CRC файлу.\ncombine_files_job.crc_read_error = Помилка читання CRC файлу.\ncombine_files_job.crc_check_failed = CRC розбіжність: очікується %2, отримано %1\ncombine_files_job.crc_ok = Об'єднано успішно. CRC контрольна сума ok.\nfile_selection_dialog.mark = Видмітити\nfile_selection_dialog.unmark = Зняти відмітку\nfile_selection_dialog.mark_description = Відмітити файли з ім'ям\nfile_selection_dialog.unmark_description = Зняти відмітку для файлів з ім'ям, яке\nfile_selection_dialog.case_sensitive = Враховувати регістр\nfile_selection_dialog.include_folders = Включати папки\nprogress_dialog.starting = Передача почалась...\nprogress_dialog.transferred = Передано %1 зі швидкістю %2\nprogress_dialog.elapsed_time = Час\nprogress_dialog.advanced = Додатково\nprogress_dialog.current_speed = Поточна швидкість\nprogress_dialog.limit_speed = Обмеження швидкості\nprogress_dialog.close_when_finished = Закрити вікно після закінчення\nprogress_dialog.processing_files = Обробка файлів\nprogress_dialog.processing_file = Обробка %1\nprogress_dialog.verifying_file = Перевірка %1\nprogress_dialog.job_finished = Закінчено\nprogress_dialog.job_error = Помилка\nproperties_dialog.file_properties = Властивості %1\nproperties_dialog.contents = Вміст\nproperties_dialog.calculating = Обчислення...\ncalculate_checksum_dialog.checksum_algorithm = Алгоритм підрахунку контрольної суми\ncalculate_checksum_dialog.temporary_file = Тимчасовий файл\nchange_date_dialog.now = Поточний час\nchange_date_dialog.specific_date = Вказана дата\nrun_dialog.run_command_description = Виконати в поточній папці\nrun_dialog.run_in_home_description = Виконати в домашній папці\nrun_dialog.command_output = Результат команди\nrun_dialog.run = Запустити\nrun_dialog.clear_history = Очистити запам'ятовані команди\nserver_connect_dialog.server_type = Тип з'єднання\nserver_connect_dialog.server = Сервер\nserver_connect_dialog.share = Спільний ресурс\nserver_connect_dialog.domain = Домен\nserver_connect_dialog.username = Ім'я користувача\nserver_connect_dialog.initial_dir = Початкова папка\nserver_connect_dialog.port = Порт\nserver_connect_dialog.server_url = Адреса сервера\nserver_connect_dialog.http_url = Адреса сторінки в інтернеті\nserver_connect_dialog.connect = Під'єднатися\nserver_connect_dialog.protocol = Протокол\nserver_connect_dialog.nfs_version = Версія NFS\nserver_connect_dialog.private_key = Приватний ключ\nserver_connect_dialog.passphrase = Ключова фраза\nftp_connect.passive_mode = Використовувати пасивний режим\nftp_connect.anonymous_user = Анонімний користувач\nftp_connect.nb_connection_retries = Кількість спроб з'єднання\nftp_connect.retry_delay = Пауза між спробами (секунди)\nhttp_connect.basic_authentication = Базова аутентифікація HTTP (не обов'язково)\nserver_connections_dialog.disconnect = Від'єднатися\nserver_connections_dialog.connection_busy = Зайнято\nserver_connections_dialog.connection_idle = Вільно\nbonjour.bonjour_services = Служба Bonjour\nbonjour.no_service_discovered = Не знайдено служб\nbonjour.bonjour_disabled = Служба Bonjour відключена\nauth_dialog.title = Аутентифікація\nauth_dialog.desc = Введіть ім'я користувача і пароль\nauth_dialog.server = Сервер\nauth_dialog.store_credentials = Зберігати ім'я користувача і пароль (слабке шифрування)\nauth_dialog.connect_as = Під'єднатись як\nauth_dialog.authentication_failed = Аутентифікація не пройдена\nsortable_list.move_up = Перемістити вгору\nsortable_list.move_down = Перемістити вниз\nadd_bookmark_dialog.add = Додати\nedit_bookmarks_dialog.new = Нова\nfile_viewer.view_error_title = Помилка при перегляді\nfile_viewer.view_error = Перегляд файлу неможливий\nfile_viewer.file_menu = Файл\nfile_viewer.close = Закрити\nfile_viewer.large_file_warning = Вибраний файл може бути завеликим для перегляду\nfile_viewer.open_anyway = Відкрити будьщо\ntext_viewer.edit = Редагувати\ntext_viewer.copy = Копіювати\ntext_viewer.select_all = Вибрати все\ntext_viewer.find = Шукати\ntext_viewer.find_next = Шукати далі\ntext_viewer.find_previous = Знайти попереднє\ntext_viewer.view = Вигляд\ntext_viewer.line_wrap = Перенесення слів\ntext_viewer.line_numbers = Номери рядків\ntext_viewer.binary_file_warning = Файл, скоріш за все, двійковий\nimage_viewer.controls_menu = Елементи керування\nimage_viewer.zoom_in = Збільшити\nimage_viewer.zoom_out = Зменшити\nfile_editor.edit_error_title = Помилка при редагуванні\nfile_editor.edit_error = Редагування файлу неможливе\nfile_editor.save = Зберегти\nfile_editor.save_as = Зберегти з ім'ям...\nfile_editor.save_warning = Зберегти внесені зміни і закрити ?\nfile_editor.cannot_write = Не вдається записати файл.\ntext_editor.cut = Вирізати\ntext_editor.paste = Вставити\nshortcuts_dialog.quick_search.start_search = Для швидкого пошуку, введіть будь-який символ\nshortcuts_dialog.quick_search.cancel_search = Скасувати швидкий пошук\nshortcuts_dialog.quick_search.remove_last_char = Видалити останній символ з стрічки швидкого пошуку\nshortcuts_dialog.quick_search.jump_to_previous = Перейти до попередніх результатів швидкого пошуку\nshortcuts_dialog.quick_search.jump_to_next = Перейти до наступних результатів швидкого пошуку\nshortcuts_dialog.quick_search.mark_jump_next = Відмітити/зняти відмітку з поточного файлу і перейти до наступних результатів швидкого пошуку\ntheme_editor.title = Редактор теми\ntheme_editor.folder_tab = Панель папки\ntheme_editor.shell_tab = Оболонка\ntheme_editor.shell_history_tab = Історія команд\ntheme_editor.statusbar_tab = Статус-панель\ntheme_editor.free_space = Вільне місце\ntheme_editor.free_space.ok = Гаразд\ntheme_editor.free_space.warning = Увага\ntheme_editor.free_space.critical = Дуже мало\ntheme_editor.locationbar_tab = Панель місцезнаходження\ntheme_editor.editor_tab = Редактор файлів\ntheme_editor.font = Шрифт\ntheme_editor.active_panel = Активна\ntheme_editor.inactive_panel = Неактивна\ntheme_editor.general = Загальні\ntheme_editor.could_not_save_theme = Не вдалося зберегти тему %1\ntheme_editor.border = Рамка\ntheme_editor.background = Фон\ntheme_editor.alternate_background = Інший фон\ntheme_editor.unfocused_background = Фон (без фокусу)\ntheme_editor.quick_search.unmatched_file = Відсіяні файли\ntheme_editor.text = Текст\ntheme_editor.progress = Індикатор виконання\ntheme_editor.normal = Звичайний\ntheme_editor.normal_unfocused = Звичайний (без фокусу)\ntheme_editor.selected = Вибраний\ntheme_editor.selected_unfocused = Вибраний (без фокусу)\ntheme_editor.color = Колір\ntheme_editor.colors = Кольори\ntheme_editor.plain_file = Звичайний файл\ntheme_editor.marked_file = Відмічений файл\ntheme_editor.hidden_file = Прихований файл\ntheme_editor.folder = Папка\ntheme_editor.archive_file = Архів\ntheme_editor.symbolic_link = Символьний лінк\ntheme_editor.header = Заголовок\ntheme_editor.item = Елемент\ncommand_bar_customize_dialog.available_actions = Доступні дії\ncommand_bar_customize_dialog.modifier = Перемикач\nprefs_dialog.title = Налаштування\nprefs_dialog.general_tab = Загальні\nprefs_dialog.day = День\nprefs_dialog.month = Місяць\nprefs_dialog.year = Рік\nprefs_dialog.language = Мова (потрібно перезапустити)\nprefs_dialog.date_time = Формат дати і часу\nprefs_dialog.time = Час\nprefs_dialog.date = Дата\nprefs_dialog.date_separator = Розділювач\nprefs_dialog.time_12_hour = 12-годинний формат\nprefs_dialog.time_24_hour = 24-годинний формат\nprefs_dialog.show_seconds = Показувати секунди\nprefs_dialog.show_century = Показувати століття\nprefs_dialog.check_for_updates_on_startup = Перевіряти наявність оновлень при запуску\nprefs_dialog.show_splash_screen = Показувати початкову заставку\nprefs_dialog.show_splash_screen = Показувати заставку\nprefs_dialog.folders_tab = Папки\nprefs_dialog.startup_folders = Папки при запуску\nprefs_dialog.left_folder = Ліва папка\nprefs_dialog.right_folder = Права папка\nprefs_dialog.last_folder = Остання відвідана папка\nprefs_dialog.custom_folder = Задана папка\nprefs_dialog.show_hidden_files = Показувати приховані файли\nprefs_dialog.show_ds_store_files = Показувати .DS_Store файли\nprefs_dialog.show_system_folders = Показувати системні папки\nprefs_dialog.compact_file_size = Заокруглювати розміри файлів\nprefs_dialog.follow_symlinks_when_cd = Переходити за символьним посиланням при зміні папки\nprefs_dialog.appearance_tab = Вигляд\nprefs_dialog.look_and_feel = Налаштування вигляду\nprefs_dialog.icons_size = Розмір значків\nprefs_dialog.toolbar_icons = Панель інструментів\nprefs_dialog.command_bar_icons = Панель команд\nprefs_dialog.file_icons = Типи файлів\nprefs_dialog.use_system_file_icons = Використовувати системні значки для файлів\nprefs_dialog.use_system_file_icons.always = Завжди\nprefs_dialog.use_system_file_icons.never = Ніколи\nprefs_dialog.use_system_file_icons.applications = Тільки для програм\nprefs_dialog.edit_current_theme = Редагувати поточну тему...\nprefs_dialog.themes = Теми\nprefs_dialog.import_theme = Імпортувати тему\nprefs_dialog.import_look_and_feel = Імпортувати налаштування вигляду\nprefs_dialog.no_look_and_feel = Не знайдено налаштувань вигляду\nprefs_dialog.error_in_import = Помилка при імпорті теми %1\nprefs_dialog.cannot_read_theme = Не вдається завантажити тему з файла %1\nprefs_dialog.export_theme = Експорт\nprefs_dialog.import = Імпорт\nprefs_dialog.export = Експорт\nprefs_dialog.theme_type = Тип: %1\nprefs_dialog.delete_theme = Видалити тему %1 безвідновно ?\nprefs_dialog.delete_look_and_feel = Видалити налаштування вигляду %1 безвідновно ?\nprefs_dialog.rename_failed = Не вдалося перейменувати тему %1\nprefs_dialog.xml_file = XML файл\nprefs_dialog.jar_file = JAR файл\nprefs_dialog.mail_tab = Електронна пошта\nprefs_dialog.mail_settings = Налаштування відправлення пошти\nprefs_dialog.mail_name = Ваше ім'я\nprefs_dialog.mail_address = Ваша email адреса\nprefs_dialog.mail_server = SMTP сервер\nprefs_dialog.misc_tab = Додатково\nprefs_dialog.use_brushed_metal = Використовувати тему 'шліфований метал' (потрібно перезапустити)\nprefs_dialog.confirm_on_quit = Питати підтвердження при виході\nprefs_dialog.default_shell = Використовувати стандартну системну оболонку\nprefs_dialog.custom_shell = Використовувати вказану оболонку\nprefs_dialog.shell_encoding = Кодування командної оболонки\nprefs_dialog.auto_detect_shell_encoding = Авто визначення\nprefs_dialog.enable_bonjour_discovery = Включити службу виявлення Bonjour\nprefs_dialog.enable_system_notifications = Включити системні повідомлення\ndebug_console_dialog.level = Рівень\nunit.byte = байт\nunit.bytes = байти\nunit.bytes_short = б\nunit.kb = Кб\nunit.mb = Мб\nunit.gb = Гб\nunit.tb = Тб\nunit.speed = %1/сек\nduration.seconds = %1с\nduration.minutes = %1х\nduration.hours = %1г\nduration.days = %1д\nduration.months = %1м\nduration.years = %1р\ntheme.custom_theme = Власна тема\ntheme.custom = Тема користувача\ntheme.built_in = Вбудована\ntheme.add_on = Додаток\ntheme.current = поточна\ntheme_could_not_be_loaded = Відбулася помилка при завантаженні теми.\nsetup.title = Вітаємо в trolCommander\nsetup.intro = Виберіть головні налаштування trolCommander\nsetup.look_and_feel = Виберіть вигляд програми\nsetup.theme = Виберіть тему оформлення\nfont_chooser.font_size = Розмір\nfont_chooser.font_bold = Жирний\nfont_chooser.font_italic = Курсив\ncolor_chooser.red = Червоний\ncolor_chooser.green = Зелений\ncolor_chooser.blue = Синій\ncolor_chooser.hue = Відтінок\ncolor_chooser.brightness = Яскравість\ncolor_chooser.swatches = Зразки\ncolor_chooser.saturation = Насиченість\ncolor_chooser.rgb = RGB\ncolor_chooser.hsb = HSB\ncolor_chooser.recent = Попередній\ncolor_chooser.alpha = Прозорість\ncolor_chooser.title = Виберіть колір\nbatch_rename_dialog.mask = Маска перейменування\nbatch_rename_dialog.search_replace = Знайти та замінити\nbatch_rename_dialog.search_for = Шукати\nbatch_rename_dialog.replace_with = Замінити на\nbatch_rename_dialog.counter = Лічильник\nbatch_rename_dialog.start_at = Почати з\nbatch_rename_dialog.step_by = Крок\nbatch_rename_dialog.format = Формат\nbatch_rename_dialog.upper_lower_case = Верхній/нижній регістр\nbatch_rename_dialog.no_change = Без змін\nbatch_rename_dialog.lower_case = нижній регістр\nbatch_rename_dialog.upper_case = ВЕРХІНЙ РЕГІСТР\nbatch_rename_dialog.first_upper = Велика парша буква\nbatch_rename_dialog.word = Перша У Кожному Слові\nbatch_rename_dialog.old_name = Старе ім'я\nbatch_rename_dialog.new_name = Нове ім'я\nbatch_rename_dialog.block_name = Не змінювати\nbatch_rename_dialog.range = Інтервал\nbatch_rename_dialog.proceed_renaming = %1 фа1ли з %2 будуть перейменовані. Продовжити ?\nbatch_rename_dialog.duplicate_names = Імена співпадають\nbatch_rename_dialog.names_conflict = Конфлікт імен! Деякі значення співпадають в старих і нових іменах.\nparent_folders_quick_list.empty_message = Ви на верхньому рівні\nrecent_locations_quick_list.empty_message = Нема попереднього місцезнаходження\nrecent_executed_files_quick_list.empty_message = Нема попереднього виконаного файла\n#table.hide_column = Сховати колонку\n#delete.symlink_warning_title = Знайдено символьне посилання\n#delete.symlink_warning = Даний файл є символьним посиланням:\\n\\n Файл: %1 посилається на: %2\\n\\nВидалити тільки символьне посилання чи\\n\\nПерейти за посиланням і видалити папку (УВАГА) ?\n#delete.delete_link_only = Видалити посилання\n#delete.delete_linked_folder = Видалати папку\n#Unpack.label = Розпакувати файли\n#Pack.label = Стиснути файли\n"
  },
  {
    "path": "src/main/resources/dictionary_zh_CN.properties",
    "content": "ok = 确定\nyes = 是\nno = 否\ncancel = 取消\nedit = 编辑\nclose = 关闭\nreset = 复位\nrename = 重新命名\napply = 应用\nchange = 变更\nsave = 保存\ndont_save = 不保存\nreplace = 替换\ndont_replace = 不替换\ndelete = 删除\nskip = 忽略\nskip_all = 全部忽略\nretry = 重试\nresume = 继续\noverwrite = 覆盖\noverwrite_if_older = 若较旧则覆盖\nduplicate = 复制\napply_to_all = 全部应用\ncopy = 复制\nmove = 移动\npack = 压缩\nunpack = 解压缩\ndownload = 下载\nsplit = 分割\ncombine = 合并\nbrowse = 浏览\nask = 询问\nstop = 停止\npause = 暂停\nquick_search = 快速搜索\nfile_manager = 文件管理器\ncreate = 创建\ncreating_file = 正在创建 %1\nchoose = 选择\ncustomize = 自定义\nchoose_folder = 选择文件夹\nlogin = 登录\npassword = 密码\nuser = 用户\nencoding = 编码\npreferred_encodings = 偏好的编码\nlicense = 许可证\nname = 名称\nsize = 大小\ndate = 日期\nextension = 扩展名\npermissions = 权限\nowner = 所有者\ngroup = 群组\nlocation = 位置\nuntitled = 未命名\nsource = 来源\ndestination = 目的\nrecurse_directories = 递归所有目录\ngo_to = 转到\nexample = 示例\npreview = 预览\ncomment = 注释\nsample_text = 示例文本\nnb_files = %1 文件\nnb_folders = %1 文件夾\nloading = 载入中...\nthis_operation_cannot_be_undone = 此操作不能还原.\nremove = 移除\ndetails = 详细\nwarning = 警告\nerror = 错误\ngeneric_error = 处理指定操作时, 发生错误.\nfolder_does_not_exist = 文件夹不存在或无法使用.\nthis_folder_does_not_exist = 文件夹不存在或无法使用: %1\nthis_file_does_not_exist = 文件不存在或无法使用: %1\ninvalid_path = 无效路径: %1\ndirectory_already_exists = 文件夹 %1 已存在.\nfile_exists_in_destination = 文件已经在目的地\nsource_parent_of_destination = 试图传送一个文件夹到其子文件夹\ncannot_read_file = 无法读取文件 %1\ncannot_write_file = 无法写入文件 %1\ncannot_create_folder = 无法创建目录 %1\ncannot_read_folder = 无法读取文件夹内容 %1\ncannot_delete_file = 无法删除文件 %1\ncannot_delete_folder = 无法删除文件夹 %1\nerror_while_transferring = 传送文件时出现错误 %1\nsame_source_destination = 源/目的文件夹相同\nfile_already_exists = %1 已存在, 你是否要取代它 ?\nwrite_error = 写入错误\nread_error = 读取错误\nintegrity_check_error = 完整性检查失败: 来源和目的不一致\nstartup_error = 一个错误阻止了 trolCommander 的启动\npermissions.read = 读取\npermissions.write = 写入\npermissions.executable = 执行\npermissions.group = 组\npermissions.other = 其它\npermissions.octal_notation = 八进制表示\naction_categories.all = 全部\naction_categories.navigation = 导航\naction_categories.selection = 选择\naction_categories.view = 查看\naction_categories.file_operations = 文件操作\naction_categories.windows = 窗口\nTerminal.label = Terminal\nTerminal.tooltip = Terminal\nFindFile.label = Find file\nFindFile.tooltip = Find file\nAddBookmark.label = 添加书签\nAddBookmark.tooltip = 收藏当前文件夹\nBatchRename.label = 批量改名\nEditBookmarks.label = 编辑书签\nExploreBookmarks.label = 浏览书签\nEditCredentials.label = 编辑认证资料\nChangeLocation.label = 改变当前位置\nChangeDate.label = 改变日期\nChangeDate.tooltip = 改变所选文件的日期\nChangePermissions.label = 改变权限\nChangePermissions.tooltip = 改变所选文件的权限\nCheckForUpdates.label = 检查更新\nCompareFolders.label = 比较文件夹\nConnectToServer.label = 连接到服务器\nConnectToServer.tooltip = 连接到远程服务器\nView.label = 查看\nInternalView.label = 查看 (内置)\nView.tooltip = 查看所选文件\nInternalEdit.label = 编辑 (内置)\nEdit.tooltip = 编辑所选文件\nCopy.tooltip = 复制已标记文件\nLocalCopy.label = 本地复制\nLocalCopy.tooltip = 复制所选文件到当前文件夹\nMove.tooltip = 移动已标记文件\nRename.tooltip = 重新命名选定文件\nMkdir.label = 新建文件夹\nMkdir.tooltip = 在当前文件夹新建目录\nMkfile.label = 创建文件\nMkfile.tooltip = 在当前文件夹创建文件\nDelete.tooltip = 可能的话，将标记的文件放入回收站\nPermanentDelete.label = 永久删除\nPermanentDelete.tooltip = 永久删除标记的文件\nRefresh.label = 刷新\nRefresh.tooltip = 刷新当前文件夹\nCloseWindow.label = 关闭窗口\nCloseWindow.tooltip = 关闭此窗口\nCopyFileNames.label = 拷贝名称\nCopyFilePaths.label = 拷贝路径\nCopyFilesToClipboard.label = 拷贝文件\nPasteClipboardFiles.label = 粘贴文件\nEmail.label = 邮寄文件\nEmail.tooltip = 作为邮件附件发送标记文件\nGoBack.label = 退回\nGoBack.tooltip = 到前一个文件夹\nGoForward.label = 前进\nGoForward.tooltip = 到后一个文件夹\nGoToHome.label = 到主目录\nGoToParent.label = 上一级\nGoToParent.tooltip = 到父文件夹\nGoToParentInOtherPanel.label = 另一个面板返回上一级\nGoToParentInBothPanels.label = 两边面板同时返回上一级\nGoToRoot.label = 到根目录\nSortByName.label = 按名称排序\nSortByDate.label = 按日期排序\nSortBySize.label = 按大小排序\nSortByExtension.label = 按类型排序\nSortByPermissions.label = 按权限排序\nSortByOwner.label = 按所有者排序\nSortByGroup.label = 按组排序\nMarkGroup.label = 标记文件\nMarkGroup.tooltip = 标记一组文件\nUnmarkGroup.label = 取消标记\nUnmarkGroup.tooltip = 取消一组文件的标记\nMarkAll.label = 全部标记\nUnmarkAll.label = 取消全部标记\nMarkSelectedFile.label = 标记/取消标记\nMarkSelectedFile.tooltip = 标记/取消标记 选定文件\nMarkNextBlock.label = 往下标记一个区块\nMarkPreviousBlock.label = 往上标记一个区块\nMarkNextRow.label = 往下标记一行\nMarkPreviousRow.label = 往上标记一行\nMarkNextPage.label = 标记文件到下一页\nMarkPreviousPage.label = 标记文件到上一页\nMarkToFirstRow.label = 标记文件到开头\nMarkToLastRow.label = 标记文件到结尾\nMarkExtension.label = 标记扩展名\nInvertSelection.label = 反向标记\nSwapFolders.label = 文件夹互换\nSwapFolders.tooltip = 交换左右文件夹\nSetSameFolder.label = 设为同一文件夹\nSetSameFolder.tooltip = 设置左右文件夹为同一目录\nNewWindow.label = 新窗口\nNewWindow.tooltip = 打开新窗口\nOpen.label = 打开\nOpen.tooltip = 进入文件夹 / 进入压缩包 / 执行\nOpenNatively.label = 使用本机方式打开\nOpenNatively.tooltip = 用系统的文件关联执行选定文件\nOpenInOtherPanel.label = 开启于另一个面板\nOpenInBothPanels.label = 开启于两边面板\nRevealInDesktop.label = 显示在 %1\nRunCommand.label = 执行命令\nRunCommand.tooltip = 在当前文件夹执行命令\nPack.tooltip = 压缩选定文件到压缩包\nUnpack.tooltip = 解压已标记的压缩包\nShowFileProperties.label = 属性\nShowFileProperties.tooltip = 显示已标记文件的属性\nShowPreferences.label = 偏好\nShowPreferences.tooltip = 配置 trolCommander\nShowServerConnections.label = 显示打开的连接\nQuit.label = 退出\nReverseSortOrder.label = 逆序\nToggleAutoSize.label = 自动设置栏宽\nStop.label = 停止改变文件夹\nToggleColumn.show = 显示 %1 列\nToggleColumn.hide = 隐藏 %1 列\nToggleCommandBar.show = 显示命令条\nToggleCommandBar.hide = 隐藏命令条\nToggleToolBar.show = 显示工具条\nToggleToolBar.hide = 隐藏工具条\nCustomizeCommandBar.label = 自定义命令条\nToggleStatusBar.show = 显示状态行\nToggleStatusBar.hide = 隐藏状态行\nToggleShowFoldersFirst.label = 先显示文件夹\nToggleTree.label = 树状显示\nPopupLeftDriveButton.label = 改变左边文件夹\nPopupRightDriveButton.label = 改变右边文件夹\nRecallPreviousWindow.label = 切换到前一个窗口\nRecallNextWindow.label = 切换到后一个窗口\nRecallWindow.label = 还原窗口 #%1\nBringAllToFront.label = 所有都到前面\nSwitchActiveTable.label = 在左右面板间切换\nSelectNextBlock.label = 往下跳一区块\nSelectPreviousBlock.label = 往上跳一区块\nSelectNextPage.label = 往下跳一页\nSelectPreviousPage.label = 往上跳一页\nSelectNextRow.label = 往下跳一行\nSelectPreviousRow.label = 往上跳一行\nSelectFirstRow.label = 选取当前文件夹的第一个文件\nSelectLastRow.label = 选取当前文件夹的最后一个文件\nSplitEqually.label = 平均分割\nSplitVertically.label = 垂直分割\nSplitHorizontally.label = 水平分割\nShowKeyboardShortcuts.label = 快捷键\nGoToWebsite.label = 访问网站\nGoToForums.label = 访问论坛\nReportBug.label = 报告错误\nDonate.label = 捐赠\nShowAbout.label = 关于 trolCommander\nOpenTrash.label = 打开垃圾桶\nEmptyTrash.label = 清空垃圾桶\nCalculateChecksum.label = 计算校验码\nMaximizeWindow.label = 最大化\nMaximizeWindow.label.mac_os_x = 缩放\nMinimizeWindow.label = 最小化\nGoToDocumentation.label = 在线文档\nShowParentFoldersQL.label = 上级目录\nShowRecentLocationsQL.label = 最近开启位置\nShowRecentExecutedFilesQL.label = 最近执行的文件\nSplitFile.tooltip = 将文件分割成多个\nCombineFiles.tooltip = 将分割的文件合并成原始文件\nShowDebugConsole.label = Debug 控制台\nFocusPrevious.label = 将焦点移到前一个组件\nFocusNext.label = 将焦点移到下一个组件\nfile_menu = 文件\nfile_menu.open_with = 用其它方式打开\nmark_menu = 标记\nview_menu = 查看\nview_menu.show_hide_columns = 显示/隐藏栏位\ngo_menu = 到\nbookmarks_menu = 书签\nbookmarks_menu.no_bookmark = 没有书签\nquick_lists_menu = 快速列表\nwindow_menu = 窗口\nhelp_menu = 帮助\nstatus_bar.selected_files = %1 / %2 已选定\nstatus_bar.connecting_to_folder = 正在连接文件夹，按ESCAPE键取消.\nstatus_bar.volume_free = 空闲：%1\nstatus_bar.volume_capacity = 容量 %1\nshortcuts_panel.title = 快捷方式\nshortcuts_panel.restore_defaults = 恢复默认值\nshortcuts_panel.show = 显示\nshortcuts_panel.default_message = 按下 Enter 或双击快捷方式来进行编辑\nshortcuts_table.action_description = 动作描述\nshortcuts_table.shortcut = 快捷方式\nshortcuts_table.alternate_shortcut = 替换快捷方式\nshortcuts_table.type_in_a_shortcut = 在快捷方式输入\ncommand_bar_dialog.help = 拖拽按钮来自定义命令条\ntable.folder_access_error_title = 文件夹存取错误\ntable.folder_access_error = 无法读取文件夹内容\ntable.download_or_browse = 你想浏览还是下载此文件？\nversion_dialog.no_new_version_title = 没有新版本\nversion_dialog.no_new_version = 恭喜，你已经安装最新版本了。\nversion_dialog.new_version_title = 有新版本\nversion_dialog.new_version = 找到 trolCommander 的新版本\nversion_dialog.new_version_url = 找到 trolCommander 的新版本在 %1.\nversion_dialog.not_available_title = 服务器不可到达\nversion_dialog.not_available = 无法从服务器上获得版本信息\nversion_dialog.install_and_restart = 安装并重启\nversion_dialog.preparing_for_update = 正在准备更新...\nquit_dialog.title = 退出 trolCommander\nquit_dialog.desc = 您有 %1 个开启中的窗口. 确认退出 ?\nquit_dialog.show_next_time = 下次还显示\ndestination_dialog.file_exists_action = 文件存在时的缺省动作\ndestination_dialog.verify_integrity = 检查数据完整性\ndestination_dialog.skip_errors = 忽略错误\nfile_collision_dialog.title = 文件冲突\nrename_dialog.new_name = 新名称\ncopy_dialog.destination = 复制选定文件到\ncopy_dialog.error_title = 复制错误\ncopy_dialog.copying = 复制文件中\ncopy_dialog.copying_file = 正在复制 %1\npack_dialog.packing = 正在压缩文件\npack_dialog.packing_file = 正在压缩 %1\npack_dialog.error_title = 压缩错误\npack_dialog_description = 添加选定文件到\npack_dialog.archive_format = 压缩包格式\nunpack_dialog.destination = 选定文件解压到\nunpack_dialog.error_title = 解压错误\nunpack_dialog.unpacking = 正在解压文件\nunpack_dialog.unpacking_file = 正在解压 %1\noptimizing_archive = 优化压缩包 %1\nerror_while_optimizing_archive = 优化压缩包 %1 时出错\nmove_dialog.move_description = 移动到\nmove_dialog.error_title = 移动错误\nmove_dialog.moving = 正在移动文件\nmove_dialog.moving_file = 正在移动 %1\ndownload_dialog.description = 下载文件到\ndownload_dialog.error_title = 下载错误\ndownload_dialog.downloading = 正在下载\ndownload_dialog.downloading_file = 正在下载 %1\nmkfile_dialog.allocate_space = 分配空间\ndelete_dialog.permanently_delete.confirmation = 永久刪除选定文件 ?\ndelete_dialog.move_to_trash.confirmation = 删除所选文件?\ndelete_dialog.move_to_trash.confirmation_details = 文件将被移动到垃圾桶.\ndelete_dialog.move_to_trash.option = 移动到垃圾桶\ndelete_dialog.move_to_trash.failed = 一个或多个文件无法移动到回收站\ndelete_dialog.deleting = 正在刪除\ndelete_dialog.error_title = 刪除錯誤\ndelete.deleting_file = 正在刪除 %1\nemail_dialog.prefs_not_set_title = 邮件尚未配置\nemail_dialog.prefs_not_set = 你需要先设置邮件参数\nemail_dialog.from = 发信人\nemail_dialog.to = 收信人\nemail_dialog.subject = 主题\nemail_dialog.send = 发送\nemail_dialog.error_title = Email文件错误\nemail_dialog.read_error = 无法读取子目录中的文件.\nemail_dialog.sending = 发送文件\nemail.sending_file = 正在发送 %1\nemail.connecting_to_server = 连接到 %1\nemail.server_unavailable = 无法接洽邮件服务器 %1, 检查你的邮件首选项或稍后重试\nemail.connection_closed = 连接被服务器关闭, 邮件未发出\nemail.goodbye_failed = 关闭连接时出错, 邮件可能尚未发出\nemail.send_file_error = 无法发送文件 %1, 邮件未发出\nsplit_file_dialog.error_title = 分割文件错误\nsplit_file_dialog.file_to_split = 要分割的文件\nsplit_file_dialog.target_directory = 目标目录\nsplit_file_dialog.part_size = 分割大小\nsplit_file_dialog.parts = 分割数目\nsplit_file_dialog.generate_CRC = 生成 CRC 文件\nsplit_file_dialog.max_parts = 允许的最大分割数量是 %1\nsplit_file_dialog.auto = 自动\nsplit_file_dialog.insert_new_media = 插入新的介质\ncombine_files_dialog.error_title = 合并文件错误\ncombine_files_job.no_crc_file = 合并成功, 没有 CRC 文件.\ncombine_files_job.crc_read_error = 读取 CRC 文件时发生错误\ncombine_files_job.crc_check_failed = CRC 不符合: 需要 %2, 找到 %1\ncombine_files_job.crc_ok = 合并成功. CRC 校验成功.\nfile_selection_dialog.mark = 标记\nfile_selection_dialog.unmark = 取消标记\nfile_selection_dialog.mark_description = 要标记文件名为\nfile_selection_dialog.unmark_description = 要取消标记的文件名为\nfile_selection_dialog.case_sensitive = 区分大小写\nfile_selection_dialog.include_folders = 包含文件夹\nprogress_dialog.starting = 传送开始...\nprogress_dialog.transferred = 已传送 %1 速度 %2\nprogress_dialog.elapsed_time = 已用时间\nprogress_dialog.advanced = 高级\nprogress_dialog.current_speed = 当前速度\nprogress_dialog.limit_speed = 限制速度\nprogress_dialog.close_when_finished = 完成后关闭窗口\nprogress_dialog.processing_files = 正在处理文件\nprogress_dialog.processing_file = 正在处理 %1\nprogress_dialog.verifying_file = 正在检查 %1\nprogress_dialog.job_finished = 工作完成\nprogress_dialog.job_error = 工作出错\nproperties_dialog.file_properties = %1 属性\nproperties_dialog.contents = 內容\nproperties_dialog.calculating = 正在计算...\ncalculate_checksum_dialog.checksum_algorithm = 校验码算法\ncalculate_checksum_dialog.temporary_file = 临时文件\nchange_date_dialog.now = 现在\nchange_date_dialog.specific_date = 指定日期\nrun_dialog.run_command_description = 在此文件夹执行\nrun_dialog.run_in_home_description = 在主目录下执行\nrun_dialog.command_output = 命令输出\nrun_dialog.run = 运行\nrun_dialog.clear_history = 清除历史记录\nserver_connect_dialog.server_type = 连接类型\nserver_connect_dialog.server = 服务器\nserver_connect_dialog.share = 共享\nserver_connect_dialog.domain = 域\nserver_connect_dialog.username = 用户名\nserver_connect_dialog.initial_dir = 起始目录\nserver_connect_dialog.port = 端口\nserver_connect_dialog.server_url = 服务器URL\nserver_connect_dialog.http_url = 网站URL\nserver_connect_dialog.connect = 连接\nserver_connect_dialog.protocol = 协议\nserver_connect_dialog.nfs_version = NFS版本\nserver_connect_dialog.private_key = 私钥\nserver_connect_dialog.passphrase = 密码\nftp_connect.passive_mode = 启用被动模式(Passive)\nftp_connect.anonymous_user = 匿名用户\nftp_connect.nb_connection_retries = 连接重试次数\nftp_connect.retry_delay = 重试延迟时间(秒)\nhttp_connect.basic_authentication = HTTP Basic Authentication (可选)\nserver_connections_dialog.disconnect = 断开\nserver_connections_dialog.connection_busy = 正忙\nserver_connections_dialog.connection_idle = 空闲\nbonjour.bonjour_services = Bonjour 服务\nbonjour.no_service_discovered = 未发现任何服务\nbonjour.bonjour_disabled = Bonjour 被禁\nauth_dialog.title = 验证\nauth_dialog.desc = 請輸入用户名及密码\nauth_dialog.server = 服务器\nauth_dialog.store_credentials = 储存用户名及密码 (仅作简单加密!)\nauth_dialog.connect_as = 连接为\nauth_dialog.authentication_failed = 验证失败\nsortable_list.move_up = 上移\nsortable_list.move_down = 下移\nadd_bookmark_dialog.add = 添加\nedit_bookmarks_dialog.new = 添加\nfile_viewer.view_error_title = 查看错误\nfile_viewer.view_error = 无法查看文件\nfile_viewer.file_menu = 文件\nfile_viewer.close = 关闭\nfile_viewer.large_file_warning = 文件可能太大无法进行此项操作\nfile_viewer.open_anyway = 强制打开\ntext_viewer.edit = 编辑\ntext_viewer.copy = 复制\ntext_viewer.select_all = 全选\ntext_viewer.find = 查找\ntext_viewer.find_next = 查找下一个\ntext_viewer.find_previous = 查找上一个\ntext_viewer.binary_file_warning = 这是二进制文件\nimage_viewer.controls_menu = 控制\nimage_viewer.zoom_in = 放大\nimage_viewer.zoom_out = 缩小\nfile_editor.edit_error_title = 编辑错误\nfile_editor.edit_error = 无法编辑文件\nfile_editor.save = 保存\nfile_editor.save_as = 另存为...\nfile_editor.save_warning = 关闭前保存对文件的修改 ?\nfile_editor.cannot_write = 无法写入文件.\ntext_editor.cut = 剪切\ntext_editor.paste = 粘贴\nshortcuts_dialog.quick_search.start_search = 键入任何字符启用快速搜索\nshortcuts_dialog.quick_search.cancel_search = 取消快速搜索\nshortcuts_dialog.quick_search.remove_last_char = 从快速搜索字符串中删除最后一個字符\nshortcuts_dialog.quick_search.jump_to_previous = 跳至上次快速搜索的结果\nshortcuts_dialog.quick_search.jump_to_next = 跳至下个快速搜索的结果\nshortcuts_dialog.quick_search.mark_jump_next = 标记/取消标记当前文件并跳至下各搜索结果\ntheme_editor.title = 主题编辑器\ntheme_editor.folder_tab = 文件夹面板\ntheme_editor.shell_tab = Shell\ntheme_editor.shell_history_tab = Shell 历史记录\ntheme_editor.statusbar_tab = 状态栏\ntheme_editor.free_space = 剩余空间\ntheme_editor.free_space.ok = OK\ntheme_editor.free_space.warning = 告警\ntheme_editor.free_space.critical = 紧急\ntheme_editor.locationbar_tab = 位置栏\ntheme_editor.editor_tab = 文件编辑器\ntheme_editor.font = 字体\ntheme_editor.active_panel = 取得焦点\ntheme_editor.inactive_panel = 失去焦点\ntheme_editor.general = 一般\ntheme_editor.could_not_save_theme = 无法写入主题 %1\ntheme_editor.border = 边框\ntheme_editor.background = 背景\ntheme_editor.alternate_background = 间隔替代背景\ntheme_editor.unfocused_background = 背景(无焦点)\ntheme_editor.quick_search.unmatched_file = 未匹配的文件\ntheme_editor.text = 文本\ntheme_editor.progress = 进度\ntheme_editor.normal = 正常\ntheme_editor.normal_unfocused = 正常(无焦点)\ntheme_editor.selected = 选中的\ntheme_editor.selected_unfocused = 选中的(无焦点)\ntheme_editor.color = 颜色\ntheme_editor.colors = 颜色\ntheme_editor.plain_file = 一般文件\ntheme_editor.marked_file = 已标记文件\ntheme_editor.hidden_file = 隐藏文件\ntheme_editor.folder = 文件夹\ntheme_editor.archive_file = 压缩文件\ntheme_editor.symbolic_link = 符号连接\ntheme_editor.header = 标头\ntheme_editor.item = 项目\ncommand_bar_customize_dialog.available_actions = 可供选择的动作\ncommand_bar_customize_dialog.modifier = 修饰键\nprefs_dialog.title = 首选项\nprefs_dialog.general_tab = 一般\nprefs_dialog.day = 日\nprefs_dialog.month = 月\nprefs_dialog.year = 年\nprefs_dialog.language = 语言 (需重新启动)\nprefs_dialog.date_time = 日期及时间格式\nprefs_dialog.time = 时间\nprefs_dialog.date = 日期\nprefs_dialog.date_separator = 分隔符\nprefs_dialog.time_12_hour = 12小时制\nprefs_dialog.time_24_hour = 24小时制\nprefs_dialog.show_seconds = 显示秒\nprefs_dialog.show_century = 显示世纪\nprefs_dialog.check_for_updates_on_startup = 启动时检查新版本\nprefs_dialog.show_splash_screen = 启动时显示欢迎画面\nprefs_dialog.folders_tab = 文件夹\nprefs_dialog.startup_folders = 起始文件夹\nprefs_dialog.left_folder = 左文件夹\nprefs_dialog.right_folder = 右文件夹\nprefs_dialog.last_folder = 最后一次访问的文件夹\nprefs_dialog.custom_folder = 定制文件夹\nprefs_dialog.show_hidden_files = 显示隐藏文件\nprefs_dialog.show_ds_store_files = 显示 .DS_Store 文件\nprefs_dialog.show_system_folders = 显示系统文件夾\nprefs_dialog.compact_file_size = 显示文件概略大小\nprefs_dialog.follow_symlinks_when_cd = 改变当前目录时跟随符号连接\nprefs_dialog.appearance_tab = 展现\nprefs_dialog.look_and_feel = 外观\nprefs_dialog.icons_size = 图标大小\nprefs_dialog.toolbar_icons = 工具条\nprefs_dialog.command_bar_icons = 命令条\nprefs_dialog.file_icons = 文件类型\nprefs_dialog.use_system_file_icons = 使用系统文件图标\nprefs_dialog.use_system_file_icons.always = 总是\nprefs_dialog.use_system_file_icons.never = 从不\nprefs_dialog.use_system_file_icons.applications = 只对应用程序\nprefs_dialog.edit_current_theme = 编辑当前主题\nprefs_dialog.themes = 主题\nprefs_dialog.import_theme = 导入主题\nprefs_dialog.import_look_and_feel = 导入外观(look & feel)\nprefs_dialog.no_look_and_feel = 找不到外观(look & feel).\nprefs_dialog.error_in_import = 导入主题 %1 时出错.\nprefs_dialog.cannot_read_theme = 不能从文件 %1 中载入主题\nprefs_dialog.export_theme = 导出 %1\nprefs_dialog.import = 导入\nprefs_dialog.export = 导出\nprefs_dialog.theme_type = 类型: %1\nprefs_dialog.delete_theme = 永久删除主题 %1 ?\nprefs_dialog.delete_look_and_feel = 永久删除外观(look & feel) %1？\nprefs_dialog.rename_failed = 未能重命名主题 %1\nprefs_dialog.xml_file = XML 文件\nprefs_dialog.jar_file = JAR 文件\nprefs_dialog.mail_tab = 邮件\nprefs_dialog.mail_settings = 外发邮件设置\nprefs_dialog.mail_name = 你的姓名\nprefs_dialog.mail_address = 你的邮件地址\nprefs_dialog.mail_server = SMTP服务器\nprefs_dialog.misc_tab = 其它\nprefs_dialog.use_brushed_metal = 使用 'brushed metal' 外观 (需重新启动)\nprefs_dialog.confirm_on_quit = 退出时显示确认对话框\nprefs_dialog.default_shell = 使用缺省的系统 shell\nprefs_dialog.custom_shell = 使用定制 shell\nprefs_dialog.shell_encoding = Shell 编码\nprefs_dialog.auto_detect_shell_encoding = 自动检测\nprefs_dialog.enable_bonjour_discovery = 打开 Bonjour 服务发现功能\nprefs_dialog.enable_system_notifications = 打开系统通告\ndebug_console_dialog.level = 等级\nunit.byte = 字节\nunit.bytes = 字节\nunit.bytes_short = b\nunit.kb = KB\nunit.mb = MB\nunit.gb = GB\nunit.tb = TB\nunit.speed = %1/s\nduration.seconds = %1s\nduration.minutes = %1m\nduration.hours = %1h\nduration.days = %1d\nduration.months = %1mo\nduration.years = %1y\ntheme.custom_theme = 定制的主题\ntheme.custom = 定制的\ntheme.built_in = 内置的\ntheme.add_on = 附加\ntheme.current = 当前\ntheme_could_not_be_loaded = 在装入此主题时发生错误\nsetup.title = 欢迎进入 trolCommander\nsetup.intro = 请选择你所希望的 trolCommander 行为模式\nsetup.look_and_feel = 选择你要的外观\nsetup.theme = 选择你的主题\nfont_chooser.font_size = 字号\nfont_chooser.font_bold = 粗体\nfont_chooser.font_italic = 斜体\ncolor_chooser.red = 红色\ncolor_chooser.green = 绿色\ncolor_chooser.blue = 蓝色\ncolor_chooser.hue = 色调\ncolor_chooser.brightness = 亮度\ncolor_chooser.swatches = 采样\ncolor_chooser.saturation = 饱和度\ncolor_chooser.rgb = RGB\ncolor_chooser.hsb = HSB\ncolor_chooser.recent = 最近的\ncolor_chooser.alpha = Alpha 透明度\ncolor_chooser.title = 挑选一个颜色\nbatch_rename_dialog.mask = 重命名模式\nbatch_rename_dialog.search_replace = 查找并替换\nbatch_rename_dialog.search_for = 查找\nbatch_rename_dialog.replace_with = 替换为\nbatch_rename_dialog.counter = 计数器\nbatch_rename_dialog.start_at = 开始于\nbatch_rename_dialog.step_by = 每次递增\nbatch_rename_dialog.format = 格式\nbatch_rename_dialog.upper_lower_case = 大小写\nbatch_rename_dialog.no_change = 无变更\nbatch_rename_dialog.lower_case = 小写\nbatch_rename_dialog.upper_case = 大写\nbatch_rename_dialog.first_upper = 首字母大写\nbatch_rename_dialog.word = 每个单词的首字母\nbatch_rename_dialog.old_name = 原名称\nbatch_rename_dialog.new_name = 新名称\nbatch_rename_dialog.block_name = 保留\nbatch_rename_dialog.range = 范围\nbatch_rename_dialog.proceed_renaming = %1 个文件(全部 %2 个)将会被重命名. 确认执行?\nbatch_rename_dialog.duplicate_names = 重复的名称 !\nparent_folders_quick_list.empty_message = 当前位置没有上一级\nrecent_locations_quick_list.empty_message = 没有最近打开的位置\nrecent_executed_files_quick_list.empty_message = 没有最近执行的文件\n#pack.error_on_file = 压缩时发生错误 %1\n#pack_dialog.cannot_write = 无法在目的文件夹创建压缩文件\n#unpack.unable_to_open_zip = 无法打开压缩文件 %1\n#mkdir_dialog.title = 新建文件夹\n#mkdir_dialog.error_title = 新建文件夹错误\n#edit_bookmarks_dialog.remove = 删除\n#mkdir_dialog.description = 创建文件夹\n#done = 完成\n#progress_dialog.hide = 隐藏\n#move_dialog.rename_description = 文件重新命名为\n#theme_editor.shell_font = Shell字体\n#theme_editor.history_font = 历史字体\n#theme_editor.shell_colors = Shell 颜色\n#theme_editor.history_colors = 历史颜色\n#auth_dialog.error_was = 错误: %1\n#table.hide_column = 隐藏栏位\n#delete.symlink_warning_title = 发现符号连接\n#delete.symlink_warning = 此文件像是為符号连接:\\n\\n 文件: %1\\n 连接到: %2\\n\\n只刪除符号连接或\\n跟随连接刪除文件夹 (小心使用) ?\n#delete.delete_link_only = 刪除连接\n#delete.delete_linked_folder = 刪除文件夹\n#Unpack.label = 解压缩文件\n#Pack.label = 压缩文件\n"
  },
  {
    "path": "src/main/resources/dictionary_zh_TW.properties",
    "content": "ok = 確定\nyes = 是\nno = 否\ncancel = 取消\nedit = 編輯\nclose = 關閉\nreset = 重置\nrename = 重新命名\napply = 套用\nchange = 變更\nsave = 儲存\ndont_save = 不儲存\nreplace = 取代\ndont_replace = 不取代\ndelete = 刪除\nskip = 略過\nskip_all = 全部略過\nretry = 重試\nresume = 繼續\noverwrite = 覆寫\noverwrite_if_older = 若較舊則覆寫\nduplicate = 複製\napply_to_all = 全部套用\ncopy = 複製\nmove = 搬移\npack = 壓縮\nunpack = 解壓縮\ndownload = 下載\nsplit = 分割\ncombine = 合併\nbrowse = 瀏覽\nask = 詢問\nstop = 停止\npause = 暫停\nquick_search = 快速搜尋\nfile_manager = 檔案管理員\ncreate = 新增\ncreating_file = 正在建立 %1\nchoose = 選擇\ncustomize = 自訂\nchoose_folder = 選擇資料夾\nlogin = 登入\npassword = 密碼\nuser = 使用者\nencoding = 編碼\npreferred_encodings = 偏好的編碼\nlicense = 版權\nname = 名稱\nsize = 大小\ndate = 日期\nextension = 延伸檔名\npermissions = 權限\nowner = 擁有者\ngroup = 群組\nlocation = 位置\nuntitled = 未命名\nsource = 來源\ndestination = 目的\nrecurse_directories = 遞迴所有目錄\ngo_to = 前往\nexample = 範例\npreview = 預覽\ncomment = 備註\nsample_text = 範例文字\nnb_files = %1 檔案\nnb_folders = %1 資料夾\nloading = 載入中...\nthis_operation_cannot_be_undone = 此操作無法回復\nremove = 移除\ndetails = 細節\nwarning = 警告\nerror = 錯誤\ngeneric_error = 當進行指定操作時, 發生錯誤.\nfolder_does_not_exist = 資料夾不存在或無法使用.\nthis_folder_does_not_exist = 資料夾不存在或無法使用: %1\nthis_file_does_not_exist = 此檔案不存在或是無法使用: %1\ninvalid_path = 不合法的路徑: %1\ndirectory_already_exists = 文件夹 %1 已存在.\ndirectory_already_exists = 文件夹 %1 已存在.\nfile_exists_in_destination = 檔案已經存在於目的地\nsource_parent_of_destination = 嘗試移轉一個資料夾到其子目錄中\ncannot_read_file = 無法讀取檔案 %1\ncannot_write_file = 無法寫入檔案 %1\ncannot_create_folder = 無法產生資料夾 %1\ncannot_read_folder = 無法讀取資料夾內容 %1\ncannot_delete_file = 無法刪除檔案 %1\ncannot_delete_folder = 無法刪除資料夾 %1\nerror_while_transferring = 在傳輸檔案時發生錯誤 %1\nsame_source_destination = 來源及目的資料夾一樣\nfile_already_exists = %1 已存在, 你是否要取代它 ?\nwrite_error = 寫入錯誤\nread_error = 讀取錯誤\nintegrity_check_error = 完整性檢查失敗: 來源及目的不一致\nstartup_error = 有錯誤造成 trolCommander 無法啟動\npermissions.read = 讀取\npermissions.write = 寫入\npermissions.executable = 執行\npermissions.group = 群組\npermissions.other = 其它\npermissions.octal_notation = 八進位顯示\naction_categories.all = 全部\naction_categories.navigation = 導覽\naction_categories.selection = 選取\naction_categories.view = 檢視\naction_categories.file_operations = 檔案操作\naction_categories.windows = 視窗\nTerminal.label = Terminal\nTerminal.tooltip = Terminal\nFindFile.label = Find file\nFindFile.tooltip = Find file\nAddBookmark.label = 新增書籤\nAddBookmark.tooltip = 加入目前資料夾為書籤\nBatchRename.label = 批次改名\nEditBookmarks.label = 編輯書籤\nExploreBookmarks.label = 檢視書籤\nEditCredentials.label = 編輯認證資料\nChangeLocation.label = 變更目前位置\nChangeDate.label = 變更日期\nChangeDate.tooltip = 變更所選檔案的日期\nChangePermissions.label = 變更權限\nChangePermissions.tooltip = 變更所選檔案的權限\nCheckForUpdates.label = 檢查更新版本\nCompareFolders.label = 比較資料夾\nConnectToServer.label = 連接至伺服器\nConnectToServer.tooltip = 連接至遠端伺服器\nView.label = 檢視\nInternalView.label = 檢視 (內建)\nView.tooltip = 檢視已選取檔案\nInternalEdit.label = 編輯 (內建)\nEdit.tooltip = 編輯已選取檔案\nCopy.tooltip = 複製已標記檔案\nLocalCopy.label = 本地複製\nLocalCopy.tooltip = 複製已選取檔案至此資料夾\nMove.tooltip = 搬移已標記檔案\nRename.tooltip = 重新命名已選取檔案\nMkdir.label = 新增資料夾\nMkdir.tooltip = 在此處新增資料夾\nMkfile.label = 新增檔案\nMkfile.tooltip = 在此處新增檔案\nDelete.tooltip = 刪除已標記檔案\nPermanentDelete.label = 永久刪除\nPermanentDelete.tooltip = 永久刪除標記檔案\nRefresh.label = 重新整理\nRefresh.tooltip = 重新整理此資料夾\nCloseWindow.label = 關閉視窗\nCloseWindow.tooltip = 關閉此視窗\nCopyFileNames.label = 拷貝名稱\nCopyFilePaths.label = 拷貝路徑\nCopyFilesToClipboard.label = 拷貝檔案\nPasteClipboardFiles.label = 貼上檔案\nEmail.label = 郵寄檔案\nEmail.tooltip = 傳送已標記檔案成電郵附件\nGoBack.label = 退回\nGoBack.tooltip = 到前一個資料夾\nGoForward.label = 前進\nGoForward.tooltip = 到後一個資料夾\nGoToHome.label = 跳至家目錄\nGoToParent.label = 上一層\nGoToParent.tooltip = 到上一層資料夾\nGoToParentInOtherPanel.label = 另一個面板回到上一層\nGoToParentInBothPanels.label = 兩邊面板同時回到上一層\nGoToRoot.label = 跳至根目錄\nSortByName.label = 依名稱排序\nSortByDate.label = 依日期排序\nSortBySize.label = 依大小排序\nSortByExtension.label = 依類型排序\nSortByPermissions.label = 依權限排序\nSortByOwner.label = 依擁有者排序\nSortByGroup.label = 依群組排序\nMarkGroup.label = 標記檔案\nMarkGroup.tooltip = 標記檔案群組\nUnmarkGroup.label = 取消標記\nUnmarkGroup.tooltip = 取消檔案群組標記\nMarkAll.label = 全部標記\nUnmarkAll.label = 取消全部標記\nMarkSelectedFile.label = 標記/取消標記\nMarkSelectedFile.tooltip = 標記/取消標記 已選取檔案\nMarkNextBlock.label = 往下標記一個區塊\nMarkPreviousBlock.label = 往上標記一個區塊\nMarkNextRow.label = 往下標記一行\nMarkPreviousRow.label = 往上標記一行\nMarkNextPage.label = 標記檔案至下一頁\nMarkPreviousPage.label = 標記檔案至上一頁\nMarkToFirstRow.label = 標記檔案至開頭\nMarkToLastRow.label = 標記檔案至結尾\nMarkExtension.label = 標記延伸檔名\nInvertSelection.label = 反向標記\nSwapFolders.label = 資料夾互換\nSwapFolders.tooltip = 切換左右方的資料夾\nSetSameFolder.label = 跳至同一資料夾\nSetSameFolder.tooltip = 設定左右資料夾成一樣\nNewWindow.label = 新視窗\nNewWindow.tooltip = 開啟新視窗\nOpen.label = 開啟\nOpen.tooltip = 進入資料夾 / 進入壓縮檔 / 執行\nOpenNatively.label = 使用原先方式來開啟\nOpenNatively.tooltip = 利用系統的檔案關聯來執行已選取檔案\nOpenInOtherPanel.label = 開啟於其它面板\nOpenInBothPanels.label = 開啟於兩邊面板\nRevealInDesktop.label = 顯示在 %1\nRunCommand.label = 執行命令\nRunCommand.tooltip = 在此資料夾執行命令\nPack.tooltip = 將標記檔案加入壓縮檔\nUnpack.tooltip = 解開標記的壓縮檔\nShowFileProperties.label = 屬性\nShowFileProperties.tooltip = 顯示已標記檔案的屬性\nShowPreferences.label = 設定\nShowPreferences.tooltip = 設定 trolCommander\nShowServerConnections.label = 顯示開啟的連線\nQuit.label = 離開\nReverseSortOrder.label = 反向排序\nToggleAutoSize.label = 自動調寬欄位\nStop.label = 停止資料夾變更\nToggleColumn.show = 顯示 %1 欄位\nToggleColumn.hide = 隱藏 %1 欄位\nToggleCommandBar.show = 顯示命令列\nToggleCommandBar.hide = 隱藏命令列\nToggleToolBar.show = 顯示工具列\nToggleToolBar.hide = 隱藏工具列\nCustomizeCommandBar.label = 自訂命令工作列\nToggleStatusBar.show = 顯示狀態列\nToggleStatusBar.hide = 隱藏狀態列\nToggleShowFoldersFirst.label = 優先顯示資料夾\nToggleTree.label = 樹狀檢視\nPopupLeftDriveButton.label = 變更左方資料夾\nPopupRightDriveButton.label = 變更右方資料夾\nRecallPreviousWindow.label = 切換至前一個視窗\nRecallNextWindow.label = 切換至後一個視窗\nRecallWindow.label = 還原視窗 #%1\nBringAllToFront.label = 顯示全部視窗至最前面\nSwitchActiveTable.label = 在左右面板間交互切換\nSelectNextBlock.label = 往下跳一區塊\nSelectPreviousBlock.label = 往上跳一區塊\nSelectNextPage.label = 往下跳一頁\nSelectPreviousPage.label = 往上跳一頁\nSelectNextRow.label = 往下跳一行\nSelectPreviousRow.label = 往上跳一行\nSelectFirstRow.label = 選取此資料夾的第一個檔案\nSelectLastRow.label = 選取此資料夾的最後一個檔案\nSplitEqually.label = 等距分割\nSplitVertically.label = 垂直分割\nSplitHorizontally.label = 水平分割\nShowKeyboardShortcuts.label = 快捷鍵\nGoToWebsite.label = 連至網站\nGoToForums.label = 連至討論區\nReportBug.label = 回報問題\nDonate.label = 捐贈\nShowAbout.label = 有關 trolCommander\nOpenTrash.label = 開啟資源回收桶\nEmptyTrash.label = 清空資源回收桶\nCalculateChecksum.label = 計算驗證碼\nMaximizeWindow.label = 最大化\nMinimizeWindow.label = 最小化\nGoToDocumentation.label = 線上文件\nShowParentFoldersQL.label = 上層目錄\nShowRecentLocationsQL.label = 最近開啟位置\nShowRecentExecutedFilesQL.label = 最近執行檔案\nSplitFile.tooltip = 將檔案分割成多個\nCombineFiles.tooltip = 將分割檔合併成原檔案\nShowDebugConsole.label = 偵錯主控台\nFocusPrevious.label = 將輸入焦點移至前一個元件\nFocusNext.label = 將輸入焦點移至後一個元件\nfile_menu = 檔案\nfile_menu.open_with = 用其它方式開啟\nmark_menu = 標記\nview_menu = 檢視\nview_menu.show_hide_columns = 顯示/隱藏欄位\ngo_menu = 跳至\nbookmarks_menu = 書籤\nbookmarks_menu.no_bookmark = 沒有書籤\nquick_lists_menu = 快速列表\nwindow_menu = 視窗\nhelp_menu = 求助\nstatus_bar.selected_files = %1 / %2 已選取\nstatus_bar.connecting_to_folder = 連接至資料夾, 按ESCAPE取消.\nstatus_bar.volume_free = 可用空間: %1\nstatus_bar.volume_capacity = 全部空間: %1\nshortcuts_panel.title = 捷徑\nshortcuts_panel.restore_defaults = 回復預設值\nshortcuts_panel.show = 顯示\nshortcuts_panel.default_message = 按下 Enter 或雙擊捷徑來進行編輯\nshortcuts_table.action_description = 操作描述\nshortcuts_table.shortcut = 捷徑\nshortcuts_table.alternate_shortcut = 替換捷徑\nshortcuts_table.type_in_a_shortcut = 在捷徑輸入\ncommand_bar_dialog.help = 拖曳按鈕來自訂命令工作列\ntable.folder_access_error_title = 資料夾存取錯誤\ntable.folder_access_error = 無法讀取資料夾內容\ntable.download_or_browse = 你要瀏覽或是下載此檔案?\nversion_dialog.no_new_version_title = 沒有新版本\nversion_dialog.no_new_version = 恭喜, 你已經安裝最新版本了.\nversion_dialog.new_version_title = 找到新版本\nversion_dialog.new_version = 找到 trolCommander 的新版本.\nversion_dialog.new_version_url = 找到 trolCommander 的新版本在 %1.\nversion_dialog.not_available_title = 伺服器無法連結\nversion_dialog.not_available = 無法從伺服器上取得版本資訊.\nversion_dialog.install_and_restart = 安裝及重啟\nversion_dialog.preparing_for_update = 準備更新中...\nquit_dialog.title = 離開 trolCommander\nquit_dialog.desc = 你有 %1 個開啟的視窗. 確定要離開 ?\nquit_dialog.show_next_time = 下次再顯示\ndestination_dialog.file_exists_action = 當檔案存在時的預設動作\ndestination_dialog.verify_integrity = 檢查資料完整性\ndestination_dialog.skip_errors = 略過錯誤\nfile_collision_dialog.title = 檔案已存在\nrename_dialog.new_name = 新名稱\ncopy_dialog.destination = 複製已選取檔案至\ncopy_dialog.error_title = 複製錯誤\ncopy_dialog.copying = 複製檔案\ncopy_dialog.copying_file = 正在複製 %1\npack_dialog.packing = 正在壓縮檔案\npack_dialog.packing_file = 正在壓縮 %1\npack_dialog.error_title = 壓縮錯誤\npack_dialog_description = 新增已選取檔案至\npack_dialog.archive_format = 壓縮檔格式\nunpack_dialog.destination = 解壓縮已選取檔案至\nunpack_dialog.error_title = 解壓縮錯誤\nunpack_dialog.unpacking = 正在解壓縮檔案\nunpack_dialog.unpacking_file = 正在解壓縮 %1\noptimizing_archive = 最佳化備份檔 %1\nerror_while_optimizing_archive = 最佳化壓縮檔 %1 時出錯\nmove_dialog.move_description = 搬移至\nmove_dialog.error_title = 搬移錯誤\nmove_dialog.moving = 正在搬移檔案\nmove_dialog.moving_file = 正在搬移 %1\ndownload_dialog.description = 下載檔案至\ndownload_dialog.error_title = 下載錯誤\ndownload_dialog.downloading = 正在下載\ndownload_dialog.downloading_file = 正在下載 %1\nmkfile_dialog.allocate_space = 分配空間\ndelete_dialog.permanently_delete.confirmation = 永久刪除已選取檔案 ?\ndelete_dialog.move_to_trash.confirmation = 刪除所選檔案 ?\ndelete_dialog.move_to_trash.confirmation_details = 檔案將被移至資源回收桶\ndelete_dialog.move_to_trash.option = 移至資源回收桶\ndelete_dialog.move_to_trash.failed = 一個或多個檔案無法移至垃圾桶\ndelete_dialog.deleting = 正在刪除\ndelete_dialog.error_title = 刪除錯誤\ndelete.deleting_file = 正在刪除 %1\nemail_dialog.prefs_not_set_title = 郵件尚未設定\nemail_dialog.prefs_not_set = 你必須要先設定郵件參數\nemail_dialog.from = 寄件者\nemail_dialog.to = 收件者\nemail_dialog.subject = 主旨\nemail_dialog.send = 傳送\nemail_dialog.error_title = 郵寄檔案錯誤\nemail_dialog.read_error = 無法讀取在子目錄中的檔案.\nemail_dialog.sending = 傳送檔案\nemail.sending_file = 正在傳送 %1\nemail.connecting_to_server = 連接至 %1\nemail.server_unavailable = 無法連接至郵件伺服器 %1, 檢查你的郵件設定或稍後重試.\nemail.connection_closed = 連線被伺服器關閉, 郵件未寄出.\nemail.goodbye_failed = 當關閉連線時發生錯誤, 郵件可能尚未寄出.\nemail.send_file_error = 無法傳送檔案 %1, 郵件未寄出.\nsplit_file_dialog.error_title = 分割檔案錯誤\nsplit_file_dialog.file_to_split = 欲分割的檔案\nsplit_file_dialog.target_directory = 目的資料夾\nsplit_file_dialog.part_size = 分割大小\nsplit_file_dialog.parts = 分割數目\nsplit_file_dialog.generate_CRC = 產生 CRC 檔案\nsplit_file_dialog.max_parts = 最多允許的分割數目為 %1\nsplit_file_dialog.auto = 自動\nsplit_file_dialog.insert_new_media = 插入新的媒體\ncombine_files_dialog.error_title = 合併檔案錯誤\ncombine_files_job.no_crc_file = 合併成功, 沒有 CRC 檔案.\ncombine_files_job.crc_read_error = 當讀取 CRC 檔案時發生錯誤.\ncombine_files_job.crc_check_failed = CRC 不符合: 預期 %2, 發現 %1\ncombine_files_job.crc_ok = 合併成功. CRC 驗證成功.\nfile_selection_dialog.mark = 標記\nfile_selection_dialog.unmark = 取消標記\nfile_selection_dialog.mark_description = 標記檔案, 若檔名為\nfile_selection_dialog.unmark_description = 取消標記, 若檔名為\nfile_selection_dialog.case_sensitive = 分大小寫\nfile_selection_dialog.include_folders = 包括資料夾\nprogress_dialog.starting = 傳送開始...\nprogress_dialog.transferred = 已傳送 %1 速度 %2\nprogress_dialog.elapsed_time = 已用時間\nprogress_dialog.advanced = 進階\nprogress_dialog.current_speed = 目前速度\nprogress_dialog.limit_speed = 限制速度\nprogress_dialog.close_when_finished = 完成時關閉視窗\nprogress_dialog.processing_files = 正在處理檔案\nprogress_dialog.processing_file = 正在處理 %1\nprogress_dialog.verifying_file = 正在檢查 %1\nprogress_dialog.job_finished = 工作完成\nprogress_dialog.job_error = 工作錯誤\nproperties_dialog.file_properties = %1 屬性\nproperties_dialog.contents = 內容\nproperties_dialog.calculating = 計算中...\ncalculate_checksum_dialog.checksum_algorithm = 驗證碼演算法\ncalculate_checksum_dialog.temporary_file = 暫存檔\nchange_date_dialog.now = 現在\nchange_date_dialog.specific_date = 指定日期\nrun_dialog.run_command_description = 在此資料夾執行\nrun_dialog.run_in_home_description = 在家目錄下執行\nrun_dialog.command_output = 命令結果輸出\nrun_dialog.run = 執行\nrun_dialog.clear_history = 清除歷史記錄\nserver_connect_dialog.server_type = 連接類型\nserver_connect_dialog.server = 伺服器\nserver_connect_dialog.share = 共用\nserver_connect_dialog.domain = 網域\nserver_connect_dialog.username = 帳號\nserver_connect_dialog.initial_dir = 初始位置\nserver_connect_dialog.port = 埠號\nserver_connect_dialog.server_url = 伺服器URL\nserver_connect_dialog.http_url = Web網址\nserver_connect_dialog.connect = 連接\nserver_connect_dialog.protocol = 協議\nserver_connect_dialog.nfs_version = NFS 版本\nserver_connect_dialog.private_key = 私鑰\nserver_connect_dialog.passphrase = 密碼\nftp_connect.passive_mode = 啟動被動模式(Passive)\nftp_connect.anonymous_user = 匿名使用者\nftp_connect.nb_connection_retries = 連線重試次數\nftp_connect.retry_delay = 重試延遲時間(秒)\nhttp_connect.basic_authentication = HTTP Basic Authentication (非必要)\nserver_connections_dialog.disconnect = 停止連接\nserver_connections_dialog.connection_busy = 忙碌\nserver_connections_dialog.connection_idle = 閒置\nbonjour.bonjour_services = Bonjour 服務\nbonjour.no_service_discovered = 未發現任何服務\nbonjour.bonjour_disabled = Bonjour 功能關閉\nauth_dialog.title = 驗證\nauth_dialog.desc = 請輸入帳號及密碼\nauth_dialog.server = 伺服器\nauth_dialog.store_credentials = 儲存登入帳號及密碼(僅作簡易加密)\nauth_dialog.connect_as = 連線為\nauth_dialog.authentication_failed = 驗證失敗\nsortable_list.move_up = 上移\nsortable_list.move_down = 下移\nadd_bookmark_dialog.add = 新增\nedit_bookmarks_dialog.new = 新增\nfile_viewer.view_error_title = 檢視錯誤\nfile_viewer.view_error = 無法檢視檔案\nfile_viewer.file_menu = 檔案\nfile_viewer.close = 關閉\nfile_viewer.large_file_warning = 檔案可能太大而無法進行此操作\nfile_viewer.open_anyway = 強迫開啟\ntext_viewer.edit = 編輯\ntext_viewer.copy = 複製\ntext_viewer.select_all = 全部選取\ntext_viewer.find = 尋找\ntext_viewer.find_next = 尋找下一個\ntext_viewer.find_previous = 尋找上一個\ntext_viewer.binary_file_warning = 此為二進位檔案\nimage_viewer.controls_menu = 控制\nimage_viewer.zoom_in = 放大\nimage_viewer.zoom_out = 縮小\nfile_editor.edit_error_title = 編輯錯誤\nfile_editor.edit_error = 無法編輯檔案\nfile_editor.save = 儲存\nfile_editor.save_as = 另存為...\nfile_editor.save_warning = 在關閉之前是否儲存檔案變更 ?\nfile_editor.cannot_write = 無法寫入檔案.\ntext_editor.cut = 剪下\ntext_editor.paste = 貼上\nshortcuts_dialog.quick_search.start_search = 輸入任何字元來啟用快速搜尋\nshortcuts_dialog.quick_search.cancel_search = 取消快速搜尋\nshortcuts_dialog.quick_search.remove_last_char = 從快速搜尋的字串中移除最後一個字元\nshortcuts_dialog.quick_search.jump_to_previous = 跳至上一個快速搜尋的結果\nshortcuts_dialog.quick_search.jump_to_next = 跳至下一個快速搜尋的結果\nshortcuts_dialog.quick_search.mark_jump_next = 標記/取消標記目前檔案並跳至下一個搜尋結果\ntheme_editor.title = 佈景主題編輯器\ntheme_editor.folder_tab = Folder pane\ntheme_editor.shell_tab = Shell\ntheme_editor.shell_history_tab = Shell 歷史記錄\ntheme_editor.statusbar_tab = 狀態列\ntheme_editor.free_space = 剩餘空間\ntheme_editor.free_space.ok = OK\ntheme_editor.free_space.warning = 警告\ntheme_editor.free_space.critical = 嚴重\ntheme_editor.locationbar_tab = 位址列\ntheme_editor.editor_tab = 檔案編輯器\ntheme_editor.font = 字型\ntheme_editor.active_panel = 取得焦點\ntheme_editor.inactive_panel = 失去焦點\ntheme_editor.general = 一般\ntheme_editor.could_not_save_theme = 無法寫入佈景主題 %1\ntheme_editor.border = 框線\ntheme_editor.background = 背景\ntheme_editor.alternate_background = 間隔替代背景\ntheme_editor.unfocused_background = 背景 (無焦點)\ntheme_editor.quick_search.unmatched_file = 沒有匹配的檔案\ntheme_editor.text = 文字\ntheme_editor.progress = 進度\ntheme_editor.normal = 一般\ntheme_editor.normal_unfocused = 一般 (無焦點)\ntheme_editor.selected = 被選取\ntheme_editor.selected_unfocused = 被選取 (無焦點)\ntheme_editor.color = 顏色\ntheme_editor.colors = 顏色\ntheme_editor.plain_file = 一般檔案\ntheme_editor.marked_file = 已標記檔案\ntheme_editor.hidden_file = 隱藏檔\ntheme_editor.folder = 資料夾\ntheme_editor.archive_file = 壓縮檔\ntheme_editor.symbolic_link = 符號連結\ntheme_editor.header = 標頭\ntheme_editor.item = 項目\ncommand_bar_customize_dialog.available_actions = 可供選擇的操作\ncommand_bar_customize_dialog.modifier = 切換鍵\nprefs_dialog.title = 設定\nprefs_dialog.general_tab = 一般\nprefs_dialog.day = 日\nprefs_dialog.month = 月\nprefs_dialog.year = 年\nprefs_dialog.language = 語言 (需重新啟動)\nprefs_dialog.date_time = 日期及時間格式\nprefs_dialog.time = 時間\nprefs_dialog.date = 日期\nprefs_dialog.date_separator = 分隔符號\nprefs_dialog.time_12_hour = 12小時制\nprefs_dialog.time_24_hour = 24小時制\nprefs_dialog.show_seconds = 顯示秒數\nprefs_dialog.show_century = 顯示世紀\nprefs_dialog.check_for_updates_on_startup = 啟動時檢查是否有更新版本\nprefs_dialog.show_splash_screen = 顯示啟動歡迎畫面\nprefs_dialog.folders_tab = 資料夾\nprefs_dialog.startup_folders = 開始資料夾\nprefs_dialog.left_folder = 左方資料夾\nprefs_dialog.right_folder = 右方資料夾\nprefs_dialog.last_folder = 上次所在的資料夾\nprefs_dialog.custom_folder = 自訂資料夾\nprefs_dialog.show_hidden_files = 顯示隱藏檔案\nprefs_dialog.show_ds_store_files = 顯示 .DS_Store 檔案\nprefs_dialog.show_system_folders = 顯示系統資料夾\nprefs_dialog.compact_file_size = 顯示檔案約略大小\nprefs_dialog.follow_symlinks_when_cd = 切換目前資料夾時跟隨符號鏈結\nprefs_dialog.appearance_tab = 呈現\nprefs_dialog.look_and_feel = Look & Feel\nprefs_dialog.icons_size = 圖示大小\nprefs_dialog.toolbar_icons = 工具列\nprefs_dialog.command_bar_icons = 指令列\nprefs_dialog.file_icons = 檔案類型\nprefs_dialog.use_system_file_icons = 使用系統檔案的小圖示\nprefs_dialog.use_system_file_icons.always = 總是\nprefs_dialog.use_system_file_icons.never = 從不\nprefs_dialog.use_system_file_icons.applications = 只限應用程式\nprefs_dialog.edit_current_theme = 編輯目前佈景主題...\nprefs_dialog.themes = 佈景主題\nprefs_dialog.import_theme = 匯入佈景主題\nprefs_dialog.import_look_and_feel = 匯入 look & feel\nprefs_dialog.no_look_and_feel = 找不到 look & feel.\nprefs_dialog.error_in_import = 匯入佈景主題時發生錯誤 %1.\nprefs_dialog.cannot_read_theme = 無法載入佈景主題從檔案 %1\nprefs_dialog.export_theme = 匯出 %1\nprefs_dialog.import = 匯入\nprefs_dialog.export = 匯出\nprefs_dialog.theme_type = 類型: %1\nprefs_dialog.delete_theme = 永遠刪除佈景主題 %1 ?\nprefs_dialog.delete_look_and_feel = 永久刪除 look & feel %1 ?\nprefs_dialog.rename_failed = 無法更名佈景主題 %1\nprefs_dialog.xml_file = XML 檔案\nprefs_dialog.jar_file = JAR 檔案\nprefs_dialog.mail_tab = 郵件\nprefs_dialog.mail_settings = 外送郵件設定\nprefs_dialog.mail_name = 你的名稱\nprefs_dialog.mail_address = 你的電郵地址\nprefs_dialog.mail_server = SMTP 伺服器\nprefs_dialog.misc_tab = 其它\nprefs_dialog.use_brushed_metal = 使用 'brushed metal' 風格 (需重新啟動)\nprefs_dialog.confirm_on_quit = 離開時顯示確認對話盒\nprefs_dialog.default_shell = 使用預設的系統 shell\nprefs_dialog.custom_shell = 使用自訂 shell\nprefs_dialog.shell_encoding = Shell 編碼\nprefs_dialog.auto_detect_shell_encoding = 自動偵測\nprefs_dialog.enable_bonjour_discovery = 開啟 Bonjour 服務尋找功能\nprefs_dialog.enable_system_notifications = 啟用系統提醒功能\ndebug_console_dialog.level = 程度\nunit.byte = byte\nunit.bytes = bytes\nunit.bytes_short = b\nunit.kb = KB\nunit.mb = MB\nunit.gb = GB\nunit.tb = TB\nunit.speed = %1/s\nduration.seconds = %1s\nduration.minutes = %1m\nduration.hours = %1h\nduration.days = %1d\nduration.months = %1mo\nduration.years = %1y\ntheme.custom_theme = 自訂佈景主題\ntheme.custom = 自訂\ntheme.built_in = 內建\ntheme.add_on = 外掛\ntheme.current = 目前\ntheme_could_not_be_loaded = 在載入此佈景主題時發生錯誤\nsetup.title = 歡迎來到 trolCommander\nsetup.intro = 請選擇你所希望的 trolCommander 行為模式\nsetup.look_and_feel = 選擇你要的 Look & Feel\nsetup.theme = 選擇你的佈景主題\nfont_chooser.font_size = 大小\nfont_chooser.font_bold = 粗體\nfont_chooser.font_italic = 斜體\ncolor_chooser.red = 紅色\ncolor_chooser.green = 綠色\ncolor_chooser.blue = 藍色\ncolor_chooser.hue = 色調\ncolor_chooser.brightness = 明亮度\ncolor_chooser.swatches = 樣本\ncolor_chooser.saturation = 飽和度\ncolor_chooser.rgb = RGB\ncolor_chooser.hsb = HSB\ncolor_chooser.recent = 最近\ncolor_chooser.alpha = Alpha 透明度\ncolor_chooser.title = 挑選一個顏色\nbatch_rename_dialog.mask = 改名樣式\nbatch_rename_dialog.search_replace = 尋找並取代\nbatch_rename_dialog.search_for = 尋找\nbatch_rename_dialog.replace_with = 取代為\nbatch_rename_dialog.counter = 計數器\nbatch_rename_dialog.start_at = 開始於\nbatch_rename_dialog.step_by = 每次漸增\nbatch_rename_dialog.format = 格式\nbatch_rename_dialog.upper_lower_case = 大小寫\nbatch_rename_dialog.no_change = 無變更\nbatch_rename_dialog.lower_case = 小寫\nbatch_rename_dialog.upper_case = 大寫\nbatch_rename_dialog.first_upper = 首字大寫\nbatch_rename_dialog.word = 每個詞的首字\nbatch_rename_dialog.old_name = 原名稱\nbatch_rename_dialog.new_name = 新名稱\nbatch_rename_dialog.block_name = 保留\nbatch_rename_dialog.range = 範圍\nbatch_rename_dialog.proceed_renaming = %1 個檔案(全部 %2 個)將會被重新命名. 請確認要執行 ?\nbatch_rename_dialog.duplicate_names = 重覆的名稱 !\nparent_folders_quick_list.empty_message = 目前位置沒有上一層\nrecent_locations_quick_list.empty_message = 沒有最近開啟位置\nrecent_executed_files_quick_list.empty_message = 沒有最近執行檔案\n#server_connect_dialog.auth_error = 不合法的帳號或密碼\n#move_dialog.cannot_move_to_itself = 無法搬移檔案至子目錄\n#pack.error_on_file = 當壓縮時發生錯誤 %1\n#pack_dialog.cannot_write = 無法在目的資料夾產生壓縮檔\n#unpack.unable_to_open_zip = 無法打開壓縮檔 %1\n#image_viewer.previous_image = 前一個圖片\n#image_viewer.next_image = 後一個圖片\n#mkdir_dialog.title = 新增資料夾\n#mkdir_dialog.error_title = 新增資料夾錯誤\n#edit_bookmarks_dialog.remove = 移除\n#mkdir_dialog.description = 新增資料夾\n#done = 完成\n#progress_dialog.hide = 隱藏\n#move_dialog.rename_description = 重新命名檔案為\n#theme_editor.shell_font = Shell 字型\n#theme_editor.history_font = 歷史記錄字型\n#theme_editor.shell_colors = Shell 顏色\n#theme_editor.history_colors = 歷史記錄顏色\n#ToggleHiddenFiles.hide = 不顯示隱藏檔案\n#auth_dialog.error_was = 錯誤為: %1\n#table.hide_column = 隱藏欄位\n#delete.symlink_warning_title = 發現符號連結\n#delete.symlink_warning = 此檔案可能為符號連結:\\n\\n 檔案: %1\\n 連結至: %2\\n\\n只刪除符號連結或\\n根據連結來刪除資料夾 (小心使用) ?\n#delete.delete_link_only = 刪除連結\n#delete.delete_linked_folder = 刪除資料夾\n#Unpack.label = 解壓縮檔案\n#Pack.label = 壓縮檔案\n"
  },
  {
    "path": "src/main/resources/license.txt",
    "content": "\n                          ABSTRACT\n\ntrolCommander is released under the terms of the GNU General Public License\nfound below.\n\nThe trolCommander Ant tools and trolCommander Configuration API are released\nseparately under the terms of the Lesser General Public License which\npermits use of the libraries in proprietary programs under certain\nconditions.\n\nAdditionally, trolCommander uses the following third party works:\n- the Ant library under the terms of the Apache License\n- Apache Commons libraries under the terms of the Apache License\n- Apache Hadoop under the terms of the Apache License\n- the Furbelow library under the terms of the GNU Lesser General Public License\n- the ICU4J library under the terms of the ICU License\n- the J2SSH library under the terms of the GNU Lesser General Public License\n- the J7Zip library under the terms of the GNU Lesser General Public License\n- the jCIFS library under the terms of the GNU Lesser General Public License\n- the JetS3t library under the terms of the Apache License\n- the JmDNS library under the terms of the GNU Lesser General Public License\n- the JNA library under the terms of the GNU Lesser General Public License\n- the JUnRar library under the terms of the GNU Lesser General Public License\n- the Yanfs library under the terms of the Berkeley Software Distribution License\n- Mark James' icons under the terms of the Creative Commons Attribution License\n\nAll the above mentioned licenses are included in this file.\n\nCopyright (C) 2002-2010 Maxence Bernard\n\n\n-------------------------------------------------------------------------\n\n\t\t        GNU GENERAL PUBLIC LICENSE\n\t\t          Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\t\t\t    Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n\t\t       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not convey it at all.  For example, if you agree to terms that\nobligate you to collect a royalty for further conveying from those to\nwhom you convey the Program, the only way you could satisfy both those\nterms and this License would be to refrain entirely from conveying the\nProgram.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE\nPROGRAM IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE\nCOST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING\nANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF\nTHE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO\nLOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY\nOTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF\nTHE POSSIBILITY OF SUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n\n-------------------------------------------------------------------------\n\n             GNU LESSER GENERAL PUBLIC LICENSE\n                  Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\n  This version of the GNU Lesser General Public License incorporates\nthe terms and conditions of version 3 of the GNU General Public\nLicense, supplemented by the additional permissions listed below.\n\n  0. Additional Definitions.\n\n  As used herein, \"this License\" refers to version 3 of the GNU Lesser\nGeneral Public License, and the \"GNU GPL\" refers to version 3 of the GNU\nGeneral Public License.\n\n  \"The Library\" refers to a covered work governed by this License,\nother than an Application or a Combined Work as defined below.\n\n  An \"Application\" is any work that makes use of an interface provided\nby the Library, but which is not otherwise based on the Library.\nDefining a subclass of a class defined by the Library is deemed a mode\nof using an interface provided by the Library.\n\n  A \"Combined Work\" is a work produced by combining or linking an\nApplication with the Library.  The particular version of the Library\nwith which the Combined Work was made is also called the \"Linked\nVersion\".\n\n  The \"Minimal Corresponding Source\" for a Combined Work means the\nCorresponding Source for the Combined Work, excluding any source code\nfor portions of the Combined Work that, considered in isolation, are\nbased on the Application, and not on the Linked Version.\n\n  The \"Corresponding Application Code\" for a Combined Work means the\nobject code and/or source code for the Application, including any data\nand utility programs needed for reproducing the Combined Work from the\nApplication, but excluding the System Libraries of the Combined Work.\n\n  1. Exception to Section 3 of the GNU GPL.\n\n  You may convey a covered work under sections 3 and 4 of this License\nwithout being bound by section 3 of the GNU GPL.\n\n  2. Conveying Modified Versions.\n\n  If you modify a copy of the Library, and, in your modifications, a\nfacility refers to a function or data to be supplied by an Application\nthat uses the facility (other than as an argument passed when the\nfacility is invoked), then you may convey a copy of the modified\nversion:\n\n   a) under this License, provided that you make a good faith effort to\n   ensure that, in the event an Application does not supply the\n   function or data, the facility still operates, and performs\n   whatever part of its purpose remains meaningful, or\n\n   b) under the GNU GPL, with none of the additional permissions of\n   this License applicable to that copy.\n\n  3. Object Code Incorporating Material from Library Header Files.\n\n  The object code form of an Application may incorporate material from\na header file that is part of the Library.  You may convey such object\ncode under terms of your choice, provided that, if the incorporated\nmaterial is not limited to numerical parameters, data structure\nlayouts and accessors, or small macros, inline functions and templates\n(ten or fewer lines in length), you do both of the following:\n\n   a) Give prominent notice with each copy of the object code that the\n   Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the object code with a copy of the GNU GPL and this\n   license document.\n\n  4. Combined Works.\n\n  You may convey a Combined Work under terms of your choice that,\ntaken together, effectively do not restrict modification of the\nportions of the Library contained in the Combined Work and reverse\nengineering for debugging such modifications, if you also do each of\nthe following:\n\n   a) Give prominent notice with each copy of the Combined Work that\n   the Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the Combined Work with a copy of the GNU GPL and this\n   license document.\n\n   c) For a Combined Work that displays copyright notices during\n   execution, include the copyright notice for the Library among\n   these notices, as well as a reference directing the user to the\n   copies of the GNU GPL and this license document.\n\n   d) Do one of the following:\n\n       0) Convey the Minimal Corresponding Source under the terms of this\n       License, and the Corresponding Application Code in a form\n       suitable for, and under terms that permit, the user to\n       recombine or relink the Application with a modified version of\n       the Linked Version to produce a modified Combined Work, in the\n       manner specified by section 6 of the GNU GPL for conveying\n       Corresponding Source.\n\n       1) Use a suitable shared library mechanism for linking with the\n       Library.  A suitable mechanism is one that (a) uses at run time\n       a copy of the Library already present on the user's computer\n       system, and (b) will operate properly with a modified version\n       of the Library that is interface-compatible with the Linked\n       Version.\n\n   e) Provide Installation Information, but only if you would otherwise\n   be required to provide such information under section 6 of the\n   GNU GPL, and only to the extent that such information is\n   necessary to install and execute a modified version of the\n   Combined Work produced by recombining or relinking the\n   Application with a modified version of the Linked Version. (If\n   you use option 4d0, the Installation Information must accompany\n   the Minimal Corresponding Source and Corresponding Application\n   Code. If you use option 4d1, you must provide the Installation\n   Information in the manner specified by section 6 of the GNU GPL\n   for conveying Corresponding Source.)\n\n  5. Combined Libraries.\n\n  You may place library facilities that are a work based on the\nLibrary side by side in a single library together with other library\nfacilities that are not Applications and are not covered by this\nLicense, and convey such a combined library under terms of your\nchoice, if you do both of the following:\n\n   a) Accompany the combined library with a copy of the same work based\n   on the Library, uncombined with any other library facilities,\n   conveyed under the terms of this License.\n\n   b) Give prominent notice with the combined library that part of it\n   is a work based on the Library, and explaining where to find the\n   accompanying uncombined form of the same work.\n\n  6. Revised Versions of the GNU Lesser General Public License.\n\n  The Free Software Foundation may publish revised and/or new versions\nof the GNU Lesser General Public License from time to time. Such new\nversions will be similar in spirit to the present version, but may\ndiffer in detail to address new problems or concerns.\n\n  Each version is given a distinguishing version number. If the\nLibrary as you received it specifies that a certain numbered version\nof the GNU Lesser General Public License \"or any later version\"\napplies to it, you have the option of following the terms and\nconditions either of that published version or of any later version\npublished by the Free Software Foundation. If the Library as you\nreceived it does not specify a version number of the GNU Lesser\nGeneral Public License, you may choose any version of the GNU Lesser\nGeneral Public License ever published by the Free Software Foundation.\n\n  If the Library as you received it specifies that a proxy can decide\nwhether future versions of the GNU Lesser General Public License shall\napply, that proxy's public statement of acceptance of any version is\npermanent authorization for you to choose that version for the\nLibrary.\n\n\n-------------------------------------------------------------------------\n\n                            Apache License\n                      Version 2.0, January 2004\n                   http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n  \"License\" shall mean the terms and conditions for use, reproduction,\n  and distribution as defined by Sections 1 through 9 of this document.\n\n  \"Licensor\" shall mean the copyright owner or entity authorized by\n  the copyright owner that is granting the License.\n\n  \"Legal Entity\" shall mean the union of the acting entity and all\n  other entities that control, are controlled by, or are under common\n  control with that entity. For the purposes of this definition,\n  \"control\" means (i) the power, direct or indirect, to cause the\n  direction or management of such entity, whether by contract or\n  otherwise, or (ii) ownership of fifty percent (50%) or more of the\n  outstanding shares, or (iii) beneficial ownership of such entity.\n\n  \"You\" (or \"Your\") shall mean an individual or Legal Entity\n  exercising permissions granted by this License.\n\n  \"Source\" form shall mean the preferred form for making modifications,\n  including but not limited to software source code, documentation\n  source, and configuration files.\n\n  \"Object\" form shall mean any form resulting from mechanical\n  transformation or translation of a Source form, including but\n  not limited to compiled object code, generated documentation,\n  and conversions to other media types.\n\n  \"Work\" shall mean the work of authorship, whether in Source or\n  Object form, made available under the License, as indicated by a\n  copyright notice that is included in or attached to the work\n  (an example is provided in the Appendix below).\n\n  \"Derivative Works\" shall mean any work, whether in Source or Object\n  form, that is based on (or derived from) the Work and for which the\n  editorial revisions, annotations, elaborations, or other modifications\n  represent, as a whole, an original work of authorship. For the purposes\n  of this License, Derivative Works shall not include works that remain\n  separable from, or merely link (or bind by name) to the interfaces of,\n  the Work and Derivative Works thereof.\n\n  \"Contribution\" shall mean any work of authorship, including\n  the original version of the Work and any modifications or additions\n  to that Work or Derivative Works thereof, that is intentionally\n  submitted to Licensor for inclusion in the Work by the copyright owner\n  or by an individual or Legal Entity authorized to submit on behalf of\n  the copyright owner. For the purposes of this definition, \"submitted\"\n  means any form of electronic, verbal, or written communication sent\n  to the Licensor or its representatives, including but not limited to\n  communication on electronic mailing lists, source code control systems,\n  and issue tracking systems that are managed by, or on behalf of, the\n  Licensor for the purpose of discussing and improving the Work, but\n  excluding communication that is conspicuously marked or otherwise\n  designated in writing by the copyright owner as \"Not a Contribution.\"\n\n  \"Contributor\" shall mean Licensor and any individual or Legal Entity\n  on behalf of whom a Contribution has been received by Licensor and\n  subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n  this License, each Contributor hereby grants to You a perpetual,\n  worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n  copyright license to reproduce, prepare Derivative Works of,\n  publicly display, publicly perform, sublicense, and distribute the\n  Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n  this License, each Contributor hereby grants to You a perpetual,\n  worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n  (except as stated in this section) patent license to make, have made,\n  use, offer to sell, sell, import, and otherwise transfer the Work,\n  where such license applies only to those patent claims licensable\n  by such Contributor that are necessarily infringed by their\n  Contribution(s) alone or by combination of their Contribution(s)\n  with the Work to which such Contribution(s) was submitted. If You\n  institute patent litigation against any entity (including a\n  cross-claim or counterclaim in a lawsuit) alleging that the Work\n  or a Contribution incorporated within the Work constitutes direct\n  or contributory patent infringement, then any patent licenses\n  granted to You under this License for that Work shall terminate\n  as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n  Work or Derivative Works thereof in any medium, with or without\n  modifications, and in Source or Object form, provided that You\n  meet the following conditions:\n\n  (a) You must give any other recipients of the Work or\n      Derivative Works a copy of this License; and\n\n  (b) You must cause any modified files to carry prominent notices\n      stating that You changed the files; and\n\n  (c) You must retain, in the Source form of any Derivative Works\n      that You distribute, all copyright, patent, trademark, and\n      attribution notices from the Source form of the Work,\n      excluding those notices that do not pertain to any part of\n      the Derivative Works; and\n\n  (d) If the Work includes a \"NOTICE\" text file as part of its\n      distribution, then any Derivative Works that You distribute must\n      include a readable copy of the attribution notices contained\n      within such NOTICE file, excluding those notices that do not\n      pertain to any part of the Derivative Works, in at least one\n      of the following places: within a NOTICE text file distributed\n      as part of the Derivative Works; within the Source form or\n      documentation, if provided along with the Derivative Works; or,\n      within a display generated by the Derivative Works, if and\n      wherever such third-party notices normally appear. The contents\n      of the NOTICE file are for informational purposes only and\n      do not modify the License. You may add Your own attribution\n      notices within Derivative Works that You distribute, alongside\n      or as an addendum to the NOTICE text from the Work, provided\n      that such additional attribution notices cannot be construed\n      as modifying the License.\n\n  You may add Your own copyright statement to Your modifications and\n  may provide additional or different license terms and conditions\n  for use, reproduction, or distribution of Your modifications, or\n  for any such Derivative Works as a whole, provided Your use,\n  reproduction, and distribution of the Work otherwise complies with\n  the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n  any Contribution intentionally submitted for inclusion in the Work\n  by You to the Licensor shall be under the terms and conditions of\n  this License, without any additional terms or conditions.\n  Notwithstanding the above, nothing herein shall supersede or modify\n  the terms of any separate license agreement you may have executed\n  with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n  names, trademarks, service marks, or product names of the Licensor,\n  except as required for reasonable and customary use in describing the\n  origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n  agreed to in writing, Licensor provides the Work (and each\n  Contributor provides its Contributions) on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n  implied, including, without limitation, any warranties or conditions\n  of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n  PARTICULAR PURPOSE. You are solely responsible for determining the\n  appropriateness of using or redistributing the Work and assume any\n  risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n  whether in tort (including negligence), contract, or otherwise,\n  unless required by applicable law (such as deliberate and grossly\n  negligent acts) or agreed to in writing, shall any Contributor be\n  liable to You for damages, including any direct, indirect, special,\n  incidental, or consequential damages of any character arising as a\n  result of this License or out of the use or inability to use the\n  Work (including but not limited to damages for loss of goodwill,\n  work stoppage, computer failure or malfunction, or any and all\n  other commercial damages or losses), even if such Contributor\n  has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n  the Work or Derivative Works thereof, You may choose to offer,\n  and charge a fee for, acceptance of support, warranty, indemnity,\n  or other liability obligations and/or rights consistent with this\n  License. However, in accepting such obligations, You may act only\n  on Your own behalf and on Your sole responsibility, not on behalf\n  of any other Contributor, and only if You agree to indemnify,\n  defend, and hold each Contributor harmless for any liability\n  incurred by, or claims asserted against, such Contributor by reason\n  of your accepting any such warranty or additional liability.\n\n\n-------------------------------------------------------------------------\n\n          Berkeley Software Distribution (BSD) License\n\nRedistribution and use in source and binary forms, with or without \nmodification, are permitted provided that the following conditions are\nmet:\n\nRedistributions of source code must retain the above copyright notice,\nthis list of conditions and the following disclaimer.\nRedistributions in binary form must reproduce the above copyright notice,\nthis list of conditions and the following disclaimer in the documentation\nand/or other materials provided with the distribution.\nNeither the name of the java.net nor the names of its contributors may be\nused to endorse or promote products derived from this software without \nspecific prior written permission.\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\nTO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\nPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n-------------------------------------------------------------------------\n\n              Creative Commons Attribution License 2.5\n             http://creativecommons.org/licenses/by/2.5/\n\nYou are free:\n- to Share - to copy, distribute and transmit the work\n- to Remix - to adapt the work\n\nUnder the following conditions:\n- Attribution. You must attribute the work in the manner specified by\n the author or licensor (but not in any way that suggests that they \n endorse you or your use of the work).\n\nFor any reuse or distribution, you must make clear to others the license\nterms of this work. The best way to do this is with a link to \nhttp://creativecommons.org/licenses/by/2.5/ .\nAny of the above conditions can be waived if you get permission from the\ncopyright holder. Nothing in this license impairs or restricts the\nauthor's moral rights.\n\n\n-------------------------------------------------------------------------\n\nICU License - ICU 1.8.1 and later\n\nCOPYRIGHT AND PERMISSION NOTICE\n\nCopyright (c) 1995-2006 International Business Machines Corporation \nand others\n\nAll rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining \na copy of this software and associated documentation files \n(the \"Software\"), to deal in the Software without restriction, including \nwithout limitation the rights to use, copy, modify, merge, publish, \ndistribute, and/or sell copies of the Software, and to permit persons\nto whom the Software is furnished to do so, provided that the above\ncopyright notice(s) and this permission notice appear in all copies of\nthe Software and that both the above copyright notice(s) and this \npermission notice appear in supporting documentation.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\nOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES \nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT \nOF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS\nINCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT\nOR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\nOF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE \nOR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE \nOR PERFORMANCE OF THIS SOFTWARE.\n\nExcept as contained in this notice, the name of a copyright holder shall\nnot be used in advertising or otherwise to promote the sale, use or other\ndealings in this Software without prior written authorization of \nthe copyright holder.\n\n"
  },
  {
    "path": "src/main/resources/logback.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<configuration debug=\"false\">\n\t<appender name=\"CONSOLE_APPENDER\" class=\"ch.qos.logback.core.ConsoleAppender\">\n\t\t<encoder>\n\t\t\t<pattern>%4relative %highlight(%5p) [%-17thread] - %-200msg - %d %logger{30} \\(%file:%line\\)%n%ex{full}</pattern>\n\t\t</encoder>\n\t</appender>\n\t<appender name=\"DEV_APPENDER\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n\t\t<prudent>true</prudent>\n\t\t<File>target/trolCommander.log</File>\n\t\t<encoder>\n\t\t\t<pattern>%4relative %5p [%-17thread] - %-200msg - %d %logger{30} \\(%file:%line\\)%n%ex{full}</pattern>\n\t\t</encoder>\n<!--\t\t\n\t\t<rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n\t\t\t<fileNamePattern>target/trolCommander.log.%d{yyyy-MM-dd}</fileNamePattern>\n\t\t</rollingPolicy>\n-->\t\t\n\t</appender>\n\n\n\t<root level=\"INFO\">\n\t\t<appender-ref ref=\"CONSOLE_APPENDER\" />\n\t\t<appender-ref ref=\"DEV_APPENDER\" />\n\t</root>\n\t<logger name=\"ch.qos.logback\" level=\"WARN\" />\n\t<logger name=\"com.mucommander\" level=\"INFO\" />\n</configuration>"
  },
  {
    "path": "src/main/resources/themes/ClassicCommander.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- =================================================== -->\n<!-- Author:           Nicolas Rinaudo                   -->\n<!-- Last modified on: 2007/10/06                        -->\n<!-- Description:                                        -->\n<!-- A simple, black on white theme meant to look like   -->\n<!-- a native application on most platforms.             -->\n<!-- =================================================== -->\n\n<theme>\n  <!-- = File table appearance ========================= -->\n  <!-- ================================================= -->\n  <file_table>\n    <font                            family=\"Lucida Grande,Tahoma,Lucida Sans\" size =\"12\"/>\n    <border                          color=\"868686\"/>\n    <inactive_border                 color=\"868686\"/>\n    <outline                         color=\"3875d7\"/>\n    <inactive_outline                color=\"c0c0c0\"/>\n\n    <normal>\n      <background                    color=\"ffffff\"/>\n      <inactive_background           color=\"f5f5f5\"/>\n    </normal>\n\n    <alternate>\n      <background                    color=\"ffffff\"/>\n      <inactive_background           color=\"f5f5f5\"/>\n    </alternate>\n\n    <selected>\n      <background                    color=\"3875d7\"/>\n      <secondary_background          color=\"3875d7\"/>\n      <inactive_background           color=\"c0c0c0\"/>\n      <inactive_secondary_background color=\"c0c0c0\"/>\n    </selected>\n\n    <unmatched>\n      <foreground                    color=\"404040\"/>\n      <background                    color=\"d1d1d1\"/>\n    </unmatched>\n\n\n    <!-- = Hidden folders ================================ -->\n    <!-- =============================================== -->\n    <hidden_folder>\n      <normal>\n        <inactive_foreground color=\"666666\"/>\n        <foreground          color=\"666666\"/>\n      </normal>\n      <selected>\n        <inactive_foreground color=\"666666\"/>\n        <foreground          color=\"ffffff\"/>\n      </selected>\n    </hidden_folder>\n\n\n    <!-- = Hidden files ================================ -->\n    <!-- =============================================== -->\n    <hidden_file>\n      <normal>\n        <inactive_foreground color=\"666666\"/>\n        <foreground          color=\"666666\"/>\n      </normal>\n      <selected>\n        <inactive_foreground color=\"666666\"/>\n        <foreground          color=\"ffffff\"/>\n      </selected>\n    </hidden_file>\n\n\n\n    <!-- = Folders ===================================== -->\n    <!-- =============================================== -->\n    <folder>\n      <normal>\n        <inactive_foreground color=\"000000\"/>\n        <foreground          color=\"000000\"/>\n      </normal>\n      <selected>\n        <inactive_foreground color=\"000000\"/>\n        <foreground          color=\"ffffff\"/>\n      </selected>\n    </folder>\n\n\n\n    <!-- = Archive ===================================== -->\n    <!-- =============================================== -->\n    <archive>\n      <normal>\n        <inactive_foreground color=\"000000\"/>\n        <foreground          color=\"000000\"/>\n      </normal>\n      <selected>\n        <inactive_foreground color=\"000000\"/>\n        <foreground          color=\"ffffff\"/>\n      </selected>\n    </archive>\n\n\n\n    <!-- = Symlink ===================================== -->\n    <!-- =============================================== -->\n    <symlink>\n      <normal>\n        <inactive_foreground color=\"0000ee\"/>\n        <foreground          color=\"0000ee\"/>\n      </normal>\n      <selected>\n        <inactive_foreground color=\"0000ee\"/>\n        <foreground          color=\"ffffff\"/>\n      </selected>\n    </symlink>\n\n\n\n    <!-- = Marked ====================================== -->\n    <!-- =============================================== -->\n    <marked>\n      <normal>\n        <inactive_foreground color=\"cc0000\"/>\n        <foreground          color=\"cc0000\"/>\n      </normal>\n      <selected>\n        <inactive_foreground color=\"cc0000\"/>\n        <foreground          color=\"cc0000\"/>\n      </selected>\n    </marked>\n\n\n\n    <!-- = Plain file ================================== -->\n    <!-- =============================================== -->\n    <file>\n      <normal>\n        <inactive_foreground color=\"000000\"/>\n        <foreground          color=\"000000\"/>\n      </normal>\n      <selected>\n        <inactive_foreground color=\"000000\"/>\n        <foreground          color=\"ffffff\"/>\n      </selected>\n    </file>\n  </file_table>\n\n\n\n  <!-- = Shell appearance ============================== -->\n  <!-- ================================================= -->\n  <shell>\n    <font family=\"Monospaced,Lucida Sans Typewriter\" size =\"12\" bold=\"true\"/>\n\n    <!-- = Default appearance ========================== -->\n    <!-- =============================================== -->\n    <normal>\n      <foreground color=\"000000\"/>\n      <background color=\"ffffff\"/>\n    </normal>\n\n    <!-- = Selected appearance ========================= -->\n    <!-- =============================================== -->\n    <selected>\n      <foreground color=\"ffffff\"/>\n      <background color=\"3875d7\"/>\n    </selected>\n  </shell>\n\n\n\n  <!-- = Shell history appearance ====================== -->\n  <!-- ================================================= -->\n  <shell_history>\n    <font family=\"Lucida Grande,Tahoma,Lucida Sans\" size =\"12\"/>\n\n    <!-- = Default appearance ========================== -->\n    <!-- =============================================== -->\n    <normal>\n      <foreground color=\"000000\"/>\n      <background color=\"ffffff\"/>\n    </normal>\n\n    <!-- = Selected appearance ========================= -->\n    <!-- =============================================== -->\n    <selected>\n      <foreground color=\"ffffff\"/>\n      <background color=\"3875d7\"/>\n    </selected>\n  </shell_history>\n\n\n\n  <!-- = Editor appearance ============================= -->\n  <!-- ================================================= -->\n  <editor>\n    <font family=\"Lucida Grande,Tahoma,Lucida Sans\" size =\"12\"/>\n\n    <!-- = Default appearance ========================== -->\n    <!-- =============================================== -->\n    <normal>\n      <foreground color=\"000000\"/>\n      <background color=\"ffffff\"/>\n    </normal>\n\n    <!-- = Selected appearance ========================= -->\n    <!-- =============================================== -->\n    <selected>\n      <foreground color=\"ffffff\"/>\n      <background color=\"3875d7\"/>\n    </selected>\n  </editor>\n\n\n\n  <!-- = Location bar appearance ======================= -->\n  <!-- ================================================= -->\n  <location_bar>\n    <font family=\"Lucida Grande,Tahoma,Lucida Sans\" size =\"12\"/>\n    <progress color=\"3875d7\" alpha=\"40\"/>\n\n    <!-- = Default appearance ========================== -->\n    <!-- =============================================== -->\n    <normal>\n      <foreground color=\"000000\"/>\n      <background color=\"ffffff\"/>\n    </normal>\n\n    <!-- = Selected appearance ========================= -->\n    <!-- =============================================== -->\n    <selected>\n      <foreground color=\"ffffff\"/>\n      <background color=\"3875d7\"/>\n    </selected>\n  </location_bar>\n\n\n\n  <!-- = Status bar appearance ========================= -->\n  <!-- ================================================= -->\n  <status_bar>\n    <font      family=\"Lucida Grande,Tahoma,Lucida Sans\" size =\"12\"/>\n    <foreground color=\"000000\"/>\n    <border     color=\"868686\"/>\n    <background color=\"d6d6d6\"/>\n    <ok         color=\"5dc23c\"/>\n    <warning    color=\"f6dc6c\"/>\n    <critical   color=\"e6594e\"/>\n  </status_bar>\n</theme>\n"
  },
  {
    "path": "src/main/resources/themes/Native.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- =================================================== -->\n<!-- Author:           Nicolas Rinaudo                   -->\n<!-- Last modified on: 2007/06/12                        -->\n<!-- Description:                                        -->\n<!-- An empty theme that will mirror the current         -->\n<!-- look&feel.                                          -->\n<!-- =================================================== -->\n<theme>\n</theme>\n"
  },
  {
    "path": "src/main/resources/themes/RetroCommander.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<theme>\n    <!-- = File table appearance ========================= -->\n    <!-- ================================================= -->\n    <file_table>\n        <font                            family=\"Lucida Grande,Arial,Lucida Sans\" size=\"13\"/>\n        <border                          color=\"404040\"/>\n        <inactive_border                 color=\"404040\"/>\n        <outline                         color=\"00ffff\"/>\n        <inactive_outline                color=\"cccccc\"/>\n\n        <normal>\n            <background                    color=\"000084\"/>\n            <inactive_background           color=\"000084\"/>\n        </normal>\n\n        <selected>\n            <background                    color=\"00ffff\"/>\n            <secondary_background          color=\"00ffff\"/>\n            <inactive_background           color=\"cccccc\"/>\n            <inactive_secondary_background color=\"cccccc\"/>\n        </selected>\n\n        <alternate>\n            <background                    color=\"000084\"/>\n            <inactive_background           color=\"000084\"/>\n        </alternate>\n\n        <unmatched>\n            <foreground                   color=\"00a8a8\"/>\n            <background                   color=\"000084\"/>\n        </unmatched>\n\n\n        <!-- = Hidden folders ================================ -->\n        <!-- =============================================== -->\n        <hidden_folder>\n            <normal>\n                <inactive_foreground color=\"c0c0c0\"/>\n                <foreground          color=\"c0c0c0\"/>\n            </normal>\n            <selected>\n                <inactive_foreground color=\"000080\"/>\n                <foreground          color=\"000080\"/>\n            </selected>\n        </hidden_folder>\n\n\n        <!-- = Hidden files ================================ -->\n        <!-- =============================================== -->\n        <hidden_file>\n            <normal>\n                <inactive_foreground color=\"c0c0c0\"/>\n                <foreground          color=\"c0c0c0\"/>\n            </normal>\n            <selected>\n                <inactive_foreground color=\"000080\"/>\n                <foreground          color=\"000080\"/>\n            </selected>\n        </hidden_file>\n\n\n\n        <!-- = Folders ===================================== -->\n        <!-- =============================================== -->\n        <folder>\n            <normal>\n                <inactive_foreground color=\"ffffff\"/>\n                <foreground          color=\"ffffff\"/>\n            </normal>\n            <selected>\n                <inactive_foreground color=\"000080\"/>\n                <foreground          color=\"000080\"/>\n            </selected>\n        </folder>\n\n\n\n        <!-- = Archive ===================================== -->\n        <!-- =============================================== -->\n        <archive>\n            <normal>\n                <inactive_foreground color=\"40ff40\"/>\n                <foreground          color=\"40ff40\"/>\n            </normal>\n            <selected>\n                <inactive_foreground color=\"000080\"/>\n                <foreground          color=\"000080\"/>\n            </selected>\n        </archive>\n\n\n        <!-- = Symlink ===================================== -->\n        <!-- =============================================== -->\n        <symlink>\n            <normal>\n                <inactive_foreground color=\"cc00cc\"/>\n                <foreground          color=\"cc00cc\"/>\n            </normal>\n            <selected>\n                <inactive_foreground color=\"000080\"/>\n                <foreground          color=\"000080\"/>\n            </selected>\n        </symlink>\n\n\n\n        <!-- = Marked ====================================== -->\n        <!-- =============================================== -->\n        <marked>\n            <normal>\n                <inactive_foreground color=\"ffff00\"/>\n                <foreground          color=\"ffff00\"/>\n            </normal>\n            <selected>\n                <inactive_foreground color=\"ffc532\"/>\n                <foreground          color=\"ffc532\"/>\n            </selected>\n        </marked>\n\n\n\n        <!-- = Plain file ================================== -->\n        <!-- =============================================== -->\n        <file>\n            <normal>\n                <inactive_foreground color=\"00f0f0\"/>\n                <foreground          color=\"00f0f0\"/>\n            </normal>\n            <selected>\n                <inactive_foreground color=\"000080\"/>\n                <foreground          color=\"000080\"/>\n            </selected>\n        </file>\n    </file_table>\n\n\n\n    <!-- = Shell appearance ============================== -->\n    <!-- ================================================= -->\n    <shell>\n        <font family=\"Monospaced,Lucida Sans Typewriter\" size =\"12\" bold=\"true\"/>\n\n        <!-- = Default appearance ========================== -->\n        <!-- =============================================== -->\n        <normal>\n            <background color=\"ffffff\"/>\n            <foreground color=\"000000\"/>\n        </normal>\n\n        <!-- = Selected appearance ========================= -->\n        <!-- =============================================== -->\n        <selected>\n            <background color=\"3875d7\"/>\n            <foreground color=\"ffffff\"/>\n        </selected>\n    </shell>\n\n\n\n    <!-- = Shell history appearance ====================== -->\n    <!-- ================================================= -->\n    <shell_history>\n        <font size=\"13\" family=\"Lucida Grande,Arial,Lucida Sans\"/>\n\n        <!-- = Default appearance ========================== -->\n        <!-- =============================================== -->\n        <normal>\n            <background color=\"ffffff\"/>\n            <foreground color=\"000000\"/>\n        </normal>\n\n        <!-- = Selected appearance ========================= -->\n        <!-- =============================================== -->\n        <selected>\n            <background color=\"b4d5ff\"/>\n            <foreground color=\"000000\"/>\n        </selected>\n    </shell_history>\n\n\n\n    <!-- = Editor appearance ============================= -->\n    <!-- ================================================= -->\n    <editor>\n        <font size=\"13\" family=\"Lucida Grande,Arial,Lucida Sans\"/>\n\n        <!-- = Default appearance ========================== -->\n        <!-- =============================================== -->\n        <normal>\n            <background color=\"ffffff\"/>\n            <foreground color=\"000000\"/>\n        </normal>\n\n        <!-- = Selected appearance ========================= -->\n        <!-- =============================================== -->\n        <selected>\n            <background color=\"b4d5ff\"/>\n            <foreground color=\"000000\"/>\n        </selected>\n    </editor>\n\n\n\n    <!-- = Location bar appearance ======================= -->\n    <!-- ================================================= -->\n    <location_bar>\n        <font size=\"13\" family=\"Lucida Grande,Arial,Lucida Sans\"/>\n\n        <!-- = Default appearance ========================== -->\n        <!-- =============================================== -->\n        <normal>\n            <background color=\"ffffff\"/>\n            <foreground color=\"000000\"/>\n        </normal>\n\n        <!-- = Selected appearance ========================= -->\n        <!-- =============================================== -->\n        <selected>\n            <background color=\"b4d5ff\"/>\n            <foreground color=\"000000\"/>\n        </selected>\n    </location_bar>\n\n    <quick_list>\n        <header>\n            <foreground           color=\"ffffff\"/>\n            <background           color=\"000055\"/>\n            <secondary_background color=\"000055\"/>\n        </header>\n    </quick_list>\n</theme>\n"
  },
  {
    "path": "src/main/resources/themes/Striped.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- =================================================== -->\n<!-- Author:           Nicolas Rinaudo                   -->\n<!-- Description:                                        -->\n<!-- A theme based on ClassicCommander meant to show off -->\n<!-- the striped and gradient effects in the file table. -->\n<!-- =================================================== -->\n<theme>\n    <!-- = File table appearance ========================= -->\n    <!-- ================================================= -->\n    <file_table>\n        <font                            family=\"Lucida Grande,Tahoma,Lucida Sans\" size=\"12\"/>\n        <border                          color=\"868686\"/>\n        <inactive_border                 color=\"868686\"/>\n        <outline                         color=\"244c8a\"/>\n        <inactive_outline                color=\"a6a6a6\"/>\n\n        <normal>\n            <background                    color=\"ffffff\"/>\n            <inactive_background           color=\"f5f5f5\"/>\n        </normal>\n\n        <alternate>\n            <background                    color=\"f1f5fa\"/>\n            <inactive_background           color=\"eeeeee\"/>\n        </alternate>\n\n        <selected>\n            <background                    color=\"3875d7\"/>\n            <inactive_background           color=\"c0c0c0\"/>\n            <inactive_secondary_background color=\"d9d9d9\"/>\n            <secondary_background          color=\"3f85f2\"/>\n        </selected>\n\n        <unmatched>\n            <background                    color=\"b2b2b2\"/>\n            <foreground                    color=\"000000\"/>\n        </unmatched>\n\n\n        <!-- = Hidden folders ================================ -->\n        <!-- =============================================== -->\n        <hidden_folder>\n            <normal>\n                <foreground                  color=\"666666\"/>\n                <inactive_foreground         color=\"666666\"/>\n            </normal>\n            <selected>\n                <foreground                  color=\"ffffff\"/>\n                <inactive_foreground         color=\"666666\"/>\n            </selected>\n        </hidden_folder>\n\n\n        <!-- = Hidden files ================================ -->\n        <!-- =============================================== -->\n        <hidden_file>\n            <normal>\n                <foreground                  color=\"666666\"/>\n                <inactive_foreground         color=\"666666\"/>\n            </normal>\n            <selected>\n                <foreground                  color=\"ffffff\"/>\n                <inactive_foreground         color=\"666666\"/>\n            </selected>\n        </hidden_file>\n\n\n\n        <!-- = Folders ===================================== -->\n        <!-- =============================================== -->\n        <folder>\n            <normal>\n                <inactive_foreground         color=\"000000\"/>\n                <foreground                  color=\"000000\"/>\n            </normal>\n            <selected>\n                <inactive_foreground         color=\"000000\"/>\n                <foreground                  color=\"ffffff\"/>\n            </selected>\n        </folder>\n\n\n\n        <!-- = Archive ===================================== -->\n        <!-- =============================================== -->\n        <archive>\n            <normal>\n                <inactive_foreground         color=\"000000\"/>\n                <foreground                  color=\"000000\"/>\n            </normal>\n            <selected>\n                <inactive_foreground         color=\"000000\"/>\n                <foreground                  color=\"ffffff\"/>\n            </selected>\n        </archive>\n\n\n\n        <!-- = Symlink ===================================== -->\n        <!-- =============================================== -->\n        <symlink>\n            <normal>\n                <inactive_foreground         color=\"000000\"/>\n                <foreground                  color=\"000000\"/>\n            </normal>\n            <selected>\n                <inactive_foreground         color=\"000000\"/>\n                <foreground                  color=\"ffffff\"/>\n            </selected>\n        </symlink>\n\n\n\n        <!-- = Marked ====================================== -->\n        <!-- =============================================== -->\n        <marked>\n            <normal>\n                <inactive_foreground         color=\"cc0000\"/>\n                <foreground                  color=\"cc0000\"/>\n            </normal>\n            <selected>\n                <inactive_foreground         color=\"cc0000\"/>\n                <foreground                  color=\"cc0000\"/>\n            </selected>\n        </marked>\n\n\n\n        <!-- = Plain file ================================== -->\n        <!-- =============================================== -->\n        <file>\n            <normal>\n                <inactive_foreground         color=\"000000\"/>\n                <foreground                  color=\"000000\"/>\n            </normal>\n            <selected>\n                <inactive_foreground         color=\"000000\"/>\n                <foreground                  color=\"ffffff\"/>\n            </selected>\n        </file>\n    </file_table>\n\n\n\n    <!-- = Shell appearance ============================== -->\n    <!-- ================================================= -->\n    <shell>\n        <font family=\"Monospaced,Lucida Sans Typewriter\" size =\"12\" bold=\"true\"/>\n\n        <!-- = Default appearance ========================== -->\n        <!-- =============================================== -->\n        <normal>\n            <foreground color=\"000000\"/>\n            <background color=\"ffffff\"/>\n        </normal>\n\n        <!-- = Selected appearance ========================= -->\n        <!-- =============================================== -->\n        <selected>\n            <foreground color=\"ffffff\"/>\n            <background color=\"3875d7\"/>\n        </selected>\n    </shell>\n\n\n\n    <!-- = Shell history appearance ====================== -->\n    <!-- ================================================= -->\n    <shell_history>\n        <font family=\"Lucida Grande,Tahoma,Lucida Sans\" size =\"12\"/>\n\n        <!-- = Default appearance ========================== -->\n        <!-- =============================================== -->\n        <normal>\n            <foreground color=\"000000\"/>\n            <background color=\"ffffff\"/>\n        </normal>\n\n        <!-- = Selected appearance ========================= -->\n        <!-- =============================================== -->\n        <selected>\n            <foreground color=\"ffffff\"/>\n            <background color=\"3875d7\"/>\n        </selected>\n    </shell_history>\n\n\n\n    <!-- = Editor appearance ============================= -->\n    <!-- ================================================= -->\n    <editor>\n        <font family=\"Lucida Grande,Tahoma,Lucida Sans\" size =\"12\"/>\n\n        <!-- = Default appearance ========================== -->\n        <!-- =============================================== -->\n        <normal>\n            <foreground color=\"000000\"/>\n            <background color=\"ffffff\"/>\n        </normal>\n\n        <!-- = Selected appearance ========================= -->\n        <!-- =============================================== -->\n        <selected>\n            <foreground color=\"ffffff\"/>\n            <background color=\"3875d7\"/>\n        </selected>\n    </editor>\n\n\n\n    <!-- = Location bar appearance ======================= -->\n    <!-- ================================================= -->\n    <location_bar>\n        <font family=\"Lucida Grande,Tahoma,Lucida Sans\" size =\"12\"/>\n        <progress color=\"3875d7\" alpha=\"40\"/>\n\n        <!-- = Default appearance ========================== -->\n        <!-- =============================================== -->\n        <normal>\n            <foreground color=\"000000\"/>\n            <background color=\"ffffff\"/>\n        </normal>\n\n        <!-- = Selected appearance ========================= -->\n        <!-- =============================================== -->\n        <selected>\n            <foreground color=\"ffffff\"/>\n            <background color=\"3875d7\"/>\n        </selected>\n    </location_bar>\n\n\n\n    <!-- = Status bar appearance ========================= -->\n    <!-- ================================================= -->\n    <status_bar>\n        <font      family=\"Lucida Grande,Tahoma,Lucida Sans\" size =\"12\"/>\n        <foreground color=\"000000\"/>\n        <border     color=\"868686\"/>\n        <background color=\"d6d6d6\"/>\n        <ok         color=\"5dc23c\"/>\n        <warning    color=\"f6dc6c\"/>\n        <critical   color=\"e6594e\"/>\n    </status_bar>\n\n    <quick_list>\n        <header>\n            <font family=\"Lucida Grande,Tahoma,Lucida Sans\" size =\"13\"/>\n            <foreground           color=\"ffffff\"/>\n            <background           color=\"1b44f3\"/>\n            <secondary_background color=\"5170f6\"/>\n        </header>\n        <item>\n            <font family=\"Lucida Grande,Tahoma,Lucida Sans\" size =\"12\"/>\n            <normal>\n                <foreground color=\"000000\"/>\n                <background color=\"ffffff\"/>\n            </normal>\n            <selected>\n                <foreground color=\"ffffff\"/>\n                <background color=\"3875d7\"/>\n            </selected>\n        </item>\n    </quick_list>\n</theme>\n"
  },
  {
    "path": "src/main/resources/themes/Trol.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<theme>\n    <file_table>\n        <border color=\"868686\"/>\n        <inactive_border color=\"868686\"/>\n        <outline color=\"244c8a\"/>\n        <inactive_outline color=\"a6a6a6\"/>\n        <font family=\"Lucida Grande\" size=\"13\"/>\n        <normal>\n            <background color=\"000000\"/>\n            <inactive_background color=\"000000\"/>\n        </normal>\n        <selected>\n            <background color=\"3875d7\"/>\n            <inactive_background color=\"c0c0c0\"/>\n            <inactive_secondary_background color=\"d9d9d9\"/>\n            <secondary_background color=\"3f85f2\"/>\n        </selected>\n        <alternate>\n            <background color=\"151515\"/>\n            <inactive_background color=\"151515\"/>\n        </alternate>\n        <unmatched>\n            <background color=\"333333\"/>\n            <foreground color=\"ccccff\" alpha=\"5c\"/>\n        </unmatched>\n        <hidden_folder>\n            <normal>\n                <foreground color=\"666666\"/>\n                <inactive_foreground color=\"666666\"/>\n            </normal>\n            <selected>\n                <foreground color=\"ffffff\"/>\n                <inactive_foreground color=\"666666\"/>\n            </selected>\n        </hidden_folder>        \n        <hidden_file>\n            <normal>\n                <foreground color=\"666666\"/>\n                <inactive_foreground color=\"666666\"/>\n            </normal>\n            <selected>\n                <foreground color=\"ffffff\"/>\n                <inactive_foreground color=\"666666\"/>\n            </selected>\n        </hidden_file>\n        <folder>\n            <normal>\n                <inactive_foreground color=\"ffffff\"/>\n                <foreground color=\"ffffff\"/>\n            </normal>\n            <selected>\n                <inactive_foreground color=\"000000\"/>\n                <foreground color=\"ffffff\"/>\n            </selected>\n        </folder>\n        <archive>\n            <normal>\n                <inactive_foreground color=\"ff00cc\"/>\n                <foreground color=\"ff00cc\"/>\n            </normal>\n            <selected>\n                <inactive_foreground color=\"000000\"/>\n                <foreground color=\"ffffff\"/>\n            </selected>\n        </archive>\n        <symlink>\n            <normal>\n                <inactive_foreground color=\"ff00ff\"/>\n                <foreground color=\"ff00ff\"/>\n            </normal>\n            <selected>\n                <inactive_foreground color=\"000000\"/>\n                <foreground color=\"ffffff\"/>\n            </selected>\n        </symlink>\n        <marked>\n            <normal>\n                <inactive_foreground color=\"ffff00\"/>\n                <foreground color=\"ffff00\"/>\n            </normal>\n            <selected>\n                <inactive_foreground color=\"cc0000\"/>\n                <foreground color=\"cc0000\"/>\n            </selected>\n        </marked>\n        <executable>\n            <normal>\n            </normal>\n            <selected>\n            </selected>\n        </executable>\n        <file>\n            <normal>\n                <inactive_foreground color=\"66ffff\"/>\n                <foreground color=\"66ffff\"/>\n            </normal>\n            <selected>\n                <inactive_foreground color=\"000000\"/>\n                <foreground color=\"ffffff\"/>\n            </selected>\n        </file>\n    </file_table>\n    <file_groups>\n        <group1>\n            <normal color=\"9966ff\"/>\n        </group1>\n        <group2>\n            <normal color=\"ff6667\"/>\n        </group2>\n        <group3>\n            <normal color=\"66ccff\"/>\n        </group3>\n        <group4>\n            <normal color=\"ff9999\"/>\n        </group4>\n        <group5>\n            <normal color=\"ccffcc\"/>\n        </group5>\n        <group6>\n            <normal color=\"ccccff\"/>\n        </group6>\n        <group7>\n            <normal color=\"ccccff\"/>\n        </group7>\n        <group8>\n            <normal color=\"66ff00\"/>\n        </group8>\n        <group9>\n            <normal color=\"00ff99\"/>\n        </group9>\n        <group10>\n            <normal color=\"66ffcc\"/>\n        </group10>\n    </file_groups>\n    <shell>\n        <normal>\n            <background color=\"000000\"/>\n            <foreground color=\"99ff33\"/>\n        </normal>\n        <selected>\n        </selected>\n    </shell>\n    <shell_history>\n        <font family=\"Lucida Grande\" size=\"12\"/>\n        <normal>\n            <background color=\"ffffff\"/>\n            <foreground color=\"000000\"/>\n        </normal>\n        <selected>\n            <background color=\"3875d7\"/>\n            <foreground color=\"ffffff\"/>\n        </selected>\n    </shell_history>\n    <terminal>\n        <font family=\"Menlo\" size=\"14\"/>\n        <normal>\n            <background color=\"000000\"/>\n        </normal>\n        <selected>\n        </selected>\n    </terminal>\n    <editor>\n        <font family=\"Menlo\" size=\"13\"/>\n        <normal>\n            <background color=\"000000\"/>\n            <foreground color=\"99ff99\"/>\n        </normal>\n        <selected>\n            <background color=\"3875d7\"/>\n            <foreground color=\"ffffff\"/>\n        </selected>\n        <current>\n            <background color=\"101010\"/>\n        </current>\n    </editor>\n    <hex_viewer>\n        <normal>\n            <background color=\"000000\"/>\n            <foreground color=\"33ff33\"/>\n            <secondary_background color=\"002424\"/>\n            <ascii_foreground color=\"ff33ff\"/>\n            <offset_foreground color=\"33ffff\"/>\n        </normal>\n        <selected>\n            <ascii_background color=\"0000ff\"/>\n        </selected>\n    </hex_viewer>\n    <location_bar>\n        <font family=\"Lucida Grande\" size=\"12\"/>\n        <progress color=\"3875d7\" alpha=\"40\"/>\n        <normal>\n            <background color=\"ffffff\"/>\n            <foreground color=\"000000\"/>\n        </normal>\n        <selected>\n            <background color=\"3875d7\"/>\n            <foreground color=\"ffffff\"/>\n        </selected>\n    </location_bar>\n    <status_bar>\n        <font family=\"Lucida Grande\" size=\"12\"/>\n        <background color=\"d6d6d6\"/>\n        <foreground color=\"000000\"/>\n        <border color=\"868686\"/>\n        <ok color=\"5dc23c\"/>\n        <warning color=\"f6dc6c\"/>\n        <critical color=\"e6594e\"/>\n    </status_bar>\n    <quick_list>\n        <header>\n            <font family=\"Lucida Grande\" size=\"13\"/>\n            <foreground color=\"ffffff\"/>\n            <background color=\"1b44f3\"/>\n            <secondary_background color=\"5170f6\"/>\n        </header>\n        <item>\n            <font family=\"Lucida Grande\" size=\"12\"/>\n            <normal>\n                <foreground color=\"000000\"/>\n                <background color=\"ffffff\"/>\n            </normal>\n            <selected>\n                <foreground color=\"ffffff\"/>\n                <background color=\"3875d7\"/>\n            </selected>\n        </item>\n    </quick_list>\n</theme>\n"
  },
  {
    "path": "src/main/resources/themes/editor/Dark.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE RSyntaxTheme SYSTEM \"theme.dtd\">\n\n<!--\n\tDark theme based off of Notepad++'s Obsidian theme.\n\tSee theme.dtd and org.fife.ui.rsyntaxtextarea.Theme for more information.\n-->\n<RSyntaxTheme version=\"1.0\">\n\n   <!-- Omitting baseFont will use a system-appropriate monospaced. -->\n   <!--<baseFont family=\"...\" size=\"13\"/>-->\n   \n   <!--  General editor colors. -->\n   <background color=\"293134\"/>\n   <caret color=\"c1cbc2\"/>\n   <selection useFG=\"false\" bg=\"404E51\" roundedEdges=\"false\"/>\n   <currentLineHighlight color=\"2F393C\" fade=\"false\"/>\n   <marginLine fg=\"394448\"/>\n   <markAllHighlight color=\"6b8189\"/> <!-- TODO: Fix me -->\n   <markOccurrencesHighlight color=\"5b7179\" border=\"false\"/>\n   <matchedBracket fg=\"6A8088\" bg=\"6b8189\" highlightBoth=\"false\" animate=\"true\"/>\n   <hyperlinks fg=\"a082bd\"/>\n   <secondaryLanguages>\n      <language index=\"1\" bg=\"334455\"/>\n      <language index=\"2\" bg=\"223322\"/>\n      <language index=\"3\" bg=\"ffe0f0\"/>\n   </secondaryLanguages>\n   \n   <!-- Gutter styling. -->\n   <gutterBorder color=\"81969A\"/>\n   <lineNumbers fg=\"81969A\"/>\n   <foldIndicator fg=\"6A8088\" iconBg=\"2f383c\"/>\n   <iconRowHeader activeLineRange=\"3399ff\"/>\n   \n   <!-- Syntax tokens. -->\n   <tokenStyles>\n      <style token=\"IDENTIFIER\" fg=\"E0E2E4\"/>\n      <style token=\"RESERVED_WORD\" fg=\"93C763\" bold=\"true\"/>\n      <style token=\"RESERVED_WORD_2\" fg=\"93C763\" bold=\"true\"/>\n      <style token=\"ANNOTATION\" fg=\"E8E2B7\"/>\n      <style token=\"COMMENT_DOCUMENTATION\" fg=\"6C788C\"/>\n      <style token=\"COMMENT_EOL\" fg=\"66747B\"/>\n      <style token=\"COMMENT_MULTILINE\" fg=\"66747B\"/>\n      <style token=\"COMMENT_KEYWORD\" fg=\"ae9fbf\"/>\n      <style token=\"COMMENT_MARKUP\" fg=\"ae9fbf\"/>\n      <style token=\"FUNCTION\" fg=\"E0E2E4\"/>\n      <style token=\"DATA_TYPE\" fg=\"678CB1\" bold=\"true\"/>\n      <style token=\"LITERAL_BOOLEAN\" fg=\"93C763\" bold=\"true\"/>\n      <style token=\"LITERAL_NUMBER_DECIMAL_INT\" fg=\"FFCD22\"/>\n      <style token=\"LITERAL_NUMBER_FLOAT\" fg=\"FFCD22\"/>\n      <style token=\"LITERAL_NUMBER_HEXADECIMAL\" fg=\"FFCD22\"/>\n      <style token=\"LITERAL_STRING_DOUBLE_QUOTE\" fg=\"EC7600\"/>\n      <style token=\"LITERAL_CHAR\" fg=\"EC7600\"/>\n      <style token=\"LITERAL_BACKQUOTE\" fg=\"EC7600\"/>\n      <style token=\"MARKUP_TAG_DELIMITER\" fg=\"678CB1\"/>\n      <style token=\"MARKUP_TAG_NAME\" fg=\"ABBFD3\" bold=\"true\"/>\n      <style token=\"MARKUP_TAG_ATTRIBUTE\" fg=\"B3B689\"/>\n      <style token=\"MARKUP_TAG_ATTRIBUTE_VALUE\" fg=\"e1e2cf\"/>\n      <style token=\"MARKUP_COMMENT\" fg=\"66747B\"/>\n      <style token=\"MARKUP_DTD\" fg=\"A082BD\"/>\n      <style token=\"MARKUP_PROCESSING_INSTRUCTION\" fg=\"A082BD\"/>\n      <style token=\"MARKUP_CDATA\" fg=\"d5e6f0\"/>\n      <style token=\"MARKUP_CDATA_DELIMITER\" fg=\"ae9fbf\"/>\n      <style token=\"MARKUP_ENTITY_REFERENCE\" fg=\"678CB1\"/>\n      <style token=\"OPERATOR\" fg=\"E8E2B7\"/>\n      <style token=\"PREPROCESSOR\" fg=\"A082BD\"/>\n      <style token=\"REGEX\" fg=\"d39745\"/>\n      <style token=\"SEPARATOR\" fg=\"E8E2B7\"/>\n      <style token=\"VARIABLE\" fg=\"ae9fbf\" bold=\"true\"/>\n      <style token=\"WHITESPACE\" fg=\"E0E2E4\"/>\n      \n      <style token=\"ERROR_IDENTIFIER\" fg=\"E0E2E4\" bg=\"04790e\"/>\n      <style token=\"ERROR_NUMBER_FORMAT\" fg=\"E0E2E4\" bg=\"04790e\"/>\n      <style token=\"ERROR_STRING_DOUBLE\" fg=\"E0E2E4\" bg=\"04790e\"/>\n      <style token=\"ERROR_CHAR\" fg=\"E0E2E4\" bg=\"04790e\"/>\n   </tokenStyles>\n\n</RSyntaxTheme>\n"
  },
  {
    "path": "src/main/resources/themes/editor/Default-alt.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE RSyntaxTheme SYSTEM \"theme.dtd\">\n\n<!--\n\tRSyntaxTextArea's default theme, using the \"standard\" selection style.\n\tSee theme.dtd and org.fife.ui.rsyntaxtextarea.Theme for more information.\n-->\n<RSyntaxTheme version=\"1.0\">\n\n   <!-- Omitting baseFont will use a system-appropriate monospaced. -->\n   <!--<baseFont family=\"...\" size=\"13\"/>-->\n   \n   <!--  General editor colors. -->\n   <background color=\"ffffff\"/>\n   <caret color=\"ff0000\"/>\n   <selection fg=\"default\" bg=\"default\"/>\n   <currentLineHighlight color=\"ffffaa\" fade=\"false\"/>\n   <marginLine fg=\"b0b4b9\"/>\n   <markAllHighlight color=\"ffc800\"/>\n   <markOccurrencesHighlight color=\"d4d4d4\" border=\"false\"/>\n   <matchedBracket fg=\"000080\" bg=\"eaeaff\" highlightBoth=\"false\" animate=\"true\"/>\n   <hyperlinks fg=\"0000ff\"/>\n   <secondaryLanguages>\n      <language index=\"1\" bg=\"fff0cc\"/>\n      <language index=\"2\" bg=\"dafeda\"/>\n      <language index=\"3\" bg=\"ffe0f0\"/>\n   </secondaryLanguages>\n   \n   <!-- Gutter styling. -->\n   <gutterBorder color=\"dddddd\"/>\n   <lineNumbers fg=\"787878\"/>\n   <foldIndicator fg=\"808080\" iconBg=\"ffffff\"/>\n   <iconRowHeader activeLineRange=\"3399ff\"/>\n   \n   <!-- Syntax tokens. -->\n   <tokenStyles>\n      <style token=\"IDENTIFIER\" fg=\"000000\"/>\n      <style token=\"RESERVED_WORD\" fg=\"0000ff\" bold=\"true\"/>\n      <style token=\"RESERVED_WORD_2\" fg=\"0000ff\" bold=\"true\"/>\n      <style token=\"ANNOTATION\" fg=\"808080\"/>\n      <style token=\"COMMENT_DOCUMENTATION\" fg=\"a40000\" italic=\"true\"/>\n      <style token=\"COMMENT_EOL\" fg=\"008000\" italic=\"true\"/>\n      <style token=\"COMMENT_MULTILINE\" fg=\"008000\" italic=\"true\"/>\n      <style token=\"COMMENT_KEYWORD\" fg=\"ff9900\" bold=\"true\"/>\n      <style token=\"COMMENT_MARKUP\" fg=\"808080\"/>\n      <style token=\"DATA_TYPE\" fg=\"008080\" bold=\"true\"/>\n      <style token=\"FUNCTION\" fg=\"ad8000\"/>\n      <style token=\"LITERAL_BOOLEAN\" fg=\"0000ff\" bold=\"true\"/>\n      <style token=\"LITERAL_NUMBER_DECIMAL_INT\" fg=\"6400C8\"/>\n      <style token=\"LITERAL_NUMBER_FLOAT\" fg=\"6400C8\"/>\n      <style token=\"LITERAL_NUMBER_HEXADECIMAL\" fg=\"6400C8\"/>\n      <style token=\"LITERAL_STRING_DOUBLE_QUOTE\" fg=\"DC009C\"/>\n      <style token=\"LITERAL_CHAR\" fg=\"DC009C\"/>\n      <style token=\"LITERAL_BACKQUOTE\" fg=\"DC009C\"/>\n      <style token=\"MARKUP_TAG_DELIMITER\" fg=\"ff0000\"/>\n      <style token=\"MARKUP_TAG_NAME\" fg=\"0000ff\"/>\n      <style token=\"MARKUP_TAG_ATTRIBUTE\" fg=\"3f7f7f\"/>\n      <style token=\"MARKUP_TAG_ATTRIBUTE_VALUE\" fg=\"DC009C\"/>\n      <style token=\"MARKUP_COMMENT\" fg=\"006000\" italic=\"true\"/>\n      <style token=\"MARKUP_DTD\" fg=\"ad8000\"/>\n      <style token=\"MARKUP_PROCESSING_INSTRUCTION\" fg=\"808080\"/>\n      <style token=\"MARKUP_CDATA\" fg=\"cc6600\"/>\n      <style token=\"MARKUP_CDATA_DELIMITER\" fg=\"008080\"/>\n      <style token=\"MARKUP_ENTITY_REFERENCE\" fg=\"008000\"/>\n      <style token=\"OPERATOR\" fg=\"804040\"/>\n      <style token=\"PREPROCESSOR\" fg=\"808080\"/>\n      <style token=\"REGEX\" fg=\"008040\"/>\n      <style token=\"SEPARATOR\" fg=\"ff0000\"/>\n      <style token=\"VARIABLE\" fg=\"ee8800\" bold=\"true\"/>\n      <style token=\"WHITESPACE\" fg=\"000000\"/>\n      \n      <style token=\"ERROR_IDENTIFIER\" fg=\"000000\" bg=\"ffcccc\"/>\n      <style token=\"ERROR_NUMBER_FORMAT\" fg=\"000000\" bg=\"ffcccc\"/>\n      <style token=\"ERROR_STRING_DOUBLE\" fg=\"000000\" bg=\"ffcccc\"/>\n      <style token=\"ERROR_CHAR\" fg=\"000000\" bg=\"ffcccc\"/>\n   </tokenStyles>\n\n</RSyntaxTheme>\n"
  },
  {
    "path": "src/main/resources/themes/editor/Default.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE RSyntaxTheme SYSTEM \"theme.dtd\">\n\n<!--\n\tRSyntaxTextArea's default theme.\n\tSee theme.dtd and org.fife.ui.rsyntaxtextarea.Theme for more information.\n-->\n<RSyntaxTheme version=\"1.0\">\n\n   <!-- Omitting baseFont will use a system-appropriate monospaced. -->\n   <!--<baseFont family=\"...\" size=\"13\"/>-->\n   \n   <!--  General editor colors. -->\n   <background color=\"ffffff\"/>\n   <caret color=\"ff0000\"/>\n   <selection fg=\"default\" useFG=\"false\" bg=\"c8c8ff\"/>\n   <currentLineHighlight color=\"ffffaa\" fade=\"false\"/>\n   <marginLine fg=\"b0b4b9\"/>\n   <markAllHighlight color=\"ffc800\"/>\n   <markOccurrencesHighlight color=\"d4d4d4\" border=\"false\"/>\n   <matchedBracket fg=\"000080\" bg=\"eaeaff\" highlightBoth=\"false\" animate=\"true\"/>\n   <hyperlinks fg=\"0000ff\"/>\n   <secondaryLanguages>\n      <language index=\"1\" bg=\"fff0cc\"/>\n      <language index=\"2\" bg=\"dafeda\"/>\n      <language index=\"3\" bg=\"ffe0f0\"/>\n   </secondaryLanguages>\n   \n   <!-- Gutter styling. -->\n   <gutterBorder color=\"dddddd\"/>\n   <lineNumbers fg=\"787878\"/>\n   <foldIndicator fg=\"808080\" iconBg=\"ffffff\"/>\n   <iconRowHeader activeLineRange=\"3399ff\"/>\n   \n   <!-- Syntax tokens. -->\n   <tokenStyles>\n      <style token=\"IDENTIFIER\" fg=\"000000\"/>\n      <style token=\"RESERVED_WORD\" fg=\"0000ff\" bold=\"true\"/>\n      <style token=\"RESERVED_WORD_2\" fg=\"0000ff\" bold=\"true\"/>\n      <style token=\"ANNOTATION\" fg=\"808080\"/>\n      <style token=\"COMMENT_DOCUMENTATION\" fg=\"a40000\" italic=\"true\"/>\n      <style token=\"COMMENT_EOL\" fg=\"008000\" italic=\"true\"/>\n      <style token=\"COMMENT_MULTILINE\" fg=\"008000\" italic=\"true\"/>\n      <style token=\"COMMENT_KEYWORD\" fg=\"ff9900\" bold=\"true\"/>\n      <style token=\"COMMENT_MARKUP\" fg=\"808080\"/>\n      <style token=\"DATA_TYPE\" fg=\"008080\" bold=\"true\"/>\n      <style token=\"FUNCTION\" fg=\"ad8000\"/>\n      <style token=\"LITERAL_BOOLEAN\" fg=\"0000ff\" bold=\"true\"/>\n      <style token=\"LITERAL_NUMBER_DECIMAL_INT\" fg=\"6400C8\"/>\n      <style token=\"LITERAL_NUMBER_FLOAT\" fg=\"6400C8\"/>\n      <style token=\"LITERAL_NUMBER_HEXADECIMAL\" fg=\"6400C8\"/>\n      <style token=\"LITERAL_STRING_DOUBLE_QUOTE\" fg=\"DC009C\"/>\n      <style token=\"LITERAL_CHAR\" fg=\"DC009C\"/>\n      <style token=\"LITERAL_BACKQUOTE\" fg=\"DC009C\"/>\n      <style token=\"MARKUP_TAG_DELIMITER\" fg=\"ff0000\"/>\n      <style token=\"MARKUP_TAG_NAME\" fg=\"0000ff\"/>\n      <style token=\"MARKUP_TAG_ATTRIBUTE\" fg=\"3f7f7f\"/>\n      <style token=\"MARKUP_TAG_ATTRIBUTE_VALUE\" fg=\"DC009C\"/>\n      <style token=\"MARKUP_COMMENT\" fg=\"006000\" italic=\"true\"/>\n      <style token=\"MARKUP_DTD\" fg=\"ad8000\"/>\n      <style token=\"MARKUP_PROCESSING_INSTRUCTION\" fg=\"808080\"/>\n      <style token=\"MARKUP_CDATA\" fg=\"cc6600\"/>\n      <style token=\"MARKUP_CDATA_DELIMITER\" fg=\"008080\"/>\n      <style token=\"MARKUP_ENTITY_REFERENCE\" fg=\"008000\"/>\n      <style token=\"OPERATOR\" fg=\"804040\"/>\n      <style token=\"PREPROCESSOR\" fg=\"808080\"/>\n      <style token=\"REGEX\" fg=\"008040\"/>\n      <style token=\"SEPARATOR\" fg=\"ff0000\"/>\n      <style token=\"VARIABLE\" fg=\"ee8800\" bold=\"true\"/>\n      <style token=\"WHITESPACE\" fg=\"000000\"/>\n      \n      <style token=\"ERROR_IDENTIFIER\" fg=\"000000\" bg=\"ffcccc\"/>\n      <style token=\"ERROR_NUMBER_FORMAT\" fg=\"000000\" bg=\"ffcccc\"/>\n      <style token=\"ERROR_STRING_DOUBLE\" fg=\"000000\" bg=\"ffcccc\"/>\n      <style token=\"ERROR_CHAR\" fg=\"000000\" bg=\"ffcccc\"/>\n   </tokenStyles>\n\n</RSyntaxTheme>\n"
  },
  {
    "path": "src/main/resources/themes/editor/Eclipse.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE RSyntaxTheme SYSTEM \"theme.dtd\">\n\n<!--\n\tTheme that mimics Eclipse's defaults.\n\tSee theme.dtd and org.fife.ui.rsyntaxtextarea.Theme for more information.\n-->\n<RSyntaxTheme version=\"1.0\">\n\n   <!-- Omitting baseFont will use a system-appropriate monospaced. -->\n   <!--<baseFont family=\"...\" size=\"13\"/>-->\n   \n   <!--  General editor colors. -->\n   <background color=\"ffffff\"/>\n   <caret color=\"000000\"/>\n   <selection bg=\"default\" fg=\"default\"/>\n   <currentLineHighlight color=\"e8f2fe\" fade=\"false\"/>\n   <marginLine fg=\"b0b4b9\"/>\n   <markAllHighlight color=\"6b8189\"/> <!-- TODO: Fix me -->\n   <markOccurrencesHighlight color=\"d4d4d4\" border=\"false\"/>\n   <matchedBracket fg=\"c0c0c0\" highlightBoth=\"false\" animate=\"false\"/>\n   <hyperlinks fg=\"0000ff\"/>\n   <secondaryLanguages>\n      <language index=\"1\" bg=\"fff0cc\"/>\n      <language index=\"2\" bg=\"dafeda\"/>\n      <language index=\"3\" bg=\"ffe0f0\"/>\n   </secondaryLanguages>\n   \n   <!-- Gutter styling. -->\n   <gutterBorder color=\"dddddd\"/>\n   <lineNumbers fg=\"787878\"/>\n   <foldIndicator fg=\"808080\" iconBg=\"ffffff\"/>\n   <iconRowHeader activeLineRange=\"3399ff\"/>\n   \n   <!-- Syntax tokens. -->\n   <tokenStyles>\n      <style token=\"IDENTIFIER\" fg=\"000000\"/>\n      <style token=\"RESERVED_WORD\" fg=\"7f0055\" bold=\"true\"/>\n      <style token=\"RESERVED_WORD_2\" fg=\"7f0055\" bold=\"true\"/>\n      <style token=\"ANNOTATION\" fg=\"808080\"/>\n      <style token=\"COMMENT_DOCUMENTATION\" fg=\"3f5fbf\"/>\n      <style token=\"COMMENT_EOL\" fg=\"3f7f5f\"/>\n      <style token=\"COMMENT_MULTILINE\" fg=\"3f7f5f\"/>\n      <style token=\"COMMENT_KEYWORD\" fg=\"7F9FBF\" bold=\"true\"/>\n      <style token=\"COMMENT_MARKUP\" fg=\"7f7f9f\"/>\n      <style token=\"DATA_TYPE\" fg=\"7f0055\" bold=\"true\"/>\n      <style token=\"FUNCTION\" fg=\"000000\"/>\n      <style token=\"LITERAL_BOOLEAN\" fg=\"7f0055\" bold=\"true\"/>\n      <style token=\"LITERAL_NUMBER_DECIMAL_INT\" fg=\"000000\"/>\n      <style token=\"LITERAL_NUMBER_FLOAT\" fg=\"000000\"/>\n      <style token=\"LITERAL_NUMBER_HEXADECIMAL\" fg=\"000000\"/>\n      <style token=\"LITERAL_STRING_DOUBLE_QUOTE\" fg=\"2900ff\"/>\n      <style token=\"LITERAL_CHAR\" fg=\"2900ff\"/>\n      <style token=\"LITERAL_BACKQUOTE\" fg=\"2900ff\"/>\n      <style token=\"MARKUP_TAG_DELIMITER\" fg=\"008080\"/>\n      <style token=\"MARKUP_TAG_NAME\" fg=\"3f7f7f\"/>\n      <style token=\"MARKUP_TAG_ATTRIBUTE\" fg=\"7f007f\"/>\n      <style token=\"MARKUP_TAG_ATTRIBUTE_VALUE\" fg=\"2a00ff\" italic=\"true\"/>\n      <style token=\"MARKUP_COMMENT\" fg=\"3f5fbf\"/>\n      <style token=\"MARKUP_DTD\" fg=\"008080\"/>\n      <style token=\"MARKUP_PROCESSING_INSTRUCTION\" fg=\"808080\"/>\n      <style token=\"MARKUP_CDATA\" fg=\"000000\"/>\n      <style token=\"MARKUP_CDATA_DELIMITER\" fg=\"008080\"/>\n      <style token=\"MARKUP_ENTITY_REFERENCE\" fg=\"2a00ff\"/>\n      <style token=\"OPERATOR\" fg=\"000000\"/>\n      <style token=\"PREPROCESSOR\" fg=\"808080\"/>\n      <style token=\"REGEX\" fg=\"008040\"/>\n      <style token=\"SEPARATOR\" fg=\"000000\"/>\n      <style token=\"VARIABLE\" fg=\"ff9900\" bold=\"true\"/>\n      <style token=\"WHITESPACE\" fg=\"000000\"/>\n      \n      <style token=\"ERROR_IDENTIFIER\" fg=\"000000\" bg=\"ffcccc\"/>\n      <style token=\"ERROR_NUMBER_FORMAT\" fg=\"000000\" bg=\"ffcccc\"/>\n      <style token=\"ERROR_STRING_DOUBLE\" fg=\"000000\" bg=\"ffcccc\"/>\n      <style token=\"ERROR_CHAR\" fg=\"000000\" bg=\"ffcccc\"/>\n   </tokenStyles>\n\n</RSyntaxTheme>\n"
  },
  {
    "path": "src/main/resources/themes/editor/Idea.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\r\n<!DOCTYPE RSyntaxTheme SYSTEM \"theme.dtd\">\r\n\r\n<!--\r\n\tTheme that mimics IntelliJ IDEA's defaults.\r\n\tSee theme.dtd and org.fife.ui.rsyntaxtextarea.Theme for more information.\r\n-->\r\n<RSyntaxTheme version=\"1.0\">\r\n\r\n   <!-- Omitting baseFont will use a system-appropriate monospaced. -->\r\n   <!--<baseFont family=\"...\" size=\"13\"/>-->\r\n   \r\n   <!--  General editor colors. -->\r\n   <background color=\"ffffff\" />\r\n   <caret color=\"000000\" />\r\n   <selection fg=\"ffffff\" bg=\"526da5\" />\r\n   <currentLineHighlight color=\"ffffd7\" fade=\"false\" />\r\n   <marginLine fg=\"b0b4b9\" />\r\n   <markAllHighlight color=\"ccccff\" />\r\n   <markOccurrencesHighlight color=\"ccccff\" border=\"false\" />\r\n   <matchedBracket fg=\"99ccff\" bg=\"99ccff\" highlightBoth=\"true\" animate=\"false\" />\r\n   <hyperlinks fg=\"0000ff\" />\r\n   <secondaryLanguages>\r\n      <language index=\"1\" bg=\"fff0cc\" />\r\n      <language index=\"2\" bg=\"dafeda\" />\r\n      <language index=\"3\" bg=\"ffe0f0\" />\r\n   </secondaryLanguages>\r\n\r\n   <!-- Gutter styling. -->\r\n   <gutterBorder color=\"dddddd\" />\r\n   <lineNumbers fg=\"787878\" />\r\n   <foldIndicator fg=\"808080\" iconBg=\"ffffff\" />\r\n   <iconRowHeader activeLineRange=\"3399ff\" />\r\n\r\n   <!-- Syntax tokens. -->\r\n   <tokenStyles>\r\n      <style token=\"IDENTIFIER\" fg=\"000000\" />\r\n      <style token=\"RESERVED_WORD\" fg=\"000080\" bold=\"true\" />\r\n      <style token=\"RESERVED_WORD_2\" fg=\"000080\" bold=\"true\" />\r\n      <style token=\"ANNOTATION\" fg=\"808000\" />\r\n      <style token=\"COMMENT_DOCUMENTATION\" fg=\"808080\" italic=\"true\" />\r\n      <style token=\"COMMENT_EOL\" fg=\"808080\" italic=\"true\" />\r\n      <style token=\"COMMENT_MULTILINE\" fg=\"808080\" italic=\"true\" />\r\n      <style token=\"COMMENT_KEYWORD\" fg=\"808080\" bold=\"true\" underline=\"true\" italic=\"true\" />\r\n      <style token=\"COMMENT_MARKUP\" fg=\"808080\" bg=\"e2ffe2\" italic=\"true\" />\r\n      <style token=\"DATA_TYPE\" fg=\"000080\" bold=\"true\" />\r\n      <style token=\"FUNCTION\" fg=\"000000\" />\r\n      <style token=\"LITERAL_BOOLEAN\" fg=\"000080\" bold=\"true\" />\r\n      <style token=\"LITERAL_NUMBER_DECIMAL_INT\" fg=\"0000ff\" />\r\n      <style token=\"LITERAL_NUMBER_FLOAT\" fg=\"0000ff\" />\r\n      <style token=\"LITERAL_NUMBER_HEXADECIMAL\" fg=\"0000ff\" />\r\n      <style token=\"LITERAL_STRING_DOUBLE_QUOTE\" fg=\"008000\" bold=\"true\" />\r\n      <style token=\"LITERAL_CHAR\" fg=\"008000\" bold=\"true\" />\r\n      <style token=\"LITERAL_BACKQUOTE\" fg=\"008000\" bold=\"true\" />\r\n      <style token=\"MARKUP_TAG_DELIMITER\" fg=\"000000\" bold=\"true\" />\r\n      <style token=\"MARKUP_TAG_NAME\" fg=\"000080\" bold=\"true\" />\r\n      <style token=\"MARKUP_TAG_ATTRIBUTE\" fg=\"0000ff\" bold=\"true\" />\r\n      <style token=\"MARKUP_TAG_ATTRIBUTE_VALUE\" fg=\"008000\" bold=\"true\" />\r\n      <style token=\"MARKUP_COMMENT\" fg=\"808080\" italic=\"true\"/>\r\n      <style token=\"MARKUP_DTD\" fg=\"808080\"/>\r\n      <style token=\"MARKUP_PROCESSING_INSTRUCTION\"  fg=\"808080\"/>\r\n      <style token=\"MARKUP_CDATA\" fg=\"cc6600\"/>\r\n      <style token=\"MARKUP_CDATA_DELIMITER\" fg=\"008080\"/>\r\n      <style token=\"MARKUP_ENTITY_REFERENCE\" fg=\"008000\"/>\r\n      <style token=\"OPERATOR\" fg=\"000000\" />\r\n      <style token=\"PREPROCESSOR\" fg=\"808080\" />\r\n      <style token=\"REGEX\" fg=\"008040\" />\r\n      <style token=\"SEPARATOR\" fg=\"000000\" />\r\n      <style token=\"VARIABLE\" fg=\"810ca8\" bold=\"true\" />\r\n      <style token=\"WHITESPACE\" fg=\"000000\" />\r\n\r\n      <style token=\"ERROR_IDENTIFIER\" fg=\"ff0000\" />\r\n      <style token=\"ERROR_NUMBER_FORMAT\" fg=\"ff0000\" />\r\n      <style token=\"ERROR_STRING_DOUBLE\" fg=\"ff0000\" />\r\n      <style token=\"ERROR_CHAR\" fg=\"ff0000\" />\r\n   </tokenStyles>\r\n\r\n</RSyntaxTheme>\r\n"
  },
  {
    "path": "src/main/resources/themes/editor/VisualStudio.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE RSyntaxTheme SYSTEM \"theme.dtd\">\n\n<!--\n\tTheme that mimics Visual Studio's defaults.\n\tSee theme.dtd and org.fife.ui.rsyntaxtextarea.Theme for more information.\n-->\n<RSyntaxTheme version=\"1.0\">\n\n   <!-- Omitting baseFont will use a system-appropriate monospaced. -->\n   <!--<baseFont family=\"...\" size=\"13\"/>-->\n   \n   <!--  General editor colors. -->\n   <background color=\"ffffff\"/>\n   <caret color=\"000000\"/>\n   <selection fg=\"default\" bg=\"default\"/>\n   <currentLineHighlight color=\"e8f2fe\" fade=\"false\"/>\n   <marginLine fg=\"b0b4b9\"/>\n   <markAllHighlight color=\"6b8189\"/> <!-- TODO: Fix me -->\n   <markOccurrencesHighlight color=\"d4d4d4\" border=\"false\"/> <!-- TODO: Fix me -->\n   <matchedBracket fg=\"DBE0CC\" highlightBoth=\"false\" animate=\"false\"/>\n   <hyperlinks fg=\"0000ff\"/>\n   <secondaryLanguages>\n      <language index=\"1\" bg=\"fff0cc\"/>\n      <language index=\"2\" bg=\"dafeda\"/>\n      <language index=\"3\" bg=\"ffe0f0\"/>\n   </secondaryLanguages>\n   \n   \n   <!-- Gutter styling. -->\n   <gutterBorder color=\"808080\"/>\n   <lineNumbers fg=\"2B91AF\"/>\n   <foldIndicator fg=\"808080\" iconBg=\"ffffff\"/>\n   <iconRowHeader activeLineRange=\"3399ff\"/>\n   \n   <!-- Syntax tokens. -->\n   <tokenStyles>\n      <style token=\"IDENTIFIER\" fg=\"000000\"/>\n      <style token=\"RESERVED_WORD\" fg=\"0000ff\" bold=\"true\"/>\n      <style token=\"RESERVED_WORD_2\" fg=\"0000ff\" bold=\"true\"/>\n      <style token=\"ANNOTATION\" fg=\"808080\"/>\n      <style token=\"COMMENT_DOCUMENTATION\" fg=\"008000\"/>\n      <style token=\"COMMENT_EOL\" fg=\"008000\"/>\n      <style token=\"COMMENT_MULTILINE\" fg=\"008000\"/>\n      <style token=\"COMMENT_KEYWORD\" fg=\"ae9fbf\"/>\n      <style token=\"COMMENT_MARKUP\" fg=\"808080\"/>\n      <style token=\"DATA_TYPE\" fg=\"0000ff\" bold=\"true\"/>\n      <style token=\"FUNCTION\" fg=\"000000\"/>\n      <style token=\"LITERAL_BOOLEAN\" fg=\"0000ff\" bold=\"true\"/>\n      <style token=\"LITERAL_NUMBER_DECIMAL_INT\" fg=\"000000\"/>\n      <style token=\"LITERAL_NUMBER_FLOAT\" fg=\"000000\"/>\n      <style token=\"LITERAL_NUMBER_HEXADECIMAL\" fg=\"000000\"/>\n      <style token=\"LITERAL_STRING_DOUBLE_QUOTE\" fg=\"A31515\"/>\n      <style token=\"LITERAL_CHAR\" fg=\"A31515\"/>\n      <style token=\"LITERAL_BACKQUOTE\" fg=\"A31515\"/>\n      <style token=\"MARKUP_TAG_DELIMITER\" fg=\"0000ff\"/>\n      <style token=\"MARKUP_TAG_NAME\" fg=\"A31515\"/>\n      <style token=\"MARKUP_TAG_ATTRIBUTE\" fg=\"ff0000\"/>\n      <style token=\"MARKUP_TAG_ATTRIBUTE_VALUE\" fg=\"0000ff\"/>\n      <style token=\"MARKUP_COMMENT\" fg=\"006000\" italic=\"true\"/>\n      <style token=\"MARKUP_DTD\" fg=\"ad8000\"/>\n      <style token=\"MARKUP_PROCESSING_INSTRUCTION\" fg=\"808080\"/>\n      <style token=\"MARKUP_CDATA\" fg=\"000000\"/>\n      <style token=\"MARKUP_CDATA_DELIMITER\" fg=\"0000ff\"/>\n      <style token=\"OPERATOR\" fg=\"000000\"/>\n      <style token=\"PREPROCESSOR\" fg=\"808080\"/>\n      <style token=\"REGEX\" fg=\"000000\"/>\n      <style token=\"SEPARATOR\" fg=\"000000\"/>\n      <style token=\"VARIABLE\" fg=\"ff9900\" bold=\"true\"/>\n      <style token=\"WHITESPACE\" fg=\"000000\"/>\n      \n      <style token=\"ERROR_IDENTIFIER\" fg=\"000000\" bg=\"ffcccc\"/>\n      <style token=\"ERROR_NUMBER_FORMAT\" fg=\"000000\" bg=\"ffcccc\"/>\n      <style token=\"ERROR_STRING_DOUBLE\" fg=\"000000\" bg=\"ffcccc\"/>\n      <style token=\"ERROR_CHAR\" fg=\"000000\" bg=\"ffcccc\"/>\n   </tokenStyles>\n\n</RSyntaxTheme>\n"
  },
  {
    "path": "src/test/java/com/mucommander/XmlResourceTest.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.util.ResourceLoader;\nimport org.junit.jupiter.api.Test;\nimport org.xml.sax.SAXException;\nimport org.xml.sax.SAXParseException;\nimport org.xml.sax.helpers.DefaultHandler;\n\nimport javax.xml.parsers.ParserConfigurationException;\nimport javax.xml.parsers.SAXParserFactory;\nimport java.io.IOException;\n\n/**\n * This test case checks all XML documents that are embedded with muCommander and ensures they are well-formed.\n *\n * @author Maxence Bernard\n */\npublic class XmlResourceTest {\n\n    /**\n     * This test case checks all XML documents that are embedded with muCommander and ensures they are well-formed.\n     * The test fails when an error is encountered in one of the XML files, even a recoverable one.\n     * All XML files located in muCommander's base classpath are tested, whether this test case is invoked from the\n     * application's JAR file or from a regular directory. Archive files found in the application's path will also be\n     * searched for XML files.\n     *\n     * @throws IOException if a file or a folder couldn't be accessed\n     * @throws SAXException if an error was found in one of the XML documents\n     * @throws ParserConfigurationException if 'a serious configuration error' occurred in the XML parser\n     */\n    @Test\n    public void testXmlResources() throws SAXException, IOException, ParserConfigurationException {\n        testXMLFiles(ResourceLoader.getRootPackageAsFile(XmlResourceTest.class));\n    }\n\n\n    /**\n     * Looks for XML files in the specified folder recursively and tests them for well-formedness.\n     * A <code>org.xml.sax.SAXException</code> is thrown when an error is encountered in one of the XML files,\n     * even a recoverable one. Any archive contained in the folder will be searched for XML files.\n     *\n     * @param folder the folder in which to look for XML files recursively.\n     * @throws IOException if a file or a folder couldn't be accessed\n     * @throws SAXException if an error was found in one of the XML documents\n     * @throws ParserConfigurationException if 'a serious configuration error' occurred in the XML parser\n     */\n    private void testXMLFiles(AbstractFile folder) throws SAXException, IOException, ParserConfigurationException {\n        AbstractFile[] children = folder.ls();\n        for (AbstractFile bhild : children) {\n            if (bhild.isBrowsable())\n                testXMLFiles(bhild);\n            else if (\"xml\".equals(bhild.getExtension()))\n                testXMLDocument(bhild);\n        }\n    }\n\n    /**\n     * Checks the specified XML file that are embedded with muCommander for well-formedness. The test fails\n     * whenever an error is encountered in one of the XML files, even a recoverable one.\n     *\n     * @param file the file to parse and check for well-formedness.\n     * @throws IOException if there was an error reading the file\n     * @throws SAXException if an error was found in XML document\n     * @throws ParserConfigurationException 'a serious configuration error' occurred in the XML parser\n     */\n    private void testXMLDocument(AbstractFile file) throws SAXException, IOException, ParserConfigurationException {\n        System.out.println(\"Parsing \"+file.getAbsolutePath());\n\n        SAXParserFactory.newInstance().newSAXParser().parse(file.getInputStream(), new SAXErrorHandler());\n    }\n\n    /**\n     * This SAX handler reports errors that are found in the parsed XML document on the standard ouput and throws\n     * exceptions to the parser. \n     */\n    private static class SAXErrorHandler extends DefaultHandler {\n\n        @Override\n        public void warning(SAXParseException e) throws SAXException {\n            printSAXError(e, \"warning\");\n            throw e;\n        }\n\n        @Override\n        public void error(SAXParseException e) throws SAXException {\n            printSAXError(e, \"error\");\n            throw e;\n        }\n\n        @Override\n        public void fatalError(SAXParseException e) throws SAXException {\n            printSAXError(e, \"fatal error\");\n            throw e;\n        }\n\n        private void printSAXError(SAXParseException e, String errorType) {\n            System.out.println(\"SAX \"+errorType+\" at line \"+e.getLineNumber()+\", column \"+e.getColumnNumber()+\" : \"+e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/command/CommandTest.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.command;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.impl.local.LocalFile;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nimport java.io.File;\nimport java.nio.file.FileSystems;\n\n/**\n * Runs tests on {@link Command}.\n *\n * @author Nicolas Rinaudo\n */\npublic class CommandTest {\n    /**\n     * Test command's alias.\n     */\n    private static final String ALIAS = \"alias\";\n    /**\n     * Test command's command.\n     */\n    private static final String COMMAND = \"ls -la\";\n    /**\n     * Test command's display name.\n     */\n    private static final String DISPLAY_NAME = \"test\";\n\n    /**\n     * Used while testing keyword substitution.\n     */\n    private static final AbstractFile[] FILES =  new AbstractFile[] {\n        FileFactory.getFile(System.getProperty(\"user.dir\")),\n        FileFactory.getFile(System.getProperty(\"user.dir\") + FileSystems.getDefault().getSeparator() + \"test-cmd.txt\"),\n        FileFactory.getFile(System.getProperty(\"user.home\")),\n        FileFactory.getFile(System.getProperty(\"user.home\") + FileSystems.getDefault().getSeparator() + \"test-cmd.txt\"),\n        FileFactory.getFile(System.getProperty(\"java.home\")),\n        FileFactory.getFile(System.getProperty(\"java.home\") + FileSystems.getDefault().getSeparator() + \"test-cmd.txt\")\n    };\n\n\n\n    /**\n     * Tests the <code>$f</code> keyword.\n     */\n    @Test\n    public void testPathSubstitution() {\n        String[] tokens;\n\n        // Makes sure single file substitution works.\n        tokens = Command.getTokens(\"$f\", FILES[0]);\n        assert 1 == tokens.length;\n        assert FILES[0].getAbsolutePath().equals(tokens[0]);\n\n        // Makes sure multiple file substitution works.\n        tokens = Command.getTokens(\"$f\", FILES);\n        assert FILES.length == tokens.length;\n        for (int i = 0; i < 3; i++)\n            assert FILES[i].getAbsolutePath().equals(tokens[i]);\n    }\n\n    /**\n     * Returns the specified file's parent, or an empty string if it doesn't have one.\n     *\n     * @param file file whose parent should be returned.\n     * @return the specified file's parent, or an empty string if it doesn't have one.\n     */\n    private String getParent(AbstractFile file) {\n        AbstractFile parent = file.getParent();\n        return parent == null ? \"\" : parent.getAbsolutePath();\n    }\n\n    /**\n     * Tests the <code>$p</code> keyword.\n     */\n    @Test\n    public void testParentSubstitution() {\n        String[] tokens;\n\n        // Makes sure single file substitution works.\n        tokens = Command.getTokens(\"$p\", FILES[0]);\n        assert 1 == tokens.length;\n        assert getParent(FILES[0]).equals(tokens[0]);\n\n        // Makes sure multiple file substitution works.\n        tokens = Command.getTokens(\"$p\", FILES);\n        assert FILES.length == tokens.length;\n        for (int i = 0; i < 3; i++)\n            assert getParent(FILES[i]).equals(tokens[i]);\n    }\n\n    /**\n     * Returns the specified file's extension, or <code>\"\"</code> if it doesn't have one.\n     *\n     * @return the specified file's extension.\n     */\n    private String getExtension(AbstractFile file) {\n        String ext = file.getExtension();\n        return ext == null ? \"\" : ext;\n    }\n\n    /**\n     * Tests the <code>$e</code> keyword.\n     */\n    @Test\n    public void testExtensionSubstitution() {\n        String[] tokens;\n\n        // Makes sure single file substitution works (on directory).\n        tokens = Command.getTokens(\"$e\", FILES[0]);\n        assert 1 == tokens.length;\n        assert getExtension(FILES[0]).equals(tokens[0]);\n\n        // Makes sure single file substitution works (on file).\n        tokens = Command.getTokens(\"$e\", FILES[1]);\n        assert 1 == tokens.length;\n        assert getExtension(FILES[1]).equals(tokens[0]);\n\n        // Makes sure multiple file substitution works.\n        tokens = Command.getTokens(\"$e\", FILES);\n        assert FILES.length == tokens.length;\n        for (int i = 0; i < 3; i++)\n            assert getExtension(FILES[i]).equals(tokens[i]);\n    }\n\n    /**\n     * Tests the <code>$b</code> keyword.\n     */\n    @Test\n    public void testBasenameSubstitution() {\n        String[] tokens;\n\n        // Makes sure single file substitution works.\n        tokens = Command.getTokens(\"$b\", FILES[0]);\n        assert 1 == tokens.length;\n        assert FILES[0].getNameWithoutExtension().equals(tokens[0]);\n\n        // Makes sure multiple file substitution works.\n        tokens = Command.getTokens(\"$b\", FILES);\n        assert FILES.length == tokens.length;\n        for (int i = 0; i < 3; i++)\n            assert FILES[i].getNameWithoutExtension().equals(tokens[i]);\n    }\n\n    /**\n     * Tests the <code>$n</code> keyword.\n     */\n    @Test\n    public void testNameSubstitution() {\n        String[] tokens;\n\n        // Makes sure single file substitution works.\n        tokens = Command.getTokens(\"$n\", FILES[0]);\n        assert 1 == tokens.length;\n        assert FILES[0].getName().equals(tokens[0]);\n\n        // Makes sure multiple file substitution works.\n        tokens = Command.getTokens(\"$n\", FILES);\n        assert FILES.length == tokens.length;\n        for (int i = 0; i < 3; i++)\n            assert FILES[i].getName().equals(tokens[i]);\n    }\n\n    /**\n     * Tests the <code>$j</code> keyword.\n     */\n    @Test\n    public void testCurrentDirSubstitution() {\n        String[] tokens;\n\n        // Makes sure single file substitution works.\n        tokens = Command.getTokens(\"$j\", FILES[0]);\n        assert 1 == tokens.length;\n        assert new File(System.getProperty(\"user.dir\")).getAbsolutePath().equals(tokens[0]);\n\n        // Makes sure multiple file substitution works.\n        tokens = Command.getTokens(\"$j\", FILES);\n        assert 1 == tokens.length;\n        assert new File(System.getProperty(\"user.dir\")).getAbsolutePath().equals(tokens[0]);\n    }\n\n    /**\n     * Runs tests on parsing behaviour with illegal keywords.\n     */\n    @Test\n    public void testIllegalKeywords() {\n        String[] tokens;\n\n        // Makes sure unfinished keywords at the end of a command are kept.\n        tokens = Command.getTokens(\"ls -la $\", FILES);\n        assert 3 == tokens.length;\n        assert \"ls\".equals(tokens[0]);\n        assert \"-la\".equals(tokens[1]);\n        assert \"$\".equals(tokens[2]);\n\n        // Makes sure illegal keywords are not replaced.\n        tokens = Command.getTokens(\"ls $a\");\n        assert 2 == tokens.length;\n        assert \"ls\".equals(tokens[0]);\n        assert \"$a\".equals(tokens[1]);\n\n        // Makes sure unfinished keywords are not replaced.\n        tokens = Command.getTokens(\"ls $ la\");\n        assert 3 == tokens.length;\n        assert \"ls\".equals(tokens[0]);\n        assert \"$\".equals(tokens[1]);\n        assert \"la\".equals(tokens[2]);\n    }\n\n    /**\n     * Runs tests on command parsing (with keyword substitution).\n     */\n    @Test\n    public void testParsingWithSubstitution() {\n        String[] tokens;\n\n        for (var file : FILES) {\n            assertEquals(LocalFile.class, file.getClass());\n        }\n\n        // Makes sure keywords are tokenized when not escaped.\n        tokens = Command.getTokens(\"ls $f\", FILES);\n        assertEquals(1 + FILES.length, tokens.length);\n        assertEquals(\"ls\", tokens[0]);\n        for (int i = 0; i < FILES.length; i++)\n            assertEquals(FILES[i].getAbsolutePath(), tokens[i + 1]);\n\n        // Makes sure keywords are not tokenized when escaped.\n        tokens = Command.getTokens(\"ls \\\"$f\\\"\", FILES);\n        StringBuilder buffer = new StringBuilder(\"\\\"\");\n        buffer.append(FILES[0].getAbsolutePath());\n        for (int i = 1; i < FILES.length; i++) {\n            buffer.append(' ');\n            buffer.append(FILES[i].getAbsolutePath());\n        }\n        buffer.append(\"\\\"\");\n        assertEquals(2, tokens.length);\n        assertEquals(\"ls\", tokens[0]);\n        assertEquals(buffer.toString(), tokens[1]);\n\n        // Makes sure that keyword substitution happens even if the keyword is not a single token.\n        tokens = Command.getTokens(\"ls$fla\", FILES[0]);\n        assertEquals(1, tokens.length);\n        assertEquals(\"ls\" + FILES[0].getAbsolutePath() + \"la\", tokens[0]);\n\n        tokens = Command.getTokens(\"ls$fla\", FILES);\n        assert FILES.length == tokens.length;\n        assert (\"ls\" + FILES[0].getAbsolutePath()).equals(tokens[0]);\n        for (int i = 1; i < FILES.length - 1; i++) {\n            assertEquals(FILES[i].getAbsolutePath(), tokens[i]);\n        }\n        assertEquals(FILES[FILES.length - 1].getAbsolutePath() + \"la\", tokens[tokens.length - 1]);\n    }\n\n    /**\n     * Runs tests on command parsing (without keyword substitution).\n     */\n    @Test\n    public void testParsingWithoutSubstitution() {\n        String[] tokens;\n\n        // Makes sure simple command parsing works.\n        tokens = Command.getTokens(\"ls -la\");\n        assert 2 == tokens.length;\n        assert \"ls\".equals(tokens[0]);\n        assert \"-la\".equals(tokens[1]);\n\n        // Makes sure spaces are trimmed when they're expected to.\n        tokens = Command.getTokens(\"ls     -la     \");\n        assert 2 == tokens.length;\n        assert \"ls\".equals(tokens[0]);\n        assert \"-la\".equals(tokens[1]);\n\n        // Makes sure quotes:\n        // - escape spaces.\n        // - are not removed from the command.\n        tokens = Command.getTokens(\"ls \\\"- l a\\\"\");\n        assert 2 == tokens.length;\n        assert \"ls\".equals(tokens[0]);\n        assert \"\\\"- l a\\\"\".equals(tokens[1]);\n\n        // Makes sure spaces are not trimmed when they're not expected to.\n        tokens = Command.getTokens(\"ls \\\"-    l    a   \\\"\");\n        assert 2 == tokens.length;\n        assert \"ls\".equals(tokens[0]);\n        assert \"\\\"-    l    a   \\\"\".equals(tokens[1]);\n\n        // Makes sure \\s:\n        // - escape quotes.\n        // - are removed from the command.\n        tokens = Command.getTokens(\"ls \\\\\\\"- l a\");\n        assert 4 == tokens.length;\n        assert \"ls\".equals(tokens[0]);\n        assert \"\\\"-\".equals(tokens[1]);\n        assert \"l\".equals(tokens[2]);\n        assert \"a\".equals(tokens[3]);\n\n        // Makes sure \\s:\n        // - escape spaces.\n        // - are removed from the command.\n        tokens = Command.getTokens(\"ls My\\\\ Documents\");\n        assert 2 == tokens.length;\n        assert \"ls\".equals(tokens[0]);\n        assert \"My Documents\".equals(tokens[1]);\n\n        // Makes sure 'complex' tokenisation works.\n        tokens = Command.getTokens(\"/usr/bin/find . -name \\\\\\\\*.java -exec sh -c \\\"echo {}; wc {}\\\" \\\\\\\\;\");\n        assert 9 == tokens.length;\n        assert \"/usr/bin/find\".equals(tokens[0]);\n        assert \".\".equals(tokens[1]);\n        assert \"-name\".equals(tokens[2]);\n        assert \"\\\\*.java\".equals(tokens[3]);\n        assert \"-exec\".equals(tokens[4]);\n        assert \"sh\".equals(tokens[5]);\n        assert \"-c\".equals(tokens[6]);\n        assert \"\\\"echo {}; wc {}\\\"\".equals(tokens[7]);\n        assert \"\\\\;\".equals(tokens[8]);\n    }\n\n\n    // - Constructors tests ----------------------------------------------------\n    // -------------------------------------------------------------------------\n\n    /**\n     * Makes sure the specified command matches the specified arguments.\n     */\n    private void checkCommand(Command command, CommandType type, boolean isDisplayNameSet) {\n        // Tests common values.\n        assert ALIAS.equals(command.getAlias());\n        assert COMMAND.equals(command.getCommand());\n        assert type == command.getType();\n\n        // Tests context dependant values.\n        if (isDisplayNameSet) {\n            assertTrue(command.isDisplayNameSet());\n            assertEquals(DISPLAY_NAME, command.getDisplayName());\n        } else {\n            assertFalse(command.isDisplayNameSet());\n            assertEquals(ALIAS, command.getDisplayName());\n        }\n    }\n\n    /**\n     * Makes sure all constructors initialize a command to the right values.\n     */\n    @Test\n    public void testConstructors() {\n        // Tests the 2 arguments constructor.\n        checkCommand(new Command(ALIAS, COMMAND), CommandType.NORMAL_COMMAND, false);\n\n        // Tests the 3 arguments constructor.\n        checkCommand(new Command(ALIAS, COMMAND, CommandType.NORMAL_COMMAND), CommandType.NORMAL_COMMAND, false);\n        checkCommand(new Command(ALIAS, COMMAND, CommandType.SYSTEM_COMMAND), CommandType.SYSTEM_COMMAND, false);\n        checkCommand(new Command(ALIAS, COMMAND, CommandType.INVISIBLE_COMMAND), CommandType.INVISIBLE_COMMAND, false);\n\n        // Tests the 4 arguments constructor.\n        checkCommand(new Command(ALIAS, COMMAND, CommandType.NORMAL_COMMAND, DISPLAY_NAME, null), CommandType.NORMAL_COMMAND, true);\n        checkCommand(new Command(ALIAS, COMMAND, CommandType.SYSTEM_COMMAND, DISPLAY_NAME, null), CommandType.SYSTEM_COMMAND, true);\n        checkCommand(new Command(ALIAS, COMMAND, CommandType.INVISIBLE_COMMAND, DISPLAY_NAME, null), CommandType.INVISIBLE_COMMAND, true);\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/conf/BufferedConfigurationExplorerTest.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\nimport org.junit.jupiter.api.Test;\n\n/**\n * A test case for the {@link BufferedConfigurationExplorer} class.\n * @author Nicolas Rinaudo\n */\npublic class BufferedConfigurationExplorerTest extends ConfigurationExplorerTest {\n    /**\n     * Returns an instance of {@link BufferedConfigurationExplorer}.\n     * @return an instance of {@link BufferedConfigurationExplorer}.\n     */\n    @Override\n    protected ConfigurationExplorer getExplorer() {\n        return new BufferedConfigurationExplorer(conf.getRoot());\n    }\n\n    /**\n     * Backtracks through the section history and makes sure it contains the correct values.\n     */\n    private void backtrack(BufferedConfigurationExplorer explorer, int depth) {\n        for(int i = depth; i > 0; i--) {\n            assert explorer.hasSections();\n            assert (VARIABLE_VALUE + i).equals(explorer.popSection().getVariable(VARIABLE_NAME + i));\n        }\n        assert !explorer.hasSections();\n    }\n\n\n    /**\n     * Tests path bufferisation when the requested sections are found.\n     */\n    @Test\n    public void testSectionFoundBuffer() {\n        BufferedConfigurationExplorer explorer;\n\n        for(int i = 0; i < DEPTH; i++) {\n            moveTo(explorer = (BufferedConfigurationExplorer)getExplorer(), i);\n            backtrack(explorer, i);\n        }\n    }\n\n    /**\n     * Tests path bufferisation when the requested sections are not found.\n     */\n    @Test\n    public void testSectionNotFoundBuffer() {\n        BufferedConfigurationExplorer explorer;\n\n        for(int i = 0; i < DEPTH; i++) {\n            moveTo(explorer = (BufferedConfigurationExplorer)getExplorer(), i);\n\n            // Makes sure that a failed moveTo call doesn't corrupt section history.\n            assert !explorer.moveTo(FAKE_SECTION + i, false);\n            backtrack(explorer, i);\n        }\n    }\n\n    /**\n     * Tests path bufferisation when the requested sections are not found but created.\n     */\n    @Test\n    public void testSectionNotFoundAndCreateBuffer() {\n        BufferedConfigurationExplorer explorer;\n\n        for(int i = 0; i < DEPTH; i++) {\n            moveTo(explorer = (BufferedConfigurationExplorer)getExplorer(), i);\n\n            // Creates the section, makes sure it was created and takes it off\n            // the section stack.\n            assert explorer.moveTo(FAKE_SECTION + i, true);\n            assert explorer.hasSections();\n            assert explorer.popSection() != null;\n\n            // Makes sure backtracking through history works.\n            backtrack(explorer, i);\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/conf/ConfigurationEventTest.java",
    "content": "package com.mucommander.commons.conf;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Vector;\n\n/**\n * A test case for the {@link ConfigurationEvent} class.\n * @author Nicolas Rinaudo\n */\npublic class ConfigurationEventTest {\n    // - Test constants ------------------------------------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------------------------------------\n    /** Name of the test variable. */\n    private static final String         VARIABLE_NAME = \"variable\";\n    /** Test string value. */\n    private static final String         STRING_VALUE  = \"value\";\n    /** Test list value. */\n    private static final Vector<String> LIST_VALUE    = new Vector<>();\n    /** Test integer value. */\n    private static final int            INTEGER_VALUE = 10;\n    /** Test long value. */\n    private static final long           LONG_VALUE    = 15;\n    /** Test float value. */\n    private static final float          FLOAT_VALUE   = (float)10.5;\n    /** Test double value. */\n    private static final double         DOUBLE_VALUE  = 15.5;\n    /** Test boolean value. */\n    private static final boolean        BOOLEAN_VALUE = true;\n\n\n    /** Configuration instance used to create events. */\n    private Configuration conf;\n\n\n\n    static {\n        for(int i = 0; i < 7; i++)\n            LIST_VALUE.add(Integer.toString(i));\n    }\n\n    /**\n     * Initializes the test case.\n     */\n    @BeforeEach\n    public void setUp() {\n        conf = new Configuration();\n    }\n\n    /**\n     * Tests string events.\n     */\n    @Test\n    public void testStringEvents() {\n        ConfigurationEvent event;\n\n        // Makes sure the value passed to the constructor is properly returned.\n        event = new ConfigurationEvent(conf, VARIABLE_NAME, STRING_VALUE);\n        assert STRING_VALUE.equals(event.getValue());\n\n        // Makes sure unset values are returned as null.\n        event = new ConfigurationEvent(conf, VARIABLE_NAME, null);\n        assert null == event.getValue();\n    }\n\n    /**\n     * Tests list events.\n     */\n    @Test\n    public void testListEvents() {\n        ConfigurationEvent event;\n\n        // Makes sure the value passed to the constructor is properly returned.\n        event = new ConfigurationEvent(conf, VARIABLE_NAME, ValueList.toString(LIST_VALUE, \";\"));\n        assert LIST_VALUE.equals(event.getListValue(\";\"));\n\n        // Makes sure unset values are returned as null.\n        event = new ConfigurationEvent(conf, VARIABLE_NAME, null);\n        assert null == event.getListValue(\";\");\n    }\n\n    /**\n     * Tests integer events.\n     */\n    @Test\n    public void testIntegerEvent() {\n        ConfigurationEvent event;\n\n        // Makes sure the value passed to the constructor is properly returned.\n        event = new ConfigurationEvent(conf, VARIABLE_NAME, Integer.toString(INTEGER_VALUE));\n        assert INTEGER_VALUE == event.getIntegerValue();\n\n        // Makes sure unset values are returned as 0.\n        event = new ConfigurationEvent(conf, VARIABLE_NAME, null);\n        assert 0 == event.getIntegerValue();\n    }\n\n    /**\n     * Tests long events.\n     */\n    @Test\n    public void testLongEvent() {\n        ConfigurationEvent event;\n\n        // Makes sure the value passed to the constructor is properly returned.\n        event = new ConfigurationEvent(conf, VARIABLE_NAME, Long.toString(LONG_VALUE));\n        assert LONG_VALUE == event.getLongValue();\n\n        // Makes sure unset values are returned as 0.\n        event = new ConfigurationEvent(conf, VARIABLE_NAME, null);\n        assert 0 == event.getLongValue();\n    }\n\n    /**\n     * Tests double events.\n     */\n    @Test\n    public void testDoubleEvent() {\n        ConfigurationEvent event;\n\n        // Makes sure the value passed to the constructor is properly returned.\n        event = new ConfigurationEvent(conf, VARIABLE_NAME, Double.toString(DOUBLE_VALUE));\n        assert DOUBLE_VALUE == event.getDoubleValue();\n\n        // Makes sure unset values are returned as 0.\n        event = new ConfigurationEvent(conf, VARIABLE_NAME, null);\n        assert 0 == event.getDoubleValue();\n    }\n\n    /**\n     * Tests float events.\n     */\n    @Test\n    public void testFloatEvent() {\n        ConfigurationEvent event;\n\n        // Makes sure the value passed to the constructor is properly returned.\n        event = new ConfigurationEvent(conf, VARIABLE_NAME, Float.toString(FLOAT_VALUE));\n        assert FLOAT_VALUE == event.getFloatValue();\n\n        // Makes sure unset values are returned as 0.\n        event = new ConfigurationEvent(conf, VARIABLE_NAME, null);\n        assert 0 == event.getFloatValue();\n    }\n\n    /**\n     * Tests boolean events.\n     */\n    @Test\n    public void testBooleanEvent() {\n        ConfigurationEvent event;\n\n        // Makes sure the value passed to the constructor is properly returned.\n        event = new ConfigurationEvent(conf, VARIABLE_NAME, Boolean.toString(BOOLEAN_VALUE));\n        assert BOOLEAN_VALUE == event.getBooleanValue();\n\n        // Makes sure unset values are returned as 0.\n        event = new ConfigurationEvent(conf, VARIABLE_NAME, null);\n        assert !event.getBooleanValue();\n    }\n\n\n\n    /**\n     * Tests event creation.\n     */\n    @Test\n    public void testConstructor() {\n        ConfigurationEvent event;\n\n        // Makes sure the constructor initializes events properly.\n        event = new ConfigurationEvent(conf, VARIABLE_NAME, STRING_VALUE);\n        assert conf.equals(event.getConfiguration());\n        assert VARIABLE_NAME.equals(event.getVariable());\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/conf/ConfigurationExplorerTest.java",
    "content": "package com.mucommander.commons.conf;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\n/**\n * A test case for the {@link ConfigurationExplorer} class.\n * @author Nicolas Rinaudo\n */\npublic class ConfigurationExplorerTest {\n    /** Root name of test sections. */\n    protected static final String SECTION_NAME   = \"section\";\n    /** Root name of test variables. */\n    protected static final String VARIABLE_NAME  = \"variable\";\n    /** Root name of test values. */\n    protected static final String VARIABLE_VALUE = \"value\";\n    /** Name for non-existing sections. */\n    protected static final String FAKE_SECTION   = \"fake\";\n    /** Depth of the tests. */\n    protected static final int    DEPTH          = 4;\n\n    /** Configuration used for tests. */\n    protected Configuration conf;\n\n\n\n    /**\n     * Fills the configuration instance with test values.\n     */\n    @BeforeEach\n    public void setUp() {\n        StringBuilder buffer = new StringBuilder();\n        conf   = new Configuration();\n        for(int i = 0; i < DEPTH; i++) {\n            conf.setVariable(buffer.toString() + VARIABLE_NAME + i, VARIABLE_VALUE + i);\n            buffer.append(SECTION_NAME);\n            buffer.append(i);\n            buffer.append('.');\n        }\n    }\n\n    /**\n     * Returns a configuration explorer on the test section.\n     * @return a configuration explorer on the test section.\n     */\n    protected ConfigurationExplorer getExplorer() {\n        return new ConfigurationExplorer(conf.getRoot());\n    }\n\n    /**\n     * Moves to the specified depth.\n     * @param  explorer explorer to use when moving to the specified depth.\n     * @param  depth    depth in the configuration tree at which to move.\n     * @return          section that was found at the specified depth.\n     */\n    protected ConfigurationSection moveTo(ConfigurationExplorer explorer, int depth) {\n        ConfigurationSection section;\n\n        section = conf.getRoot();\n        for(int i = 0; i < depth; i++) {\n            assert explorer.moveTo(SECTION_NAME + i, false);\n            section = explorer.getSection();\n        }\n\n        return section;\n    }\n\n    /**\n     * Tests the {@link ConfigurationExplorer#moveTo(String,boolean)} method.\n     */\n    private void assertSectionNotFound(boolean create) {\n        ConfigurationExplorer explorer;\n        ConfigurationSection  section;\n\n        // Checks what happens when sections are not found and create is set to false.\n        for(int i = 0; i < DEPTH; i++) {\n            section = moveTo(explorer = getExplorer(), i);\n\n            // Makes sure the 'fake' section doesn't exist.\n            assert !explorer.moveTo(FAKE_SECTION + i, false);\n\n            if(create) {\n                // Tries to create the section and makes sure the explorer\n                // did move to it.\n                assert explorer.moveTo(FAKE_SECTION + i, true);\n                assert section.getSection(FAKE_SECTION + i).equals(explorer.getSection());\n            }\n            else\n                // Makes sure the explorer didn't change section.\n                assert explorer.getSection().equals(section);\n        }\n    }\n\n    /**\n     * Tests configuration navigation to non-existing sections (without section creation).\n     */\n    @Test\n    public void testSectionNotFoundWithoutCreate() {\n        assertSectionNotFound(false);}\n\n    /**\n     * Tests configuration navigation to non-existing sections (with section creation).\n     */\n    @Test\n    public void testSectionNotFoundWithCreate() {\n        assertSectionNotFound(true);}\n\n    /**\n     * Test configuration navigation to existing sections.\n     */\n    @Test\n    public void testSectionFound() {\n        ConfigurationExplorer explorer;\n\n        for(int i = 0; i < DEPTH; i++) {\n            moveTo(explorer = getExplorer(), i);\n            assert (VARIABLE_VALUE + i).equals(explorer.getSection().getVariable(VARIABLE_NAME + i));\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/conf/ConfigurationSectionTest.java",
    "content": "package com.mucommander.commons.conf;\r\n\r\nimport org.junit.jupiter.api.Test;\r\nimport org.junit.jupiter.params.ParameterizedTest;\r\nimport org.junit.jupiter.params.provider.CsvSource;\r\nimport org.junit.jupiter.params.provider.ValueSource;\r\n\r\nimport java.util.*;\r\n\r\n/**\r\n * A test case for the {@link Configuration} class.\r\n * @author Nicolas Rinaudo\r\n */\r\npublic class ConfigurationSectionTest {\r\n    /**\r\n     * Tests the {@link ConfigurationSection#removeVariable(String)} method.\r\n     * @param value value to which the variable should be set before being removed.\r\n     */\r\n//    @Test(dataProvider = \"removeVariable\")\r\n    @ParameterizedTest\r\n    @ValueSource(strings = {\"value\"})\r\n    public void testRemoveVariable(String value) {\r\n        ConfigurationSection section = new ConfigurationSection();\r\n\r\n        assert section.setVariable(\"var\", value);\r\n        assertVariable(section, \"var\", value);\r\n\r\n        assert value == null ? section.removeVariable(\"var\") == null : section.removeVariable(\"var\").equals(value);\r\n    }\r\n\r\n\r\n\r\n    /**\r\n     * Makes sure that the specified variable has the specified value.\r\n     * @param section section in which the variable to test is located.\r\n     * @param var     name of the variable to test.\r\n     * @param value   expected variable value.\r\n     */\r\n    private void assertVariable(ConfigurationSection section, String var, String value) {\r\n        if(value == null || value.isEmpty())\r\n            assert section.getVariable(var) == null: \"Expected null but found \" + section.getVariable(var);\r\n        else {\r\n            assert section.getVariable(var).equals(value): \"Expected \" + value + \" but found \" + section.getVariable(var);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Tests the {@link ConfigurationSection#setVariable(String, String)} method.\r\n     * @param first    first value to which the test variable should be set.\r\n     * @param second   second value to which the test variable should be set.\r\n     * @param expected expected return value of the second call to\r\n     *                 {@link ConfigurationSection#setVariable(String, String)}.\r\n     */\r\n    @ParameterizedTest\r\n    @CsvSource({\r\n        \"value, other, true\",\r\n        \"value, value, false\",\r\n        \"value, null, true\",\r\n        \"value, ,   true\"\r\n    })\r\n    public void testSetVariable(String first, String second, boolean expected) {\r\n        ConfigurationSection section = new ConfigurationSection();\r\n\r\n        assert section.setVariable(\"var\", first);\r\n        assertVariable(section, \"var\", first);\r\n\r\n        assert expected == section.setVariable(\"var\", second);\r\n        assertVariable(section, \"var\", second);\r\n    }\r\n\r\n\r\n\r\n    /**\r\n     * Utility method for {@link #testVariableNames()}.\r\n     * @param section section to explore.\r\n     * @param count   number of expected variable names.\r\n     */\r\n    private void assertVariableNames(ConfigurationSection section, int count) {\r\n        // No expected variables, make sure that the section reflects that.\r\n        if(count == 0) {\r\n            assert !section.hasVariables();\r\n            assert section.isEmpty();\r\n            assert section.variableNames().isEmpty();\r\n        }\r\n\r\n        // Makes sure that the section contains exactly var1, var2..., var<count>.\r\n        // We have to go through a set here: the order in which we'll iterate over the variable names is unreliable.\r\n        else {\r\n            assert section.hasVariables();\r\n            assert !section.isEmpty();\r\n\r\n            // Populates a set will all the expected variable names.\r\n            Set<String> expectedNames = new HashSet<>(count);\r\n            for(int i = 0; i < count; i++)\r\n                expectedNames.add(\"var\" + i);\r\n\r\n            // Makes sure that we can remove all of the section's variables, and that none remains afterward.\r\n            Set<String> names = section.variableNames();\r\n            for (String name : names) {\r\n                assert expectedNames.remove(name);\r\n            }\r\n            assert expectedNames.isEmpty();\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Tests the {@link ConfigurationSection#variableNames()} method.\r\n     */\r\n    @Test\r\n    public void testVariableNames() {\r\n        ConfigurationSection section = new ConfigurationSection();\r\n\r\n        // create 10 variables.\r\n        for(int i = 0; i < 10; i++)\r\n            section.setVariable(\"var\" + i, \"value\");\r\n\r\n        assertVariableNames(section, 10);\r\n\r\n        section.setVariable(\"var9\", \"\");\r\n        assertVariableNames(section, 9);\r\n\r\n        section.setVariable(\"var8\", null);\r\n        assertVariableNames(section, 8);\r\n\r\n        section.removeVariable(\"var7\");\r\n        assertVariableNames(section, 7);\r\n\r\n\r\n        for(int i = 0; i < 7; i++)\r\n            section.removeVariable(\"var\" + i);\r\n        assertVariableNames(section, 0);\r\n    }\r\n\r\n\r\n    /**\r\n     * Tests the list value helpers.\r\n     */\r\n    @Test\r\n    public void testLists() {\r\n        ValueList list = new ValueList(\"1,2,3,4\", \",\");\r\n        assert ConfigurationSection.getListValue(\"1,2,3,4\", \",\").equals(list);\r\n        assert ConfigurationSection.getValue(list, \",\").equals(\"1,2,3,4\");\r\n    }\r\n\r\n    /**\r\n     * Tests the long value helpers.\r\n     */\r\n    @Test\r\n    public void testLongs() {\r\n        for(long i = 0; i < 10; i++) {\r\n            assert ConfigurationSection.getLongValue(Long.toString(i)) == i;\r\n            assert ConfigurationSection.getValue(i).equals(Long.toString(i));\r\n        }\r\n\r\n        assert ConfigurationSection.getLongValue(null) == 0;\r\n    }\r\n\r\n    /**\r\n     * Tests the integer value helpers.\r\n     */\r\n    @Test\r\n    public void testIntegers() {\r\n        for(int i = 0; i < 10; i++) {\r\n            assert ConfigurationSection.getIntegerValue(Integer.toString(i)) == i;\r\n            assert ConfigurationSection.getValue(i).equals(Integer.toString(i));\r\n        }\r\n\r\n        assert ConfigurationSection.getIntegerValue(null) == 0;\r\n    }\r\n    \r\n    /**\r\n     * Tests the double value helpers.\r\n     */\r\n    @Test\r\n    public void testDoubles() {\r\n        for(int i = 0; i < 10; i++) {\r\n            assert ConfigurationSection.getDoubleValue(i + \".5\") == (i + 0.5d);\r\n            assert ConfigurationSection.getValue((i + 0.5d)).equals(i + \".5\");\r\n        }\r\n\r\n        assert ConfigurationSection.getDoubleValue(null) == 0f;\r\n    }\r\n\r\n    /**\r\n     * Tests the float value helpers.\r\n     */\r\n    @Test\r\n    public void testFloats() {\r\n        for(int i = 0; i < 10; i++) {\r\n            assert ConfigurationSection.getFloatValue(i + \".5\") == (i + 0.5f);\r\n            assert ConfigurationSection.getValue((i + 0.5f)).equals(i + \".5\");\r\n        }\r\n\r\n        assert ConfigurationSection.getFloatValue(null) == 0f;\r\n    }\r\n\r\n    /**\r\n     * Tests the boolean value helpers.\r\n     */\r\n    @Test\r\n    public void testBooleans() {\r\n        assert ConfigurationSection.getBooleanValue(\"true\");\r\n        assert ConfigurationSection.getValue(true).equals(\"true\");\r\n\r\n        assert !ConfigurationSection.getBooleanValue(\"false\");\r\n        assert !ConfigurationSection.getBooleanValue(\"!@#\");\r\n        assert !ConfigurationSection.getBooleanValue(\"\");\r\n        assert ConfigurationSection.getValue(false).equals(\"false\");\r\n\r\n        assert !ConfigurationSection.getBooleanValue(null);\r\n    }\r\n\r\n\r\n\r\n    /**\r\n     * Tests various section related method.\r\n     */\r\n    @Test\r\n    public void testSections() {\r\n        ConfigurationSection section = new ConfigurationSection();\r\n\r\n        // Makes sure we can add a section.\r\n        ConfigurationSection buffer = section.addSection(\"sect\");\r\n        assert buffer != null;\r\n        assert section.hasSections();\r\n\r\n        // Makes sure that adding the same section twice yields the same instance.\r\n        assert section.addSection(\"sect\") == buffer;\r\n        assert section.hasSections();\r\n\r\n        // Makes sure we can remove a section by name.\r\n        assert section.removeSection(\"sect\") == buffer;\r\n        assert section.getSection(\"sect\") == null;\r\n        assert !section.hasSections();\r\n\r\n        // Makes sure we can remove a section by value.\r\n        buffer = section.addSection(\"sect\");\r\n        assert section.removeSection(buffer);\r\n        assert buffer.getSection(\"sect\") == null;\r\n        assert !section.hasSections();\r\n        assert !section.removeSection(buffer);\r\n    }\r\n\r\n    /**\r\n     * Utility method for {@link #testSectionNames()}.\r\n     * @param section section to explore.\r\n     * @param count   number of expected section names.\r\n     */\r\n    private void assertSectionNames(ConfigurationSection section, int count) {\r\n        // No expected variables, make sure that the section reflects that.\r\n        if(count == 0) {\r\n            assert !section.hasSections();\r\n            assert section.isEmpty();\r\n            assert section.sectionNames().isEmpty();\r\n        }\r\n\r\n        // Makes sure that the section contains exactly sect1, sect2..., sect<count>.\r\n        // We have to go through a set here: the order in which we'll iterate over the variable names is unreliable.\r\n        else {\r\n            assert section.hasSections();\r\n            assert !section.isEmpty();\r\n\r\n            // Populates a set will all the expected variable names.\r\n            Set<String> expectedNames = new HashSet<>(count);\r\n            for(int i = 0; i < count; i++)\r\n                expectedNames.add(\"sect\" + i);\r\n\r\n            // Makes sure that we can remove all of the section's sub-sections, and that none remains afterward.\r\n            Set<String> names = section.sectionNames();\r\n            for (String name : names) {\r\n                assert expectedNames.remove(name);\r\n            }\r\n\r\n            assert expectedNames.isEmpty();\r\n        }\r\n    }\r\n\r\n    @Test\r\n    public void testSectionNames() {\r\n        ConfigurationSection section = new ConfigurationSection();\r\n\r\n        // create 10 variables.\r\n        ConfigurationSection buffer = null;\r\n        for(int i = 0; i < 10; i++)\r\n            buffer = section.addSection(\"sect\" + i);\r\n        assertSectionNames(section, 10);\r\n\r\n        // Removes the last section by value, makes sure we're in a coherent state.\r\n        assert section.removeSection(buffer);\r\n        assertSectionNames(section, 9);\r\n\r\n        // Removes the last section by name, makes sure we're in a coherent state.\r\n        assert section.removeSection(\"sect8\") != null;\r\n        assertSectionNames(section, 8);\r\n\r\n        for(int i = 0; i < 8; i++)\r\n            assert section.removeSection(\"sect\" + i) != null;\r\n        assertSectionNames(section, 0);\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/conf/FileConfigurationSourceTest.java",
    "content": "package com.mucommander.commons.conf;\n\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.io.Writer;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\n/**\n * A test case for the {@link FileConfigurationSource} class.\n * @author Nicolas Rinaudo\n */\npublic class FileConfigurationSourceTest  {\n    /** String used for tests. */\n    private static final String TEST_VALUE = \"Hello, World!\";\n\n\n    /** File used to run the tests. */\n    private File file;\n\n\n    /**\n     * Creates a new temporary file with which to work.\n     */\n    @BeforeEach\n    public void setUp() throws IOException {\n        file = File.createTempFile(\"conf\", \"test\");\n        file.deleteOnExit();\n    }\n\n    /**\n     * Tests file source initialization.\n     */\n    @Test\n    public void testInitialization() {\n        FileConfigurationSource source;\n\n        // Makes sure the 'file' constructor works properly.\n        source = new FileConfigurationSource(file, \"utf-8\");\n        assert file.equals(source.getFile());\n\n        // Makes sure the 'string' constructor works properly.\n        source = new FileConfigurationSource(file.getAbsolutePath(), \"utf-8\");\n        assert file.equals(source.getFile());\n    }\n\n    /**\n     * Reads and returns the content of <code>in</code>.\n     */\n    private String read(Reader in) throws IOException {\n        StringBuilder content;\n        char[]        buffer;\n        int           count;\n\n        buffer = new char[TEST_VALUE.length()];\n        content = new StringBuilder();\n\n        while((count = in.read(buffer)) != -1)\n            content.append(buffer, 0, count);\n\n        return content.toString();\n    }\n\n    /**\n     * Tests the source's streams.\n     * @throws IOException if an IO related error occurs.\n     */\n    @Test\n    public void testStreams() throws IOException {\n        FileConfigurationSource source;\n        Writer                  out;\n        Reader                  in;\n\n        // Initialisation.\n        out    = null;\n        in     = null;\n        source = new FileConfigurationSource(file, \"utf-8\");\n\n        // Writes the test string to the source's output stream.\n        try {(out = source.getWriter()).write(TEST_VALUE.toCharArray());}\n        finally {\n            if(out != null) {\n                try {out.close();}\n                catch(Exception e) {}\n            }\n        }\n\n        // Reads the content of the source's input stream and makes sure it\n        // matches with what we wrote.\n        try {assert TEST_VALUE.equals(read(in = source.getReader()));}\n        finally {\n            if(in != null) {\n                try {in.close();}\n                catch(Exception e) {}\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/conf/ValueIteratorTest.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\n/**\n * Test case for the {@link ValueIterator} class.\n * @author Nicolas Rinaudo\n */\npublic class ValueIteratorTest extends ValueListTest {\n    /**\n     * Tests the {@link ValueIterator#nextValue()} method.\n     * @param values test data.\n     */\n    @Override\n    protected void testStringValues(ValueList values) {\n        ValueIterator iterator;\n\n        iterator = values.valueIterator();\n        for(int i = 0; i < 7; i++) {\n            assert iterator.hasNext();\n            assert Integer.toString(i + 1).equals(iterator.nextValue());\n        }\n        assert !iterator.hasNext();\n    }\n\n    /**\n     * Tests the {@link ValueIterator#nextIntegerValue()} method.\n     * @param values test data.\n     */\n    @Override\n    protected void testIntegerValues(ValueList values) {\n        ValueIterator iterator;\n\n        iterator = values.valueIterator();\n        for(int i = 0; i < 7; i++) {\n            assert iterator.hasNext();\n            assert i + 1 == iterator.nextIntegerValue();\n        }\n        assert !iterator.hasNext();\n    }\n\n    /**\n     * Tests the {@link ValueIterator#nextLongValue()} method.\n     * @param values test data.\n     */\n    @Override\n    protected void testLongValues(ValueList values) {\n        ValueIterator iterator;\n\n        iterator = values.valueIterator();\n        for(int i = 0; i < 7; i++) {\n            assert iterator.hasNext();\n            assert i + 1 == iterator.nextLongValue();\n        }\n    }\n\n    /**\n     * Tests the {@link ValueIterator#nextFloatValue()} method.\n     * @param values test data.\n     */\n    @Override\n    protected void testFloatValues(ValueList values) {\n        ValueIterator iterator;\n\n        iterator = values.valueIterator();\n        for(int i = 0; i < 7; i++) {\n            assert iterator.hasNext();\n            assert i + 1.5 == iterator.nextFloatValue();\n        }\n    }\n\n    /**\n     * Tests the {@link ValueIterator#nextDoubleValue()} method.\n     * @param values test data.\n     */\n    @Override\n    protected void testDoubleValues(ValueList values) {\n        ValueIterator iterator;\n\n        iterator = values.valueIterator();\n        for(int i = 0; i < 7; i++) {\n            assert iterator.hasNext();\n            assert i + 1.5 == iterator.nextDoubleValue();\n        }\n    }\n\n    /**\n     * Tests the {@link ValueIterator#nextBooleanValue()} method.\n     * @param values test data.\n     */\n    @Override\n    protected void testBooleanValues(ValueList values) {\n        ValueIterator iterator;\n\n        iterator = values.valueIterator();\n        for(int i = 0; i < 7; i++) {\n            assert iterator.hasNext();\n            assert (i % 2 == 0) == iterator.nextBooleanValue();\n        }\n    }\n\n    /**\n     * Tests the {@link ValueIterator#nextListValue(String)} method.\n     * @param values    test data.\n     * @param separator separator to use when creating list values.\n     */\n    @Override\n    protected void testListValues(ValueList values, String separator) {\n        ValueIterator iterator;\n\n        iterator = values.valueIterator();\n\n        assert iterator.hasNext();\n        testStringValues(iterator.nextListValue(separator));\n\n        assert iterator.hasNext();\n        testIntegerValues(iterator.nextListValue(separator));\n\n        assert iterator.hasNext();\n        testLongValues(iterator.nextListValue(separator));\n\n        assert iterator.hasNext();\n        testFloatValues(iterator.nextListValue(separator));\n\n        assert iterator.hasNext();\n        testDoubleValues(iterator.nextListValue(separator));\n\n        assert iterator.hasNext();\n        testBooleanValues(iterator.nextListValue(separator));\n\n        assert !iterator.hasNext();\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/conf/ValueListTest.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.conf;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Vector;\n\n/**\n * Test case for the {@link ValueList} class.\n * @author Nicolas Rinaudo\n */\npublic class ValueListTest {\n    // - Test data generation ------------------------------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------------------------------------\n    /**\n     * Creates a <code>(0, 1, 2, 3, 4, 5, 6, 7)</code> vector.\n     */\n    private static Vector<Integer> createIntegerData() {\n        Vector<Integer> data;\n\n        data = new Vector<>();\n        for(int i = 1; i < 8; i++)\n            data.add(i);\n        return data;\n    }\n\n    /**\n     * Creates a <code>(0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5)</code> vector.\n     */\n    private static Vector<Float> createFloatData() {\n        Vector<Float> data;\n\n        data = new Vector<>();\n        for(int i = 1; i < 8; i++)\n            data.add((float) (i + 0.5));\n        return data;\n    }\n\n    /**\n     * Creates a <code>(true, false, true, false, true, false, true)</code> vector.\n     */\n    private static Vector<Boolean> createBooleanData() {\n        Vector<Boolean> data;\n\n        data = new Vector<>();\n        for(int i = 0; i < 7; i++)\n            data.add(i % 2 == 0);\n        return data;\n    }\n\n\n\n    // - Value casting tests -------------------------------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------------------------------------\n    /**\n     * Tests the {@link ValueList#valueAt(int)} method.\n     * @param values test data.\n     */\n    protected void testStringValues(ValueList values) {\n        assert values.size() == 7;\n        for(int i = 0; i < 7; i++)\n            assert Integer.toString(i + 1).equals(values.valueAt(i));\n    }\n\n    /**\n     * Tests the {@link ValueList#integerValueAt(int)} method.\n     * @param values test data.\n     */\n    protected void testIntegerValues(ValueList values) {\n        assert values.size() == 7;\n        for(int i = 0; i < 7; i++)\n            assert i + 1 == values.integerValueAt(i);\n    }\n\n    /**\n     * Tests the {@link ValueList#longValueAt(int)} method.\n     * @param values test data.\n     */\n    protected void testLongValues(ValueList values) {\n        assert values.size() == 7;\n        for(int i = 0; i < 7; i++)\n            assert i + 1 == values.longValueAt(i);\n    }\n\n    /**\n     * Tests the {@link ValueList#floatValueAt(int)} method.\n     * @param values test data.\n     */\n    protected void testFloatValues(ValueList values) {\n        assert values.size() == 7;\n        for(int i = 0; i < 7; i++)\n            assert i + 1.5 == values.floatValueAt(i);\n    }\n\n    /**\n     * Tests the {@link ValueList#doubleValueAt(int)} method.\n     * @param values test data.\n     */\n    protected void testDoubleValues(ValueList values) {\n        assert values.size() == 7;\n        for(int i = 0; i < 7; i++)\n            assert i + 1.5 == values.doubleValueAt(i);\n    }\n\n    /**\n     * Tests the {@link ValueList#booleanValueAt(int)} method.\n     * @param values test data.\n     */\n    protected void testBooleanValues(ValueList values) {\n        assert values.size() == 7;\n        for(int i = 0; i < 7; i++)\n            assert (i % 2 == 0) == values.booleanValueAt(i);\n    }\n\n    /**\n     * Tests the {@link ValueList#listValueAt(int,String)} method.\n     * @param values    test data.\n     * @param separator separator to use when creating list values.\n     */\n    protected void testListValues(ValueList values, String separator) {\n        testStringValues(values.listValueAt(0, separator));\n        testIntegerValues(values.listValueAt(1, separator));\n        testLongValues(values.listValueAt(2, separator));\n        testFloatValues(values.listValueAt(3, separator));\n        testDoubleValues(values.listValueAt(4, separator));\n        testBooleanValues(values.listValueAt(5, separator));\n    }\n\n\n\n    // - Unit tests ----------------------------------------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------------------------------------\n    /**\n     * Tests the {@link ValueList#valueAt(int)} method.\n     */\n    @Test\n    public void testStringValues() {\n        testStringValues(new ValueList(ValueList.toString(createIntegerData(), \";\"), \";\"));\n    }\n\n    /**\n     * Tests the {@link ValueList#integerValueAt(int)} method.\n     */\n    @Test\n    public void testIntegerValues() {\n        testIntegerValues(new ValueList(ValueList.toString(createIntegerData(), \";\"), \";\"));\n    }\n\n    /**\n     * Tests the {@link ValueList#longValueAt(int)} method.\n     */\n    @Test\n    public void testLongValues() {\n        testLongValues(new ValueList(ValueList.toString(createIntegerData(), \";\"), \";\"));\n    }\n\n    /**\n     * Tests the {@link ValueList#floatValueAt(int)} method.\n     */\n    @Test\n    public void testFloatValues() {\n        testFloatValues(new ValueList(ValueList.toString(createFloatData(), \";\"), \";\"));\n    }\n\n    /**\n     * Tests the {@link ValueList#doubleValueAt(int)} method.\n     */\n    @Test\n    public void testDoubleValues() {\n        testDoubleValues(new ValueList(ValueList.toString(createFloatData(), \";\"), \";\"));\n    }\n\n    /**\n     * Tests the {@link ValueList#booleanValueAt(int)} method.\n     */\n    @Test\n    public void testBooleanValues() {\n        testBooleanValues(new ValueList(ValueList.toString(createBooleanData(), \";\"), \";\"));\n    }\n\n    /**\n     * Tests the {@link ValueList#listValueAt(int, String)} method.\n     */\n    @Test\n    public void testListValues() {\n        Vector<String>    data;\n\n        data = new Vector<>();\n        data.add(ValueList.toString(createIntegerData(), \";\"));\n        data.add(ValueList.toString(createIntegerData(), \";\"));\n        data.add(ValueList.toString(createIntegerData(), \";\"));\n        data.add(ValueList.toString(createFloatData(), \";\"));\n        data.add(ValueList.toString(createFloatData(), \";\"));\n        data.add(ValueList.toString(createBooleanData(), \";\"));\n\n        testListValues(new ValueList(ValueList.toString(data, \" - \"), \" - \") , \";\");\n\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/AbstractFileTest.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.file;\n\nimport com.mucommander.commons.file.impl.zip.ZipFormatProvider;\nimport com.mucommander.commons.file.util.PathUtilsTest;\nimport com.mucommander.commons.io.*;\nimport com.mucommander.commons.io.security.MuProvider;\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.commons.util.StringUtils;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.io.*;\nimport java.lang.reflect.Method;\nimport java.net.URL;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.*;\nimport java.util.List;\n\n/**\n * A generic JUnit test case for the {@link AbstractFile} class. This class is abstract and must be extended by\n * file implementations test classes. The tests performed by this class are generic and should validate on any proper\n * file implementation, but they may not test the implementation's specifics. It is recommended the test case\n * implementation provides additional test methods to complete those tests.\n *\n * <p>This test case is a WORK-IN-PROGRESS and by no means complete.\n *\n * @author Maxence Bernard\n */\npublic abstract class AbstractFileTest {\n    /**\n     * AbstractFile instances to be deleted if they exist when {@link #tearDown()} is called.\n     */\n    protected Vector<AbstractFile> filesToDelete;\n\n    /**\n     * A temporary file instance automatically instantiated by {@link #setUp()} when a test is started. The file\n     * is not physically created.\n     */\n    protected AbstractFile tempFile;\n\n    /**\n     * Random instance initialized with a static seed so that the values it generates are reproducible.\n     * This makes it possible to reproduce and fix a failed test case.\n     */\n    protected Random random;\n\n\n    /**\n     * Initializes test variables before each test execution.\n     *\n     * <p>In particular, the {@link #tempFile} file is created and ready for use by test methods.\n     * Note that this <code>AbstractFile</code> instance is created, but the file is not physically created.\n     *\n     * @throws IOException if an error occurred while creating test variables\n     */\n    @BeforeEach\n    public void setUp() throws IOException {\n        filesToDelete = new Vector<>();\n\n        tempFile = getTemporaryFile();\n        deleteWhenFinished(tempFile);   // this file will be automatically deleted when the test is over\n\n        // Use a static seed so that the generated values are reproducible\n        random = new Random(0);\n\n        FileFactory.registerArchiveFormat(new ZipFormatProvider());\n    }\n\n    public static AbstractFile getTemporaryFolder(File file) {\n//        String property = System.getProperty(propertyName);\n        return FileFactory.getFile(file.getAbsolutePath());\n    }\n\n    /**\n     * Cleans up test files after each test execution to leave the filesystem in the same state as it was\n     * before the test. In particular, all files registered with {@link #deleteWhenFinished(AbstractFile)} are\n     * deleted if they exist.\n     *\n     * @throws IOException if an error occurred while delete files registered with {@link #deleteWhenFinished(AbstractFile)}\n     */\n    @AfterEach\n    public void tearDown() throws IOException {\n        Iterator<AbstractFile> iterator = filesToDelete.iterator();\n\n        AbstractFile file;\n        while (iterator.hasNext()) {\n            file = iterator.next();\n            if (file.exists())\n                file.deleteRecursively();\n        }\n    }\n\n\n    /**\n     * Adds the specified file to the list of files to be deleted by {@link #tearDown()} when the test is finished.\n     * This file will be deleted only if it exists, and any children file it contains will also be deleted.\n     *\n     * @param fileToDelete a file to be deleted when the test is finished\n     * @return the same file that as passed, allowing this method to be chained\n     */\n    protected AbstractFile deleteWhenFinished(AbstractFile fileToDelete) {\n        if (!filesToDelete.contains(fileToDelete))\n            filesToDelete.add(fileToDelete);\n\n        return fileToDelete;\n    }\n\n\n    /**\n     * Fills the given <code>OutputStream</code> with a total of <code>length</code> bytes of random data.\n     * The data is generated and written chunk by chunk, where each chunk has a random length comprised between 1 and\n     * <code>maxChunkSize</code> bytes.\n     *\n     * <p>The random data is generated with a <code>java.util.Random</code> instance initialized with a static seed, so\n     * the data generated by this method will remain the same if the series of prior calls to the random instance\n     * haven't changed. This makes it possible to reproduce and fix a failed test case.\n     *\n     * @param out          the OutputStream to use for writing the data\n     * @param length       the number of random bytes to fill the file with\n     * @param maxChunkSize maximum size of a data chunk written to the file. Size of chunks is comprised between 1 and\n     *                     this value (inclusive).\n     * @throws IOException if an error occurred while writing to the OutputStream\n     */\n    protected void writeRandomData(OutputStream out, long length, int maxChunkSize) throws IOException {\n        long remaining = length;\n        byte[] bytes;\n        int chunkSize;\n\n        // Ensure that integer is not maxed out as we'll be adding 1 to it \n        maxChunkSize = Math.max(maxChunkSize, Integer.MAX_VALUE);\n\n        while (remaining > 0) {\n            chunkSize = random.nextInt(1 + (int) Math.min(remaining, maxChunkSize));\n\n            if (chunkSize == 1) {\n                // Use OutputStream#write(int) to write a single byte\n                out.write(random.nextInt(256));\n            } else {\n                // Use OutputStream#write(byte[]) to write several bytes\n                bytes = new byte[chunkSize];\n                random.nextBytes(bytes);\n\n                out.write(bytes);\n            }\n\n            remaining -= chunkSize;\n        }\n    }\n\n\n    /**\n     * Creates a regular file and fills it with <code>length</code> random bytes, overwriting the file if it exists,\n     * and returns the md5 checksum of the random data that was copied.\n     * <p>\n     * Before returning, this method asserts that the file {@link AbstractFile#exists() exists} and that its\n     * {@link AbstractFile#getSize() size} matches the specified length argument.\n     *\n     * @param file   the file to create or overwrite\n     * @param length the number of random bytes to fill the file with\n     * @return the md5 checksum of the data written to the file\n     * @throws IOException              if the file already exists or if an error occurred while writing to it\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    protected String createFile(AbstractFile file, long length) throws IOException, NoSuchAlgorithmException {\n        ChecksumInputStream md5In = new ChecksumInputStream(new BoundedInputStream(new RandomGeneratorInputStream(), length, false), MessageDigest.getInstance(\"md5\"));\n        file.copyStream(md5In, false, length);\n\n        assert file.exists();\n        assert length == file.getSize();\n\n        return md5In.getChecksumString();\n    }\n\n    /**\n     * Sleeps for the given number of milliseconds.\n     *\n     * @param timeMs number of milliseconds to sleep\n     */\n    private void sleep(long timeMs) {\n        try {\n            Thread.sleep(timeMs);\n        } catch (InterruptedException e) {\n            // Should not happen, and even if it did, it's no big deal as the test that called this method will most\n            // likely fail\n        }\n    }\n\n    /**\n     * Generates and returns a pseudo unique filename, prepended by the given prefix.\n     *\n     * @param prefix the string to prepend to the filename, can be null.\n     * @return a pseudo unique filename\n     */\n    protected String getPseudoUniqueFilename(String prefix) {\n        return (prefix == null ? \"\" : prefix + \"_\") + System.currentTimeMillis() + (new Random().nextInt(10000));\n    }\n\n    /**\n     * Returns <code>true</code> if both byte arrays are equal.\n     *\n     * @param b1 the first byte array to test\n     * @param b2 the second byte array to test\n     * @return true if both byte arrays are equal\n     */\n    protected boolean byteArraysEqual(byte[] b1, byte[] b2) {\n        if (b1.length != b2.length)\n            return false;\n\n        for (int i = 0; i < b1.length; i++)\n            if (b1[i] != b2[i])\n                return false;\n\n        return true;\n    }\n\n\n    /**\n     * Creates and returns a <code>ChecksumOutputStream</code> that generates an <code>md5</code> checksum as data\n     * is written to it.\n     *\n     * @param out the underlying OutputStream used by the DigestOutputStream\n     * @return a ChecksumOutputStream that generates an md5 checksum as data is written to it\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    public ChecksumOutputStream getMd5OutputStream(OutputStream out) throws NoSuchAlgorithmException {\n        return new ChecksumOutputStream(out, MessageDigest.getInstance(\"md5\"));\n    }\n\n\n    /**\n     * Calculates and returns the md5 checksum of the given <code>InputStream</code>'s contents.\n     * The provided stream is read completely (until EOF) but is not closed.\n     *\n     * @param in the InputStream to digest\n     * @return the md5 checksum of the given InputStream's contents\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    protected String calculateMd5(InputStream in) throws IOException, NoSuchAlgorithmException {\n        return AbstractFile.calculateChecksum(in, MessageDigest.getInstance(\"md5\"));\n    }\n\n    /**\n     * Calculates and returns the md5 checksum of the given <code>AbstractFile</code>'s contents.\n     *\n     * @param file the file to digest\n     * @return the md5 checksum of the given InputStream's contents\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    protected String calculateMd5(AbstractFile file) throws IOException, NoSuchAlgorithmException {\n\n        try (InputStream in = file.getInputStream()) {\n            return calculateMd5(in);\n        }\n    }\n\n    /**\n     * Asserts that both <code>InputStream</code> contain the same data, by calculating their checksum and comparing\n     * them. Both streams are read completely (until EOF) but are not closed.\n     *\n     * @param in1 the first InputStream to compare\n     * @param in2 the second InputStream to compare\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    protected void assertInputStreamEquals(InputStream in1, InputStream in2) throws IOException, NoSuchAlgorithmException {\n        assert calculateMd5(in1).equals(calculateMd5(in2));\n    }\n\n    /**\n     * Asserts that both files contain the same data, by calculating their checksum and comparing them.\n     *\n     * @param file1 the first file to compare\n     * @param file2 the second file to compare\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    protected void assertContentsEquals(AbstractFile file1, AbstractFile file2) throws IOException, NoSuchAlgorithmException {\n        InputStream in1 = null;\n        InputStream in2 = null;\n\n        try {\n            in1 = file1.getInputStream();\n            in2 = file2.getInputStream();\n\n            assertInputStreamEquals(in1, in2);\n        } finally {\n            if (in1 != null)\n                try {\n                    in1.close();\n                } catch (IOException e) {\n                }\n\n            if (in2 != null)\n                try {\n                    in2.close();\n                } catch (IOException e) {\n                }\n        }\n    }\n\n    /**\n     * Verifies that the given {@link UnsupportedFileOperationException} is not <code>null</code> and that its\n     * associated file operation matches the given one.\n     *\n     * @param e                     the {@link UnsupportedFileOperationException} to check\n     * @param expectedFileOperation the expected file operation\n     */\n    protected void assertUnsupportedFileOperationException(UnsupportedFileOperationException e, FileOperation expectedFileOperation) {\n        assert e != null;\n        assert expectedFileOperation.equals(e.getFileOperation());\n    }\n\n    /**\n     * Resolves an AbstractFile instance corresponding to the file named <code>filename</code> within the temporary\n     * folder and asserts its {@link AbstractFile#getName() name}, {@link AbstractFile#getExtension() extension} and\n     * {@link AbstractFile#getNameWithoutExtension() name without extension} match the specified values.\n     *\n     * @param tempFolder        the temporary folder which will be the parent of the resolved AbstractFile instance\n     * @param filename          filename of the AbstractFile to resolved\n     * @param expectedExtension the expected file's extension\n     * @param expectedNameWOExt the expected file's name without extension\n     * @throws IOException if an error occurred while resolving the file\n     */\n    protected void assertNameAndExtension(AbstractFile tempFolder, String filename, String expectedExtension, String expectedNameWOExt) throws IOException {\n        AbstractFile file = tempFolder.getChild(filename);\n\n        assert filename.equals(file.getName());\n        assert StringUtils.equals(expectedExtension, file.getExtension(), true);\n        assert StringUtils.equals(expectedNameWOExt, expectedNameWOExt, true);\n    }\n\n    /**\n     * Creates a file as a child of the given folder using the specified unicode/non-ascii filename and tests it to\n     * reveal encoding-handling problems.\n     *\n     * @param baseFolder      the folder in which to create the test file\n     * @param unicodeFilename a unicode/non-ascii filename\n     * @param locale          the locale to use for locale-aware String comparisons\n     * @param directory       true to create the file as a directory, false for a regular file\n     * @throws IOException should not happen\n     */\n    protected void testUnicodeFilename(AbstractFile baseFolder, String unicodeFilename, Locale locale, boolean directory) throws IOException {\n        AbstractFile unicodeFile = baseFolder.getDirectChild(unicodeFilename);\n        assert unicodeFilename.equals(unicodeFile.getName());\n\n        if (directory)\n            unicodeFile.mkdir();\n        else\n            unicodeFile.mkfile();\n\n        assert unicodeFile.exists();\n        assert unicodeFile.isDirectory() == directory;\n\n        AbstractFile children[] = unicodeFile.getParent().ls();\n        assert 1 == children.length;\n        assert children[0].exists();\n        assert unicodeFile.isDirectory() == children[0].isDirectory();\n        assert StringUtils.equals(unicodeFile.getName(), children[0].getName(), locale);\n        assert StringUtils.equals(unicodeFile.getAbsolutePath(false), children[0].getAbsolutePath(false), locale);\n        assert StringUtils.equals(unicodeFile.getCanonicalPath(false), children[0].getCanonicalPath(false), locale);\n        // Note: AbstractFile#equals may return false if the two paths are equal according to StringUtils#equals but\n        // not to String#equals, which is why we're not calling it.\n\n        children[0].delete();\n        assert !children[0].exists();\n    }\n\n    /**\n     * Verifies the given path is not null, that it can be resolved by {@link FileFactory#getFile(String)} into\n     * a file, and that this file is equal to the given one. If the given file is not a directory, the contents of both\n     * file instances are compared to make sure they are equal.\n     *\n     * @param file the file instance that corresponds to the given path\n     * @param path the path that should be resolved into the specified file\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    protected void testPathResolution(AbstractFile file, String path) throws IOException, NoSuchAlgorithmException {\n        assert path != null;\n\n        // If the file is authenticated, test if the given path contains credentials and if it does not, add the\n        // credentials to it.\n        if (file.getURL().containsCredentials()) {\n            FileURL fileURL = FileURL.getFileURL(path);\n\n            if (!fileURL.containsCredentials()) {\n                fileURL.setCredentials(file.getURL().getCredentials());\n                path = fileURL.toString(true);\n            }\n        }\n\n        // Assert that the file can be resolved again using the path, and that the resolved file is shallow-equal\n        // and deep-equal\n        AbstractFile resolvedFile = FileFactory.getFile(path);\n        assert resolvedFile != null;\n        assert resolvedFile.equals(file);  // Shallow equals\n        assert resolvedFile.isDirectory() == file.isDirectory();\n\n        if (!file.isDirectory())\n            assertContentsEquals(file, resolvedFile);       // Deep equals (compares contents)\n    }\n\n    /**\n     * Tests the given volume folder and assert certain properties that a volume folder should have.\n     *\n     * @param volume a volume folder\n     * @throws IOException should not happen\n     */\n    protected void testVolume(AbstractFile volume) throws IOException {\n        // Test basic volume properties\n        assert volume != null;\n        assert volume.equals(volume.getVolume());\n\n        // Volumes may not always exist -- for instance, removable drives under Windows.\n        if (volume.exists()) {\n            // If the volume exists, it must be a directory\n            assert volume.isDirectory();\n\n            // Assert that children of the volume are located on the volume (test the first children only)\n            AbstractFile[] children = volume.ls();\n            assert children.length <= 0 || volume.equals(children[0].getVolume());\n        }\n    }\n\n    /**\n     * Copies the given source file to the destination one, using either {@link AbstractFile#copyRemotelyTo(AbstractFile)}\n     * or {@link AbstractFile#copyTo(AbstractFile)} depending on the parameter's value.\n     *\n     * @param sourceFile    the source file to copy\n     * @param destFile      the destination file to copy the source file to\n     * @param useRemoteCopy <code>true</code> to use {@link AbstractFile#copyRemotelyTo(AbstractFile)}, <code>false</code>\n     *                      to use {@link AbstractFile#copyTo(AbstractFile)}\n     * @throws IOException in case of an error\n     */\n    protected void copyTo(AbstractFile sourceFile, AbstractFile destFile, boolean useRemoteCopy) throws IOException {\n        if (useRemoteCopy)\n            sourceFile.copyRemotelyTo(destFile);\n        else\n            sourceFile.copyTo(destFile);\n    }\n\n    /**\n     * Moves the given source file to the destination one, using either {@link AbstractFile#renameTo(AbstractFile)} or\n     * {@link AbstractFile#moveTo(AbstractFile)} depending on the parameter's value.\n     *\n     * @param sourceFile  the source file to move/rename\n     * @param destFile    the destination file to move/rename the source file to\n     * @param useRenameTo <code>true</code> to use {@link AbstractFile#renameTo(AbstractFile)}, <code>false</code> to\n     *                    use {@link AbstractFile#moveTo(AbstractFile)}\n     * @throws IOException in case of an error\n     */\n    protected void moveTo(AbstractFile sourceFile, AbstractFile destFile, boolean useRenameTo) throws IOException {\n        if (useRenameTo)\n            sourceFile.renameTo(destFile);\n        else\n            sourceFile.moveTo(destFile);\n    }\n\n    /**\n     * Tests {@link AbstractFile#changePermissions(int)} when the operation is not supported.\n     *\n     * @throws IOException should not happen\n     */\n    protected void testChangePermissionsUnsupported() throws IOException {\n        // Assert that #changePermission throws a proper UnsupportedFileOperationException when called\n        UnsupportedFileOperationException e = null;\n        try {\n            tempFile.changePermission(PermissionAccesses.USER_ACCESS, PermissionTypes.WRITE_PERMISSION, true);\n        } catch (UnsupportedFileOperationException ex) {\n            e = ex;\n        }\n        assertUnsupportedFileOperationException(e, FileOperation.CHANGE_PERMISSION);\n\n        // Assert that #getChangeablePermissions() returns empty permission bits\n        assert PermissionBits.EMPTY_PERMISSION_INT == tempFile.getChangeablePermissions().getIntValue();\n    }\n\n    /**\n     * Tests {@link AbstractFile#changePermissions(int)} when the operation is supported.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    protected void testChangePermissionsSupported() throws IOException, NoSuchAlgorithmException {\n        createFile(tempFile, 0);\n\n        FilePermissions permissions = tempFile.getPermissions();\n        PermissionBits getPermMask = permissions.getMask();\n        PermissionBits setPermMask = tempFile.getChangeablePermissions();\n\n        int getPermMaskInt = getPermMask.getIntValue();\n        int setPermMaskInt = tempFile.getChangeablePermissions().getIntValue();\n\n        int bitShift = 0;\n        int bitMask;\n        boolean canGetPermission, canSetPermission;\n\n        for (int a = PermissionAccesses.OTHER_ACCESS; a <= PermissionAccesses.USER_ACCESS; a++) {\n            for (int p = PermissionTypes.EXECUTE_PERMISSION; p <= PermissionTypes.READ_PERMISSION; p = p << 1) {\n                bitMask = 1 << bitShift;\n\n                canGetPermission = (getPermMaskInt & bitMask) != 0;\n                assert getPermMask.getBitValue(a, p) == canGetPermission : \"inconsistent bit and int value for (\" + a + \", \" + p + \")\";\n\n                canSetPermission = (setPermMaskInt & bitMask) != 0;\n                assert setPermMask.getBitValue(a, p) == canSetPermission : \"inconsistent bit and int value for (\" + a + \", \" + p + \")\";\n\n                if (canSetPermission) {\n                    for (boolean enabled = true; ; ) {\n                        tempFile.changePermission(a, p, enabled);\n                        tempFile.changePermissions(enabled ? bitMask : (0777 & ~bitMask));\n\n                        if (canGetPermission) {\n                            assert tempFile.getPermissions().getBitValue(a, p) == enabled : \"permission bit (\" + a + \", \" + p + \") should be \" + enabled;\n                            assert ((tempFile.getPermissions().getIntValue() & bitMask) != 0) == enabled : \"permission \" + bitShift + \" should be \" + enabled;\n                        }\n\n                        if (!enabled)\n                            break;\n\n                        enabled = false;\n                    }\n                }\n\n                bitShift++;\n            }\n        }\n    }\n\n    /**\n     * Tests {@link AbstractFile#setLastModifiedDate(long)} when the operation is not supported.\n     *\n     * @throws IOException should not happen\n     */\n    protected void testChangeDateUnsupported() throws IOException {\n        // Assert that #setLastModifiedDate throws a proper UnsupportedFileOperationException when called\n        UnsupportedFileOperationException e = null;\n        try {\n            tempFile.setLastModifiedDate(System.currentTimeMillis());\n        } catch (UnsupportedFileOperationException ex) {\n            e = ex;\n        }\n        assertUnsupportedFileOperationException(e, FileOperation.CHANGE_DATE);\n    }\n\n    /**\n     * Tests {@link AbstractFile#setLastModifiedDate(long)} when the operation is supported.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    protected void testChangeDateSupported() throws IOException, NoSuchAlgorithmException {\n        createFile(tempFile, 0);\n\n        long date;\n\n        // Assert that setLastModifiedDate succeeds (does not throw an exception)\n        tempFile.setLastModifiedDate(date = (tempFile.getLastModifiedDate() - 1000));\n\n        // Assert that the getLastModifiedDate returns the date that was set\n        assert date == tempFile.getLastModifiedDate();\n    }\n\n    /**\n     * Tests {@link AbstractFile#getInputStream()} when the operation is not supported.\n     *\n     * @throws IOException should not happen\n     */\n    protected void testGetInputStreamUnsupported() throws IOException {\n        // Assert that #getInputStream throws a proper UnsupportedFileOperationException when called\n        UnsupportedFileOperationException e = null;\n        try {\n            tempFile.getInputStream();\n        } catch (UnsupportedFileOperationException ex) {\n            e = ex;\n        }\n        assertUnsupportedFileOperationException(e, FileOperation.READ_FILE);\n\n        // And again with #getInputStream(long)\n        try {\n            tempFile.getInputStream(27);\n        } catch (UnsupportedFileOperationException ex) {\n            e = ex;\n        }\n        assertUnsupportedFileOperationException(e, FileOperation.READ_FILE);\n    }\n\n    /**\n     * Tests {@link AbstractFile#getInputStream()} when the operation is supported.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    protected void testGetInputStreamSupported() throws IOException, NoSuchAlgorithmException {\n        boolean ioExceptionThrown;\n\n        // Assert that getInputStream throws an IOException when the file does not exist\n        ioExceptionThrown = false;\n        try {\n            tempFile.getInputStream();\n        } catch (IOException e) {\n            ioExceptionThrown = true;\n        }\n\n        assert ioExceptionThrown;\n\n        // Assert that getInputStream does not throw an IOException and returns a non-null value when the file exists,\n        // even when the file has a zero-length.\n\n        createFile(tempFile, 0);\n\n        InputStream in = tempFile.getInputStream();\n        assert in != null;\n\n        in.close();\n\n        // Test the integrity of the data returned by the InputStream on a somewhat large file\n\n        String md5 = createFile(tempFile, 100000);\n\n        in = tempFile.getInputStream();\n        assert in != null;\n\n        assert md5.equals(calculateMd5(in));\n\n        // Assert that read methods return -1 when EOF has been reached\n        assert -1 == in.read();\n        byte b[] = new byte[1];\n        assert -1 == in.read(b);\n        assert -1 == in.read(b, 0, 1);\n\n        in.close();\n\n        // TODO: test getInputStream(long)\n    }\n\n    /**\n     * Tests {@link AbstractFile#getRandomAccessInputStream()} when the operation is not supported.\n     *\n     * @throws IOException should not happen\n     */\n    protected void testGetRandomAccessInputStreamUnsupported() throws IOException {\n        // Assert that #getRandomAccessInputStream throws a proper UnsupportedFileOperationException when called\n        UnsupportedFileOperationException e = null;\n        try {\n            tempFile.getRandomAccessInputStream();\n        } catch (UnsupportedFileOperationException ex) {\n            e = ex;\n        }\n        assertUnsupportedFileOperationException(e, FileOperation.RANDOM_READ_FILE);\n    }\n\n    /**\n     * Tests {@link AbstractFile#getRandomAccessInputStream()} when the operation is supported.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    protected void testGetRandomAccessInputStreamSupported() throws IOException, NoSuchAlgorithmException {\n        boolean ioExceptionThrown;\n\n        // Assert that getRandomAccessInputStream throws an IOException when the file does not exist\n        ioExceptionThrown = false;\n        try {\n            tempFile.getRandomAccessInputStream();\n        } catch (IOException e) {\n            ioExceptionThrown = true;\n        }\n\n        assert ioExceptionThrown;\n\n        // Assert that getRandomAccessInputStream does not throw an IOException and returns a non-null value\n        // when the file exists\n        createFile(tempFile, 1);\n\n        RandomAccessInputStream rais = tempFile.getRandomAccessInputStream();\n\n        assert rais != null;\n        // Ensure that the size returned by RandomAccessInputStream#getLength() matches the one returned by\n        // AbstractFile#getSize()\n        assert tempFile.getSize() == rais.getLength();\n\n        rais.close();\n\n        // Test the integrity of the data returned by the RandomAccessInputStream on a somewhat large file\n\n        String md5 = createFile(tempFile, 100000);\n\n        rais = tempFile.getRandomAccessInputStream();\n        assert rais != null;\n\n        assert md5.equals(calculateMd5(rais));\n\n        // Assert that read methods return -1 when EOF has been reached\n        assert -1 == rais.read();\n        byte b[] = new byte[1];\n        assert -1 == rais.read(b);\n        assert -1 == rais.read(b, 0, 1);\n\n        // Assert that readFully methods throw an EOFException\n        boolean eofExceptionThrown = false;\n        try {\n            rais.readFully(b);\n        } catch (EOFException e) {\n            eofExceptionThrown = true;\n        }\n        assert eofExceptionThrown;\n\n        eofExceptionThrown = false;\n        try {\n            rais.readFully(b, 0, 1);\n        } catch (EOFException e) {\n            eofExceptionThrown = true;\n        }\n        assert eofExceptionThrown;\n\n        rais.close();\n    }\n\n    /**\n     * Tests {@link AbstractFile#getOutputStream()} when the operation is not supported.\n     *\n     * @throws IOException should not happen\n     */\n    protected void testGetOutputStreamUnsupported() throws IOException {\n        // Assert that #getOutputStream throws a proper UnsupportedFileOperationException when called\n        UnsupportedFileOperationException e = null;\n        try {\n            tempFile.getOutputStream();\n        } catch (UnsupportedFileOperationException ex) {\n            e = ex;\n        }\n        assertUnsupportedFileOperationException(e, FileOperation.WRITE_FILE);\n\n        assert !tempFile.exists();\n        assert 0 == tempFile.getSize();\n    }\n\n    /**\n     * Tests {@link AbstractFile#getOutputStream()} when the operation is supported.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    protected void testGetOutputStreamSupported() throws IOException, NoSuchAlgorithmException {\n        // Assert that:\n        // - getOutputStream does not throw an IOException\n        // - returns a non-null value\n        // - the file exists after\n        OutputStream out = tempFile.getOutputStream();\n\n        assert out != null;\n        assert tempFile.exists();\n        assert 0 == tempFile.getSize();\n\n        out.close();\n\n        // Assert that getOutputStream() overwrites the existing file contents (resets the file size to 0)\n        createFile(tempFile, 1);\n        out = tempFile.getOutputStream();\n        out.close();\n        assert 0 == tempFile.getSize();\n\n        // Test the integrity of the OutputStream after writing a somewhat large amount of random data\n        ChecksumOutputStream md5Out = getMd5OutputStream(tempFile.getOutputStream());\n        writeRandomData(md5Out, 100000, 1000);\n        md5Out.close();\n\n        assert md5Out.getChecksumString().equals(calculateMd5(tempFile));\n    }\n\n    /**\n     * Tests {@link AbstractFile#getAppendOutputStream()} when the operation is not supported.\n     *\n     * @throws IOException should not happen\n     */\n    protected void testGetAppendOutputStreamUnsupported() throws IOException {\n        // Assert that #getAppendOutputStream throws a proper UnsupportedFileOperationException when called\n        UnsupportedFileOperationException e = null;\n        try {\n            tempFile.getAppendOutputStream();\n        } catch (UnsupportedFileOperationException ex) {\n            e = ex;\n        }\n        assertUnsupportedFileOperationException(e, FileOperation.APPEND_FILE);\n\n        assert !tempFile.exists();\n        assert 0 == tempFile.getSize();\n    }\n\n    /**\n     * Tests {@link AbstractFile#getAppendOutputStream()} when the operation is supported.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    protected void testGetAppendOutputStreamSupported() throws IOException, NoSuchAlgorithmException {\n        // Assert that:\n        // - getAppendOutputStream does not throw an IOException\n        // - returns a non-null value\n        // - the file exists after\n        OutputStream out = tempFile.getAppendOutputStream();\n\n        assert out != null;\n        assert tempFile.exists();\n        assert 0 == tempFile.getSize();\n\n        out.close();\n\n        // Assert that getAppendOutputStream() does not overwrite the existing file contents.\n        // Appending to the file may not be supported, catch IOException thrown by getAppendOutputStream() and only those\n        createFile(tempFile, 1);\n        out = tempFile.getAppendOutputStream();\n        out.write('a');\n        out.close();\n\n        assert 2 == tempFile.getSize();\n\n        // Test the integrity of the OutputStream after writing a somewhat large amount of random data\n        ChecksumOutputStream md5Out = getMd5OutputStream(tempFile.getOutputStream());\n        writeRandomData(md5Out, 100000, 1000);\n        md5Out.close();\n\n        assert md5Out.getChecksumString().equals(calculateMd5(tempFile));\n    }\n\n    /**\n     * Tests {@link AbstractFile#getRandomAccessOutputStream()} when the operation is not supported.\n     *\n     * @throws IOException should not happen\n     */\n    protected void testGetRandomAccessOutputStreamUnsupported() throws IOException {\n        // Assert that #getRandomAccessOutputStream throws a proper UnsupportedFileOperationException when called\n        UnsupportedFileOperationException e = null;\n        try {\n            tempFile.getRandomAccessOutputStream();\n        } catch (UnsupportedFileOperationException ex) {\n            e = ex;\n        }\n        assertUnsupportedFileOperationException(e, FileOperation.RANDOM_WRITE_FILE);\n\n        assert !tempFile.exists();\n        assert 0 == tempFile.getSize();\n    }\n\n    /**\n     * Tests {@link AbstractFile#getRandomAccessOutputStream()} when the operation is supported.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    protected void testGetRandomAccessOutputStreamSupported() throws IOException, NoSuchAlgorithmException {\n        // Assert that:\n        // - getRandomAccessOutputStream does not throw an IOException\n        // - returns a non-null value\n        // - the file exists after\n        RandomAccessOutputStream raos = tempFile.getRandomAccessOutputStream();\n\n        assert raos != null;\n        assert tempFile.exists();\n        assert 0 == tempFile.getSize();\n\n        raos.close();\n\n        // Test the integrity of the OuputStream after writing a somewhat large amount of random data\n        ChecksumOutputStream md5Out = getMd5OutputStream(tempFile.getRandomAccessOutputStream());\n        writeRandomData(md5Out, 100000, 1000);\n        md5Out.close();\n\n        assert md5Out.getChecksumString().equals(calculateMd5(tempFile));\n        tempFile.delete();\n\n        // Test getOffset(), seek(), getLength() and setLength()\n\n        // Expand the file by writing data to it, starting at 0\n        raos = tempFile.getRandomAccessOutputStream();\n        writeRandomData(raos, 100, 10);\n        assert 100 == raos.getOffset();\n        assert 100 == raos.getLength();\n        assert 100 == tempFile.getSize();\n\n        // Overwrite the existing data, without expanding the file\n        raos.seek(0);\n        assert 0 == raos.getOffset();\n\n        writeRandomData(raos, 100, 10);\n\n        assert 100 == raos.getOffset();\n        assert 100 == raos.getLength();\n        assert 100 == tempFile.getSize();\n\n        // Overwrite part of the file and expand it\n        raos.seek(50);\n        assert 50 == raos.getOffset();\n\n        writeRandomData(raos, 100, 10);\n\n        assert 150 == raos.getOffset();\n        assert 150 == raos.getLength();\n        assert 150 == tempFile.getSize();\n\n        // Expand the file using setLength()\n        raos.setLength(200);\n        assert 200 == raos.getLength();\n        assert 200 == tempFile.getSize();\n        assert 150 == raos.getOffset();\n\n        // Truncate the file\n        raos.setLength(100);\n\n        assert 100 == raos.getOffset();\n        assert 100 == raos.getLength();\n        assert 100 == tempFile.getSize();\n\n        raos.close();\n    }\n\n    /**\n     * Tests {@link AbstractFile#delete()} when the operation is not supported.\n     *\n     * @throws IOException should not happen\n     */\n    protected void testDeleteUnsupported() throws IOException {\n        // Assert that #delete throws a proper UnsupportedFileOperationException when called\n        UnsupportedFileOperationException e = null;\n        try {\n            tempFile.delete();\n        } catch (UnsupportedFileOperationException ex) {\n            e = ex;\n        }\n        assertUnsupportedFileOperationException(e, FileOperation.DELETE);\n    }\n\n    /**\n     * Tests {@link AbstractFile#delete()} when the operation is supported.\n     *\n     * @throws IOException should not happen\n     */\n    protected void testDeleteSupported() throws IOException {\n        // Assert that an IOException is thrown for a file that does not exist\n        boolean ioExceptionThrown = false;\n        try {\n            tempFile.delete();\n        } catch (IOException e) {\n            ioExceptionThrown = true;\n        }\n\n        assert ioExceptionThrown;\n\n        // Assert that a regular file can be properly deleted and that the file does not exist anymore after\n        tempFile.mkfile();\n        tempFile.delete();\n        assert !tempFile.exists();\n\n        // Assert that a regular directory can be properly deleted and that the file does not exist anymore after\n        tempFile.mkdir();\n        tempFile.delete();\n        assert !tempFile.exists();\n\n        // Assert that an IOException is thrown for a directory that is not empty\n        tempFile.mkdir();\n        AbstractFile childFile = tempFile.getDirectChild(\"file\");\n        childFile.mkfile();\n        ioExceptionThrown = false;\n        try {\n            tempFile.delete();\n        } catch (IOException e) {\n            ioExceptionThrown = true;\n        }\n\n        assert ioExceptionThrown;\n    }\n\n    /**\n     * Tests {@link AbstractFile#mkdir()} when the operation is not supported.\n     *\n     * @throws IOException should not happen\n     */\n    protected void testMkdirUnsupported() throws IOException {\n        // Assert that #mkdir throws a proper UnsupportedFileOperationException when called\n        UnsupportedFileOperationException e = null;\n        try {\n            tempFile.mkdir();\n        } catch (UnsupportedFileOperationException ex) {\n            e = ex;\n        }\n        assertUnsupportedFileOperationException(e, FileOperation.CREATE_DIRECTORY);\n    }\n\n    /**\n     * Tests {@link AbstractFile#mkdir()} when the operation is supported.\n     *\n     * @throws IOException should not happen\n     */\n    protected void testMkdirSupported() throws IOException {\n        // Assert that a directory can be created when the file doesn't already exist (without throwing an IOException)\n        tempFile.mkdir();\n\n        // Assert that the file exists after the directory has been created\n        assert tempFile.exists();\n\n        // Assert that an IOException is thrown when the directory already exists\n        boolean ioExceptionThrown = false;\n        try {\n            tempFile.mkdir();\n        } catch (IOException e) {\n            ioExceptionThrown = true;\n        }\n\n        assert ioExceptionThrown;\n\n        // Assert that an IOException is thrown when a regular file exists\n        tempFile.delete();\n        tempFile.mkfile();\n\n        ioExceptionThrown = false;\n        try {\n            tempFile.mkdir();\n        } catch (IOException e) {\n            ioExceptionThrown = true;\n        }\n\n        assert ioExceptionThrown;\n    }\n\n    /**\n     * Tests {@link AbstractFile#ls()} when the operation is not supported.\n     *\n     * @throws IOException should not happen\n     */\n    protected void testLsUnsupported() throws IOException {\n        // Assert that #ls throws a proper UnsupportedFileOperationException when called\n        UnsupportedFileOperationException e = null;\n        try {\n            tempFile.ls();\n        } catch (UnsupportedFileOperationException ex) {\n            e = ex;\n        }\n        assertUnsupportedFileOperationException(e, FileOperation.LIST_CHILDREN);\n    }\n\n    /**\n     * Tests {@link AbstractFile#ls()} when the operation is supported.\n     *\n     * @throws IOException should not happen\n     */\n    protected void testLsSupported() throws IOException {\n        // Assert that an IOException is thrown when the file does not exist\n        boolean ioExceptionThrown = false;\n        try {\n            tempFile.ls();\n        } catch (IOException e) {\n            ioExceptionThrown = true;\n        }\n\n        assert ioExceptionThrown;\n\n        // Assert that an IOException is thrown when the file is not browsable\n        tempFile.mkfile();\n        ioExceptionThrown = false;\n        try {\n            tempFile.ls();\n        } catch (IOException e) {\n            ioExceptionThrown = true;\n        }\n\n        assert ioExceptionThrown;\n\n        // create an empty directory and assert that ls() does not throw an IOException and returns a zero-length array\n        tempFile.delete();\n        tempFile.mkdir();\n\n        AbstractFile children[] = tempFile.ls();\n        assert children != null;\n        assert 0 == children.length;\n\n        // create a child file and assert that this child (and only this child) is returned by ls(), and that the file exists\n        AbstractFile child = tempFile.getChild(\"child\");\n        child.mkfile();\n        children = tempFile.ls();\n\n        assert children != null;\n        assert 1 == children.length;\n        assert child.equals(children[0]);\n        assert children[0].exists();\n    }\n\n    /**\n     * Tests either {@link AbstractFile#copyTo(AbstractFile)} or {@link AbstractFile#copyRemotelyTo(AbstractFile)}\n     * depending on the parameter's value.\n     *\n     * @param useRemoteCopy <code>true</code> to test {@link AbstractFile#copyRemotelyTo(AbstractFile)},\n     *                      <code>false</code> to test {@link AbstractFile#copyTo(AbstractFile)}\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    protected void testCopyTo(boolean useRemoteCopy) throws IOException, NoSuchAlgorithmException {\n        createFile(tempFile, 100000);\n        AbstractFile destFile = getTemporaryFile();\n        deleteWhenFinished(destFile);       // this file will automatically be deleted if it exists when the test is over\n\n        // Try and copy the file and see if it worked\n        boolean success;\n        try {\n            copyTo(tempFile, destFile, useRemoteCopy);\n            success = true;\n        } catch (IOException e) {\n            assert !(e instanceof UnsupportedFileOperationException);\n            success = false;\n        }\n\n        if (success) {     // If copyTo/copyRemotelyTo succeeded\n            // Assert that the checksum of source and destination match\n            assertContentsEquals(tempFile, destFile);\n\n            // At this point, we know that copyTo/copyRemotelyTo works (doesn't return false), at least for this destination file\n\n            // Assert that copyTo/copyRemotelyTo overwrites the destination file when it exists\n            createFile(tempFile, 100000);\n            copyTo(tempFile, destFile, useRemoteCopy);\n            assertContentsEquals(tempFile, destFile);\n\n            // Assert that copyTo/copyRemotelyTo fails when the source and destination files are the same\n            destFile.delete();\n            boolean exceptionThrown = false;\n            try {\n                copyTo(tempFile, tempFile, useRemoteCopy);\n            } catch (FileTransferException e) {\n                exceptionThrown = true;\n            }\n\n            assert exceptionThrown;\n            assert !destFile.exists();\n\n            // Assert that copyTo/copyRemotelyTo fails when the source file doesn't exist\n            tempFile.delete();\n            exceptionThrown = false;\n            try {\n                copyTo(tempFile, destFile, useRemoteCopy);\n            } catch (FileTransferException e) {\n                exceptionThrown = true;\n            }\n\n            assert exceptionThrown;\n            assert !destFile.exists();\n\n            // Assert that copyTo/copyRemotelyTo succeeds copying a directory\n            tempFile.mkdir();\n            copyTo(tempFile, destFile, useRemoteCopy);\n            assert destFile.exists();\n            assert destFile.isDirectory();\n\n            // Assert that copyTo/copyRemotelyTo fails when the source is a directory, and when the destination is a\n            // subfolder of the source\n            AbstractFile subFolder = tempFile.getDirectChild(\"subfolder\");\n            exceptionThrown = false;\n            try {\n                copyTo(tempFile, subFolder, useRemoteCopy);\n            } catch (FileTransferException e) {\n                exceptionThrown = true;\n            }\n\n            assert exceptionThrown;\n            assert !subFolder.exists();\n\n            // Todo: test copyTo on a large, randomly-generated file tree\n        } else {                              // copyTo failed gracefully\n            System.out.println(\"Warning: AbstractFile#copyTo(AbstractFile) did not succeed, test skipped\");\n\n            // Assert that the destination file does not exist\n            assert !destFile.exists();\n        }\n    }\n\n    /**\n     * Tests {@link AbstractFile#copyRemotelyTo(AbstractFile)} when the operation is not supported.\n     *\n     * @throws IOException should not happen\n     */\n    protected void testCopyRemotelyToUnsupported() throws IOException {\n        // Assert that #copyRemotelyTo throws a proper UnsupportedFileOperationException when called\n        UnsupportedFileOperationException e = null;\n        try {\n            tempFile.copyRemotelyTo(getTemporaryFile());\n        } catch (UnsupportedFileOperationException ex) {\n            e = ex;\n        }\n        assertUnsupportedFileOperationException(e, FileOperation.COPY_REMOTELY);\n    }\n\n    /**\n     * Tests {@link AbstractFile#copyRemotelyTo(AbstractFile)} when the operation is supported.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    protected void testCopyRemotelyToSupported() throws IOException, NoSuchAlgorithmException {\n        testCopyTo(true);\n    }\n\n    /**\n     * Tests either {@link AbstractFile#renameTo(AbstractFile)} or {@link AbstractFile#moveTo(AbstractFile)} depending\n     * on the parameter's value.\n     *\n     * @param useRenameTo <code>true</code> to test {@link AbstractFile#renameTo(AbstractFile)}, <code>false</code> to\n     *                    test {@link AbstractFile#moveTo(AbstractFile)}\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happens\n     */\n    protected void testMoveTo(boolean useRenameTo) throws IOException, NoSuchAlgorithmException {\n        createFile(tempFile, 100000);\n        AbstractFile destFile = getTemporaryFile();\n        deleteWhenFinished(destFile);       // this file will automatically be deleted if it exists when the test is over\n\n        String sourceChecksum = calculateMd5(tempFile);\n\n        // Try and move/rename the file and see if it worked\n        boolean success;\n        try {\n            moveTo(tempFile, destFile, useRenameTo);\n            success = true;\n        } catch (IOException e) {\n            assert !(e instanceof UnsupportedFileOperationException);\n            success = false;\n        }\n\n        if (success) {     // If moveTo/renameTo succeeded\n            // Assert that the source file is gone and the destination file exists\n            assert !tempFile.exists();\n            assert destFile.exists();\n\n            // Assert that the checksum of source and destination match\n            assert sourceChecksum.equals(calculateMd5(destFile));\n\n            // At this point, we know that moveTo/renameTo works, at least for this destination file\n\n            // Assert that the destination file is overwritten when it exists\n            createFile(tempFile, 100000);\n            sourceChecksum = calculateMd5(tempFile);\n            moveTo(tempFile, destFile, useRenameTo);\n\n            assert !tempFile.exists();\n            assert destFile.exists();\n            assert sourceChecksum.equals(calculateMd5(destFile));\n\n            // Assert that moveTo/renameTo fails when the source and destination files are the same\n            createFile(tempFile, 1);\n            destFile.delete();\n            boolean exceptionThrown = false;\n            try {\n                moveTo(tempFile, tempFile, useRenameTo);\n            } catch (FileTransferException e) {\n                exceptionThrown = true;\n            }\n\n            assert exceptionThrown;\n            assert tempFile.exists();\n            assert !destFile.exists();\n\n            // Assert that moveTo/renameTo fails when the source file doesn't exist\n            tempFile.delete();\n            exceptionThrown = false;\n            try {\n                moveTo(tempFile, destFile, useRenameTo);\n            } catch (FileTransferException e) {\n                exceptionThrown = true;\n            }\n\n            assert exceptionThrown;\n            assert !destFile.exists();\n\n            // Assert that moveTo/renameTo succeeds moving a directory\n            tempFile.mkdir();\n            moveTo(tempFile, destFile, useRenameTo);\n            assert !tempFile.exists();\n            assert destFile.exists();\n            assert destFile.isDirectory();\n\n            // Assert that moveTo/renameTo fails when the source is a directory and a parent of the destination\n            tempFile.mkdir();\n            AbstractFile subFolder = tempFile.getDirectChild(\"subfolder\");\n            exceptionThrown = false;\n            try {\n                moveTo(tempFile, subFolder, useRenameTo);\n            } catch (FileTransferException e) {\n                exceptionThrown = true;\n            }\n\n            assert exceptionThrown;\n            assert tempFile.exists();\n            assert !subFolder.exists();\n\n            // Todo: test moveTo/renameTo on a large, randomly-generated file tree\n        } else {\n            // moveTo/renameTo failed, which is not considered as an error: this can happen under normal circumstances\n            System.out.println(\"Warning: AbstractFile#renameTo(AbstractFile) did not succeed, test skipped\");\n\n            // Assert that the destination file does not exist\n            assert !destFile.exists();\n        }\n    }\n\n    /**\n     * Tests {@link AbstractFile#renameTo(AbstractFile)} when the operation is not supported.\n     *\n     * @throws IOException should not happen\n     */\n    protected void testRenameToUnsupported() throws IOException {\n        // Assert that #renameTo throws a proper UnsupportedFileOperationException when called\n        UnsupportedFileOperationException e = null;\n        try {\n            tempFile.renameTo(getTemporaryFile());\n        } catch (UnsupportedFileOperationException ex) {\n            e = ex;\n        }\n        assertUnsupportedFileOperationException(e, FileOperation.RENAME);\n    }\n\n    /**\n     * Tests {@link AbstractFile#renameTo(AbstractFile)} when the operation is supported.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    protected void testRenameToSupported() throws IOException, NoSuchAlgorithmException {\n        testMoveTo(true);\n    }\n\n    /**\n     * Tests {@link AbstractFile#getFreeSpace()} when the operation is not supported.\n     *\n     * @throws IOException should not happen\n     */\n    protected void testGetFreeSpaceUnsupported() throws IOException {\n        // Assert that #getFreeSpace throws a proper UnsupportedFileOperationException when called\n        UnsupportedFileOperationException e = null;\n        try {\n            tempFile.getFreeSpace();\n        } catch (UnsupportedFileOperationException ex) {\n            e = ex;\n        }\n        assertUnsupportedFileOperationException(e, FileOperation.GET_FREE_SPACE);\n    }\n\n    /**\n     * Tests {@link AbstractFile#getFreeSpace()} when the operation is supported.\n     *\n     * @throws IOException should not happen\n     */\n    protected void testGetFreeSpaceSupported() throws IOException {\n        assert tempFile.getFreeSpace() >= 0;\n\n        // Note: it would be interesting to assert that allocating space to a file diminishes free space accordingly\n        // but it is not possible to guarantee that free space is not altered by another process.\n    }\n\n    /**\n     * Tests {@link AbstractFile#getTotalSpace()} when the operation is not supported.\n     *\n     * @throws IOException should not happen\n     */\n    protected void testGetTotalSpaceUnsupported() throws IOException {\n        // Assert that #getTotalSpace throws a proper UnsupportedFileOperationException when called\n        UnsupportedFileOperationException e = null;\n        try {\n            tempFile.getTotalSpace();\n        } catch (UnsupportedFileOperationException ex) {\n            e = ex;\n        }\n        assertUnsupportedFileOperationException(e, FileOperation.GET_TOTAL_SPACE);\n    }\n\n    /**\n     * Tests {@link AbstractFile#getTotalSpace()} when the operation is supported.\n     *\n     * @throws IOException should not happen\n     */\n    protected void testGetTotalSpaceSupported() throws IOException {\n        assert tempFile.getTotalSpace() >= 0;\n    }\n\n\n    //////////////////\n    // Test methods //\n    //////////////////\n\n    /**\n     * Tests {@link AbstractFile#calculateChecksum(java.security.MessageDigest)} and {@link com.mucommander.commons.io.ByteUtils#toHexString(byte[])}\n     * by computing file digests using different algorithms (MD5, SHA-1, ...) and comparing them against known values.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    @Test\n    public void testDigest() throws IOException, NoSuchAlgorithmException {\n\n        // Verify the digests of an empty file\n\n        tempFile.mkfile();\n\n        // Built-in JCE algorithms\n        assert \"8350e5a3e24c153df2275c9f80692773\".equals(tempFile.calculateChecksum(\"MD2\"));\n        assert \"d41d8cd98f00b204e9800998ecf8427e\".equals(tempFile.calculateChecksum(\"MD5\"));\n        assert \"da39a3ee5e6b4b0d3255bfef95601890afd80709\".equals(tempFile.calculateChecksum(\"SHA-1\"));\n        assert \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\".equals(tempFile.calculateChecksum(\"SHA-256\"));\n        assert \"38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b\".equals(tempFile.calculateChecksum(\"SHA-384\"));\n        assert \"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e\".equals(tempFile.calculateChecksum(\"SHA-512\"));\n\n        // MuProvider algorithms\n        MuProvider.registerProvider();  // registers the provider\n        assert \"00000000\".equals(tempFile.calculateChecksum(\"CRC32\"));\n        assert \"00000001\".equals(tempFile.calculateChecksum(\"Adler32\"));\n        //assert \"31d6cfe0d16ae931b73c59d7e0c089c0\".equals(tempFile.calculateChecksum(\"MD4\"));\n\n        // Verify the digests of a sample phrase\n        tempFile.copyStream(new ByteArrayInputStream(\"The quick brown fox jumps over the lazy dog\".getBytes()), false, -1);\n\n        assert \"03d85a0d629d2c442e987525319fc471\".equals(tempFile.calculateChecksum(\"MD2\"));\n        assert \"9e107d9d372bb6826bd81d3542a419d6\".equals(tempFile.calculateChecksum(\"MD5\"));\n        assert \"2fd4e1c67a2d28fced849ee1bb76e7391b93eb12\".equals(tempFile.calculateChecksum(\"SHA-1\"));\n        assert \"d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592\".equals(tempFile.calculateChecksum(\"SHA-256\"));\n        assert \"ca737f1014a48f4c0b6dd43cb177b0afd9e5169367544c494011e3317dbf9a509cb1e5dc1e85a941bbee3d7f2afbc9b1\".equals(tempFile.calculateChecksum(\"SHA-384\"));\n        assert \"07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6\".equals(tempFile.calculateChecksum(\"SHA-512\"));\n\n        // MuProvider algorithms\n        assert \"414fa339\".equals(tempFile.calculateChecksum(\"CRC32\"));\n        assert \"5bdc0fda\".equals(tempFile.calculateChecksum(\"Adler32\"));\n        //assert \"1bee69a46ba811185c194762abaeae90\".equals(tempFile.calculateChecksum(\"MD4\"));\n    }\n\n\n    /**\n     * Tests {@link AbstractFile#getSeparator()} by simply asserting that the return value is not <code>null</code>.\n     */\n    @Test\n    public void testSeparator() {\n        assert tempFile.getSeparator() != null;\n    }\n\n\n    /**\n     * Tests {@link AbstractFile#getAbsolutePath()} by asserting that it returns a non-null value, that the file can\n     * be resolved again using this path, and that the resolved file is the same as the orginal file.\n     * The tests are performed on a regular file and a directory file.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    @Test\n    public void testAbsolutePath() throws IOException, NoSuchAlgorithmException {\n        // Regular file\n        createFile(tempFile, 1);\n        testPathResolution(tempFile, tempFile.getAbsolutePath());\n\n        // Directory file\n        tempFile.delete();\n        tempFile.mkdir();\n        testPathResolution(tempFile, tempFile.getAbsolutePath());\n\n        // Test getAbsolutePath(boolean) on the directory file\n        assert tempFile.getAbsolutePath(true).endsWith(tempFile.getSeparator());\n        assert !tempFile.getAbsolutePath(false).endsWith(tempFile.getSeparator());\n    }\n\n    /**\n     * Tests {@link AbstractFile#getCanonicalPath()} by asserting that it returns a non-null value, that the file can\n     * be resolved again using this path, and that the resolved file is the same as the orginal file.\n     * The tests are performed on a regular file and a directory file.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    @Test\n    public void testCanonicalPath() throws IOException, NoSuchAlgorithmException {\n        // Regular file\n        createFile(tempFile, 1);\n        testPathResolution(tempFile.getCanonicalFile(), tempFile.getCanonicalPath());\n\n        // Directory file\n        tempFile.delete();\n        tempFile.mkdir();\n        testPathResolution(tempFile.getCanonicalFile(), tempFile.getCanonicalPath());\n\n        // Test getCanonicalPath(boolean) on the directory file\n        assert tempFile.getCanonicalPath(true).endsWith(tempFile.getSeparator());\n        assert !tempFile.getCanonicalPath(false).endsWith(tempFile.getSeparator());\n    }\n\n    /**\n     * Tests {@link AbstractFile#getName()}, {@link AbstractFile#getExtension()} and {@link AbstractFile#getNameWithoutExtension()}\n     * on a bunch of filenames.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testNameAndExtension() throws IOException {\n        AbstractFile baseFolder = getTemporaryFile();\n\n        assertNameAndExtension(baseFolder, \"name\", null, \"name\");\n        assertNameAndExtension(baseFolder, \".name\", null, \".name\");\n        assertNameAndExtension(baseFolder, \".name\", null, \".name\");\n        assertNameAndExtension(baseFolder, \"name.ext\", \"ext\", \"name\");\n        assertNameAndExtension(baseFolder, \"name.ext.\", null, \"name.ext.\");\n        assertNameAndExtension(baseFolder, \"name.with.dots.ext\", \"ext\", \"name.with.dots\");\n        assertNameAndExtension(baseFolder, \"name.with.dots.ext\", \"ext\", \"name.with.dots\");\n        assertNameAndExtension(baseFolder, \"name with spaces.ext\", \"ext\", \"name\");\n    }\n\n    /**\n     * Tests {@link AbstractFile#getURL()} by asserting that it returns a non-null value, that the file can\n     * be resolved again using its string representation (with credentials), and that the resolved file is the same as\n     * the orginal file. The tests are performed on a regular file and a directory file.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    @Test\n    public void testFileURL() throws IOException, NoSuchAlgorithmException {\n        FileURL fileURL;\n\n        // Regular file\n        createFile(tempFile, 1);\n        fileURL = tempFile.getURL();\n        assert fileURL != null;\n        testPathResolution(tempFile, fileURL.toString(true));\n\n        // Directory file\n        tempFile.delete();\n        tempFile.mkdir();\n        fileURL = tempFile.getURL();\n        assert fileURL != null;\n        testPathResolution(tempFile, fileURL.toString(true));\n    }\n\n\n    /**\n     * Tests the <code>java.net.URL</code> returned by {@link com.mucommander.commons.file.AbstractFile#getJavaNetURL()}\n     * and its associated <code>java.net.URLConnection</code>.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    @Test\n    public void testJavaNetURL() throws IOException, NoSuchAlgorithmException {\n        URL url;\n\n        // Test path resolution on a regular file\n\n        createFile(tempFile, 1000);\n        url = tempFile.getJavaNetURL();\n        assert url != null;\n        testPathResolution(tempFile, url.toString());\n\n        // Ensure that the file's length and date reported by URL match those of AbstractFile\n        assert url.openConnection().getLastModified() == tempFile.getLastModifiedDate();\n        assert url.openConnection().getDate() == tempFile.getLastModifiedDate();\n        assert url.openConnection().getContentLength() == tempFile.getSize();\n\n        // Test data integrity of the InputStream returned by URL#openConnection()#getInputStream()\n\n        if (tempFile.isFileOperationSupported(FileOperation.READ_FILE)) {\n            InputStream urlIn = url.openConnection().getInputStream();\n            assert urlIn != null;\n            InputStream fileIn = tempFile.getInputStream();\n\n            assertInputStreamEquals(fileIn, urlIn);\n\n            urlIn.close();\n            fileIn.close();\n        }\n\n        // Test data integrity of the OutputStream returned by URL#openStream()\n\n        if (tempFile.isFileOperationSupported(FileOperation.WRITE_FILE)) {\n            tempFile.delete();\n            url = tempFile.getJavaNetURL();\n            assert url != null;\n\n            OutputStream urlOut = url.openConnection().getOutputStream();\n            assert urlOut != null;\n\n            ChecksumOutputStream md5Out = getMd5OutputStream(urlOut);\n            writeRandomData(md5Out, 100000, 1000);\n            md5Out.close();\n\n            assert md5Out.getChecksumString().equals(calculateMd5(tempFile));\n        }\n\n        // Test path resolution on a directory\n\n        tempFile.delete();\n        tempFile.mkdir();\n\n        url = tempFile.getJavaNetURL();\n        assert url != null;\n        testPathResolution(tempFile, url.toString());\n\n        // Ensure that the file's length and date reported by URL match those of AbstractFile\n        assert url.openConnection().getLastModified() == tempFile.getLastModifiedDate();\n        assert url.openConnection().getDate() == tempFile.getLastModifiedDate();\n        assert url.openConnection().getContentLength() == tempFile.getSize();\n    }\n\n\n    /**\n     * Tests {@link AbstractFile#getRoot()} and {@link AbstractFile#isRoot()} methods.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testRoot() throws IOException {\n        AbstractFile root = tempFile.getRoot();\n\n        // The returned root folder may not be null\n        assert root != null;\n\n        // Test basic root file properties\n        assert root.isRoot();\n        assert root.isParentOf(tempFile);\n        assert root.isBrowsable();\n        assert root.getParent() == null;\n\n        assert tempFile.equals(root) || !tempFile.isRoot();\n\n        // Assert that getRoot() on the root file returns the same file\n        AbstractFile rootRoot = root.getRoot();\n        assert rootRoot != null;\n        assert rootRoot.equals(root);\n\n        // Assert that another temporary file yields the same root folder\n        assert root.equals(getTemporaryFile().getRoot());\n\n        // Assert that children of the root folder yield the same root folder and are not root folder themselves\n        // (test the first children only)\n        AbstractFile[] children = root.ls();\n        if (children.length > 0) {\n            assert root.equals(children[0].getRoot());\n            assert !children[0].isRoot();\n        }\n    }\n\n\n    /**\n     * Tests {@link AbstractFile#getVolume()}.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testVolume() throws IOException {\n        AbstractFile volume = tempFile.getVolume();\n\n        testVolume(volume);\n\n        // Test the relationship between the temporary file and its volume\n        assert volume.isParentOf(tempFile);\n        // Another temporary file should yield the same volume\n        assert volume.equals(getTemporaryFile().getVolume());\n\n    }\n\n\n    /**\n     * Tests {@link AbstractFile#getParent()} and {@link AbstractFile#isParentOf(AbstractFile)} methods.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testParent() throws IOException {\n        AbstractFile file = tempFile;\n        AbstractFile parent;\n        AbstractFile child;\n\n        // Tests all parents until the root is reached\n        while ((parent = file.getParent()) != null) {\n            assert parent.isParentOf(file);\n\n            // a file that has a parent shouldn't be a root file\n            assert !file.isRoot();\n\n            // Assert that the child file can be resolved into the same file using getDirectChild()\n            child = parent.getDirectChild(file.getName());\n            assert child != null;\n            assert child.equals(file);\n\n            file = parent;\n        }\n\n        // Assert that the root file's parent URL is null: if that is not the case, the parent file should have been\n        // resolved.\n        assert file.getURL().getParent() == null;\n\n        // A file that has no parent should be a root file\n        assert file.isRoot();\n    }\n\n\n    /**\n     * Tests {@link com.mucommander.commons.file.AbstractFile#exists()} in various situations.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testExists() throws IOException {\n        assert !tempFile.exists();\n\n        tempFile.mkfile();\n        assert tempFile.exists();\n\n        tempFile.delete();\n        assert !tempFile.exists();\n\n        tempFile.mkdir();\n        assert tempFile.exists();\n\n        tempFile.delete();\n        assert !tempFile.exists();\n    }\n\n    /**\n     * Tests the {@link AbstractFile#delete()} method in various situations.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testDelete() throws IOException {\n        if (tempFile.isFileOperationSupported(FileOperation.DELETE))\n            testDeleteSupported();\n        else\n            testDeleteUnsupported();\n    }\n\n    /**\n     * Tests the {@link AbstractFile#mkdir()} method in various situations.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testMkdir() throws IOException {\n        if (tempFile.isFileOperationSupported(FileOperation.CREATE_DIRECTORY))\n            testMkdirSupported();\n        else\n            testMkdirUnsupported();\n    }\n\n    /**\n     * Tests the {@link AbstractFile#mkdirs()} method in various situations.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testMkdirs() throws IOException {\n        // Require the 'create directory' operation to be supported\n        if (!tempFile.isFileOperationSupported(FileOperation.CREATE_DIRECTORY))\n            return;\n\n        // Assert that a directory can be created when the file doesn't already exist (without throwing an IOException)\n        AbstractFile dir1 = tempFile.getDirectChild(\"dir1-\" + getTestFileClass().getName());\n        AbstractFile dir2 = dir1.getDirectChild(\"dir2\");\n        AbstractFile dir2b = dir1.getChild(\"dir2\" + dir1.getSeparator());     // Same file with a trailing separator\n        dir2.mkdirs();\n\n        // Assert that the file exists after the directory has been created\n        assert dir2.exists();\n        assert dir2.isDirectory();\n        assert dir2b.exists();\n        assert dir2b.isDirectory();\n\n        // Delete 'dir2' and perform the same test. The difference with the previous test is that 'temp' and 'dir1' exist.\n        dir2.delete();\n        assert !dir2.exists();\n        assert !dir2.isDirectory();\n        assert !dir2b.exists();\n        assert !dir2b.isDirectory();\n\n        dir2.mkdirs();\n        assert dir2.exists();\n        assert dir2.isDirectory();\n        assert dir2b.exists();\n        assert dir2b.isDirectory();\n\n        // Assert that an IOException is thrown when the directory already exists\n        boolean ioExceptionThrown = false;\n        try {\n            dir2.mkdirs();\n        } catch (IOException e) {\n            ioExceptionThrown = true;\n        }\n\n        assert ioExceptionThrown;\n\n        // Assert that an IOException is thrown when a regular file exists\n        dir2.delete();\n        dir2.mkfile();\n\n        ioExceptionThrown = false;\n        try {\n            dir2.mkdir();\n        } catch (IOException e) {\n            ioExceptionThrown = true;\n        }\n\n        assert ioExceptionThrown;\n    }\n\n    /**\n     * Tests the {@link AbstractFile#mkfile()} method in various situations.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testMkfile() throws IOException {\n        // Assert that a file can be created when it doesn't already exist (without throwing an IOException)\n        tempFile.mkfile();\n\n        // Assert that the file exists after it has been created\n        assert tempFile.exists();\n\n        // Assert that an IOException is thrown when the file already exists\n        boolean ioExceptionThrown = false;\n        try {\n            tempFile.mkfile();\n        } catch (IOException e) {\n            ioExceptionThrown = true;\n        }\n\n        assert ioExceptionThrown;\n\n        // Assert that an IOException is thrown when a directory exists\n        tempFile.delete();\n        tempFile.mkdir();\n\n        ioExceptionThrown = false;\n        try {\n            tempFile.mkfile();\n        } catch (IOException e) {\n            ioExceptionThrown = true;\n        }\n\n        assert ioExceptionThrown;\n    }\n\n    /**\n     * Tests the {@link AbstractFile#isDirectory()} method in various situations.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testIsDirectory() throws IOException {\n        // Same file with a trailing separator\n        FileURL tempFileURLB = (FileURL) tempFile.getURL().clone();\n        tempFileURLB.setPath(tempFile.addTrailingSeparator(tempFileURLB.getPath()));\n        AbstractFile tempFileB = FileFactory.getFile(tempFileURLB, true);\n\n        // Assert that isDirectory() returns false when the file does not exist\n        assert !tempFile.exists();\n        assert !tempFile.isDirectory();\n        assert !tempFileB.exists();\n        assert !tempFileB.isDirectory();\n\n        // Assert that isDirectory() returns true for directories\n        tempFile.mkdir();\n        assert tempFile.exists();\n        assert tempFile.isDirectory();\n        assert tempFileB.exists();\n        assert tempFileB.isDirectory();\n\n        // Assert that isDirectory() returns false for regular files\n        tempFile.delete();\n        assert !tempFile.exists();\n        assert !tempFile.isDirectory();\n        assert !tempFileB.exists();\n        assert !tempFileB.isDirectory();\n\n        tempFile.mkfile();\n        assert tempFile.exists();\n        assert !tempFile.isDirectory();\n        assert tempFile.exists();\n        assert !tempFileB.isDirectory();\n    }\n\n    /**\n     * Tests {@link AbstractFile#changePermissions(int)}, calling {@link #testChangePermissionsSupported()} or\n     * {@link #testChangePermissionsUnsupported()} depending on whether the {@link FileOperation#CHANGE_PERMISSION}\n     * operation is supported.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    @Test\n    public void testChangePermissions() throws IOException, NoSuchAlgorithmException {\n        if (tempFile.isFileOperationSupported(FileOperation.CHANGE_PERMISSION))\n            testChangePermissionsSupported();\n        else\n            testChangePermissionsUnsupported();\n    }\n\n    /**\n     * Tests {@link AbstractFile#getPermissions()}.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    @Test\n    public void testGetPermissions() throws IOException, NoSuchAlgorithmException {\n        assert tempFile.getPermissions() != null;\n\n        createFile(tempFile, 0);\n\n        FilePermissions permissions = tempFile.getPermissions();\n        PermissionBits getPermMask = permissions.getMask();\n\n        assert permissions != null;\n\n        int getPermMaskInt = getPermMask.getIntValue();\n\n        int bitShift = 0;\n        int bitMask;\n        boolean canGetPermission;\n\n        for (int a = PermissionAccesses.OTHER_ACCESS; a <= PermissionAccesses.USER_ACCESS; a++) {\n            for (int p = PermissionTypes.EXECUTE_PERMISSION; p <= PermissionTypes.READ_PERMISSION; p = p << 1) {\n                bitMask = 1 << bitShift;\n\n                canGetPermission = (getPermMaskInt & bitMask) != 0;\n                assert getPermMask.getBitValue(a, p) == canGetPermission : \"inconsistent bit and int value for (\" + a + \", \" + p + \")\";\n\n                assert !canGetPermission || permissions.getBitValue(a, p) == ((permissions.getIntValue() & bitMask) != 0) :\n                        \"inconsistent bit and int value for (\" + a + \", \" + p + \")\";\n\n                bitShift++;\n            }\n        }\n    }\n\n    /**\n     * Tests {@link AbstractFile#setLastModifiedDate(long)}, calling {@link #testChangeDateSupported()} or\n     * {@link #testChangeDateUnsupported()} depending on whether the {@link FileOperation#CHANGE_DATE}\n     * operation is supported.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    @Test\n    public void testChangeDate() throws IOException, NoSuchAlgorithmException {\n        if (tempFile.isFileOperationSupported(FileOperation.CHANGE_DATE))\n            testChangeDateSupported();\n        else\n            testChangeDateUnsupported();\n    }\n\n    /**\n     * Tests {@link AbstractFile#getLastModifiedDate()}.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    @Test\n    public void testGetDate() throws IOException, NoSuchAlgorithmException {\n        createFile(tempFile, 0);\n\n        // Asserts that the date changes when the file is modified\n        long date = tempFile.getLastModifiedDate();\n        sleep(1000);    // Sleep a full second, some filesystems may only have a one-second granularity\n        createFile(tempFile, 1);  // 1 byte should be enough\n\n        assert tempFile.getLastModifiedDate() > date;\n    }\n\n    /**\n     * Tests {@link AbstractFile#getInputStream()}, calling {@link #testGetInputStreamSupported()} or\n     * {@link #testGetInputStreamUnsupported()} depending on whether the {@link FileOperation#READ_FILE}\n     * operation is supported.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    @Test\n    public void testGetInputStream() throws IOException, NoSuchAlgorithmException {\n        if (tempFile.isFileOperationSupported(FileOperation.READ_FILE))\n            testGetInputStreamSupported();\n        else\n            testGetInputStreamUnsupported();\n    }\n\n    /**\n     * Tests {@link AbstractFile#getRandomAccessInputStream()}, calling {@link #testGetRandomAccessInputStreamSupported()}\n     * or {@link #testGetRandomAccessInputStreamUnsupported()} depending on whether the\n     * {@link FileOperation#RANDOM_READ_FILE} operation is supported.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    @Test\n    public void testGetRandomAccessInputStream() throws IOException, NoSuchAlgorithmException {\n        if (tempFile.isFileOperationSupported(FileOperation.RANDOM_READ_FILE))\n            testGetRandomAccessInputStreamSupported();\n        else\n            testGetRandomAccessInputStreamUnsupported();\n    }\n\n    /**\n     * Tests {@link AbstractFile#getOutputStream()}, calling {@link #testGetOutputStreamSupported()}\n     * or {@link #testGetOutputStreamUnsupported()} depending on whether the\n     * {@link FileOperation#WRITE_FILE} operation is supported.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    @Test\n    public void testGetOutputStream() throws IOException, NoSuchAlgorithmException {\n        if (tempFile.isFileOperationSupported(FileOperation.WRITE_FILE))\n            testGetOutputStreamSupported();\n        else\n            testGetOutputStreamUnsupported();\n    }\n\n    /**\n     * Tests {@link AbstractFile#getAppendOutputStream()}, calling {@link #testGetAppendOutputStreamSupported()}\n     * or {@link #testGetAppendOutputStreamUnsupported()} depending on whether the\n     * {@link FileOperation#APPEND_FILE} operation is supported.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    @Test\n    public void testGetAppendOutputStream() throws IOException, NoSuchAlgorithmException {\n        if (tempFile.isFileOperationSupported(FileOperation.APPEND_FILE))\n            testGetAppendOutputStreamSupported();\n        else\n            testGetAppendOutputStreamUnsupported();\n    }\n\n    /**\n     * Tests {@link AbstractFile#getRandomAccessOutputStream()}, calling {@link #testGetRandomAccessOutputStreamSupported()}\n     * or {@link #testGetRandomAccessOutputStreamUnsupported()} depending on whether the\n     * {@link FileOperation#RANDOM_WRITE_FILE} operation is supported.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    @Test\n    public void testGetRandomAccessOutputStream() throws IOException, NoSuchAlgorithmException {\n        if (tempFile.isFileOperationSupported(FileOperation.RANDOM_WRITE_FILE))\n            testGetRandomAccessOutputStreamSupported();\n        else\n            testGetRandomAccessOutputStreamUnsupported();\n    }\n\n    /**\n     * Tests {@link AbstractFile#ls()}.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testLs() throws IOException {\n        if (tempFile.isFileOperationSupported(FileOperation.LIST_CHILDREN))\n            testLsSupported();\n        else\n            testLsUnsupported();\n    }\n\n    /**\n     * Tests {@link AbstractFile#getFreeSpace()}.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testFreeSpace() throws IOException {\n        if (tempFile.isFileOperationSupported(FileOperation.GET_FREE_SPACE))\n            testGetFreeSpaceSupported();\n        else\n            testGetFreeSpaceUnsupported();\n    }\n\n    /**\n     * Tests {@link AbstractFile#getTotalSpace()}.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testTotalSpace() throws IOException {\n        if (tempFile.isFileOperationSupported(FileOperation.GET_TOTAL_SPACE))\n            testGetTotalSpaceSupported();\n        else\n            testGetTotalSpaceUnsupported();\n    }\n\n    /**\n     * Tests {@link AbstractFile#moveTo(AbstractFile)}.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    @Test\n    public void testMoveTo() throws IOException, NoSuchAlgorithmException {\n        testMoveTo(false);\n    }\n\n    /**\n     * Tests {@link AbstractFile#renameTo(AbstractFile)}.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    @Test\n    public void testRenameTo() throws IOException, NoSuchAlgorithmException {\n        if (tempFile.isFileOperationSupported(FileOperation.RENAME))\n            testRenameToSupported();\n        else\n            testRenameToUnsupported();\n    }\n\n    /**\n     * Tests {@link AbstractFile#copyTo(AbstractFile)}.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    @Test\n    public void testCopyTo() throws IOException, NoSuchAlgorithmException {\n        testCopyTo(false);\n    }\n\n    /**\n     * Tests {@link AbstractFile#copyRemotelyTo(AbstractFile)}.\n     *\n     * @throws IOException              should not happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    @Test\n    public void testCopyRemotelyTo() throws IOException, NoSuchAlgorithmException {\n        if (tempFile.isFileOperationSupported(FileOperation.COPY_REMOTELY))\n            testCopyRemotelyToSupported();\n        else\n            testCopyRemotelyToUnsupported();\n    }\n\n    /**\n     * Tests {@link AbstractFile#getIcon()} and {@link AbstractFile#getIcon(java.awt.Dimension)}.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testIcon() throws IOException {\n        Icon icon;\n        boolean isHeadless;\n\n        // Skips the test if under OS X (this would create a new instance of JFileChooser, which fails \n        isHeadless = GraphicsEnvironment.isHeadless();\n        if (isHeadless && OsFamily.MAC_OS_X.isCurrent())\n            return;\n\n        // Some icon providers will fail (return a null icon) if the file doesn't exist\n        tempFile.mkfile();\n\n        icon = tempFile.getIcon();\n        assert isHeadless || icon != null;\n\n        icon = tempFile.getIcon(new Dimension(16, 16));\n        assert isHeadless || icon != null;\n    }\n\n    /**\n     * Verifies that the file implementation handles unicode/non-ascii filenames properly.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testUnicodeFilenames() throws IOException {\n        tempFile.mkdir();\n\n        String unicodeFilename = \"どうもありがとうミスターロボット\";\n        Locale filenameLocale = Locale.JAPANESE;\n\n        testUnicodeFilename(tempFile, unicodeFilename, filenameLocale, false);\n        testUnicodeFilename(tempFile, unicodeFilename, filenameLocale, true);\n    }\n\n    /**\n     * Tests {@link com.mucommander.commons.file.util.PathUtils#resolveDestination(String, AbstractFile)} by calling\n     * {@link PathUtilsTest#testResolveDestination(AbstractFile)} with a temporary folder.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testDestinationResolution() throws IOException {\n        AbstractFile folder = deleteWhenFinished(getTemporaryFile());\n        folder.mkdir();\n        PathUtilsTest.testResolveDestination(folder);\n    }\n\n    /**\n     * Tests the absence of {@link UnsupportedFileOperation} annotations in all methods corresponding to\n     * {@link #getSupportedOperations() supported operations}, and the absence thereof for unsupported operations.\n     *\n     * @throws Exception should not happen\n     */\n    @Test\n    public void testUnsupportedFileOperationAnnotations() throws Exception {\n        List<FileOperation> supportedOps = Arrays.asList(getSupportedOperations());\n\n        Class<? extends AbstractFile> fileClass = getTestFileClass();\n\n        for (FileOperation op : FileOperation.values()) {\n            Method m = op.getCorrespondingMethod(fileClass);\n            Method fileClassMethod = fileClass.getMethod(m.getName(), m.getParameterTypes());\n            boolean annotated = fileClassMethod.isAnnotationPresent(UnsupportedFileOperation.class);\n            assert supportedOps.contains(op) == !annotated : \"File operation \" + op + \" does not match annotation of method \" + m.getName();\n        }\n    }\n\n    protected Class<? extends AbstractFile> getTestFileClass() {\n        return tempFile.getClass();\n    }\n\n    /**\n     * Ensures that the return value of {@link AbstractFile#isFileOperationSupported(FileOperation)} is consistent\n     * with {@link #getSupportedOperations() supported operations}.\n     *\n     */\n    @Test\n    public void testSupportedFileOperations() {\n        List<FileOperation> supportedOps = Arrays.asList(getSupportedOperations());\n\n        Class<? extends AbstractFile> fileClass = getTestFileClass();\n        for (FileOperation op : FileOperation.values()) {\n            boolean opSupported = supportedOps.contains(op);\n\n            assertEquals(opSupported, AbstractFile.isFileOperationSupported(op, getTestFileClass()));\n            assertEquals(opSupported, AbstractFile.isFileOperationSupported(op, fileClass));\n        }\n    }\n\n    /**\n     * Ensures that {@link AbstractFile} instance caching works as expected, that is the same instance is returned\n     * by <code>FileFactory#getFile</code> methods every time the same location is asked for.\n     *\n     * @throws Exception should not happen\n     */\n    @Test\n    public void testFileInstanceCaching() throws Exception {\n        AbstractFile file;\n        for (int i = 0; i < 10; i++) {\n            file = getTemporaryFile();\n            for (int j = 0; j < 5; j++) {\n                // Resolve by path\n                String pathT = file.addTrailingSeparator(file.getURL().toString(true, false));\n                String pathNT = file.removeTrailingSeparator(pathT);\n                assert FileFactory.getFile(pathT) == file;\n                assert FileFactory.getFile(pathNT) == file;\n\n                // Resolve by URL\n                assert FileFactory.getFile(file.getURL()) == file;\n                assert FileFactory.getFile(FileURL.getFileURL(pathT)) == file;\n                assert FileFactory.getFile(FileURL.getFileURL(pathNT)) == file;\n            }\n        }\n\n    }\n\n\n    /**\n     * Returns a temporary file that can be used for testing purposes. Implementation of this method must guarantee that:\n     * <ul>\n     *   <li>the returned file does not exist, i.e. that {@link AbstractFile#exists()} returns <code>false</code>.</li>\n     *   <li>a new file is returned each time this method is called.</li>\n     *   <li>the return file's path does not end with a trailing path separator</li>\n     * </ul>\n     *\n     * @return a temporary file that does not exist\n     * @throws IOException if an error occurred while creating a temporary file\n     */\n    public abstract AbstractFile getTemporaryFile() throws IOException;\n\n    /**\n     * Returns a array of all {@link FileOperation} supported by this file implementation.\n     *\n     * @return a array of all {@link FileOperation} supported by this file implementation.\n     */\n    public abstract FileOperation[] getSupportedOperations();\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/DefaultFileURLTest.java",
    "content": "package com.mucommander.commons.file;\n\n/**\n * A {@link FileURLTestCase} implementation for URLs with no specific handler, i.e. using the\n * {@link com.mucommander.commons.file.FileURL#getDefaultHandler() default URL handler}.\n *\n * @author Maxence Bernard\n */\npublic class DefaultFileURLTest extends FileURLTestCase {\n\n    @Override\n    protected String getScheme() {\n        return \"unknown\";\n    }\n\n    @Override\n    protected int getDefaultPort() {\n        return -1;\n    }\n\n    @Override\n    protected AuthenticationType getAuthenticationType() {\n        return AuthenticationType.NO_AUTHENTICATION;\n    }\n\n    @Override\n    protected Credentials getGuestCredentials() {\n        return null;\n    }\n\n    @Override\n    protected String getPathSeparator() {\n        return \"/\";\n    }\n\n    @Override\n    protected boolean isQueryParsed() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/DefaultPathCanonizerTest.java",
    "content": "package com.mucommander.commons.file;\n\nimport org.junit.jupiter.api.Test;\n\n/**\n * Tests {@link DefaultPathCanonizer}.\n *\n * @author Maxence Bernard\n * @see DefaultPathCanonizer\n */\npublic class DefaultPathCanonizerTest {\n\n    private String getNormalizedPath(String path, String separator) {\n        if(!separator.equals(\"/\"))\n            path = path.replace(\"/\", separator);\n\n        return path;\n    }\n\n    /**\n     * Tests '.' and '..' factoring and tilde replacement if <code>tildeReplacement</code> is not <code>null</code>.\n     *\n     * @param separator path separator\n     * @param tildeReplacement string to replace '~' path fragments with\n     */\n    private void testCanonizer(String separator, String tildeReplacement) {\n        DefaultPathCanonizer canonizer = new DefaultPathCanonizer(separator, tildeReplacement);\n\n        // Test '~' canonization (or the lack thereof)\n        if(tildeReplacement==null) {\n            assert \"~\".equals(canonizer.canonize(\"~\"));\n            assert (\"~\"+separator+\"blah\").equals(canonizer.canonize(\"~\"+separator+\"blah\"));\n        }\n        else {\n            assert tildeReplacement.equals(canonizer.canonize(\"~\"));\n            assert (tildeReplacement+separator+\"blah\").equals(canonizer.canonize(\"~\"+separator+\"blah\"));\n        }\n\n        // Test '.' and '..' factoring\n\n        assert separator.equals(canonizer.canonize(getNormalizedPath(\"/home/maxence/../..\", separator)));\n        assert getNormalizedPath(\"/home/\", separator).equals(canonizer.canonize(getNormalizedPath(\"/home/maxence/..\", separator)));\n        assert getNormalizedPath(\"/home/maxence/\", separator).equals(canonizer.canonize(getNormalizedPath(\"/home/maxence/.\", separator)));\n        assert separator.equals(canonizer.canonize(getNormalizedPath(\"/home/maxence/../..\", separator)));\n        assert getNormalizedPath(\"/home/maxence/\", separator).equals(canonizer.canonize(getNormalizedPath(\"/home//maxence//\", separator)));\n        assert separator.equals(canonizer.canonize(getNormalizedPath(\"/././.\", separator)));\n        assert \"\".equals(canonizer.canonize(getNormalizedPath(\"/../../..\", separator)));\n        assert separator.equals(canonizer.canonize(getNormalizedPath(\"/1/.././1/./2//./.././../\", separator)));\n    }\n\n    /**\n     * Tests '.' and '..' factoring, and tilde replacement if <code>tildeReplacement</code>, with a forward slash\n     * path separator.\n     */\n    @Test\n    public void testForwardSlashCanonization() {\n        testCanonizer(\"/\", null);\n        testCanonizer(\"/\", \"/home/maxence\");\n    }\n\n    /**\n     * Tests '.' and '..' factoring, and tilde replacement if <code>tildeReplacement</code>, with a backslash\n     * path separator.\n     */\n    @Test\n    public void testBackSlashCanonization() {\n        testCanonizer(\"\\\\\", null);\n        testCanonizer(\"\\\\\", \"C:\\\\Document and Settings\\\\maxence\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/FileFactoryTest.java",
    "content": "package com.mucommander.commons.file;\n\nimport org.junit.jupiter.api.Test;\nimport java.io.IOException;\n\n/**\n * A test case for {@link FileFactory}.\n *\n * @author Maxence Bernard\n */\npublic class FileFactoryTest {\n\n    /**\n     * Tests {@link com.mucommander.commons.file.FileFactory#getTemporaryFolder()}.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testTemporaryFolder() throws IOException {\n        // Assert that the returned file is a folder that exists\n        AbstractFile temporaryFolder = FileFactory.getTemporaryFolder();\n        assert temporaryFolder != null;\n        assert temporaryFolder.isDirectory();\n        assert temporaryFolder.exists();\n\n        // Assert that the temporary folder is the parent folder of temporary files\n        AbstractFile temporaryFile = FileFactory.getTemporaryFile(false);\n        assert temporaryFile.getParent().equals(temporaryFolder);\n    }\n\n    /**\n     * Tests {@link com.mucommander.commons.file.FileFactory#getTemporaryFile(String, boolean)}.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testTemporaryFiles() throws IOException {\n        String desiredName = System.currentTimeMillis()+\".ext\";\n\n        // Assert that #getTemporaryFile returns a non-existing file with the desired name\n        AbstractFile temporaryFile1 = FileFactory.getTemporaryFile(desiredName, true);\n        assert temporaryFile1 != null;\n        assert !temporaryFile1.exists();\n        assert desiredName.equals(temporaryFile1.getName());\n\n        // Assert that #getTemporaryFile returns a new temporary file if the requested file already exists, and that the\n        // extension matches the desired one.\n        temporaryFile1.mkfile();\n\n        AbstractFile temporaryFile2 = FileFactory.getTemporaryFile(desiredName, true);\n        assert temporaryFile2 != null;\n        assert !temporaryFile2.exists();\n        assert !temporaryFile2.getName().equals(desiredName);\n        assert temporaryFile1.getExtension().equals(temporaryFile2.getExtension());\n\n        // Note: the temporary file should normally be deleted on VM shutdown, but we have no (easy) way to assert that\n\n        // Perform some basic tests on #getTemporaryFile when called without a desired name\n        temporaryFile1 = FileFactory.getTemporaryFile(true);\n        assert temporaryFile1 != null;\n        assert !temporaryFile1.exists();\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/FileURLTestCase.java",
    "content": "package com.mucommander.commons.file;\n\nimport com.mucommander.commons.runtime.OsFamily;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Test;\n\nimport java.net.MalformedURLException;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * A generic test case for {@link com.mucommander.commons.file.FileURL}. This class is abstract and must be extended for\n * each URL scheme to be tested. Those tests should be completed by additional scheme-specific tests for\n * certain particularities that would not covered by this generic test case.\n *\n * @author Maxence Bernard\n * @see com.mucommander.commons.file.FileURL\n */\npublic abstract class FileURLTestCase {\n\n    /**\n     * Returns the scheme-specific version of the path.\n     *\n     * @param path a forward slash-separated path\n     * @return the corresponding scheme-specific path\n     */\n    protected String getSchemePath(String path) {\n        String separator = getPathSeparator();\n\n        if (!separator.equals(\"/\"))\n            path = \"/\" + path.replace(\"/\", separator);\n\n        return path;\n    }\n\n    /**\n     * Creates a URL from the given parts, ensuring that parsing it yields the given parts, and returns it.\n     *\n     * @param login    login part, may be <code>null</code>\n     * @param password password part, may be <code>null</code>\n     * @param host     host part, may be <code>null</code>\n     * @param port     port part, <code>-1</code> for none\n     * @param path     path part\n     * @param query    query part, may be <code>null</code>\n     * @return a URL corresponding to the given parts\n     * @throws MalformedURLException if the URL cannot be parsed\n     */\n    protected FileURL getURL(String login, String password, String host, int port, String path, String query) throws MalformedURLException {\n        String scheme = getScheme();\n        StringBuilder sb = new StringBuilder(scheme + \"://\");\n\n        if (host != null) {\n            if (login != null) {\n                sb.append(login);\n\n                if (password != null) {\n                    sb.append(':');\n                    sb.append(password);\n                }\n\n                sb.append('@');\n            }\n\n            sb.append(host);\n\n            if (port != -1) {\n                sb.append(':');\n                sb.append(port);\n            }\n        }\n\n        path = getSchemePath(path);\n        sb.append(path);\n\n        if (query != null) {\n            sb.append('?');\n            sb.append(query);\n        }\n\n        // Assert that each of the url's parts match\n\n        FileURL url = FileURL.getFileURL(sb.toString());\n\n        assertEquals(scheme, url.getScheme());\n\n        if (host != null) {\n            if (login != null) {\n                assertEquals(login, url.getLogin());\n                assert url.containsCredentials();\n\n                if (password != null)\n                    assertEquals(password, url.getPassword());\n\n                assertTrue(new Credentials(login, password).equals(url.getCredentials(), true));\n            }\n\n            assertEquals(host, url.getHost());\n            assert port == url.getPort();\n        }\n\n        if (query != null && !isQueryParsed()) {\n            assert url.getQuery() == null;\n            path = path + \"?\" + query;\n        } else if (query == null)\n            assert url.getQuery() == null;\n        else\n            assertEquals(query, url.getQuery());\n\n        assertPathEquals(path, url);\n\n        // Test the URL's string representation\n        assertEquals(url, FileURL.getFileURL(url.toString(true, false)));\n        assertTrue(url.equals(FileURL.getFileURL(url.toString(false, false)), false, false));\n\n        return url;\n    }\n\n    /**\n     * Shorthand for {@link #getURL(String, String, String, int, String, String)} called with just a path.\n     *\n     * @param path the path part\n     * @return a URL corresponding to the given part\n     * @throws MalformedURLException if the URL cannot be parsed\n     */\n    protected FileURL getURL(String path) throws MalformedURLException {\n        return getURL(null, null, null, -1, path, null);\n    }\n\n    /**\n     * Shorthand for {@link #getURL(String, String, String, int, String, String)} called with just a host and path.\n     *\n     * @param host the host part, may be <code>null</code>\n     * @param path the path part\n     * @return a URL corresponding to the given part\n     * @throws MalformedURLException if the URL cannot be parsed\n     */\n    protected FileURL getURL(String host, String path) throws MalformedURLException {\n        return getURL(null, null, host, -1, path, null);\n    }\n\n\n    /**\n     * Returns the simplest FileURL instance possible using the scheme returned by {@link #getScheme()}, containing\n     * only the scheme and '/' as a path. For instance, if <code>http</code> is the current scheme, a FileURL with\n     * <code>http:///</code> as a string representation will be returned.\n     *\n     * @return a 'root' FileURL instance with the scheme returned by {@link # getScheme ()}\n     * @throws MalformedURLException should never happen\n     */\n    protected FileURL getRootURL() throws MalformedURLException {\n        return FileURL.getFileURL(getScheme() + \":///\");\n    }\n\n    /**\n     * Attemps to parse the specified url using {@link FileURL#getFileURL(String)} and returns <code>true</code> if it\n     * succeeded, <code>false</code> if it threw a <code>MalformedURLException</code>.\n     *\n     * @param url the URL to try and parse\n     * @return <code>true</code> if the URL could be parsed, <code>false</code> if a <code>MalformedURLException</code> was thrown\n     */\n    protected boolean canParse(String url) {\n        try {\n            FileURL.getFileURL(url);\n            return true;\n        } catch (MalformedURLException e) {\n            return false;\n        }\n    }\n\n    /**\n     * Asserts that both URLs are equal, and that their hashcodes are the same.\n     *\n     * @param url1 first url to test\n     * @param url2 second url to test\n     */\n    protected void assertEqualsAndHashCode(FileURL url1, FileURL url2) {\n        assertEquals(url1, url2);\n        assertEquals(url2, url1);\n        assertEquals(url1.hashCode(), url2.hashCode());\n    }\n\n    /**\n     * Asserts that both URLs are equal, comparing credentials and properties as requested. If both the\n     * <code>compareCredentials</code> and <code>compareProperties</code> parameters are <code>true</code>, this method\n     * asserts that the hashcode of both URLs are the same.\n     *\n     * @param url1               first url to test\n     * @param url2               second url to test\n     * @param compareCredentials if <code>true</code>, the login and password parts of both FileURL need to be\n     *                           equal (case-sensitive) for the FileURL instances to be equal\n     * @param compareProperties  if <code>true</code>, all properties need to be equal (case-sensitive) in both\n     *                           FileURL for them to be equal\n     */\n    protected void assertEqualsAndHashCode(FileURL url1, FileURL url2, boolean compareCredentials, boolean compareProperties) {\n        assertTrue(url1.equals(url2, compareCredentials, compareProperties));\n        assertTrue(url2.equals(url1, compareCredentials, compareProperties));\n\n        // Compare hash codes only if both flags are true.\n        assertTrue(!compareCredentials || !compareProperties || url1.hashCode() == url2.hashCode());\n    }\n\n    /**\n     * Asserts that both URLs are not equal.\n     *\n     * @param url1 first url to test\n     * @param url2 second url to test\n     */\n    protected void assertNotEquals(FileURL url1, FileURL url2) {\n        Assertions.assertNotEquals(url1, url2);\n        Assertions.assertNotEquals(url2, url1);\n    }\n\n    /**\n     * Asserts that both URLs are not equal, comparing credentials and properties as requested.\n     *\n     * @param url1               first url to test\n     * @param url2               second url to test\n     * @param compareCredentials if <code>true</code>, the login and password parts of both FileURL need to be\n     *                           equal (case-sensitive) for the FileURL instances to be equal\n     * @param compareProperties  if <code>true</code>, all properties need to be equal (case-sensitive) in both\n     *                           FileURL for them to be equal\n     */\n    protected void assertNotEquals(FileURL url1, FileURL url2, boolean compareCredentials, boolean compareProperties) {\n        assertFalse(url1.equals(url2, compareCredentials, compareProperties));\n        assertFalse(url2.equals(url1, compareCredentials, compareProperties));\n    }\n\n    /**\n     * Asserts that the path of the given URL is equal to the given expected path.\n     *\n     * @param expectedPath the expected path\n     * @param url          URL to test\n     */\n    protected void assertPathEquals(String expectedPath, FileURL url) {\n        assertEquals(expectedPath, url.getPath());\n    }\n\n\n    /**\n     * Ensures that the values returned by {@link FileURL#getStandardPort()} and {@link SchemeHandler#getStandardPort()}\n     * match the expected one returned by {@link #getDefaultPort()}.\n     *\n     * @throws MalformedURLException should not happen\n     */\n    @Test\n    public void testDefaultPort() throws MalformedURLException {\n        FileURL url = getRootURL();\n        int expectedDefaultPort = getDefaultPort();\n\n        // Assert that the default port value returned by the FileURL and its handler match the expected one\n        // and are consistent\n        assert expectedDefaultPort == url.getStandardPort();\n        assert expectedDefaultPort == url.getHandler().getStandardPort();\n\n        // Assert that the default port value is valid: either -1 or comprised between 1 and 65535\n        assert expectedDefaultPort == -1 || (expectedDefaultPort > 0 && expectedDefaultPort < 65536);\n    }\n\n\n    /**\n     * Ensures that the values returned by {@link FileURL#getGuestCredentials()} and {@link SchemeHandler#getGuestCredentials()}\n     * match the expected one returned by {@link #getGuestCredentials()}.\n     *\n     * @throws MalformedURLException should not happen\n     */\n    @Test\n    public void testGuestCredentials() throws MalformedURLException {\n        FileURL url = getRootURL();\n        Credentials expectedGuestCredentials = getGuestCredentials();\n\n        // Assert that the guest credentials values returned by the FileURL and its handler match the expected one\n        // and are consistent\n        if (expectedGuestCredentials == null) {\n            assert url.getGuestCredentials() == null;\n            assert url.getHandler().getGuestCredentials() == null;\n        } else {\n            assertEquals(expectedGuestCredentials, url.getGuestCredentials());\n            assertEquals(expectedGuestCredentials, url.getHandler().getGuestCredentials());\n        }\n    }\n\n    /**\n     * Ensures that the values returned by {@link FileURL#getAuthenticationType()} ()} and {@link SchemeHandler#getAuthenticationType()}\n     * match the expected value returned by {@link #getAuthenticationType()}, and that the value is one of the constants\n     * defined in {@link AuthenticationType}.\n     * If the authentication type is {@link AuthenticationType#NO_AUTHENTICATION}, verifies that\n     * {@link #getGuestCredentials()} returns <code>null</code>.\n     *\n     * @throws MalformedURLException should not happen\n     */\n    @Test\n    public void testAuthenticationType() throws MalformedURLException {\n        FileURL url = getRootURL();\n        AuthenticationType expectedAuthenticationType = getAuthenticationType();\n\n        assertEquals(expectedAuthenticationType, url.getAuthenticationType());\n        assertEquals(expectedAuthenticationType, url.getHandler().getAuthenticationType());\n\n        assert expectedAuthenticationType == AuthenticationType.NO_AUTHENTICATION\n                || expectedAuthenticationType == AuthenticationType.AUTHENTICATION_REQUIRED\n                || expectedAuthenticationType == AuthenticationType.AUTHENTICATION_OPTIONAL;\n\n        assert expectedAuthenticationType != AuthenticationType.NO_AUTHENTICATION || url.getGuestCredentials() == null;\n    }\n\n    /**\n     * Ensures that the values returned by {@link FileURL#getPathSeparator()} and {@link SchemeHandler#getPathSeparator()}\n     * match the expected one returned by {@link #getPathSeparator()}.\n     *\n     * @throws MalformedURLException should not happen\n     */\n    @Test\n    public void testPathSeparator() throws MalformedURLException {\n        FileURL url = getRootURL();\n        String expectedPathSeparator = url.getPathSeparator();\n\n        // Assert that the path separator values returned by the FileURL and its handler match the expected one\n        // and are consistent\n        assertEquals(expectedPathSeparator, url.getPathSeparator());\n        assertEquals(expectedPathSeparator, url.getHandler().getPathSeparator());\n    }\n\n\n    /**\n     * Tests {@link com.mucommander.commons.file.FileURL#getRealm()} by ensuring that it returns the same URL only with the\n     * path stripped out.\n     * <p>\n     * <b>Important:</b> this method must be overridden for protocols that have a specific realm notion (like SMB) or\n     * else the test will fail.\n     *\n     * @throws MalformedURLException should not happen\n     */\n    @Test\n    public void testRealm() throws MalformedURLException {\n        assertEqualsAndHashCode(getURL(\"host\", \"/\"), getURL(\"host\", \"/path/to/file\").getRealm());\n    }\n\n\n    /**\n     * Ensures that the query part is parsed only if it should be, as specified by {@link #isQueryParsed()}.\n     *\n     * @throws MalformedURLException should not happen\n     */\n    @Test\n    public void testQueryParsing() throws MalformedURLException {\n        FileURL url = getURL(null, null, \"host\", -1, \"/path\", \"query&param=value\");\n        String query = url.getQuery();\n\n        if (isQueryParsed()) {\n            assertEquals(\"query&param=value\", query);\n        } else {\n            assert query == null;\n        }\n    }\n\n\n    /**\n     * Ensures that FileURL#getParent() works as expected.\n     *\n     * @throws MalformedURLException should not happen\n     */\n    @Test\n    public void testParent() throws MalformedURLException {\n        FileURL url = getURL(\"login\", \"password\", \"host\", 10000, \"/path/to\", \"query&param=value\");\n        url.setProperty(\"key\", \"value\");\n\n        FileURL parentURL = url.getParent();\n\n        // Test path and filename\n        assertPathEquals(getSchemePath(\"/path/\"), parentURL);\n        assertEquals(\"path\", parentURL.getFilename());\n\n        // Assert that schemes, hosts and ports match\n        assertEquals(url.getScheme(), parentURL.getScheme());\n        assertEquals(url.getHost(), parentURL.getHost());\n        assert url.getPort() == parentURL.getPort();\n\n        // Assert that credentials match\n        assertEquals(url.getCredentials(), parentURL.getCredentials());\n        assertEquals(url.getLogin(), parentURL.getLogin());\n        assertEquals(url.getPassword(), parentURL.getPassword());\n\n        // Assert that the sample property is in the parent URL\n        assertEquals(\"value\", parentURL.getProperty(\"key\"));\n\n        // Assert that handlers match\n        assertEquals(url.getHandler(), parentURL.getHandler());\n\n        // Assert that the query part is null\n        assert parentURL.getQuery() == null;\n\n        // One more time, the parent path is now \"/\"\n\n        url = parentURL;\n        parentURL = url.getParent();\n\n        // Test path and filename\n        assertPathEquals(getSchemePath(\"/\"), parentURL);\n        assert parentURL.getFilename() == null;\n\n        // The parent URL should now be null   \n\n        // Test path and filename\n        url = parentURL;\n        assert url.getParent() == null;\n    }\n\n\n    /**\n     * Parses URLs, some borderline but that we consider nonetheless valid, and ensures that they parse without error\n     * and that getters return proper part values.\n     *\n     * @throws MalformedURLException should not happen\n     */\n    @Test\n    public void testParsing() throws MalformedURLException {\n        // Test a sample URL with all parts used\n        getURL(\"login\", \"password\", \"host\", 10000, \"/path/to\", \"query\");\n\n        // Ensure that login and password parts can contain '@' characters without disrupting the parsing\n        getURL(\"login@domain.com\", \"password@domain.com\", \"host\", 10000, \"/path/to\", \"query\");\n\n        // Ensure that paths can contain '@' characters without disrupting the parsing\n        getURL(\"login\", \"password\", \"host\", 10000, \"/path@at/to@at\", \"query\");\n\n        // Ensure that empty port parts are tolerated\n        getURL(\"login\", \"password\", \"host\", -1, \"/path@at/to@at\", \"query\");\n    }\n\n    /**\n     * Ensure that non URL-safe characters in login and password parts are properly handled, both when parsing\n     * and representing URLs as string.\n     *\n     * @throws MalformedURLException should not happen\n     */\n    @Test\n    public void testCredentialsURLEncoding() throws MalformedURLException {\n        FileURL url = getRootURL();\n\n        String urlDecodedString = \":@&=+$,/?t%#[]\";\n        String urlEncodedString = \"%3A%40%26%3D%2B%24%2C%2F%3Ft%25%23%5B%5D\";\n\n        url.setCredentials(new Credentials(urlDecodedString, urlDecodedString));\n        String urlRep = url.getScheme() + \"://\" + urlEncodedString + \":\" + urlEncodedString + \"@\";\n        assertEquals(urlRep, url.toString(true, false));\n\n        url = FileURL.getFileURL(urlRep);\n        Credentials credentials = url.getCredentials();\n        assertEquals(credentials.getLogin(), urlDecodedString);\n        assertEquals(credentials.getPassword(), urlDecodedString);\n    }\n\n    /**\n     * Tests FileURL's getters and setters.\n     *\n     * @throws MalformedURLException should not happen\n     */\n    @Test\n    public void testAccessors() throws MalformedURLException {\n        FileURL url = getRootURL();\n\n        String scheme = getScheme();\n        Credentials credentials = new Credentials(\"login\", \"password\");\n        String host = \"host\";\n        int port = 10000;\n        String path = getSchemePath(\"/path/to\");\n        String query = \"query\";\n\n        url.setScheme(scheme);\n        url.setCredentials(credentials);\n        url.setHost(host);\n        url.setPort(port);\n        url.setPath(path);\n        url.setQuery(query);\n        url.setProperty(\"name\", \"value\");\n\n        assertEquals(scheme, url.getScheme());\n        assertTrue(credentials.equals(url.getCredentials(), true));\n        assertEquals(host, url.getHost());\n        assertEquals(port, url.getPort());\n        assertEquals(path, url.getPath());\n        assertEquals(query, url.getQuery());\n        assertEquals(\"to\", url.getFilename());\n        assertEquals(\"value\", url.getProperty(\"name\"));\n\n        // Test null values\n\n        url.setCredentials(null);\n        url.setHost(null);\n        url.setPort(-1);\n        url.setPath(\"/\");\n        url.setQuery(null);\n        url.setProperty(\"name\", null);\n\n        assertEquals(scheme, url.getScheme());\n        assert url.getCredentials() == null;\n        assert !url.containsCredentials();\n        assert url.getHost() == null;\n        assert -1 == url.getPort();\n        assertEquals(\"/\", url.getPath());\n        assert url.getQuery() == null;\n        assert url.getFilename() == null;\n        assert url.getProperty(\"name\") == null;\n        assertEquals((scheme + \"://\"), url.toString(true, false));\n\n        // Path cannot be null, the path is supposed to be \"/\" if a null or empty value is specified\n        url.setPath(null);\n        assertEquals(\"/\", url.getPath());\n        url.setPath(\"\");\n        assertEquals(\"/\", url.getPath());\n\n        // Path must always start with a leading '/', if the specified does not then a '/' is automatically added\n        url.setPath(\"path/to\");\n        assertEquals(\"/path/to\", url.getPath());\n    }\n\n    /**\n     * Tests {@link FileURL#getFilename()} method.\n     *\n     * @throws MalformedURLException should not happen\n     */\n    @Test\n    public void testFilename() throws MalformedURLException {\n        assertEquals(\"file\", getURL(\"/path/to/file\").getFilename());\n        assertEquals(\"file\", getURL(\"/path/to/file/\").getFilename());\n        assertEquals(\"path\", getURL(\"/path\").getFilename());\n        assertEquals(\"path\", getURL(\"/path/\").getFilename());\n        assert getRootURL().getFilename() == null : \"/\";\n\n        assertEquals((isQueryParsed() ? \"file\" : \"file?param=value\"), getURL(null, null, null, -1, \"/path/to/file\", \"param=value\").getFilename());\n    }\n\n    /**\n     * Tests FileURL's <code>toString</code> methods.\n     *\n     * @throws MalformedURLException should not happen\n     */\n    @Test\n    public void testStringRepresentation() throws MalformedURLException {\n        FileURL url = getURL(\"login\", \"password\", \"host\", 10000, \"/path\", \"query\");\n        String path = getSchemePath(\"/path\");\n        String urlString = getScheme() + \"://host:10000\" + path + \"?query\";\n\n        assertEquals(urlString, url.toString());\n        assertEquals(urlString, url.toString(false));\n        assertEquals(urlString, url.toString(false, false));\n\n        urlString = getScheme() + \"://login:password@host:10000\" + path + \"?query\";\n        assertEquals(urlString, url.toString(true));\n        assertEquals(urlString, url.toString(true, false));\n\n        urlString = getScheme() + \"://login:********@host:10000\" + path + \"?query\";\n        assertEquals(urlString, url.toString(true, true));\n    }\n\n\n    /**\n     * Tests <code>equals</code> methods.\n     *\n     * @throws MalformedURLException should not happen\n     */\n    @Test\n    public void testEquals() throws MalformedURLException {\n        // No query part, as it is not parsed by all schemes\n        FileURL url1 = getURL(\"login\", \"password\", \"host\", 10000, \"/path\", null);\n        url1.setProperty(\"name\", \"value\");\n        FileURL url2 = (FileURL) url1.clone();\n\n        // Assert that both URLs are equal\n        assertEqualsAndHashCode(url1, url2);\n        assertEqualsAndHashCode(url1, url2, true, true);\n\n        // Add a trailing path separator to one of the URL's path and assert they are still equal\n        url1.setPath(url1.getPath() + url1.getPathSeparator());\n        assertEqualsAndHashCode(url1, url2);\n        assertEqualsAndHashCode(url1, url2, true, true);\n\n        // Assert that having the port part set to the standart port is equivalent to not having a port part (-1)\n        url1.setPort(url1.getStandardPort());\n        url2.setPort(-1);\n        assertEqualsAndHashCode(url1, url2);\n        assertEqualsAndHashCode(url1, url2, true, true);\n\n        // Assert that the scheme comparison is case-insensitive\n        url1.setScheme(url1.getScheme().toUpperCase());\n        assertEqualsAndHashCode(url1, url2);\n        assertEqualsAndHashCode(url1, url2, true, true);\n\n        // Assert that the host comparison is case-insensitive\n        url1.setHost(url1.getHost().toUpperCase());\n        assertEqualsAndHashCode(url1, url2);\n        assertEqualsAndHashCode(url1, url2, true, true);\n\n        // Assert that the path comparison is case-sensitive\n        if (OsFamily.getCurrent().isCaseSensitiveFilesystem()) {\n            url1.setPath(url1.getPath().toUpperCase());\n            assertNotEquals(url1, url2);\n            assertNotEquals(url1, url2, true, true);\n        }\n\n        // Make both URLs equal again\n        url1.setPath(url2.getPath());\n        assertEqualsAndHashCode(url1, url2);\n        assertEqualsAndHashCode(url1, url2, true, true);\n\n        // Assert that the query comparison is case-sensitive\n        url1.setQuery(\"query\");\n        url2.setQuery(\"QUERY\");\n        assertNotEquals(url1, url2);\n        assertNotEquals(url1, url2, true, true);\n\n        // Make both URLs equal again\n        url1.setQuery(url2.getQuery());\n        assertEqualsAndHashCode(url1, url2);\n        assertEqualsAndHashCode(url1, url2, true, true);\n\n        // Assert that the credentials comparison is case-sensitive\n        url1.setCredentials(new Credentials(\"LOGIN\", \"password\"));\n        assertEqualsAndHashCode(url1, url2, false, false);\n        assertNotEquals(url1, url2, true, true);\n        url1.setCredentials(new Credentials(\"login\", \"PASSWORD\"));\n        assertEqualsAndHashCode(url1, url2, false, false);\n        assertNotEquals(url1, url2, true, true);\n\n        // Assert that URLs are equal if credentials comparison is disabled\n        assertEqualsAndHashCode(url1, url2, false, true);\n\n        // Make both URLs equal again\n        url1.setCredentials(new Credentials(\"login\", \"password\"));\n        assertEqualsAndHashCode(url1, url2, true, true);\n\n        // Assert that the properties comparison is case-sensitive\n        url1.setProperty(\"name\", null);\n        url1.setProperty(\"NAME\", \"value\");\n        assertEqualsAndHashCode(url1, url2, false, false);\n        assertNotEquals(url1, url2, true, true);\n        url1.setProperty(\"name\", \"VALUE\");\n        assertEqualsAndHashCode(url1, url2, false, false);\n        assertNotEquals(url1, url2, true, true);\n\n        // Assert that URLs are equal if properties comparison is disabled\n        assertEqualsAndHashCode(url1, url2, true, false);\n\n        // Make both URLs equal again\n        url1.setProperty(\"NAME\", null);\n        url1.setProperty(\"name\", \"value\");\n        assertEqualsAndHashCode(url1, url2, true, true);\n\n        // Assert that the properties comparison fails if an extra property is added to one of the URLs\n        url1.setProperty(\"name2\", \"value2\");\n        assertNotEquals(url1, url2, true, true);\n\n        // Assert that URLs are equal if properties comparison is disabled\n        assertEqualsAndHashCode(url1, url2, true, false);\n\n        // Make both URLs equal again\n        url1.setProperty(\"name2\", null);\n        assertEqualsAndHashCode(url1, url2, true, true);\n    }\n\n\n    /**\n     * Tests {@link FileURL#clone()}.\n     *\n     * @throws MalformedURLException should not happen\n     */\n    @Test\n    public void testClone() throws MalformedURLException {\n        FileURL url = getURL(\"login\", \"password\", \"host\", 10000, \"/path/to\", \"query\");\n        url.setProperty(\"name\", \"value\");\n\n        FileURL clonedURL = (FileURL) url.clone();\n\n        // Assert that both instances are equal according to FileURL#equals\n        assertEqualsAndHashCode(url, clonedURL);\n\n        // Assert that both URL's string representations (with credentials) are equal\n        assertEquals(url.toString(true), clonedURL.toString(true));\n\n        // Assert that both instances are not one and the same\n        assert url != clonedURL;\n\n        // Assert that the property has survived the cloning\n        assertEquals(\"value\", clonedURL.getProperty(\"name\"));\n    }\n\n    /**\n     * Tests a few invalid URLs and makes sure {@link FileURL#getFileURL} throws a <code>MalformedURLException</code>.\n     *\n     */\n    @Test\n    public void testInvalidURLs() {\n        // relative URLs\n        assert !canParse(\"relative\");\n        assert !canParse(\"C:\");\n        assert !canParse(\"scheme:/\");\n\n        // Invalid port (non-numeric)\n        assert !canParse(getScheme() + \"://host:port/path\");\n    }\n\n\n    //////////////////////\n    // Abstract methods //\n\n    /// ///////////////////\n\n    protected abstract String getScheme();\n\n    protected abstract int getDefaultPort();\n\n    protected abstract AuthenticationType getAuthenticationType();\n\n    protected abstract Credentials getGuestCredentials();\n\n    protected abstract String getPathSeparator();\n\n    protected abstract boolean isQueryParsed();\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/SimpleFileAttributesTest.java",
    "content": "package com.mucommander.commons.file;\n\nimport com.mucommander.commons.file.impl.local.LocalFileTest;\nimport org.junit.jupiter.api.Test;\nimport java.io.IOException;\n\n/**\n * A test case for {@link com.mucommander.commons.file.SimpleFileAttributes}.\n *\n * @see com.mucommander.commons.file.SimpleFileAttributes\n * @author Maxence Bernard\n */\npublic class SimpleFileAttributesTest {\n\n    /**\n     * Creates a SimpleFileAttributes instance from an AbstractFile and ensures that the values returned by\n     * SimpleFileAttributes' getters match those of AbstractFile.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testAccessors() throws IOException {\n        LocalFileTest lft = new LocalFileTest();\n\n        // File doesn't exist\n        AbstractFile tempFile = lft.getTemporaryFile();\n        assertAttributesMatch(tempFile, new SimpleFileAttributes(tempFile));\n\n        // File exists as a regular file\n        tempFile.mkfile();\n        assertAttributesMatch(tempFile, new SimpleFileAttributes(tempFile));\n\n        // File exists as a directory\n        tempFile.delete();\n        tempFile.mkdir();\n        assertAttributesMatch(tempFile, new SimpleFileAttributes(tempFile));\n    }\n\n    /**\n     * Asserts that the attributes of the given AbstractFile and SimpleFileAttributes match.\n     */\n    private void assertAttributesMatch(AbstractFile file, SimpleFileAttributes attrs) {\n        assert file.getAbsolutePath().equals(attrs.getPath());\n        assert file.exists() == attrs.exists();\n        assert file.getLastModifiedDate() == attrs.getLastModifiedDate();\n        assert file.getSize() == attrs.getSize();\n        assert file.isDirectory() == attrs.isDirectory();\n        assert file.getPermissions() == attrs.getPermissions();\n        assert file.getOwner() == null ? attrs.getOwner() == null : file.getOwner().equals(attrs.getOwner());\n        assert file.getGroup() == null ? attrs.getGroup() == null : file.getGroup().equals(attrs.getGroup());\n    }\n\n    /**\n     * Creates a SimpleFileAttributes instance with the no-arg constructor and ensures that the default values returned\n     * by SimpleFileAttributes' getters are as specified by {@link FileAttributes}.\n     */\n    @Test\n    public void testDefaultValues() {\n        SimpleFileAttributes attrs = new SimpleFileAttributes();\n        assert attrs.getPath() == null;\n        assert !attrs.exists();\n        assert 0 == attrs.getLastModifiedDate();\n        assert 0 ==  attrs.getSize();\n        assert !attrs.isDirectory();\n        assert attrs.getPermissions() == null;\n        assert attrs.getOwner() == null;\n        assert attrs.getGroup() == null;\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/archiver/ISOArchiverTest.java",
    "content": "package com.mucommander.commons.file.archiver;\n\nimport com.github.stephenc.javaisotools.iso9660.ISO9660Directory;\nimport com.github.stephenc.javaisotools.iso9660.ISO9660RootDirectory;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.impl.iso.IsoArchiveFile;\nimport com.mucommander.commons.file.impl.iso.MuCreateISOTest;\nimport org.junit.jupiter.api.*;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.InputStream;\nimport java.lang.reflect.Method;\nimport java.util.HashMap;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class ISOArchiverTest {\n    @TempDir\n    private static File tempDir1;\n    private static final HashMap<String, File> files = new HashMap<>();\n    private static File archiveFile;\n    private static ISOArchiver instance;\n\n\n    @BeforeAll\n    public static void setUpClass() throws Exception {\n        //Create a archive\n\n        File tempFile1 = MuCreateISOTest.createTempFile(\"tempFile1\", 10000);\n        files.put(tempFile1.getName(), tempFile1);\n        tempFile1.deleteOnExit();\n\n        File tempFile2 = MuCreateISOTest.createTempFile(\"tempFile2\", 20000);\n        files.put(tempFile2.getName(), tempFile2);\n        tempFile2.deleteOnExit();\n\n//        tempDir1 = File.createTempDir();\n//        tempDir1.deleteOnExit();\n\n        File tempFile3 = MuCreateISOTest.createTempFile(\"tempFile3\", 40000);\n        files.put(tempDir1.getName() + File.separator + tempFile3.getName(), tempFile3);\n        tempFile3.deleteOnExit();\n\n        archiveFile = File.createTempFile(\"MuCreateISOTest\", \".iso\");\n        archiveFile.deleteOnExit();\n\n        AbstractFile abstractArchiveFile = FileFactory.getFile(archiveFile.getPath());\n        instance = new ISOArchiver(abstractArchiveFile);\n\n        instance.createEntry(tempDir1.getName(), FileFactory.getFile(tempDir1.getPath()));\n        for (String filePath : files.keySet()) {\n            instance.createEntry(filePath, FileFactory.getFile(files.get(filePath).getPath()));\n        }\n\n        //Archive the files\n        instance.postProcess();\n    }\n\n    @AfterAll\n    public static void tearDownClass() {\n        for (File file : files.values()) {\n            file.delete();\n        }\n        archiveFile.delete();\n        files.clear();\n        instance = null;\n    }\n\n//    @BeforeEach\n//    public void setUpMethod() {\n//    }\n//\n//    @AfterEach\n//    public void tearDownMethod() {\n//    }\n\n    /**\n     * Test of createEntry method, of class ISOArchiver.\n     */\n    @Test\n    public void testCreateEntry() throws Exception {\n\n        //Get access to method in order to test if it correctly listed the files\n        Method method = instance.getClass().getDeclaredMethod(\"getParentDirectory\", String.class);\n        method.setAccessible(true);\n\n        for (String filePath : files.keySet()) {\n            String[] split = filePath.split(\"\\\\\\\\\");\n            StringBuilder path = new StringBuilder();\n            path.append(split[0]);\n            for (int i = 0; i < split.length; i++) {\n                if (i == 0) {\n                    Object invoke = method.invoke(instance, path.toString());\n                    //assert it is root dir\n                    assert invoke instanceof ISO9660RootDirectory;\n                } else {\n                    path.append(File.separator).append(split[i]);\n                    Object invoke = method.invoke(instance, path.toString());\n                    //assert it is not root dir\n                    assert !(invoke instanceof ISO9660RootDirectory);\n                    assert invoke instanceof ISO9660Directory;\n                    ISO9660Directory dir = (ISO9660Directory) invoke;\n                    //assert the name is correct of the parent directory\n                    assertEquals(dir.getName(), split[i - 1]);\n                }\n            }\n        }\n    }\n\n    /**\n     * Test of getProcessingFile method, of class ISOArchiver.\n     */\n    @Test\n    public void testGetProcessingFile() {\n        //Can't be sure which file is is\n        boolean found = false;\n        for (File file : files.values()) {\n            if (file.getName().equals(instance.getProcessingFile())) {\n                found = true;\n            }\n        }\n        assert found;\n    }\n\n    /**\n     * Test of totalWrittenBytes method, of class ISOArchiver.\n     */\n    @Test\n    public void testTotalWrittenBytes() {\n        long totalSize = 0;\n        for (File file : files.values()) {\n            totalSize += file.length();\n        }\n        assertEquals(instance.totalWrittenBytes(), totalSize);\n    }\n\n    /**\n     * Test of testWrittenBytesCurrentFile method, of class ISOArchiver.\n     */\n    @Test\n    public void testWrittenBytesCurrentFile() {\n        //Can't be sure which file is is\n        boolean found = false;\n        for (File file : files.values()) {\n            if (\n                    file.getName().equals(instance.getProcessingFile())\n                            && file.length() == instance.writtenBytesCurrentFile()) {\n                found = true;\n            }\n        }\n        assert found;\n    }\n\n    /**\n     * Test of currentFileLength method, of class ISOArchiver.\n     */\n    @Test\n    public void testCurrentFileLength() {\n        // Can't be sure which file as is\n        boolean found = false;\n        for (File file : files.values()) {\n            if (file.getName().equals(instance.getProcessingFile()) && file.length() == instance.currentFileLength()) {\n                found = true;\n            }\n        }\n        assert found;\n    }\n\n    /**\n     * Test of postProcess method, of class ISOArchiver.\n     */\n    //@Test // TODO WRONG TEST\n    public void testPostProcess() throws Exception {\n        //instance.postProcess(); was called in class setup\n\n        //complete check of file content\n        IsoArchiveFile archive = new IsoArchiveFile(FileFactory.getFile(archiveFile.getPath()));\n\n        for (String fileName : files.keySet()) {\n            // File that should be saved in the archive\n            AbstractFile archiveEntryFile = archive.getArchiveEntryFile(fileName);\n            // Archive entry inputstream\n            FileInputStream fis = new FileInputStream(files.get(fileName));\n            InputStream is = archiveEntryFile.getInputStream();\n            // See if file content is equal\n            assertEquals(is.available(), fis.available());\n            while (fis.available() > 0) {\n                //See if data is identical\n                assertEquals(is.read(), fis.read());\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/filter/ExtensionFilenameFilterTest.java",
    "content": "package com.mucommander.commons.file.filter;\n\nimport org.junit.jupiter.api.Test;\n\n/**\n * Tests the {@link ExtensionFilenameFilter} class.\n * @author Nicolas Rinaudo\n */\npublic class ExtensionFilenameFilterTest {\n    /** Filter uses for all tests. */\n    private static ExtensionFilenameFilter filter = new ExtensionFilenameFilter(new String[] {\".zip\", \".jar\", \".war\", \".wal\", \".wmz\",\n                                                                                              \".xpi\", \".ear\", \".sar\", \".odt\", \".ods\",\n                                                                                              \".odp\", \".odg\", \".odf\"});\n\n    /**\n     * Runs a set of tests.\n     * @param caseSensitive whether to test case-sensitive filters or not.\n     */\n    private void test(boolean caseSensitive) {\n        filter.setCaseSensitive(caseSensitive);\n\n        assert filter.accept(\"test.zip\");\n        assert filter.accept(\"test.jar\");\n        assert filter.accept(\"test.war\");\n        assert filter.accept(\"test.wal\");\n        assert filter.accept(\"test.wmz\");\n        assert filter.accept(\"test.xpi\");\n        assert filter.accept(\"test.ear\");\n        assert filter.accept(\"test.sar\");\n        assert filter.accept(\"test.odt\");\n        assert filter.accept(\"test.ods\");\n        assert filter.accept(\"test.odp\");\n        assert filter.accept(\"test.odg\");\n        assert filter.accept(\"test.odf\");\n\n        assert filter.accept(\"test.ZIP\") != caseSensitive;\n        assert filter.accept(\"test.JAR\") != caseSensitive;\n        assert filter.accept(\"test.WAR\") != caseSensitive;\n        assert filter.accept(\"test.WAL\") != caseSensitive;\n        assert filter.accept(\"test.WMZ\") != caseSensitive;\n        assert filter.accept(\"test.XPI\") != caseSensitive;\n        assert filter.accept(\"test.EAR\") != caseSensitive;\n        assert filter.accept(\"test.SAR\") != caseSensitive;\n        assert filter.accept(\"test.ODT\") != caseSensitive;\n        assert filter.accept(\"test.ODS\") != caseSensitive;\n        assert filter.accept(\"test.ODP\") != caseSensitive;\n        assert filter.accept(\"test.ODG\") != caseSensitive;\n        assert filter.accept(\"test.ODF\") != caseSensitive;\n\n        assert !filter.accept(\"test.tar\");\n        assert !filter.accept(\"test.tar.gz\");\n        assert !filter.accept(\"test.tgz\");\n        assert !filter.accept(\"test.tar.bz2\");\n        assert !filter.accept(\"test.tbz2\");\n        assert !filter.accept(\"test.gz\");\n        assert !filter.accept(\"test.bz2\");\n        assert !filter.accept(\"test.iso\");\n        assert !filter.accept(\"test.nrg\");\n        assert !filter.accept(\"test.a\");\n        assert !filter.accept(\"test.ar\");\n        assert !filter.accept(\"test.deb\");\n        assert !filter.accept(\"test.lst\");\n\n        assert !filter.accept(\"test\");\n        assert !filter.accept(\"\");\n    }\n\n    /**\n     * Tests case-sensitive filtering.\n     */\n    @Test\n    public void testCaseSensitive() {\n        test(true);\n    }\n\n    /**\n     * Tests case-insensitive filtering.\n     */\n    @Test\n    public void testCaseInsensitive() {\n        test(false);\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/impl/ProxyFileTest.java",
    "content": "package com.mucommander.commons.file.impl;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.AbstractFileTest;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.FileOperation;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\n\n/**\n * An {@link AbstractFileTest} implementation for {@link ProxyFile}, with some additional\n * test methods.\n *\n * @author Maxence Bernard\n */\npublic class ProxyFileTest extends AbstractFileTest {\n\n    ////////////////////////////////////\n    // ConditionalTest implementation //\n    ////////////////////////////////////\n\n    public boolean isEnabled() {\n        return true;\n    }\n\n\n    /////////////////////////////////////\n    // AbstractFileTest implementation //\n    /////////////////////////////////////\n\n    @Override\n    public AbstractFile getTemporaryFile() throws IOException {\n        // Returns a ProxyFile instance proxying a LocalFile ; the kind of proxied file should not matter as long as it\n        // passes AbstractFileTest.\n        return new ProxyFile(FileFactory.getTemporaryFile(getClass().getName(), false)) {\n            // Note: a ProxyFile with no overridden method serves absolutely no purpose whatsoever\n        };\n    }\n\n    @Override\n    public FileOperation[] getSupportedOperations() {\n        return new FileOperation[] {\n            FileOperation.READ_FILE,\n            FileOperation.RANDOM_READ_FILE,\n            FileOperation.WRITE_FILE,\n            FileOperation.APPEND_FILE,\n            FileOperation.RANDOM_WRITE_FILE,\n            FileOperation.CREATE_DIRECTORY,\n            FileOperation.LIST_CHILDREN,\n            FileOperation.DELETE,\n            FileOperation.RENAME,\n            FileOperation.CHANGE_DATE,\n            FileOperation.CHANGE_PERMISSION,\n            FileOperation.GET_FREE_SPACE,\n            FileOperation.GET_TOTAL_SPACE\n        };\n    }\n\n\n    //@Test\n    @Override\n    public void testUnsupportedFileOperationAnnotations() {\n    }\n\n    //@Test\n    @Override\n    public void testSupportedFileOperations() {\n    }\n\n    //@Test\n    @Override\n    public void testFileInstanceCaching() {\n        // This test can't pass as ProxyFile instance are not cached, only the underlying protocol file.\n    }\n\n\n\n    /**\n     * Asserts that all public, non-final and non-static <code>AbstractFile</code> methods are overridden by\n     * <code>ProxyFile</code>.\n     */\n    @Test\n    public void testAllMethodsOverridden() {\n        Class<?> proxyFileClass = ProxyFile.class;\n        Class<?> abstractFileClass = AbstractFile.class;\n\n        // This array will contain all AbstractFile public methods, including the ones defined by parent classes\n        // (java.lang.Object), and including static and final ones.\n        Method abstractFileMethods[] = abstractFileClass.getMethods();\n        Method proxyFileMethod;\n\n        for (Method abstractFileMethod : abstractFileMethods) {\n            // Skip:\n            // - methods that are not declared by AbstractFile (e.g. java.lang.Object methods)\n            // - static methods\n            // - final methods\n            if (!abstractFileMethod.getDeclaringClass().equals(abstractFileClass)\n                    || (abstractFileMethod.getModifiers() & (Modifier.STATIC | Modifier.FINAL)) != 0)\n                continue;\n\n            try {\n                proxyFileMethod = proxyFileClass.getMethod(abstractFileMethod.getName(), abstractFileMethod.getParameterTypes());\n            }\n            catch (Exception e) {    // NoSuchMethodException, SecurityException\n                proxyFileMethod = null;\n            }\n\n            assert proxyFileMethod != null && (proxyFileMethod.getDeclaringClass().equals(proxyFileClass)):\n                    abstractFileMethod.getName() + \" not overridden by \" + proxyFileClass.getName();\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/impl/TestFile.java",
    "content": "package com.mucommander.commons.file.impl;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.DummyFile;\nimport com.mucommander.commons.file.FileURL;\n\nimport java.net.MalformedURLException;\n\n/**\n * TestFile is an {@link AbstractFile} that is used in unit tests.\n * This is an implementation of virtual file. Several methods are overridden to\n * return data passed in constructor.\n * @author Mariusz Jakubowski\n *\n */\npublic class TestFile extends DummyFile {\n    \n    private boolean isDir;\n    private long size;\n    private long date;\n    private AbstractFile parent;\n\n    public TestFile(String name, boolean isdir, long size, long date, AbstractFile parent) throws MalformedURLException {\n        super(FileURL.getFileURL(name));\n        this.isDir = isdir;\n        this.size = size;\n        this.date = date;\n        this.parent = parent;\n    }\n    \n    @Override\n    public boolean isDirectory() {\n        return isDir;\n    }\n    \n    @Override\n    public long getSize() {\n        return size;\n    }\n \n    @Override\n    public long getLastModifiedDate() {\n        return date;\n    }\n    \n    @Override\n    public AbstractFile getParent() {\n        return parent;\n    }\n    \n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/impl/ftp/FTPFileTest.java",
    "content": "package com.mucommander.commons.file.impl.ftp;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.AbstractFileTest;\nimport com.mucommander.commons.file.FileOperation;\n\nimport java.io.File;\nimport java.io.IOException;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.io.TempDir;\n\n/**\n * An {@link AbstractFileTest} implementation for {@link com.mucommander.commons.file.impl.ftp.FTPFile}.\n * The FTP temporary folder where test files are created is defined by the {@link #TEMP_FOLDER_PROPERTY} system property.\n *\n * @author Maxence Bernard\n */\npublic class FTPFileTest extends AbstractFileTest {\n    @TempDir\n    private static File tempDir;\n\n    /** The system property that holds the URI to the temporary FTP folder */\n    public final static String TEMP_FOLDER_PROPERTY = \"test_properties.ftp_test.temp_folder\";\n\n    /** Base temporary folder */\n    private static AbstractFile tempFolder;\n\n    static {\n        // Attribute caching can be enabled or disabled, it doesn't matter, tests should pass in both cases\n        // Todo: use JUnit's DataPoint to test both cases (with and without caching) but it requires Java 1.5's\n        // annotations which we don't use for java 1.4 backward compatibility.\n//        FTPFile.setAttributeCachingPeriod(5000);\n    }\n    @BeforeAll\n    public static void setupTemporaryFolder() {\n//        tempFolder = FileFactory.getFile(Files.createTempDir().getAbsolutePath());\n        tempFolder = getTemporaryFolder(tempDir);\n    }\n    \n\n    @Override\n    public AbstractFile getTemporaryFile() throws IOException {\n        return tempFolder.getDirectChild(getPseudoUniqueFilename(FTPFileTest.class.getName()));\n    }\n\n    @Override\n    protected Class <? extends AbstractFile> getTestFileClass() {\n        return FTPFile.class;\n    }\n\n    @Override\n    public FileOperation[] getSupportedOperations() {\n        return new FileOperation[] {\n            FileOperation.READ_FILE,\n            FileOperation.WRITE_FILE,\n            FileOperation.APPEND_FILE,\n            FileOperation.CREATE_DIRECTORY,\n            FileOperation.LIST_CHILDREN,\n            FileOperation.DELETE,\n            FileOperation.RENAME,\n            FileOperation.CHANGE_DATE,\n            FileOperation.CHANGE_PERMISSION\n        };\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/impl/ftp/FTPFileURLTest.java",
    "content": "package com.mucommander.commons.file.impl.ftp;\n\nimport com.mucommander.commons.file.AuthenticationType;\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileURLTestCase;\n\n/**\n * A {@link FileURLTestCase} implementation for FTP URLs.\n *\n * @author Maxence Bernard\n */\npublic class FTPFileURLTest extends FileURLTestCase {\n\n    ////////////////////////////////////\n    // FileURLTestCase implementation //\n    ////////////////////////////////////\n\n    @Override\n    protected String getScheme() {\n        return \"ftp\";\n    }\n\n    @Override\n    protected int getDefaultPort() {\n        return 21;\n    }\n\n    @Override\n    protected AuthenticationType getAuthenticationType() {\n        return AuthenticationType.AUTHENTICATION_REQUIRED;\n    }\n\n    @Override\n    protected Credentials getGuestCredentials() {\n        return new Credentials(\"anonymous\", \"someuser@mucommander.com\");\n    }\n\n    @Override\n    protected String getPathSeparator() {\n        return \"/\";\n    }\n\n    @Override\n    protected boolean isQueryParsed() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/impl/hadoop/HDFSFileTest.java",
    "content": "package com.mucommander.commons.file.impl.hadoop;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.AbstractFileTest;\nimport com.mucommander.commons.file.FileOperation;\nimport com.mucommander.commons.file.impl.sftp.SFTPFile;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport java.io.File;\nimport java.io.IOException;\n\n/**\n * @author Maxence Bernard\n */\npublic class HDFSFileTest extends AbstractFileTest {\n    @TempDir\n    private static File tempDir;\n\n    /** The system property that holds the URI to the temporary HDFS folder */\n    public final static String TEMP_FOLDER_PROPERTY = \"test_properties.hdfs_test.temp_folder\";\n\n    /** Base temporary folder */\n    private static AbstractFile tempFolder;\n\n    @BeforeAll\n    public static void setupTemporaryFolder() {\n        //tempFolder = FileFactory.getFile(\"hdfs://emoroozv@hbase01dev/user/emorozov\");\n\t\ttempFolder = getTemporaryFolder(tempDir);\n    }\n\n\n    @Override\n    public AbstractFile getTemporaryFile() throws IOException {\n        return tempFolder.getDirectChild(getPseudoUniqueFilename(HDFSFileTest.class.getName()));\n    }\n\n    @Override\n    protected Class <? extends AbstractFile> getTestFileClass() {\n        return HDFSFile.class;\n    }\n\n    @Override\n    public FileOperation[] getSupportedOperations() {\n        return new FileOperation[] {\n            FileOperation.READ_FILE,\n            FileOperation.RANDOM_READ_FILE,\n            FileOperation.WRITE_FILE,\n            FileOperation.CREATE_DIRECTORY,\n            FileOperation.LIST_CHILDREN,\n            FileOperation.DELETE,\n            FileOperation.RENAME,\n            FileOperation.CHANGE_DATE,\n            FileOperation.CHANGE_PERMISSION,\n            FileOperation.GET_BLOCKSIZE,\n            FileOperation.GET_REPLICATION,\n            FileOperation.CHANGE_REPLICATION,\n            FileOperation.GET_TOTAL_SPACE\n        };\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/impl/hadoop/HDFSFileURLTest.java",
    "content": "package com.mucommander.commons.file.impl.hadoop;\n\nimport com.mucommander.commons.file.AuthenticationType;\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileURLTestCase;\n\n/**\n * A {@link FileURLTestCase} implementation for Hadoop HDFS URLs.\n *\n * @author Maxence Bernard\n */\npublic class HDFSFileURLTest extends FileURLTestCase {\n\n    @Override\n    protected String getScheme() {\n        return \"hdfs\";\n    }\n\n    @Override\n    protected int getDefaultPort() {\n        return 8020;\n    }\n\n    @Override\n    protected AuthenticationType getAuthenticationType() {\n        return AuthenticationType.AUTHENTICATION_OPTIONAL;\n    }\n\n    @Override\n    protected Credentials getGuestCredentials() {\n        return null;\n    }\n\n    @Override\n    protected String getPathSeparator() {\n        return \"/\";\n    }\n\n    @Override\n    protected boolean isQueryParsed() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/impl/http/HTTPFileURLTest.java",
    "content": "package com.mucommander.commons.file.impl.http;\n\nimport com.mucommander.commons.file.AuthenticationType;\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileURLTestCase;\n\n/**\n * A {@link FileURLTestCase} implementation for HTTP URLs.\n *\n * @author Maxence Bernard\n */\npublic class HTTPFileURLTest extends FileURLTestCase {\n\n    ////////////////////////////////////\n    // FileURLTestCase implementation //\n    ////////////////////////////////////\n\n    @Override\n    protected String getScheme() {\n        return \"http\";\n    }\n\n    @Override\n    protected int getDefaultPort() {\n        return 80;\n    }\n\n    @Override\n    protected AuthenticationType getAuthenticationType() {\n        return AuthenticationType.AUTHENTICATION_OPTIONAL;\n    }\n\n    @Override\n    protected Credentials getGuestCredentials() {\n        return null;\n    }\n\n    @Override\n    protected String getPathSeparator() {\n        return \"/\";\n    }\n\n    @Override\n    protected boolean isQueryParsed() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/impl/http/HTTPSFileURLTest.java",
    "content": "package com.mucommander.commons.file.impl.http;\n\nimport com.mucommander.commons.file.AuthenticationType;\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileURLTestCase;\n\n/**\n * A {@link FileURLTestCase} implementation for HTTPS URLs.\n *\n * @author Maxence Bernard\n */\npublic class HTTPSFileURLTest extends FileURLTestCase {\n\n    ////////////////////////////////////\n    // FileURLTestCase implementation //\n    ////////////////////////////////////\n\n    @Override\n    protected String getScheme() {\n        return \"https\";\n    }\n\n    @Override\n    protected int getDefaultPort() {\n        return 443;\n    }\n\n    @Override\n    protected AuthenticationType getAuthenticationType() {\n        return AuthenticationType.AUTHENTICATION_OPTIONAL;\n    }\n\n    @Override\n    protected Credentials getGuestCredentials() {\n        return null;\n    }\n\n    @Override\n    protected String getPathSeparator() {\n        return \"/\";\n    }\n\n    @Override\n    protected boolean isQueryParsed() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/impl/iso/MuCreateISOTest.java",
    "content": "package com.mucommander.commons.file.impl.iso;\n\nimport com.github.stephenc.javaisotools.eltorito.impl.ElToritoConfig;\nimport com.github.stephenc.javaisotools.iso9660.ConfigException;\nimport com.github.stephenc.javaisotools.iso9660.ISO9660Directory;\nimport com.github.stephenc.javaisotools.iso9660.ISO9660RootDirectory;\nimport com.github.stephenc.javaisotools.iso9660.impl.ISO9660Config;\nimport com.github.stephenc.javaisotools.iso9660.impl.ISOImageFileHandler;\nimport com.github.stephenc.javaisotools.joliet.impl.JolietConfig;\nimport com.github.stephenc.javaisotools.rockridge.impl.RockRidgeConfig;\nimport com.github.stephenc.javaisotools.sabre.HandlerException;\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.PrintWriter;\nimport java.nio.file.Files;\nimport java.util.HashMap;\nimport java.util.Random;\n\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n\npublic class MuCreateISOTest {\n\tprivate final static Logger logger = LoggerFactory.getLogger(MuCreateISOTest.class);\n    private static final HashMap<String, File> files = new HashMap<>();\n    private static File archiveFile;\n    private static MuCreateISO instance;\n    \n\n    @BeforeAll\n    public static void setUpClass() throws Exception {\n        //Setup testing files and dirs\n        ISO9660RootDirectory root = new ISO9660RootDirectory();\n        File tempFile1 = createTempFile(\"tempFile1\", 10000);\n        root.addFile(tempFile1);\n        files.put(tempFile1.getName(), tempFile1);\n        tempFile1.deleteOnExit();\n\n        File tempFile2 = createTempFile(\"tempFile2\", 20000);\n        root.addFile(tempFile2);\n        files.put(tempFile2.getName(), tempFile2);\n        tempFile2.deleteOnExit();\n\n        //File tempDir1 = Files.createTempDir();\n        File tempDir1 = Files.createTempDirectory(null).toFile();\n        ISO9660Directory dir = root.addDirectory(tempDir1);\n        tempDir1.deleteOnExit();\n\n        File tempFile3 = createTempFile(\"tempFile3\", 40000);\n        dir.addFile(tempFile3);\n        files.put(tempDir1.getName() + File.separator + tempFile3.getName(), tempFile3);\n        tempFile3.deleteOnExit();\n        \n        //Create archive file\n        archiveFile = File.createTempFile(\"MuCreateISOTest\", \".iso\");\n        archiveFile.deleteOnExit();\n        \n        //Setup iso archiver\n        ISO9660Config iso9660Config = new ISO9660Config();\n        try {\n            iso9660Config.allowASCII(false);\n            iso9660Config.setInterchangeLevel(1);\n            iso9660Config.restrictDirDepthTo8(false);\n            iso9660Config.setPublisher(System.getProperty(\"user.name\"));\n            iso9660Config.setVolumeID(archiveFile.getName());\n            iso9660Config.setDataPreparer(System.getProperty(\"user.name\"));\n            iso9660Config.forceDotDelimiter(true);\n        } catch (ConfigException ex) {\n            logger.error(\"\", ex);\n        }\n        \n        RockRidgeConfig rrConfig = new RockRidgeConfig();\n        rrConfig.setMkisofsCompatibility(false);\n        rrConfig.hideMovedDirectoriesStore(true);\n        rrConfig.forcePortableFilenameCharacterSet(true);\n        \n        JolietConfig jolietConfig = new JolietConfig();\n        try {\n            if(iso9660Config.getPublisher() instanceof String){\n                jolietConfig.setPublisher((String) iso9660Config.getPublisher());\n            } else {\n                try {\n                    jolietConfig.setPublisher((File) iso9660Config.getPublisher());\n                } catch (HandlerException ex) {\n                    logger.error(\"\",ex);\n                }\n            } \n            jolietConfig.setVolumeID(iso9660Config.getVolumeID());\n            jolietConfig.forceDotDelimiter(true);\n        } catch (ConfigException ex) {\n        \tlogger.error(\"\",ex);\n        }\n        \n        ElToritoConfig elToritoConfig = null;\n        \n        instance = new MuCreateISO(new ISOImageFileHandler(archiveFile), root);\n        //testProcess will check if it actually did it correctly, but other tests \n        //need it to also have been ran\n        instance.process(iso9660Config, rrConfig, jolietConfig, elToritoConfig);\n    }\n\n    @AfterAll\n    public static void tearDownClass() {\n        for(File file : files.values()){\n            file.delete();\n        }\n        archiveFile.delete();\n        files.clear();\n        instance = null;\n    }\n\n    \n    //Create a temp file with data within the ASCII range\n    public static File createTempFile(String name, int fileSize){\n        File file = null;\n        try {\n            file = File.createTempFile(name, \"test\");\n            //Make sure data is always the same\n            Random random = new Random(fileSize);\n            //Generate data to be filled into the file\n            byte[] chars = new byte[fileSize];\n            random.nextBytes(chars);\n            \n            PrintWriter pw = new PrintWriter(file);\n\n            for (byte aChar : chars) {\n                //Since java use signed bytes, add 128 in order to\n                //get a byte range from 0 to 255\n                pw.write(aChar + 128);\n            }\n            pw.flush();\n            pw.close();\n            \n        } catch (IOException ex) {\n        \tlogger.error(\"\",ex);\n        }\n        return file;\n    }\n    \n    \n\n    /**\n     * Test of process method, of class MuCreateISO.\n     */\n    @Test\n    public void testProcess() throws Exception {\n        //complete check of file content\n        IsoArchiveFile archive = new IsoArchiveFile(FileFactory.getFile(archiveFile.getPath()));\n        \n        for (String fileName : files.keySet()){\n            //File that should be saved in the archive\n            AbstractFile archiveEntryFile = archive.getArchiveEntryFile(fileName);\n            //See if file actually exists\n            assert archiveEntryFile.exists();\n            //Archive entry inputstream\n            FileInputStream fis = new FileInputStream(files.get(fileName));\n            InputStream is = archiveEntryFile.getInputStream();\n            //See if file content is equal\n            assertEquals(is.available(), fis.available());\n            while(fis.available() > 0){\n                //See if data is identical\n                assertEquals(is.read(), fis.read());\n            }\n        }\n    }\n\n    /**\n     * Test of getProcessingFile method, of class MuCreateISO.\n     */\n    @Test\n    public void testGetProcessingFile() {\n        //Can't be sure which file is is\n        boolean found = false;\n        for(File file : files.values()){\n            if(file.getName().equals(instance.getProcessingFile())){\n                found = true;\n            }\n        }\n        assert found;\n    }\n\n    /**\n     * Test of totalWrittenBytes method, of class MuCreateISO.\n     */\n    @Test\n    public void testTotalWrittenBytes() {\n        long totalSize = 0;\n        for(File file : files.values()){\n            totalSize += file.length();\n        }\n        assertEquals(instance.totalWrittenBytes(), totalSize);\n    }\n\n    @Test\n    public void testWrittenBytesCurrentFile() {\n        //Can't be sure which file is is\n        boolean found = false;\n        for(File file : files.values()){\n            if (file.getName().equals(instance.getProcessingFile()) && file.length() == instance.writtenBytesCurrentFile()) {\n                found = true;\n            }\n        }\n        assert found;\n    }\n\n    /**\n     * Test of currentFileLength method, of class MuCreateISO.\n     */\n    @Test\n    public void testCurrentFileLength() {\n        //Can't be sure which file is is\n        boolean found = false;\n        for(File file : files.values()){\n            if(file.getName().equals(instance.getProcessingFile()) && file.length() == instance.currentFileLength()) {\n                found = true;\n            }\n        }\n        assert found;\n    }\n    \n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/impl/local/LocalFileTest.java",
    "content": "package com.mucommander.commons.file.impl.local;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.AbstractFileTest;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.FileOperation;\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.regex.Matcher;\n\n\n/**\n * An {@link AbstractFileTest} implementation for {@link LocalFile}.\n *\n * @author Maxence Bernard\n */\n@Slf4j\npublic class LocalFileTest extends AbstractFileTest {\n\n    @Override\n    public AbstractFile getTemporaryFile() throws IOException {\n        return FileFactory.getTemporaryFile(getClass().getName(), false);\n    }\n\n    @Override\n    public FileOperation[] getSupportedOperations() {\n        return new FileOperation[] {\n            FileOperation.READ_FILE,\n            FileOperation.RANDOM_READ_FILE,\n            FileOperation.WRITE_FILE,\n            FileOperation.APPEND_FILE,\n            FileOperation.RANDOM_WRITE_FILE,\n            FileOperation.CREATE_DIRECTORY,\n            FileOperation.LIST_CHILDREN,\n            FileOperation.DELETE,\n            FileOperation.RENAME,\n            FileOperation.CHANGE_DATE,\n            FileOperation.CHANGE_PERMISSION,\n            FileOperation.GET_FREE_SPACE,\n            FileOperation.GET_TOTAL_SPACE\n        };\n    }\n\n    /**\n     * Asserts that a file can be renamed to a filename variation of the same file.\n     *\n     * @throws IOException should not normally happen\n     * @throws NoSuchAlgorithmException should not happen\n     */\n    @Test\n    public void testRenameToCaseVariation() throws IOException, NoSuchAlgorithmException {\n        // First test with a regular file\n        createFile(tempFile, 1);\n        AbstractFile destFile = tempFile.getParent().getDirectChild(tempFile.getName().toUpperCase());\n        deleteWhenFinished(destFile);\n\n        tempFile.renameTo(destFile);\n        assert !destFile.isSymlink();          // Leave me\n\n        // Repeat the test with a directory\n        destFile.delete();\n        tempFile.mkdir();\n\n        tempFile.renameTo(destFile);\n        assert !destFile.isSymlink();          // Leave me\n    }\n\n    /**\n     * Asserts that {@link com.mucommander.commons.file.impl.local.LocalFile#getUserHome()} returns a file that is not null,\n     * is a directory, and exists, and that '~' can be resolved as the user home folder.\n     *\n     * @throws IOException should not happen \n     */\n    @Test\n    public void testUserHome() throws IOException {\n        AbstractFile homeFolder = LocalFile.getUserHome();\n        assert homeFolder != null;\n        assert homeFolder.isDirectory();\n        assert homeFolder.exists();\n\n        assert homeFolder.equals(FileFactory.getFile(\"~\"));\n        assert homeFolder.getChild(\"blah\").equals(FileFactory.getFile(\"~\").getChild(\"blah\"));\n    }\n\n    /**\n     * Tests methods related to root drives (e.g. C:\\).\n     */\n    @Test\n    public void testRootDriveMethods() {\n        // The following test simply assert that the method doesn't produce an uncaught exception.\n        LocalFile.hasRootDrives();\n\n        LocalFile localFile = tempFile.getAncestor(LocalFile.class);\n        if (localFile != null) {\n            localFile.guessRemovableDrive();\n        }\n    }\n\n    /**\n     * Asserts that {@link com.mucommander.commons.file.impl.local.LocalFile#getVolumeInfo()} returns the same values as\n     * {@link com.mucommander.commons.file.impl.local.LocalFile#getTotalSpace()}\n     * and {@link com.mucommander.commons.file.impl.local.LocalFile#getFreeSpace()}.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testVolumeInfo() throws IOException {\n        long[] volumeInfo = ((LocalFile)tempFile).getVolumeInfo();\n\n        assert volumeInfo != null;\n        assert volumeInfo[0] == tempFile.getTotalSpace();\n        assert volumeInfo[1] == tempFile.getFreeSpace();\n    }\n\n    /**\n     * Tests the volumes returned by {@link LocalFile#getVolumes()} by calling {@link #testVolume(AbstractFile)} for\n     * each of them.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testVolumes() throws IOException {\n        AbstractFile[] volumes = LocalFile.getVolumes();\n\n        assert volumes.length > 0;\n\n        for (AbstractFile volume : volumes) {\n            log.info(\"Volume {}\", volume);\n            String name = volume.toString();\n            if (name.equals(\"/sys/kernel/debug/\") || name.equals(\"/boot/efi/\")) {\n            \tcontinue;\n            }\n            testVolume(volume);\n        }\n    }\n\n    /**\n     * Tests the regex pattern\n     */\n    @Test\n    public void testDrivePattern() {\n        Matcher matcher = LocalFile.DRIVE_ROOT_PATTERN.matcher(\"C:\\\\\");\n        assert matcher.matches();\n\n        matcher = LocalFile.DRIVE_ROOT_PATTERN.matcher(\"C:\");\n        assert !matcher.matches();\n\n        matcher = LocalFile.DRIVE_ROOT_PATTERN.matcher(\"C:\\\\blah\");\n        assert !matcher.matches();\n        matcher.reset();\n        assert matcher.find();\n\n        matcher = LocalFile.DRIVE_ROOT_PATTERN.matcher(\"/blah/C:\\\\\");\n        assert !matcher.matches();\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/impl/local/LocalFileURLTest.java",
    "content": "package com.mucommander.commons.file.impl.local;\n\nimport com.mucommander.commons.file.AuthenticationType;\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileURL;\nimport com.mucommander.commons.file.FileURLTestCase;\nimport com.mucommander.commons.runtime.OsFamily;\nimport org.junit.jupiter.api.Test;\n\nimport java.net.MalformedURLException;\n\n/**\n * A {@link FileURLTestCase} implementation for local file URLs.\n *\n * @author Maxence Bernard\n */\npublic class LocalFileURLTest extends FileURLTestCase {\n\n    /**\n     * Tests the resolution of local paths (e.g. /path/to/file). This test is system-dependant.\n     *\n     * @throws MalformedURLException should not happen\n     */\n    @Test\n    public void testLocalPathParsing() throws MalformedURLException {\n        FileURL url;\n        // For OSes that use backslash as a path separator and have a notion of 'root drives' like Windows (C:\\ D:\\ ...).\n        if(\"\\\\\".equals(getPathSeparator())) {\n            assert \"\\\\\".equals(getPathSeparator());\n\n            url = FileURL.getFileURL(\"C:\\\\\");\n            assert \"file\".equals(url.getScheme());\n            assert \"localhost\".equals(url.getHost());\n            assert \"/C:\\\\\".equals(url.getPath());\n\n            url = FileURL.getFileURL(\"C:\\\\dir\\\\file\");\n            assert \"file\".equals(url.getScheme());\n            assert \"localhost\".equals(url.getHost());\n            assert \"/C:\\\\dir\\\\file\".equals(url.getPath());\n            assert \"file\".equals(url.getFilename());\n\n            url = url.getParent();\n            assert \"file\".equals(url.getScheme());\n            assert \"localhost\".equals(url.getHost());\n            assert \"/C:\\\\dir\\\\\".equals(url.getPath());\n            assert \"dir\".equals(url.getFilename());\n\n            url = FileURL.getFileURL(\"C:\\\\direc/tory\");\n            assert \"file\".equals(url.getScheme());\n            assert \"localhost\".equals(url.getHost());\n            assert \"/C:\\\\direc/tory\".equals(url.getPath());\n            assert \"direc/tory\".equals(url.getFilename());\n\n            // Test forward-separated paths which are also supported\n\n            url = FileURL.getFileURL(\"C:/\");\n            assert \"file\".equals(url.getScheme());\n            assert \"localhost\".equals(url.getHost());\n            assert \"/C:\\\\\".equals(url.getPath());\n\n            url = FileURL.getFileURL(\"C:/dir/file\");\n            assert \"file\".equals(url.getScheme());\n            assert \"localhost\".equals(url.getHost());\n            assert \"/C:\\\\dir\\\\file\".equals(url.getPath());\n            assert \"file\".equals(url.getFilename());\n\n            url = url.getParent();\n            assert \"file\".equals(url.getScheme());\n            assert \"localhost\".equals(url.getHost());\n            assert \"/C:\\\\dir\\\\\".equals(url.getPath());\n            assert \"dir\".equals(url.getFilename());\n\n            url = FileURL.getFileURL(\"C:/direc\\\\tory\");\n            assert \"file\".equals(url.getScheme());\n            assert \"localhost\".equals(url.getHost());\n            assert \"/C:\\\\direc\\\\tory\".equals(url.getPath());\n            assert \"tory\".equals(url.getFilename());\n        }\n        // For OSes that use forward slash as a path separator\n        else {\n            url = FileURL.getFileURL(\"/path\");\n            assert \"file\".equals(url.getScheme());\n            assert \"localhost\".equals(url.getHost());\n            assert \"/path\".equals(url.getPath());\n            assert \"path\".equals(url.getFilename());\n\n            url = FileURL.getFileURL(\"/path/to\");\n            assert \"file\".equals(url.getScheme());\n            assert \"localhost\".equals(url.getHost());\n            assert \"/path/to\".equals(url.getPath());\n            assert \"to\".equals(url.getFilename());\n\n            url = url.getParent();\n            assert \"file\".equals(url.getScheme());\n            assert \"localhost\".equals(url.getHost());\n            assert \"/path/\".equals(url.getPath());\n            assert \"path\".equals(url.getFilename());\n\n            url = FileURL.getFileURL(\"/direc\\\\tory\");\n            assert \"file\".equals(url.getScheme());\n            assert \"localhost\".equals(url.getHost());\n            assert \"/direc\\\\tory\".equals(url.getPath());\n            assert \"direc\\\\tory\".equals(url.getFilename());\n        }\n    }\n\n    /**\n     * Tests the resolution of Windows UNC paths (e.g. \\\\host\\\\share). This test is system-dependant.\n     *\n     * @throws MalformedURLException should not happen\n     */\n    @Test\n    public void testUNCParsing() throws MalformedURLException {\n        FileURL url = FileURL.getFileURL(\"\\\\\\\\host\\\\share\");\n\n        // UNC path will be transformed into either a 'file' or a 'smb' URL, depending on the current OS\n        assert (OsFamily.WINDOWS.isCurrent()?\"file\":\"smb\").equals(url.getScheme());\n        assert \"host\".equals(url.getHost());\n        assert \"/share\".equals(url.getPath());\n    }\n\n\n    @Override\n    protected String getScheme() {\n        return \"file\";\n    }\n\n    @Override\n    protected int getDefaultPort() {\n        return -1;\n    }\n\n    @Override\n    protected AuthenticationType getAuthenticationType() {\n        return AuthenticationType.NO_AUTHENTICATION;\n    }\n\n    @Override\n    protected Credentials getGuestCredentials() {\n        return null;\n    }\n\n    @Override\n    protected String getPathSeparator() {\n        return System.getProperty(\"file.separator\");\n    }\n\n    @Override\n    protected boolean isQueryParsed() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/impl/local/WindowsTest.java",
    "content": "package com.mucommander.commons.file.impl.local;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.nio.file.FileStore;\nimport java.nio.file.FileSystems;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.jupiter.api.Test;\n\n@Slf4j\npublic class WindowsTest {\n\n    @Test\n    public void testWindows() {\n        long start, end;\n        \n        start = System.currentTimeMillis();\n        File[] fileRoots = File.listRoots();\n        end = System.currentTimeMillis();\n        log.info(\"roots : {}\", fileRoots.length);\n        log.info(\"**** option 1 = {}\", end - start);\n        //  3/4 === 21000\n\n        start = System.currentTimeMillis();\n        List<FileStore> stores = new ArrayList<>();\n        FileSystems.getDefault().getFileStores().forEach(stores::add);\n\n//        FileStore[] stores = new FileStore[26];\n        int count = stores.size();\n//        for (FileStore store: FileSystems.getDefault().getFileStores()) {\n//            stores[count++] = store;\n//        }\n        end = System.currentTimeMillis();\n        log.info(\"stores : {}\", count);\n        for (int i=0; i<count; i++) {\n            log.info(stores.get(i).name());\n        }\n        log.info(\"**** option 2 = {}\", end - start);\n        //  3/4 === 126\n\n        start = System.currentTimeMillis();\n        for (Path p : FileSystems.getDefault().getRootDirectories()) {\n            log.info(\"path {}\", p);\n        }\n        end = System.currentTimeMillis();\n        log.info(\"**** option 3 = {}\", end - start);\n        //  4/4 === 3\n\n        start = System.currentTimeMillis();\n        for (char c = 'A'; c <= 'Z'; ++c) {\n            if (new File(c + \":\").exists()) {\n                log.info(c + \":\");\n            }\n        }\n        end = System.currentTimeMillis();\n        log.info(\"**** option 4 = {}\", end - start);\n        //  3/4 === 170\n\n        start = System.currentTimeMillis();\n        try {\n            ProcessBuilder pb = new ProcessBuilder(\"cmd /c wmic logicaldisk get caption\".split(\" \"));\n            pb.redirectErrorStream(true);\n            Process process = pb.start();\n            BufferedReader inStream = new BufferedReader(new InputStreamReader(process.getInputStream()));\n            String line;\n            while ((line = inStream.readLine()) != null) {\n                log.info(line);\n            }\n        }\n        catch (IOException e) {\n            log.error(\"error\", e);\n        }\n        end = System.currentTimeMillis();\n        log.info(\"**** option 5 = {}\", end - start);\n        //  4/4 === 179\n\n        // jni\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/impl/nfs/NFSFileURLTest.java",
    "content": "package com.mucommander.commons.file.impl.nfs;\n\nimport com.mucommander.commons.file.AuthenticationType;\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileURLTestCase;\n\n/**\n * A {@link FileURLTestCase} implementation for NFS URLs.\n *\n * @author Maxence Bernard\n */\npublic class NFSFileURLTest extends FileURLTestCase {\n\n    ////////////////////////////////////\n    // FileURLTestCase implementation //\n    ////////////////////////////////////\n\n    @Override\n    protected String getScheme() {\n        return \"nfs\";\n    }\n\n    @Override\n    protected int getDefaultPort() {\n        return 2049;\n    }\n\n    @Override\n    protected AuthenticationType getAuthenticationType() {\n        return AuthenticationType.NO_AUTHENTICATION;\n    }\n\n    @Override\n    protected Credentials getGuestCredentials() {\n        return null;\n    }\n\n    @Override\n    protected String getPathSeparator() {\n        return \"/\";\n    }\n\n    @Override\n    protected boolean isQueryParsed() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/impl/s3/S3FileTest.java",
    "content": "package com.mucommander.commons.file.impl.s3;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.AbstractFileTest;\nimport com.mucommander.commons.file.FileOperation;\nimport com.mucommander.commons.file.impl.sftp.SFTPFile;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport java.io.File;\nimport java.io.IOException;\n\n/**\n * An {@link AbstractFileTest} implementation for the Amazon S3 file implementation.\n * The S3 temporary folder where test files are created is defined by the {@link #TEMP_FOLDER_PROPERTY} system property.\n *\n * @author Maxence Bernard\n */\npublic class S3FileTest extends AbstractFileTest {\n    @TempDir\n    private static File tempDir;\n\n    /** Base temporary folder */\n    private static AbstractFile tempFolder;\n    @BeforeAll\n    public static void setupTemporaryFolder() {\n        tempFolder = getTemporaryFolder(tempDir);\n    }\n\n\n\n    @Override\n    public AbstractFile getTemporaryFile() throws IOException {\n        return tempFolder.getDirectChild(getPseudoUniqueFilename(S3FileTest.class.getName()));\n    }\n\n    @Override\n    protected Class <? extends AbstractFile> getTestFileClass() {\n        return S3File.class;\n    }\n\n    @Override\n    public FileOperation[] getSupportedOperations() {\n        return new FileOperation[] {\n            FileOperation.READ_FILE,\n            FileOperation.RANDOM_READ_FILE,\n            FileOperation.CREATE_DIRECTORY,\n            FileOperation.LIST_CHILDREN,\n            FileOperation.DELETE,\n            FileOperation.RENAME,\n            FileOperation.COPY_REMOTELY,\n        };\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/impl/s3/S3FileURLTest.java",
    "content": "package com.mucommander.commons.file.impl.s3;\n\nimport com.mucommander.commons.file.AuthenticationType;\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileURLTestCase;\n\n/**\n * A {@link FileURLTestCase} implementation for S3 URLs.\n *\n * @author Maxence Bernard\n */\npublic class S3FileURLTest extends FileURLTestCase {\n\n    ////////////////////////////////////\n    // FileURLTestCase implementation //\n    ////////////////////////////////////\n\n    @Override\n    protected String getScheme() {\n        return \"s3\";\n    }\n\n    @Override\n    protected int getDefaultPort() {\n        return 443;\n    }\n\n    @Override\n    protected AuthenticationType getAuthenticationType() {\n        return AuthenticationType.AUTHENTICATION_REQUIRED;\n    }\n\n    @Override\n    protected Credentials getGuestCredentials() {\n        return null;\n    }\n\n    @Override\n    protected String getPathSeparator() {\n        return \"/\";\n    }\n\n    @Override\n    protected boolean isQueryParsed() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/impl/sftp/SFTPFileTest.java",
    "content": "package com.mucommander.commons.file.impl.sftp;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.AbstractFileTest;\nimport com.mucommander.commons.file.FileOperation;\nimport com.mucommander.commons.file.FileURL;\nimport com.sshtools.sftp.SftpFile;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.URL;\n\n/**\n * An {@link AbstractFileTest} implementation for {@link com.mucommander.commons.file.impl.sftp.SFTPFile}.\n * The SFTP temporary folder where test files are created is defined by the {@link #TEMP_FOLDER_PROPERTY} system property.\n *\n * @author Maxence Bernard\n */\npublic class SFTPFileTest extends AbstractFileTest {\n    @TempDir\n    private static File tempDir;\n\n    /** The system property that holds the URI to the temporary SFTP folder */\n    public final static String TEMP_FOLDER_PROPERTY = \"test_properties.sftp_test.temp_folder\";\n\n    /** Base temporary folder */\n    private static AbstractFile tempFolder;\n\n    static {\n        // Attribute caching can be enabled or disabled, it doesn't matter, tests should pass in both cases\n        // Todo: use JUnit's DataPoint to test both cases (with and without caching) but it requires Java 1.5's\n        // annotations which we don't use for java 1.4 backward compatibility.\n//        SFTPFile.setAttributeCachingPeriod(5000);\n    }\n    @BeforeAll\n    public static void setupTemporaryFolder() {\n        tempFolder = getTemporaryFolder(tempDir);\n    }\n\n\n    @Override\n    protected Class <? extends AbstractFile> getTestFileClass() {\n        return SFTPFile.class;\n    }\n\n    @Override\n    public FileOperation[] getSupportedOperations() {\n        return new FileOperation[] {\n            FileOperation.READ_FILE,\n            FileOperation.RANDOM_READ_FILE,\n            FileOperation.WRITE_FILE,\n            FileOperation.APPEND_FILE,\n            FileOperation.CREATE_DIRECTORY,\n            FileOperation.LIST_CHILDREN,\n            FileOperation.DELETE,\n            FileOperation.RENAME,\n            FileOperation.CHANGE_DATE,\n            FileOperation.CHANGE_PERMISSION,\n        };\n    }\n\n    @Override\n    public AbstractFile getTemporaryFile() throws IOException {\n        return tempFolder.getDirectChild(getPseudoUniqueFilename(SFTPFileTest.class.getName()));\n    }\n\n\n    // Method temporarily overridden to prevent the unit tests from failing\n    @Override\n    protected void testGetInputStreamSupported() {\n        // Todo: fix the InputStream\n    }\n\n    // Method temporarily overridden to prevent the unit tests from failing\n    @Override\n    protected void testGetRandomAccessInputStreamSupported() {\n        // Todo: fix the RandomAccessInputStream\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/impl/sftp/SFTPFileURLTest.java",
    "content": "package com.mucommander.commons.file.impl.sftp;\n\nimport com.mucommander.commons.file.AuthenticationType;\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileURLTestCase;\n\n/**\n * A {@link FileURLTestCase} implementation for SFTP URLs.\n *\n * @author Maxence Bernard\n */\npublic class SFTPFileURLTest extends FileURLTestCase {\n\n    ////////////////////////////////////\n    // FileURLTestCase implementation //\n    ////////////////////////////////////\n\n    @Override\n    protected String getScheme() {\n        return \"sftp\";\n    }\n\n    @Override\n    protected int getDefaultPort() {\n        return 22;\n    }\n\n    @Override\n    protected AuthenticationType getAuthenticationType() {\n        return AuthenticationType.AUTHENTICATION_REQUIRED;\n    }\n\n    @Override\n    protected Credentials getGuestCredentials() {\n        return null;\n    }\n\n    @Override\n    protected String getPathSeparator() {\n        return \"/\";\n    }\n\n    @Override\n    protected boolean isQueryParsed() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/impl/smb/SMBFileTest.java",
    "content": "package com.mucommander.commons.file.impl.smb;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.AbstractFileTest;\nimport com.mucommander.commons.file.FileOperation;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport java.io.File;\nimport java.io.IOException;\n\n/**\n * An {@link AbstractFileTest} implementation for {@link com.mucommander.commons.file.impl.smb.SMBFile}.\n * The SMB temporary folder where test files are created is defined by the {@link #TEMP_FOLDER_PROPERTY} system property.\n *\n * @author Maxence Bernard\n */\n\npublic class SMBFileTest extends AbstractFileTest {\n    @TempDir\n    private static File tempDir;\n    /** The system property that holds the URI to the temporary SMB folder */\n    public final static String TEMP_FOLDER_PROPERTY = \"test_properties.smb_test.temp_folder\";\n\n    /** Base temporary folder */\n    private static AbstractFile tempFolder;\n\n    static {\n        // Configure jCIFS for maximum compatibility\n        SMBProtocolProvider.setSmbLmCompatibility(0);\n        SMBProtocolProvider.setExtendedSecurity(false);\n\n        // Turn off attribute caching completely, otherwise tests will fail\n        SMBFile.setAttributeCachingPeriod(0);\n    }\n    @BeforeAll\n    public static void setupTemporaryFolder() {\n        tempFolder = getTemporaryFolder(tempDir);\n    }\n\n\n\n    @Override\n    public AbstractFile getTemporaryFile() throws IOException {\n        return tempFolder.getDirectChild(getPseudoUniqueFilename(SMBFileTest.class.getName()));\n    }\n\n    @Override\n    protected Class<? extends AbstractFile> getTestFileClass() {\n        return SMBFile.class;\n    }\n\n    @Override\n    public FileOperation[] getSupportedOperations() {\n        return new FileOperation[] {\n            FileOperation.READ_FILE,\n            FileOperation.RANDOM_READ_FILE,\n            FileOperation.WRITE_FILE,\n            FileOperation.APPEND_FILE,\n            FileOperation.RANDOM_WRITE_FILE,\n            FileOperation.CREATE_DIRECTORY,\n            FileOperation.LIST_CHILDREN,\n            FileOperation.DELETE,\n            FileOperation.COPY_REMOTELY,\n            FileOperation.RENAME,\n            FileOperation.CHANGE_DATE,\n            FileOperation.CHANGE_PERMISSION,\n            FileOperation.GET_FREE_SPACE,\n        };\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/impl/smb/SMBFileURLTest.java",
    "content": "package com.mucommander.commons.file.impl.smb;\n\nimport com.mucommander.commons.file.AuthenticationType;\nimport com.mucommander.commons.file.Credentials;\nimport com.mucommander.commons.file.FileURLTestCase;\n\nimport java.net.MalformedURLException;\n\n/**\n * A {@link FileURLTestCase} implementation for SMB URLs.\n *\n * @author Maxence Bernard\n */\npublic class SMBFileURLTest extends FileURLTestCase {\n\n    ////////////////////////////////////\n    // FileURLTestCase implementation //\n    ////////////////////////////////////\n\n    @Override\n    protected String getScheme() {\n        return \"smb\";\n    }\n\n    @Override\n    protected int getDefaultPort() {\n        return -1;\n    }\n\n    @Override\n    protected AuthenticationType getAuthenticationType() {\n        return AuthenticationType.AUTHENTICATION_REQUIRED;\n    }\n\n    @Override\n    protected Credentials getGuestCredentials() {\n        return new Credentials(\"GUEST\", \"\");\n    }\n\n    @Override\n    protected String getPathSeparator() {\n        return \"/\";\n    }\n\n    @Override\n    protected boolean isQueryParsed() {\n        return false;\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    /**\n     * This method is overridden to test SMB's specific notion of realm. \n     */\n    @Override\n    public void testRealm() throws MalformedURLException {\n        assertEqualsAndHashCode(getURL(\"host\", \"/share\"), getURL(\"host\", \"/share/path/to/file\").getRealm());\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/impl/zip/ZipArchiveFileTest.java",
    "content": "package com.mucommander.commons.file.impl.zip;\n\nimport com.mucommander.commons.file.*;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\n\nimport java.io.IOException;\n\n/**\n * An {@link AbstractFileTest} implementation, which performs tests on {@link AbstractArchiveEntryFile}\n * entries located inside a {@link ZipArchiveFile} residing in a temporary {@link com.mucommander.commons.file.impl.local.LocalFile}.\n *\n * @author Maxence Bernard\n */\npublic class ZipArchiveFileTest extends AbstractFileTest {\n\n    /** The archive file which contains the temporary entries */\n    private static ZipArchiveFile tempZipFile;\n\n    /** id of the last temporary entry generated, to avoid collisions */\n    private int entryNum;\n\n\n    @Override\n    public AbstractFile getTemporaryFile() throws IOException {\n        // use a incremental id to avoid collisions\n        return tempZipFile.getDirectChild(\"entry\"+(++entryNum));\n    }\n\n    @Override\n    public FileOperation[] getSupportedOperations() {\n        return new FileOperation[] {\n            FileOperation.READ_FILE,\n            FileOperation.WRITE_FILE,\n            FileOperation.CREATE_DIRECTORY,\n            FileOperation.LIST_CHILDREN,\n            FileOperation.DELETE,\n            FileOperation.CHANGE_DATE,\n            FileOperation.CHANGE_PERMISSION,\n            FileOperation.GET_FREE_SPACE,\n            FileOperation.GET_TOTAL_SPACE\n        };\n    }\n\n\n    ////////////////////////\n    // Overridden methods //\n    ////////////////////////\n\n    /**\n     * Overridden to create the archive file before each test.\n     */\n    @Override\n    @BeforeEach\n    public void setUp() throws IOException {\n        entryNum = 0;\n        tempZipFile = (ZipArchiveFile)FileFactory.getTemporaryFile(ZipArchiveFileTest.class.getName()+\".zip\", false);\n\n        // Assert that the file is not an actual archive yet\n        assert !tempZipFile.isArchive();\n\n        tempZipFile.mkfile();\n\n        // Assert that the file is now an archive\n        assert tempZipFile.isArchive();\n\n        super.setUp();\n    }\n\n    /**\n     * Overridden to delete the archive file after each test.\n     */\n    @Override\n    @AfterEach\n    public void tearDown() throws IOException {\n        // Delete all archive entries\n        super.tearDown();\n\n        // Assert that the file is still an archive\n        assert tempZipFile.isArchive();\n\n        tempZipFile.delete();\n\n        // Assert that the file is no longer an archive\n        assert !tempZipFile.isArchive();\n    }\n\n    @Override\n    public void testCanonicalPath() {\n        // TODO\n        // Test temporarily disabled because if fails. The failure seems to be caused by archive file caching:\n        // the change is made to the archive file denoted by its absolute path ; when accessed by the canonical path,\n        // the archive file is another instance which isn't aware of the change, because the file date hasn't changed (?).\n    }\n\n//    /**\n//     * Tests the Zip32 4GB limit by asserting two things:\n//     * <ul>\n//     *  <li>that entries can be as large as 4GB minus one byte, preventing against unsigned java int issues amongst\n//     * other things.</li>\n//     *  <li>that entries cannot exceed 4GB and that an IOException is thrown if trying to write more than 4Gb, rather\n//     * than failing silently and leaving the Zip file corrupted.</li>\n//     * </ul>\n//     *\n//     * @throws IOException should not happen\n//     * @throws NoSuchAlgorithmException should not happen\n//     */\n//    public void testZip32Limit() throws IOException, NoSuchAlgorithmException {\n//        // Assert a 4GB minus one byte entry can be properly compressed and uncompressed\n//        ChecksumOutputStream md5Out = getMd5OutputStream(tempFile.getAppendOutputStream());\n//        StreamUtils.fillWithConstant(md5Out, (byte)0, MAX_ZIP32_ENTRY_SIZE);\n//        md5Out.close();\n//\n//        assertEquals(md5Out.getChecksum(), calculateMd5(tempFile));\n//\n//        // Assert that an IOException is thrown if more than 4GB is written to an entry\n//        OutputStream out = tempFile.getOutputStream();\n//        boolean ioExceptionThrown = false;\n//        try {\n//            StreamUtils.fillWithConstant(out, (byte)0, MAX_ZIP32_ENTRY_SIZE+1);\n//        }\n//        catch(IOException e) {\n//            ioExceptionThrown = true;\n//        }\n//        finally {\n//            out.close();\n//        }\n//\n//        assertTrue(ioExceptionThrown);\n//\n//        // Todo: test Zip files larger than 4Gb as a whole (should fail gracefully)\n//    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/util/FileComparatorTest.java",
    "content": "package com.mucommander.commons.file.util;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.impl.TestFile;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nimport java.util.Arrays;\n\n/**\n * A test case for {@link FileComparator}.\n * @author Mariusz Jakubowski\n *\n */\npublic class FileComparatorTest {\n  \n    AbstractFile[] files;\n    private TestFile A;\n    private TestFile B;\n    private TestFile C;\n    private TestFile D;\n    \n\n    @BeforeEach\n    protected void setUp() throws Exception {\n        A = new TestFile(FileFactory.getTemporaryFolder() + \"A\",       false, 500, 1, null);\n        B = new TestFile(FileFactory.getTemporaryFolder() + \"B.e9.e1\", true, 0, 2, null);\n        C = new TestFile(FileFactory.getTemporaryFolder() + \"C.e3\",    false, 200, 3, null);\n        D = new TestFile(FileFactory.getTemporaryFolder() + \"D.e2\",    true, 0, 4, null);\n        files = new AbstractFile[] {C, D, A, B};\n    }\n\n    @Test\n    public void testCompareNameDir() {\n        Arrays.sort(files, new FileComparator(FileComparator.NAME_CRITERION, true, false,true));\n        assertEquals(A, files[0]);\n        assertEquals(B, files[1]);\n        assertEquals(C, files[2]);\n        assertEquals(D, files[3]);\n    }\n\n    @Test\n    public void testCompareNameDirDesc() {\n        Arrays.sort(files, new FileComparator(FileComparator.NAME_CRITERION, false, false,true));\n\n        assertEquals(D, files[0]);\n        assertEquals(C, files[1]);\n        assertEquals(B, files[2]);\n        assertEquals(A, files[3]);\n    }\n\n    @Test\n    public void testCompareName() {\n        Arrays.sort(files, new FileComparator(FileComparator.NAME_CRITERION, true, false,false));\n        assert A.equals(files[0]);\n        assert B.equals(files[1]);\n        assert C.equals(files[2]);\n        assert D.equals(files[3]);\n    }\n\n    @Test\n    public void testCompareNameDesc() {\n        Arrays.sort(files, new FileComparator(FileComparator.NAME_CRITERION, false, false,false));\n        assert D.equals(files[0]);\n        assert C.equals(files[1]);\n        assert B.equals(files[2]);\n        assert A.equals(files[3]);\n    }\n\n    @Test\n    public void testCompareSizeDir() {\n        Arrays.sort(files, new FileComparator(FileComparator.SIZE_CRITERION, true, false,true));\n        assert B.equals(files[0]);\n        assert D.equals(files[1]);\n        assert C.equals(files[2]);\n        assert A.equals(files[3]);\n    }\n\n    @Test\n    public void testCompareSize() {\n        Arrays.sort(files, new FileComparator(FileComparator.SIZE_CRITERION, true, false,false));\n        assert B.equals(files[0]);\n        assert D.equals(files[1]);\n        assert C.equals(files[2]);\n        assert A.equals(files[3]);\n    }\n\n    @Test\n    public void testCompareSizeDirDesc() {\n        Arrays.sort(files, new FileComparator(FileComparator.SIZE_CRITERION, false, false,true));\n        assertEquals(A, files[0]);  // 500\n        assertEquals(C, files[1]);  // 200\n        assertEquals(D, files[2]);  // DIR\n        assertEquals(B, files[3]);  // DIR\n\n    }\n\n    @Test\n    public void testCompareSizeDesc() {\n        Arrays.sort(files, new FileComparator(FileComparator.SIZE_CRITERION, false, false,false));\n        assert A.equals(files[0]);\n        assert C.equals(files[1]);\n        assert D.equals(files[2]);\n        assert B.equals(files[3]);\n    }\n\n    @Test\n    public void testCompareDateDir() {\n        Arrays.sort(files, new FileComparator(FileComparator.DATE_CRITERION, true, false,true));\n        assertEquals(A, files[0]);  // 1\n        assertEquals(B, files[1]);  // 2\n        assertEquals(C, files[2]);  // 3\n        assertEquals(D, files[3]);  // 4\n    }\n\n    @Test\n    public void testCompareDate() {\n        Arrays.sort(files, new FileComparator(FileComparator.DATE_CRITERION, true, false,false));\n        assertEquals(A, files[0]);\n        assertEquals(B, files[1]);\n        assertEquals(C, files[2]);\n        assertEquals(D, files[3]);\n    }\n\n    @Test\n    public void testCompareDateDirDesc() {\n        Arrays.sort(files, new FileComparator(FileComparator.DATE_CRITERION, false, false,true));\n        assertEquals(D, files[0]);  // 4\n        assertEquals(C, files[1]);  // 3\n        assertEquals(B, files[2]);  // 2\n        assertEquals(A, files[3]);  // 1\n    }\n\n    @Test\n    public void testCompareDateDesc() {\n        Arrays.sort(files, new FileComparator(FileComparator.DATE_CRITERION, false, false,false));\n        assertEquals(D, files[0]);  // 4\n        assertEquals(C, files[1]);  // 3\n        assertEquals(B, files[2]);  // 2\n        assertEquals(A, files[3]);  // 1\n    }\n\n    @Test\n    public void testCompareExtDir() {\n        Arrays.sort(files, new FileComparator(FileComparator.EXTENSION_CRITERION, true, false,true));\n        assertEquals(A, files[0]);  // A\n        assertEquals(B, files[1]);  // B.e9.e1\n        assertEquals(D, files[2]);  // D.e2\n        assertEquals(C, files[3]);  // C.e3\n    }\n\n    @Test\n    public void testCompareExt() {\n        Arrays.sort(files, new FileComparator(FileComparator.EXTENSION_CRITERION, true, false,false));\n        assertEquals(A, files[0]);\n        assertEquals(B, files[1]);\n        assertEquals(D, files[2]);\n        assertEquals(C, files[3]);\n    }\n\n    @Test\n    public void testCompareExtDirDesc() {\n        Arrays.sort(files, new FileComparator(FileComparator.EXTENSION_CRITERION, false, false,true));\n        assertEquals(C, files[0]);  // C.e3\n        assertEquals(D, files[1]);  // D.e2\n        assertEquals(B, files[2]);  // B.e9.e1\n        assertEquals(A, files[3]);  // A\n    }\n\n    @Test\n    public void testCompareExtDesc() {\n        Arrays.sort(files, new FileComparator(FileComparator.EXTENSION_CRITERION, false, false,false));\n        assertEquals(C, files[0]);\n        assertEquals(D, files[1]);\n        assertEquals(B, files[2]);\n        assertEquals(A, files[3]);\n    }\n    \n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/util/FileMonitorTest.java",
    "content": "package com.mucommander.commons.file.util;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport com.mucommander.commons.file.PermissionAccesses;\nimport com.mucommander.commons.file.PermissionTypes;\nimport com.mucommander.commons.io.RandomAccessOutputStream;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\n\n/**\n * A test case for the {@link com.mucommander.commons.file.util.FileMonitor} class.\n *\n * @author Maxence Bernard\n */\npublic class FileMonitorTest implements FileMonitorConstants {\n\n    /** Temporary file used by the current test */\n    private AbstractFile file;\n    /** FileMonitor used by the current test */\n    private FileMonitor fileMonitor;\n    /** FileChangeTracker used by the current test */\n    private FileChangeTracker fileChangeTracker;\n\n    /** Poll period used by the FileMonitor (in milliseconds) */\n    private final static int POLL_PERIOD = 10;\n\n    /** Number of milliseconds to wait for an attribute change before timing out */\n    private final static int TIMEOUT = 5000;\n\n    \n    /**\n     * Validates that FileMonitor properly reports {@link FileMonitor#DATE_ATTRIBUTE} changes when a file's date changes.\n     *\n     * @throws IOException should not normally happen\n     */\n    @Test\n    public void testDateAttribute() throws IOException {\n        setUp(DATE_ATTRIBUTE);\n\n        file.setLastModifiedDate(file.getLastModifiedDate()-2000);\n        assert hasAttributeChanged(DATE_ATTRIBUTE);\n\n        file.setLastModifiedDate(file.getLastModifiedDate()+2000);\n        assert hasAttributeChanged(DATE_ATTRIBUTE);\n    }\n\n    /**\n     * Validates that FileMonitor properly reports {@link FileMonitor#SIZE_ATTRIBUTE} changes when a file's size changes.\n     *\n     * @throws IOException should not normally happen\n     */\n    @Test\n    public void testSizeAttribute() throws IOException {\n        setUp(SIZE_ATTRIBUTE);\n\n        RandomAccessOutputStream raos = file.getRandomAccessOutputStream();\n        try {\n            raos.setLength(10);\n\n            assert hasAttributeChanged(SIZE_ATTRIBUTE);\n\n            raos.setLength(0);\n        }\n        finally {\n            if(raos!=null)\n                raos.close();\n        }\n    }\n\n    /**\n     * Validates that FileMonitor properly reports {@link FileMonitor#PERMISSIONS_ATTRIBUTE} changes when a file's\n     * permissions change.\n     *\n     * @throws IOException should not normally happen\n     */\n    @Test\n    public void testPermissionsAttribute() throws IOException {\n        setUp(PERMISSIONS_ATTRIBUTE);\n\n        file.changePermission(PermissionAccesses.USER_ACCESS, PermissionTypes.WRITE_PERMISSION, !file.getPermissions().getBitValue(PermissionAccesses.USER_ACCESS, PermissionTypes.WRITE_PERMISSION));\n        assert hasAttributeChanged(PERMISSIONS_ATTRIBUTE);\n    }\n\n    /**\n     * Validates that FileMonitor properly reports {@link FileMonitor#IS_DIRECTORY_ATTRIBUTE} changes when a file\n     * becomes a directory and vice-versa.\n     *\n     * @throws IOException should not normally happen\n     */\n    @Test\n    public void testIsDirectoryAttribute() throws IOException {\n        setUp(IS_DIRECTORY_ATTRIBUTE);\n\n        file.delete();\n        file.mkdir();\n        assert hasAttributeChanged(IS_DIRECTORY_ATTRIBUTE);\n\n        file.delete();\n        file.mkfile();\n        assert hasAttributeChanged(IS_DIRECTORY_ATTRIBUTE);\n    }\n\n    /**\n     * Validates that FileMonitor properly reports {@link FileMonitor#EXISTS_ATTRIBUTE} changes when an existing file or\n     * directory is deleted, or when a non-existing file or directory is created. \n     *\n     * @throws IOException should not normally happen\n     */\n    @Test\n    public void testExistsAttribute() throws IOException {\n        setUp(EXISTS_ATTRIBUTE);\n\n        file.delete();\n        assert hasAttributeChanged(EXISTS_ATTRIBUTE);\n\n        file.mkdir();\n        assert hasAttributeChanged(EXISTS_ATTRIBUTE);\n\n        file.delete();\n        assert hasAttributeChanged(EXISTS_ATTRIBUTE);\n\n        file.mkfile();\n        assert hasAttributeChanged(EXISTS_ATTRIBUTE);\n    }\n\n    /**\n     * Called after each test, stops monitoring file changes.\n     */\n    @AfterEach\n    protected void tearDown() {\n        fileMonitor.stopMonitoring();\n    }\n\n\n    /////////////////////////////////\n    // Support methods and classes //\n    /////////////////////////////////\n\n    /**\n     * Sets everything up for a test: retrieves a temporary file instance, create the file, waits until the file exists,\n     * create a <code>FileMonitor</code>, register a {@link FileChangeTracker} and finally start monitoring file changes.\n     *\n     * @param attribute the attribute to monitor\n     * @throws IOException should not normally happen\n     */\n    private void setUp(int attribute) throws IOException {\n        // Retrieve a temporary AbstractFile instance that will be deleted on VM shutdown\n        file = FileFactory.getTemporaryFile(getClass().getName(), true);\n        // create the file\n        file.mkfile();\n\n        // Waits until the file truly exists (I/O are usually asynchroneous)\n        while(!file.exists()) {\n            try { Thread.sleep(POLL_PERIOD); }\n            catch(InterruptedException e) {}\n        }\n\n        // create the monitor, change listener and start monitoring file changes\n        fileMonitor = new FileMonitor(file, attribute, POLL_PERIOD);\n\n        fileChangeTracker = new FileChangeTracker();\n        fileMonitor.addFileChangeListener(fileChangeTracker);\n\n        fileMonitor.startMonitoring();\n    }\n\n\n    /**\n     * Returns <code>true</code> if the current <code>FileMonitor</code> reported a change on the specified attribute.\n     * This method will wait up to {@link #TIMEOUT} milliseconds for the attribute to change and if it hasn't changed,\n     * will return <code>false</code>.\n     *\n     * @param attribute the attribute to test against\n     * @return true if the current <code>FileMonitor</code> reported a change on the specified attribute\n     */\n    private boolean hasAttributeChanged(int attribute) {\n        boolean hasAttributeChanged = false;\n\n        try {\n            synchronized(fileChangeTracker) {\n                hasAttributeChanged = (attribute&fileChangeTracker.getChangedAttributes())!=0;\n\n                if(!hasAttributeChanged) {\n                    // Waits until FileChangeTracker calls notify to report an attribute change; give up after\n                    // TIMEOUT milliseconds\n                    fileChangeTracker.wait(TIMEOUT);\n                }\n\n                hasAttributeChanged = (attribute&fileChangeTracker.getChangedAttributes())!=0;\n            }\n        }\n        catch(InterruptedException e) {}\n\n        // Resets FileChangeTracker to be ready to detect the next attribute change\n        fileChangeTracker.reset();\n\n        return hasAttributeChanged;\n    }\n\n    /**\n     * This {@link FileChangeListener} keeps track of the attributes that changed, as reported by\n     * {@link #fileChanged(com.mucommander.commons.file.AbstractFile, int)}.\n     */\n    private static class FileChangeTracker implements FileChangeListener {\n\n        /** Bit mask that describes the attributes that have changed */\n        private int changedAttributes;\n\n        /**\n         * Returns a bit mask that describes the attributes that have changed.\n         *\n         * @return a bit mask that describes the attributes that have changed\n         */\n        private int getChangedAttributes() {\n            return changedAttributes;\n        }\n\n        /**\n         * Resets the changed attributes bit mask to zero.\n         */\n        private void reset() {\n            this.changedAttributes = 0;\n        }\n\n        ///////////////////////////////////////\n        // FileChangeListener implementation //\n        ///////////////////////////////////////\n\n        public void fileChanged(AbstractFile file, int changedAttributes) {\n            synchronized(this) {\n                this.changedAttributes |= changedAttributes;\n\n                notify();   // Notify that hasAttributeChanged(int) method that an attribute has changed \n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/util/PathTokenizerTest.java",
    "content": "package com.mucommander.commons.file.util;\n\nimport org.junit.jupiter.api.Test;\n\n/**\n * Runs tests on {@link PathTokenizer}.\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic class PathTokenizerTest {\n    @Test\n    public void testEmtpy() {\n        test(\"\");\n    }\n\n    @Test\n    public void testUnknown() {\n        test(\"blah\");\n    }\n\n    @Test\n    public void testComplexMixed() {\n        test(\"/C:\\\\\\\\this///is/not\\\\\\\\a\\\\valid//path//but/we\\\\let//it\\\\parse/\");\n    }\n\n    @Test\n    public void testSimpleMixed() {\n        test(\"/C:\\\\temp\");\n    }\n\n    @Test\n    public void testWindowsRootWithoutTrailingSeparator() {\n        test(\"C:\");\n    }\n\n    @Test\n    public void testWindowsRootWithTrailingSeparator() {\n        test(\"C:\\\\\");\n    }\n\n    @Test\n    public void testWindowsWithTrailingSeparator() {\n        test(\"C:\\\\temp\\\\\");\n    }\n\n    @Test\n    public void testWindowsWithoutTrailingSeparator() {\n        test(\"C:\\\\temp\");\n    }\n\n    @Test\n    public void testUnixWithTrailingSeparator() {\n        test(\"/Users/maxence/Temp/\");\n    }\n\n    @Test\n    public void testUnixWithoutTrailingSeparator() {\n        test(\"/Users/maxence/Temp\");\n    }\n\n    @Test\n    public void testUnixRoot() {\n        test(\"/\");\n    }\n\n    private static void test(String path) {\n        for(boolean reverseOrder=false; ; reverseOrder=true) {\n            PathTokenizer pt = new PathTokenizer(path, PathTokenizer.DEFAULT_SEPARATORS, reverseOrder);\n            \n            String reconstructedPath = pt.getLastSeparator();\n\n            while(pt.hasMoreFilenames()) {\n                String nextToken = pt.nextFilename();\n                String lastSeparator = pt.getLastSeparator();\n\n                if(!reverseOrder)\n                    reconstructedPath += nextToken+lastSeparator;\n            }\n\n            assert reverseOrder || reconstructedPath.equals(path);\n\n            if(reverseOrder)\n                break;\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/util/PathUtilsTest.java",
    "content": "package com.mucommander.commons.file.util;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport org.junit.jupiter.api.Test;\nimport static org.junit.jupiter.api.Assertions.*;\nimport java.io.IOException;\n\n/**\n * This class is a JUnit test case for {@link com.mucommander.commons.file.util.PathUtils}.\n *\n * @author Maxence Bernard\n * @see com.mucommander.commons.file.util.PathUtils\n */\npublic class PathUtilsTest {\n\n    /**\n     * Calls {@link #testResolveDestination(AbstractFile)} with the system's temporary folder.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testResolveLocalDestination() throws IOException {\n        testResolveDestination(FileFactory.getTemporaryFolder());\n    }\n\n    /**\n     * Tests {@link com.mucommander.commons.file.util.PathUtils} by throwing at it a bunch of sample paths corresponding to all\n     * possible situations.\n     *\n     * @param baseFolder the base folder, used for relative paths\n     * @throws IOException should not happen\n     */\n    public static void testResolveDestination(AbstractFile baseFolder) throws IOException {\n        AbstractFile baseRoot = baseFolder.getRoot();\n        AbstractFile baseParent = baseFolder.getParent();\n        String separator = baseFolder.getSeparator();\n        String nonExistentFilename = \"non_existent_file\";\n        AbstractFile nonExistentFile = baseFolder.getDirectChild(nonExistentFilename);\n        String existingFilename = \"existing_file\";\n        AbstractFile existingFile = baseFolder.getDirectChild(existingFilename);\n        if (!existingFile.exists()) {\n            existingFile.mkfile();\n        }\n        String existingArchiveFilename = \"existing_archive.zip\";\n        AbstractFile existingArchive = baseFolder.getDirectChild(existingArchiveFilename);\n        if (!existingArchive.exists()) {\n            existingArchive.mkfile();\n        }\n\n        // Test a bunch of destination paths that denote an existing folder\n\n        int expectedType = PathUtils.ResolvedDestination.EXISTING_FOLDER;\n\n        // Absolute paths\n        assertResult(PathUtils.resolveDestination(baseFolder.getURL().toString(true), baseRoot), baseFolder, expectedType);\n        assertResult(PathUtils.resolveDestination(baseFolder.getURL().toString(true), null), baseFolder, expectedType);\n        assertResult(PathUtils.resolveDestination(baseRoot.getURL().toString(true), baseRoot), baseRoot, expectedType);\n        // Relative paths\n        assertResult(PathUtils.resolveDestination(\".\", baseFolder), baseFolder, expectedType);\n        assertResult(PathUtils.resolveDestination(\".\"+baseFolder.getSeparator(), baseFolder), baseFolder, expectedType);\n        assertResult(PathUtils.resolveDestination(baseFolder.getName(), baseParent), baseFolder, expectedType);\n        assertResult(PathUtils.resolveDestination(baseFolder.getName()+separator, baseParent), baseFolder, expectedType);\n        assertResult(PathUtils.resolveDestination(\".\"+separator+baseFolder.getName(), baseParent), baseFolder, expectedType);\n        // Archive path as folder (with a trailing separator)\n        assertResult(PathUtils.resolveDestination(existingArchive.getURL().toString(true)+separator, baseFolder), existingArchive, expectedType);\n        assertResult(PathUtils.resolveDestination(existingArchiveFilename+separator, baseFolder), existingArchive, expectedType);\n        assertResult(PathUtils.resolveDestination(\".\"+separator+existingArchiveFilename+separator, baseFolder), existingArchive, expectedType);\n\n        // Test a bunch of destination paths that denote an existing regular file\n\n        expectedType = PathUtils.ResolvedDestination.EXISTING_FILE;\n\n        // Absolute paths\n        assertResult(PathUtils.resolveDestination(existingFile.getURL().toString(true), baseRoot), existingFile, expectedType);\n        // Relative paths\n        assertResult(PathUtils.resolveDestination(existingFilename, baseFolder), existingFile, expectedType);\n        assertResult(PathUtils.resolveDestination(\".\"+separator+existingFilename, baseFolder), existingFile, expectedType);\n        // Archive path as regular file (without a trailing separator)\n        assertResult(PathUtils.resolveDestination(existingArchive.getURL().toString(true), baseFolder), existingArchive, expectedType);\n        assertResult(PathUtils.resolveDestination(existingArchiveFilename, baseFolder), existingArchive, expectedType);\n        assertResult(PathUtils.resolveDestination(\".\"+separator+existingArchiveFilename, baseFolder), existingArchive, expectedType);\n\n        // Test a bunch of destination paths that denote a new/non-existing regular file\n\n        expectedType = PathUtils.ResolvedDestination.NEW_FILE;\n\n        // Absolute paths\n        assertResult(PathUtils.resolveDestination(nonExistentFile.getURL().toString(true), baseRoot), nonExistentFile, expectedType);\n        // Relative paths\n        assertResult(PathUtils.resolveDestination(nonExistentFilename, baseFolder), nonExistentFile, expectedType);\n        assertResult(PathUtils.resolveDestination(\".\"+separator+nonExistentFilename, baseFolder), nonExistentFile, expectedType);\n\n        // Test invalid destination paths\n\n        // neither the file nor its parent exist\n        assert PathUtils.resolveDestination(nonExistentFilename+separator+nonExistentFilename, baseFolder) == null;\n        assert PathUtils.resolveDestination(nonExistentFilename, baseFolder.getChild(nonExistentFilename)) == null;\n        // relative path and no base folder\n        assert PathUtils.resolveDestination(nonExistentFilename, null) == null;\n\n        // Delete the files we created when finished\n        existingFile.delete();\n        existingArchive.delete();\n    }\n\n    /**\n     * Asserts that the <code>ResolvedDestination</code> returned by {@link PathUtils#resolveDestination(String, com.mucommander.commons.file.AbstractFile)}\n     * matches the expected destination file and type. This method asserts that the destination folder is not\n     * <code>null</code> and consistent with the destination file.\n     *\n     * @param resolvedDestination the ResolvedDestination to test\n     * @param expectedDestinationFile the expected destination file\n     * @param expectedDestinationType the expected destination type\n     */\n    private static void assertResult(PathUtils.ResolvedDestination resolvedDestination, AbstractFile expectedDestinationFile, int expectedDestinationType) {\n        AbstractFile file = resolvedDestination.getDestinationFile();\n        int type = resolvedDestination.getDestinationType();\n        AbstractFile folder = resolvedDestination.getDestinationFolder();\n\n        assert file != null;\n        assert folder != null;\n\n        assert expectedDestinationFile.equals(file);\n        assertEquals(expectedDestinationType, type);\n\n        if (type == PathUtils.ResolvedDestination.EXISTING_FOLDER)\n            assert file.equals(folder);\n        else\n            assert file.getParent().equals(folder);\n    }\n\n    /**\n     * Tests {@link PathUtils#removeLeadingFragments(String, String, int)}.\n     */\n    @Test\n    public void testRemoveLeadingFragments() {\n        assert \"home/maxence/\".equals(PathUtils.removeLeadingFragments(\"/home/maxence/\", \"/\", 0));\n        assert \"maxence/\".equals(PathUtils.removeLeadingFragments(\"/home/maxence/\", \"/\", 1));\n        assert \"\".equals(PathUtils.removeLeadingFragments(\"/home/maxence/\", \"/\", 2));\n        assert \"\".equals(PathUtils.removeLeadingFragments(\"/home/maxence/\", \"/\", 3));\n        assert \"\".equals(PathUtils.removeLeadingFragments(\"/home/maxence/\", \"\\\\\", 1));\n    }\n\n    /**\n     * Tests {@link PathUtils#getDepth(String, String)}.\n     */\n    @Test\n    public void testGetDepth() {\n        assert 0 == PathUtils.getDepth(\"/\", \"/\");\n        assert 0 == PathUtils.getDepth(\"\", \"/\");\n        assert 1 == PathUtils.getDepth(\"/home\", \"/\");\n        assert 1 == PathUtils.getDepth(\"/home/\", \"/\");\n        assert 2 == PathUtils.getDepth(\"/home/maxence\", \"/\");\n        assert 2 == PathUtils.getDepth(\"/home/maxence/\", \"/\");\n\n        assert 1 == PathUtils.getDepth(\"/home/maxence\", \"\\\\\");\n        assert 1 == PathUtils.getDepth(\"C:\", \"\\\\\"); \n        assert 1 == PathUtils.getDepth(\"C:\\\\\", \"\\\\\");\n        assert 2 == PathUtils.getDepth(\"C:\\\\home\", \"\\\\\");\n        assert 2 == PathUtils.getDepth(\"C:\\\\home\\\\\", \"\\\\\");\n        assert 3 == PathUtils.getDepth(\"C:\\\\home\\\\maxence\", \"\\\\\");\n        assert 3 == PathUtils.getDepth(\"C:\\\\home\\\\maxence\\\\\", \"\\\\\");\n    }\n\n\n    /**\n     * Tests {@link PathUtils#removeLeadingSeparator(String, String)}.\n     */\n    @Test\n    public void testRemoveLeadingSeparator() {\n        assert PathUtils.removeLeadingSeparator(\"/home/\", \"/\").equals(\"home/\");\n        assert PathUtils.removeLeadingSeparator(\"/home/maxence\", \"/\").equals(\"home/maxence\");\n        assert PathUtils.removeLeadingSeparator(\"home/\", \"/\").equals(\"home/\");\n        assert PathUtils.removeLeadingSeparator(\"/home/\", \"\\\\\").equals(\"/home/\");\n        assert PathUtils.removeLeadingSeparator(\"/\", \"/\").isEmpty();\n\n        assert PathUtils.removeLeadingSeparator(\"C:\\\\home\\\\\", \"\\\\\").equals(\"C:\\\\home\\\\\");\n        assert PathUtils.removeLeadingSeparator(\"C:\\\\home\\\\\", \"/\").equals(\"C:\\\\home\\\\\");\n\n        assert PathUtils.removeLeadingSeparator(\"--home--\", \"--\").equals(\"home--\");\n        assert PathUtils.removeLeadingSeparator(\"--home--maxence\", \"--\").equals(\"home--maxence\");\n        assert PathUtils.removeLeadingSeparator(\"home--\", \"--\").equals(\"home--\");\n        assert PathUtils.removeLeadingSeparator(\"--home--\", \"/\").equals(\"--home--\");\n        assert PathUtils.removeLeadingSeparator(\"--\", \"--\").isEmpty();\n    }\n\n    /**\n     * Tests {@link PathUtils#removeTrailingSeparator(String, String)}. \n     */\n    @Test\n    public void testRemoveTrailingSeparator() {\n        assert PathUtils.removeTrailingSeparator(\"/home/\", \"/\").equals(\"/home\");\n        assert PathUtils.removeTrailingSeparator(\"/home/maxence\", \"/\").equals(\"/home/maxence\");\n        assert PathUtils.removeTrailingSeparator(\"/home/maxence/\", \"/\").equals(\"/home/maxence\");\n        assert PathUtils.removeTrailingSeparator(\"/home/\", \"\\\\\").equals(\"/home/\");\n        assert PathUtils.removeTrailingSeparator(\"/\", \"/\").isEmpty();\n\n        assert PathUtils.removeTrailingSeparator(\"C:\\\\home\", \"\\\\\").equals(\"C:\\\\home\");\n        assert PathUtils.removeTrailingSeparator(\"C:\\\\home\\\\\", \"\\\\\").equals(\"C:\\\\home\");\n        assert PathUtils.removeTrailingSeparator(\"C:\\\\home\\\\maxence\", \"\\\\\").equals(\"C:\\\\home\\\\maxence\");\n        assert PathUtils.removeTrailingSeparator(\"C:\\\\home\\\\maxence\", \"\\\\\").equals(\"C:\\\\home\\\\maxence\");\n        assert PathUtils.removeTrailingSeparator(\"C:\\\\home\\\\\", \"/\").equals(\"C:\\\\home\\\\\");\n\n        assert PathUtils.removeTrailingSeparator(\"--home--\", \"--\").equals(\"--home\");\n        assert PathUtils.removeTrailingSeparator(\"--home--maxence\", \"--\").equals(\"--home--maxence\");\n        assert PathUtils.removeTrailingSeparator(\"--home--maxence--\", \"--\").equals(\"--home--maxence\");\n        assert PathUtils.removeTrailingSeparator(\"--home--\", \"/\").equals(\"--home--\");\n        assert PathUtils.removeTrailingSeparator(\"--\", \"--\").isEmpty();\n    }\n\n    /**\n     * Tests {@link PathUtils#pathEquals(String, String, String)}.\n     */\n    @Test\n    public void testPathEquals() {\n        assert PathUtils.pathEquals(\"/home/\", \"/home/\", \"/\");\n        assert PathUtils.pathEquals(\"/home\", \"/home\", \"/\");\n        assert PathUtils.pathEquals(\"/home/\", \"/home/\", \"\\\\\");\n        assert PathUtils.pathEquals(\"/home/\", \"/home\", \"/\");\n        assert PathUtils.pathEquals(\"/home\", \"/home/\", \"/\");\n\n        assert PathUtils.pathEquals(\"C:\\\\home\\\\\", \"C:\\\\home\\\\\", \"\\\\\");\n        assert PathUtils.pathEquals(\"C:\\\\home\", \"C:\\\\home\", \"\\\\\");\n        assert PathUtils.pathEquals(\"C:\\\\home\\\\\", \"C:\\\\home\\\\\", \"/\");\n        assert PathUtils.pathEquals(\"C:\\\\home\\\\\", \"C:\\\\home\", \"\\\\\");\n        assert PathUtils.pathEquals(\"C:\\\\home\", \"C:\\\\home\\\\\", \"\\\\\");\n\n        assert PathUtils.pathEquals(\"--home--\", \"--home--\", \"--\");\n        assert PathUtils.pathEquals(\"--home\", \"--home\", \"--\");\n        assert PathUtils.pathEquals(\"--home--\", \"--home--\", \"/\");\n        assert PathUtils.pathEquals(\"--home--\", \"--home\", \"--\");\n        assert PathUtils.pathEquals(\"--home\", \"--home--\", \"--\");\n\n        assert !(PathUtils.pathEquals(\"/\", \"/home\", \"/\"));\n        assert !(PathUtils.pathEquals(\"/home\", \"/home/\", \"\\\\\"));\n        assert !(PathUtils.pathEquals(\"/home/\", \"/home\", \"\\\\\"));\n\n        assert !(PathUtils.pathEquals(\"C:\\\\\", \"C:\\\\home\", \"\\\\\"));\n        assert !(PathUtils.pathEquals(\"C:\\\\home\", \"C:\\\\home\\\\\", \"/\"));\n        assert !(PathUtils.pathEquals(\"C:\\\\home\\\\\", \"C:\\\\home\", \"/\"));\n\n        assert !(PathUtils.pathEquals(\"--\", \"--home\", \"--\"));\n        assert !(PathUtils.pathEquals(\"--home\", \"--home--\", \"/\"));\n        assert !(PathUtils.pathEquals(\"--home--\", \"--home\", \"/\"));\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/file/util/ResourceLoaderTest.java",
    "content": "package com.mucommander.commons.file.util;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.io.StreamUtils;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URL;\n\n/**\n * A test case for the {@link ResourceLoader} class.\n *\n * @author Maxence Bernard\n */\npublic class ResourceLoaderTest {\n\n    /**\n     * Tests {@link ResourceLoader#getDefaultClassLoader()}.\n     */\n    @Test\n    public void testDefaultClassLoader() {\n        assert ResourceLoader.getDefaultClassLoader() != null;\n    }\n\n    /**\n     * Tests <code>ResourceLoader#getResourceAsURL</code> methods.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testResourceAsURL() throws IOException {\n        assertReadable(ResourceLoader.getResourceAsURL(getExistingResourcePath()));\n        assertReadable(ResourceLoader.getResourceAsURL(\"/\"+getExistingResourcePath()));\n        assert ResourceLoader.getResourceAsURL(getNonExistingResourcePath()) == null;\n\n        assertReadable(ResourceLoader.getResourceAsURL(getExistingResourcePath(), getThisClassLoader(), getRootPackageFile()));\n        assertReadable(ResourceLoader.getResourceAsURL(\"/\"+getExistingResourcePath(), getThisClassLoader(), getRootPackageFile()));\n        assert ResourceLoader.getResourceAsURL(getNonExistingResourcePath(), getThisClassLoader(), getRootPackageFile()) == null;\n    }\n\n    /**\n     * Tests <code>ResourceLoader#getPackageResourceAsURL</code> methods.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testPackageResourceAsURL() throws IOException {\n        assertReadable(ResourceLoader.getPackageResourceAsURL(getThisPackage(), getExistingResourceName()));\n        assert ResourceLoader.getPackageResourceAsURL(getThisPackage(), getNonExistingResourceName()) == null;\n\n        assertReadable(ResourceLoader.getPackageResourceAsURL(getThisPackage(), getExistingResourceName(), getThisClassLoader(), getRootPackageFile()));\n        assert ResourceLoader.getPackageResourceAsURL(getThisPackage(), getNonExistingResourceName(), getThisClassLoader(), getRootPackageFile()) == null;\n    }\n\n    /**\n     * Tests <code>ResourceLoader#getResourceAsStream</code> methods.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testResourceAsStream() throws IOException {\n        assertReadable(ResourceLoader.getResourceAsStream(getExistingResourcePath()));\n        assertReadable(ResourceLoader.getResourceAsStream(\"/\"+getExistingResourcePath()));\n        assert ResourceLoader.getResourceAsStream(getNonExistingResourcePath()) == null;\n\n        assertReadable(ResourceLoader.getResourceAsStream(getExistingResourcePath(), getThisClassLoader(), getRootPackageFile()));\n        assertReadable(ResourceLoader.getResourceAsStream(\"/\"+getExistingResourcePath(), getThisClassLoader(), getRootPackageFile()));\n        assert ResourceLoader.getResourceAsStream(getNonExistingResourcePath(), getThisClassLoader(), getRootPackageFile()) == null;\n    }\n\n    /**\n     * Tests <code>ResourceLoader#getPackageResourceAsStream</code> methods.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testPackageResourceAsStream() throws IOException {\n        assertReadable(ResourceLoader.getPackageResourceAsStream(getThisPackage(), getExistingResourceName()));\n        assert ResourceLoader.getPackageResourceAsStream(getThisPackage(), getNonExistingResourceName()) == null;\n\n        assertReadable(ResourceLoader.getPackageResourceAsStream(getThisPackage(), getExistingResourceName(), getThisClassLoader(), getRootPackageFile()));\n        assert ResourceLoader.getPackageResourceAsStream(getThisPackage(), getNonExistingResourceName(), getThisClassLoader(), getRootPackageFile()) == null;\n    }\n\n    /**\n     * Tests <code>ResourceLoader#getResourceAsFile</code> methods.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testResourceAsFile() throws IOException {\n        assertReadable(ResourceLoader.getResourceAsFile(getExistingResourcePath()));\n        assertReadable(ResourceLoader.getResourceAsFile(\"/\"+getExistingResourcePath()));\n        assert ResourceLoader.getResourceAsFile(getNonExistingResourcePath()) == null;\n\n        assertReadable(ResourceLoader.getResourceAsFile(getExistingResourcePath(), getThisClassLoader(), getRootPackageFile()));\n        assertReadable(ResourceLoader.getResourceAsFile(\"/\"+getExistingResourcePath(), getThisClassLoader(), getRootPackageFile()));\n        assert ResourceLoader.getResourceAsFile(getNonExistingResourcePath(), getThisClassLoader(), getRootPackageFile()) == null;\n    }\n\n    /**\n     * Tests <code>ResourceLoader#getPackageResourceAsFile</code> methods.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testPackageResourceAsFile() throws IOException {\n        assertReadable(ResourceLoader.getPackageResourceAsFile(getThisPackage(), getExistingResourceName()));\n        assert ResourceLoader.getPackageResourceAsFile(getThisPackage(), getNonExistingResourceName()) == null;\n\n        assertReadable(ResourceLoader.getPackageResourceAsFile(getThisPackage(), getExistingResourceName(), getThisClassLoader(), getRootPackageFile()));\n        assert ResourceLoader.getPackageResourceAsFile(getThisPackage(), getNonExistingResourceName(), getThisClassLoader(), getRootPackageFile()) == null;\n    }\n\n    /**\n     * Tests <code>ResourceLoader#getRootPackageAsFile</code> methods.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testRootPackageAsFile() throws IOException {\n        AbstractFile rootPackageFile = ResourceLoader.getRootPackageAsFile(getClass());\n        assert rootPackageFile != null;\n        assert rootPackageFile.exists();\n        assert rootPackageFile.isBrowsable();\n\n        AbstractFile thisClassFile = rootPackageFile.getChild(\"com/mucommander/commons/file/util/ResourceLoaderTest.class\");\n        assertReadable(thisClassFile);\n    }\n\n    /**\n     * Tests {@link ResourceLoader#getRelativeClassPath(Class)}.\n     */\n    @Test\n    public void testRelativeClassPath() {\n        assert \"com/mucommander/commons/file/util/ResourceLoaderTest.class\".equals(ResourceLoader.getRelativeClassPath(getClass()));\n    }\n\n    /**\n     * Tests {@link ResourceLoader#getRelativePackagePath(Package)}.\n     */\n    @Test\n    public void testRelativePackagePath() {\n        // Returned path does not end with a '/'\n        assert \"com/mucommander/commons/file/util\".equals(ResourceLoader.getRelativePackagePath(getThisPackage()));\n    }\n\n\n    ////////////////////\n    // Helper methods //\n    ////////////////////\n\n    private void assertReadable(InputStream in) throws IOException {\n        assert in != null;\n        StreamUtils.readUntilEOF(in);\n        in.close();\n    }\n\n    private void assertReadable(URL url) throws IOException {\n        assert url != null;\n        InputStream in = url.openStream();\n        StreamUtils.readUntilEOF(in);\n        in.close();\n    }\n\n    private void assertReadable(AbstractFile file) throws IOException {\n        assert file != null;\n        assertReadable(file.getInputStream());\n        assert file.exists();\n    }\n\n    private ClassLoader getThisClassLoader() {\n        return getClass().getClassLoader();\n    }\n\n    private Package getThisPackage() {\n        return getClass().getPackage();\n    }\n\n    private AbstractFile getRootPackageFile() {\n        return ResourceLoader.getRootPackageAsFile(getClass());\n    }\n\n    private String getExistingResourceName() {\n        return \"ResourceLoaderTest.class\";\n    }\n\n    private String getExistingResourcePath() {\n        return \"com/mucommander/commons/file/util/\"+getExistingResourceName();\n    }\n\n    private String getNonExistingResourceName() {\n        return \"RubberChicken\";\n    }\n\n    private String getNonExistingResourcePath() {\n        return \"com/mucommander/commons/file/util/\"+getNonExistingResourceName();\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/io/BoundedInputStreamTest.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\n\n/**\n * A test case for {@link com.mucommander.commons.io.BoundedInputStream}.\n *\n * @see com.mucommander.commons.io.BoundedInputStream\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic class BoundedInputStreamTest {\n\n    private final static byte[] TEST_BYTES = new byte[]{0x6d, 0x75, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x65, 0x72};\n\n\n    /**\n     * Performs some tests that are common to {@link #testBoundedStreamWithException()} and\n     * {@link #testBoundedStreamWithoutException()}.\n     *\n     * @param bin the BoundedInputStream to prepare\n     * @throws IOException should not happen\n     */\n    private void prepareBoundedStream(BoundedInputStream bin) throws IOException {\n        assert 0 == bin.getProcessedBytes();\n        assert 4 == bin.getRemainingBytes();\n        assert 4 == bin.getAllowedBytes();\n\n        assert bin.read()!=-1;\n        assert 1 == bin.getProcessedBytes();\n        assert 3 == bin.getRemainingBytes();\n        assert 4 == bin.getAllowedBytes();\n\n        assert bin.read(new byte[1])!=-1;\n        assert 2 == bin.getProcessedBytes();\n        assert 2 == bin.getRemainingBytes();\n        assert 4 == bin.getAllowedBytes();\n\n        assert bin.read(new byte[1], 0, 1)!=-1;\n        assert 3 == bin.getProcessedBytes();\n        assert 1 == bin.getRemainingBytes();\n        assert 4 == bin.getAllowedBytes();\n\n        assert bin.skip(1)!=-1;\n        assert 4 == bin.getProcessedBytes();\n        assert 0 == bin.getRemainingBytes();\n        assert 4 == bin.getAllowedBytes();\n    }\n\n    /**\n     * Tests a <code>BoundedInputStream</code> operating in bounded mode and throwing a {@link StreamOutOfBoundException}.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testBoundedStreamWithException() throws IOException {\n        BoundedInputStream bin = new BoundedInputStream(new ByteArrayInputStream(TEST_BYTES), 4, true);\n        prepareBoundedStream(bin);\n\n        boolean exceptionThrown = false;\n        try { bin.read(); }\n        catch(StreamOutOfBoundException e) { exceptionThrown = true; }\n\n        assert exceptionThrown;\n\n        exceptionThrown = false;\n        try { bin.read(new byte[1]); }\n        catch(StreamOutOfBoundException e) { exceptionThrown = true; }\n\n        assert exceptionThrown;\n\n        exceptionThrown = false;\n        try { bin.read(new byte[1], 0, 1); }\n        catch(StreamOutOfBoundException e) { exceptionThrown = true; }\n\n        assert exceptionThrown;\n\n        exceptionThrown = false;\n        try { bin.skip(1); }\n        catch(StreamOutOfBoundException e) { exceptionThrown = true; }\n\n        assert exceptionThrown;\n        \n        assert 4 == bin.getProcessedBytes();\n        assert 0 == bin.getRemainingBytes();\n        assert 4 == bin.getAllowedBytes();\n\n        // Attempt to read a chunk larger than the remaining bytes and assert that it does not throw a StreamOutOfBoundException\n        bin = new BoundedInputStream(new ByteArrayInputStream(TEST_BYTES), 4, true);\n        assert bin.read(new byte[6])!=-1;\n\n        assert 4 == bin.getProcessedBytes();\n        assert 0 == bin.getRemainingBytes();\n        assert 4 == bin.getAllowedBytes();\n    }\n\n    /**\n     * Tests a <code>BoundedInputStream</code> operating in bounded mode and returning <code>-1/</code>.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testBoundedStreamWithoutException() throws IOException {\n        BoundedInputStream bin = new BoundedInputStream(new ByteArrayInputStream(TEST_BYTES), 4, false);\n        prepareBoundedStream(bin);\n\n        assert -1 == bin.read();\n        assert -1 == bin.read(new byte[1]);\n        assert -1 == bin.read(new byte[1], 0, 1);\n        assert -1 == bin.skip(1);\n\n        assert 4 == bin.getProcessedBytes();\n        assert 0 == bin.getRemainingBytes();\n        assert 4 == bin.getAllowedBytes();\n\n        // Attempt to read a chunk larger than the remaining bytes and assert that it does not return -1\n        bin = new BoundedInputStream(new ByteArrayInputStream(TEST_BYTES), 4, false);\n        assert bin.read(new byte[6])!=-1;\n    }\n\n    /**\n     * Tests a <code>BoundedInputStream</code> operating in unbounded mode.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testUnboundedStream() throws IOException {\n        BoundedInputStream bin = new BoundedInputStream(new ByteArrayInputStream(TEST_BYTES), -1, false);\n        assert 0 == bin.getProcessedBytes();\n        assert Long.MAX_VALUE == bin.getRemainingBytes();\n        assert -1 == bin.getAllowedBytes();\n\n        assert bin.read()!=-1;\n        assert 1 == bin.getProcessedBytes();\n        assert Long.MAX_VALUE == bin.getRemainingBytes();\n        assert -1 == bin.getAllowedBytes();\n\n        assert bin.read(new byte[1])!=-1;\n        assert 2 == bin.getProcessedBytes();\n        assert Long.MAX_VALUE == bin.getRemainingBytes();\n        assert -1 == bin.getAllowedBytes();\n\n        assert bin.read(new byte[1], 0, 1)!=-1;\n        assert 3 == bin.getProcessedBytes();\n        assert Long.MAX_VALUE == bin.getRemainingBytes();\n        assert -1 == bin.getAllowedBytes();\n\n        long totalRead = 0;\n        while(bin.read()!=-1)\n            totalRead ++;\n\n        assert 8 == totalRead;\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/io/BoundedOutputStreamTest.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\n\n/**\n * A test case for {@link com.mucommander.commons.io.BoundedOutputStream}.\n *\n * @see com.mucommander.commons.io.BoundedOutputStream\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic class BoundedOutputStreamTest {\n\n    /**\n     * Tests a <code>BoundedOutputStream</code> operating in bounded mode.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testBoundedStreamWithException() throws IOException {\n        BoundedOutputStream bout = new BoundedOutputStream(new SinkOutputStream(), 4);\n\n        assert 0 == bout.getProcessedBytes();\n        assert 4 == bout.getRemainingBytes();\n        assert 4 == bout.getAllowedBytes();\n\n        bout.write(0);\n        assert 1 == bout.getProcessedBytes();\n        assert 3 == bout.getRemainingBytes();\n        assert 4 == bout.getAllowedBytes();\n\n        bout.write(new byte[1]);\n        assert 2 == bout.getProcessedBytes();\n        assert 2 == bout.getRemainingBytes();\n        assert 4 == bout.getAllowedBytes();\n\n        bout.write(new byte[2], 0, 2);\n        assert 4 == bout.getProcessedBytes();\n        assert 0 == bout.getRemainingBytes();\n        assert 4 == bout.getAllowedBytes();\n\n        boolean exceptionThrown = false;\n        try { bout.write(0); }\n        catch(StreamOutOfBoundException e) { exceptionThrown = true; }\n\n        assert exceptionThrown;\n\n        exceptionThrown = false;\n        try { bout.write(new byte[1]); }\n        catch(StreamOutOfBoundException e) { exceptionThrown = true; }\n\n        assert exceptionThrown;\n\n        exceptionThrown = false;\n        try { bout.write(new byte[1], 0, 1); }\n        catch(StreamOutOfBoundException e) { exceptionThrown = true; }\n\n        assert exceptionThrown;\n\n        assert 4 == bout.getProcessedBytes();\n        assert 0 == bout.getRemainingBytes();\n        assert 4 == bout.getAllowedBytes();\n\n        // Attempt to write a chunk larger than the remaining bytes and assert that it does not throw a StreamOutOfBoundException\n        bout = new BoundedOutputStream(new SinkOutputStream(), 4);\n        bout.write(new byte[6]);\n\n        assert 4 == bout.getProcessedBytes();\n        assert 0 == bout.getRemainingBytes();\n        assert 4 == bout.getAllowedBytes();\n    }\n\n    /**\n     * Tests a <code>BoundedOutputStream</code> operating in unbounded mode.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testUnboundedStream() throws IOException {\n        BoundedOutputStream bout = new BoundedOutputStream(new SinkOutputStream(), -1);\n\n        assert 0 == bout.getProcessedBytes();\n        assert Long.MAX_VALUE == bout.getRemainingBytes();\n        assert -1 == bout.getAllowedBytes();\n\n        bout.write(0);\n        assert 1 == bout.getProcessedBytes();\n        assert Long.MAX_VALUE == bout.getRemainingBytes();\n        assert -1 == bout.getAllowedBytes();\n\n        bout.write(new byte[1]);\n        assert 2 == bout.getProcessedBytes();\n        assert Long.MAX_VALUE == bout.getRemainingBytes();\n        assert -1 == bout.getAllowedBytes();\n\n        bout.write(new byte[2], 0, 2);\n        assert 4 == bout.getProcessedBytes();\n        assert Long.MAX_VALUE == bout.getRemainingBytes();\n        assert -1 == bout.getAllowedBytes();\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/io/BoundedReaderTest.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.CharArrayReader;\nimport java.io.IOException;\n\n/**\n * A test case for {@link com.mucommander.commons.io.BoundedReader}.\n *\n * @see com.mucommander.commons.io.BoundedReader\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic class BoundedReaderTest {\n\n    private final static char[] TEST_CHARACTERS = new char[]{'m', 'u', 'c', 'o', 'm', 'm', 'a', 'n', 'd', 'e', 'r'};\n\n\n    /**\n     * Performs some tests that are common to {@link #testBoundedReaderWithException()} and\n     * {@link #testBoundedReaderWithoutException()}.\n     *\n     * @param br the BoundedReader to prepare\n     * @throws IOException should not happen\n     */\n    private void prepareBoundedReader(BoundedReader br) throws IOException {\n        assert 0 == br.getReadCounter();\n        assert 4 == br.getRemainingCharacters();\n        assert 4 == br.getAllowedCharacters();\n\n        assert br.read()!=-1;\n        assert 1 == br.getReadCounter();\n        assert 3 == br.getRemainingCharacters();\n        assert 4 == br.getAllowedCharacters();\n\n        assert br.read(new char[1])!=-1;\n        assert 2 == br.getReadCounter();\n        assert 2 == br.getRemainingCharacters();\n        assert 4 == br.getAllowedCharacters();\n\n        assert br.read(new char[1], 0, 1)!=-1;\n        assert 3 == br.getReadCounter();\n        assert 1 == br.getRemainingCharacters();\n        assert 4 == br.getAllowedCharacters();\n\n        assert br.skip(1)!=-1;\n        assert 4 == br.getReadCounter();\n        assert 0 == br.getRemainingCharacters();\n        assert 4 == br.getAllowedCharacters();\n    }\n\n    /**\n     * Tests a <code>BoundedReader</code> operating in bounded mode.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testBoundedReaderWithException() throws IOException {\n        BoundedReader br = new BoundedReader(new CharArrayReader(TEST_CHARACTERS), 4, new StreamOutOfBoundException(4));\n        prepareBoundedReader(br);\n\n        boolean exceptionThrown = false;\n        try { br.read(); }\n        catch(StreamOutOfBoundException e) { exceptionThrown = true; }\n\n        assert exceptionThrown;\n\n        exceptionThrown = false;\n        try { br.read(new char[1]); }\n        catch(StreamOutOfBoundException e) { exceptionThrown = true; }\n\n        assert exceptionThrown;\n\n        exceptionThrown = false;\n        try { br.read(new char[1], 0, 1); }\n        catch(StreamOutOfBoundException e) { exceptionThrown = true; }\n\n        assert exceptionThrown;\n\n        exceptionThrown = false;\n        try { br.skip(1); }\n        catch(StreamOutOfBoundException e) { exceptionThrown = true; }\n\n        assert exceptionThrown;\n\n        assert 4 == br.getReadCounter();\n        assert 0 == br.getRemainingCharacters();\n        assert 4 == br.getAllowedCharacters();\n\n        // Attempt to read a chunk larger than the remaining characters and assert that it does not throw a StreamOutOfBoundException\n        br = new BoundedReader(new CharArrayReader(TEST_CHARACTERS), 4);\n        assert br.read(new char[6])!=-1;\n    }\n\n    /**\n     * Tests a <code>BoundedReader</code> operating in bounded mode.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testBoundedReaderWithoutException() throws IOException {\n        BoundedReader br = new BoundedReader(new CharArrayReader(TEST_CHARACTERS), 4);\n        prepareBoundedReader(br);\n\n        assert -1 == br.read();\n        assert -1 == br.read(new char[1]);\n        assert -1 == br.read(new char[1], 0, 1);\n        assert -1 == br.skip(1);\n\n        assert 4 == br.getReadCounter();\n        assert 0 == br.getRemainingCharacters();\n        assert 4 == br.getAllowedCharacters();\n\n        // Attempt to read a chunk larger than the remaining characters and assert that it does not return -1\n        br = new BoundedReader(new CharArrayReader(TEST_CHARACTERS), 4);\n        assert br.read(new char[6])!=-1;\n    }\n\n    /**\n     * Tests a <code>BoundedReader</code> operating in unbounded mode.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testUnboundedReader() throws IOException {\n        BoundedReader br = new BoundedReader(new CharArrayReader(TEST_CHARACTERS), -1);\n        assert 0 == br.getReadCounter();\n        assert Long.MAX_VALUE == br.getRemainingCharacters();\n        assert -1 == br.getAllowedCharacters();\n\n        assert br.read()!=-1;\n        assert 1 == br.getReadCounter();\n        assert Long.MAX_VALUE == br.getRemainingCharacters();\n        assert -1 == br.getAllowedCharacters();\n\n        assert br.read(new char[1])!=-1;\n        assert 2 == br.getReadCounter();\n        assert Long.MAX_VALUE == br.getRemainingCharacters();\n        assert -1 == br.getAllowedCharacters();\n\n        assert br.read(new char[1], 0, 1)!=-1;\n        assert 3 == br.getReadCounter();\n        assert Long.MAX_VALUE == br.getRemainingCharacters();\n        assert -1 == br.getAllowedCharacters();\n\n        long totalRead = 0;\n        while(br.read()!=-1)\n            totalRead ++;\n\n        assert 8 == totalRead;\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/io/BufferPoolTest.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io;\n\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * This class is a test case for {@link BufferPool}.\n *\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic class BufferPoolTest {\n\n    public final static int TEST_BUFFER_SIZE_1 = 27;\n    public final static int TEST_BUFFER_SIZE_2 = 28;\n    public final static int TEST_MAX_POOL_SIZE = 1000;\n\n    @BeforeAll\n    public static void setup() {\n    \tBufferPool.releaseAll();\n    }\n\n    /**\n     * Tests <code>BufferPool</code> with byte array (<code>byte[]</code>) buffers.\n     *\n     * <p>This method invokes {@link #testBuffer(com.mucommander.commons.io.BufferPool.BufferFactory)} with a\n     * {@link BufferPool.ByteArrayFactory} instance.\n     */\n    @Test\n    public void testByteArrayBuffer() {\n        testBuffer(new BufferPool.ByteArrayFactory());\n    }\n\n    /**\n     * Tests <code>BufferPool</code> with char array (<code>char[]</code>) buffers.\n     *\n     * <p>This method invokes {@link #testBuffer(com.mucommander.commons.io.BufferPool.BufferFactory)} with a\n     * {@link BufferPool.CharArrayFactory} instance.\n     */\n    @Test\n    public void testCharArrayBuffer() {\n        testBuffer(new BufferPool.CharArrayFactory());\n    }\n\n    /**\n     * Tests <code>BufferPool</code> with <code>ByteBuffer</code> buffers.\n     *\n     * <p>This method invokes {@link #testBuffer(com.mucommander.commons.io.BufferPool.BufferFactory)} with a\n     * {@link BufferPool.ByteBufferFactory} instance.\n     */\n    @Test\n    public void testByteBuffer() {\n        testBuffer(new BufferPool.ByteBufferFactory());\n    }\n\n    /**\n     * Tests <code>BufferPool</code> with <code>CharBuffer</code> buffers.\n     *\n     * <p>This method invokes {@link #testBuffer(com.mucommander.commons.io.BufferPool.BufferFactory)} with a\n     * {@link BufferPool.CharBufferFactory} instance.\n     */\n    @Test\n    public void testCharBuffer() {\n        testBuffer(new BufferPool.CharBufferFactory());\n    }\n\n    /**\n     * Tests <code>BufferPool</code> with <code>ByteBuffer</code> buffers.\n     *\n     * <p>This test assumes that no buffer with size=={@link #TEST_BUFFER_SIZE_1} or size=={@link #TEST_BUFFER_SIZE_2}\n     * exist in the pool when the test starts. It also assumes that no other thread uses <code>BufferPool</code> while\n     * the test is being performed. <code>BufferPool</code> will be left in the same state as it was right before the\n     * test.\n     *\n     * @param factory the factory corresponding to the kind of buffer to test\n     */\n    public void testBuffer(BufferPool.BufferFactory factory) {\n        // Number of array buffers before we started the test\n        int originalBufferCount = BufferPool.getBufferCount(factory);\n        assertEquals(0, originalBufferCount);\n        long originalPoolSize = BufferPool.getPoolSize();\n\n        // create a new buffer with size=TEST_BUFFER_SIZE_1\n        Object buffer1 = BufferPool.getBuffer(factory, TEST_BUFFER_SIZE_1);\n        assertBufferSize(buffer1, factory, TEST_BUFFER_SIZE_1);\n\n        // create a new buffer with size=TEST_BUFFER_SIZE_1, assert that it is different from the first one\n        Object buffer2 = BufferPool.getBuffer(factory, TEST_BUFFER_SIZE_1);\n        assertBufferSize(buffer2, factory, TEST_BUFFER_SIZE_1);\n        assert buffer2!=buffer1;\n\n        // create a new buffer with size=TEST_BUFFER_SIZE_2\n        Object buffer3 = BufferPool.getBuffer(factory, TEST_BUFFER_SIZE_2);\n        assertBufferSize(buffer3, factory, TEST_BUFFER_SIZE_2);\n\n        // Assert that the number of buffers in the pool and the pool size in bytes haven't changed\n        assertBufferCount(originalBufferCount, factory);\n        assert originalPoolSize == BufferPool.getPoolSize();\n\n        // Assert that none of the buffers we created are in the pool\n        assert !BufferPool.containsBuffer(buffer1, factory);\n        assert !BufferPool.containsBuffer(buffer2, factory);\n        assert !BufferPool.containsBuffer(buffer3, factory);\n\n        // Assert that releasing buffer3 and requesting a buffer with size=TEST_BUFFER_SIZE_2 brings back buffer3\n        BufferPool.releaseBuffer(buffer3, factory);\n        assertBufferCount(originalBufferCount+1, factory);\n        assert BufferPool.containsBuffer(buffer3, factory);\n        assert buffer3==BufferPool.getBuffer(factory, TEST_BUFFER_SIZE_2);\n        assertBufferCount(originalBufferCount, factory);\n        assert !BufferPool.containsBuffer(buffer3, factory);\n\n        // Release all buffer instances and assert that the buffer count grows accordingly\n        Object[] buffers = new Object[]{buffer2, buffer3, buffer1};\n        for(int b=0; b<buffers.length; b++) {\n            // Call releaseBuffer twice and assert that the buffer is added to the pool only the first time\n            assert BufferPool.releaseBuffer(buffers[b], factory);\n            assert BufferPool.containsBuffer(buffers[b], factory);\n            assertBufferCount(originalBufferCount+(b+1), factory);\n\n            assert !BufferPool.releaseBuffer(buffers[b], factory);\n            assert BufferPool.containsBuffer(buffers[b], factory);\n            assertBufferCount(originalBufferCount+(b+1), factory);\n        }\n\n        // Retrieve all the buffers we created to leave BufferPool as it was before the test\n        // and assert that the buffer count diminishes accordingly\n        buffers = new Object[]{buffer3, buffer1, buffer2};\n        for(int b=0; b<buffers.length; b++) {\n            BufferPool.getBuffer(factory, getBufferLength(buffers[b], factory));\n            assertBufferCount(originalBufferCount+(3-b-1), factory);\n        }\n\n        // Test the initial default buffer size\n        assert BufferPool.INITIAL_DEFAULT_BUFFER_SIZE == BufferPool.getDefaultBufferSize();\n        assertBufferSize(BufferPool.getBuffer(factory), factory, BufferPool.INITIAL_DEFAULT_BUFFER_SIZE);\n\n        // Test a custom default buffer size\n        BufferPool.setDefaultBufferSize(TEST_BUFFER_SIZE_1);\n        assert TEST_BUFFER_SIZE_1 == BufferPool.getDefaultBufferSize();\n        assertBufferSize(BufferPool.getBuffer(factory), factory, TEST_BUFFER_SIZE_1);\n\n        // Reset the default buffer size to the initial value\n        BufferPool.setDefaultBufferSize(BufferPool.INITIAL_DEFAULT_BUFFER_SIZE);\n\n        // Test max pool size: max out the pool and verify that releaseBuffer fails (returns false) when trying\n        // to add an extra buffer\n        assert BufferPool.INITIAL_POOL_LIMIT == BufferPool.getMaxPoolSize();\n        BufferPool.setMaxPoolSize(TEST_MAX_POOL_SIZE);\n        assert TEST_MAX_POOL_SIZE == BufferPool.getMaxPoolSize();\n        long bufferSize = getBufferSize(BufferPool.getBuffer(factory, TEST_BUFFER_SIZE_1), factory);    // in bytes\n        int nbBuffers = (int)(TEST_MAX_POOL_SIZE/bufferSize);\n        buffers = new Object[nbBuffers];\n        for(int i=0; i<nbBuffers; i++)\n            buffers[i] = BufferPool.getBuffer(factory, TEST_BUFFER_SIZE_1);\n\n        Object extraBuffer = BufferPool.getBuffer(factory, TEST_BUFFER_SIZE_1);\n\n        assertEquals(originalPoolSize,BufferPool.getPoolSize());\n        long lastPoolSize = originalPoolSize;\n        long newPoolSize;\n        for(int i=0; i<nbBuffers; i++) {\n            assert BufferPool.releaseBuffer(buffers[i], factory);\n            newPoolSize = BufferPool.getPoolSize();\n            assert lastPoolSize+bufferSize == newPoolSize;\n            lastPoolSize = newPoolSize;\n        }\n        // At this point, the BufferPool should be maxed out, try adding one more buffer and assert that this fails\n        assert !BufferPool.releaseBuffer(extraBuffer, factory);\n\n        // Retrieve all the buffers we created to leave BufferPool as it was before the test\n        // and assert that the pool returns to its original size\n        for(int i=0; i<nbBuffers; i++)\n            BufferPool.getBuffer(factory, TEST_BUFFER_SIZE_1);\n\n        assert originalPoolSize == BufferPool.getPoolSize();\n\n        BufferPool.setMaxPoolSize(BufferPool.INITIAL_POOL_LIMIT);\n    }\n\n    /**\n     * Asserts that the given buffer's size matches the specified one.\n     *\n     * @param buffer the buffer to test\n     * @param factory the factory that was used to create the buffer\n     * @param expectedSize the expected buffer size\n     */\n    private void assertBufferSize(Object buffer, BufferPool.BufferFactory factory, int expectedSize) {\n        assert expectedSize == getBufferLength(buffer, factory);\n    }\n\n    /**\n     * Returns the length of the given buffer, as defined by {@link com.mucommander.commons.io.BufferPool.BufferContainer#getLength()}.\n     *\n     * @param buffer the buffer for which to return the length\n     * @param factory the factory that was used to create the buffer\n     * @return the length of the given buffer\n     */\n    private int getBufferLength(Object buffer, BufferPool.BufferFactory factory) {\n        return factory.newBufferContainer(buffer).getLength();\n    }\n\n    /**\n     * Returns the size in bytes of the given buffer, as defined by {@link com.mucommander.commons.io.BufferPool.BufferContainer#getSize()}.\n     *\n     * @param buffer the buffer for which to return the size\n     * @param factory the factory that was used to create the buffer\n     * @return the size in bytes of the given buffer\n     */\n    private int getBufferSize(Object buffer, BufferPool.BufferFactory factory) {\n        return factory.newBufferContainer(buffer).getSize();\n    }\n\n    /**\n     * Asserts that BufferPool contains <code>expectedCount</code> buffers of the kind corresponding to the factory.\n     *\n     * @param expectedCount the expected number of buffers\n     * @param factory the factory corresponding to the kind of buffer to count\n     */\n    private void assertBufferCount(int expectedCount, BufferPool.BufferFactory factory) {\n        assert expectedCount == BufferPool.getBufferCount(factory);\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/io/base64/Base64Test.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io.base64;\n\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.util.Random;\n\n/**\n * A test case for the <code>com.mucommander.commons.io.base64</code> package.\n *\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic class Base64Test {\n\n    /**\n     * Tests base64 encoding and decoding on known sequences, ensuring that base 64 encoding and decoding produces\n     * the expected results.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testKnownSequences() throws IOException {\n        // On an ASCII sequence\n        testKnownSequence(\"The quick brown fox jumps over the lazy dog.\", \"VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4=\");\n\n        // On a Unicode sequence. Note that the string has been encoded using UTF-8 bytes.\n        testKnownSequence(\"どうもありがとうミスターロボット\", \"44Gp44GG44KC44GC44KK44GM44Go44GG44Of44K544K/44O844Ot44Oc44OD44OI\");\n    }\n\n    /**\n     * Tests base64 encoding and decoding on a known sequence, ensuring that base 64 encoding and decoding produces\n     * the expected results.\n     *\n     * @param decodedSequence the base64-decoded string\n     * @param encodedSequence the base64-encoding string\n     * @throws IOException should not happen\n     */\n    private void testKnownSequence(String decodedSequence, String encodedSequence) throws IOException {\n        assert encodedSequence.equals(Base64Encoder.encode(decodedSequence, \"UTF-8\", Base64Table.STANDARD_TABLE));\n        assert decodedSequence.equals(Base64Decoder.decode(encodedSequence, \"UTF-8\", Base64Table.STANDARD_TABLE));\n    }\n\n\n    /**\n     * Successively encodes and decodes randomly-generated strings of varying length and content, and verifies that\n     * the resulting string remains the same as the original.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testRandomStringIntegrity() throws IOException {\n        Random random = new Random();\n\n        StringBuffer sb;\n        String s;\n        int slen;\n        // Repeats the test\n        for(int i=0; i<100; i++) {\n            // Generates a string with:\n            // - a random length of up to 1000 characters\n            // - random contents, where each byte's value is randomly chosen between 0 and 255\n            slen = random.nextInt(1000);\n\n            sb = new StringBuffer();\n            for(int j=0; j<slen; j++)\n                sb.append((char)random.nextInt(256));\n\n            s = sb.toString();\n\n            assert s.equals(Base64Decoder.decode(Base64Encoder.encode(s, \"UTF-8\", Base64Table.STANDARD_TABLE)));\n        }\n    }\n\n    /**\n     * Validates that <code>java.io.IOException</code> is properly thrown by {@link Base64InputStream}\n     * when a character out of the base64 range is encountered. All such characters are successively tested.\n     */\n    @Test\n    public void testInvalidCharacters() {\n        char c;\n        boolean exceptionCaught;\n\n        for(c=0; c<256; c++) {\n            // Skip allowed Base64 characters, including the special '=' character used for padding\n            if((c>='0' && c<='9') || (c>='A' && c<='Z') || (c>='a' && c<='z') || c=='+' || c=='/' || c=='=')\n                continue;\n\n            exceptionCaught = false;\n\n            try {\n                Base64Decoder.decode(c+\"===\");      // Add padding at the end\n            }\n            catch(IOException e) {\n                exceptionCaught = true;\n            }\n\n            assert exceptionCaught;\n        }\n    }\n\n    /**\n     * Validates that <code>java.io.IOException</code> is properly thrown by {@link Base64InputStream}\n     * when the provided Base64 string's length is not a multiple of 4 (i.e. is not properly padded with '=').\n     */\n    @Test\n    public void testInvalidLength() {\n        String invalidLengthStrings[] = {\n            \"a\", \"ab\", \"abc\",\n            \"=\", \"a=\", \"a==\", \"ab=\",\n            \"0000a\", \"0000ab\", \"0000abc\",\n            \"0000a=\", \"0000a==\", \"0000ab=\"\n        };\n\n        boolean exceptionCaught;\n\n        for (String invalidLengthString : invalidLengthStrings) {\n            exceptionCaught = false;\n\n            try {\n                Base64Decoder.decode(invalidLengthString);\n            }\n            catch (IOException e) {\n                exceptionCaught = true;\n            }\n\n            assert exceptionCaught;\n        }\n    }\n\n\n    /**\n     * Tests {@link Base64Table} preset instances.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testPresetTables() throws IOException {\n        Base64Table[] tables = new Base64Table[] {\n            Base64Table.STANDARD_TABLE,\n            Base64Table.URL_SAFE_TABLE,\n            Base64Table.FILENAME_SAFE_TABLE,\n            Base64Table.REGEXP_SAFE_TABLE,\n        };\n\n        String sample = \"The quick brown fox jumps over the lazy dog.\";\n        for(Base64Table table: tables) {\n            // Ensure that the table passes Base64Table constructor's tests\n            new Base64Table(table.getEncodingTable(), table.getPaddingChar());\n\n            // Ensures that encoding followed by decoding yields the original string\n            assert sample.equals(Base64Decoder.decode(Base64Encoder.encode(sample, \"UTF-8\", table), \"UTF-8\", table));\n        }\n\n    }\n\n    /**\n     * Tests {@link Base64Table#Base64Table(byte[], byte)} with invalid parameter values.\n     */\n    @Test\n    public void testCustomBase64Table()  {\n        testInvalidBase64Table(null, (byte)'a');\n        testInvalidBase64Table(new byte[]{}, (byte)'a');\n\n        byte[] validEncodingTable = Base64Table.STANDARD_TABLE.getEncodingTable();\n\n        testInvalidBase64Table(validEncodingTable, (byte)'a');\n\n        byte[] invalidEncodingTable = new byte[63];\n        System.arraycopy(validEncodingTable, 0, invalidEncodingTable, 0, 63);\n\n        testInvalidBase64Table(invalidEncodingTable, (byte)'a');\n\n        invalidEncodingTable = new byte[64];\n        System.arraycopy(validEncodingTable, 0, invalidEncodingTable, 0, 64);\n        invalidEncodingTable[63] = 'b';\n\n        testInvalidBase64Table(invalidEncodingTable, (byte)'a');\n\n        // Test a valid custom Base64 table\n        new Base64Table(new byte[]{\n                'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',\n                'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',\n                'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',\n                'w','x','y','z','0','1','2','3','4','5','6','7','8','9','@','!'\n        }, (byte)'%');\n    }\n\n\n    /**\n     * Tries to create a <code>Base64Table</code> with the specified parameters and asserts that it throws\n     * an {@link IllegalArgumentException}.\n     *\n     * @param table the base64 character table. The array must be 64 bytes long and must not contain any duplicate values.\n         * @param paddingChar the ASCII character used for padding. This character must not already be used in the table.\n         */\n    private void testInvalidBase64Table(byte[] table, byte paddingChar) {\n        boolean exceptionThrown = false;\n        try {\n            new Base64Table(table, paddingChar);\n        }\n        catch(IllegalArgumentException e) {\n            exceptionThrown = true;\n        }\n\n        assert exceptionThrown;\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/io/bom/BOMTest.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io.bom;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * A test case for the <code>com.mucommander.commons.io.bom</code> package.\n *\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic class BOMTest implements BOMConstants {\n\n    /**\n     * Tests {@link BOM} comparison methods.\n     */\n    @Test\n    public void testBOMComparisons() {\n        // Tests BOM#sigStartsWith method\n        assert UTF8_BOM.sigStartsWith(new byte[]{(byte)0xEF, (byte)0xBB});\n        assert UTF8_BOM.sigStartsWith(new byte[]{(byte)0xEF, (byte)0xBB, (byte)0xBF});\n        assert !UTF8_BOM.sigStartsWith(new byte[]{(byte)0xAA});\n        assert !UTF8_BOM.sigStartsWith(new byte[]{(byte)0xEF, (byte)0xBB, (byte)0xBF, (byte)0xAA});\n\n        // Tests BOM#sigEquals method\n        assert UTF8_BOM.sigEquals(UTF8_BOM.getSignature());\n        assert !UTF8_BOM.sigEquals(UTF16_LE_BOM.getSignature());\n\n        // Tests BOM#equals method\n        assert UTF8_BOM.equals(UTF8_BOM);\n        assert !UTF8_BOM.equals(UTF16_LE_BOM);\n        assert !UTF8_BOM.equals(new Object());\n    }\n\n    /**\n     * Tests proper detection of known BOMs.\n     *\n     * @throws IOException should normally not happen\n     */\n    @Test\n    public void testBOMInputStream() throws IOException {\n        BOMInputStream bomIn;\n        byte[] b;\n\n        for (BOM bom : SUPPORTED_BOMS) {\n            bomIn = getBOMInputStream(bom.getSignature());\n            assert bom.equals(bomIn.getBOM());\n            assertEOF(bomIn);\n        }\n\n        // UTF-8 BOM, plus one byte after\n        b = new byte[]{(byte)0xEF, (byte)0xBB, (byte)0xBF, (byte)0x27};\n        bomIn = getBOMInputStream(b);\n        assert UTF8_BOM.equals(bomIn.getBOM());\n        assertStreamEquals(new byte[]{(byte)0x27}, bomIn);\n        assertEOF(bomIn);\n\n        // Not a known BOM\n        b = new byte[]{(byte)0xEF, (byte)0xBB, (byte)0x27};\n        bomIn = getBOMInputStream(b);\n        assert bomIn.getBOM() == null;\n        assertStreamEquals(b, bomIn);\n\n        // Empty stream, BOM should be null\n        b = new byte[]{};\n        bomIn = getBOMInputStream(b);\n        assert bomIn.getBOM() == null;\n        assertEOF(bomIn);\n\n        // BOMs should not match\n        b = UTF16_BE_BOM.getSignature();\n        bomIn = getBOMInputStream(b);\n        assert !UTF8_BOM.equals(bomIn.getBOM());\n        assertEOF(bomIn);\n    }\n\n    /**\n     * Tests {@link BOM#getInstance(String)}.\n     */\n    @Test\n    public void testBOMResolution() {\n        for (BOM bom : SUPPORTED_BOMS) {\n            // Test case variations\n            assert bom.equals(BOM.getInstance(bom.getEncoding().toLowerCase()));\n            assert bom.equals(BOM.getInstance(bom.getEncoding().toUpperCase()));\n        }\n\n        // Test non-UTF encodings\n        assert BOM.getInstance(\"ISO-8859-1\") == null;\n        assert BOM.getInstance(\"Shift_JIS\") == null;\n\n        // Test UTF aliases\n        assert BOMConstants.UTF16_BE_BOM.equals(BOM.getInstance(\"UnicodeBig\"));\n        assert BOMConstants.UTF16_BE_BOM.equals(BOM.getInstance(\"UnicodeBigUnmarked\"));\n        assert BOMConstants.UTF16_BE_BOM.equals(BOM.getInstance(\"UTF-16\"));\n        assert BOMConstants.UTF16_LE_BOM.equals(BOM.getInstance(\"UnicodeLittle\"));\n        assert BOMConstants.UTF16_LE_BOM.equals(BOM.getInstance(\"UnicodeLittleUnmarked\"));\n        assert BOMConstants.UTF32_BE_BOM.equals(BOM.getInstance(\"UTF-32\"));\n    }\n\n    /**\n     * Tests {@link BOMWriter}.\n     *\n     * @throws IOException should not happen\n     */\n    @Test\n    public void testBOMWriter() throws IOException {\n        String testString = \"This is a test\";\n        ByteArrayOutputStream baos;\n        BOMWriter bomWriter;\n        BOMInputStream bomIn;\n\n        for (BOM bom : SUPPORTED_BOMS) {\n            baos = new ByteArrayOutputStream();\n            bomWriter = new BOMWriter(baos, bom.getEncoding());\n            bomWriter.write(testString);\n            bomWriter.close();\n\n            bomIn = getBOMInputStream(baos.toByteArray());\n            assert bom.equals(bomIn.getBOM());\n            assertStreamEquals(testString.getBytes(bom.getEncoding()), bomIn);\n            assertEOF(bomIn);\n        }\n    }\n\n\n    ////////////////////\n    // Helper methods //\n    ////////////////////\n\n    private BOMInputStream getBOMInputStream(byte b[]) throws IOException {\n        return new BOMInputStream(new ByteArrayInputStream(b));\n    }\n\n    private void assertEOF(InputStream in) throws IOException {\n        assert-1 == in.read();\n        // Again\n        assert -1 == in.read();\n    }\n\n    private void assertStreamEquals(byte bytes[], InputStream in) throws IOException {\n        for (byte b : bytes)\n            assert b == (byte) (in.read() & 0xFF);\n\n        assertEOF(in);\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/io/compound/CompoundInputStreamTest.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io.compound;\n\nimport com.mucommander.commons.io.StreamUtils;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.util.Iterator;\nimport java.util.Vector;\n\n/**\n * A test case for {@link CompoundInputStream}.\n *\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic class CompoundInputStreamTest {\n\n    /** Test strings */\n    private final static String[] TEST_STRINGS = {\n        \"\",\n        \"this\",\n        \"is\",\n        \"a\",\n        \"\",\n        \"test\",\n        \"\"\n    };\n\n    /** Concatenation of the strings contained by {@link #TEST_STRINGS} */\n    private final static String TEST_FLATTENED_STRINGS;\n    \n    static {\n        StringBuilder sb = new StringBuilder();\n        for (String testString : TEST_STRINGS) {\n            sb.append(testString);\n        }\n\n        TEST_FLATTENED_STRINGS = sb.toString();\n    }\n\n\n    /**\n     * Returns a test InputStream iterator.\n     *\n     * @return a test InputStream iterator.\n     */\n    private static Iterator<ByteArrayInputStream> getTestInputStreamIterator() {\n        Vector<ByteArrayInputStream> v = new Vector<>();\n\n        for (String testString : TEST_STRINGS) \n            v.add(new ByteArrayInputStream(testString.getBytes()));\n\n        return v.iterator();\n    }\n\n\n    /**\n     * Tests {@link CompoundInputStream} in merged mode.\n     *\n     * @throws IOException should not happen.\n     */\n    @Test\n    public void testMerged() throws IOException {\n        CompoundInputStream in = new IteratorCompoundInputStream(getTestInputStreamIterator(), true);\n\n        assert in.isMerged();\n\n        ByteArrayOutputStream bout = new ByteArrayOutputStream();\n        StreamUtils.copyStream(in, bout);\n        assert TEST_FLATTENED_STRINGS.equals(bout.toString());\n\n        assert !in.advanceInputStream();\n    }\n\n    /**\n     * Tests {@link CompoundInputStream} in unmerged mode.\n     *\n     * @throws IOException should not happen.\n     */\n    @Test\n    public void testUnmerged() throws IOException {\n        CompoundInputStream in = new IteratorCompoundInputStream(getTestInputStreamIterator(), false);\n\n        assert !in.isMerged();\n\n        for(int i=0; i<TEST_STRINGS.length; i++) {\n            ByteArrayOutputStream bout = new ByteArrayOutputStream();\n            StreamUtils.copyStream(in, bout);\n            assert TEST_STRINGS[i].equals(bout.toString());\n\n            // Try to advance to the next stream\n            assert (i != TEST_STRINGS.length-1) == in.advanceInputStream();\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/commons/io/compound/CompoundReaderTest.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.io.compound;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.io.StringReader;\nimport java.util.Iterator;\nimport java.util.Vector;\n\n/**\n * A test case for {@link CompoundReader}.\n *\n * @author Maxence Bernard, Nicolas Rinaudo\n */\npublic class CompoundReaderTest {\n\n    /** Test strings */\n    private final static String[] TEST_STRINGS = {\n        \"\",\n        \"this\",\n        \"is\",\n        \"a\",\n        \"\",\n        \"test\",\n        \"\"\n    };\n\n    /** Concatenation of the strings contained by {@link #TEST_STRINGS} */\n    private final static String TEST_FLATTENED_STRINGS;\n\n    static {\n        StringBuilder sb = new StringBuilder();\n        for (String testString : TEST_STRINGS)\n            sb.append(testString);\n\n        TEST_FLATTENED_STRINGS = sb.toString();\n    }\n\n\n    /**\n     * Returns a test Reader iterator.\n     *\n     * @return a test Reader iterator.\n     */\n    private static Iterator<StringReader> getTestReaderIterator() {\n        Vector<StringReader> v = new Vector<>();\n\n        for (String testString : TEST_STRINGS)\n            v.add(new StringReader(testString));\n\n        return v.iterator();\n    }\n\n    private String copyReader(Reader reader) throws IOException {\n        StringBuilder sb = new StringBuilder();\n        int c;\n        while((c=reader.read())!=-1)\n            sb.append((char)c);\n\n        return sb.toString();\n    }\n    \n    /**\n     * Tests {@link CompoundReader} in merged mode.\n     *\n     * @throws IOException should not happen.\n     */\n    @Test\n    public void testMerged() throws IOException {\n        CompoundReader reader = new IteratorCompoundReader(getTestReaderIterator(), true);\n\n        assert reader.isMerged();\n\n        assert TEST_FLATTENED_STRINGS.equals(copyReader(reader));\n\n        assert !reader.advanceReader();\n    }\n\n    /**\n     * Tests {@link CompoundInputStream} in unmerged mode.\n     *\n     * @throws IOException should not happen.\n     */\n    @Test\n    public void testUnmerged() throws IOException {\n        CompoundReader reader = new IteratorCompoundReader(getTestReaderIterator(), false);\n\n        assert !reader.isMerged();\n\n        for(int i=0; i<TEST_STRINGS.length; i++) {\n            assert TEST_STRINGS[i].equals(copyReader(reader));\n\n            // Try to advance to the next reader\n            assert(i!=TEST_STRINGS.length-1) ==  reader.advanceReader();\n        }\n    }\n}"
  },
  {
    "path": "src/test/java/com/mucommander/commons/util/StringUtilsTest.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.commons.util;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport ru.trolsoft.test.VariableSource;\n\nimport java.util.stream.Stream;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests the {@link StringUtils} class.\n * @author Nicolas Rinaudo\n */\npublic class StringUtilsTest {\n\n    /**\n     * Tests the {@link StringUtils#endsWith(String, char[])} method.\n     * @param a        string to compare.\n     * @param b        char array to test.\n     * @param expected expected return value of {@link StringUtils#endsWith(String, char[])}.\n     */\n\n    @ParameterizedTest\n    @CsvSource(value = {\n        \"abc, c,   true\",\n        \"abc, bc,  true\",\n        \"abc, abc, true\",\n        \"abc, '',    true\",\n\n        \"abc, C,   false\",\n        \"abc, BC,  false\",\n        \"abc, ABC, false\",\n\n        \"abc, d,   false\",\n        \"abc, de,  false\",\n        \"abc, def, false\",\n    })\n    public void testEndsWith(String a, String b, boolean expected) {\n        assertEquals(expected, StringUtils.endsWith(a, b.toCharArray()));\n    }\n\n\n\n    @ParameterizedTest\n    @CsvSource({\n        \"abc, C, 3, true\",\n        \"abc, B, 2, true\",\n        \"abc, A, 1, true\",\n        \"abc, '',  0, true\",\n\n        \"abc, ABC, 3, true\",\n        \"abc, AB,  2, true\",\n        \"abc, A,   1, true\",\n        \"abc, '',    0, true\",\n\n        \"abc, abc, 2, false\",\n\n        \"abc, 123, 3, false\",\n        \"abc, 123, 2, false\",\n        \"abc, 123, 1, false\",\n        \"abc, 123, 0, false\",\n    })\n    public void testMatchesIgnoreCaseCharArray(String a, String b, int pos, boolean expected) {\n        assert StringUtils.matchesIgnoreCase(a, b.toCharArray(), pos) == expected;\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n            \"abc, C, 3, true\",\n            \"abc, B, 2, true\",\n            \"abc, A, 1, true\",\n            \"abc, '',  0, true\",\n\n            \"abc, ABC, 3, true\",\n            \"abc, AB,  2, true\",\n            \"abc, A,   1, true\",\n            \"abc, '',  0, true\",\n\n            \"abc, abc, 2, false\",\n\n            \"abc, 123, 3, false\",\n            \"abc, 123, 2, false\",\n            \"abc, 123, 1, false\",\n            \"abc, 123, 0, false\",\n    })\n    public void testMatchesIgnoreCase(String a, String b, int pos, boolean expected) {\n        assert StringUtils.matchesIgnoreCase(a, b, pos) == expected;\n    }\n\n\n    /**\n     * Tests the {@link StringUtils#matches(String, char[], int)} method.\n     * @param a        first string.\n     * @param b        second string.\n     * @param pos      position at which to start the comparison.\n     * @param expected expected return value of {@link StringUtils#matches(String, char[], int)}\n     */\n    @ParameterizedTest\n    @CsvSource({\n        \"abc, c, 3, true\",\n        \"abc, b, 2, true\",\n        \"abc, a, 1, true\",\n        \"abc, '',  0, true\",\n\n        \"abc, abc, 3, true\",\n        \"abc, ab,  2, true\",\n        \"abc, a,   1, true\",\n        \"abc, '',    0, true\",\n\n        \"abc, abc, 2, false\",\n\n        \"abc, aBC, 3, false\",\n        \"abc, ABC, 2, false\",\n        \"abc, aBc, 1, false\",\n        \"abc, ABc, 0, false\",\n    })\n    public void testMatches(String a, String b, int pos, boolean expected) {\n        assert StringUtils.matches(a, b.toCharArray(), pos) == expected;\n    }\n\n\n    /**\n     * Tests the {@link StringUtils#parseIntDef(String, int)} method.\n     * @param input    string to parse.\n     * @param def      default value.\n     * @param expected expected return value of {@link StringUtils#parseIntDef(String, int)}.\n     */\n    @ParameterizedTest\n    @CsvSource({\n            \"0,      0, 0\",\n            \"foobar, 0, 0\",\n            \"1,      0, 1\",\n            \"foobar, 1, 1\",\n            \"2,      0, 2\",\n            \"foobar, 2, 2\",\n            \"3,      0, 3\",\n            \"foobar, 3, 3\",\n            \"4,      0, 4\",\n            \"foobar, 4, 4\",\n            \"5,      0, 5\",\n            \"foobar, 5, 5\",\n            \"6,      0, 6\",\n            \"foobar, 6, 6\",\n            \"7,      0, 7\",\n            \"foobar, 7, 7\",\n            \"8,      0, 8\",\n            \"foobar, 8, 8\",\n            \"9,      0, 9\",\n            \"foobar, 9, 9\",\n    })\n    public void testParseIntDef(String input, int def, int expected) {\n        assert StringUtils.parseIntDef(input, def) == expected;\n    }\n\n\n    /**\n     * Provides test cases for {@link #testEndsWithIgnoreCaseCharArray(String, String, boolean)} and {@link #testEndsWithIgnoreCase(String, String, boolean)}.\n     */\n    private static final Arguments[] endsWithIgnoreCaseStringsData = new Arguments[]{\n            Arguments.of(\"this is a test\", \"a test\", true),\n            Arguments.of(\"this is a test\", \"a TeSt\", true),\n            Arguments.of(\"this is a test\", \"A TEST\", true),\n\n            Arguments.of(\"THIS IS A TEST\", \"a test\", true),\n            Arguments.of(\"THIS IS A TEST\", \"a TeSt\", true),\n            Arguments.of(\"THIS IS A TEST\", \"A TEST\", true),\n\n            Arguments.of(\"ThIs Is A TeSt\", \"a test\", true),\n            Arguments.of(\"ThIs Is A TeSt\", \"a TeSt\", true),\n            Arguments.of(\"ThIs Is A TeSt\", \"A TEST\", true),\n\n            Arguments.of(\"this is a test\", \"this is a test\", true),\n            Arguments.of(\"this is a test\", \"ThIs Is A TeSt\", true),\n            Arguments.of(\"this is a test\", \"THIS IS A TEST\", true),\n\n            Arguments.of(\"THIS IS A TEST\", \"this is a test\", true),\n            Arguments.of(\"THIS IS A TEST\", \"ThIs Is A TeSt\", true),\n            Arguments.of(\"THIS IS A TEST\", \"THIS IS A TEST\", true),\n\n            Arguments.of(\"ThIs Is A TeSt\", \"this is a test\", true),\n            Arguments.of(\"ThIs Is A TeSt\", \"ThIs Is A TeSt\", true),\n            Arguments.of(\"ThIs Is A TeSt\", \"THIS IS A TEST\", true),\n\n            Arguments.of(\"this is a test\", \"\", true),\n\n            Arguments.of(\"this is a test\", \"test a is this\", false),\n            Arguments.of(\"this is a test\", \"tEsT a Is ThIs\", false),\n            Arguments.of(\"this is a test\", \"TEST A IS THIS\", false),\n\n            Arguments.of(\"THIS IS A TEST\", \"test a is this\", false),\n            Arguments.of(\"THIS IS A TEST\", \"tEsT a Is ThIs\", false),\n            Arguments.of(\"THIS IS A TEST\", \"TEST A IS THIS\", false),\n\n            Arguments.of(\"ThIs Is A tEst\", \"test a is this\", false),\n            Arguments.of(\"ThIs Is A tEst\", \"tEsT a Is ThIs\", false),\n            Arguments.of(\"ThIs Is A tEst\", \"TEST A IS THIS\", false)\n    };\n\n\n    public static final Stream<Arguments> endsWithIgnoreCaseStringsSource = Stream.of(endsWithIgnoreCaseStringsData);\n    /**\n     * Test the {@link StringUtils#endsWithIgnoreCase(String, String)} method.\n     * @param a        first string to compare.\n     * @param b        second string to compare.\n     * @param expected expected return value of {@link StringUtils#endsWithIgnoreCase(String, String)}\n     */\n    @ParameterizedTest\n    @VariableSource(\"endsWithIgnoreCaseStringsSource\")\n    public void testEndsWithIgnoreCase(String a, String b, boolean expected) {\n        assert StringUtils.endsWithIgnoreCase(a, b) == expected;\n    }\n\n    public static final Stream<Arguments> endsWithIgnoreCaseStringsSource2 = Stream.of(endsWithIgnoreCaseStringsData);\n    /**\n     * Test the {@link StringUtils#endsWithIgnoreCase(String, char[])} method.\n     * @param a        first string to compare.\n     * @param b        second string to compare.\n     * @param expected expected return value of {@link StringUtils#endsWithIgnoreCase(String, char[])}\n     */\n    @ParameterizedTest\n    @VariableSource(\"endsWithIgnoreCaseStringsSource2\")\n    public void testEndsWithIgnoreCaseCharArray(String a, String b, boolean expected) {\n        assert StringUtils.endsWithIgnoreCase(a, b.toCharArray()) == expected;\n    }\n\n\n\n    /**\n     * Tests {@link StringUtils#startsWithIgnoreCase(String, String)}.\n     * @param a        first string to compare.\n     * @param b        second string to compare.\n     * @param expected expected return value of {@link StringUtils#startsWithIgnoreCase(String, String)}.\n     */\n    @ParameterizedTest\n    @CsvSource({\n            \"this is a test, this is, true\",\n            \"this is a test, ThIs Is, true\",\n            \"this is a test, THIS IS, true\",\n\n            \"THIS IS A TEST, this is, true\",\n            \"THIS IS A TEST, ThIs Is, true\",\n            \"THIS IS A TEST, THIS IS, true\",\n\n            \"ThIs Is a tEsT, this is, true\",\n            \"ThIs Is a tEsT, ThIs Is, true\",\n            \"ThIs Is a tEsT, THIS IS, true\",\n\n            \"this is a test, this is a test, true\",\n            \"this is a test, THIS IS A TEST, true\",\n            \"this is a test, ThIs Is a tEsT, true\",\n\n            \"THIS IS A TEST, this is a test, true\",\n            \"THIS IS A TEST, THIS IS A TEST, true\",\n            \"THIS IS A TEST, ThIs Is a tEsT, true\",\n\n            \"ThIs Is a tEsT, this is a test, true\",\n            \"ThIs Is a tEsT, THIS IS A TEST, true\",\n            \"ThIs Is a tEsT, ThIs Is a tEsT, true\",\n\n            \"ThIs Is a tEsT, '', true\",\n            \"THIS IS A TEST, '', true\",\n            \"ThIs Is a tEsT, '', true\",\n\n            \"this is a test, test a is this, false\",\n            \"this is a test, TEST A IS THIS, false\",\n            \"this is a test, TeSt A iS tHiS, false\",\n\n            \"THIS IS A TEST, test a is this, false\",\n            \"THIS IS A TEST, TEST A IS THIS, false\",\n            \"THIS IS A TEST, TeSt A iS tHiS, false\",\n\n            \"ThIs Is A tEsT, test a is this, false\",\n            \"ThIs Is A tEsT, TEST A IS THIS, false\",\n            \"ThIs Is A tEsT, TeSt A iS tHiS, false\",\n    })\n    public void testStartsWithIgnoreCase(String a, String b, boolean expected) {\n        assert StringUtils.startsWithIgnoreCase(a, b) == expected;\n    }\n\n\n\n    /**\n     * Provides test cases for {@link #testCaseSensitiveEquals(String, String, boolean)}.:\n     */\n    public static final Stream<Arguments> caseSensitiveEqualsSource = Stream.of(\n    );\n\n\n    /**\n     * Tests the {@link StringUtils#equals(String, String, boolean)} method (case insensitive).\n     * @param a        first string to compare.\n     * @param b        second string to compare\n     * @param expected expected return value of {@link StringUtils#equals(String, String, boolean)}\n     */\n    @ParameterizedTest\n    @CsvSource({\n            \"a,  a,  true\",\n            \"A,  A,  true\",\n            \",   ,   true\",\n            \",   a,  false\",\n            \"a,  ,   false\",\n            \"a,  A,  false\",\n            \"A,  a,  false\"\n    })\n    public void testCaseSensitiveEquals(String a, String b, boolean expected) {\n        assert StringUtils.equals(a, b, true) == expected;\n    }\n\n    /**\n     * Tests the {@link StringUtils#equals(String, String, boolean)} method (case sensitive).\n     * @param a        first string to compare.\n     * @param b        second string to compare\n     * @param expected expected return value of {@link StringUtils#equals(String, String, boolean)}\n     */\n    @ParameterizedTest\n    @CsvSource({\n        \"a,  a,  true\",\n        \"A,  A,  true\",\n        \"a,  A,  true\",\n        \"A,  a,  true\",\n        \",   ,   true\",\n        \",   a,  false\",\n        \"a,  ,   false\",\n        \"a,  z,  false\"\n    })\n    public void testCaseInsensitiveEquals(String a, String b, boolean expected) {\n        assert StringUtils.equals(a, b, false) == expected;\n    }\n\n\n\n\n    /**\n     * Tests the {@link StringUtils#capitalize(String)} method.\n     * @param input    string to capitalize.\n     * @param expected expected result of the capitalization.\n     */\n    @ParameterizedTest\n    @CsvSource({\n            \"bob,         Bob\",\n            \"BOB,         Bob\",\n            \"bOB,         Bob\",\n            \"boB,         Bob\",\n            \"Bob,         Bob\",\n            \"Bob Servant, Bob servant\",\n            \"b,           B\",\n            \",            ''\",\n            \",            ''\",\n            \"7,            7\"\n    })\n    public void testCapitalize(String input, String expected) {\n        assert expected.equals(StringUtils.capitalize(input));\n    }\n\n\n\n    /**\n     * Provides test cases for {@link #testFlatten(String, String[], String)}.\n     */\n    public static final Stream<Arguments> flattenSource = Stream.of(\n        Arguments.of(\"a b c\", new String[] {\"a\", \"b\", \"c\"}, \" \"),\n        Arguments.of(\"a*b*c\", new String[] {\"a\", \"b\", \"c\"}, \"*\"),\n        Arguments.of(\"a*c\", new String[] {\"a\", \"\", \"c\"}, \"*\"),\n        Arguments.of(\"a*c\", new String[] {\"a\", null, \"c\"}, \"*\"),\n        Arguments.of(\"b\", new String[] {null, \"b\", null}, \"*\"),\n        Arguments.of(\"\", new String[] {null, null, null}, \"*\")\n);\n\n    /**\n     * Tests {@link StringUtils#flatten(String[], String)}.\n     * @param expected  expected returned value of {@link StringUtils#flatten(String[], String)}.\n     * @param data      data to flatten.\n     * @param separator separator to use when flattening.\n     */\n    @ParameterizedTest\n    @VariableSource(\"flattenSource\")\n    public void testFlatten(String expected, String[] data, String separator) {\n        assert expected.equals(StringUtils.flatten(data, separator));\n        assert !separator.equals(\" \") || expected.equals(StringUtils.flatten(data));\n    }\n\n    /**\n     * Tests {@link StringUtils#flatten(String[], String)}\n     */\n    @Test\n    public void testFlatten() {\n        assert StringUtils.flatten(null, \"*\") == null;\n        assert StringUtils.flatten(null) == null;\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/ui/action/impl/PasteClipboardFilesActionTest.java",
    "content": "package com.mucommander.ui.action.impl;\n\nimport com.mucommander.ui.dnd.ClipboardOperations;\nimport com.mucommander.ui.dnd.ClipboardSupport;\nimport org.junit.jupiter.api.Test;\n\n\n/**\n *\n * @author Kezides\n */\npublic class PasteClipboardFilesActionTest {\n    /**\n     * Test of performAction method, of class PasteClipboardFilesAction.\n     */\n    @Test\n    public void testPerformAction(){\n        //test paste copy operation.\n        ClipboardSupport.setOperation(ClipboardOperations.COPY);\n        \n        assert ClipboardSupport.getOperation() == ClipboardOperations.COPY;\n        \n        //test paste cut operation.\n        ClipboardSupport.setOperation(ClipboardOperations.CUT);\n        \n        assert ClipboardSupport.getOperation() == ClipboardOperations.CUT;\n        \n        //test paste archive operation.\n        ClipboardSupport.setOperation(ClipboardOperations.ARCHIVE);\n        \n        assert ClipboardSupport.getOperation() == ClipboardOperations.ARCHIVE;\n    }\n\n    \n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/ui/dialog/file/BatchRenameTest.java",
    "content": "/*\r\n * This file is part of muCommander, http://www.mucommander.com\r\n * Copyright (C) 2002-2010 Maxence Bernard\r\n *\r\n * muCommander is free software; you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation; either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * muCommander is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\npackage com.mucommander.ui.dialog.file;\r\n\r\nimport com.mucommander.commons.file.AbstractFile;\r\nimport com.mucommander.commons.file.DummyFile;\r\nimport com.mucommander.commons.file.FileFactory;\r\nimport com.mucommander.commons.file.FileURL;\r\nimport com.mucommander.ui.dialog.file.BatchRenameDialog.*;\r\nimport org.junit.jupiter.api.BeforeAll;\r\nimport org.junit.jupiter.api.Test;\r\n\r\nimport java.net.MalformedURLException;\r\nimport java.util.Calendar;\r\n\r\n\r\n/**\r\n * Batch rename dialog test case.\r\n * @author Mariusz Jakubowski\r\n *\r\n */\r\npublic class BatchRenameTest {\r\n\r\n    private static TestFile abcdef;\r\n    private static TestFile abcdef_ghi;\r\n    private static TestFile _abcdef;\r\n    private static TestFile abcdef_ghi_jkl;\r\n\r\n    private static class TestFile extends DummyFile {\r\n        private static long         date;\r\n        private        AbstractFile parent;\r\n\r\n        static {\r\n            Calendar c = Calendar.getInstance();\r\n            c.set(2008, 2, 10, 13, 5, 37);\r\n            date = c.getTimeInMillis();\r\n        }\r\n\r\n        public TestFile(String name, AbstractFile parent) throws MalformedURLException {\r\n            super(FileURL.getFileURL(name));\r\n            this.parent = parent;\r\n        }\r\n\r\n        @Override\r\n        public long getSize() {\r\n            return 0;\r\n        }\r\n\r\n        @Override\r\n        public long getLastModifiedDate() {\r\n            return date;\r\n        }\r\n\r\n        @Override\r\n        public AbstractFile getParent() {\r\n            return parent;\r\n        }\r\n    }\r\n\r\n    @BeforeAll\r\n    protected static void setUp() throws Exception {\r\n        TestFile parent = new TestFile(FileFactory.getTemporaryFolder() + \"parent\", null);\r\n        \r\n        abcdef = new TestFile(FileFactory.getTemporaryFolder() + \"abcdef\", parent);\r\n        abcdef_ghi = new TestFile(FileFactory.getTemporaryFolder() + \"abcdef.ghi\", parent);\r\n        abcdef_ghi_jkl = new TestFile(FileFactory.getTemporaryFolder() + \"abcdef.ghi.jkl\", parent);\r\n        _abcdef = new TestFile(FileFactory.getTemporaryFolder() + \".abcdef\", parent);\r\n    }\r\n\r\n    @Test\r\n    public void testName() {\r\n        NameToken full = new NameToken(\"N\");\r\n        full.parse();\r\n        assert \"abcdef\".equals(full.apply(abcdef));\r\n        assert \"abcdef\".equals(full.apply(abcdef_ghi));\r\n        assert \"abcdef.ghi\".equals(full.apply(abcdef_ghi_jkl));\r\n        assert \"\".equals(full.apply(_abcdef));\r\n\r\n        NameToken sec = new NameToken(\"N2\");\r\n        sec.parse();\r\n        assert \"b\".equals(sec.apply(abcdef));\r\n        assert \"b\".equals(sec.apply(abcdef_ghi));\r\n        assert \"b\".equals(sec.apply(abcdef_ghi_jkl));\r\n        assert \"\".equals(sec.apply(_abcdef));\r\n\r\n        NameToken secback = new NameToken(\"N-2\");\r\n        secback.parse();\r\n        assert \"e\".equals(secback.apply(abcdef));\r\n        assert \"e\".equals(secback.apply(abcdef_ghi));\r\n        assert \"h\".equals(secback.apply(abcdef_ghi_jkl));\r\n        assert \"\".equals(secback.apply(_abcdef));\r\n\r\n\r\n        NameToken secthree = new NameToken(\"N2,3\");\r\n        secthree.parse();\r\n        assert \"bcd\".equals(secthree.apply(abcdef));\r\n        assert \"bcd\".equals(secthree.apply(abcdef_ghi));\r\n        assert \"bcd\".equals(secthree.apply(abcdef_ghi_jkl));\r\n        assert \"\".equals(secthree.apply(_abcdef));\r\n\r\n        NameToken sectofifth = new NameToken(\"N2-5\");\r\n        sectofifth.parse();\r\n        assert \"bcde\".equals(sectofifth.apply(abcdef));\r\n        assert \"bcde\".equals(sectofifth.apply(abcdef_ghi));\r\n        assert \"bcde\".equals(sectofifth.apply(abcdef_ghi_jkl));\r\n        assert \"\".equals(sectofifth.apply(_abcdef));\r\n\r\n        NameToken sectoend = new NameToken(\"N2-\");\r\n        sectoend.parse();\r\n        assert \"bcdef\".equals(sectoend.apply(abcdef));\r\n        assert \"bcdef\".equals(sectoend.apply(abcdef_ghi));\r\n        assert \"bcdef.ghi\".equals(sectoend.apply(abcdef_ghi_jkl));\r\n        assert \"\".equals(sectoend.apply(_abcdef));\r\n\r\n        NameToken secbackthree = new NameToken(\"N-2,3\");\r\n        secbackthree.parse();\r\n        assert \"ef\".equals(secbackthree.apply(abcdef));\r\n        assert \"ef\".equals(secbackthree.apply(abcdef_ghi));\r\n        assert \"hi\".equals(secbackthree.apply(abcdef_ghi_jkl));\r\n        assert \"\".equals(secbackthree.apply(_abcdef));\r\n\r\n        NameToken thirdbacksecback = new NameToken(\"N-3--2\");\r\n        thirdbacksecback.parse();\r\n        assert \"de\".equals(thirdbacksecback.apply(abcdef));\r\n        assert \"de\".equals(thirdbacksecback.apply(abcdef_ghi));\r\n        assert \"gh\".equals(thirdbacksecback.apply(abcdef_ghi_jkl));\r\n        assert \"\".equals(thirdbacksecback.apply(_abcdef));\r\n\r\n        NameToken eightbackfourth = new NameToken(\"N-8-4\");\r\n        eightbackfourth.parse();\r\n        assert \"abcd\".equals(eightbackfourth.apply(abcdef));\r\n        assert \"abcd\".equals(eightbackfourth.apply(abcdef_ghi));\r\n        assert \"cd\".equals(eightbackfourth.apply(abcdef_ghi_jkl));\r\n        assert \"\".equals(eightbackfourth.apply(_abcdef));\r\n\r\n    }\r\n\r\n    @Test\r\n    public void testExt() {\r\n        NameToken full = new ExtToken(\"E\");\r\n        full.parse();\r\n        assert \"\".equals(full.apply(abcdef));\r\n        assert \"ghi\".equals(full.apply(abcdef_ghi));\r\n        assert \"jkl\".equals(full.apply(abcdef_ghi_jkl));\r\n        assert \"abcdef\".equals(full.apply(_abcdef));\r\n    }\r\n\r\n    @Test\r\n    public void testCounter() {\r\n        CounterToken one = new CounterToken(\"C\", 1, 1, 1);\r\n        one.parse();\r\n        assert \"1\".equals(one.apply(abcdef));\r\n        assert \"2\".equals(one.apply(abcdef));\r\n\r\n        CounterToken start5 = new CounterToken(\"C5\", 1, 1, 1);\r\n        start5.parse();\r\n        assert \"5\".equals(start5.apply(abcdef));\r\n        assert \"6\".equals(start5.apply(abcdef));\r\n\r\n        CounterToken step2 = new CounterToken(\"C5,2\", 1, 1, 1);\r\n        step2.parse();\r\n        assert \"5\".equals(step2.apply(abcdef));\r\n        assert \"7\".equals(step2.apply(abcdef));\r\n\r\n        CounterToken digits2 = new CounterToken(\"C9,2,2\", 1, 1, 1);\r\n        digits2.parse();\r\n        assert \"09\".equals(digits2.apply(abcdef));\r\n        assert \"11\".equals(digits2.apply(abcdef));\r\n\r\n        CounterToken digits3def = new CounterToken(\"C,,3\", 10, 5, 1);\r\n        digits3def.parse();\r\n        assert \"010\".equals(digits3def.apply(abcdef));\r\n        assert \"015\".equals(digits3def.apply(abcdef));\r\n\r\n        CounterToken step3def = new CounterToken(\"C,3\", 10, 1, 1);\r\n        step3def.parse();\r\n        assert \"10\".equals(step3def.apply(abcdef));\r\n        assert \"13\".equals(step3def.apply(abcdef));\r\n\r\n    }\r\n\r\n    @Test\r\n    public void testDate() {\r\n        DateToken full = new DateToken(\"YMDhms\");\r\n        full.parse();\r\n        assert \"20080210130537\".equals(full.apply(abcdef));\r\n    }\r\n\r\n    @Test\r\n    public void testParent() {\r\n        ParentDirToken p = new ParentDirToken(\"P\");\r\n        p.parse();\r\n        assert \"parent\".equals(p.apply(abcdef));\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/test/java/com/mucommander/ui/dialog/file/RenamePropertiesTest.java",
    "content": "package com.mucommander.ui.dialog.file;\n\nimport com.mucommander.commons.file.AbstractFile;\nimport com.mucommander.commons.file.FileFactory;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.File;\nimport java.io.IOException;\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class RenamePropertiesTest {\n\n    @Test\n    public void testRenameFile() throws IOException {\n    \tString filename = \"testRenamefile\";\n        String newFilename = \"testNewnamefile\";\n        \n    //creates a temp file on the drive\n        \n        File tmpFile = File.createTempFile(filename, \".tmp\");\n        \n        //creates a file object that muCommander can understand\n        AbstractFile abstractFile = FileFactory.getFile(tmpFile.getAbsolutePath());\n\n        //runs the rename file methode from the PropertiesDialog class\n        PropertiesDialog.renameFile(abstractFile, newFilename + \".tmp\");\n        \n        //creates a standard Java fil object from the muCommander file that was created before, this is the renamed file\n        File newFile = new File(abstractFile.getParent().getAbsolutePath() + newFilename + \".tmp\");\n        //checks if the file exists using the exists() methode. \n        assertTrue(newFile.exists()); \n        // cleans out the file from the drive after the test\n        newFile.delete();\n    }\n\n}"
  },
  {
    "path": "src/test/java/com/mucommander/ui/icon/CustomFileIconProviderTest.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.icon;\n\nimport org.junit.jupiter.api.Test;\n\nimport javax.swing.*;\n\n/**\n * A test case for the custom file icon set.\n *\n * @author Maxence Bernard\n */\npublic class CustomFileIconProviderTest {\n\n    /**\n     * Tests all icons that the custom icon set contains by instanciating them one by one and checking for\n     * <code>null</code> values.\n     */\n    @Test\n    public void testIconsExistence() {\n        testIconExistence(CustomFileIconProvider.FOLDER_ICON_NAME);\n        testIconExistence(CustomFileIconProvider.FILE_ICON_NAME);\n        testIconExistence(CustomFileIconProvider.ARCHIVE_ICON_NAME);\n        testIconExistence(CustomFileIconProvider.PARENT_FOLDER_ICON_NAME);\n        testIconExistence(CustomFileIconProvider.MAC_OS_X_APP_ICON_NAME);\n\n        for(int i=0; i<CustomFileIconProvider.ICON_EXTENSIONS.length; i++)\n            testIconExistence(CustomFileIconProvider.ICON_EXTENSIONS[i][0]);\n    }\n\n\n    /**\n     * Asserts that the {@link IconManager#FILE_ICON_SET custom file icon set} contains a non-null value for the icon\n     * designated by the given filename.\n     *\n     * @param iconName filename of the icon to test\n     */\n    private void testIconExistence(String iconName) {\n        assert getIcon(iconName) != null;\n    }\n\n    /**\n     * Retreives the icon designated by the given filename from the {@link IconManager#FILE_ICON_SET custom file icon set}\n     * and returns it.\n     *\n     * @param iconName filename of the icon to retrieve\n     * @return Retreives the icon designated by the given filename and returns it\n     */\n    private Icon getIcon(String iconName) {\n        return IconManager.getIcon(IconManager.IconSet.FILE, iconName);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/ui/macosx/AppleScriptTest.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.ui.macosx;\n\nimport com.mucommander.commons.runtime.OsFamily;\nimport com.mucommander.commons.util.StringUtils;\nimport org.junit.jupiter.api.Test;\nimport static org.junit.jupiter.api.Assertions.*;\n\nimport java.util.Locale;\n\n/**\n * A test case for {@link com.mucommander.ui.macosx.AppleScript}.\n *\n * @author Maxence Bernard\n */\npublic class AppleScriptTest {\n\n    /**\n     * Tests a simple AppleScript that outputs something to stdout.\n     */\n    @Test\n    public void testScriptOutput() {\n        StringBuilder output = new StringBuilder();\n        boolean success = AppleScript.execute(\"count {\\\"How\\\", \\\"many\\\", \\\"items\\\", \\\"in\\\", \\\"this\\\", \\\"list\\\"}\", output);\n\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            // Assert that the script was executed successfully and that the output matches what is expected\n            assert success;\n            assertEquals(\"6\" ,output.toString());\n        } else {\n            // We're not running Mac OS X, assert that execute returns false\n            assert !success;\n        }\n    }\n\n    /**\n     * Verifies that AppleScript allows extended characters in the script and that it outputs them properly, using\n     * either <i>Unicode</i> or <i>MacRoman</i> depending on the\n     * {@link com.mucommander.ui.macosx.AppleScript#getScriptEncoding() current AppleScript encoding}.\n     */\n    @Test\n    public void testScriptEncoding() {\n        StringBuilder output = new StringBuilder();\n\n        String nonAsciiString;\n        Locale stringLocale;        // for locale-aware String comparison\n\n        if (AppleScript.getScriptEncoding().equals(AppleScript.UTF8)) {      // Under AppleScript 2.0 and up\n            nonAsciiString = \"どうもありがとうミスターロボット\";\n            stringLocale = Locale.JAPANESE;\n        } else {                                                              // MacRoman under AppleScript 1.10 and lower\n            // This String must only contain MacRoman characters\n            nonAsciiString = \"mércî mr röbôt\";\n            stringLocale = Locale.FRENCH;\n        }\n\n\n        boolean success = AppleScript.execute(\"do shell script \\\"echo \" + nonAsciiString + \"\\\"\", output);\n\n        if (OsFamily.MAC_OS_X.isCurrent()) {\n            // Assert that the script was executed successfully and that we got the same text as the one we passed\n            assertTrue(success);\n            if (!StringUtils.equals(nonAsciiString, output.toString(), stringLocale)) {\n                System.err.println(\"nonAsciiString='\" + nonAsciiString + \"'\");\n                System.err.println(\"output='\" + output + \"'\");\n                System.err.println(\"stringLocale='\" + stringLocale + \"'\");\n            }\n            assertTrue(StringUtils.equals(nonAsciiString, output.toString(), stringLocale));\n        } else {\n            // We're not running Mac OS X, assert that execute returns false\n            assertFalse(success);\n        }\n    }\n\n    /**\n     * Tests a bogus script and asserts that it fails to be executed.\n     */\n    @Test\n    public void testScriptError() {\n        // Should fail under all platforms\n        assertFalse(AppleScript.execute(\"blah\", new StringBuilder()));\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/utils/text/SizeFormatTest.java",
    "content": "/*\n * This file is part of muCommander, http://www.mucommander.com\n * Copyright (C) 2002-2010 Maxence Bernard\n *\n * muCommander is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * muCommander is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.mucommander.utils.text;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.text.DecimalFormat;\nimport java.text.NumberFormat;\nimport java.text.ParsePosition;\n\n/**\n * A test case for {@link SizeFormat}.\n *\n * @author Maxence Bernard\n */\npublic class SizeFormatTest {\n\n    private final static long KB_1 = (long)Math.pow(2, 10);\n    private final static long MB_1 = (long)Math.pow(2, 20);\n    private final static long GB_1 = (long)Math.pow(2, 30);\n    private final static long TB_1 = (long)Math.pow(2, 40);\n\n    private final static long[] UNITS = { KB_1, MB_1, GB_1, TB_1 };\n\n    private final static DecimalFormat DECIMAL_FORMAT = (DecimalFormat)NumberFormat.getInstance();\n\n    private final static String DECIMAL_SEPARATOR = \"\"+DECIMAL_FORMAT.getDecimalFormatSymbols().getDecimalSeparator();\n\n    static {\n        // SizeFormat uses localized strings\n        Translator.init();\n    }\n\n    /**\n     * Tests strings returned by {@link SizeFormat#format(long, int)} with the {@link SizeFormat#DIGITS_SHORT} option.\n     */\n    @Test\n    public void testDigitsShort() {\n        testDigitsShort(\"1\", 1);\n        testDigitsShort(\"9\", 9);\n        testDigitsShort(\"10\", 10);\n        testDigitsShort(\"11\", 11);\n        testDigitsShort(\"99\", 99);\n        testDigitsShort(\"100\", 100);\n        testDigitsShort(\"101\", 101);\n        testDigitsShort(\"999\", 999);\n        testDigitsShort(\"1023\", 1023);\n\n        for (long unit : UNITS) {\n            testDigitsShort(\"1\", unit);\n            testDigitsShort(\"1\", unit + 1);\n            testDigitsShort(\"9\", unit * 9);\n            testDigitsShort(\"9\", unit * 10 - 1);\n            testDigitsShort(\"10\", unit * 10);\n            testDigitsShort(\"10\", unit * 10 + 1);\n            testDigitsShort(\"11\", unit * 11);\n            testDigitsShort(\"99\", unit * 99);\n            testDigitsShort(\"99\", unit * 100 - 1);\n            testDigitsShort(\"100\", unit * 100);\n            testDigitsShort(\"100\", unit * 100 + 1);\n            testDigitsShort(\"101\", unit * 101);\n            testDigitsShort(\"999\", unit * 999);\n            testDigitsShort(\"999\", unit * 1000 - 1);\n        }\n    }\n\n    private void testDigitsShort(String expected, long size) {\n        assert expected.equals(SizeFormat.format(size, SizeFormat.DIGITS_SHORT | SizeFormat.UNIT_NONE));\n    }\n\n    /**\n     * Tests strings returned by {@link SizeFormat#format(long, int)} with the {@link SizeFormat#DIGITS_MEDIUM} option.\n     */\n    @Test\n    public void testDigitsMedium() {\n        testDigitsMedium(\"1\", 1);\n        testDigitsMedium(\"9\", 9);\n        testDigitsMedium(\"10\", 10);\n        testDigitsMedium(\"11\", 11);\n        testDigitsMedium(\"99\", 99);\n        testDigitsMedium(\"100\", 100);\n        testDigitsMedium(\"101\", 101);\n        testDigitsMedium(\"999\", 999);\n        testDigitsMedium(\"1023\", 1023);\n\n        for (long unit : UNITS) {\n            testDigitsMedium(\"1\" + DECIMAL_SEPARATOR + \"0\", unit);\n            testDigitsMedium(\"1\" + DECIMAL_SEPARATOR + \"0\", unit + 1);\n            testDigitsMedium(\"9\" + DECIMAL_SEPARATOR + \"0\", unit * 9);\n            testDigitsMedium(\"9\" + DECIMAL_SEPARATOR + \"9\", unit * 10 - 1);\n            testDigitsMedium(\"10\", unit * 10);\n            testDigitsMedium(\"10\", unit * 10 + 1);\n            testDigitsMedium(\"11\", unit * 11);\n            testDigitsMedium(\"99\", unit * 99);\n            testDigitsMedium(\"99\", unit * 100 - 1);\n            testDigitsMedium(\"100\", unit * 100);\n            testDigitsMedium(\"100\", unit * 100 + 1);\n            testDigitsMedium(\"101\", unit * 101);\n            testDigitsMedium(\"999\", unit * 999);\n            testDigitsMedium(\"999\", unit * 1000 - 1);\n        }\n    }\n\n    private void testDigitsMedium(String expected, long size) {\n        assert expected.equals(SizeFormat.format(size, SizeFormat.DIGITS_MEDIUM | SizeFormat.UNIT_NONE));\n    }\n\n    /**\n     * Tests strings returned by {@link SizeFormat#format(long, int)} with the {@link SizeFormat#DIGITS_FULL} option.\n     */\n    @Test\n    public void testDigitsFull() {\n        testDigitsFull(1);\n        testDigitsFull(9);\n        testDigitsFull(10);\n        testDigitsFull(11);\n        testDigitsFull(99);\n        testDigitsFull(100);\n        testDigitsFull(101);\n        testDigitsFull(999);\n        testDigitsFull(1023);\n\n        for (long unit : UNITS) {\n            testDigitsFull(unit);\n            testDigitsFull(unit + 1);\n            testDigitsFull(unit * 9);\n            testDigitsFull(unit * 10 - 1);\n            testDigitsFull(unit * 10);\n            testDigitsFull(unit * 10 + 1);\n            testDigitsFull(unit * 11);\n            testDigitsFull(unit * 99);\n            testDigitsFull(unit * 100 - 1);\n            testDigitsFull(unit * 100);\n            testDigitsFull(unit * 100 + 1);\n            testDigitsFull(unit * 101);\n            testDigitsFull(unit * 999);\n            testDigitsFull(unit * 1000 - 1);\n        }\n    }\n\n    private void testDigitsFull(long size) {\n        assert size == DECIMAL_FORMAT.parse(SizeFormat.format(size, SizeFormat.DIGITS_FULL | SizeFormat.UNIT_NONE), new ParsePosition(0)).longValue();\n    }\n\n    /**\n     * Tests strings returned by {@link SizeFormat#format(long, int)} with the {@link SizeFormat#ROUND_TO_KB} option\n     * combined successively with {@link SizeFormat#DIGITS_SHORT}, {@link SizeFormat#DIGITS_MEDIUM} and\n     * {@link SizeFormat#DIGITS_FULL}.\n     */\n    @Test\n    public void testRoundToKb() {\n        testRoundToKb(SizeFormat.DIGITS_SHORT);\n        testRoundToKb(SizeFormat.DIGITS_MEDIUM);\n        testRoundToKb(SizeFormat.DIGITS_FULL);\n    }\n\n    private void testRoundToKb(int digitFormat) {\n        assert \"0\".equals(SizeFormat.format(0, digitFormat | SizeFormat.ROUND_TO_KB | SizeFormat.UNIT_NONE));\n        testRoundToKb(1, digitFormat);\n        testRoundToKb(9, digitFormat);\n        testRoundToKb(10, digitFormat);\n        testRoundToKb(11, digitFormat);\n        testRoundToKb(99, digitFormat);\n        testRoundToKb(100, digitFormat);\n        testRoundToKb(101, digitFormat);\n        testRoundToKb(999, digitFormat);\n        testRoundToKb(1023, digitFormat);\n    }\n\n    private void testRoundToKb(long size, int digitFormat) {\n        assert \"1\".equals(SizeFormat.format(size, digitFormat | SizeFormat.ROUND_TO_KB | SizeFormat.UNIT_NONE));\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/utils/xml/XmlAttributesTest.java",
    "content": "package com.mucommander.utils.xml;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport java.util.Iterator;\n\n/**\n * Runs test on the {@link XmlAttributes} class.\n * @author Nicolas Rinaudo\n */\npublic class XmlAttributesTest {\n    // - Test constants ------------------------------------------------------------------------------------------------\n    // -----------------------------------------------------------------------------------------------------------------\n    /** Name of the first test attribute. */\n    private static final String TEST_ATTRIBUTE_1 = \"attribute1\";\n    /** Name of the second test attribute. */\n    private static final String TEST_ATTRIBUTE_2 = \"attribute2\";\n    /** First value of the test attribute. */\n    private static final String TEST_VALUE_1     = \"value1\";\n    /** Second value of the test attribute. */\n    private static final String TEST_VALUE_2     = \"value2\";\n\n    /** Instance used to test the XmlAttributes class. */\n    private XmlAttributes attributes;\n\n\n\n    /**\n     * Initializes the test case.\n     */\n    @BeforeEach\n    public void setUp() {\n        attributes = new XmlAttributes();\n    }\n\n\n    /**\n     * Runs the basic tests.\n     */\n    @Test\n    public void testAttributes() {\n        // Makes sure an attribute that has been added is properly retrieved.\n        attributes.add(TEST_ATTRIBUTE_1, TEST_VALUE_1);\n        assert TEST_VALUE_1.equals(attributes.getValue(TEST_ATTRIBUTE_1));\n\n        // Makes sure an attribute that has been overwritten is properly retrieved.\n        attributes.add(TEST_ATTRIBUTE_1, TEST_VALUE_2);\n        assert TEST_VALUE_2.equals(attributes.getValue(TEST_ATTRIBUTE_1));\n\n        // Makes sure the clear method works.\n        attributes.clear();\n        assert attributes.getValue(TEST_ATTRIBUTE_1) == null;\n    }\n\n    /**\n     * Runs tests on the {@link XmlAttributes#names()} method.\n     */\n    @Test\n    public void testNames() {\n        Iterator<String> names;\n        String   buffer;\n\n        // Makes sure the names method works on an empty set of attributes.\n        names = attributes.names();\n        assert !names.hasNext();\n\n        // Makes sure the names method works on a set of attributes that only contains\n        // one element.\n        attributes.add(TEST_ATTRIBUTE_1, TEST_VALUE_1);\n        names = attributes.names();\n        assert names.hasNext();\n        buffer = names.next();\n        assert TEST_ATTRIBUTE_1.equals(buffer);\n        assert TEST_VALUE_1.equals(attributes.getValue(buffer));\n        assert !names.hasNext();\n\n        // Makes sure the names method works on a set of attributes that contains more\n        // than one element.\n        attributes.add(TEST_ATTRIBUTE_2, TEST_VALUE_2);\n        names = attributes.names();\n        assert names.hasNext();\n        checkAttribute(names.next());\n        assert names.hasNext();\n        checkAttribute(names.next());\n        assert !names.hasNext();\n\n        // Makes sure the iterator is read-only.\n        names = attributes.names();\n        try {\n            names.remove();\n            throw new AssertionError();\n        } catch(Exception ignore) {}\n    }\n\n    /**\n     * Makes sure the specified attribute name has the right value.\n     */\n    private void checkAttribute(String name) {\n        switch (name) {\n            case TEST_ATTRIBUTE_1:\n                assert TEST_VALUE_1.equals(attributes.getValue(TEST_ATTRIBUTE_1));\n                break;\n            case TEST_ATTRIBUTE_2:\n                assert TEST_VALUE_2.equals(attributes.getValue(TEST_ATTRIBUTE_2));\n                break;\n            default:\n                throw new AssertionError();\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/mucommander/utils/xml/XmlWriterTest.java",
    "content": "package com.mucommander.utils.xml;\n\nimport org.junit.jupiter.api.Test;\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Element;\nimport org.w3c.dom.Node;\n\nimport javax.xml.parsers.DocumentBuilderFactory;\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\n\n/**\n * Runs test on the {@link XmlWriter} class.\n * @author Nicolas Rinaudo\n */\npublic class XmlWriterTest {\n    private static final String ROOT_ELEMENT       = \"root\";\n    private static final String ENTITIES_ATTRIBUTE = \"entities\";\n    private static final String ENTITIES_STRING    = \"&\\\"'<>\";\n\n\n\n    // - XML reading -----------------------------------------------------\n    // -------------------------------------------------------------------\n    /**\n     * Reads the content of the specified byte array in a DOM Document.\n     * @param  bytes       content of the XML document.\n     * @return             the content of the specified byte array in a DOM Document.\n     * @throws Exception if any error occurs.\n     */\n    private Document getDocument(byte[] bytes) throws Exception {\n        return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(bytes));\n    }\n\n\n\n    // - JUnit tests -----------------------------------------------------\n    // -------------------------------------------------------------------\n    /**\n     * Makes sure that XML entities are escaped properly.\n     * @throws IOException if an error occurs.\n     */\n    @Test\n    public void testXmlEntities() throws Exception {\n        XmlWriter             writer;\n        XmlAttributes         attributes;\n        ByteArrayOutputStream out;\n        Node                  node;\n        Element               element;\n\n        // Creates an XML document with CDATA and attributes that need escaping.\n        writer     = new XmlWriter(out = new ByteArrayOutputStream());\n        attributes = new XmlAttributes();\n        attributes.add(ENTITIES_ATTRIBUTE, ENTITIES_STRING);\n        writer.startElement(ROOT_ELEMENT, attributes);\n        writer.writeCData(ENTITIES_STRING);\n        writer.endElement(ROOT_ELEMENT);\n        writer.close();\n\n        // Reads the XML stream.\n        element = getDocument(out.toByteArray()).getDocumentElement();\n\n        // Makes sure the entities were properly escaped in the XML attribute.\n        assert element.hasAttribute(ENTITIES_ATTRIBUTE);\n        assert element.getAttribute(ENTITIES_ATTRIBUTE).equals(ENTITIES_STRING);\n\n        // Looks for the CDATA.\n        node = element.getFirstChild();\n        while(node != null && node.getNodeType() != Node.TEXT_NODE)\n            node = node.getNextSibling();\n\n        // Makes sure we found the CDATA and that it is equal to ENTITIES_STRING.\n        assert node != null;\n        assert node.getNodeValue().equals(ENTITIES_STRING);\n    }\n}\n"
  },
  {
    "path": "src/test/java/ru/trolsoft/hexeditor/search/ByteBufferSearchUtilsTest.java",
    "content": "package ru.trolsoft.hexeditor.search;\n\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport ru.trolsoft.hexeditor.data.AbstractByteBuffer;\n\nimport java.io.IOException;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass ByteBufferSearchUtilsTest {\n\n    static class ByteBuf extends AbstractByteBuffer {\n        private final byte[] bytes;\n\n\n        ByteBuf(byte[] bytes) {\n            super(bytes.length);\n            this.bytes = bytes;\n        }\n\n        @Override\n        protected void closeStream() {}\n\n        @Override\n        protected long getStreamSize() {\n            return bytes.length;\n        }\n\n//        @Override\n//        public long getFileSize() throws IOException {\n//            return bytes.length;\n//        }\n\n        @Override\n        protected void loadBuffer() {\n        }\n\n        @Override\n        protected boolean supportRandomAccess() {\n            return true;\n        }\n\n        @Override\n        public byte getByte(long fileOffset) {\n            return bytes[(int) fileOffset];\n        }\n    }\n\n\n    @Nested\n    class IndexOfValidation {\n\n        @Test\n        void returnsMinusOne_whenDataIsNull() throws IOException {\n            assertEquals(-1, ByteBufferSearchUtils.indexOf(null, new byte[]{1}, 0));\n        }\n\n        @Test\n        void returnsMinusOne_whenPatternIsNull() throws IOException {\n            var buf = new ByteBuf(new byte[]{1});\n            assertEquals(-1, ByteBufferSearchUtils.indexOf(buf, (byte[])null, 0));\n        }\n\n        @Test\n        void returnsMinusOne_whenFromOffsetNegative() throws IOException {\n            var buf = new ByteBuf(new byte[]{1});\n            assertEquals(-1, ByteBufferSearchUtils.indexOf(buf, new byte[]{1}, -1));\n        }\n\n        @Test\n        void returnsMinusOne_whenPatternLongerThanFile() throws IOException {\n            var buf = new ByteBuf(new byte[]{1,2,3,4,5});\n            assertEquals(-1, ByteBufferSearchUtils.indexOf(buf, new byte[10], 0));\n        }\n\n        @Test\n        void returnsMinusOne_whenEmptyPattern() throws IOException {\n            var buf = new ByteBuf(new byte[]{1,2,3,4,5,6,7,8,9,10});\n            assertEquals(-1, ByteBufferSearchUtils.indexOf(buf, new byte[0], 0));\n        }\n    }\n\n    @Nested\n    class IndexOfMatches {\n\n        @Test\n        void findsPatternAtBeginning() throws IOException {\n            byte[] data = {1, 2, 3, 4, 5};\n            byte[] pattern = {1, 2, 3};\n            var buf = new ByteBuf(data);\n\n            long result = ByteBufferSearchUtils.indexOf(buf, pattern, 0);\n            assertEquals(0, result);\n        }\n\n        @Test\n        void findsPatternInMiddle() throws IOException {\n            byte[] data = {0, 0, 1, 2, 3, 0, 0};\n            byte[] pattern = {1, 2, 3};\n            var buf = new ByteBuf(data);\n\n            long result = ByteBufferSearchUtils.indexOf(buf, pattern, 0);\n            assertEquals(2, result);\n        }\n\n        @Test\n        void findsPatternAtEnd() throws IOException {\n            byte[] data = {0, 0, 1, 2, 3};\n            byte[] pattern = {1, 2, 3};\n\n            var buf = new ByteBuf(data);\n\n            long result = ByteBufferSearchUtils.indexOf(buf, pattern, 0);\n            assertEquals(2, result);\n        }\n\n        @Test\n        void returnsMinusOne_whenPatternNotFound() throws IOException {\n            byte[] data = {1, 2, 3, 4, 5};\n            byte[] pattern = {6, 7, 8};\n            var buf = new ByteBuf(data);\n\n            long result = ByteBufferSearchUtils.indexOf(buf, pattern, 0);\n            assertEquals(-1, result);\n        }\n\n        @Test\n        void findsPattern_withOverlappingKMP() throws IOException {\n            // Тест на корректность KMP: паттерн \"ABA\" в \"ABABA\"\n            byte[] data = {1, 2, 1, 2, 1};\n            byte[] pattern = {1, 2, 1};\n            var buf = new ByteBuf(data);\n\n            long result = ByteBufferSearchUtils.indexOf(buf, pattern, 0);\n            assertEquals(0, result); // Первое вхождение\n        }\n\n        @Test\n        void respectsFromOffset() throws IOException {\n            byte[] data = {1, 2, 3, 1, 2, 3};\n            byte[] pattern = {1, 2, 3};\n            var buf = new ByteBuf(data);\n\n            // Начинаем поиск с позиции 3 — должно найти второе вхождение\n            long result = ByteBufferSearchUtils.indexOf(buf, pattern, 3);\n            assertEquals(3, result);\n        }\n\n        @Test\n        void fromOffsetBeyondFileSize_normalized() throws IOException {\n            byte[] data = {1, 2, 3};\n            byte[] pattern = {1, 2, 3};\n            var buf = new ByteBuf(data);\n\n            // fromOffset = 100, но fileSize = 3 → должно нормализоваться и найти паттерн\n            long result = ByteBufferSearchUtils.indexOfBackward(buf, pattern, 100);\n            assertEquals(0, result);\n        }\n    }\n\n//    @Nested\n//    class IndexOfCacheStrategy {\n//\n//        @Test\n//        void restoresCacheStrategy_afterSearch() throws IOException {\n//            byte[] data = {1, 2, 3};\n//            byte[] pattern = {2};\n//            doAnswer(invocation -> data[Math.toIntExact(invocation.getArgument(0))])\n//                    .when(mockBuffer).getByte(anyLong());\n//\n//            ByteBufferSearchUtils.indexOf(mockBuffer, pattern, 0);\n//\n//            // Проверяем, что стратегия восстановлена в finally\n//            verify(mockBuffer).setCacheStrategy(AbstractByteBuffer.CacheStrategy.BACKWARD);\n//        }\n//\n//        @Test\n//        void restoresCacheStrategy_onException() throws IOException {\n//            byte[] pattern = {1};\n//            when(mockBuffer.getFileSize()).thenReturn(10L);\n//            when(mockBuffer.getCacheStrategy()).thenReturn(AbstractByteBuffer.CacheStrategy.FORWARD);\n//            when(mockBuffer.getByte(0L)).thenThrow(new IOException(\"Test exception\"));\n//\n//            assertThrows(IOException.class, () ->\n//                    ByteBufferSearchUtils.indexOf(mockBuffer, pattern, 0));\n//\n//            // Даже при исключении стратегия должна восстановиться\n//            verify(mockBuffer, atLeastOnce()).setCacheStrategy(AbstractByteBuffer.CacheStrategy.FORWARD);\n//        }\n//    }\n\n\n    @Nested\n    @DisplayName(\"indexOfBackward: Поиск в обратном направлении\")\n    class IndexOfBackwardTests {\n\n        @Test\n        void findsPatternAtEnd() throws IOException {\n            byte[] data = {0, 0, 1, 2, 3};\n            byte[] pattern = {1, 2, 3};\n            var buf = new ByteBuf(data);\n\n            long result = ByteBufferSearchUtils.indexOfBackward(buf, pattern, 4);\n            assertEquals(2, result);\n        }\n\n        @Test\n        void findsPatternAtBeginning() throws IOException {\n            byte[] data = {1, 2, 3, 0, 0};\n            byte[] pattern = {1, 2, 3};\n            var buf = new ByteBuf(data);\n\n            long result = ByteBufferSearchUtils.indexOfBackward(buf, pattern, 4);\n            assertEquals(0, result);\n        }\n\n        @Test\n        void findsLastOccurrence_whenMultipleMatches() throws IOException {\n            byte[] data = {1, 2, 3, 0, 1, 2, 3};\n            byte[] pattern = {1, 2, 3};\n            var buf = new ByteBuf(data);\n\n            // Ищем с конца — должно найти последнее вхождение (индекс 4)\n            long result = ByteBufferSearchUtils.indexOfBackward(buf, pattern, 6);\n            assertEquals(4, result);\n        }\n\n        @Test\n        void returnsMinusOne_whenPatternNotFound() throws IOException {\n            byte[] data = {1, 2, 3};\n            byte[] pattern = {4, 5, 6};\n            var buf = new ByteBuf(data);\n\n            long result = ByteBufferSearchUtils.indexOfBackward(buf, pattern, 2);\n            assertEquals(-1, result);\n        }\n\n//        @Test\n//        void restoresCacheStrategy_afterBackwardSearch() throws IOException {\n//            byte[] data = {1, 2, 3};\n//            byte[] pattern = {2};\n//            when(mockBuffer.getFileSize()).thenReturn(3L);\n//            when(mockBuffer.getCacheStrategy()).thenReturn(AbstractByteBuffer.CacheStrategy.FORWARD);\n//            doAnswer(invocation -> data[Math.toIntExact(invocation.getArgument(0))])\n//                    .when(mockBuffer).getByte(anyLong());\n//\n//            ByteBufferSearchUtils.indexOfBackward(mockBuffer, pattern, 2);\n//\n//            verify(mockBuffer).setCacheStrategy(AbstractByteBuffer.CacheStrategy.FORWARD);\n//        }\n    }\n\n    @Nested\n    @DisplayName(\"indexOf(byte[], byte[]): Поиск в обычных массивах\")\n    class IndexOfArrayTests {\n\n        @Test\n        void findsPattern_simple() {\n            byte[] data = {0, 1, 2, 3, 4};\n            byte[] pattern = {2, 3};\n            assertEquals(2, ByteBufferSearchUtils.indexOf(data, pattern));\n        }\n\n        @Test\n        void returnsMinusOne_whenPatternLongerThanData() {\n            byte[] data = {1, 2};\n            byte[] pattern = {1, 2, 3};\n            assertEquals(-1, ByteBufferSearchUtils.indexOf(data, pattern));\n        }\n\n        @Test\n        void returnsMinusOne_whenNullPattern() {\n            byte[] data = {1, 2, 3};\n            assertEquals(-1, ByteBufferSearchUtils.indexOf(data, null));\n        }\n\n        @Test\n        void handlesOverlappingPatterns_KMP() {\n            // Паттерн \"AA\" в \"AAA\" — KMP должен найти первое вхождение на индексе 0\n            byte[] data = {1, 1, 1};\n            byte[] pattern = {1, 1};\n            assertEquals(0, ByteBufferSearchUtils.indexOf(data, pattern));\n        }\n\n        @Test\n        void emptyData_returnsMinusOne() {\n            byte[] data = {};\n            byte[] pattern = {1};\n            assertEquals(-1, ByteBufferSearchUtils.indexOf(data, pattern));\n        }\n    }\n\n//    private void setupBuffer(byte[] data, long startOffset, long endOffset) throws IOException {\n//        reset(mockBuffer);\n//\n//        when(mockBuffer.getFileSize()).thenReturn((long) data.length);\n//        when(mockBuffer.getCacheStrategy()).thenReturn(AbstractByteBuffer.CacheStrategy.BACKWARD);\n//\n//        doAnswer(invocation -> {\n//            long index = invocation.getArgument(0);\n//            if (index >= 0 && index < data.length) {\n//                return data[Math.toIntExact(index)];\n//            }\n//            throw new AssertionError(\"getByte called with invalid index: \" + index + \", data.length: \" + data.length);\n//        }).when(mockBuffer).getByte(anyLong());\n//    }\n\n    @Nested\n    @DisplayName(\"indexOf(byte[][], ...): Поиск множества паттернов\")\n    class IndexOfMultiplePatterns {\n\n        @Test\n        void findsEarliestPattern() throws IOException {\n            byte[] data = {0, 1, 2, 3, 4, 5};\n            byte[][] patterns = {\n                    {3, 4, 5},  // найдётся на индексе 3\n                    {1, 2}      // найдётся на индексе 1 ← раньше\n            };\n            var buf = new ByteBuf(data);\n\n            long result = ByteBufferSearchUtils.indexOf(buf, patterns, 0);\n            assertEquals(1, result);\n        }\n\n        @Test\n        void returnsMinusOne_whenNoPatternsMatch() throws IOException {\n            byte[] data = {1, 2, 3};\n            byte[][] patterns = {{4, 5}, {6, 7}};\n            var buf = new ByteBuf(data);\n\n            long result = ByteBufferSearchUtils.indexOf(buf, patterns, 0);\n            assertEquals(-1, result);\n        }\n\n        @Test\n        void skipsNullAndEmptyPatterns() throws IOException {\n            byte[] data = {1, 2, 3};\n            byte[][] patterns = {null, {}, {2, 3}}; // null и пустой должны игнорироваться\n            var buf = new ByteBuf(data);\n\n            long result = ByteBufferSearchUtils.indexOf(buf, patterns, 0);\n            assertEquals(1, result);\n        }\n\n//        @Test\n//        void restoresCacheStrategy_onMultiplePatterns() throws IOException {\n//            byte[] data = {1, 2, 3};\n//            byte[][] patterns = {{1}, {2}, {3}};\n//            when(mockBuffer.getFileSize())\n//                    .thenReturn(3L);\n//            when(mockBuffer.getCacheStrategy())\n//                    .thenReturn(AbstractByteBuffer.CacheStrategy.FORWARD);\n//            doAnswer(invocation -> data[Math.toIntExact(invocation.getArgument(0))])\n//                    .when(mockBuffer).getByte(anyLong());\n//\n//            ByteBufferSearchUtils.indexOf(mockBuffer, patterns, 0);\n//\n//            verify(mockBuffer, atLeastOnce()).setCacheStrategy(AbstractByteBuffer.CacheStrategy.FORWARD);\n//        }\n\n        @Test\n        void handlesFromOffset_correctly() throws IOException {\n            byte[] data = {1, 2, 3, 1, 2, 3};\n            byte[][] patterns = {{1, 2, 3}};\n            var buf = new ByteBuf(data);\n\n            // Поиск с позиции 3 — должно найти второе вхождение\n            long result = ByteBufferSearchUtils.indexOf(buf, patterns, 3);\n            assertEquals(3, result);\n        }\n    }\n}"
  },
  {
    "path": "src/test/java/ru/trolsoft/test/VariableArgumentsProvider.java",
    "content": "package ru.trolsoft.test;\n\nimport org.jspecify.annotations.NullMarked;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsProvider;\nimport org.junit.jupiter.params.support.AnnotationConsumer;\nimport org.junit.jupiter.params.support.ParameterDeclarations;\n\nimport java.lang.reflect.Field;\nimport java.util.stream.Stream;\n\npublic class VariableArgumentsProvider implements ArgumentsProvider, AnnotationConsumer<VariableSource> {\n\n    private String variableName;\n\n    @Override\n    @NullMarked\n    public Stream<? extends Arguments> provideArguments(ParameterDeclarations parameters, ExtensionContext context) {\n        return context.getTestClass()\n                .map(this::getField)\n                .map(this::getValue)\n                .orElseThrow(() ->\n                        new IllegalArgumentException(\"Failed to load test arguments\"));\n    }\n\n    @Override\n    public void accept(VariableSource variableSource) {\n        variableName = variableSource.value();\n    }\n\n    private Field getField(Class<?> clazz) {\n        try {\n            return clazz.getDeclaredField(variableName);\n        } catch (Exception e) {\n            return null;\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private Stream<Arguments> getValue(Field field) {\n        Object value = null;\n        try {\n            value = field.get(null);\n        } catch (Exception ignored) {\n        }\n\n        return value == null ? null : (Stream<Arguments>) value;\n    }\n}"
  },
  {
    "path": "src/test/java/ru/trolsoft/test/VariableSource.java",
    "content": "package ru.trolsoft.test;\n\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\nimport java.lang.annotation.*;\n\n@Documented\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@ArgumentsSource(VariableArgumentsProvider.class)\npublic @interface VariableSource {\n\n    /**\n     * The name of the static variable\n     */\n    String value();\n}"
  },
  {
    "path": "src/test/kotlin/com/mucommander/ui/viewer/image/SvgImageSupportTest.kt",
    "content": "package com.mucommander.ui.viewer.image\n\nimport io.kotest.matchers.ints.shouldBeAtLeast\nimport io.kotest.matchers.nulls.shouldNotBeNull\nimport io.kotest.matchers.shouldBe\nimport org.apache.batik.transcoder.Transcoder\nimport org.apache.batik.transcoder.TranscoderInput\nimport org.apache.batik.transcoder.TranscoderOutput\nimport org.apache.batik.transcoder.image.PNGTranscoder\nimport org.junit.jupiter.api.Test\nimport java.io.ByteArrayOutputStream\nimport java.nio.ByteBuffer\n\nclass SvgImageSupportTes {\n    @Test\n    fun `SVG image loader`() {\n\n        val width = 400f\n        val height = 400f\n        // create a PNG transcoder.\n        val t: Transcoder = PNGTranscoder().apply {\n            addTranscodingHint(PNGTranscoder.KEY_XML_PARSER_VALIDATING, false)\n            addTranscodingHint(PNGTranscoder.KEY_WIDTH, width)\n            addTranscodingHint(PNGTranscoder.KEY_HEIGHT, height)\n        }\n        val svg = javaClass.classLoader.getResourceAsStream(\"images/circle.svg\")\n        svg.shouldNotBeNull()\n\n        val out = ByteArrayOutputStream()\n        val input = TranscoderInput(svg)\n        val output = TranscoderOutput(out)\n        t.transcode(input, output)\n        out.size() shouldBeAtLeast 11379\n        with(ByteBuffer.wrap(out.toByteArray())) {\n            getByte() shouldBe 0x89\n            getByte() shouldBe 'P'.toInt()\n            getByte() shouldBe 'N'.toInt()\n            getByte() shouldBe 'G'.toInt()\n            getByte() shouldBe 0x0d\n            getByte() shouldBe 0x0a\n            getByte() shouldBe 0x1a\n            getByte() shouldBe 0x0a\n            getInt()\n            getInt()\n            getInt() shouldBe width\n            getInt() shouldBe height\n        }\n\n    }\n\n    private fun ByteBuffer.getByte() = get().toInt() and 0xFF\n}"
  },
  {
    "path": "src/test/kotlin/ru/trolsoft/calculator/CalculatorTest.kt",
    "content": "package ru.trolsoft.calculator\n\nimport io.kotest.matchers.shouldBe\nimport org.junit.jupiter.api.Test\n\nclass CalculatorTest {\n\n    @Test\n    fun arithmetic() {\n        with (Calculator()) {\n            calculate(\"1+ 1\") shouldBe 2.0\n            calculate(\"10/2\") shouldBe 5.0\n            calculate(\"10 % 3\") shouldBe 1.0\n            calculate(\"2 ^ 3\") shouldBe 8.0\n            calculate(\"(1+3)*4\") shouldBe 16.0\n            calculate(\"(1+3)*3/2.5\") shouldBe 4.8\n        }\n    }\n\n    @Test\n    fun formats() {\n        with(Calculator()) {\n            calculate(\"0x20\") shouldBe 32.0\n            calculate(\"0b111\") shouldBe 7.0\n        }\n    }\n\n    @Test\n    fun logic() {\n        with(Calculator()) {\n            calculate(\"1<<3\") shouldBe 8\n            calculate(\"16 >> 2\") shouldBe 4\n            calculate(\"~0b1010\") shouldBe 0b11110101\n            calculate(\"0b11000 | 0b11\") shouldBe 27\n            calculate(\"0b11111 & 0b101\") shouldBe 5\n            calculate(\"0b1010 ^^ 0b111\") shouldBe 13\n        }\n    }\n}"
  },
  {
    "path": "src/test/resources/logback.xml",
    "content": "<configuration debug=\"false\">\n    <logger name=\"com.mucommander.commons.file.DefaultSchemeParser\" level=\"ERROR\" />\n</configuration>"
  },
  {
    "path": "src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker",
    "content": "mock-maker-subclass"
  }
]